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 | ||||
| recursive-include tests *.py | ||||
| recursive-include tests/unit/testdata * | ||||
| recursive-include tests/integration/testdata * | ||||
|  |  | |||
|  | @ -26,21 +26,28 @@ class PluginApiMixin(object): | |||
|         self._raise_for_status(res) | ||||
|         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. | ||||
| 
 | ||||
|             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 | ||||
|                 plugin_data_dir (string): Path to the plugin data directory. | ||||
|                     Plugin data directory must contain the ``config.json`` | ||||
|                     manifest file and the ``rootfs`` directory. | ||||
|                 gzip (bool): Compress the context using gzip. Default: False | ||||
| 
 | ||||
|             Returns: | ||||
|                 ``True`` if successful | ||||
|         """ | ||||
|         # FIXME: Needs implementation | ||||
|         raise NotImplementedError() | ||||
|         url = self._url('/plugins/create') | ||||
| 
 | ||||
|         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') | ||||
|     def disable_plugin(self, name): | ||||
|  |  | |||
|  | @ -100,20 +100,22 @@ class Plugin(Model): | |||
| class PluginCollection(Collection): | ||||
|     model = Plugin | ||||
| 
 | ||||
|     def create(self, name, rootfs, manifest): | ||||
|     def create(self, name, plugin_data_dir, gzip=False): | ||||
|         """ | ||||
|             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 | ||||
|                 plugin_data_dir (string): Path to the plugin data directory. | ||||
|                     Plugin data directory must contain the ``config.json`` | ||||
|                     manifest file and the ``rootfs`` directory. | ||||
|                 gzip (bool): Compress the context using gzip. Default: False | ||||
| 
 | ||||
|             Returns: | ||||
|                 (: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) | ||||
| 
 | ||||
|     def get(self, name): | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ from .utils import ( | |||
|     create_host_config, parse_bytes, ping_registry, parse_env_file, version_lt, | ||||
|     version_gte, decode_json_header, split_command, create_ipam_config, | ||||
|     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 | ||||
|  |  | |||
|  | @ -80,16 +80,35 @@ def decode_json_header(header): | |||
| 
 | ||||
| 
 | ||||
| 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) | ||||
|     exclude = exclude or [] | ||||
| 
 | ||||
|     for path in sorted(exclude_paths(root, exclude, dockerfile=dockerfile)): | ||||
|         i = t.gettarinfo(os.path.join(root, path), arcname=path) | ||||
|     return create_archive( | ||||
|         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: | ||||
|             # This happens when we encounter a socket file. We can safely | ||||
|             # ignore it and proceed. | ||||
|  | @ -102,13 +121,11 @@ def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False): | |||
| 
 | ||||
|         try: | ||||
|             # 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: | ||||
|             # When we encounter a directory the file object is set to None. | ||||
|             f = None | ||||
| 
 | ||||
|         t.addfile(i, f) | ||||
| 
 | ||||
|             t.addfile(i, None) | ||||
|     t.close() | ||||
|     fileobj.seek(0) | ||||
|     return fileobj | ||||
|  |  | |||
|  | @ -1,11 +1,15 @@ | |||
| import os | ||||
| 
 | ||||
| import docker | ||||
| import pytest | ||||
| 
 | ||||
| from .base import BaseAPIIntegrationTest, TEST_API_VERSION | ||||
| from ..helpers import requires_api_version | ||||
| 
 | ||||
| SSHFS = 'vieux/sshfs:latest' | ||||
| 
 | ||||
| 
 | ||||
| @requires_api_version('1.25') | ||||
| class PluginTest(BaseAPIIntegrationTest): | ||||
|     @classmethod | ||||
|     def teardown_class(cls): | ||||
|  | @ -24,6 +28,12 @@ class PluginTest(BaseAPIIntegrationTest): | |||
|         except docker.errors.APIError: | ||||
|             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): | ||||
|         try: | ||||
|             return self.client.inspect_plugin(plugin_name) | ||||
|  | @ -112,3 +122,14 @@ class PluginTest(BaseAPIIntegrationTest): | |||
|         assert filter(lambda x: x['status'] == 'Download complete', logs) | ||||
|         assert self.client.inspect_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_volumes = [] | ||||
|         self.tmp_networks = [] | ||||
|         self.tmp_plugins = [] | ||||
| 
 | ||||
|     def tearDown(self): | ||||
|         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