Skip to content

Commit

Permalink
Add support for configs management
Browse files Browse the repository at this point in the history
Signed-off-by: Joffrey F <[email protected]>
  • Loading branch information
shin- committed Nov 7, 2017
1 parent cd47a1f commit b130163
Show file tree
Hide file tree
Showing 11 changed files with 464 additions and 3 deletions.
2 changes: 2 additions & 0 deletions docker/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import websocket

from .build import BuildApiMixin
from .config import ConfigApiMixin
from .container import ContainerApiMixin
from .daemon import DaemonApiMixin
from .exec_api import ExecApiMixin
Expand Down Expand Up @@ -43,6 +44,7 @@
class APIClient(
requests.Session,
BuildApiMixin,
ConfigApiMixin,
ContainerApiMixin,
DaemonApiMixin,
ExecApiMixin,
Expand Down
91 changes: 91 additions & 0 deletions docker/api/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
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)
9 changes: 9 additions & 0 deletions docker/client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
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
Expand Down Expand Up @@ -80,6 +81,14 @@ def from_env(cls, **kwargs):
**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):
"""
Expand Down
69 changes: 69 additions & 0 deletions docker/models/configs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
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]
10 changes: 10 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------

Expand Down
1 change: 1 addition & 0 deletions docs/client.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Client reference

.. autoclass:: DockerClient()

.. autoattribute:: configs
.. autoattribute:: containers
.. autoattribute:: images
.. autoattribute:: networks
Expand Down
30 changes: 30 additions & 0 deletions docs/configs.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
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
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
69 changes: 69 additions & 0 deletions tests/integration/api_config_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# -*- 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']
Loading

0 comments on commit b130163

Please sign in to comment.