mirror of https://github.com/docker/docker-py.git
Add create_plugin implementation
Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
cd05d8d53d
commit
e1ad3186ef
|
@ -5,3 +5,4 @@ include README.rst
|
||||||
include LICENSE
|
include LICENSE
|
||||||
recursive-include tests *.py
|
recursive-include tests *.py
|
||||||
recursive-include tests/unit/testdata *
|
recursive-include tests/unit/testdata *
|
||||||
|
recursive-include tests/integration/testdata *
|
||||||
|
|
|
@ -26,21 +26,28 @@ class PluginApiMixin(object):
|
||||||
self._raise_for_status(res)
|
self._raise_for_status(res)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def create_plugin(self, name, rootfs, manifest):
|
@utils.minimum_version('1.25')
|
||||||
|
def create_plugin(self, name, plugin_data_dir, gzip=False):
|
||||||
"""
|
"""
|
||||||
Create a new plugin.
|
Create a new plugin.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (string): The name of the plugin. The ``:latest`` tag is
|
name (string): The name of the plugin. The ``:latest`` tag is
|
||||||
optional, and is the default if omitted.
|
optional, and is the default if omitted.
|
||||||
rootfs (string): Path to the plugin's ``rootfs``
|
plugin_data_dir (string): Path to the plugin data directory.
|
||||||
manifest (string): Path to the plugin's manifest file
|
Plugin data directory must contain the ``config.json``
|
||||||
|
manifest file and the ``rootfs`` directory.
|
||||||
|
gzip (bool): Compress the context using gzip. Default: False
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
``True`` if successful
|
``True`` if successful
|
||||||
"""
|
"""
|
||||||
# FIXME: Needs implementation
|
url = self._url('/plugins/create')
|
||||||
raise NotImplementedError()
|
|
||||||
|
with utils.create_archive(root=plugin_data_dir, gzip=gzip) as archv:
|
||||||
|
res = self._post(url, params={'name': name}, data=archv)
|
||||||
|
self._raise_for_status(res)
|
||||||
|
return True
|
||||||
|
|
||||||
@utils.minimum_version('1.25')
|
@utils.minimum_version('1.25')
|
||||||
def disable_plugin(self, name):
|
def disable_plugin(self, name):
|
||||||
|
|
|
@ -100,20 +100,22 @@ class Plugin(Model):
|
||||||
class PluginCollection(Collection):
|
class PluginCollection(Collection):
|
||||||
model = Plugin
|
model = Plugin
|
||||||
|
|
||||||
def create(self, name, rootfs, manifest):
|
def create(self, name, plugin_data_dir, gzip=False):
|
||||||
"""
|
"""
|
||||||
Create a new plugin.
|
Create a new plugin.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
name (string): The name of the plugin. The ``:latest`` tag is
|
name (string): The name of the plugin. The ``:latest`` tag is
|
||||||
optional, and is the default if omitted.
|
optional, and is the default if omitted.
|
||||||
rootfs (string): Path to the plugin's ``rootfs``
|
plugin_data_dir (string): Path to the plugin data directory.
|
||||||
manifest (string): Path to the plugin's manifest file
|
Plugin data directory must contain the ``config.json``
|
||||||
|
manifest file and the ``rootfs`` directory.
|
||||||
|
gzip (bool): Compress the context using gzip. Default: False
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
(:py:class:`Plugin`): The newly created plugin.
|
(:py:class:`Plugin`): The newly created plugin.
|
||||||
"""
|
"""
|
||||||
self.client.api.create_plugin(name, rootfs, manifest)
|
self.client.api.create_plugin(name, plugin_data_dir, gzip)
|
||||||
return self.get(name)
|
return self.get(name)
|
||||||
|
|
||||||
def get(self, name):
|
def get(self, name):
|
||||||
|
|
|
@ -6,7 +6,7 @@ from .utils import (
|
||||||
create_host_config, parse_bytes, ping_registry, parse_env_file, version_lt,
|
create_host_config, parse_bytes, ping_registry, parse_env_file, version_lt,
|
||||||
version_gte, decode_json_header, split_command, create_ipam_config,
|
version_gte, decode_json_header, split_command, create_ipam_config,
|
||||||
create_ipam_pool, parse_devices, normalize_links, convert_service_networks,
|
create_ipam_pool, parse_devices, normalize_links, convert_service_networks,
|
||||||
format_environment
|
format_environment, create_archive
|
||||||
)
|
)
|
||||||
|
|
||||||
from .decorators import check_resource, minimum_version, update_headers
|
from .decorators import check_resource, minimum_version, update_headers
|
||||||
|
|
|
@ -80,16 +80,35 @@ def decode_json_header(header):
|
||||||
|
|
||||||
|
|
||||||
def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):
|
def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):
|
||||||
if not fileobj:
|
|
||||||
fileobj = tempfile.NamedTemporaryFile()
|
|
||||||
t = tarfile.open(mode='w:gz' if gzip else 'w', fileobj=fileobj)
|
|
||||||
|
|
||||||
root = os.path.abspath(path)
|
root = os.path.abspath(path)
|
||||||
exclude = exclude or []
|
exclude = exclude or []
|
||||||
|
|
||||||
for path in sorted(exclude_paths(root, exclude, dockerfile=dockerfile)):
|
return create_archive(
|
||||||
i = t.gettarinfo(os.path.join(root, path), arcname=path)
|
files=sorted(exclude_paths(root, exclude, dockerfile=dockerfile)),
|
||||||
|
root=root, fileobj=fileobj, gzip=gzip
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def build_file_list(root):
|
||||||
|
files = []
|
||||||
|
for dirname, dirnames, fnames in os.walk(root):
|
||||||
|
for filename in fnames + dirnames:
|
||||||
|
longpath = os.path.join(dirname, filename)
|
||||||
|
files.append(
|
||||||
|
longpath.replace(root, '', 1).lstrip('/')
|
||||||
|
)
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
|
def create_archive(root, files=None, fileobj=None, gzip=False):
|
||||||
|
if not fileobj:
|
||||||
|
fileobj = tempfile.NamedTemporaryFile()
|
||||||
|
t = tarfile.open(mode='w:gz' if gzip else 'w', fileobj=fileobj)
|
||||||
|
if files is None:
|
||||||
|
files = build_file_list(root)
|
||||||
|
for path in files:
|
||||||
|
i = t.gettarinfo(os.path.join(root, path), arcname=path)
|
||||||
if i is None:
|
if i is None:
|
||||||
# This happens when we encounter a socket file. We can safely
|
# This happens when we encounter a socket file. We can safely
|
||||||
# ignore it and proceed.
|
# ignore it and proceed.
|
||||||
|
@ -102,13 +121,11 @@ def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# We open the file object in binary mode for Windows support.
|
# We open the file object in binary mode for Windows support.
|
||||||
f = open(os.path.join(root, path), 'rb')
|
with open(os.path.join(root, path), 'rb') as f:
|
||||||
|
t.addfile(i, f)
|
||||||
except IOError:
|
except IOError:
|
||||||
# When we encounter a directory the file object is set to None.
|
# When we encounter a directory the file object is set to None.
|
||||||
f = None
|
t.addfile(i, None)
|
||||||
|
|
||||||
t.addfile(i, f)
|
|
||||||
|
|
||||||
t.close()
|
t.close()
|
||||||
fileobj.seek(0)
|
fileobj.seek(0)
|
||||||
return fileobj
|
return fileobj
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
|
import os
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from .base import BaseAPIIntegrationTest, TEST_API_VERSION
|
from .base import BaseAPIIntegrationTest, TEST_API_VERSION
|
||||||
|
from ..helpers import requires_api_version
|
||||||
|
|
||||||
SSHFS = 'vieux/sshfs:latest'
|
SSHFS = 'vieux/sshfs:latest'
|
||||||
|
|
||||||
|
|
||||||
|
@requires_api_version('1.25')
|
||||||
class PluginTest(BaseAPIIntegrationTest):
|
class PluginTest(BaseAPIIntegrationTest):
|
||||||
@classmethod
|
@classmethod
|
||||||
def teardown_class(cls):
|
def teardown_class(cls):
|
||||||
|
@ -24,6 +28,12 @@ class PluginTest(BaseAPIIntegrationTest):
|
||||||
except docker.errors.APIError:
|
except docker.errors.APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
for p in self.tmp_plugins:
|
||||||
|
try:
|
||||||
|
self.client.remove_plugin(p, force=True)
|
||||||
|
except docker.errors.APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
def ensure_plugin_installed(self, plugin_name):
|
def ensure_plugin_installed(self, plugin_name):
|
||||||
try:
|
try:
|
||||||
return self.client.inspect_plugin(plugin_name)
|
return self.client.inspect_plugin(plugin_name)
|
||||||
|
@ -112,3 +122,14 @@ class PluginTest(BaseAPIIntegrationTest):
|
||||||
assert filter(lambda x: x['status'] == 'Download complete', logs)
|
assert filter(lambda x: x['status'] == 'Download complete', logs)
|
||||||
assert self.client.inspect_plugin(SSHFS)
|
assert self.client.inspect_plugin(SSHFS)
|
||||||
assert self.client.enable_plugin(SSHFS)
|
assert self.client.enable_plugin(SSHFS)
|
||||||
|
|
||||||
|
def test_create_plugin(self):
|
||||||
|
plugin_data_dir = os.path.join(
|
||||||
|
os.path.dirname(__file__), 'testdata/dummy-plugin'
|
||||||
|
)
|
||||||
|
assert self.client.create_plugin(
|
||||||
|
'docker-sdk-py/dummy', plugin_data_dir
|
||||||
|
)
|
||||||
|
self.tmp_plugins.append('docker-sdk-py/dummy')
|
||||||
|
data = self.client.inspect_plugin('docker-sdk-py/dummy')
|
||||||
|
assert data['Config']['Entrypoint'] == ['/dummy']
|
||||||
|
|
|
@ -27,6 +27,7 @@ class BaseIntegrationTest(unittest.TestCase):
|
||||||
self.tmp_folders = []
|
self.tmp_folders = []
|
||||||
self.tmp_volumes = []
|
self.tmp_volumes = []
|
||||||
self.tmp_networks = []
|
self.tmp_networks = []
|
||||||
|
self.tmp_plugins = []
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
client = docker.from_env(version=TEST_API_VERSION)
|
client = docker.from_env(version=TEST_API_VERSION)
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"description": "Dummy test plugin for docker python SDK",
|
||||||
|
"documentation": "https://github.com/docker/docker-py",
|
||||||
|
"entrypoint": ["/dummy"],
|
||||||
|
"network": {
|
||||||
|
"type": "host"
|
||||||
|
},
|
||||||
|
"interface" : {
|
||||||
|
"types": ["docker.volumedriver/1.0"],
|
||||||
|
"socket": "dummy.sock"
|
||||||
|
},
|
||||||
|
"env": [
|
||||||
|
{
|
||||||
|
"name":"DEBUG",
|
||||||
|
"settable":["value"],
|
||||||
|
"value":"0"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue