def test_no_options(self): """ Make sure that with no extra options a valid docker build command is created """ result = builder('build') self.assertEqual(str(result), "docker build .") result = builder('build', pretty=False) self.assertEqual(str(result), "docker build .")
def test_list_arg_invalid_type_raises(self): """ Make sure that if something that doesn't boil down to a string raises an exception """ result = builder('run', pretty=False).image('busybox').attach(0) self.assertEqual(str(result), "docker run --attach 0 busybox") result = builder('run', pretty=False).image('busybox').attach([0]) self.assertEqual(str(result), "docker run --attach 0 busybox") result = builder('run', pretty=False).image('busybox').attach({0}) self.assertEqual(str(result), "docker run --attach 0 busybox") result = builder('run', pretty=False).image('busybox').attach((0)) self.assertEqual(str(result), "docker run --attach 0 busybox")
def test_raises_missing_positional(self): """ docker run needs an image to start a container from, and there's no good way to assume a default, so we just force the user to specify an image before they try to get the cli command """ result = builder('exec') with self.assertRaises(ControlException): str(result) result = result.container('foo') with self.assertRaises(ControlException): str(result) result = builder('exec').command('ps aux') with self.assertRaises(ControlException): str(result)
def test_raises_missing_positional(self): """ docker rm should raise an error when there were no containers specified """ result = builder('rm') with self.assertRaises(ControlException): str(result)
def test_sorting(self): """ Make sure that flags are spit out in alphabetized order so they easier to visually inspect. But make sure that multiple arguments to flags are kept in the order they were specified, this way we don't break intentional overrides """ result = builder('run', pretty=False) \ .image('ubuntu') \ .tty() \ .interactive() \ .volume('/mnt:/mnt') \ .volume('/home:/home') \ .volume('/var/foo:/var/foo') \ .user('1000') \ .volume('/var/foo:/usr/share/lib/foo') \ .volume([ '/var/lib/bucket:/var/lib/bucket', '/home:/home', ]) \ .volume({'/var/log/foo:/foo/log'}) \ .entrypoint('/bin/bash') self.assertEqual( str(result), "docker run --entrypoint /bin/bash --interactive --tty " "--user 1000 " "--volume /mnt:/mnt " "--volume /home:/home " "--volume /var/foo:/var/foo " "--volume /var/foo:/usr/share/lib/foo " "--volume /var/lib/bucket:/var/lib/bucket " "--volume /home:/home " "--volume /var/log/foo:/foo/log " "ubuntu")
def test_nondefault_path(self): """ Make sure that changing the path from the default results in modified output """ result = builder('build', pretty=False).tag('foobar').path('build') self.assertEqual(str(result), "docker build --tag foobar build")
def dump_run(self, prod=False, pretty=True): """dump out a CLI version of how this container would be started""" rep = builder('run', pretty=pretty).image(self.image) \ .volume(sorted(self.volumes_for(prod))) if self.env_file: rep = rep.env_file(self.env_file) for k, v in self.container.items(): rep = { 'command': rep.command, 'cpu_shares': rep.cpu_shares, 'detach': rep.detach, 'entrypoint': rep.entrypoint, 'environment': rep.env, 'hostname': rep.hostname, 'name': rep.name, 'ports': rep.publish, 'stdin_open': rep.interactive, 'tty': rep.tty, 'user': rep.user, 'working_dir': rep.workdir, }[k](v) for k, v in self.host_config.items(): rep = { 'devices': rep.device, 'dns': rep.dns, 'dns_search': rep.dns_search, 'extra_hosts': rep.add_host, 'ipc_mode': rep.ipc, 'links': rep.link, 'volumes_from': rep.volumes_from, }[k](v) return rep.detach()
def test_pass_command_list(self): """ docker-py accepts command as a list. I want to be able to pass that straight into this library and have it work correctly """ result = builder('exec', pretty=False).container('foo').command(['ps', 'aux']) self.assertEqual(str(result), "docker exec foo ps aux")
def test_pass_command_list(self): """ docker-py accepts command as a list. I want to be able to pass that straight into this library and have it work correctly """ result = builder('run', pretty=False).image('busybox').command(['ps', 'aux']) self.assertEqual(str(result), "docker run busybox ps aux")
def test_single_values_overwrite(self): """ When you set a single value, and then set it again. The later value should have precedence """ result = builder('build', pretty=False).tag('foobar').tag('alpine:test') self.assertEqual(str(result), "docker build --tag alpine:test .")
def test_adding_containers(self): """make sure that containers are kept in order, and don't error""" result = builder('rm').container('abernathy') self.assertEqual(str(result), "docker rm abernathy") result = result.container(['foo', 'bar']).container({'foobar'}) result = result.container(('hawking')).container(0) self.assertEqual(str(result), "docker rm abernathy foo bar foobar hawking 0")
def test_raises_if_no_image(self): """ docker run needs an image to start a container from, and there's no good way to assume a default, so we just force the user to specify an image before they try to get the cli command """ result = builder('run').env('FOO=bar').user('1000').detach() with self.assertRaises(ControlException): str(result)
def test_no_pretty(self): """Everything should show up on one line""" result = builder('build', pretty=False).tag('busybox').pull().rm() self.assertEqual(str(result), "docker build --pull --rm --tag busybox .") result = builder('run', pretty=False).image('busybox') self.assertEqual(str(result), "docker run busybox") result = builder('run', pretty=False) \ .detach() \ .tty() \ .user('foobar') \ .hostname('foo') \ .volume('/mnt:/mnt') \ .image('busybox') \ .command('cat') self.assertEqual( str(result), "docker run --detach --hostname foo --tty " "--user foobar --volume /mnt:/mnt busybox cat")
def test_bool_arg_removal(self): """ Test that passing a False to a boolean setter removes the option from output """ result = builder('build', pretty=False).tty(False) self.assertEqual(str(result), "docker build .") result = result.tty() self.assertEqual(str(result), "docker build --tty .") result = result.tty(False) self.assertEqual(str(result), "docker build .")
def dump_build(self, prod=False, pretty=True): """dump out a CLI version of how this image would be built""" rep = builder('build', pretty=pretty) \ .tag(self.image) \ .path(dirname(self.controlfile)) \ .file(self.dockerfile['prod'] if prod else self.dockerfile['dev']) \ .pull(options.pull) \ .rm(options.no_rm) \ .force_rm(options.force) \ .no_cache(not options.cache) return rep
def test_pretty(self): """ Flags should show up one per line. Positional arguments should all be on one line. """ result = builder('build').tag('busybox').pull().rm() self.assertEqual( str(result), "docker build \\\n\t--pull \\\n\t--rm \\\n\t" "--tag busybox \\\n\t.") result = builder('run').image('busybox') self.assertEqual(str(result), "docker run busybox") result = builder('run') \ .detach() \ .tty() \ .user('foobar') \ .hostname('foo') \ .volume('/mnt:/mnt') \ .image('busybox') \ .command('cat') self.assertEqual( str(result), "docker run \\\n\t--detach \\\n\t--hostname foo \\\n\t" "--tty \\\n\t--user foobar \\\n\t" "--volume /mnt:/mnt \\\n\tbusybox \\\n\tcat")
def test_incompatible_command_raises(self): """make sure a command that isn't a list or a string are unhappy""" with self.assertRaises(TypeError): builder('exec').command((0, 1))
def command(args, ctrl): """ Call a custom command on a container. If the container wasn't running before the command was run, then the container is left in the same state. """ module_logger.debug(", ".join(sorted(args.services))) no_err = True services = sorted( ctrl.services[name] for name in args.services if (options.command in ctrl.services[name].commands.keys() or '*' in ctrl.services[name].commands.keys())) for service in services: if len(services) > 1: module_logger.info('running command in %s', service['name']) cmd = service.commands[options.command if options.command in service.commands.keys() else '*' ].format(COMMAND=options.command) # Check if the container is running. If we need to run the command # exclusively, take down the container. # Once that decision is made, we need the container running. Exec'ing # a command into a running container produces better output than # running the container with the command, so we run the container # with a command that simply holds the container open so we can exec # the command we want into the container. put_it_back = False try: container = CreatedContainer(service['name'], service) if options.replace: if options.dump: print( builder('stop').container(service['name']).time(service.expected_timeout) ) print( builder('rm').container(service['name']).time(service.expected_timeout) ) else: container.stop() container.remove() put_it_back = True raise ContainerDoesNotExist(service['name']) if not container.inspect['State']['Running']: container.remove() raise ContainerDoesNotExist(service['name']) else: kill_it = False except ContainerDoesNotExist: module_logger.debug("saving service: ('%s', '%s')", service['entrypoint'], service['command']) saved_entcmd = (service['entrypoint'], service['command'], service['stdin_open']) service['entrypoint'] = '/bin/cat' service['command'] = '' service['stdin_open'] = True service['tty'] = True container = Container(service) kill_it = True # TODO: when does this get printed? # if not options.dump: # print(service.dump_run()) # else: if not options.dump: try: container = container.create(prod=options.prod) container.start() except ImageNotFound as e: module_logger.critical(e) continue except ContainerException as e: module_logger.debug('outer start containerexception caught') module_logger.critical(e) no_err = False # module_logger.debug('Container running: %s', container.inspect['State']['Running']) # time.sleep(1) # container.check() # module_logger.debug('Container running: %s', container.inspect['State']['Running']) # We take the generator that docker gives us for the exec output and # print it to the console. The Exec spawned a TTY so programs that care # will output color. if options.dump and not kill_it: print(builder('exec', pretty=False).container(service['name']).command(cmd).tty()) elif options.dump: ent_, _, cmd_ = cmd.partition(' ') run = service.dump_run() \ .entrypoint(ent_) \ .command(cmd_) \ .rm() \ .tty() \ .interactive(saved_entcmd[2]) print(run) else: gen = (l.decode('utf-8') for l in container.exec(cmd)) for line in gen: print(line, end='') if container.inspect_exec()['ExitCode'] != 0: no_err = False # After the command we make sure to clean up the container. Since we # spawned the container running a command that just holds the container # open, if we replaced a running container we need to take down this # dummy container and start it with its normal entrypoint if (put_it_back or kill_it) and not options.dump: if options.force: module_logger.debug('Killing %s', service['name']) container.kill() else: module_logger.debug('Stopping %s', service['name']) container.stop() module_logger.debug('Removing %s', service['name']) container.remove() if options.wipe: container.remove_volumes() if put_it_back: if saved_entcmd[0]: service['entrypoint'] = saved_entcmd[0] else: del service['entrypoint'] if saved_entcmd[1]: service['command'] = saved_entcmd[1] else: del service['command'] if isinstance(saved_entcmd[2], bool): service['stdin_open'] = saved_entcmd[2] else: del service['stdin_open'] module_logger.debug("retrieved service: ('%s', '%s')", service['entrypoint'], service['command']) container = Container(service) if options.dump: print(service.dump_run()) else: try: container = container.create(prod=options.prod) container.start() except ContainerException as e: module_logger.debug('outer start containerexception caught') module_logger.critical(e) no_err = False return no_err
def test_tagged(self): """Make sure that a tag is stuck on correctly""" result = builder('build', pretty=False).tag('foobar') self.assertEqual(str(result), "docker build --tag foobar .")
def test_broken_path(self): """In case you do weird stuff and null out the default path""" result = builder('build').path('') with self.assertRaises(ControlException): str(result)