APIClient implementation of plugin methods

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2017-01-31 18:39:33 -08:00
parent d3798b157c
commit 9296971e4c
4 changed files with 337 additions and 1 deletions

View File

@ -14,6 +14,7 @@ from .daemon import DaemonApiMixin
from .exec_api import ExecApiMixin
from .image import ImageApiMixin
from .network import NetworkApiMixin
from .plugin import PluginApiMixin
from .service import ServiceApiMixin
from .swarm import SwarmApiMixin
from .volume import VolumeApiMixin
@ -46,6 +47,7 @@ class APIClient(
ExecApiMixin,
ImageApiMixin,
NetworkApiMixin,
PluginApiMixin,
ServiceApiMixin,
SwarmApiMixin,
VolumeApiMixin):
@ -225,10 +227,12 @@ class APIClient(
# Go <1.1 can't unserialize null to a string
# so we do this disgusting thing here.
data2 = {}
if data is not None:
if data is not None and isinstance(data, dict):
for k, v in six.iteritems(data):
if v is not None:
data2[k] = v
else:
data2 = data
if 'headers' not in kwargs:
kwargs['headers'] = {}

207
docker/api/plugin.py Normal file
View File

@ -0,0 +1,207 @@
import six
from .. import auth, utils
class PluginApiMixin(object):
@utils.minimum_version('1.25')
@utils.check_resource
def configure_plugin(self, name, options):
"""
Configure a plugin.
Args:
name (string): The name of the plugin. The ``:latest`` tag is
optional, and is the default if omitted.
options (dict): A key-value mapping of options
Returns:
``True`` if successful
"""
url = self._url('/plugins/{0}/set', name)
data = options
if isinstance(data, dict):
data = ['{0}={1}'.format(k, v) for k, v in six.iteritems(data)]
res = self._post_json(url, data=data)
self._raise_for_status(res)
return True
def create_plugin(self, name, rootfs, manifest):
"""
Create a new plugin.
Args:
name (string): The name of the plugin. The ``:latest`` tag is
optional, and is the default if omitted.
rootfs (string): Path to the plugin's ``rootfs``
manifest (string): Path to the plugin's manifest file
Returns:
``True`` if successful
"""
# FIXME: Needs implementation
raise NotImplementedError()
@utils.minimum_version('1.25')
def disable_plugin(self, name):
"""
Disable an installed plugin.
Args:
name (string): The name of the plugin. The ``:latest`` tag is
optional, and is the default if omitted.
Returns:
``True`` if successful
"""
url = self._url('/plugins/{0}/disable', name)
res = self._post(url)
self._raise_for_status(res)
return True
@utils.minimum_version('1.25')
def enable_plugin(self, name, timeout=0):
"""
Enable an installed plugin.
Args:
name (string): The name of the plugin. The ``:latest`` tag is
optional, and is the default if omitted.
timeout (int): Operation timeout (in seconds). Default: 0
Returns:
``True`` if successful
"""
url = self._url('/plugins/{0}/enable', name)
params = {'timeout': timeout}
res = self._post(url, params=params)
self._raise_for_status(res)
return True
@utils.minimum_version('1.25')
def inspect_plugin(self, name):
"""
Retrieve plugin metadata.
Args:
name (string): The name of the plugin. The ``:latest`` tag is
optional, and is the default if omitted.
Returns:
A dict containing plugin info
"""
url = self._url('/plugins/{0}/json', name)
return self._result(self._get(url), True)
@utils.minimum_version('1.25')
def pull_plugin(self, remote, privileges, name=None):
"""
Pull and install a plugin. After the plugin is installed, it can be
enabled using :py:meth:`~enable_plugin`.
Args:
remote (string): Remote reference for the plugin to install.
The ``:latest`` tag is optional, and is the default if
omitted.
privileges (list): A list of privileges the user consents to
grant to the plugin. Can be retrieved using
:py:meth:`~plugin_privileges`.
name (string): Local name for the pulled plugin. The
``:latest`` tag is optional, and is the default if omitted.
Returns:
An iterable object streaming the decoded API logs
"""
url = self._url('/plugins/pull')
params = {
'remote': remote,
}
if name:
params['name'] = name
headers = {}
registry, repo_name = auth.resolve_repository_name(remote)
header = auth.get_config_header(self, registry)
if header:
headers['X-Registry-Auth'] = header
response = self._post_json(
url, params=params, headers=headers, data=privileges,
stream=True
)
self._raise_for_status(response)
return self._stream_helper(response, decode=True)
@utils.minimum_version('1.25')
def plugins(self):
"""
Retrieve a list of installed plugins.
Returns:
A list of dicts, one per plugin
"""
url = self._url('/plugins')
return self._result(self._get(url), True)
@utils.minimum_version('1.25')
def plugin_privileges(self, name):
"""
Retrieve list of privileges to be granted to a plugin.
Args:
name (string): Name of the remote plugin to examine. The
``:latest`` tag is optional, and is the default if omitted.
Returns:
A list of dictionaries representing the plugin's
permissions
"""
params = {
'remote': name,
}
url = self._url('/plugins/privileges')
return self._result(self._get(url, params=params), True)
@utils.minimum_version('1.25')
@utils.check_resource
def push_plugin(self, name):
"""
Push a plugin to the registry.
Args:
name (string): Name of the plugin to upload. The ``:latest``
tag is optional, and is the default if omitted.
Returns:
``True`` if successful
"""
url = self._url('/plugins/{0}/pull', name)
headers = {}
registry, repo_name = auth.resolve_repository_name(name)
header = auth.get_config_header(self, registry)
if header:
headers['X-Registry-Auth'] = header
res = self._post(url, headers=headers)
self._raise_for_status(res)
return self._stream_helper(res, decode=True)
@utils.minimum_version('1.25')
def remove_plugin(self, name, force=False):
"""
Remove an installed plugin.
Args:
name (string): Name of the plugin to remove. The ``:latest``
tag is optional, and is the default if omitted.
force (bool): Disable the plugin before removing. This may
result in issues if the plugin is in use by a container.
Returns:
``True`` if successful
"""
url = self._url('/plugins/{0}', name)
res = self._delete(url, params={'force': force})
self._raise_for_status(res)
return True

View File

@ -87,6 +87,17 @@ Services
:members:
:undoc-members:
Plugins
-------
.. py:module:: docker.api.plugin
.. rst-class:: hide-signature
.. autoclass:: PluginApiMixin
:members:
:undoc-members:
The Docker daemon
-----------------

View File

@ -0,0 +1,114 @@
import docker
import pytest
from .base import BaseAPIIntegrationTest, TEST_API_VERSION
SSHFS = 'vieux/sshfs:latest'
class PluginTest(BaseAPIIntegrationTest):
@classmethod
def teardown_class(cls):
c = docker.APIClient(
version=TEST_API_VERSION, timeout=60,
**docker.utils.kwargs_from_env()
)
try:
c.remove_plugin(SSHFS, force=True)
except docker.errors.APIError:
pass
def teardown_method(self, method):
try:
self.client.disable_plugin(SSHFS)
except docker.errors.APIError:
pass
def ensure_plugin_installed(self, plugin_name):
try:
return self.client.inspect_plugin(plugin_name)
except docker.errors.NotFound:
prv = self.client.plugin_privileges(plugin_name)
for d in self.client.pull_plugin(plugin_name, prv):
pass
return self.client.inspect_plugin(plugin_name)
def test_enable_plugin(self):
pl_data = self.ensure_plugin_installed(SSHFS)
assert pl_data['Enabled'] is False
assert self.client.enable_plugin(SSHFS)
pl_data = self.client.inspect_plugin(SSHFS)
assert pl_data['Enabled'] is True
with pytest.raises(docker.errors.APIError):
self.client.enable_plugin(SSHFS)
def test_disable_plugin(self):
pl_data = self.ensure_plugin_installed(SSHFS)
assert pl_data['Enabled'] is False
assert self.client.enable_plugin(SSHFS)
pl_data = self.client.inspect_plugin(SSHFS)
assert pl_data['Enabled'] is True
self.client.disable_plugin(SSHFS)
pl_data = self.client.inspect_plugin(SSHFS)
assert pl_data['Enabled'] is False
with pytest.raises(docker.errors.APIError):
self.client.disable_plugin(SSHFS)
def test_inspect_plugin(self):
self.ensure_plugin_installed(SSHFS)
data = self.client.inspect_plugin(SSHFS)
assert 'Config' in data
assert 'Name' in data
assert data['Name'] == SSHFS
def test_plugin_privileges(self):
prv = self.client.plugin_privileges(SSHFS)
assert isinstance(prv, list)
for item in prv:
assert 'Name' in item
assert 'Value' in item
assert 'Description' in item
def test_list_plugins(self):
self.ensure_plugin_installed(SSHFS)
data = self.client.plugins()
assert len(data) > 0
plugin = [p for p in data if p['Name'] == SSHFS][0]
assert 'Config' in plugin
def test_configure_plugin(self):
pl_data = self.ensure_plugin_installed(SSHFS)
assert pl_data['Enabled'] is False
self.client.configure_plugin(SSHFS, {
'DEBUG': '1'
})
pl_data = self.client.inspect_plugin(SSHFS)
assert 'Env' in pl_data['Settings']
assert 'DEBUG=1' in pl_data['Settings']['Env']
self.client.configure_plugin(SSHFS, ['DEBUG=0'])
pl_data = self.client.inspect_plugin(SSHFS)
assert 'DEBUG=0' in pl_data['Settings']['Env']
def test_remove_plugin(self):
pl_data = self.ensure_plugin_installed(SSHFS)
assert pl_data['Enabled'] is False
assert self.client.remove_plugin(SSHFS) is True
def test_force_remove_plugin(self):
self.ensure_plugin_installed(SSHFS)
self.client.enable_plugin(SSHFS)
assert self.client.inspect_plugin(SSHFS)['Enabled'] is True
assert self.client.remove_plugin(SSHFS, force=True) is True
def test_install_plugin(self):
try:
self.client.remove_plugin(SSHFS, force=True)
except docker.errors.APIError:
pass
prv = self.client.plugin_privileges(SSHFS)
logs = [d for d in self.client.pull_plugin(SSHFS, prv)]
assert filter(lambda x: x['status'] == 'Download complete', logs)
assert self.client.inspect_plugin(SSHFS)
assert self.client.enable_plugin(SSHFS)