Thanks to visit codestin.com
Credit goes to github.com

Skip to content

use ContainerConfiguration object for LocalstackContainer #8771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jul 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 63 additions & 70 deletions localstack/utils/bootstrap.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import copy
import functools
import logging
import os
Expand All @@ -7,17 +8,17 @@
import threading
import time
from functools import wraps
from typing import Dict, Iterable, List, Optional, Set
from typing import Dict, Iterable, Optional, Set

from localstack import config, constants
from localstack.config import get_edge_port_http, is_env_true
from localstack.constants import DEFAULT_VOLUME_DIR
from localstack.runtime import hooks
from localstack.utils.container_networking import get_main_container_name
from localstack.utils.container_utils.container_client import (
ContainerConfiguration,
ContainerException,
PortMappings,
SimpleVolumeBind,
VolumeBind,
VolumeMappings,
)
Expand Down Expand Up @@ -365,82 +366,47 @@ def extract_port_flags(user_flags, port_mappings: PortMappings):
return user_flags


# TODO merge with docker_utils.py:ContainerConfiguration
class LocalstackContainer:
name: str
image_name: str
volumes: VolumeMappings
ports: PortMappings
entrypoint: str
additional_flags: List[str]
command: List[str]

privileged: bool = True
remove: bool = True
interactive: bool = False
tty: bool = False
detach: bool = False
inherit_env: bool = True

logfile: Optional[str] = None
stdin: Optional[str] = None
user: Optional[str] = None
cap_add: Optional[List[str]] = None
network: Optional[str] = None
dns: Optional[str] = None
workdir: Optional[str] = None
config: ContainerConfiguration

def __init__(self, name: str = None):
self.name = name or config.MAIN_CONTAINER_NAME
self.entrypoint = os.environ.get("ENTRYPOINT", "")
self.command = shlex.split(os.environ.get("CMD", ""))
self.image_name = get_docker_image_to_start()
self.ports = PortMappings(bind_host=config.EDGE_BIND_HOST)
self.volumes = VolumeMappings()
self.env_vars = {}
self.additional_flags = []

self.logfile = os.path.join(config.dirs.tmp, f"{self.name}_container.log")

def _get_mount_volumes(self) -> List[SimpleVolumeBind]:
# FIXME: VolumeMappings should be supported by the docker client
mount_volumes = []
for volume in self.volumes:
if isinstance(volume, tuple):
mount_volumes.append(volume)
elif isinstance(volume, VolumeBind):
mount_volumes.append((volume.host_dir, volume.container_dir))
else:
raise NotImplementedError("no support for volume type %s" % type(volume))

return mount_volumes
self.config = self._get_default_configuration(name)
self.logfile = os.path.join(config.dirs.tmp, f"{self.config.name}_container.log")

self.additional_flags = [] # TODO: see comment in run()

def _get_default_configuration(self, name: str = None) -> ContainerConfiguration:
"""Returns a ContainerConfiguration populated with default values or values gathered from the
environment for starting the LocalStack container."""
return ContainerConfiguration(
image_name=get_docker_image_to_start(),
name=name or config.MAIN_CONTAINER_NAME,
volumes=VolumeMappings(),
remove=True,
# FIXME: update with https://github.com/localstack/localstack/pull/7991
ports=PortMappings(bind_host=config.EDGE_BIND_HOST),
entrypoint=os.environ.get("ENTRYPOINT"),
command=shlex.split(os.environ.get("CMD", "")) or None,
env_vars={},
)

def run(self):
if isinstance(DOCKER_CLIENT, CmdDockerClient):
DOCKER_CLIENT.default_run_outfile = self.logfile

# FIXME: this is pretty awkward, but additional_flags in the LocalstackContainer API was always a
# list of ["-e FOO=BAR", ...], whereas in the DockerClient it is expected to be a string. so we
# need to re-assemble it here. the better way would be to not use additional_flags here all
# together. it is still used in ext in `configure_pro_container` which could be refactored to use
# the additional port bindings.
cfg = copy.deepcopy(self.config)
if not cfg.additional_flags:
cfg.additional_flags = ""
if self.additional_flags:
cfg.additional_flags += " " + " ".join(self.additional_flags)

try:
return DOCKER_CLIENT.run_container(
image_name=self.image_name,
stdin=self.stdin,
name=self.name,
entrypoint=self.entrypoint or None,
remove=self.remove,
interactive=self.interactive,
tty=self.tty,
detach=self.detach,
command=self.command or None,
mount_volumes=self._get_mount_volumes(),
ports=self.ports,
env_vars=self.env_vars,
user=self.user,
cap_add=self.cap_add,
network=self.network,
dns=self.dns,
additional_flags=" ".join(self.additional_flags),
workdir=self.workdir,
privileged=self.privileged,
)
return DOCKER_CLIENT.run_container_from_config(cfg)
except ContainerException as e:
if LOG.isEnabledFor(logging.DEBUG):
LOG.exception("Error while starting LocalStack container")
Expand All @@ -454,6 +420,33 @@ def truncate_log(self):
with open(self.logfile, "wb") as fd:
fd.write(b"")

# these properties are there to not break code that configures the container by using these values
# that code should ideally be refactored soon-ish to use the config instead.

@property
def env_vars(self) -> Dict[str, str]:
return self.config.env_vars

@property
def entrypoint(self) -> Optional[str]:
return self.config.entrypoint

@entrypoint.setter
def entrypoint(self, value: str):
self.config.entrypoint = value

@property
def name(self) -> str:
return self.config.name

@property
def volumes(self) -> VolumeMappings:
return self.config.volumes

@property
def ports(self) -> PortMappings:
return self.config.ports


class LocalstackContainerServer(Server):
container: LocalstackContainer
Expand Down Expand Up @@ -638,7 +631,7 @@ def start_infra_in_docker_detached(console):
console.log("configuring container")
container = LocalstackContainer()
configure_container(container)
container.detach = True
container.config.detach = True
container.truncate_log()

# start the Localstack container as a Server
Expand Down
37 changes: 34 additions & 3 deletions localstack/utils/container_utils/container_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
if sys.version_info >= (3, 8):
from typing import Literal, Protocol, get_args
else:
from typing_extensions import Protocol, get_args, Literal
from typing_extensions import Literal, Protocol, get_args

from localstack import config
from localstack.utils.collections import HashableList, ensure_list
Expand Down Expand Up @@ -409,7 +409,7 @@ class ContainerConfiguration:
volumes: Optional[VolumeMappings] = None
ports: Optional[PortMappings] = None
entrypoint: Optional[str] = None
additional_flags: Optional[List[str]] = None
additional_flags: Optional[str] = None
command: Optional[List[str]] = None
env_vars: Dict[str, str] = dataclasses.field(default_factory=dict)

Expand Down Expand Up @@ -836,7 +836,7 @@ def run_container(
tty: bool = False,
detach: bool = False,
command: Optional[Union[List[str], str]] = None,
mount_volumes: Optional[List[SimpleVolumeBind]] = None,
mount_volumes: Optional[Union[VolumeMappings, List[SimpleVolumeBind]]] = None,
ports: Optional[PortMappings] = None,
env_vars: Optional[Dict[str, str]] = None,
user: Optional[str] = None,
Expand All @@ -856,6 +856,37 @@ def run_container(
:return: A tuple (stdout, stderr)
"""

def run_container_from_config(
self, container_config: ContainerConfiguration
) -> Tuple[bytes, bytes]:
"""Like ``run_container`` but uses the parameters from the configuration."""

return self.run_container(
image_name=container_config.image_name,
stdin=container_config.stdin,
name=container_config.name,
entrypoint=container_config.entrypoint,
remove=container_config.remove,
interactive=container_config.interactive,
tty=container_config.tty,
detach=container_config.detach,
command=container_config.command,
mount_volumes=container_config.volumes,
ports=container_config.ports,
env_vars=container_config.env_vars,
user=container_config.user,
cap_add=container_config.cap_add,
cap_drop=container_config.cap_drop,
security_opt=container_config.security_opt,
network=container_config.network,
dns=container_config.dns,
additional_flags=container_config.additional_flags,
workdir=container_config.workdir,
platform=container_config.platform,
privileged=container_config.privileged,
ulimits=container_config.ulimits,
)

@abstractmethod
def exec_in_container(
self,
Expand Down