diff --git a/README.md b/README.md index 442123b55f..39be61daaf 100644 --- a/README.md +++ b/README.md @@ -232,7 +232,7 @@ Identical to the `docker search` command. ```python c.start(container, binds=None, port_bindings=None, lxc_conf=None, publish_all_ports=False, links=None, privileged=False, - dns=None, dns_search=None, volumes_from=None, network_mode=None) + dns=None, dns_search=None, volumes_from=None, network_mode=None, restart_policy=None) ``` Similar to the `docker start` command, but doesn't support attach @@ -258,6 +258,27 @@ docker bridge, 'none': no networking for this container, 'container:[name|id]': reuses another container network stack), 'host': use the host network stack inside the container. +`restart_policy` is available since v1.2.0 and sets the RestartPolicy for how a container should or should not be +restarted on exit. By default the policy is set to no meaning do not restart the container when it exits. +The user may specify the restart policy as a dictionary for example: +for example: +``` +{ + "MaximumRetryCount": 0, + "Name": "always" +} +``` +for always restarting the container on exit or can specify to restart the container to restart on failure and can limit +number of restarts. +for example: +``` +{ + "MaximumRetryCount": 5, + "Name": "on-failure" +} +``` + + ```python c.stop(container, timeout=10) ``` diff --git a/docker/client.py b/docker/client.py index c4871a693c..93f6d35f9d 100644 --- a/docker/client.py +++ b/docker/client.py @@ -806,7 +806,8 @@ def search(self, term): def start(self, container, binds=None, port_bindings=None, lxc_conf=None, publish_all_ports=False, links=None, privileged=False, - dns=None, dns_search=None, volumes_from=None, network_mode=None): + dns=None, dns_search=None, volumes_from=None, network_mode=None, + restart_policy=None): if isinstance(container, dict): container = container.get('Id') @@ -859,13 +860,15 @@ def start(self, container, binds=None, port_bindings=None, lxc_conf=None, if volumes_from is not None: warnings.warn(warning_message.format('volumes_from'), DeprecationWarning) - if dns_search: start_config['DnsSearch'] = dns_search if network_mode: start_config['NetworkMode'] = network_mode + if restart_policy: + start_config['RestartPolicy'] = restart_policy + url = self._url("/containers/{0}/start".format(container)) res = self._post_json(url, data=start_config) self._raise_for_status(res) diff --git a/tests/integration_test.py b/tests/integration_test.py index e9d2fdc673..170edd7df7 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -612,6 +612,23 @@ def runTest(self): self.assertIn('{0}_NAME='.format(link_env_prefix2), logs) self.assertIn('{0}_ENV_FOO=1'.format(link_env_prefix2), logs) + +class TestRestartingContainer(BaseTestCase): + def runTest(self): + container = self.client.create_container('busybox', ['false']) + id = container['Id'] + self.client.start(id, restart_policy={ + { + "Name": "on-failure", + "MaximumRetryCount": 1 + } + }) + self.client.wait(id) + self.client.remove_container(id) + containers = self.client.containers(all=True) + res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] + self.assertEqual(len(res), 0) + ################# # LINKS TESTS # ################# diff --git a/tests/test.py b/tests/test.py index 4fced9359a..e2fe5fad59 100644 --- a/tests/test.py +++ b/tests/test.py @@ -1,7 +1,7 @@ # Copyright 2013 dotCloud inc. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. # You may obtain a copy of the License at # http://www.apache.org/licenses/LICENSE-2.0 @@ -60,6 +60,7 @@ def fake_resp(url, data=None, **kwargs): status_code, content = fake_api.fake_responses[url]() return response(status_code=status_code, content=content) + fake_request = mock.Mock(side_effect=fake_resp) url_prefix = 'http+unix://var/run/docker.sock/v{0}/'.format( docker.client.DEFAULT_DOCKER_API_VERSION) @@ -616,7 +617,7 @@ def test_start_container_with_binds_rw(self): mount_origin = '/tmp' self.client.start(fake_api.FAKE_CONTAINER_ID, binds={mount_origin: { - "bind": mount_dest, "ro": False}}) + "bind": mount_dest, "ro": False}}) except Exception as e: self.fail('Command should not raise exception: {0}'.format(e)) @@ -812,6 +813,34 @@ def test_start_container_with_dict_instead_of_id(self): docker.client.DEFAULT_TIMEOUT_SECONDS ) + def test_start_container_with_restart_policy(self): + try: + self.client.start(fake_api.FAKE_CONTAINER_ID, + restart_policy={ + "Name": "always", + "MaximumRetryCount": 0 + }) + except Exception as e: + self.fail('Command should not raise exception: {0}'.format(e)) + args = fake_request.call_args + self.assertEqual( + args[0][0], + url_prefix + 'containers/3cc2351ab11b/start' + ) + self.assertEqual( + json.loads(args[1]['data']), + {"PublishAllPorts": False, "Privileged": False, + "RestartPolicy": {"MaximumRetryCount": 0, "Name": "always"}} + ) + self.assertEqual( + args[1]['headers'], + {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], + docker.client.DEFAULT_TIMEOUT_SECONDS + ) + def test_resize_container(self): try: self.client.resize( @@ -1536,11 +1565,11 @@ def test_tar_with_excludes(self): f.write("content") for exclude, names in ( - (['*.py'], ['bar/a.txt', 'bar/other.png', - 'test/foo/a.txt', 'test/foo/other.png']), - (['*.png', 'bar'], ['test/foo/a.txt', 'test/foo/b.py']), - (['test/foo', 'a.txt'], ['bar/a.txt', 'bar/b.py', - 'bar/other.png']), + (['*.py'], ['bar/a.txt', 'bar/other.png', + 'test/foo/a.txt', 'test/foo/other.png']), + (['*.png', 'bar'], ['test/foo/a.txt', 'test/foo/b.py']), + (['test/foo', 'a.txt'], ['bar/a.txt', 'bar/b.py', + 'bar/other.png']), ): archive = docker.utils.tar(base, exclude=exclude) tar = tarfile.open(fileobj=archive)