Merge pull request #268 from orchardup/update-docker-py-0.3.2

Update to docker-py 0.3.2
This commit is contained in:
Aanand Prasad 2014-06-25 14:16:48 +01:00
commit 256dccc554
9 changed files with 156 additions and 43 deletions

View File

@ -17,7 +17,7 @@ class Container(object):
Construct a container object from the output of GET /containers/json.
"""
new_dictionary = {
'ID': dictionary['Id'],
'Id': dictionary['Id'],
'Image': dictionary['Image'],
}
for name in dictionary.get('Names', []):
@ -36,7 +36,7 @@ class Container(object):
@property
def id(self):
return self.dictionary['ID']
return self.dictionary['Id']
@property
def image(self):

View File

@ -12,7 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .version import version
__version__ = version
__title__ = 'docker-py'
__version__ = '0.3.0'
from .client import Client # flake8: noqa

View File

@ -16,6 +16,7 @@ import json
import re
import shlex
import struct
import warnings
import requests
import requests.exceptions
@ -29,7 +30,7 @@ from . import errors
if not six.PY3:
import websocket
DEFAULT_DOCKER_API_VERSION = '1.9'
DEFAULT_DOCKER_API_VERSION = '1.12'
DEFAULT_TIMEOUT_SECONDS = 60
STREAM_HEADER_SIZE_BYTES = 8
@ -95,7 +96,8 @@ class Client(requests.Session):
mem_limit=0, ports=None, environment=None, dns=None,
volumes=None, volumes_from=None,
network_disabled=False, entrypoint=None,
cpu_shares=None, working_dir=None, domainname=None):
cpu_shares=None, working_dir=None, domainname=None,
memswap_limit=0):
if isinstance(command, six.string_types):
command = shlex.split(str(command))
if isinstance(environment, dict):
@ -121,8 +123,12 @@ class Client(requests.Session):
volumes_dict[vol] = {}
volumes = volumes_dict
if volumes_from and not isinstance(volumes_from, six.string_types):
volumes_from = ','.join(volumes_from)
if volumes_from:
if not isinstance(volumes_from, six.string_types):
volumes_from = ','.join(volumes_from)
else:
# Force None, an empty list or dict causes client.start to fail
volumes_from = None
attach_stdin = False
attach_stdout = False
@ -137,6 +143,14 @@ class Client(requests.Session):
attach_stdin = True
stdin_once = True
if utils.compare_version('1.10', self._version) >= 0:
message = ('{0!r} parameter has no effect on create_container().'
' It has been moved to start()')
if dns is not None:
raise errors.DockerException(message.format('dns'))
if volumes_from is not None:
raise errors.DockerException(message.format('volumes_from'))
return {
'Hostname': hostname,
'Domainname': domainname,
@ -158,7 +172,8 @@ class Client(requests.Session):
'NetworkDisabled': network_disabled,
'Entrypoint': entrypoint,
'CpuShares': cpu_shares,
'WorkingDir': working_dir
'WorkingDir': working_dir,
'MemorySwap': memswap_limit
}
def _post_json(self, url, data, **kwargs):
@ -235,7 +250,7 @@ class Client(requests.Session):
start = walker + STREAM_HEADER_SIZE_BYTES
end = start + length
walker = end
yield str(buf[start:end])
yield buf[start:end]
def _multiplexed_socket_stream_helper(self, response):
"""A generator of multiplexed data blocks coming from a response
@ -296,8 +311,10 @@ class Client(requests.Session):
return stream_result() if stream else \
self._result(response, binary=True)
sep = bytes() if six.PY3 else str()
return stream and self._multiplexed_socket_stream_helper(response) or \
''.join([x for x in self._multiplexed_buffer_helper(response)])
sep.join([x for x in self._multiplexed_buffer_helper(response)])
def attach_socket(self, container, params=None, ws=False):
if params is None:
@ -318,14 +335,20 @@ 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, timeout=None):
nocache=False, rm=False, stream=False, timeout=None,
custom_context=False, encoding=None):
remote = context = headers = None
if path is None and fileobj is None:
raise TypeError("Either path or fileobj needs to be provided.")
if fileobj is not None:
if custom_context:
if not fileobj:
raise TypeError("You must specify fileobj with custom_context")
context = fileobj
elif fileobj is not None:
context = utils.mkbuildcontext(fileobj)
elif path.startswith(('http://', 'https://', 'git://', 'github.com/')):
elif path.startswith(('http://', 'https://',
'git://', 'github.com/')):
remote = path
else:
context = utils.tar(path)
@ -341,8 +364,11 @@ class Client(requests.Session):
'nocache': nocache,
'rm': rm
}
if context is not None:
headers = {'Content-Type': 'application/tar'}
if encoding:
headers['Content-Encoding'] = encoding
if utils.compare_version('1.9', self._version) >= 0:
# If we don't have any auth data so far, try reloading the config
@ -393,10 +419,11 @@ class Client(requests.Session):
json=True)
def containers(self, quiet=False, all=False, trunc=True, latest=False,
since=None, before=None, limit=-1):
since=None, before=None, limit=-1, size=False):
params = {
'limit': 1 if latest else limit,
'all': 1 if all else 0,
'size': 1 if size else 0,
'trunc_cmd': 1 if trunc else 0,
'since': since,
'before': before
@ -424,12 +451,13 @@ class Client(requests.Session):
mem_limit=0, ports=None, environment=None, dns=None,
volumes=None, volumes_from=None,
network_disabled=False, name=None, entrypoint=None,
cpu_shares=None, working_dir=None, domainname=None):
cpu_shares=None, working_dir=None, domainname=None,
memswap_limit=0):
config = self._container_config(
image, command, hostname, user, detach, stdin_open, tty, mem_limit,
ports, environment, dns, volumes, volumes_from, network_disabled,
entrypoint, cpu_shares, working_dir, domainname
entrypoint, cpu_shares, working_dir, domainname, memswap_limit
)
return self.create_container_from_config(config, name)
@ -458,6 +486,12 @@ class Client(requests.Session):
self._raise_for_status(res)
return res.raw
def get_image(self, image):
res = self._get(self._url("/images/{0}/get".format(image)),
stream=True)
self._raise_for_status(res)
return res.raw
def history(self, image):
res = self._get(self._url("/images/{0}/history".format(image)))
self._raise_for_status(res)
@ -513,6 +547,10 @@ class Client(requests.Session):
True)
def insert(self, image, url, path):
if utils.compare_version('1.12', self._version) >= 0:
raise errors.DeprecatedMethod(
'insert is not available for API version >=1.12'
)
api_url = self._url("/images/" + image + "/insert")
params = {
'url': url,
@ -544,6 +582,10 @@ class Client(requests.Session):
self._raise_for_status(res)
def load_image(self, data):
res = self._post(self._url("/images/load"), data=data)
self._raise_for_status(res)
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
@ -572,7 +614,27 @@ class Client(requests.Session):
self._auth_configs[registry] = req_data
return self._result(response, json=True)
def logs(self, container, stdout=True, stderr=True, stream=False):
def logs(self, container, stdout=True, stderr=True, stream=False,
timestamps=False):
if isinstance(container, dict):
container = container.get('Id')
if utils.compare_version('1.11', self._version) >= 0:
params = {'stderr': stderr and 1 or 0,
'stdout': stdout and 1 or 0,
'timestamps': timestamps and 1 or 0,
'follow': stream and 1 or 0}
url = self._url("/containers/{0}/logs".format(container))
res = self._get(url, params=params, stream=stream)
if stream:
return self._multiplexed_socket_stream_helper(res)
elif six.PY3:
return bytes().join(
[x for x in self._multiplexed_buffer_helper(res)]
)
else:
return str().join(
[x for x in self._multiplexed_buffer_helper(res)]
)
return self.attach(
container,
stdout=stdout,
@ -581,6 +643,9 @@ class Client(requests.Session):
logs=True
)
def ping(self):
return self._result(self._get(self._url('/_ping')))
def port(self, container, private_port):
if isinstance(container, dict):
container = container.get('Id')
@ -597,6 +662,8 @@ class Client(requests.Session):
return h_ports
def pull(self, repository, tag=None, stream=False):
if not tag:
repository, tag = utils.parse_repository_tag(repository)
registry, repo_name = auth.resolve_repository_name(repository)
if repo_name.count(":") == 1:
repository, tag = repository.rsplit(":", 1)
@ -653,16 +720,17 @@ class Client(requests.Session):
return stream and self._stream_helper(response) \
or self._result(response)
def remove_container(self, container, v=False, link=False):
def remove_container(self, container, v=False, link=False, force=False):
if isinstance(container, dict):
container = container.get('Id')
params = {'v': v, 'link': link}
params = {'v': v, 'link': link, 'force': force}
res = self._delete(self._url("/containers/" + container),
params=params)
self._raise_for_status(res)
def remove_image(self, image):
res = self._delete(self._url("/images/" + image))
def remove_image(self, image, force=False, noprune=False):
params = {'force': force, 'noprune': noprune}
res = self._delete(self._url("/images/" + image), params=params)
self._raise_for_status(res)
def restart(self, container, timeout=10):
@ -678,8 +746,9 @@ class Client(requests.Session):
params={'term': term}),
True)
def start(self, container, binds=None, volumes_from=None, port_bindings=None,
lxc_conf=None, publish_all_ports=False, links=None, privileged=False, network_mode=None):
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):
if isinstance(container, dict):
container = container.get('Id')
@ -693,19 +762,7 @@ class Client(requests.Session):
'LxcConf': lxc_conf
}
if binds:
bind_pairs = [
'%s:%s:%s' % (
h, d['bind'],
'ro' if 'ro' in d and d['ro'] else 'rw'
) for h, d in binds.items()
]
start_config['Binds'] = bind_pairs
if volumes_from and not isinstance(volumes_from, six.string_types):
volumes_from = ','.join(volumes_from)
start_config['VolumesFrom'] = volumes_from
start_config['Binds'] = utils.convert_volume_binds(binds)
if port_bindings:
start_config['PortBindings'] = utils.convert_port_bindings(
@ -726,6 +783,28 @@ class Client(requests.Session):
start_config['Privileged'] = privileged
if utils.compare_version('1.10', self._version) >= 0:
if dns is not None:
start_config['Dns'] = dns
if volumes_from is not None:
if isinstance(volumes_from, six.string_types):
volumes_from = volumes_from.split(',')
start_config['VolumesFrom'] = volumes_from
else:
warning_message = ('{0!r} parameter is discarded. It is only'
' available for API version greater or equal'
' than 1.10')
if dns is not None:
warnings.warn(warning_message.format('dns'),
DeprecationWarning)
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
@ -733,6 +812,15 @@ class Client(requests.Session):
res = self._post_json(url, data=start_config)
self._raise_for_status(res)
def resize(self, container, height, width):
if isinstance(container, dict):
container = container.get('Id')
params = {'h': height, 'w': width}
url = self._url("/containers/{0}/resize".format(container))
res = self._post(url, params=params)
self._raise_for_status(res)
def stop(self, container, timeout=10):
if isinstance(container, dict):
container = container.get('Id')

View File

@ -59,3 +59,7 @@ class InvalidRepository(DockerException):
class InvalidConfigFile(DockerException):
pass
class DeprecatedMethod(DockerException):
pass

View File

@ -1,3 +1,4 @@
from .utils import (
compare_version, convert_port_bindings, mkbuildcontext, ping, tar, parse_repository_tag
compare_version, convert_port_bindings, convert_volume_binds,
mkbuildcontext, ping, tar, parse_repository_tag
) # flake8: noqa

View File

@ -92,6 +92,13 @@ def _convert_port_binding(binding):
result['HostIp'] = binding[0]
else:
result['HostPort'] = binding[0]
elif isinstance(binding, dict):
if 'HostPort' in binding:
result['HostPort'] = binding['HostPort']
if 'HostIp' in binding:
result['HostIp'] = binding['HostIp']
else:
raise ValueError(binding)
else:
result['HostPort'] = binding
@ -116,13 +123,25 @@ def convert_port_bindings(port_bindings):
return result
def convert_volume_binds(binds):
result = []
for k, v in binds.items():
if isinstance(v, dict):
result.append('%s:%s:%s' % (
k, v['bind'], 'ro' if v.get('ro', False) else 'rw'
))
else:
result.append('%s:%s:rw' % (k, v))
return result
def parse_repository_tag(repo):
column_index = repo.rfind(':')
if column_index < 0:
return repo, ""
return repo, None
tag = repo[column_index+1:]
slash_index = tag.find('/')
if slash_index < 0:
return repo[:column_index], tag
return repo, ""
return repo, None

View File

@ -0,0 +1 @@
version = "0.3.2"

View File

@ -181,7 +181,6 @@ class Service(object):
intermediate_container = Container.create(
self.client,
image=container.image,
volumes_from=container.id,
entrypoint=['echo'],
command=[],
)
@ -190,7 +189,6 @@ class Service(object):
container.remove()
options = dict(override_options)
options['volumes_from'] = intermediate_container.id
new_container = self.create_container(**options)
self.start_container(new_container, volumes_from=intermediate_container.id)

View File

@ -16,14 +16,14 @@ class ContainerTest(unittest.TestCase):
"Names":["/figtest_db_1"]
}, has_been_inspected=True)
self.assertEqual(container.dictionary, {
"ID": "abc",
"Id": "abc",
"Image":"busybox:latest",
"Name": "/figtest_db_1",
})
def test_environment(self):
container = Container(None, {
'ID': 'abc',
'Id': 'abc',
'Config': {
'Env': [
'FOO=BAR',