mirror of https://github.com/docker/docker-py.git
Merge pull request #115 from dotcloud/port_volumes
Simplified port bindings and volume mapping
This commit is contained in:
commit
08d6de7ca1
77
README.md
77
README.md
|
@ -27,18 +27,14 @@ Identical to the `docker ps` command.
|
||||||
* `c.copy(container, resource)`
|
* `c.copy(container, resource)`
|
||||||
Identical to the `docker cp` command.
|
Identical to the `docker cp` command.
|
||||||
|
|
||||||
* <code>c.create_container(image, command=None, hostname=None, user=None, detach=False,
|
* <code>c.create_container(image, command=None, hostname=None, user=None,
|
||||||
stdin_open=False, tty=False, mem_limit=0, ports=None, environment=None,
|
detach=False, stdin_open=False, tty=False, mem_limit=0, ports=None, environment=None, dns=None, volumes=None, volumes_from=None,
|
||||||
dns=None, volumes=None, volumes_from=None, privileged=False, name=None)</code>
|
privileged=False, name=None)</code>
|
||||||
Creates a container that can then be `start`ed. Parameters are similar to those
|
Creates a container that can then be `start`ed. Parameters are similar to those
|
||||||
for the `docker run` command except it doesn't support the attach options
|
for the `docker run` command except it doesn't support the attach options
|
||||||
(`-a`)
|
(`-a`)
|
||||||
In order to create volumes that can be rebinded at start time, use the
|
See "Port bindings" and "Using volumes" below for more information on how to
|
||||||
following syntax: `volumes={"/srv": {}}`. The `ports` parameter is a
|
create port bindings and volume mappings.
|
||||||
dictionary whose key is the port to expose and the value is an empty
|
|
||||||
dictionary: `ports={"2181/tcp": {}}`. Note, this will simply expose the ports in
|
|
||||||
the container, but does not make them available on the host. See `start`
|
|
||||||
below.
|
|
||||||
|
|
||||||
* `c.diff(container)`
|
* `c.diff(container)`
|
||||||
Identical to the `docker diff` command.
|
Identical to the `docker diff` command.
|
||||||
|
@ -105,16 +101,10 @@ Identical to the `docker search` command.
|
||||||
* `c.start(container, binds=None, port_bindings=None, lxc_conf=None)`
|
* `c.start(container, binds=None, port_bindings=None, lxc_conf=None)`
|
||||||
Similar to the `docker start` command, but doesn't support attach options.
|
Similar to the `docker start` command, but doesn't support attach options.
|
||||||
Use `docker logs` to recover `stdout`/`stderr`
|
Use `docker logs` to recover `stdout`/`stderr`
|
||||||
`binds` Allows to bind a directory in the host to the container.
|
`binds` Allows to bind a directory in the host to the container. See
|
||||||
Similar to the `docker run` command with option `-v="/host:/mnt"`.
|
"Using volumes" below for more information.
|
||||||
Note that you must declare "blank" volumes at container creation to use binds.
|
`port_bindings` Exposes container ports to the host. See "Port bindings" below
|
||||||
Example of binds mapping from host to container: `{'/mnt/srv/': '/srv'}`
|
for more information.
|
||||||
`port_bindings` Exposes container ports to the host. This is a
|
|
||||||
dictionary whose key is the container's port and the value is a `[{'HostIp': ''
|
|
||||||
'HostPort': ''}]` list. Leaving `HostIp` blank will expose the port on
|
|
||||||
all host interfaces. By leaving the `HostPort` blank, Docker will
|
|
||||||
automatically assign a port. For example: `port_bindings={"2181/tcp": [{'HostIp': '',
|
|
||||||
'HostPort': ''}]}`.
|
|
||||||
`lxc_conf` allows to pass LXC configuration options using a dictionary.
|
`lxc_conf` allows to pass LXC configuration options using a dictionary.
|
||||||
|
|
||||||
* `c.stop(container, timeout=10)`
|
* `c.stop(container, timeout=10)`
|
||||||
|
@ -133,3 +123,52 @@ Identical to the `docker version` command.
|
||||||
Wait for a container and return its exit code. Similar to the `docker wait`
|
Wait for a container and return its exit code. Similar to the `docker wait`
|
||||||
command.
|
command.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Port bindings
|
||||||
|
=============
|
||||||
|
|
||||||
|
Port bindings is done in two parts. Firstly, by providing a list of ports to
|
||||||
|
open inside the container in the `Client.create_container` method.
|
||||||
|
|
||||||
|
client.create_container('busybox', 'ls', ports=[1111, 2222])
|
||||||
|
|
||||||
|
If you wish to use UDP instead of TCP (default), you can declare it like such:
|
||||||
|
|
||||||
|
client.create_container('busybox', 'ls', ports=[(1111, 'udp'), 2222])
|
||||||
|
|
||||||
|
Bindings are then declared in the `Client.start` method.
|
||||||
|
|
||||||
|
client.start(container_id, port_bindings={
|
||||||
|
1111: 4567,
|
||||||
|
2222: None
|
||||||
|
})
|
||||||
|
|
||||||
|
You can limit the host address on which the port will be exposed like such:
|
||||||
|
|
||||||
|
client.start(container_id, port_bindings={
|
||||||
|
1111: ('127.0.0.1', 4567)
|
||||||
|
})
|
||||||
|
|
||||||
|
or without host port assignment:
|
||||||
|
|
||||||
|
client.start(container_id, port_bindings={
|
||||||
|
1111: ('127.0.0.1',)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Using volumes
|
||||||
|
=============
|
||||||
|
|
||||||
|
Similarly, volume declaration is done in two parts. First, you have to provide
|
||||||
|
a list of mountpoints to the `Client.create_container` method.
|
||||||
|
|
||||||
|
client.create_container('busybox', 'ls', volumes=['/mnt/vol1', '/mnt/vol2'])
|
||||||
|
|
||||||
|
Volume mappings are then declared inside the `Client.start` method like this:
|
||||||
|
|
||||||
|
client.start(container_id, bindings={
|
||||||
|
'/mnt/vol2': '/home/user1/',
|
||||||
|
'/mnt/vol1': '/var/www'
|
||||||
|
})
|
|
@ -133,6 +133,27 @@ class Client(requests.Session):
|
||||||
'{0}={1}'.format(k, v) for k, v in environment.items()
|
'{0}={1}'.format(k, v) for k, v in environment.items()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if ports and isinstance(ports, list):
|
||||||
|
exposed_ports = {}
|
||||||
|
for port_definition in ports:
|
||||||
|
port = port_definition
|
||||||
|
proto = None
|
||||||
|
if isinstance(port_definition, tuple):
|
||||||
|
if len(port_definition) == 2:
|
||||||
|
proto = port_definition[1]
|
||||||
|
port = port_definition[0]
|
||||||
|
exposed_ports['{0}{1}'.format(
|
||||||
|
port,
|
||||||
|
'/' + proto if proto else ''
|
||||||
|
)] = {}
|
||||||
|
ports = exposed_ports
|
||||||
|
|
||||||
|
if volumes and isinstance(volumes, list):
|
||||||
|
volumes_dict = {}
|
||||||
|
for vol in volumes:
|
||||||
|
volumes_dict[vol] = {}
|
||||||
|
volumes = volumes_dict
|
||||||
|
|
||||||
attach_stdin = False
|
attach_stdin = False
|
||||||
attach_stdout = False
|
attach_stdout = False
|
||||||
attach_stderr = False
|
attach_stderr = False
|
||||||
|
@ -598,7 +619,9 @@ class Client(requests.Session):
|
||||||
start_config['Binds'] = bind_pairs
|
start_config['Binds'] = bind_pairs
|
||||||
|
|
||||||
if port_bindings:
|
if port_bindings:
|
||||||
start_config['PortBindings'] = port_bindings
|
start_config['PortBindings'] = utils.convert_port_bindings(
|
||||||
|
port_bindings
|
||||||
|
)
|
||||||
|
|
||||||
start_config['PublishAllPorts'] = publish_all_ports
|
start_config['PublishAllPorts'] = publish_all_ports
|
||||||
|
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
from .utils import compare_version, mkbuildcontext, ping, tar # flake8: noqa
|
from .utils import (
|
||||||
|
compare_version, convert_port_bindings, mkbuildcontext, ping, tar
|
||||||
|
) # flake8: noqa
|
||||||
|
|
|
@ -60,3 +60,37 @@ def ping(url):
|
||||||
return res.status >= 400
|
return res.status >= 400
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_port_binding(binding):
|
||||||
|
result = {'HostIp': '', 'HostPort': ''}
|
||||||
|
if isinstance(binding, tuple):
|
||||||
|
if len(binding) == 2:
|
||||||
|
result['HostPort'] = binding[1]
|
||||||
|
result['HostIp'] = binding[0]
|
||||||
|
elif isinstance(binding[0], six.string_types):
|
||||||
|
result['HostIp'] = binding[0]
|
||||||
|
else:
|
||||||
|
result['HostPort'] = binding[0]
|
||||||
|
else:
|
||||||
|
result['HostPort'] = binding
|
||||||
|
|
||||||
|
if result['HostPort'] is None:
|
||||||
|
result['HostPort'] = ''
|
||||||
|
else:
|
||||||
|
result['HostPort'] = str(result['HostPort'])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def convert_port_bindings(port_bindings):
|
||||||
|
result = {}
|
||||||
|
for k, v in six.iteritems(port_bindings):
|
||||||
|
key = str(k)
|
||||||
|
if '/' not in key:
|
||||||
|
key = key + '/tcp'
|
||||||
|
if isinstance(v, list):
|
||||||
|
result[key] = [_convert_port_binding(binding) for binding in v]
|
||||||
|
else:
|
||||||
|
result[key] = [_convert_port_binding(v)]
|
||||||
|
return result
|
||||||
|
|
|
@ -190,7 +190,7 @@ class DockerClientTest(unittest.TestCase):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.client.create_container('busybox', ['ls', mount_dest],
|
self.client.create_container('busybox', ['ls', mount_dest],
|
||||||
volumes={mount_dest: {}})
|
volumes=[mount_dest])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.fail('Command should not raise exception: {0}'.format(e))
|
self.fail('Command should not raise exception: {0}'.format(e))
|
||||||
|
|
||||||
|
@ -207,6 +207,30 @@ class DockerClientTest(unittest.TestCase):
|
||||||
self.assertEqual(args[1]['headers'],
|
self.assertEqual(args[1]['headers'],
|
||||||
{'Content-Type': 'application/json'})
|
{'Content-Type': 'application/json'})
|
||||||
|
|
||||||
|
def test_create_container_with_ports(self):
|
||||||
|
try:
|
||||||
|
self.client.create_container('busybox', 'ls',
|
||||||
|
ports=[1111, (2222, 'udp'), (3333,)])
|
||||||
|
except Exception as e:
|
||||||
|
self.fail('Command should not raise exception: {0}'.format(e))
|
||||||
|
|
||||||
|
args = fake_request.call_args
|
||||||
|
self.assertEqual(args[0][0],
|
||||||
|
'unix://var/run/docker.sock/v1.6/containers/create')
|
||||||
|
self.assertEqual(json.loads(args[1]['data']),
|
||||||
|
json.loads('''
|
||||||
|
{"Tty": false, "Image": "busybox",
|
||||||
|
"Cmd": ["ls"], "AttachStdin": false,
|
||||||
|
"Memory": 0, "ExposedPorts": {
|
||||||
|
"1111": {},
|
||||||
|
"2222/udp": {},
|
||||||
|
"3333": {}
|
||||||
|
},
|
||||||
|
"AttachStderr": true, "Privileged": false,
|
||||||
|
"AttachStdout": true, "OpenStdin": false}'''))
|
||||||
|
self.assertEqual(args[1]['headers'],
|
||||||
|
{'Content-Type': 'application/json'})
|
||||||
|
|
||||||
def test_create_container_privileged(self):
|
def test_create_container_privileged(self):
|
||||||
try:
|
try:
|
||||||
self.client.create_container('busybox', 'true', privileged=True)
|
self.client.create_container('busybox', 'true', privileged=True)
|
||||||
|
@ -324,6 +348,53 @@ class DockerClientTest(unittest.TestCase):
|
||||||
docker.client.DEFAULT_TIMEOUT_SECONDS
|
docker.client.DEFAULT_TIMEOUT_SECONDS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_start_container_with_port_binds(self):
|
||||||
|
self.maxDiff = None
|
||||||
|
try:
|
||||||
|
self.client.start(fake_api.FAKE_CONTAINER_ID, port_bindings={
|
||||||
|
1111: None,
|
||||||
|
2222: 2222,
|
||||||
|
'3333/udp': (3333,),
|
||||||
|
4444: ('127.0.0.1',),
|
||||||
|
5555: ('127.0.0.1', 5555),
|
||||||
|
6666: [('127.0.0.1',), ('192.168.0.1',)]
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
self.fail('Command should not raise exception: {0}'.format(e))
|
||||||
|
|
||||||
|
args = fake_request.call_args
|
||||||
|
self.assertEqual(args[0][0], 'unix://var/run/docker.sock/v1.6/'
|
||||||
|
'containers/3cc2351ab11b/start')
|
||||||
|
self.assertEqual(json.loads(args[1]['data']), {
|
||||||
|
"PublishAllPorts": False,
|
||||||
|
"PortBindings": {
|
||||||
|
"1111/tcp": [{"HostPort": "", "HostIp": ""}],
|
||||||
|
"2222/tcp": [{"HostPort": "2222", "HostIp": ""}],
|
||||||
|
"3333/udp": [{"HostPort": "3333", "HostIp": ""}],
|
||||||
|
"4444/tcp": [{
|
||||||
|
"HostPort": "",
|
||||||
|
"HostIp": "127.0.0.1"
|
||||||
|
}],
|
||||||
|
"5555/tcp": [{
|
||||||
|
"HostPort": "5555",
|
||||||
|
"HostIp": "127.0.0.1"
|
||||||
|
}],
|
||||||
|
"6666/tcp": [{
|
||||||
|
"HostPort": "",
|
||||||
|
"HostIp": "127.0.0.1"
|
||||||
|
}, {
|
||||||
|
"HostPort": "",
|
||||||
|
"HostIp": "192.168.0.1"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.assertEqual(args[1]['headers'],
|
||||||
|
{'Content-Type': 'application/json'})
|
||||||
|
self.assertEqual(
|
||||||
|
args[1]['timeout'],
|
||||||
|
docker.client.DEFAULT_TIMEOUT_SECONDS
|
||||||
|
)
|
||||||
|
|
||||||
def test_start_container_with_links(self):
|
def test_start_container_with_links(self):
|
||||||
# one link
|
# one link
|
||||||
try:
|
try:
|
||||||
|
|
Loading…
Reference in New Issue