mirror of https://github.com/docker/docker-py.git
Merge branch 'master' of git://github.com/dotcloud/docker-py
This commit is contained in:
commit
477831334c
10
README.md
10
README.md
|
@ -55,7 +55,7 @@ Identical to the `docker cp` command.
|
|||
c.create_container(image, command=None, hostname=None, user=None,
|
||||
detach=False, stdin_open=False, tty=False, mem_limit=0,
|
||||
ports=None, environment=None, dns=None, volumes=None,
|
||||
volumes_from=None, name=None)
|
||||
volumes_from=None, network_disabled=False, name=None)
|
||||
```
|
||||
|
||||
Creates a container that can then be `start`ed. Parameters are similar
|
||||
|
@ -142,6 +142,14 @@ Identical to the `docker logs` command. The `stream` parameter makes the
|
|||
`logs` function return a blocking generator you can iterate over to
|
||||
retrieve log output as it happens.
|
||||
|
||||
```python
|
||||
c.attach(container, stdout=True, stderr=True, stream=False, logs=False)
|
||||
```
|
||||
|
||||
The `logs` function is a wrapper around this one, which you can use
|
||||
instead if you want to fetch/stream container output without first
|
||||
retrieving the entire backlog.
|
||||
|
||||
```python
|
||||
c.port(container, private_port)
|
||||
```
|
||||
|
|
|
@ -13,14 +13,16 @@
|
|||
# limitations under the License.
|
||||
|
||||
import base64
|
||||
import fileinput
|
||||
import json
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
import docker.utils as utils
|
||||
from ..utils import utils
|
||||
|
||||
INDEX_URL = 'https://index.docker.io/v1/'
|
||||
DOCKER_CONFIG_FILENAME = '.dockercfg'
|
||||
|
||||
|
||||
def swap_protocol(url):
|
||||
|
@ -59,12 +61,15 @@ def resolve_repository_name(repo_name):
|
|||
return expand_registry_url(parts[0]), parts[1]
|
||||
|
||||
|
||||
def resolve_authconfig(authconfig, registry):
|
||||
default = {}
|
||||
if registry == INDEX_URL or registry == '':
|
||||
# default to the index server
|
||||
return authconfig['Configs'].get(INDEX_URL, default)
|
||||
# if its not the index server there are three cases:
|
||||
def resolve_authconfig(authconfig, registry=None):
|
||||
"""Return the authentication data from the given auth configuration for a
|
||||
specific registry. We'll do our best to infer the correct URL for the
|
||||
registry, trying both http and https schemes. Returns an empty dictionnary
|
||||
if no data exists."""
|
||||
# Default to the public index server
|
||||
registry = registry or INDEX_URL
|
||||
|
||||
# Ff its not the index server there are three cases:
|
||||
#
|
||||
# 1. this is a full config url -> it should be used as is
|
||||
# 2. it could be a full url, but with the wrong protocol
|
||||
|
@ -77,11 +82,9 @@ def resolve_authconfig(authconfig, registry):
|
|||
if not registry.startswith('http:') and not registry.startswith('https:'):
|
||||
registry = 'https://' + registry
|
||||
|
||||
if registry in authconfig['Configs']:
|
||||
return authconfig['Configs'][registry]
|
||||
elif swap_protocol(registry) in authconfig['Configs']:
|
||||
return authconfig['Configs'][swap_protocol(registry)]
|
||||
return default
|
||||
if registry in authconfig:
|
||||
return authconfig[registry]
|
||||
return authconfig.get(swap_protocol(registry), None)
|
||||
|
||||
|
||||
def decode_auth(auth):
|
||||
|
@ -98,38 +101,53 @@ def encode_header(auth):
|
|||
|
||||
|
||||
def load_config(root=None):
|
||||
root = root or os.environ['HOME']
|
||||
config = {
|
||||
'Configs': {},
|
||||
'rootPath': root
|
||||
}
|
||||
"""Loads authentication data from a Docker configuration file in the given
|
||||
root directory."""
|
||||
conf = {}
|
||||
data = None
|
||||
|
||||
config_file = os.path.join(root, '.dockercfg')
|
||||
if not os.path.exists(config_file):
|
||||
return config
|
||||
config_file = os.path.join(root or os.environ.get('HOME', '.'),
|
||||
DOCKER_CONFIG_FILENAME)
|
||||
|
||||
f = open(config_file)
|
||||
# First try as JSON
|
||||
try:
|
||||
config['Configs'] = json.load(f)
|
||||
for k, conf in six.iteritems(config['Configs']):
|
||||
conf['Username'], conf['Password'] = decode_auth(conf['auth'])
|
||||
del conf['auth']
|
||||
config['Configs'][k] = conf
|
||||
except Exception:
|
||||
f.seek(0)
|
||||
buf = []
|
||||
for line in f:
|
||||
k, v = line.split(' = ')
|
||||
buf.append(v)
|
||||
if len(buf) < 2:
|
||||
raise Exception("The Auth config file is empty")
|
||||
user, pwd = decode_auth(buf[0])
|
||||
config['Configs'][INDEX_URL] = {
|
||||
'Username': user,
|
||||
'Password': pwd,
|
||||
'Email': buf[1]
|
||||
}
|
||||
finally:
|
||||
f.close()
|
||||
with open(config_file) as f:
|
||||
conf = {}
|
||||
for registry, entry in six.iteritems(json.load(f)):
|
||||
username, password = decode_auth(entry['auth'])
|
||||
conf[registry] = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'email': entry['email'],
|
||||
'serveraddress': registry,
|
||||
}
|
||||
return conf
|
||||
except:
|
||||
pass
|
||||
|
||||
return config
|
||||
# If that fails, we assume the configuration file contains a single
|
||||
# authentication token for the public registry in the following format:
|
||||
#
|
||||
# auth = AUTH_TOKEN
|
||||
# email = email@domain.com
|
||||
try:
|
||||
data = []
|
||||
for line in fileinput.input(config_file):
|
||||
data.append(line.strip().split(' = ')[1])
|
||||
if len(data) < 2:
|
||||
# Not enough data
|
||||
raise Exception('Invalid or empty configuration file!')
|
||||
|
||||
username, password = decode_auth(data[0])
|
||||
conf[INDEX_URL] = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'email': data[1],
|
||||
'serveraddress': INDEX_URL,
|
||||
}
|
||||
return conf
|
||||
except:
|
||||
pass
|
||||
|
||||
# If all fails, return an empty config
|
||||
return {}
|
||||
|
|
195
docker/client.py
195
docker/client.py
|
@ -21,9 +21,9 @@ import requests
|
|||
import requests.exceptions
|
||||
import six
|
||||
|
||||
import docker.auth as auth
|
||||
import docker.unixconn as unixconn
|
||||
import docker.utils as utils
|
||||
from .auth import auth
|
||||
from .unixconn import unixconn
|
||||
from .utils import utils
|
||||
|
||||
if not six.PY3:
|
||||
import websocket
|
||||
|
@ -65,22 +65,23 @@ class APIError(requests.exceptions.HTTPError):
|
|||
|
||||
|
||||
class Client(requests.Session):
|
||||
def __init__(self, base_url="unix://var/run/docker.sock", version="1.6",
|
||||
def __init__(self, base_url=None, version="1.6",
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS):
|
||||
super(Client, self).__init__()
|
||||
if base_url is None:
|
||||
base_url = "unix://var/run/docker.sock"
|
||||
if base_url.startswith('unix:///'):
|
||||
base_url = base_url.replace('unix:/', 'unix:')
|
||||
if base_url.startswith('tcp:'):
|
||||
base_url = base_url.replace('tcp:', 'http:')
|
||||
if base_url.endswith('/'):
|
||||
base_url = base_url[:-1]
|
||||
self.base_url = base_url
|
||||
self._version = version
|
||||
self._timeout = timeout
|
||||
self._auth_configs = auth.load_config()
|
||||
|
||||
self.mount('unix://', unixconn.UnixAdapter(base_url, timeout))
|
||||
try:
|
||||
self._cfg = auth.load_config()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _set_request_timeout(self, kwargs):
|
||||
"""Prepare the kwargs for an HTTP request by inserting the timeout
|
||||
|
@ -120,7 +121,8 @@ class Client(requests.Session):
|
|||
def _container_config(self, image, command, hostname=None, user=None,
|
||||
detach=False, stdin_open=False, tty=False,
|
||||
mem_limit=0, ports=None, environment=None, dns=None,
|
||||
volumes=None, volumes_from=None):
|
||||
volumes=None, volumes_from=None,
|
||||
network_disabled=False):
|
||||
if isinstance(command, six.string_types):
|
||||
command = shlex.split(str(command))
|
||||
if isinstance(environment, dict):
|
||||
|
@ -132,15 +134,12 @@ class Client(requests.Session):
|
|||
exposed_ports = {}
|
||||
for port_definition in ports:
|
||||
port = port_definition
|
||||
proto = None
|
||||
proto = 'tcp'
|
||||
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 ''
|
||||
)] = {}
|
||||
exposed_ports['{0}/{1}'.format(port, proto)] = {}
|
||||
ports = exposed_ports
|
||||
|
||||
if volumes and isinstance(volumes, list):
|
||||
|
@ -176,6 +175,7 @@ class Client(requests.Session):
|
|||
'Image': image,
|
||||
'Volumes': volumes,
|
||||
'VolumesFrom': volumes_from,
|
||||
'NetworkDisabled': network_disabled
|
||||
}
|
||||
|
||||
def _post_json(self, url, data, **kwargs):
|
||||
|
@ -227,7 +227,9 @@ class Client(requests.Session):
|
|||
|
||||
def _stream_helper(self, response):
|
||||
"""Generator for data coming from a chunked-encoded HTTP response."""
|
||||
socket = self._stream_result_socket(response).makefile()
|
||||
socket_fp = self._stream_result_socket(response)
|
||||
socket_fp.setblocking(1)
|
||||
socket = socket_fp.makefile()
|
||||
while True:
|
||||
size = int(socket.readline(), 16)
|
||||
if size <= 0:
|
||||
|
@ -280,15 +282,26 @@ class Client(requests.Session):
|
|||
break
|
||||
yield data
|
||||
|
||||
def attach(self, container):
|
||||
socket = self.attach_socket(container)
|
||||
def attach(self, container, stdout=True, stderr=True,
|
||||
stream=False, logs=False):
|
||||
if isinstance(container, dict):
|
||||
container = container.get('Id')
|
||||
params = {
|
||||
'logs': logs and 1 or 0,
|
||||
'stdout': stdout and 1 or 0,
|
||||
'stderr': stderr and 1 or 0,
|
||||
'stream': stream and 1 or 0,
|
||||
}
|
||||
u = self._url("/containers/{0}/attach".format(container))
|
||||
response = self._post(u, params=params, stream=stream)
|
||||
|
||||
while True:
|
||||
chunk = socket.recv(4096)
|
||||
if chunk:
|
||||
yield chunk
|
||||
else:
|
||||
break
|
||||
# Stream multi-plexing was introduced in API v1.6.
|
||||
if utils.compare_version('1.6', self._version) < 0:
|
||||
return stream and self._stream_result(response) or \
|
||||
self._result(response, binary=True)
|
||||
|
||||
return stream and self._multiplexed_socket_stream_helper(response) or \
|
||||
''.join([x for x in self._multiplexed_buffer_helper(response)])
|
||||
|
||||
def attach_socket(self, container, params=None, ws=False):
|
||||
if params is None:
|
||||
|
@ -307,7 +320,7 @@ class Client(requests.Session):
|
|||
u, None, params=self._attach_params(params), stream=True))
|
||||
|
||||
def build(self, path=None, tag=None, quiet=False, fileobj=None,
|
||||
nocache=False, rm=False, stream=False):
|
||||
nocache=False, rm=False, stream=False, timeout=None):
|
||||
remote = context = headers = None
|
||||
if path is None and fileobj is None:
|
||||
raise Exception("Either path or fileobj needs to be provided.")
|
||||
|
@ -331,7 +344,12 @@ class Client(requests.Session):
|
|||
headers = {'Content-Type': 'application/tar'}
|
||||
|
||||
response = self._post(
|
||||
u, data=context, params=params, headers=headers, stream=stream
|
||||
u,
|
||||
data=context,
|
||||
params=params,
|
||||
headers=headers,
|
||||
stream=stream,
|
||||
timeout=timeout,
|
||||
)
|
||||
|
||||
if context is not None:
|
||||
|
@ -387,11 +405,12 @@ class Client(requests.Session):
|
|||
def create_container(self, image, command=None, hostname=None, user=None,
|
||||
detach=False, stdin_open=False, tty=False,
|
||||
mem_limit=0, ports=None, environment=None, dns=None,
|
||||
volumes=None, volumes_from=None, name=None):
|
||||
volumes=None, volumes_from=None,
|
||||
network_disabled=False, name=None):
|
||||
|
||||
config = self._container_config(
|
||||
image, command, hostname, user, detach, stdin_open, tty, mem_limit,
|
||||
ports, environment, dns, volumes, volumes_from
|
||||
ports, environment, dns, volumes, volumes_from, network_disabled
|
||||
)
|
||||
return self.create_container_from_config(config, name)
|
||||
|
||||
|
@ -518,44 +537,42 @@ class Client(requests.Session):
|
|||
|
||||
self._raise_for_status(res)
|
||||
|
||||
def login(self, username, password=None, email=None, registry=None):
|
||||
url = self._url("/auth")
|
||||
if registry is None:
|
||||
registry = auth.INDEX_URL
|
||||
if getattr(self, '_cfg', None) is None:
|
||||
self._cfg = auth.load_config()
|
||||
authcfg = auth.resolve_authconfig(self._cfg, registry)
|
||||
if 'username' in authcfg and authcfg['username'] == username:
|
||||
def login(self, username, password=None, email=None, registry=None,
|
||||
reauth=False):
|
||||
# If we don't have any auth data so far, try reloading the config file
|
||||
# one more time in case anything showed up in there.
|
||||
if not self._auth_configs:
|
||||
self._auth_configs = auth.load_config()
|
||||
|
||||
registry = registry or auth.INDEX_URL
|
||||
|
||||
authcfg = auth.resolve_authconfig(self._auth_configs, registry)
|
||||
# If we found an existing auth config for this registry and username
|
||||
# combination, we can return it immediately unless reauth is requested.
|
||||
if authcfg and authcfg.get('username', None) == username \
|
||||
and not reauth:
|
||||
return authcfg
|
||||
|
||||
req_data = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'email': email
|
||||
'email': email,
|
||||
'serveraddress': registry,
|
||||
}
|
||||
res = self._result(self._post_json(url, data=req_data), True)
|
||||
if res['Status'] == 'Login Succeeded':
|
||||
self._cfg['Configs'][registry] = req_data
|
||||
return res
|
||||
|
||||
response = self._post_json(self._url('/auth'), data=req_data)
|
||||
if response.status_code == 200:
|
||||
self._auth_configs[registry] = req_data
|
||||
return self._result(response, json=True)
|
||||
|
||||
def logs(self, container, stdout=True, stderr=True, stream=False):
|
||||
if isinstance(container, dict):
|
||||
container = container.get('Id')
|
||||
params = {
|
||||
'logs': 1,
|
||||
'stdout': stdout and 1 or 0,
|
||||
'stderr': stderr and 1 or 0,
|
||||
'stream': stream and 1 or 0,
|
||||
}
|
||||
u = self._url("/containers/{0}/attach".format(container))
|
||||
response = self._post(u, params=params, stream=stream)
|
||||
|
||||
# Stream multi-plexing was introduced in API v1.6.
|
||||
if utils.compare_version('1.6', self._version) < 0:
|
||||
return stream and self._stream_result(response) or \
|
||||
self._result(response, binary=True)
|
||||
|
||||
return stream and self._multiplexed_socket_stream_helper(response) or \
|
||||
''.join([x for x in self._multiplexed_buffer_helper(response)])
|
||||
return self.attach(
|
||||
container,
|
||||
stdout=stdout,
|
||||
stderr=stderr,
|
||||
stream=stream,
|
||||
logs=True
|
||||
)
|
||||
|
||||
def port(self, container, private_port):
|
||||
if isinstance(container, dict):
|
||||
|
@ -564,13 +581,13 @@ class Client(requests.Session):
|
|||
self._raise_for_status(res)
|
||||
json_ = res.json()
|
||||
s_port = str(private_port)
|
||||
f_port = None
|
||||
if s_port in json_['NetworkSettings']['PortMapping']['Udp']:
|
||||
f_port = json_['NetworkSettings']['PortMapping']['Udp'][s_port]
|
||||
elif s_port in json_['NetworkSettings']['PortMapping']['Tcp']:
|
||||
f_port = json_['NetworkSettings']['PortMapping']['Tcp'][s_port]
|
||||
h_ports = None
|
||||
|
||||
return f_port
|
||||
h_ports = json_['NetworkSettings']['Ports'].get(s_port + '/udp')
|
||||
if h_ports is None:
|
||||
h_ports = json_['NetworkSettings']['Ports'].get(s_port + '/tcp')
|
||||
|
||||
return h_ports
|
||||
|
||||
def pull(self, repository, tag=None, stream=False):
|
||||
registry, repo_name = auth.resolve_repository_name(repository)
|
||||
|
@ -584,16 +601,20 @@ class Client(requests.Session):
|
|||
headers = {}
|
||||
|
||||
if utils.compare_version('1.5', self._version) >= 0:
|
||||
if getattr(self, '_cfg', None) is None:
|
||||
self._cfg = auth.load_config()
|
||||
authcfg = auth.resolve_authconfig(self._cfg, registry)
|
||||
# do not fail if no atuhentication exists
|
||||
# for this specific registry as we can have a readonly pull
|
||||
# If we don't have any auth data so far, try reloading the config
|
||||
# file one more time in case anything showed up in there.
|
||||
if not self._auth_configs:
|
||||
self._auth_configs = auth.load_config()
|
||||
authcfg = auth.resolve_authconfig(self._auth_configs, registry)
|
||||
|
||||
# Do not fail here if no atuhentication exists for this specific
|
||||
# registry as we can have a readonly pull. Just put the header if
|
||||
# we can.
|
||||
if authcfg:
|
||||
headers['X-Registry-Auth'] = auth.encode_header(authcfg)
|
||||
u = self._url("/images/create")
|
||||
response = self._post(u, params=params, headers=headers, stream=stream,
|
||||
timeout=None)
|
||||
|
||||
response = self._post(self._url('/images/create'), params=params,
|
||||
headers=headers, stream=stream, timeout=None)
|
||||
|
||||
if stream:
|
||||
return self._stream_helper(response)
|
||||
|
@ -604,26 +625,26 @@ class Client(requests.Session):
|
|||
registry, repo_name = auth.resolve_repository_name(repository)
|
||||
u = self._url("/images/{0}/push".format(repository))
|
||||
headers = {}
|
||||
if getattr(self, '_cfg', None) is None:
|
||||
self._cfg = auth.load_config()
|
||||
authcfg = auth.resolve_authconfig(self._cfg, registry)
|
||||
|
||||
if utils.compare_version('1.5', self._version) >= 0:
|
||||
# do not fail if no atuhentication exists
|
||||
# for this specific registry as we can have an anon push
|
||||
# If we don't have any auth data so far, try reloading the config
|
||||
# file one more time in case anything showed up in there.
|
||||
if not self._auth_configs:
|
||||
self._auth_configs = auth.load_config()
|
||||
authcfg = auth.resolve_authconfig(self._auth_configs, registry)
|
||||
|
||||
# Do not fail here if no atuhentication exists for this specific
|
||||
# registry as we can have a readonly pull. Just put the header if
|
||||
# we can.
|
||||
if authcfg:
|
||||
headers['X-Registry-Auth'] = auth.encode_header(authcfg)
|
||||
|
||||
if stream:
|
||||
return self._stream_helper(
|
||||
self._post_json(u, None, headers=headers, stream=True))
|
||||
else:
|
||||
return self._result(
|
||||
self._post_json(u, None, headers=headers, stream=False))
|
||||
if stream:
|
||||
return self._stream_helper(
|
||||
self._post_json(u, authcfg, stream=True))
|
||||
response = self._post_json(u, None, headers=headers, stream=stream)
|
||||
else:
|
||||
return self._result(self._post_json(u, authcfg, stream=False))
|
||||
response = self._post_json(u, authcfg, stream=stream)
|
||||
|
||||
return stream and self._stream_helper(response) \
|
||||
or self._result(response)
|
||||
|
||||
def remove_container(self, container, v=False, link=False):
|
||||
if isinstance(container, dict):
|
||||
|
|
19
setup.py
19
setup.py
|
@ -17,12 +17,17 @@ setup(
|
|||
install_requires=requirements + test_requirements,
|
||||
zip_safe=False,
|
||||
test_suite='tests',
|
||||
classifiers=['Development Status :: 4 - Beta',
|
||||
'Environment :: Other Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Topic :: Utilities',
|
||||
'License :: OSI Approved :: Apache Software License'
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta',
|
||||
'Environment :: Other Environment',
|
||||
'Intended Audience :: Developers',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 2.6',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.2',
|
||||
'Programming Language :: Python :: 3.3',
|
||||
'Topic :: Utilities',
|
||||
'License :: OSI Approved :: Apache Software License',
|
||||
],
|
||||
)
|
||||
|
|
|
@ -157,6 +157,34 @@ def get_fake_inspect_image():
|
|||
return status_code, response
|
||||
|
||||
|
||||
def get_fake_port():
|
||||
status_code = 200
|
||||
response = {
|
||||
'HostConfig': {
|
||||
'Binds': None,
|
||||
'ContainerIDFile': '',
|
||||
'Links': None,
|
||||
'LxcConf': None,
|
||||
'PortBindings': {
|
||||
'1111': None,
|
||||
'1111/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '4567'}],
|
||||
'2222': None
|
||||
},
|
||||
'Privileged': False,
|
||||
'PublishAllPorts': False
|
||||
},
|
||||
'NetworkSettings': {
|
||||
'Bridge': 'docker0',
|
||||
'PortMapping': None,
|
||||
'Ports': {
|
||||
'1111': None,
|
||||
'1111/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '4567'}],
|
||||
'2222': None}
|
||||
}
|
||||
}
|
||||
return status_code, response
|
||||
|
||||
|
||||
def get_fake_insert_image():
|
||||
status_code = 200
|
||||
response = {'StatusCode': 0}
|
||||
|
@ -282,6 +310,8 @@ fake_responses = {
|
|||
post_fake_stop_container,
|
||||
'{1}/{0}/containers/3cc2351ab11b/kill'.format(CURRENT_VERSION, prefix):
|
||||
post_fake_kill_container,
|
||||
'{1}/{0}/containers/3cc2351ab11b/json'.format(CURRENT_VERSION, prefix):
|
||||
get_fake_port,
|
||||
'{1}/{0}/containers/3cc2351ab11b/restart'.format(CURRENT_VERSION, prefix):
|
||||
post_fake_restart_container,
|
||||
'{1}/{0}/containers/3cc2351ab11b'.format(CURRENT_VERSION, prefix):
|
||||
|
|
|
@ -422,6 +422,34 @@ class TestKillWithSignal(BaseTestCase):
|
|||
self.assertEqual(state['Running'], False, state)
|
||||
|
||||
|
||||
class TestPort(BaseTestCase):
|
||||
def runTest(self):
|
||||
|
||||
port_bindings = {
|
||||
1111: ('127.0.0.1', '4567'),
|
||||
2222: ('192.168.0.100', '4568')
|
||||
}
|
||||
|
||||
container = self.client.create_container(
|
||||
'busybox', ['sleep', '60'], ports=port_bindings.keys()
|
||||
)
|
||||
id = container['Id']
|
||||
|
||||
self.client.start(container, port_bindings=port_bindings)
|
||||
|
||||
#Call the port function on each biding and compare expected vs actual
|
||||
for port in port_bindings:
|
||||
actual_bindings = self.client.port(container, port)
|
||||
port_binding = actual_bindings.pop()
|
||||
|
||||
ip, host_port = port_binding['HostIp'], port_binding['HostPort']
|
||||
|
||||
self.assertEqual(ip, port_bindings[port][0])
|
||||
self.assertEqual(host_port, port_bindings[port][1])
|
||||
|
||||
self.client.kill(id)
|
||||
|
||||
|
||||
class TestRestart(BaseTestCase):
|
||||
def runTest(self):
|
||||
container = self.client.create_container('busybox', ['sleep', '9999'])
|
||||
|
@ -775,11 +803,29 @@ class TestLoadConfig(BaseTestCase):
|
|||
f.write('email = sakuya@scarlet.net')
|
||||
f.close()
|
||||
cfg = docker.auth.load_config(folder)
|
||||
self.assertNotEqual(cfg['Configs'][docker.auth.INDEX_URL], None)
|
||||
cfg = cfg['Configs'][docker.auth.INDEX_URL]
|
||||
self.assertEqual(cfg['Username'], b'sakuya')
|
||||
self.assertEqual(cfg['Password'], b'izayoi')
|
||||
self.assertEqual(cfg['Email'], 'sakuya@scarlet.net')
|
||||
self.assertNotEqual(cfg[docker.auth.INDEX_URL], None)
|
||||
cfg = cfg[docker.auth.INDEX_URL]
|
||||
self.assertEqual(cfg['username'], b'sakuya')
|
||||
self.assertEqual(cfg['password'], b'izayoi')
|
||||
self.assertEqual(cfg['email'], 'sakuya@scarlet.net')
|
||||
self.assertEqual(cfg.get('Auth'), None)
|
||||
|
||||
|
||||
class TestLoadJSONConfig(BaseTestCase):
|
||||
def runTest(self):
|
||||
folder = tempfile.mkdtemp()
|
||||
f = open(os.path.join(folder, '.dockercfg'), 'w')
|
||||
auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii')
|
||||
email_ = 'sakuya@scarlet.net'
|
||||
f.write('{{"{}": {{"auth": "{}", "email": "{}"}}}}\n'.format(
|
||||
docker.auth.INDEX_URL, auth_, email_))
|
||||
f.close()
|
||||
cfg = docker.auth.load_config(folder)
|
||||
self.assertNotEqual(cfg[docker.auth.INDEX_URL], None)
|
||||
cfg = cfg[docker.auth.INDEX_URL]
|
||||
self.assertEqual(cfg['username'], b'sakuya')
|
||||
self.assertEqual(cfg['password'], b'izayoi')
|
||||
self.assertEqual(cfg['email'], 'sakuya@scarlet.net')
|
||||
self.assertEqual(cfg.get('Auth'), None)
|
||||
|
||||
|
||||
|
|
|
@ -34,10 +34,6 @@ except ImportError:
|
|||
import mock
|
||||
|
||||
|
||||
# FIXME: missing tests for
|
||||
# port;
|
||||
|
||||
|
||||
def response(status_code=200, content='', headers=None, reason=None, elapsed=0,
|
||||
request=None):
|
||||
res = requests.Response()
|
||||
|
@ -180,7 +176,7 @@ class DockerClientTest(unittest.TestCase):
|
|||
{"Tty": false, "Image": "busybox", "Cmd": ["true"],
|
||||
"AttachStdin": false, "Memory": 0,
|
||||
"AttachStderr": true, "AttachStdout": true,
|
||||
"OpenStdin": false}'''))
|
||||
"OpenStdin": false, "NetworkDisabled": false}'''))
|
||||
self.assertEqual(args[1]['headers'],
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
|
@ -203,7 +199,8 @@ class DockerClientTest(unittest.TestCase):
|
|||
"Cmd": ["ls", "/mnt"], "AttachStdin": false,
|
||||
"Volumes": {"/mnt": {}}, "Memory": 0,
|
||||
"AttachStderr": true,
|
||||
"AttachStdout": true, "OpenStdin": false}'''))
|
||||
"AttachStdout": true, "OpenStdin": false,
|
||||
"NetworkDisabled": false}'''))
|
||||
self.assertEqual(args[1]['headers'],
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
|
@ -222,12 +219,13 @@ class DockerClientTest(unittest.TestCase):
|
|||
{"Tty": false, "Image": "busybox",
|
||||
"Cmd": ["ls"], "AttachStdin": false,
|
||||
"Memory": 0, "ExposedPorts": {
|
||||
"1111": {},
|
||||
"1111/tcp": {},
|
||||
"2222/udp": {},
|
||||
"3333": {}
|
||||
"3333/tcp": {}
|
||||
},
|
||||
"AttachStderr": true,
|
||||
"AttachStdout": true, "OpenStdin": false}'''))
|
||||
"AttachStdout": true, "OpenStdin": false,
|
||||
"NetworkDisabled": false}'''))
|
||||
self.assertEqual(args[1]['headers'],
|
||||
{'Content-Type': 'application/json'})
|
||||
|
||||
|
@ -246,7 +244,7 @@ class DockerClientTest(unittest.TestCase):
|
|||
{"Tty": false, "Image": "busybox", "Cmd": ["true"],
|
||||
"AttachStdin": false, "Memory": 0,
|
||||
"AttachStderr": true, "AttachStdout": true,
|
||||
"OpenStdin": false}'''))
|
||||
"OpenStdin": false, "NetworkDisabled": false}'''))
|
||||
self.assertEqual(args[1]['headers'],
|
||||
{'Content-Type': 'application/json'})
|
||||
self.assertEqual(args[1]['params'], {'name': 'marisa-kirisame'})
|
||||
|
@ -587,6 +585,17 @@ class DockerClientTest(unittest.TestCase):
|
|||
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
|
||||
)
|
||||
|
||||
def test_port(self):
|
||||
try:
|
||||
self.client.port({'Id': fake_api.FAKE_CONTAINER_ID}, 1111)
|
||||
except Exception as e:
|
||||
self.fail('Command should not raise exception: {0}'.format(e))
|
||||
|
||||
fake_request.assert_called_with(
|
||||
'unix://var/run/docker.sock/v1.6/containers/3cc2351ab11b/json',
|
||||
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
|
||||
)
|
||||
|
||||
def test_stop_container(self):
|
||||
try:
|
||||
self.client.stop(fake_api.FAKE_CONTAINER_ID, timeout=2)
|
||||
|
@ -1037,10 +1046,6 @@ class DockerClientTest(unittest.TestCase):
|
|||
folder = tempfile.mkdtemp()
|
||||
cfg = docker.auth.load_config(folder)
|
||||
self.assertTrue(cfg is not None)
|
||||
self.assertTrue('Configs' in cfg)
|
||||
self.assertEqual(cfg['Configs'], {})
|
||||
self.assertTrue('rootPath' in cfg)
|
||||
self.assertEqual(cfg['rootPath'], folder)
|
||||
|
||||
def test_load_config(self):
|
||||
folder = tempfile.mkdtemp()
|
||||
|
@ -1050,12 +1055,13 @@ class DockerClientTest(unittest.TestCase):
|
|||
f.write('email = sakuya@scarlet.net')
|
||||
f.close()
|
||||
cfg = docker.auth.load_config(folder)
|
||||
self.assertNotEqual(cfg['Configs'][docker.auth.INDEX_URL], None)
|
||||
cfg = cfg['Configs'][docker.auth.INDEX_URL]
|
||||
self.assertEqual(cfg['Username'], 'sakuya')
|
||||
self.assertEqual(cfg['Password'], 'izayoi')
|
||||
self.assertEqual(cfg['Email'], 'sakuya@scarlet.net')
|
||||
self.assertEqual(cfg.get('Auth'), None)
|
||||
self.assertTrue(docker.auth.INDEX_URL in cfg)
|
||||
self.assertNotEqual(cfg[docker.auth.INDEX_URL], None)
|
||||
cfg = cfg[docker.auth.INDEX_URL]
|
||||
self.assertEqual(cfg['username'], 'sakuya')
|
||||
self.assertEqual(cfg['password'], 'izayoi')
|
||||
self.assertEqual(cfg['email'], 'sakuya@scarlet.net')
|
||||
self.assertEqual(cfg.get('auth'), None)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
Loading…
Reference in New Issue