def cleanup(config_ids, managed_by, cont_cmd='podman', default_runtime=None, log_level=None, log_file=None): """Delete containers no longer applied, rename others to preferred name. :param list config_ids: List of config IDs still applied. All containers managed by this tool will be deleted if their config ID is not specified in this list. :param str managed_by: Name of the tool managing the containers. Only containers labelled with this will be modified. :param str cont_cmd: Optional override to the container command to run. :param str default_runtime: (deprecated) does nothing. :param int log_level: optional log level for loggers :param int log_file: optional log file for messages """ log = common.configure_logging(__name__, log_level, log_file) if default_runtime: log.warning("DEPRECATION: 'default_runtime' does nothing, " "use 'cont_cmd' instead") if cont_cmd == 'podman': r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log) log.warning("paunch cleanup is partially supported with podman") else: r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log) r.delete_missing_configs(config_ids) r.rename_containers()
def delete(config_ids, managed_by, cont_cmd='podman', default_runtime=None, log_level=None, log_file=None): """Delete containers with the specified config IDs. :param list config_ids: List of config IDs to delete the containers for. :param str managed_by: Name of the tool managing the containers. Only containers labelled with this will be modified. :param str cont_cmd: Optional override to the container command to run. :param str default_runtime: (deprecated) does nothing. """ log = common.configure_logging(__name__, log_level, log_file) if default_runtime: log.warning("DEPRECATION: 'default_runtime' does nothing, " "use 'cont_cmd' instead") if not config_ids: log.warn('No config IDs specified') if cont_cmd == 'podman': r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log) log.warning("paunch cleanup is partially supported with podman") else: r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log) for conf_id in config_ids: r.remove_containers(conf_id)
def list(managed_by, cont_cmd='podman', default_runtime=None, log_level=None, log_file=None): """List all containers associated with all config IDs. :param str managed_by: Name of the tool managing the containers. Only containers labelled with this will be modified. :param str cont_cmd: Optional override to the container command to run. :param str default_runtime: (deprecated) does nothing. :param int log_level: optional log level for loggers :param int log_file: optional log file for messages :returns a dict where the key is the config ID and the value is a list of 'podman inspect' dicts for each container. :rtype: defaultdict(list) """ log = common.configure_logging(__name__, log_level, log_file) if default_runtime: log.warning("DEPRECATION: 'default_runtime' does nothing, " "use 'cont_cmd' instead") if cont_cmd == 'podman': r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log) else: r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log) return r.list_configs()
def apply(config_id, config, managed_by, labels=None, docker_cmd=None): """Execute supplied container configuration. :param str config_id: Unique config ID, should not be re-used until any running containers with that config ID have been deleted. :param dict config: Configuration data describing container actions to apply. :param str managed_by: Name of the tool managing the containers. Only containers labelled with this will be modified. :param dict labels: Optional keys/values of labels to apply to containers created with this invocation. :param str docker_cmd: Optional override to the docker command to run. :returns (list, list, int) lists of stdout and stderr for each execution, and a single return code representing the overall success of the apply. :rtype: tuple """ r = runner.DockerRunner(managed_by, docker_cmd=docker_cmd) builder = compose1.ComposeV1Builder(config_id=config_id, config=config, runner=r, labels=labels) return builder.apply()
def apply(config_id, config, managed_by, labels=None, cont_cmd='podman', default_runtime=None, log_level=None, log_file=None, cont_log_path=None, healthcheck_disabled=False): """Execute supplied container configuration. :param str config_id: Unique config ID, should not be re-used until any running containers with that config ID have been deleted. :param dict config: Configuration data describing container actions to apply. :param str managed_by: Name of the tool managing the containers. Only containers labelled with this will be modified. :param dict labels: Optional keys/values of labels to apply to containers created with this invocation. :param str cont_cmd: Optional override to the container command to run. :param str default_runtime: (deprecated) does nothing. :param int log_level: optional log level for loggers :param str log_file: optional log file for messages :param str cont_log_path: optional log path for containers. Works only for podman engine. Must be an absolute path. :param bool healthcheck_disabled: optional boolean to disable container healthcheck. :returns (list, list, int) lists of stdout and stderr for each execution, and a single return code representing the overall success of the apply. :rtype: tuple """ log = common.configure_logging(__name__, log_level, log_file) if default_runtime: log.warning("DEPRECATION: 'default_runtime' does nothing, " "use 'cont_cmd' instead") if cont_cmd == 'podman': r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log) builder = podman.PodmanBuilder( config_id=config_id, config=config, runner=r, labels=labels, log=log, cont_log_path=cont_log_path, healthcheck_disabled=healthcheck_disabled ) else: r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log) builder = compose1.ComposeV1Builder( config_id=config_id, config=config, runner=r, labels=labels, log=log ) return builder.apply()
def cleanup(config_ids, managed_by, docker_cmd=None): """Delete containers no longer applied, rename others to preferred name. :param list config_ids: List of config IDs still applied. All containers managed by this tool will be deleted if their config ID is not specified in this list. :param str managed_by: Name of the tool managing the containers. Only containers labelled with this will be modified. :param str docker_cmd: Optional override to the docker command to run. """ r = runner.DockerRunner(managed_by, docker_cmd=docker_cmd) r.delete_missing_configs(config_ids) r.rename_containers()
SH_SCRIPT = '/var/lib/container-puppet/container-puppet.sh' CONTAINER_CLI = os.environ.get('CONTAINER_CLI', 'podman') CONTAINER_LOG_STDOUT_PATH = os.environ.get('CONTAINER_LOG_STDOUT_PATH', '/var/log/containers/stdouts') CLI_CMD = '/usr/bin/' + CONTAINER_CLI LOG = get_logger() LOG.info('Running container-puppet') CONFIG_VOLUME_PREFIX = os.path.abspath( os.environ.get('CONFIG_VOLUME_PREFIX', '/var/lib/config-data')) CHECK_MODE = int(os.environ.get('CHECK_MODE', 0)) LOG.debug('CHECK_MODE: %s' % CHECK_MODE) if CONTAINER_CLI == 'docker': CLI_DCMD = ['--volume', PUPPETS] ENV = {} RUNNER = containers_runner.DockerRunner('container-puppet', cont_cmd='docker', log=LOG) elif CONTAINER_CLI == 'podman': # podman doesn't allow relabeling content in /usr and # doesn't support named volumes CLI_DCMD = ['--security-opt', 'label=disable', '--volume', PUPPETS] # podman need to find dependent binaries that are in environment ENV = {'PATH': os.environ['PATH']} RUNNER = containers_runner.PodmanRunner('container-puppet', cont_cmd='podman', log=LOG) else: LOG.error('Invalid CONTAINER_CLI: %s' % CONTAINER_CLI) raise SystemExit() config_file = os.environ.get(
def setUp(self): super(TestBaseRunner, self).setUp() self.runner = runner.DockerRunner('tester') self.podman_runner = runner.PodmanRunner('tester')
def debug(config_id, container_name, action, config, managed_by, labels=None, cont_cmd='podman', default_runtime=None, log_level=None, log_file=None): """Execute supplied container configuration. :param str config_id: Unique config ID, should not be re-used until any running containers with that config ID have been deleted. :param str container_name: Name of the container in the config you wish to manipulate. :param str action: Action to take. :param dict config: Configuration data describing container actions to apply. :param str managed_by: Name of the tool managing the containers. Only containers labeled with this will be modified. :param dict labels: Optional keys/values of labels to apply to containers created with this invocation. :param str cont_cmd: Optional override to the container command to run. :param str default_runtime: (deprecated) does nothing. :param int log_level: optional log level for loggers :param int log_file: optional log file for messages :returns integer return value from running command or failure for any other reason. :rtype: int """ log = common.configure_logging(__name__, log_level, log_file) if default_runtime: log.warning("DEPRECATION: 'default_runtime' does nothing, " "use 'cont_cmd' instead") if cont_cmd == 'podman': r = runner.PodmanRunner(managed_by, cont_cmd=cont_cmd, log=log) builder = podman.PodmanBuilder( config_id=config_id, config=config, runner=r, labels=labels, log=log ) else: r = runner.DockerRunner(managed_by, cont_cmd=cont_cmd, log=log) builder = compose1.ComposeV1Builder( config_id=config_id, config=config, runner=r, labels=labels, log=log ) if action == 'print-cmd': cmd = [ r.cont_cmd, 'run', '--name', r.unique_container_name(container_name) ] builder.container_run_args(cmd, container_name) print(' '.join(cmd)) elif action == 'run': cmd = [ r.cont_cmd, 'run', '--name', r.unique_container_name(container_name) ] builder.container_run_args(cmd, container_name) return r.execute_interactive(cmd, log) elif action == 'dump-yaml': print(yaml.safe_dump(config, default_flow_style=False)) elif action == 'dump-json': print(json.dumps(config, indent=4)) else: raise ValueError('action should be one of: "dump-json", "dump-yaml"', '"print-cmd", or "run"')
# inside of a container. import glob import json import logging import os import subprocess import sys import tempfile import time import multiprocessing from paunch import runner as containers_runner logger = None RUNNER = containers_runner.DockerRunner('docker-puppet') def get_logger(): global logger if logger is None: logger = logging.getLogger() ch = logging.StreamHandler(sys.stdout) if os.environ.get('DEBUG', False): logger.setLevel(logging.DEBUG) ch.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) ch.setLevel(logging.INFO) formatter = logging.Formatter('%(asctime)s %(levelname)s: ' '%(process)s -- %(message)s')
def setUp(self): super(TestDockerRunner, self).setUp() self.runner = runner.DockerRunner('tester')
def test_apply_failed_pull(self): orig_call = tenacity.wait.wait_random_exponential.__call__ orig_argspec = inspect.getargspec(orig_call) config = { 'one': { 'start_order': 0, 'image': 'centos:7', }, 'two': { 'start_order': 1, 'image': 'centos:7', }, 'three': { 'start_order': 2, 'image': 'centos:6', }, 'four': { 'start_order': 10, 'image': 'centos:7', }, 'four_ls': { 'action': 'exec', 'start_order': 20, 'command': ['four', 'ls', '-l', '/'] } } r = runner.DockerRunner(managed_by='tester', cont_cmd='docker') exe = mock.Mock() exe.side_effect = [ ('exists', '', 0), # inspect for image centos:6 ('', '', 1), # inspect for missing image centos:7 ('Pulling centos:7', 'ouch', 1), # pull centos:7 failure ('Pulling centos:7', 'ouch', 1), # pull centos:7 retry 2 ('Pulling centos:7', 'ouch', 1), # pull centos:7 retry 3 ('Pulling centos:7', 'ouch', 1), # pull centos:7 retry 4 ] r.execute = exe with mock.patch('tenacity.wait.wait_random_exponential.__call__') as f: f.return_value = 0 with mock.patch('inspect.getargspec') as mock_args: mock_args.return_value = orig_argspec builder = compose1.ComposeV1Builder('foo', config, r) stdout, stderr, deploy_status_code = builder.apply() self.assertEqual(1, deploy_status_code) self.assertEqual(['Pulling centos:7'], stdout) self.assertEqual(['ouch'], stderr) exe.assert_has_calls([ # inspect existing image centos:6 mock.call([ 'docker', 'inspect', '--type', 'image', '--format', 'exists', 'centos:6' ], mock.ANY, False), # inspect and pull missing image centos:7 mock.call([ 'docker', 'inspect', '--type', 'image', '--format', 'exists', 'centos:7' ], mock.ANY, False), mock.call(['docker', 'pull', 'centos:7'], mock.ANY), ])
def test_apply(self): orig_call = tenacity.wait.wait_random_exponential.__call__ orig_argspec = inspect.getargspec(orig_call) config = { 'one': { 'start_order': 0, 'image': 'centos:7', }, 'two': { 'start_order': 1, 'image': 'centos:7', }, 'three': { 'start_order': 2, 'image': 'centos:6', }, 'four': { 'start_order': 10, 'image': 'centos:7', }, 'four_ls': { 'action': 'exec', 'start_order': 20, 'command': ['four', 'ls', '-l', '/'] } } r = runner.DockerRunner(managed_by='tester', cont_cmd='docker') exe = mock.Mock() exe.side_effect = [ ('exists', '', 0), # inspect for image centos:6 ('', '', 1), # inspect for missing image centos:7 ('Pulled centos:7', 'ouch', 1), # pull centos:6 fails ('Pulled centos:7', '', 0), # pull centos:6 succeeds ('', '', 0), # ps for delete_missing_and_updated container_names ('', '', 0), # ps for after delete_missing_and_updated renames ('', '', 0), # ps to only create containers which don't exist ('Created one-12345678', '', 0), ('Created two-12345678', '', 0), ('Created three-12345678', '', 0), ('Created four-12345678', '', 0), ('a\nb\nc', '', 0) ] r.discover_container_name = lambda n, c: '%s-12345678' % n r.unique_container_name = lambda n: '%s-12345678' % n r.execute = exe with mock.patch('tenacity.wait.wait_random_exponential.__call__') as f: f.return_value = 0 with mock.patch('inspect.getargspec') as mock_args: mock_args.return_value = orig_argspec builder = compose1.ComposeV1Builder('foo', config, r) stdout, stderr, deploy_status_code = builder.apply() self.assertEqual(0, deploy_status_code) self.assertEqual([ 'Pulled centos:7', 'Created one-12345678', 'Created two-12345678', 'Created three-12345678', 'Created four-12345678', 'a\nb\nc' ], stdout) self.assertEqual([], stderr) exe.assert_has_calls([ # inspect existing image centos:6 mock.call([ 'docker', 'inspect', '--type', 'image', '--format', 'exists', 'centos:6' ], mock.ANY, False), # inspect and pull missing image centos:7 mock.call([ 'docker', 'inspect', '--type', 'image', '--format', 'exists', 'centos:7' ], mock.ANY, False), # first pull attempt fails mock.call(['docker', 'pull', 'centos:7'], mock.ANY), # second pull attempt succeeds mock.call(['docker', 'pull', 'centos:7'], mock.ANY), # ps for delete_missing_and_updated container_names mock.call([ 'docker', 'ps', '-a', '--filter', 'label=managed_by=tester', '--filter', 'label=config_id=foo', '--format', '{{.Names}} {{.Label "container_name"}}' ], mock.ANY), # ps for after delete_missing_and_updated renames mock.call([ 'docker', 'ps', '-a', '--filter', 'label=managed_by=tester', '--format', '{{.Names}} {{.Label "container_name"}}' ], mock.ANY), # ps to only create containers which don't exist mock.call([ 'docker', 'ps', '-a', '--filter', 'label=managed_by=tester', '--filter', 'label=config_id=foo', '--format', '{{.Names}} {{.Label "container_name"}}' ], mock.ANY), # run one mock.call([ 'docker', 'run', '--name', 'one-12345678', '--label', 'config_id=foo', '--label', 'container_name=one', '--label', 'managed_by=tester', '--label', 'config_data=%s' % json.dumps(config['one']), '--detach=true', 'centos:7' ], mock.ANY), # run two mock.call([ 'docker', 'run', '--name', 'two-12345678', '--label', 'config_id=foo', '--label', 'container_name=two', '--label', 'managed_by=tester', '--label', 'config_data=%s' % json.dumps(config['two']), '--detach=true', 'centos:7' ], mock.ANY), # run three mock.call([ 'docker', 'run', '--name', 'three-12345678', '--label', 'config_id=foo', '--label', 'container_name=three', '--label', 'managed_by=tester', '--label', 'config_data=%s' % json.dumps(config['three']), '--detach=true', 'centos:6' ], mock.ANY), # run four mock.call([ 'docker', 'run', '--name', 'four-12345678', '--label', 'config_id=foo', '--label', 'container_name=four', '--label', 'managed_by=tester', '--label', 'config_data=%s' % json.dumps(config['four']), '--detach=true', 'centos:7' ], mock.ANY), # execute within four mock.call(['docker', 'exec', 'four-12345678', 'ls', '-l', '/'], mock.ANY), ])
def test_apply_idempotency(self): config = { # not running yet 'one': { 'start_order': 0, 'image': 'centos:7', }, # running, but with a different config 'two': { 'start_order': 1, 'image': 'centos:7', }, # running with the same config 'three': { 'start_order': 2, 'image': 'centos:7', }, # not running yet 'four': { 'start_order': 10, 'image': 'centos:7', }, 'four_ls': { 'action': 'exec', 'start_order': 20, 'command': ['four', 'ls', '-l', '/'] } } r = runner.DockerRunner(managed_by='tester', cont_cmd='docker') exe = mock.Mock() exe.side_effect = [ # inspect for image centos:7 ('exists', '', 0), # ps for delete_missing_and_updated container_names ('''five five six six two-12345678 two three-12345678 three''', '', 0), # rm five ('', '', 0), # rm six ('', '', 0), # inspect two ('{"start_order": 1, "image": "centos:6"}', '', 0), # rm two, changed config data ('', '', 0), # inspect three ('{"start_order": 2, "image": "centos:7"}', '', 0), # ps for after delete_missing_and_updated renames ('', '', 0), # ps to only create containers which don't exist ('three-12345678 three', '', 0), ('Created one-12345678', '', 0), ('Created two-12345678', '', 0), ('Created four-12345678', '', 0), ('a\nb\nc', '', 0) ] r.discover_container_name = lambda n, c: '%s-12345678' % n r.unique_container_name = lambda n: '%s-12345678' % n r.execute = exe builder = compose1.ComposeV1Builder('foo', config, r) stdout, stderr, deploy_status_code = builder.apply() self.assertEqual(0, deploy_status_code) self.assertEqual([ 'Created one-12345678', 'Created two-12345678', 'Created four-12345678', 'a\nb\nc' ], stdout) self.assertEqual([], stderr) exe.assert_has_calls([ # inspect image centos:7 mock.call([ 'docker', 'inspect', '--type', 'image', '--format', 'exists', 'centos:7' ], mock.ANY, False), # ps for delete_missing_and_updated container_names mock.call([ 'docker', 'ps', '-a', '--filter', 'label=managed_by=tester', '--filter', 'label=config_id=foo', '--format', '{{.Names}} {{.Label "container_name"}}' ], mock.ANY), # rm containers not in config mock.call(['docker', 'rm', '-f', 'five'], mock.ANY), mock.call(['docker', 'rm', '-f', 'six'], mock.ANY), # rm two, changed config mock.call([ 'docker', 'inspect', '--type', 'container', '--format', '{{index .Config.Labels "config_data"}}', 'two-12345678' ], mock.ANY, False), mock.call(['docker', 'rm', '-f', 'two-12345678'], mock.ANY), # check three, config hasn't changed mock.call([ 'docker', 'inspect', '--type', 'container', '--format', '{{index .Config.Labels "config_data"}}', 'three-12345678' ], mock.ANY, False), # ps for after delete_missing_and_updated renames mock.call([ 'docker', 'ps', '-a', '--filter', 'label=managed_by=tester', '--format', '{{.Names}} {{.Label "container_name"}}' ], mock.ANY), # ps to only create containers which don't exist mock.call([ 'docker', 'ps', '-a', '--filter', 'label=managed_by=tester', '--filter', 'label=config_id=foo', '--format', '{{.Names}} {{.Label "container_name"}}' ], mock.ANY), # run one mock.call([ 'docker', 'run', '--name', 'one-12345678', '--label', 'config_id=foo', '--label', 'container_name=one', '--label', 'managed_by=tester', '--label', 'config_data=%s' % json.dumps(config['one']), '--detach=true', 'centos:7' ], mock.ANY), # run two mock.call([ 'docker', 'run', '--name', 'two-12345678', '--label', 'config_id=foo', '--label', 'container_name=two', '--label', 'managed_by=tester', '--label', 'config_data=%s' % json.dumps(config['two']), '--detach=true', 'centos:7' ], mock.ANY), # don't run three, its already running # run four mock.call([ 'docker', 'run', '--name', 'four-12345678', '--label', 'config_id=foo', '--label', 'container_name=four', '--label', 'managed_by=tester', '--label', 'config_data=%s' % json.dumps(config['four']), '--detach=true', 'centos:7' ], mock.ANY), # execute within four mock.call(['docker', 'exec', 'four-12345678', 'ls', '-l', '/'], mock.ANY), ])