Example #1
0
 def test_backup(self):
     cases = dict(
         default=dict(
             expected_commands=[
                 mock.call('docker inspect --type container name'),
                 mock.call('docker run --rm --tty --interactive image_id pg_dump --username postgres --if-exists --create --clean --format c --jobs 1 --file /data/backup/postgres/backup.dump', quiet=False),
             ],
             side_effect=(
                 SucceededResult('[{"Image": "image_id"}]'),
                 SucceededResult(),
             ),
             container_class_attributes=dict(
                 db_backup_dir='/data/backup/postgres',
                 db_backup_filename='backup.dump',
             ),
         ),
         regular=dict(
             expected_commands=[
                 mock.call('docker inspect --type container name'),
                 mock.call('docker run --rm --tty --interactive image_id pg_dump --username user --host localhost --port 5432 --if-exists --create --clean --format t --dbname test_db --compress 9 --jobs 2 --file /data/backup/postgres/backup.dump', quiet=False),
             ],
             side_effect=(
                 SucceededResult('[{"Image": "image_id"}]'),
                 SucceededResult(),
             ),
             container_class_attributes=dict(
                 db_backup_dir='/data/backup/postgres',
                 db_backup_filename='backup.dump',
                 db_user='******',
                 db_host='localhost',
                 db_port=5432,
                 db_name='test_db',
                 db_backup_format='t',
                 db_backup_compress_level=9,
                 db_backup_workers=2,
             ),
         ),
     )
     for case, data in cases.items():
         with self.subTest(case=case):
             container_type = type(
                 'TestContainer',
                 (postgres.PostgresqlBackupMixin,),
                 data['container_class_attributes'],
             )
             container = container_type(name='name')
             with mock.patch.object(fabricio, 'run', side_effect=data['side_effect']) as run:
                 container.backup()
                 self.assertListEqual(run.mock_calls, data['expected_commands'])
Example #2
0
 def test_destroy(self, run, put, *_):
     run.side_effect = [SucceededResult('kind/name image-name image')] + [SucceededResult('[{"Parent": "parent_id"}]')] * 4
     with mock.patch('fabricio.operations.run', run):
         config = kubernetes.Configuration(name='name', options=dict(filename='config.yml'))
         config.destroy()
         self.assertListEqual(
             [
                 mock.call('kubectl get --output=go-template --filename=config.yml --template=\'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}\''),
                 mock.call('kubectl delete --filename=config.yml'),
                 mock.call('docker inspect --type image fabricio-current-kubernetes:name', abort_exception=docker.ImageNotFoundError),
                 mock.call('docker inspect --type image fabricio-backup-kubernetes:name', abort_exception=docker.ImageNotFoundError),
                 mock.call('docker rmi fabricio-current-kubernetes:name fabricio-backup-kubernetes:name parent_id parent_id image', ignore_errors=True),
                 mock.call('rm -f config.yml', ignore_errors=True, sudo=False),
             ],
             run.mock_calls,
         )
         put.assert_called_once_with(b'configuration', 'config.yml')
Example #3
0
 def test_migrate_back_errors(self):
     cases = dict(
         current_container_not_found=dict(
             expected_exception=RuntimeError,
             expected_error_message="Container 'name' not found",
             side_effect=(
                 RuntimeError,
             ),
             expected_commands=[
                 mock.call('docker inspect --type container name'),
             ],
         ),
         backup_container_not_found=dict(
             expected_exception=RuntimeError,
             expected_error_message="Container 'name_backup' not found",
             side_effect=(
                 SucceededResult('[{"Image": "current_image_id"}]'),
                 SucceededResult(
                     'app1.0001_initial\n'
                 ),
                 RuntimeError,
             ),
             expected_commands=[
                 mock.call('docker inspect --type container name'),
                 mock.call('docker run --rm --tty --interactive current_image_id python manage.py showmigrations --plan | egrep "^\[X\]" | awk "{print \$2}"', quiet=True),
                 mock.call('docker inspect --type container name_backup'),
             ],
         ),
     )
     for case, data in cases.items():
         with self.subTest(case=case):
             with mock.patch.object(
                 fabricio,
                 'run',
                 side_effect=data['side_effect'],
             ) as run:
                 expected_commands = data['expected_commands']
                 container = DjangoContainer(name='name', image='image')
                 with self.assertRaises(data['expected_exception']) as cm:
                     container.migrate_back()
                 self.assertEqual(
                     cm.exception.args[0],
                     data['expected_error_message'],
                 )
                 self.assertListEqual(run.mock_calls, expected_commands)
 def test_revert_raises_error_when_backup_not_found(self):
     side_effect = self.command_checker(
         args_parsers=args_parser,
         expected_args_set=[
             {'args': ['kubectl', 'config', 'current-context']},
             {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
         ],
         side_effects=[
             SucceededResult(),  # manager status
             docker.ImageNotFoundError(),  # image info
         ],
     )
     with mock.patch.object(fabricio, 'run', side_effect=side_effect):
         fab.env.command = 'test_k8s_revert_raises_error_when_backup_not_found'
         configuration = kubernetes.Configuration(name='k8s')
         with self.assertRaises(docker.ServiceError):
             configuration.revert()
Example #5
0
    def test_run_with_cache_key(self):
        with mock.patch.object(fab, 'run',
                               return_value=SucceededResult()) as run:
            run.__name__ = 'mocked_run'

            fabricio.run('command', use_cache=True)
            self.assertEqual(run.call_count, 1)
            fabricio.run('command', use_cache=True)
            self.assertEqual(run.call_count, 1)

            fabricio.run('command', cache_key='key1', use_cache=True)
            self.assertEqual(run.call_count, 2)
            fabricio.run('command', cache_key='key1', use_cache=True)
            self.assertEqual(run.call_count, 2)

            fabricio.run('command', cache_key='key2', use_cache=True)
            self.assertEqual(run.call_count, 3)
            fabricio.run('command', cache_key='key2', use_cache=True)
            self.assertEqual(run.call_count, 3)
Example #6
0
 def test_revert_raises_error_when_backup_not_found(self):
     side_effect = self.command_checker(
         args_parsers=[args_parser, docker_inspect_args_parser],
         expected_args_set=[
             {
                 'args':
                 ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
             },
             {
                 'executable': ['docker', 'inspect'],
                 'type': 'image',
                 'image_or_container': 'fabricio-backup-stack:stack',
             },
         ],
         side_effects=[
             SucceededResult('  Is Manager: true'),  # manager status
             docker.ImageNotFoundError(),  # image info
         ],
     )
     with mock.patch.object(fabricio, 'run', side_effect=side_effect):
         stack = docker.Stack(name='stack')
         with self.assertRaises(docker.ServiceError):
             stack.revert()
Example #7
0
 def test_update(self, exists, *args):
     cases = dict(
         updated_without_config_change=dict(
             db_exists=True,
             old_configs=[
                 'postgresql.conf',
                 'pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('rm -f /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('rm -f /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
             ],
             update_kwargs=dict(),
             parent_update_returned=True,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         no_change=dict(
             db_exists=True,
             old_configs=[
                 'postgresql.conf',
                 'pg_hba.conf',
             ],
             expected_commands=[],
             update_kwargs=dict(),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=False,
         ),
         no_change_with_tag=dict(
             db_exists=True,
             old_configs=[
                 'postgresql.conf',
                 'pg_hba.conf',
             ],
             expected_commands=[],
             update_kwargs=dict(tag='tag'),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag='tag', registry=None),
             expected_result=False,
         ),
         no_change_with_registry=dict(
             db_exists=True,
             old_configs=[
                 'postgresql.conf',
                 'pg_hba.conf',
             ],
             expected_commands=[],
             update_kwargs=dict(registry='registry'),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag=None, registry='registry'),
             expected_result=False,
         ),
         no_change_with_tag_and_registry=dict(
             db_exists=True,
             old_configs=[
                 'postgresql.conf',
                 'pg_hba.conf',
             ],
             expected_commands=[],
             update_kwargs=dict(tag='tag', registry='registry'),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag='tag', registry='registry'),
             expected_result=False,
         ),
         forced=dict(
             db_exists=True,
             old_configs=[
                 'postgresql.conf',
                 'pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('rm -f /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('rm -f /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
             ],
             update_kwargs=dict(force=True),
             parent_update_returned=True,
             expected_update_kwargs=dict(force=True, tag=None, registry=None),
             expected_result=True,
         ),
         pg_hba_changed=dict(
             db_exists=True,
             old_configs=[
                 'postgresql.conf',
                 'old_pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('mv /data/pg_hba.conf /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('docker kill --signal HUP name'),
                 mock.call('docker inspect --type container name_backup'),
                 mock.call('docker rm name_backup'),
                 mock.call('for volume in $(docker volume ls --filter "dangling=true" --quiet); do docker volume rm "$volume"; done'),
                 mock.call('docker rmi image_id', ignore_errors=True),
                 mock.call('rm -f /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
             ],
             side_effect=(
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult('[{"Image": "image_id"}]'),
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult(),
             ),
             update_kwargs=dict(),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         pg_hba_changed_backup_container_not_found=dict(
             db_exists=True,
             old_configs=[
                 'postgresql.conf',
                 'old_pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('mv /data/pg_hba.conf /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('docker kill --signal HUP name'),
                 mock.call('docker inspect --type container name_backup'),
                 mock.call('rm -f /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
             ],
             side_effect=(
                 SucceededResult(),
                 SucceededResult(),
                 RuntimeError,
                 SucceededResult(),
             ),
             update_kwargs=dict(),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         pg_hba_changed_container_updated=dict(
             db_exists=True,
             old_configs=[
                 'postgresql.conf',
                 'old_pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('mv /data/pg_hba.conf /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('rm -f /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
             ],
             update_kwargs=dict(),
             parent_update_returned=True,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         main_conf_changed=dict(
             db_exists=True,
             old_configs=[
                 'old_postgresql.conf',
                 'pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('mv /data/postgresql.conf /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('docker restart --time 30 name'),
                 mock.call('docker inspect --type container name_backup'),
                 mock.call('docker rm name_backup'),
                 mock.call('for volume in $(docker volume ls --filter "dangling=true" --quiet); do docker volume rm "$volume"; done'),
                 mock.call('docker rmi image_id', ignore_errors=True),
                 mock.call('rm -f /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
             ],
             side_effect=(
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult('[{"Image": "image_id"}]'),
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult(),
             ),
             update_kwargs=dict(),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         main_conf_changed_backup_container_not_found=dict(
             db_exists=True,
             old_configs=[
                 'old_postgresql.conf',
                 'pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('mv /data/postgresql.conf /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('docker restart --time 30 name'),
                 mock.call('docker inspect --type container name_backup'),
                 mock.call('rm -f /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
             ],
             side_effect=(
                 SucceededResult(),
                 SucceededResult(),
                 RuntimeError,
                 SucceededResult(),
             ),
             update_kwargs=dict(),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         main_conf_changed_container_updated=dict(
             db_exists=True,
             old_configs=[
                 'old_postgresql.conf',
                 'pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('mv /data/postgresql.conf /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('rm -f /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
             ],
             update_kwargs=dict(),
             parent_update_returned=True,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         configs_changed=dict(
             db_exists=True,
             old_configs=[
                 'old_postgresql.conf',
                 'old_pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('mv /data/postgresql.conf /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('docker restart --time 30 name'),
                 mock.call('docker inspect --type container name_backup'),
                 mock.call('docker rm name_backup'),
                 mock.call('for volume in $(docker volume ls --filter "dangling=true" --quiet); do docker volume rm "$volume"; done'),
                 mock.call('docker rmi image_id', ignore_errors=True),
             ],
             side_effect=(
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult('[{"Image": "image_id"}]'),
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult(),
             ),
             update_kwargs=dict(),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         configs_changed_backup_container_not_found=dict(
             db_exists=True,
             old_configs=[
                 'old_postgresql.conf',
                 'old_pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('mv /data/postgresql.conf /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('docker restart --time 30 name'),
                 mock.call('docker inspect --type container name_backup'),
             ],
             side_effect=(
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult(),
                 RuntimeError,
             ),
             update_kwargs=dict(),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         configs_changed_container_updated=dict(
             db_exists=True,
             old_configs=[
                 'old_postgresql.conf',
                 'old_pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('mv /data/postgresql.conf /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
             ],
             update_kwargs=dict(),
             parent_update_returned=True,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
         from_scratch=dict(
             db_exists=False,
             old_configs=[
                 'old_postgresql.conf',
                 'old_pg_hba.conf',
             ],
             expected_commands=[
                 mock.call('docker run --volume /data:/data --stop-signal INT --rm --tty --interactive image:tag postgres --version', quiet=False),
                 mock.call('mv /data/postgresql.conf /data/postgresql.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf /data/pg_hba.conf.backup', ignore_errors=True, sudo=True),
                 mock.call('docker restart --time 30 name'),
                 mock.call('docker inspect --type container name_backup'),
             ],
             side_effect=(
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult(),
                 SucceededResult(),
                 RuntimeError,
             ),
             update_kwargs=dict(),
             parent_update_returned=False,
             expected_update_kwargs=dict(force=False, tag=None, registry=None),
             expected_result=True,
         ),
     )
     for case, data in cases.items():
         with self.subTest(case=case):
             postgres.open.side_effect = (
                 six.BytesIO('postgresql.conf'),
                 six.BytesIO('pg_hba.conf'),
             )
             container = TestContainer(
                 name='name',
                 options=dict(volumes='/data:/data'),
             )
             with mock.patch.object(
                 fabricio,
                 'run',
                 side_effect=data.get('side_effect'),
             ) as run:
                 with mock.patch.object(
                     container,
                     'db_exists',
                     return_value=data['db_exists'],
                 ):
                     with mock.patch.object(
                         docker.Container,
                         'update',
                         return_value=data['parent_update_returned'],
                     ) as update:
                         with mock.patch.object(
                             six.BytesIO,
                             'getvalue',
                             side_effect=data['old_configs'],
                         ):
                             result = container.update(**data['update_kwargs'])
                             self.assertListEqual(run.mock_calls, data['expected_commands'])
                             self.assertEqual(result, data['expected_result'])
                             update.assert_called_once_with(**data['expected_update_kwargs'])
Example #8
0
 def test_revert(self):
     cases = dict(
         pg_hba_reverted=dict(
             parent_revert_returned=RuntimeError,
             expected_commands = [
                 mock.call('mv /data/postgresql.conf.backup /data/postgresql.conf', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf.backup /data/pg_hba.conf', ignore_errors=True, sudo=True),
                 mock.call('docker kill --signal HUP name'),
             ],
             side_effect=(
                 FailedResult(),  # main config
                 SucceededResult(),  # pg_hba
                 SucceededResult(),  # SIGHUP
             ),
         ),
         main_conf_reverted=dict(
             parent_revert_returned=RuntimeError,
             expected_commands = [
                 mock.call('mv /data/postgresql.conf.backup /data/postgresql.conf', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf.backup /data/pg_hba.conf', ignore_errors=True, sudo=True),
                 mock.call('docker restart --time 30 name'),
             ],
             side_effect=(
                 SucceededResult(),  # main config
                 FailedResult(),  # pg_hba
                 SucceededResult(),  # restart
             ),
         ),
         configs_reverted=dict(
             parent_revert_returned=RuntimeError,
             expected_commands = [
                 mock.call('mv /data/postgresql.conf.backup /data/postgresql.conf', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf.backup /data/pg_hba.conf', ignore_errors=True, sudo=True),
                 mock.call('docker restart --time 30 name'),
             ],
             side_effect=(
                 SucceededResult(),  # main config
                 SucceededResult(),  # pg_hba
                 SucceededResult(),  # restart
             ),
         ),
         pg_hba_reverted_container_reverted=dict(
             parent_revert_returned=None,
             expected_commands = [
                 mock.call('mv /data/postgresql.conf.backup /data/postgresql.conf', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf.backup /data/pg_hba.conf', ignore_errors=True, sudo=True),
             ],
             side_effect=(
                 FailedResult(),  # main config
                 SucceededResult(),  # pg_hba
             ),
         ),
         main_conf_reverted_container_reverted=dict(
             parent_revert_returned=None,
             expected_commands = [
                 mock.call('mv /data/postgresql.conf.backup /data/postgresql.conf', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf.backup /data/pg_hba.conf', ignore_errors=True, sudo=True),
             ],
             side_effect=(
                 SucceededResult(),  # main config
                 FailedResult(),  # pg_hba
             ),
         ),
         configs_reverted_container_reverted=dict(
             parent_revert_returned=None,
             expected_commands = [
                 mock.call('mv /data/postgresql.conf.backup /data/postgresql.conf', ignore_errors=True, sudo=True),
                 mock.call('mv /data/pg_hba.conf.backup /data/pg_hba.conf', ignore_errors=True, sudo=True),
             ],
             side_effect=(
                 SucceededResult(),  # main config
                 SucceededResult(),  # pg_hba
             ),
         ),
     )
     for case, data in cases.items():
         with self.subTest(case=case):
             expected_commands = data['expected_commands']
             with mock.patch.object(
                 fabricio,
                 'run',
                 side_effect=data['side_effect']
             ) as run:
                 with mock.patch.object(
                     docker.Container,
                     'revert',
                     side_effect=data['parent_revert_returned'],
                 ):
                     container = TestContainer(
                         name='name',
                         options=dict(volumes='/data:/data'),
                     )
                     container.revert()
                     self.assertListEqual(
                         run.mock_calls,
                         expected_commands,
                     )
Example #9
0
 def test_revert(self, put, *args):
     cases = dict(
         worker=dict(
             init_kwargs=dict(name='stack'),
             side_effect=[
                 SucceededResult('  Is Manager: false'),  # manager status
             ],
             args_parser=[
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
             ],
             all_hosts=['host1', 'host2'],
         ),
         reverted=dict(
             init_kwargs=dict(name='stack'),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(
                     json.dumps([{
                         'Config': {
                             'Labels': {
                                 'fabricio.stack.compose.stack':
                                 'b2xkLWNvbXBvc2UueW1s',
                             },
                         }
                     }])),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-backup-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-current-stack:stack;',
                         'docker', 'tag', 'fabricio-backup-stack:stack',
                         'fabricio-current-stack:stack;', 'docker', 'rmi',
                         'fabricio-backup-stack:stack'
                     ],
                 },
             ],
             expected_compose_file=b'old-compose.yml',
         ),
         reverted_with_service_update=dict(
             init_kwargs=dict(name='stack'),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(
                     json.dumps([{
                         'Config': {
                             'Labels': {
                                 'fabricio.stack.compose.stack':
                                 'Y29tcG9zZS55bWw=',  # compose.yml
                                 'fabricio.stack.images.stack':
                                 'eyJpbWFnZTp0YWciOiAiZGlnZXN0In0=',  # {"image:tag": "digest"}
                             },
                         }
                     }])),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult('service image:tag\n'),  # stack services
                 SucceededResult(),  # service update
                 SucceededResult(),  # update sentinel images
             ],
             args_parser=[args_parser] * 6,
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'args': [
                         'docker', 'inspect', '--type', 'image',
                         'fabricio-backup-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'service', 'update', '--image', 'digest',
                         'service'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-current-stack:stack;',
                         'docker', 'tag', 'fabricio-backup-stack:stack',
                         'fabricio-current-stack:stack;', 'docker', 'rmi',
                         'fabricio-backup-stack:stack'
                     ],
                 },
             ],
             expected_compose_file=b'compose.yml',
         ),
         reverted_with_services_update=dict(
             init_kwargs=dict(name='stack'),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(
                     json.dumps([{
                         'Config': {
                             'Labels': {
                                 'fabricio.stack.compose.stack':
                                 'Y29tcG9zZS55bWw=',  # compose.yml
                                 'fabricio.stack.images.stack':
                                 'eyJpbWFnZTE6dGFnIjogImRpZ2VzdDEiLCAiaW1hZ2UyOnRhZyI6ICJkaWdlc3QyIn0=',  # {"image1:tag": "digest1", "image2:tag": "digest2"}
                             },
                         }
                     }])),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult('service1 image1:tag\nservice2 image2:tag'
                                 ),  # stack services
                 SucceededResult(),  # service update
                 SucceededResult(),  # service update
                 SucceededResult(),  # update sentinel images
             ],
             args_parser=[args_parser] * 7,
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'args': [
                         'docker', 'inspect', '--type', 'image',
                         'fabricio-backup-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'service', 'update', '--image',
                         'digest1', 'service1'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'service', 'update', '--image',
                         'digest2', 'service2'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-current-stack:stack;',
                         'docker', 'tag', 'fabricio-backup-stack:stack',
                         'fabricio-current-stack:stack;', 'docker', 'rmi',
                         'fabricio-backup-stack:stack'
                     ],
                 },
             ],
             expected_compose_file=b'compose.yml',
         ),
     )
     for case, data in cases.items():
         with self.subTest(case=case):
             fab.env.command = '{0}__{1}'.format(self, case)
             put.reset_mock()
             with mock.patch.dict(
                     fab.env,
                     dict(all_hosts=data.get('all_hosts', ['host']))):
                 stack = docker.Stack(**data.get('init_kwargs', {}))
                 side_effects = data.get('side_effect', [])
                 side_effect = self.command_checker(
                     args_parsers=data.get('args_parser', []),
                     expected_args_set=data.get('expected_command_args',
                                                []),
                     side_effects=side_effects,
                 )
                 with mock.patch.object(fabricio,
                                        'run',
                                        side_effect=side_effect) as run:
                     with mock.patch('six.BytesIO') as compose_file:
                         stack.revert()
                 self.assertEqual(run.call_count, len(side_effects))
                 expected_compose_file = data.get('expected_compose_file')
                 if expected_compose_file:
                     put.assert_called_once()
                     compose_file.assert_called_once_with(
                         expected_compose_file)
                 else:
                     put.assert_not_called()
Example #10
0
 def test_update(self, put, *args):
     cases = dict(
         worker=dict(
             init_kwargs=dict(name='stack'),
             update_kwargs={},
             side_effect=[
                 SucceededResult('  Is Manager: false'),  # manager status
             ],
             args_parser=[
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
             ],
             expected_result=False,
             all_hosts=['host1', 'host2'],
         ),
         no_changes=dict(
             init_kwargs=dict(name='stack'),
             update_kwargs={},
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(
                     json.dumps([{
                         'Config': {
                             'Labels': {
                                 'fabricio.stack.compose.stack':
                                 'Y29tcG9zZS55bWw=',
                                 'fabricio.stack.images.stack': 'e30=',
                             },
                         }
                     }])),  # image info
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
             ],
             expected_result=False,
             expected_compose_file='docker-compose.yml',
         ),
         forced=dict(
             init_kwargs=dict(name='stack'),
             update_kwargs=dict(force=True),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(),  # stack deploy
                 RuntimeError(),  # update sentinel images
                 SucceededResult(),  # stack images
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM scratch\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=e30=\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file='docker-compose.yml',
             should_upload_compose_file=True,
         ),
         created=dict(
             init_kwargs=dict(name='stack'),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # stack images
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM scratch\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=e30=\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file='docker-compose.yml',
             should_upload_compose_file=True,
         ),
         created_skip_sentinels_errors=dict(
             init_kwargs=dict(name='stack'),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # stack deploy
                 RuntimeError(),  # update sentinel images
                 RuntimeError(),  # stack images
                 RuntimeError(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM scratch\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw=\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file='docker-compose.yml',
             should_upload_compose_file=True,
         ),
         created_with_custom_compose=dict(
             init_kwargs=dict(name='stack',
                              options=dict(compose_file='compose.yml')),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # stack images
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM scratch\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=e30=\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file='compose.yml',
             should_upload_compose_file=True,
         ),
         created_with_custom_compose2=dict(
             init_kwargs=dict(name='stack',
                              options={'compose-file': 'compose.yml'}),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # stack images
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM scratch\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=e30=\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file='compose.yml',
             should_upload_compose_file=True,
         ),
         created_with_custom_image=dict(
             init_kwargs=dict(name='stack', image='image:tag'),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # stack images
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM image:tag\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=e30=\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file_name='docker-compose.yml',
             should_upload_compose_file=True,
         ),
         created_with_custom_image_update_params=dict(
             init_kwargs=dict(name='stack', image='image:tag'),
             update_kwargs=dict(tag='new-tag',
                                registry='registry',
                                account='account'),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # stack images
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM registry/account/image:new-tag\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=e30=\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file_name='docker-compose.yml',
             should_upload_compose_file=True,
         ),
         created_from_empty_image_with_custom_image_update_params=dict(
             init_kwargs=dict(name='stack'),
             update_kwargs=dict(tag='registry/account/image:tag'),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # stack images
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM registry/account/image:tag\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=e30=\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file_name='docker-compose.yml',
             should_upload_compose_file=True,
         ),
         updated_compose_changed=dict(
             init_kwargs=dict(name='stack'),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(
                     json.dumps([{
                         'Config': {
                             'Labels': {
                                 'fabricio.stack.compose.stack':
                                 'b2xkLWNvbXBvc2UueW1s',
                                 'fabricio.stack.images.stack':
                                 'eyJpbWFnZTp0YWciOiAiZGlnZXN0In0=',
                             },
                         }
                     }])),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
                 SucceededResult('service image:tag'),  # stack images
                 SucceededResult(),  # image pull
                 SucceededResult('digest'),  # images digests
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': ['docker', 'pull', 'image:tag'],
                 },
                 {
                     'args': [
                         'docker', 'inspect', '--type', 'image', '--format',
                         '{{index .RepoDigests 0}}', 'image:tag'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM scratch\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=eyJpbWFnZTp0YWciOiAiZGlnZXN0In0=\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file='docker-compose.yml',
             should_upload_compose_file=True,
         ),
         updated_image_changed=dict(
             init_kwargs=dict(name='stack'),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(
                     json.dumps([{
                         'Config': {
                             'Labels': {
                                 'fabricio.stack.compose.stack':
                                 'Y29tcG9zZS55bWw=',
                                 'fabricio.stack.images.stack':
                                 'eyJpbWFnZTp0YWciOiAiZGlnZXN0In0=',
                             },
                         }
                     }])),  # image info
                 SucceededResult(),  # image pull
                 SucceededResult('new-digest'),  # images digests
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
                 SucceededResult('service image:tag'),  # stack images
                 SucceededResult(),  # image pull
                 SucceededResult('new-digest'),  # images digests
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': ['docker', 'pull', 'image:tag'],
                 },
                 {
                     'args': [
                         'docker', 'inspect', '--type', 'image', '--format',
                         '{{index .RepoDigests 0}}', 'image:tag'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': ['docker', 'pull', 'image:tag'],
                 },
                 {
                     'args': [
                         'docker', 'inspect', '--type', 'image', '--format',
                         '{{index .RepoDigests 0}}', 'image:tag'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM scratch\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=eyJpbWFnZTp0YWciOiAibmV3LWRpZ2VzdCJ9\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file='docker-compose.yml',
             should_upload_compose_file=True,
         ),
         updated_images_changed=dict(
             init_kwargs=dict(name='stack'),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(
                     json.dumps([{
                         'Config': {
                             'Labels': {
                                 'fabricio.stack.compose.stack':
                                 'Y29tcG9zZS55bWw=',
                                 'fabricio.stack.images.stack':
                                 'eyJpbWFnZTE6dGFnIjogImRpZ2VzdDEiLCAiaW1hZ2UyOnRhZyI6ICJkaWdlc3QyIn0=',
                             },
                         }
                     }])),  # image info
                 SucceededResult(),  # image1 pull
                 SucceededResult(),  # image2 pull
                 SucceededResult(
                     'new-digest1\nnew-digest2\n'),  # images digests
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
                 SucceededResult(
                     'service1 image1:tag\nservice2 image2:tag\n'
                 ),  # stack images
                 SucceededResult(),  # image1 pull
                 SucceededResult(),  # image2 pull
                 SucceededResult(
                     'new-digest1\nnew-digest2\n'),  # images digests
                 SucceededResult(),  # build new sentinel image
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-current-stack:stack',
                 },
                 {
                     'args': ['docker', 'pull', 'image1:tag'],
                 },
                 {
                     'args': ['docker', 'pull', 'image2:tag'],
                 },
                 {
                     'args': [
                         'docker', 'inspect', '--type', 'image', '--format',
                         '{{index .RepoDigests 0}}', 'image1:tag',
                         'image2:tag'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-backup-stack:stack;',
                         'docker', 'tag', 'fabricio-current-stack:stack',
                         'fabricio-backup-stack:stack;', 'docker', 'rmi',
                         'fabricio-current-stack:stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'stack', 'services', '--format',
                         '{{.Name}} {{.Image}}', 'stack'
                     ],
                 },
                 {
                     'args': ['docker', 'pull', 'image1:tag'],
                 },
                 {
                     'args': ['docker', 'pull', 'image2:tag'],
                 },
                 {
                     'args': [
                         'docker', 'inspect', '--type', 'image', '--format',
                         '{{index .RepoDigests 0}}', 'image1:tag',
                         'image2:tag'
                     ],
                 },
                 {
                     'args': [
                         'echo',
                         'FROM scratch\nLABEL fabricio.stack.compose.stack=Y29tcG9zZS55bWw= fabricio.stack.images.stack=eyJpbWFnZTE6dGFnIjogIm5ldy1kaWdlc3QxIiwgImltYWdlMjp0YWciOiAibmV3LWRpZ2VzdDIifQ==\n',
                         '|', 'docker', 'build', '--tag',
                         'fabricio-current-stack:stack', '-'
                     ],
                 },
             ],
             expected_result=True,
             expected_compose_file='docker-compose.yml',
             should_upload_compose_file=True,
         ),
     )
     for case, data in cases.items():
         with self.subTest(case):
             fab.env.command = '{0}__{1}'.format(self, case)
             with mock.patch.dict(
                     fab.env,
                     dict(all_hosts=data.get('all_hosts', ['host']))):
                 service.open.return_value = six.BytesIO(b'compose.yml')
                 service.open.reset_mock()
                 put.reset_mock()
                 stack = docker.Stack(**data.get('init_kwargs', {}))
                 side_effect = self.command_checker(
                     args_parsers=data.get('args_parser', []),
                     expected_args_set=data.get('expected_command_args',
                                                []),
                     side_effects=data.get('side_effect', []),
                 )
                 with mock.patch.object(fabricio,
                                        'run',
                                        side_effect=side_effect):
                     with mock.patch('six.BytesIO') as compose_file:
                         result = stack.update(
                             **data.get('update_kwargs', {}))
                 self.assertEqual(data['expected_result'], result)
                 expected_compose_file_name = data.get(
                     'expected_compose_file_name')
                 if expected_compose_file_name:
                     service.open.assert_called_once_with(
                         expected_compose_file_name, 'rb')
                 if data.get('should_upload_compose_file', False):
                     put.assert_called_once()
                     compose_file.assert_called_once_with(b'compose.yml')
                 else:
                     put.assert_not_called()
Example #11
0
 def test_revert(self, put):
     cases = dict(
         worker=dict(
             init_kwargs=dict(name='stack'),
             side_effect=[
                 SucceededResult('  Is Manager: false'),  # manager status
             ],
             args_parser=[
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
             ],
             all_hosts=['host1', 'host2'],
         ),
         reverted=dict(
             init_kwargs=dict(name='stack'),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(
                     json.dumps([{
                         'Config': {
                             'Labels': {
                                 'fabricio.stack.compose.stack':
                                 'b2xkLWNvbXBvc2UueW1s',
                             },
                         }
                     }])),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-backup-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-current-stack:stack;',
                         'docker', 'tag', 'fabricio-backup-stack:stack',
                         'fabricio-current-stack:stack;', 'docker', 'rmi',
                         'fabricio-backup-stack:stack'
                     ],
                 },
             ],
             expected_compose_file=b'old-compose.yml',
         ),
         reverted_with_same_compose=dict(
             init_kwargs=dict(name='stack'),
             side_effect=[
                 SucceededResult('  Is Manager: true'),  # manager status
                 SucceededResult(
                     json.dumps([{
                         'Config': {
                             'Labels': {
                                 'fabricio.stack.compose.stack':
                                 'Y29tcG9zZS55bWw=',
                             },
                         }
                     }])),  # image info
                 SucceededResult(),  # stack deploy
                 SucceededResult(),  # update sentinel images
             ],
             args_parser=[
                 args_parser,
                 docker_inspect_args_parser,
                 args_parser,
                 args_parser,
             ],
             expected_command_args=[
                 {
                     'args':
                     ['docker', 'info', '2>&1', '|', 'grep', 'Is Manager:'],
                 },
                 {
                     'executable': ['docker', 'inspect'],
                     'type': 'image',
                     'image_or_container': 'fabricio-backup-stack:stack',
                 },
                 {
                     'args': [
                         'docker', 'stack', 'deploy',
                         '--compose-file=docker-compose.yml', 'stack'
                     ],
                 },
                 {
                     'args': [
                         'docker', 'rmi', 'fabricio-current-stack:stack;',
                         'docker', 'tag', 'fabricio-backup-stack:stack',
                         'fabricio-current-stack:stack;', 'docker', 'rmi',
                         'fabricio-backup-stack:stack'
                     ],
                 },
             ],
             expected_compose_file=b'compose.yml',
         ),
     )
     for case, data in cases.items():
         with self.subTest(case=case):
             fab.env.command = '{0}__{1}'.format(self, case)
             put.reset_mock()
             with mock.patch.dict(
                     fab.env,
                     dict(all_hosts=data.get('all_hosts', ['host']))):
                 stack = docker.Stack(**data.get('init_kwargs', {}))
                 side_effect = self.command_checker(
                     args_parsers=data.get('args_parser', []),
                     expected_args_set=data.get('expected_command_args',
                                                []),
                     side_effects=data.get('side_effect', []),
                 )
                 with mock.patch.object(fabricio,
                                        'run',
                                        side_effect=side_effect):
                     with mock.patch('six.BytesIO') as compose_file:
                         stack.revert()
                 expected_compose_file = data.get('expected_compose_file')
                 if expected_compose_file:
                     put.assert_called_once()
                     compose_file.assert_called_once_with(
                         expected_compose_file)
                 else:
                     put.assert_not_called()
Example #12
0
 def test_update(self, put, *args):
     cases = dict(
         worker=dict(
             init_kwargs=dict(),
             update_kwargs={},
             side_effect=[
                 FailedResult(),  # manager status
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
             ],
             expected_result=None,
             all_hosts=['host1', 'host2'],
         ),
         no_changes=dict(
             init_kwargs=dict(),
             update_kwargs={},
             side_effect=[
                 SucceededResult(),  # manager status
                 SucceededResult(json.dumps([{'Config': {
                     'Labels': {
                         'fabricio.configuration': 'azhzLnltbA==',  # k8s.yml
                         'fabricio.digests': 'e30=',  # {}
                     },
                 }}])),  # image info
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
             ],
             expected_result=False,
             expected_config_filename='k8s.yml',
         ),
         forced=dict(
             init_kwargs=dict(),
             update_kwargs=dict(force=True),
             side_effect=[
                 SucceededResult(),  # manager status
                 SucceededResult(),  # configuration deploy
                 docker.ImageNotFoundError(),  # backup image info
                 fabricio.Error(),  # update sentinel images
                 SucceededResult(),  # configuration images
                 SucceededResult(),  # build new sentinel image
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-backup-kubernetes:k8s;', 'docker', 'tag', 'fabricio-current-kubernetes:k8s', 'fabricio-backup-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['echo', 'FROM scratch\nLABEL fabricio.configuration=azhzLnltbA== fabricio.digests=e30=\n', '|', 'docker', 'build', '--tag', 'fabricio-current-kubernetes:k8s', '-']},
             ],
             expected_result=True,
             expected_config_filename='k8s.yml',
         ),
         created=dict(
             init_kwargs=dict(),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult(),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # configuration deploy
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # configuration images
                 SucceededResult(),  # build new sentinel image
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-backup-kubernetes:k8s;', 'docker', 'tag', 'fabricio-current-kubernetes:k8s', 'fabricio-backup-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['echo', 'FROM scratch\nLABEL fabricio.configuration=azhzLnltbA== fabricio.digests=e30=\n', '|', 'docker', 'build', '--tag', 'fabricio-current-kubernetes:k8s', '-']},
             ],
             expected_result=True,
             expected_config_filename='k8s.yml',
         ),
         created_skip_sentinels_errors=dict(
             init_kwargs=dict(),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult(),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # configuration deploy
                 docker.ImageNotFoundError(),  # image info
                 fabricio.Error(),  # update sentinel images
                 fabricio.Error(),  # configuration images
                 fabricio.Error(),  # build new sentinel image
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-backup-kubernetes:k8s;', 'docker', 'tag', 'fabricio-current-kubernetes:k8s', 'fabricio-backup-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['echo', 'FROM scratch\nLABEL fabricio.configuration=azhzLnltbA==\n', '|', 'docker', 'build', '--tag', 'fabricio-current-kubernetes:k8s', '-']},
             ],
             expected_result=True,
             expected_config_filename='k8s.yml',
         ),
         created_with_custom_image=dict(
             init_kwargs=dict(image='image:tag'),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult(),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # configuration deploy
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # configuration images
                 SucceededResult(),  # build new sentinel image
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-backup-kubernetes:k8s;', 'docker', 'tag', 'fabricio-current-kubernetes:k8s', 'fabricio-backup-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['echo', 'FROM image:tag\nLABEL fabricio.configuration=azhzLnltbA== fabricio.digests=e30=\n', '|', 'docker', 'build', '--tag', 'fabricio-current-kubernetes:k8s', '-']},
             ],
             expected_result=True,
             expected_config_filename='k8s.yml',
         ),
         created_with_custom_image_update_params=dict(
             init_kwargs=dict(image='image:tag'),
             update_kwargs=dict(tag='new-tag', registry='registry', account='account'),
             side_effect=[
                 SucceededResult(),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # configuration deploy
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # configuration images
                 SucceededResult(),  # build new sentinel image
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-backup-kubernetes:k8s;', 'docker', 'tag', 'fabricio-current-kubernetes:k8s', 'fabricio-backup-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['echo', 'FROM registry/account/image:new-tag\nLABEL fabricio.configuration=azhzLnltbA== fabricio.digests=e30=\n', '|', 'docker', 'build', '--tag', 'fabricio-current-kubernetes:k8s', '-']},
             ],
             expected_result=True,
             expected_config_filename='k8s.yml',
         ),
         created_from_empty_image_with_custom_image_update_params=dict(
             init_kwargs=dict(),
             update_kwargs=dict(tag='registry/account/image:tag'),
             side_effect=[
                 SucceededResult(),  # manager status
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # configuration deploy
                 docker.ImageNotFoundError(),  # image info
                 SucceededResult(),  # update sentinel images
                 SucceededResult(),  # configuration images
                 SucceededResult(),  # build new sentinel image
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-backup-kubernetes:k8s;', 'docker', 'tag', 'fabricio-current-kubernetes:k8s', 'fabricio-backup-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['echo', 'FROM registry/account/image:tag\nLABEL fabricio.configuration=azhzLnltbA== fabricio.digests=e30=\n', '|', 'docker', 'build', '--tag', 'fabricio-current-kubernetes:k8s', '-']},
             ],
             expected_result=True,
             expected_config_filename='k8s.yml',
         ),
         updated_configuration_changed=dict(
             init_kwargs=dict(),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult(),  # manager status
                 SucceededResult(json.dumps([{'Config': {
                     'Labels': {
                         'fabricio.configuration': 'b2xkLWNvbXBvc2UueW1s',
                         'fabricio.digests': 'eyJpbWFnZTp0YWciOiAiZGlnZXN0In0=',
                     },
                 }}])),  # image info
                 SucceededResult(),  # configuration deploy
                 SucceededResult('[{"Parent": "backup_parent_id"}]'),  # backup image info
                 SucceededResult(),  # update sentinel images
                 SucceededResult('kind name image:tag'),  # configuration images
                 SucceededResult(), SucceededResult(), SucceededResult(),  # image pull
                 SucceededResult('digest'),  # images digests
                 SucceededResult(),  # build new sentinel image
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-backup-kubernetes:k8s', 'backup_parent_id;', 'docker', 'tag', 'fabricio-current-kubernetes:k8s', 'fabricio-backup-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['docker', 'tag', 'image:tag', 'fabricio-temp-image:image', '&&', 'docker', 'rmi', 'image:tag']}, {'args': ['docker', 'pull', 'image:tag']}, {'args': ['docker', 'rmi', 'fabricio-temp-image:image']},
                 {'args': ['docker', 'inspect', '--type', 'image', '--format', '{{index .RepoDigests 0}}', 'image:tag']},
                 {'args': ['echo', 'FROM scratch\nLABEL fabricio.configuration=azhzLnltbA== fabricio.digests=eyJpbWFnZTp0YWciOiAiZGlnZXN0In0=\n', '|', 'docker', 'build', '--tag', 'fabricio-current-kubernetes:k8s', '-']},
             ],
             expected_result=True,
             expected_config_filename='k8s.yml',
         ),
         updated_image_changed=dict(
             init_kwargs=dict(),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult(),  # manager status
                 SucceededResult(json.dumps([{'Config': {
                     'Labels': {
                         'fabricio.configuration': 'azhzLnltbA==',
                         'fabricio.digests': 'eyJpbWFnZTp0YWciOiAiZGlnZXN0In0=',
                     },
                 }}])),  # image info
                 SucceededResult(), SucceededResult(), SucceededResult(),  # image pull
                 SucceededResult('new-digest'),  # images digests
                 SucceededResult(),  # configuration deploy
                 SucceededResult('[{"Parent": "backup_parent_id"}]'),  # backup image info
                 SucceededResult(),  # update sentinel images
                 SucceededResult('kind name image:tag'),  # configuration images
                 SucceededResult(), SucceededResult(), SucceededResult(),  # image pull
                 SucceededResult('new-digest'),  # images digests
                 SucceededResult(),  # build new sentinel image
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['docker', 'tag', 'image:tag', 'fabricio-temp-image:image', '&&', 'docker', 'rmi', 'image:tag']}, {'args': ['docker', 'pull', 'image:tag']}, {'args': ['docker', 'rmi', 'fabricio-temp-image:image']},
                 {'args': ['docker', 'inspect', '--type', 'image', '--format', '{{index .RepoDigests 0}}', 'image:tag']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-backup-kubernetes:k8s', 'backup_parent_id;', 'docker', 'tag', 'fabricio-current-kubernetes:k8s', 'fabricio-backup-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['docker', 'tag', 'image:tag', 'fabricio-temp-image:image', '&&', 'docker', 'rmi', 'image:tag']}, {'args': ['docker', 'pull', 'image:tag']}, {'args': ['docker', 'rmi', 'fabricio-temp-image:image']},
                 {'args': ['docker', 'inspect', '--type', 'image', '--format', '{{index .RepoDigests 0}}', 'image:tag']},
                 {'args': ['echo', 'FROM scratch\nLABEL fabricio.configuration=azhzLnltbA== fabricio.digests=eyJpbWFnZTp0YWciOiAibmV3LWRpZ2VzdCJ9\n', '|', 'docker', 'build', '--tag', 'fabricio-current-kubernetes:k8s', '-']},
             ],
             expected_result=True,
             expected_config_filename='k8s.yml',
         ),
         updated_images_changed=dict(
             init_kwargs=dict(),
             update_kwargs=dict(),
             side_effect=[
                 SucceededResult(),  # manager status
                 SucceededResult(json.dumps([{'Config': {
                     'Labels': {
                         'fabricio.configuration': 'azhzLnltbA==',
                         'fabricio.digests': 'eyJpbWFnZTE6dGFnIjogImRpZ2VzdDEiLCAiaW1hZ2UyOnRhZyI6ICJkaWdlc3QyIn0=',
                     },
                 }}])),  # image info
                 SucceededResult(), SucceededResult(), SucceededResult(),  # image1 pull
                 SucceededResult(), SucceededResult(), SucceededResult(),  # image2 pull
                 SucceededResult('new-digest1\nnew-digest2\n'),  # images digests
                 SucceededResult(),  # configuration deploy
                 SucceededResult('[{"Parent": "backup_parent_id"}]'),  # backup image info
                 SucceededResult(),  # update sentinel images
                 SucceededResult('kind1 image1 image1:tag\nkind2 image2 image2:tag\n'),  # configuration images
                 SucceededResult(), SucceededResult(), SucceededResult(),  # image1 pull
                 SucceededResult(), SucceededResult(), SucceededResult(),  # image2 pull
                 SucceededResult('new-digest1\nnew-digest2\n'),  # images digests
                 SucceededResult(),  # build new sentinel image
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['docker', 'tag', 'image1:tag', 'fabricio-temp-image:image1', '&&', 'docker', 'rmi', 'image1:tag']}, {'args': ['docker', 'pull', 'image1:tag']}, {'args': ['docker', 'rmi', 'fabricio-temp-image:image1']},
                 {'args': ['docker', 'tag', 'image2:tag', 'fabricio-temp-image:image2', '&&', 'docker', 'rmi', 'image2:tag']}, {'args': ['docker', 'pull', 'image2:tag']}, {'args': ['docker', 'rmi', 'fabricio-temp-image:image2']},
                 {'args': ['docker', 'inspect', '--type', 'image', '--format', '{{index .RepoDigests 0}}', 'image1:tag', 'image2:tag']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-backup-kubernetes:k8s', 'backup_parent_id;', 'docker', 'tag', 'fabricio-current-kubernetes:k8s', 'fabricio-backup-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['docker', 'tag', 'image1:tag', 'fabricio-temp-image:image1', '&&', 'docker', 'rmi', 'image1:tag']}, {'args': ['docker', 'pull', 'image1:tag']}, {'args': ['docker', 'rmi', 'fabricio-temp-image:image1']},
                 {'args': ['docker', 'tag', 'image2:tag', 'fabricio-temp-image:image2', '&&', 'docker', 'rmi', 'image2:tag']}, {'args': ['docker', 'pull', 'image2:tag']}, {'args': ['docker', 'rmi', 'fabricio-temp-image:image2']},
                 {'args': ['docker', 'inspect', '--type', 'image', '--format', '{{index .RepoDigests 0}}', 'image1:tag', 'image2:tag']},
                 {'args': ['echo', 'FROM scratch\nLABEL fabricio.configuration=azhzLnltbA== fabricio.digests=eyJpbWFnZTE6dGFnIjogIm5ldy1kaWdlc3QxIiwgImltYWdlMjp0YWciOiAibmV3LWRpZ2VzdDIifQ==\n', '|', 'docker', 'build', '--tag', 'fabricio-current-kubernetes:k8s', '-']},
             ],
             expected_result=True,
             expected_config_filename='k8s.yml',
         ),
     )
     for case, data in cases.items():
         with self.subTest(case):
             fab.env.command = '{0}__{1}'.format(self, case)
             with mock.patch.dict(fab.env, dict(all_hosts=data.get('all_hosts', ['host']))):
                 stack_module.open.return_value = six.BytesIO(b'k8s.yml')
                 stack_module.open.reset_mock()
                 put.reset_mock()
                 configuration = kubernetes.Configuration(
                     name='k8s',
                     options={'filename': 'k8s.yml'},
                     **data.get('init_kwargs', {})
                 )
                 side_effect = self.command_checker(
                     args_parsers=args_parser,
                     expected_args_set=data.get('expected_command_args', []),
                     side_effects=data.get('side_effect', []),
                 )
                 with mock.patch.object(fabricio, 'run', side_effect=side_effect):
                     with mock.patch('six.BytesIO') as filename:
                         result = configuration.update(**data.get('update_kwargs', {}))
                 self.assertEqual(data['expected_result'], result)
                 expected_compose_file_name = data.get('expected_config_filename')
                 if expected_compose_file_name:
                     stack_module.open.assert_called_once_with(expected_compose_file_name, 'rb')
                     put.assert_called_once()
                     filename.assert_called_once_with(b'k8s.yml')
Example #13
0
 def test_revert(self, put, *args):
     cases = dict(
         worker=dict(
             init_kwargs=dict(),
             side_effect=[
                 FailedResult(),  # manager status
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
             ],
             all_hosts=['host1', 'host2'],
         ),
         reverted=dict(
             init_kwargs=dict(),
             side_effect=[
                 SucceededResult(),  # manager status
                 SucceededResult(json.dumps([{'Config': {
                     'Labels': {
                         'fabricio.configuration': 'b2xkLWNvbXBvc2UueW1s',
                     },
                 }}])),  # image info
                 SucceededResult(),  # configuration deploy
                 SucceededResult('[{"Parent": "current_parent_id"}]'),  # current image info
                 SucceededResult(),  # update sentinel images
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-current-kubernetes:k8s', 'current_parent_id;', 'docker', 'tag', 'fabricio-backup-kubernetes:k8s', 'fabricio-current-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-backup-kubernetes:k8s']},
             ],
             expected_compose_file=b'old-compose.yml',
         ),
         reverted_with_pod_update=dict(
             init_kwargs=dict(),
             side_effect=[
                 SucceededResult(),  # manager status
                 SucceededResult(json.dumps([{'Config': {
                     'Labels': {
                         'fabricio.configuration': 'Y29tcG9zZS55bWw=',  # compose.yml
                         'fabricio.digests': 'eyJpbWFnZTp0YWciOiAiZGlnZXN0In0=',  # {"image:tag": "digest"}
                     },
                 }}])),  # image info
                 SucceededResult(),  # configuration deploy
                 SucceededResult('kind name image:tag\n'),  # configuration services
                 SucceededResult(),  # pod update
                 SucceededResult('[{"Parent": "current_parent_id"}]'),  # current image info
                 SucceededResult(),  # update sentinel images
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['kubectl', 'set', 'image', 'kind', 'name=digest']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-current-kubernetes:k8s', 'current_parent_id;', 'docker', 'tag', 'fabricio-backup-kubernetes:k8s', 'fabricio-current-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-backup-kubernetes:k8s']},
             ],
             expected_compose_file=b'compose.yml',
         ),
         reverted_with_pods_updates=dict(
             init_kwargs=dict(),
             side_effect=[
                 SucceededResult(),  # manager status
                 SucceededResult(json.dumps([{'Config': {
                     'Labels': {
                         'fabricio.configuration': 'Y29tcG9zZS55bWw=',  # compose.yml
                         'fabricio.digests': 'eyJpbWFnZTE6dGFnIjogImRpZ2VzdDEiLCAiaW1hZ2UyOnRhZyI6ICJkaWdlc3QyIn0=',  # {"image1:tag": "digest1", "image2:tag": "digest2"}
                     },
                 }}])),  # image info
                 SucceededResult(),  # configuration deploy
                 SucceededResult('kind1 name1 image1:tag\nkind2 name2 image2:tag'),  # configuration services
                 SucceededResult(),  # pod update
                 SucceededResult(),  # pod update
                 SucceededResult('[{"Parent": "current_parent_id"}]'),  # current image info
                 SucceededResult(),  # update sentinel images
             ],
             expected_command_args=[
                 {'args': ['kubectl', 'config', 'current-context']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-backup-kubernetes:k8s']},
                 {'args': ['kubectl', 'apply', '--filename=k8s.yml']},
                 {'args': ['kubectl', 'get', '--output', 'go-template', '--filename', 'k8s.yml', '--template', r'{{define "images"}}{{$kind := .kind}}{{$name := .metadata.name}}{{with .spec.template.spec.containers}}{{range .}}{{$kind}}/{{$name}} {{.name}} {{.image}}{{"\n"}}{{end}}{{end}}{{end}}{{if eq .kind "List"}}{{range .items}}{{template "images" .}}{{end}}{{else}}{{template "images" .}}{{end}}']},
                 {'args': ['kubectl', 'set', 'image', 'kind1', 'name1=digest1']},
                 {'args': ['kubectl', 'set', 'image', 'kind2', 'name2=digest2']},
                 {'args': ['docker', 'inspect', '--type', 'image', 'fabricio-current-kubernetes:k8s']},
                 {'args': ['docker', 'rmi', 'fabricio-current-kubernetes:k8s', 'current_parent_id;', 'docker', 'tag', 'fabricio-backup-kubernetes:k8s', 'fabricio-current-kubernetes:k8s;', 'docker', 'rmi', 'fabricio-backup-kubernetes:k8s']},
             ],
             expected_compose_file=b'compose.yml',
         ),
     )
     for case, data in cases.items():
         with self.subTest(case=case):
             fab.env.command = '{0}__{1}'.format(self, case)
             put.reset_mock()
             with mock.patch.dict(fab.env, dict(all_hosts=data.get('all_hosts', ['host']))):
                 configuration = kubernetes.Configuration(
                     name='k8s',
                     options={'filename': 'k8s.yml'},
                     **data.get('init_kwargs', {})
                 )
                 side_effects = data.get('side_effect', [])
                 side_effect = self.command_checker(
                     args_parsers=args_parser,
                     expected_args_set=data.get('expected_command_args', []),
                     side_effects=side_effects,
                 )
                 with mock.patch.object(fabricio, 'run', side_effect=side_effect) as run:
                     with mock.patch('six.BytesIO') as compose_file:
                         configuration.revert()
                 self.assertEqual(run.call_count, len(side_effects))
                 expected_compose_file = data.get('expected_compose_file')
                 if expected_compose_file:
                     put.assert_called_once()
                     compose_file.assert_called_once_with(expected_compose_file)
                 else:
                     put.assert_not_called()
Example #14
0
    def test_migrate_back(self, *args):
        cases = dict(
            container_no_change=dict(
                side_effect=(
                    SucceededResult('[{"Image": "current_image_id"}]'),
                    SucceededResult('app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'),
                    SucceededResult('[{"Image": "backup_image_id"}]'),
                    SucceededResult('app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'),
                ),
                args_parsers=[
                    args_parser,
                    docker_run_args_parser,
                    args_parser,
                    docker_run_args_parser,
                ],
                expected_args=[
                    dict(args=[
                        'docker', 'inspect', '--type', 'container', 'name'
                    ]),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    dict(args=[
                        'docker', 'inspect', '--type', 'container',
                        'name_backup'
                    ]),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'backup_image_id',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                ],
                service_type=DjangoContainer,
            ),
            service_no_change=dict(
                side_effect=(
                    SucceededResult(
                        '[{"Spec":{"TaskTemplate":{"ContainerSpec":{"Image":"image@digest"}},"Labels":{"_backup_options":"{\\"image\\": \\"image@backup\\"}"}}}]'
                    ),
                    SucceededResult('app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'),
                    SucceededResult('app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'),
                ),
                args_parsers=[
                    args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                ],
                expected_args=[
                    dict(args=['docker', 'service', 'inspect', 'name']),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@backup',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                ],
                service_type=DjangoService,
            ),
            container_no_migrations=dict(
                side_effect=(
                    SucceededResult('[{"Image": "current_image_id"}]'),
                    SucceededResult(),
                    SucceededResult('[{"Image": "backup_image_id"}]'),
                    SucceededResult(),
                ),
                args_parsers=[
                    args_parser,
                    docker_run_args_parser,
                    args_parser,
                    docker_run_args_parser,
                ],
                expected_args=[
                    dict(args=[
                        'docker', 'inspect', '--type', 'container', 'name'
                    ]),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    dict(args=[
                        'docker', 'inspect', '--type', 'container',
                        'name_backup'
                    ]),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'backup_image_id',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                ],
                service_type=DjangoContainer,
            ),
            service_no_migrations=dict(
                side_effect=(
                    SucceededResult(
                        '[{"Spec":{"TaskTemplate":{"ContainerSpec":{"Image":"image@digest"}},"Labels":{"_backup_options":"{\\"image\\": \\"image@backup\\"}"}}}]'
                    ),
                    SucceededResult(),
                    SucceededResult(),
                ),
                args_parsers=[
                    args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                ],
                expected_args=[
                    dict(args=['docker', 'service', 'inspect', 'name']),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@backup',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                ],
                service_type=DjangoService,
            ),
            container_regular=dict(
                side_effect=(
                    SucceededResult('[{"Image": "current_image_id"}]'),
                    SucceededResult('app0.0001_initial\n'
                                    'app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'
                                    'app3.0001_initial\n'
                                    'app2.0002_foo\n'
                                    'app3.0002_foo\n'),
                    SucceededResult('[{"Image": "backup_image_id"}]'),
                    SucceededResult('app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'),
                    SucceededResult(),
                    SucceededResult(),
                    SucceededResult(),
                ),
                args_parsers=[
                    args_parser,
                    docker_run_args_parser,
                    args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                ],
                expected_args=[
                    dict(args=[
                        'docker', 'inspect', '--type', 'container', 'name'
                    ]),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    dict(args=[
                        'docker', 'inspect', '--type', 'container',
                        'name_backup'
                    ]),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'backup_image_id',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app3', 'zero'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app2', '0001_initial'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app0', 'zero'
                        ],
                    },
                ],
                service_type=DjangoContainer,
            ),
            service_regular=dict(
                side_effect=(
                    SucceededResult(
                        '[{"Spec":{"TaskTemplate":{"ContainerSpec":{"Image":"image@digest"}},"Labels":{"_backup_options":"{\\"image\\": \\"image@backup\\"}"}}}]'
                    ),
                    SucceededResult('app0.0001_initial\n'
                                    'app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'
                                    'app3.0001_initial\n'
                                    'app2.0002_foo\n'
                                    'app3.0002_foo\n'),
                    SucceededResult('app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'),
                    SucceededResult(),
                    SucceededResult(),
                    SucceededResult(),
                ),
                args_parsers=[
                    args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                ],
                expected_args=[
                    dict(args=['docker', 'service', 'inspect', 'name']),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@backup',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app3', 'zero'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app2', '0001_initial'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app0', 'zero'
                        ],
                    },
                ],
                service_type=DjangoService,
            ),
            container_custom_options=dict(
                side_effect=(
                    SucceededResult('[{"Image": "current_image_id"}]'),
                    SucceededResult('app0.0001_initial\n'
                                    'app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'
                                    'app3.0001_initial\n'
                                    'app2.0002_foo\n'
                                    'app3.0002_foo\n'),
                    SucceededResult('[{"Image": "backup_image_id"}]'),
                    SucceededResult('app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'),
                    SucceededResult(),
                    SucceededResult(),
                    SucceededResult(),
                ),
                expected_args=[
                    dict(args=[
                        'docker', 'inspect', '--type', 'container', 'name'
                    ]),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'volume': ['volumes'],
                        'link': ['links'],
                        'add-host': ['hosts'],
                        'net':
                        'network',
                        'stop-signal':
                        'stop_signal',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    dict(args=[
                        'docker', 'inspect', '--type', 'container',
                        'name_backup'
                    ]),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'volume': ['volumes'],
                        'link': ['links'],
                        'add-host': ['hosts'],
                        'net':
                        'network',
                        'stop-signal':
                        'stop_signal',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'backup_image_id',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'volume': ['volumes'],
                        'link': ['links'],
                        'add-host': ['hosts'],
                        'net':
                        'network',
                        'stop-signal':
                        'stop_signal',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app3', 'zero'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'volume': ['volumes'],
                        'link': ['links'],
                        'add-host': ['hosts'],
                        'net':
                        'network',
                        'stop-signal':
                        'stop_signal',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app2', '0001_initial'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'volume': ['volumes'],
                        'link': ['links'],
                        'add-host': ['hosts'],
                        'net':
                        'network',
                        'stop-signal':
                        'stop_signal',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app0', 'zero'
                        ],
                    },
                ],
                args_parsers=[
                    args_parser,
                    docker_run_args_parser,
                    args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                ],
                init_kwargs=dict(
                    options=dict(
                        user='******',
                        env='env',
                        volumes='volumes',
                        links='links',
                        hosts='hosts',
                        network='network',
                        stop_signal='stop_signal',
                        ports='ports',
                        restart_policy='restart_policy',
                    ),
                    command='command',
                    stop_timeout='stop_timeout',
                ),
                service_type=DjangoContainer,
            ),
            service_custom_options=dict(
                side_effect=(
                    SucceededResult(
                        '[{"Spec":{"TaskTemplate":{"ContainerSpec":{"Image":"image@digest"}},"Labels":{"_backup_options":"{\\"image\\": \\"image@backup\\"}"}}}]'
                    ),
                    SucceededResult('app0.0001_initial\n'
                                    'app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'
                                    'app3.0001_initial\n'
                                    'app2.0002_foo\n'
                                    'app3.0002_foo\n'),
                    SucceededResult('app1.0001_initial\n'
                                    'app1.0002_foo\n'
                                    'app2.0001_initial\n'),
                    SucceededResult(),
                    SucceededResult(),
                    SucceededResult(),
                ),
                expected_args=[
                    dict(args=['docker', 'service', 'inspect', 'name']),
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'label': ['label'],
                        'network':
                        'network',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'label': ['label'],
                        'network':
                        'network',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@backup',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'label': ['label'],
                        'network':
                        'network',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app3', 'zero'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'label': ['label'],
                        'network':
                        'network',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app2', '0001_initial'
                        ],
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'user':
                        '******',
                        'env': ['env'],
                        'label': ['label'],
                        'network':
                        'network',
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'image@digest',
                        'command': [
                            'python', 'manage.py', 'migrate', '--no-input',
                            'app0', 'zero'
                        ],
                    },
                ],
                args_parsers=[
                    args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                    docker_run_args_parser,
                ],
                init_kwargs=dict(
                    command='command',
                    args='args',
                    options=dict(
                        user='******',
                        env='env',
                        network='network',
                        label='label',
                        stop_grace_period='stop_timeout',
                        restart_condition='restart_condition',
                        publish='ports',
                        mount='mounts',
                        replicas='replicas',
                        constraint='constraints',
                        container_label='container_labels',
                        mode='mode',
                    ),
                ),
                service_type=DjangoService,
            ),
        )

        def test_command(command, *args, **kwargs):
            parser = next(args_parsers)
            options = parser.parse_args(command.split())
            self.assertDictEqual(vars(options), next(expected_args))
            return next(side_effect)

        for case, data in cases.items():
            expected_args = iter(data['expected_args'])
            args_parsers = iter(data['args_parsers'])
            side_effect = iter(data['side_effect'])
            with self.subTest(case=case):
                fab.env.command = '{0}__{1}'.format(self, case)
                with mock.patch.object(
                        fabricio,
                        'run',
                        side_effect=test_command,
                ) as run:
                    container = data['service_type'](name='name',
                                                     image='image:tag',
                                                     **data.get(
                                                         'init_kwargs', {}))
                    container.migrate_back()
                    self.assertEqual(run.call_count, len(data['side_effect']))
Example #15
0
    def test_migrate_back_errors(self):
        cases = dict(
            current_container_not_found=dict(
                expected_exception=RuntimeError,
                side_effect=(RuntimeError(), ),
                args_parsers=[
                    docker_inspect_args_parser,
                ],
                expected_args=[
                    {
                        'executable': ['docker', 'inspect'],
                        'type': 'container',
                        'image_or_container': 'name',
                    },
                ],
            ),
            backup_container_not_found=dict(
                expected_exception=RuntimeError,
                side_effect=(
                    SucceededResult('[{"Image": "current_image_id"}]'),
                    SucceededResult('app1.0001_initial\n'),
                    RuntimeError(),
                ),
                args_parsers=[
                    docker_inspect_args_parser,
                    docker_run_args_parser,
                    docker_inspect_args_parser,
                ],
                expected_args=[
                    {
                        'executable': ['docker', 'inspect'],
                        'type': 'container',
                        'image_or_container': 'name',
                    },
                    {
                        'executable': ['docker'],
                        'run_or_create': ['run'],
                        'rm':
                        True,
                        'tty':
                        True,
                        'interactive':
                        True,
                        'image':
                        'current_image_id',
                        'command': [
                            'python', 'manage.py', 'showmigrations', '--plan',
                            '|', 'egrep', '"^\\[X\\]"', '|', 'awk', '"{print',
                            '$2}"', '&&', 'test', '${PIPESTATUS[0]}', '-eq',
                            '0'
                        ],
                    },
                    {
                        'executable': ['docker', 'inspect'],
                        'type': 'container',
                        'image_or_container': 'name_backup',
                    },
                ],
            ),
        )

        def test_command(command, *args, **kwargs):
            parser = next(args_parsers)
            options = parser.parse_args(command.split())
            self.assertDictEqual(vars(options), next(expected_args))
            result = next(side_effect)
            if isinstance(result, Exception):
                raise result
            return result

        for case, data in cases.items():
            fab.env.command = '{0}__{1}'.format(self, case)
            expected_args = iter(data['expected_args'])
            args_parsers = iter(data['args_parsers'])
            side_effect = iter(data['side_effect'])
            with self.subTest(case=case):
                with mock.patch.object(fabricio,
                                       'run',
                                       side_effect=test_command):
                    container = DjangoContainer(name='name', image='image')
                    with self.assertRaises(data['expected_exception']):
                        container.migrate_back()
Example #16
0
    def test_migrate_back(self):
        cases = dict(
            no_change=dict(
                side_effect=(
                    SucceededResult('[{"Image": "current_image_id"}]'),
                    SucceededResult(
                        'app1.0001_initial\n'
                        'app1.0002_foo\n'
                        'app2.0001_initial\n'
                    ),
                    SucceededResult('[{"Image": "backup_image_id"}]'),
                    SucceededResult(
                        'app1.0001_initial\n'
                        'app1.0002_foo\n'
                        'app2.0001_initial\n'
                    ),
                ),
                expected_commands=[
                    mock.call('docker inspect --type container name'),
                    mock.call('docker run --rm --tty --interactive current_image_id python manage.py showmigrations --plan | egrep "^\[X\]" | awk "{print \$2}"', quiet=True),
                    mock.call('docker inspect --type container name_backup'),
                    mock.call('docker run --rm --tty --interactive backup_image_id python manage.py showmigrations --plan | egrep "^\[X\]" | awk "{print \$2}"', quiet=True),
                ],
            ),
            no_migrations=dict(
                side_effect=(
                    SucceededResult('[{"Image": "current_image_id"}]'),
                    SucceededResult(),
                    SucceededResult('[{"Image": "backup_image_id"}]'),
                    SucceededResult(),
                ),
                expected_commands=[
                    mock.call('docker inspect --type container name'),
                    mock.call('docker run --rm --tty --interactive current_image_id python manage.py showmigrations --plan | egrep "^\[X\]" | awk "{print \$2}"', quiet=True),
                    mock.call('docker inspect --type container name_backup'),
                    mock.call('docker run --rm --tty --interactive backup_image_id python manage.py showmigrations --plan | egrep "^\[X\]" | awk "{print \$2}"', quiet=True),
                ],
            ),
            regular=dict(
                side_effect=(
                    SucceededResult('[{"Image": "current_image_id"}]'),
                    SucceededResult(
                        'app0.0001_initial\n'
                        'app1.0001_initial\n'
                        'app1.0002_foo\n'
                        'app2.0001_initial\n'
                        'app3.0001_initial\n'
                        'app2.0002_foo\n'
                        'app3.0002_foo\n'
                    ),
                    SucceededResult('[{"Image": "backup_image_id"}]'),
                    SucceededResult(
                        'app1.0001_initial\n'
                        'app1.0002_foo\n'
                        'app2.0001_initial\n'
                    ),
                    SucceededResult(),
                    SucceededResult(),
                    SucceededResult(),
                ),
                expected_commands=[
                    mock.call('docker inspect --type container name'),
                    mock.call('docker run --rm --tty --interactive current_image_id python manage.py showmigrations --plan | egrep "^\[X\]" | awk "{print \$2}"', quiet=True),
                    mock.call('docker inspect --type container name_backup'),
                    mock.call('docker run --rm --tty --interactive backup_image_id python manage.py showmigrations --plan | egrep "^\[X\]" | awk "{print \$2}"', quiet=True),
                    mock.call('docker run --rm --tty --interactive current_image_id python manage.py migrate --no-input app3 zero', quiet=False),
                    mock.call('docker run --rm --tty --interactive current_image_id python manage.py migrate --no-input app2 0001_initial', quiet=False),
                    mock.call('docker run --rm --tty --interactive current_image_id python manage.py migrate --no-input app0 zero', quiet=False),
                ],
            ),
            with_container_custom_options=dict(
                side_effect=(
                    SucceededResult('[{"Image": "current_image_id"}]'),
                    SucceededResult(
                        'app0.0001_initial\n'
                        'app1.0001_initial\n'
                        'app1.0002_foo\n'
                        'app2.0001_initial\n'
                        'app3.0001_initial\n'
                        'app2.0002_foo\n'
                        'app3.0002_foo\n'
                    ),
                    SucceededResult('[{"Image": "backup_image_id"}]'),
                    SucceededResult(
                        'app1.0001_initial\n'
                        'app1.0002_foo\n'
                        'app2.0001_initial\n'
                    ),
                    SucceededResult(),
                    SucceededResult(),
                    SucceededResult(),
                ),
                expected_commands=[
                    mock.call('docker inspect --type container name'),
                    mock.call('docker run --user user --env env --volume volumes --link links --add-host hosts --net network --stop-signal stop_signal --rm --tty --interactive current_image_id python manage.py showmigrations --plan | egrep "^\[X\]" | awk "{print \$2}"', quiet=True),
                    mock.call('docker inspect --type container name_backup'),
                    mock.call('docker run --user user --env env --volume volumes --link links --add-host hosts --net network --stop-signal stop_signal --rm --tty --interactive backup_image_id python manage.py showmigrations --plan | egrep "^\[X\]" | awk "{print \$2}"', quiet=True),
                    mock.call('docker run --user user --env env --volume volumes --link links --add-host hosts --net network --stop-signal stop_signal --rm --tty --interactive current_image_id python manage.py migrate --no-input app3 zero', quiet=False),
                    mock.call('docker run --user user --env env --volume volumes --link links --add-host hosts --net network --stop-signal stop_signal --rm --tty --interactive current_image_id python manage.py migrate --no-input app2 0001_initial', quiet=False),
                    mock.call('docker run --user user --env env --volume volumes --link links --add-host hosts --net network --stop-signal stop_signal --rm --tty --interactive current_image_id python manage.py migrate --no-input app0 zero', quiet=False),
                ],
                init_kwargs=dict(
                    options=dict(
                        user='******',
                        env='env',
                        volumes='volumes',
                        links='links',
                        hosts='hosts',
                        network='network',
                        stop_signal='stop_signal',

                        ports='ports',
                        restart_policy='restart_policy',
                    ),
                    cmd='cmd',
                    stop_timeout='stop_timeout',
                ),
            ),
        )
        for case, data in cases.items():
            with self.subTest(case=case):
                side_effect = data['side_effect']
                expected_commands = data['expected_commands']
                with mock.patch.object(
                    fabricio,
                    'run',
                    side_effect=side_effect,
                ) as run:
                    container = DjangoContainer(
                        name='name',
                        image='image:tag',
                        **data.get('init_kwargs', {})
                    )
                    container.migrate_back()
                    self.assertListEqual(run.mock_calls, expected_commands)