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'])
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')
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()
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)
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()
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'])
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, )
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()
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()
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()
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')
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()
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']))
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()
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)