def buildah_common_inspect_to_metadata(metadata_object, inspect_data): """ :param metadata_object: instance of conu.Metadata object :param inspect_data: dict with the inspect metadata """ ociv1 = inspect_data.get("OCIv1") if not ociv1: raise ConuException( "inspect metadata are invalid: don't have OCIv1 section") raw_env_vars = graceful_get(ociv1, "config", "Env") or [] if raw_env_vars: metadata_object.env_variables = {} for env_variable in raw_env_vars: splits = env_variable.split("=", 1) name = splits[0] value = splits[1] if len(splits) > 1 else None if value is not None: metadata_object.env_variables.update({name: value}) metadata_object.labels = graceful_get(ociv1, "config", "Labels", default={}) metadata_object.command = graceful_get(ociv1, 'config', 'Cmd') metadata_object.creation_timestamp = graceful_get(ociv1, "created")
def push(self, repository=None, tag=None): """ Push image to registry. Raise exception when push fail. :param repository: str, see constructor :param tag: str, see constructor :return: None """ image = self if repository or tag: image = self.tag_image(repository, tag) for json_e in self.d.push(repository=image.name, tag=image.tag, stream=True, decode=True): logger.debug(json_e) status = graceful_get(json_e, "status") if status: logger.info(status) else: error = graceful_get(json_e, "error") if error is not None: logger.error(status) raise ConuException("There was an error while pushing the image %s: %s", self.name, error) return image
def buildah_image_inspect_to_metadata(inspect_data): """ process data from `buildah inspect -t image` and return ImageMetadata :param inspect_data: dict, metadata from `buildah inspect -t image` :return: instance of ImageMetadata """ im = ImageMetadata() im.name = graceful_get(inspect_data, "FromImage") im.identifier = graceful_get(inspect_data, "FromImageID") buildah_common_inspect_to_metadata(im, inspect_data) return im
def buildah_container_inspect_to_metadata(inspect_data): """ process data from `buildah inspect -t container` and return ContainerMetadata :param inspect_data: dict, metadata from `buildah inspect -t container` :return: instance of ContainerMetadata """ cm = ContainerMetadata() cm.name = graceful_get(inspect_data, 'Container') cm.identifier = graceful_get(inspect_data, 'ContainerID') buildah_common_inspect_to_metadata(cm, inspect_data) return cm
def exit_code(self): """ get exit code of container. Return value is 0 for running and created containers :return: int """ return graceful_get(self.inspect(refresh=True), "State", "ExitCode")
def get_status(self): """ Get status of container :return: one of: 'created', 'restarting', 'running', 'paused', 'exited', 'dead' """ return graceful_get(self.inspect(refresh=True), "State", "Status")
def get_image_name(self): """ return name of the container image :return: str """ return graceful_get(self.inspect(refresh=False), "FromImage")
def get_name(self): """ Returns name of the container :return: str """ self.name = self.name or graceful_get(self.inspect(refresh=False), "Container") return self.name
def inspect_to_metadata(metadata_object, inspect_data): """ process data from `podman inspect` and update provided metadata object :param metadata_object: instance of Metadata :param inspect_data: dict, metadata from `podman inspect` :return: instance of Metadata """ identifier = graceful_get(inspect_data, 'Id') or graceful_get( inspect_data, 'ID') if identifier: if ":" in identifier: # if format of image name from podman inspect: # sha256:8f0e66c924c0c169352de487a3c2463d82da24e9442fc097dddaa5f800df7129 metadata_object.identifier = identifier.split(':')[1] else: # container metadata_object.identifier = identifier # format of Environment Variables from podman inspect: # ['DISTTAG=f26container', 'FGC=f26'] raw_env_vars = graceful_get(inspect_data, "Config", "Env") or [] if raw_env_vars: metadata_object.env_variables = {} for env_variable in raw_env_vars: splits = env_variable.split("=", 1) name = splits[0] value = splits[1] if len(splits) > 1 else None if value is not None: metadata_object.env_variables.update({name: value}) raw_exposed_ports = graceful_get(inspect_data, "NetworkSettings", "Ports") if raw_exposed_ports: metadata_object.exposed_ports = list( set([d["containerPort"] for d in raw_exposed_ports])) # specific to images if "RepoTags" in inspect_data: repo_tags = graceful_get(inspect_data, 'RepoTags') # podman sets it to None if the image is not tagged if not repo_tags: repo_tags = [None] metadata_object.name = repo_tags[0] metadata_object.image_names = repo_tags else: metadata_object.name = graceful_get(inspect_data, 'ImageName') metadata_object.labels = graceful_get(inspect_data, 'Config', 'Labels') metadata_object.command = graceful_get(inspect_data, 'Config', 'Cmd') metadata_object.creation_timestamp = inspect_data.get('Created', None) # specific to images digests = inspect_data.get("RepoDigests", None) if digests: metadata_object.repo_digests = digests metadata_object.digest = digests[0] return metadata_object
def pull(self): """ Pull this image from registry. Raises an exception if the image is not found in the registry. :return: None """ for json_e in self.d.pull(repository=self.name, tag=self.tag, stream=True, decode=True): logger.debug(json_e) status = graceful_get(json_e, "status") if status: logger.info(status) else: error = graceful_get(json_e, "error") logger.error(status) raise ConuException("There was an error while pulling the image %s: %s", self.name, error) self.using_transport(SkopeoTransport.DOCKER_DAEMON)
def pull(self): """ Pull this image from registry. Raises an exception if the image is not found in the registry. :return: None """ for json_s in self.d.pull(repository=self.name, tag=self.tag, stream=True): logger.debug(json_s) json_e = json.loads(json_s) status = graceful_get(json_e, "status") if status: logger.info(status) else: error = graceful_get(json_e, "error") logger.error(status) raise ConuException("There was an error while pulling the image %s: %s", self.name, error)
def get_id(self): """ get unique identifier of this container :return: str """ if self._id is None: self._id = graceful_get(self.inspect(refresh=False), "ContainerID") return self._id
def get_id(self): """ get unique identifier of this image :return: str """ if self._id is None: self._id = graceful_get(self.inspect(refresh=False), "Id") return self._id
def get_metadata(self): """ Convert dictionary returned after podman inspect command into instance of ContainerMetadata class :return: ContainerMetadata, container metadata instance """ if self._metadata is None: inspect_data = self.inspect(refresh=True) self._metadata = buildah_container_inspect_to_metadata(inspect_data) # this is a hack to avoid circular imports: feel free to fix it if self.ImageClass: image_id = graceful_get(inspect_data, "FromImageID") image_name = graceful_get(inspect_data, "FromImage") if image_name: image_repo, tag = parse_reference(image_name) else: image_repo, tag = None, None self._metadata.image = self.ImageClass(image_repo, tag=tag, identifier=image_id) return self._metadata
def is_running(self): """ returns True if the container is running :return: bool """ try: return graceful_get(self.inspect(refresh=True), "State", "Running") except subprocess.CalledProcessError: return False
def get_ports(self): """ get ports specified in container metadata :return: list of str """ ports = [] container_ports = graceful_get(self.inspect(refresh=True), "NetworkSettings", "Ports") if not container_ports: return ports for p in container_ports: # TODO: gracefullness, error handling ports.append(p.split("/")[0]) return ports
def inspect_to_metadata(metadata_object, inspect_data): """ process data from `docker inspect` and update provided metadata object :param metadata_object: instance of Metadata :param inspect_data: dict, metadata from `docker inspect` or `dockert_client.images()` :return: instance of Metadata """ identifier = graceful_get(inspect_data, 'Id') if identifier: if ":" in identifier: # format of image name from docker inspect: # sha256:8f0e66c924c0c169352de487a3c2463d82da24e9442fc097dddaa5f800df7129 metadata_object.identifier = identifier.split(':')[1] else: # container metadata_object.identifier = identifier # format of Environment Variables from docker inspect: # ['DISTTAG=f26container', 'FGC=f26'] raw_env_vars = graceful_get(inspect_data, "Config", "Env") or [] if raw_env_vars: metadata_object.env_variables = {} for env_variable in raw_env_vars: splits = env_variable.split("=", 1) name = splits[0] value = splits[1] if len(splits) > 1 else None if value is not None: metadata_object.env_variables.update({name: value}) raw_exposed_ports = graceful_get(inspect_data, "Config", "ExposedPorts") if raw_exposed_ports: metadata_object.exposed_ports = list(raw_exposed_ports.keys()) # specific to images raw_repo_tags = graceful_get(inspect_data, 'RepoTags') if raw_repo_tags: metadata_object.name = raw_repo_tags[0] metadata_object.labels = graceful_get(inspect_data, 'Config', 'Labels') metadata_object.command = graceful_get(inspect_data, 'Config', 'Cmd') metadata_object.creation_timestamp = inspect_data.get('Created', None) # specific to images metadata_object.image_names = inspect_data.get('RepoTags', None) # specific to images digests = inspect_data.get("RepoDigests", None) if digests: metadata_object.repo_digests = digests metadata_object.digest = digests[0] return metadata_object
def get_port_mappings(self, port=None): """ Get list of port mappings between container and host. The format of dicts is: {"HostIp": XX, "HostPort": YY}; When port is None - return all port mappings. The container needs to be running, otherwise this returns an empty list. :param port: int or None, container port :return: list of dict or None; dict when port=None """ port_mappings = graceful_get(self.inspect(refresh=True), "NetworkSettings", "Ports") if not port: return port_mappings if str(port) not in self.get_ports(): return [] for p in port_mappings: if p.split("/")[0] == str(port): return port_mappings[p]
def run_via_binary_in_foreground(self, run_command_instance=None, command=None, volumes=None, additional_opts=None, popen_params=None, container_name=None): """ Create a container using this image and run it in foreground; this method is useful to test real user scenarios when users invoke containers using binary and pass input into the container via STDIN. You are also responsible for: * redirecting STDIN when intending to use container.write_to_stdin afterwards by setting popen_params={"stdin": subprocess.PIPE} during run_via_binary_in_foreground * checking whether the container exited successfully via: container.popen_instance.returncode Please consult the documentation for subprocess python module for best practices on how you should work with instance of Popen :param run_command_instance: instance of PodmanRunBuilder :param command: list of str, command to run in the container, examples: - ["ls", "/"] - ["bash", "-c", "ls / | grep bin"] :param volumes: tuple or list of tuples in the form: * `("/path/to/directory", )` * `("/host/path", "/container/path")` * `("/host/path", "/container/path", "mode")` * `(conu.Directory('/host/path'), "/container/path")` (source can be also Directory instance) :param additional_opts: list of str, additional options for `docker run` :param popen_params: dict, keyword arguments passed to Popen constructor :param container_name: str, pretty container identifier :return: instance of PodmanContainer """ logger.info("run container via binary in foreground") if (command is not None or additional_opts is not None) \ and run_command_instance is not None: raise ConuException( "run_command_instance and command parameters cannot be " "passed into method at same time") if run_command_instance is None: command = command or [] additional_opts = additional_opts or [] if (isinstance(command, list) or isinstance(command, tuple) and isinstance(additional_opts, list) or isinstance(additional_opts, tuple)): run_command_instance = PodmanRunBuilder( command=command, additional_opts=additional_opts) else: raise ConuException( "command and additional_opts needs to be list of str or None" ) else: run_command_instance = run_command_instance or PodmanRunBuilder() if not isinstance(run_command_instance, PodmanRunBuilder): raise ConuException("run_command_instance needs to be an " "instance of PodmanRunBuilder") popen_params = popen_params or {} run_command_instance.image_name = self.get_id() if container_name: run_command_instance.options += ["--name", container_name] if volumes: run_command_instance.options += self.get_volume_options( volumes=volumes) def callback(): return subprocess.Popen(run_command_instance.build(), **popen_params) container_id, popen_instance = self._run_container( run_command_instance, callback) actual_name = graceful_get(self._inspect(container_id), "Name") if container_name and container_name != actual_name: raise ConuException( "Unexpected container name value. Expected = " + str(container_name) + " Actual = " + str(actual_name)) if not container_name: container_name = actual_name return PodmanContainer(self, container_id, popen_instance=popen_instance, name=container_name)
def run_via_binary(self, run_command_instance=None, command=None, volumes=None, additional_opts=None, **kwargs): """ create a container using this image and run it in background; this method is useful to test real user scenarios when users invoke containers using binary :param run_command_instance: instance of PodmanRunBuilder :param command: list of str, command to run in the container, examples: - ["ls", "/"] - ["bash", "-c", "ls / | grep bin"] :param volumes: tuple or list of tuples in the form: * `("/path/to/directory", )` * `("/host/path", "/container/path")` * `("/host/path", "/container/path", "mode")` * `(conu.Directory('/host/path'), "/container/path")` (source can be also Directory instance) :param additional_opts: list of str, additional options for `podman run` :return: instance of PodmanContainer """ logger.info("run container via binary in background") if (command is not None or additional_opts is not None) \ and run_command_instance is not None: raise ConuException( "run_command_instance and command parameters cannot be passed " "into method at same time") if run_command_instance is None: command = command or [] additional_opts = additional_opts or [] if (isinstance(command, list) or isinstance(command, tuple) and isinstance(additional_opts, list) or isinstance(additional_opts, tuple)): run_command_instance = PodmanRunBuilder( command=command, additional_opts=additional_opts) else: raise ConuException( "command and additional_opts needs to be list of str or None" ) else: run_command_instance = run_command_instance or PodmanRunBuilder() if not isinstance(run_command_instance, PodmanRunBuilder): raise ConuException( "run_command_instance needs to be an instance of PodmanRunBuilder" ) run_command_instance.image_name = self.get_id() run_command_instance.options += ["-d"] if volumes: run_command_instance.options += self.get_volume_options( volumes=volumes) def callback(): try: # FIXME: catch std{out,err}, print stdout to logger.debug, stderr to logger.error run_cmd(run_command_instance.build()) except subprocess.CalledProcessError as ex: raise ConuException("Container exited with an error: %s" % ex.returncode) container_id, _ = self._run_container(run_command_instance, callback) container_name = graceful_get(self._inspect(container_id), "Name") return PodmanContainer(self, container_id, name=container_name)
def inspect_to_container_metadata(c_metadata_object, inspect_data, image_instance): """ process data from `podman container inspect` and update provided container metadata object :param c_metadata_object: instance of ContainerMetadata :param inspect_data: dict, metadata from `podman inspect` :param image_instance: instance of PodmanImage :return: instance of ContainerMetadata """ inspect_to_metadata(c_metadata_object, inspect_data) # FIXME: Rename this function to be universal? status = ContainerStatus.get_from_docker( graceful_get(inspect_data, "State", "Status"), graceful_get(inspect_data, "State", "ExitCode"), ) image_id = graceful_get(inspect_data, "Image") if image_id: if ":" in image_id: # format of image name from podman inspect: # sha256:8f0e66c924c0c169352de487a3c2463d82da24e9442fc097dddaa5f800df7129 image_instance.identifier = image_id.split(':')[1] else: # container image_instance.identifier = image_id # format of Port mappings from docker inspect: # {'12345/tcp': [ # {'HostIp': '0.0.0.0', 'HostPort': '123'}, # {'HostIp': '0.0.0.0', 'HostPort': '1234'}]} port_mappings = dict() raw_port_mappings = graceful_get(inspect_data, 'HostConfig', 'PortBindings') or {} for key, value in raw_port_mappings.items(): for item in value: logger.debug("parsing ports: key = %s, item = %s", key, item) li = port_mappings.get(key, []) raw_host_port = item['HostPort'] if raw_host_port == "": int_port = None else: try: int_port = int(raw_host_port) except ValueError as ex: logger.error("could not parse port: %s", ex) continue li.append(int_port) port_mappings.update({key: li}) raw_port_mappings = graceful_get(inspect_data, "NetworkSettings", "Ports") c_metadata_object.port_mappings = { d["containerPort"]: [ p["hostPort"] for p in raw_port_mappings if p["containerPort"] == d["containerPort"] ] for d in raw_port_mappings } c_metadata_object.status = status c_metadata_object.hostname = graceful_get(inspect_data, 'Config', 'Hostname') # TODO: Make it work not only with one IP address c_metadata_object.ipv4_addresses = [ graceful_get(inspect_data, "NetworkSettings", "IPAddress") ] c_metadata_object.ipv6_addresses = [ graceful_get(inspect_data, "NetworkSettings", "GlobalIPv6Address") ] c_metadata_object.image = image_instance name = graceful_get(inspect_data, "Name") if name: name = name[1:] if name.startswith( "/") else name # remove / at the beginning c_metadata_object.name = name return c_metadata_object
def test_graceful_get(): assert graceful_get({"a": [{1: 2}, {"b": "c"}]}, "a", 1, "b") == "c"