"""Models for manipulating containers and storage.""" from __future__ import absolute_import import collections import getpass import json import logging import signal import time from . import fold_keys from ._containers_attach import Mixin as AttachMixin from ._containers_start import Mixin as StartMixin class Container(AttachMixin, StartMixin, collections.UserDict): """Model for a container.""" def __init__(self, client, ident, data, refresh=True): """Construct Container Model.""" super(Container, self).__init__(data) self._client = client self._id = ident if refresh: with client() as podman: self._refresh(podman) else: for k, v in self.data.items(): setattr(self, k, v) if 'containerrunning' in self.data: setattr(self, 'running', self.data['containerrunning']) self.data['running'] = self.data['containerrunning'] assert self._id == data['id'],\ 'Requested container id({}) does not match store id({})'.format( self._id, data['id'] ) def _refresh(self, podman, tries=1): try: ctnr = podman.GetContainer(self._id) except BrokenPipeError: logging.debug('Failed GetContainer(%s) try %d/3', self._id, tries) if tries > 3: raise else: with self._client() as pman: self._refresh(pman, tries + 1) else: super().update(ctnr['container']) for k, v in self.data.items(): setattr(self, k, v) if 'containerrunning' in self.data: setattr(self, 'running', self.data['containerrunning']) self.data['running'] = self.data['containerrunning'] return self def refresh(self): """Refresh status fields for this container.""" with self._client() as podman: return self._refresh(podman) def processes(self): """Show processes running in container.""" with self._client() as podman: results = podman.ListContainerProcesses(self._id) yield from results['container'] def changes(self): """Retrieve container changes.""" with self._client() as podman: results = podman.ListContainerChanges(self._id) return results['container'] def kill(self, sig=signal.SIGTERM, wait=25): """Send signal to container. default signal is signal.SIGTERM. wait n of seconds, 0 waits forever. """ with self._client() as podman: podman.KillContainer(self._id, sig) timeout = time.time() + wait while True: self._refresh(podman) if self.status != 'running': # pylint: disable=no-member return self if wait and timeout < time.time(): raise TimeoutError() time.sleep(0.5) def inspect(self): """Retrieve details about containers.""" with self._client() as podman: results = podman.InspectContainer(self._id) obj = json.loads(results['container'], object_hook=fold_keys()) return collections.namedtuple('ContainerInspect', obj.keys())(**obj) def export(self, target): """Export container from store to tarball. TODO: should there be a compress option, like images? """ with self._client() as podman: results = podman.ExportContainer(self._id, target) return results['tarfile'] def commit(self, image_name, **kwargs): """Create image from container. Keyword arguments: author -- change image's author message -- change image's message, docker format only. pause -- pause container during commit change -- Additional properties to change Change examples: CMD=/usr/bin/zsh ENTRYPOINT=/bin/sh date ENV=TEST=test_containers.TestContainers.test_commit EXPOSE=8888/tcp LABEL=unittest=test_commit USER=bozo:circus VOLUME=/data WORKDIR=/data/application All changes overwrite existing values. See inspect() to obtain current settings. """ author = kwargs.get('author', None) or getpass.getuser() change = kwargs.get('change', None) or [] message = kwargs.get('message', None) or '' pause = kwargs.get('pause', None) or True for c in change: if c.startswith('LABEL=') and c.count('=') < 2: raise ValueError( 'LABEL should have the format: LABEL=label=value, not {}'. format(c)) with self._client() as podman: results = podman.Commit(self._id, image_name, change, author, message, pause) return results['reply']['id'] def stop(self, timeout=25): """Stop container, return id on success.""" with self._client() as podman: podman.StopContainer(self._id, timeout) return self._refresh(podman) def remove(self, force=False): """Remove container, return id on success. force=True, stop running container. """ with self._client() as podman: results = podman.RemoveContainer(self._id, force) return results['container'] def restart(self, timeout=25): """Restart container with timeout, return id on success.""" with self._client() as podman: podman.RestartContainer(self._id, timeout) return self._refresh(podman) def pause(self): """Pause container, return id on success.""" with self._client() as podman: podman.PauseContainer(self._id) return self._refresh(podman) def unpause(self): """Unpause container, return id on success.""" with self._client() as podman: podman.UnpauseContainer(self._id) return self._refresh(podman) def update_container(self, *args, **kwargs): \ # pylint: disable=unused-argument """TODO: Update container..., return id on success.""" with self._client() as podman: podman.UpdateContainer() return self._refresh(podman) def wait(self): """Wait for container to finish, return 'returncode'.""" with self._client() as podman: results = podman.WaitContainer(self._id) return int(results['exitcode']) def stats(self): """Retrieve resource stats from the container.""" with self._client() as podman: results = podman.GetContainerStats(self._id) obj = results['container'] return collections.namedtuple('StatDetail', obj.keys())(**obj) def logs(self, *args, **kwargs): # pylint: disable=unused-argument """Retrieve container logs.""" with self._client() as podman: results = podman.GetContainerLogs(self._id) yield from results['container'] class Containers(): """Model for Containers collection.""" def __init__(self, client): """Construct model for Containers collection.""" self._client = client def list(self): """List of containers in the container store.""" with self._client() as podman: results = podman.ListContainers() for cntr in results['containers']: yield Container(self._client, cntr['id'], cntr, refresh=False) def delete_stopped(self): """Delete all stopped containers.""" with self._client() as podman: results = podman.DeleteStoppedContainers() return results['containers'] def get(self, id_): """Retrieve container details from store.""" with self._client() as podman: cntr = podman.GetContainer(id_) return Container(self._client, cntr['container']['id'], cntr['container'])