Kaydet (Commit) b1301637 authored tarafından Joffrey F's avatar Joffrey F Kaydeden (comit) Joffrey F

Add support for configs management

Signed-off-by: 's avatarJoffrey F <joffrey@docker.com>
üst cd47a1f9
......@@ -9,6 +9,7 @@ import six
import websocket
from .build import BuildApiMixin
from .config import ConfigApiMixin
from .container import ContainerApiMixin
from .daemon import DaemonApiMixin
from .exec_api import ExecApiMixin
......@@ -43,6 +44,7 @@ except ImportError:
class APIClient(
requests.Session,
BuildApiMixin,
ConfigApiMixin,
ContainerApiMixin,
DaemonApiMixin,
ExecApiMixin,
......
import base64
import six
from .. import utils
class ConfigApiMixin(object):
@utils.minimum_version('1.25')
def create_config(self, name, data, labels=None):
"""
Create a config
Args:
name (string): Name of the config
data (bytes): Config data to be stored
labels (dict): A mapping of labels to assign to the config
Returns (dict): ID of the newly created config
"""
if not isinstance(data, bytes):
data = data.encode('utf-8')
data = base64.b64encode(data)
if six.PY3:
data = data.decode('ascii')
body = {
'Data': data,
'Name': name,
'Labels': labels
}
url = self._url('/configs/create')
return self._result(
self._post_json(url, data=body), True
)
@utils.minimum_version('1.25')
@utils.check_resource('id')
def inspect_config(self, id):
"""
Retrieve config metadata
Args:
id (string): Full ID of the config to remove
Returns (dict): A dictionary of metadata
Raises:
:py:class:`docker.errors.NotFound`
if no config with that ID exists
"""
url = self._url('/configs/{0}', id)
return self._result(self._get(url), True)
@utils.minimum_version('1.25')
@utils.check_resource('id')
def remove_config(self, id):
"""
Remove a config
Args:
id (string): Full ID of the config to remove
Returns (boolean): True if successful
Raises:
:py:class:`docker.errors.NotFound`
if no config with that ID exists
"""
url = self._url('/configs/{0}', id)
res = self._delete(url)
self._raise_for_status(res)
return True
@utils.minimum_version('1.25')
def configs(self, filters=None):
"""
List configs
Args:
filters (dict): A map of filters to process on the configs
list. Available filters: ``names``
Returns (list): A list of configs
"""
url = self._url('/configs')
params = {}
if filters:
params['filters'] = utils.convert_filters(filters)
return self._result(self._get(url, params=params), True)
from .api.client import APIClient
from .constants import DEFAULT_TIMEOUT_SECONDS
from .models.configs import ConfigCollection
from .models.containers import ContainerCollection
from .models.images import ImageCollection
from .models.networks import NetworkCollection
......@@ -80,6 +81,14 @@ class DockerClient(object):
**kwargs_from_env(**kwargs))
# Resources
@property
def configs(self):
"""
An object for managing configs on the server. See the
:doc:`configs documentation <configs>` for full details.
"""
return ConfigCollection(client=self)
@property
def containers(self):
"""
......
from ..api import APIClient
from .resource import Model, Collection
class Config(Model):
"""A config."""
id_attribute = 'ID'
def __repr__(self):
return "<%s: '%s'>" % (self.__class__.__name__, self.name)
@property
def name(self):
return self.attrs['Spec']['Name']
def remove(self):
"""
Remove this config.
Raises:
:py:class:`docker.errors.APIError`
If config failed to remove.
"""
return self.client.api.remove_config(self.id)
class ConfigCollection(Collection):
"""Configs on the Docker server."""
model = Config
def create(self, **kwargs):
obj = self.client.api.create_config(**kwargs)
return self.prepare_model(obj)
create.__doc__ = APIClient.create_config.__doc__
def get(self, config_id):
"""
Get a config.
Args:
config_id (str): Config ID.
Returns:
(:py:class:`Config`): The config.
Raises:
:py:class:`docker.errors.NotFound`
If the config does not exist.
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
return self.prepare_model(self.client.api.inspect_config(config_id))
def list(self, **kwargs):
"""
List configs. Similar to the ``docker config ls`` command.
Args:
filters (dict): Server-side list filtering options.
Returns:
(list of :py:class:`Config`): The configs.
Raises:
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
resp = self.client.api.configs(**kwargs)
return [self.prepare_model(obj) for obj in resp]
......@@ -9,6 +9,16 @@ It's possible to use :py:class:`APIClient` directly. Some basic things (e.g. run
.. autoclass:: docker.api.client.APIClient
Configs
-------
.. py:module:: docker.api.config
.. rst-class:: hide-signature
.. autoclass:: ConfigApiMixin
:members:
:undoc-members:
Containers
----------
......
......@@ -15,6 +15,7 @@ Client reference
.. autoclass:: DockerClient()
.. autoattribute:: configs
.. autoattribute:: containers
.. autoattribute:: images
.. autoattribute:: networks
......
Configs
=======
.. py:module:: docker.models.configs
Manage configs on the server.
Methods available on ``client.configs``:
.. rst-class:: hide-signature
.. py:class:: ConfigCollection
.. automethod:: create
.. automethod:: get
.. automethod:: list
Config objects
--------------
.. autoclass:: Config()
.. autoattribute:: id
.. autoattribute:: name
.. py:attribute:: attrs
The raw representation of this object from the server.
.. automethod:: reload
.. automethod:: remove
......@@ -80,6 +80,7 @@ That's just a taste of what you can do with the Docker SDK for Python. For more,
:maxdepth: 2
client
configs
containers
images
networks
......
# -*- coding: utf-8 -*-
import docker
import pytest
from ..helpers import force_leave_swarm, requires_api_version
from .base import BaseAPIIntegrationTest
@requires_api_version('1.30')
class ConfigAPITest(BaseAPIIntegrationTest):
def setUp(self):
super(ConfigAPITest, self).setUp()
self.init_swarm()
def tearDown(self):
super(ConfigAPITest, self).tearDown()
force_leave_swarm(self.client)
def test_create_config(self):
config_id = self.client.create_config(
'favorite_character', 'sakuya izayoi'
)
self.tmp_configs.append(config_id)
assert 'ID' in config_id
data = self.client.inspect_config(config_id)
assert data['Spec']['Name'] == 'favorite_character'
def test_create_config_unicode_data(self):
config_id = self.client.create_config(
'favorite_character', u'いざよいさくや'
)
self.tmp_configs.append(config_id)
assert 'ID' in config_id
data = self.client.inspect_config(config_id)
assert data['Spec']['Name'] == 'favorite_character'
def test_inspect_config(self):
config_name = 'favorite_character'
config_id = self.client.create_config(
config_name, 'sakuya izayoi'
)
self.tmp_configs.append(config_id)
data = self.client.inspect_config(config_id)
assert data['Spec']['Name'] == config_name
assert 'ID' in data
assert 'Version' in data
def test_remove_config(self):
config_name = 'favorite_character'
config_id = self.client.create_config(
config_name, 'sakuya izayoi'
)
self.tmp_configs.append(config_id)
assert self.client.remove_config(config_id)
with pytest.raises(docker.errors.NotFound):
self.client.inspect_config(config_id)
def test_list_configs(self):
config_name = 'favorite_character'
config_id = self.client.create_config(
config_name, 'sakuya izayoi'
)
self.tmp_configs.append(config_id)
data = self.client.configs(filters={'name': ['favorite_character']})
assert len(data) == 1
assert data[0]['ID'] == config_id['ID']
......@@ -473,7 +473,7 @@ class ServiceTest(BaseAPIIntegrationTest):
secret_data = u'東方花映塚'
secret_id = self.client.create_secret(secret_name, secret_data)
self.tmp_secrets.append(secret_id)
secret_ref = docker.types.SecretReference(secret_id, secret_name)
secret_ref = docker.types.ConfigReference(secret_id, secret_name)
container_spec = docker.types.ContainerSpec(
'busybox', ['sleep', '999'], secrets=[secret_ref]
)
......@@ -481,8 +481,8 @@ class ServiceTest(BaseAPIIntegrationTest):
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'Secrets' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
secrets = svc_info['Spec']['TaskTemplate']['ContainerSpec']['Secrets']
assert 'Configs' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
secrets = svc_info['Spec']['TaskTemplate']['ContainerSpec']['Configs']
assert secrets[0] == secret_ref
container = self.get_service_container(name)
......@@ -493,3 +493,175 @@ class ServiceTest(BaseAPIIntegrationTest):
container_secret = self.client.exec_start(exec_id)
container_secret = container_secret.decode('utf-8')
assert container_secret == secret_data
@requires_api_version('1.25')
def test_create_service_with_config(self):
config_name = 'favorite_touhou'
config_data = b'phantasmagoria of flower view'
config_id = self.client.create_config(config_name, config_data)
self.tmp_configs.append(config_id)
config_ref = docker.types.ConfigReference(config_id, config_name)
container_spec = docker.types.ContainerSpec(
'busybox', ['sleep', '999'], configs=[config_ref]
)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'Configs' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
configs = svc_info['Spec']['TaskTemplate']['ContainerSpec']['Configs']
assert configs[0] == config_ref
container = self.get_service_container(name)
assert container is not None
exec_id = self.client.exec_create(
container, 'cat /run/configs/{0}'.format(config_name)
)
assert self.client.exec_start(exec_id) == config_data
@requires_api_version('1.25')
def test_create_service_with_unicode_config(self):
config_name = 'favorite_touhou'
config_data = u'東方花映塚'
config_id = self.client.create_config(config_name, config_data)
self.tmp_configs.append(config_id)
config_ref = docker.types.ConfigReference(config_id, config_name)
container_spec = docker.types.ContainerSpec(
'busybox', ['sleep', '999'], configs=[config_ref]
)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'Configs' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
configs = svc_info['Spec']['TaskTemplate']['ContainerSpec']['Configs']
assert configs[0] == config_ref
container = self.get_service_container(name)
assert container is not None
exec_id = self.client.exec_create(
container, 'cat /run/configs/{0}'.format(config_name)
)
container_config = self.client.exec_start(exec_id)
container_config = container_config.decode('utf-8')
assert container_config == config_data
@requires_api_version('1.25')
def test_create_service_with_hosts(self):
container_spec = docker.types.ContainerSpec(
'busybox', ['sleep', '999'], hosts={
'foobar': '127.0.0.1',
'baz': '8.8.8.8',
}
)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'Hosts' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
hosts = svc_info['Spec']['TaskTemplate']['ContainerSpec']['Hosts']
assert len(hosts) == 2
assert 'foobar:127.0.0.1' in hosts
assert 'baz:8.8.8.8' in hosts
@requires_api_version('1.25')
def test_create_service_with_hostname(self):
container_spec = docker.types.ContainerSpec(
'busybox', ['sleep', '999'], hostname='foobar.baz.com'
)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'Hostname' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
assert (
svc_info['Spec']['TaskTemplate']['ContainerSpec']['Hostname'] ==
'foobar.baz.com'
)
@requires_api_version('1.25')
def test_create_service_with_groups(self):
container_spec = docker.types.ContainerSpec(
'busybox', ['sleep', '999'], groups=['shrinemaidens', 'youkais']
)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'Groups' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
groups = svc_info['Spec']['TaskTemplate']['ContainerSpec']['Groups']
assert len(groups) == 2
assert 'shrinemaidens' in groups
assert 'youkais' in groups
@requires_api_version('1.25')
def test_create_service_with_dns_config(self):
dns_config = docker.types.DNSConfig(
nameservers=['8.8.8.8', '8.8.4.4'],
search=['local'], options=['debug']
)
container_spec = docker.types.ContainerSpec(
BUSYBOX, ['sleep', '999'], dns_config=dns_config
)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert 'DNSConfig' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
assert (
dns_config ==
svc_info['Spec']['TaskTemplate']['ContainerSpec']['DNSConfig']
)
@requires_api_version('1.25')
def test_create_service_with_healthcheck(self):
second = 1000000000
hc = docker.types.Healthcheck(
test='true', retries=3, timeout=1 * second,
start_period=3 * second, interval=second / 2,
)
container_spec = docker.types.ContainerSpec(
BUSYBOX, ['sleep', '999'], healthcheck=hc
)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert (
'Healthcheck' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
)
assert (
hc ==
svc_info['Spec']['TaskTemplate']['ContainerSpec']['Healthcheck']
)
@requires_api_version('1.28')
def test_create_service_with_readonly(self):
container_spec = docker.types.ContainerSpec(
BUSYBOX, ['sleep', '999'], read_only=True
)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert (
'ReadOnly' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
)
assert svc_info['Spec']['TaskTemplate']['ContainerSpec']['ReadOnly']
@requires_api_version('1.28')
def test_create_service_with_stop_signal(self):
container_spec = docker.types.ContainerSpec(
BUSYBOX, ['sleep', '999'], stop_signal='SIGINT'
)
task_tmpl = docker.types.TaskTemplate(container_spec)
name = self.get_service_name()
svc_id = self.client.create_service(task_tmpl, name=name)
svc_info = self.client.inspect_service(svc_id)
assert (
'StopSignal' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
)
assert (
svc_info['Spec']['TaskTemplate']['ContainerSpec']['StopSignal'] ==
'SIGINT'
)
......@@ -29,6 +29,7 @@ class BaseIntegrationTest(unittest.TestCase):
self.tmp_networks = []
self.tmp_plugins = []
self.tmp_secrets = []
self.tmp_configs = []
def tearDown(self):
client = docker.from_env(version=TEST_API_VERSION)
......@@ -59,6 +60,12 @@ class BaseIntegrationTest(unittest.TestCase):
except docker.errors.APIError:
pass
for config in self.tmp_configs:
try:
client.api.remove_config(config)
except docker.errors.APIError:
pass
for folder in self.tmp_folders:
shutil.rmtree(folder)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment