Kaydet (Commit) 4845dae0 authored tarafından Joffrey F's avatar Joffrey F

put/get archive implementation

Signed-off-by: 's avatarJoffrey F <joffrey@docker.com>
üst eb869c0b
......@@ -75,6 +75,12 @@ class ContainerApiMixin(object):
@utils.check_resource
def copy(self, container, resource):
if utils.version_gte(self._version, '1.20'):
warnings.warn(
'Client.copy() is deprecated for API version >= 1.20, '
'please use get_archive() instead',
DeprecationWarning
)
res = self._post_json(
self._url("/containers/{0}/copy".format(container)),
data={"Resource": resource},
......@@ -145,6 +151,21 @@ class ContainerApiMixin(object):
self._raise_for_status(res)
return res.raw
@utils.check_resource
@utils.minimum_version('1.20')
def get_archive(self, container, path):
params = {
'path': path
}
url = self._url('/containers/{0}/archive', container)
res = self._get(url, params=params, stream=True)
self._raise_for_status(res)
encoded_stat = res.headers.get('x-docker-container-path-stat')
return (
res.raw,
utils.decode_json_header(encoded_stat) if encoded_stat else None
)
@utils.check_resource
def inspect_container(self, container):
return self._result(
......@@ -214,6 +235,15 @@ class ContainerApiMixin(object):
return h_ports
@utils.check_resource
@utils.minimum_version('1.20')
def put_archive(self, container, path, data):
params = {'path': path}
url = self._url('/containers/{0}/archive', container)
res = self._put(url, params=params, data=data)
self._raise_for_status(res)
return res.status_code == 200
@utils.check_resource
def remove_container(self, container, v=False, link=False, force=False):
params = {'v': v, 'link': link, 'force': force}
......
......@@ -109,6 +109,9 @@ class Client(
def _get(self, url, **kwargs):
return self.get(url, **self._set_request_timeout(kwargs))
def _put(self, url, **kwargs):
return self.put(url, **self._set_request_timeout(kwargs))
def _delete(self, url, **kwargs):
return self.delete(url, **self._set_request_timeout(kwargs))
......
......@@ -3,7 +3,7 @@ from .utils import (
mkbuildcontext, tar, exclude_paths, parse_repository_tag, parse_host,
kwargs_from_env, convert_filters, create_host_config,
create_container_config, parse_bytes, ping_registry, parse_env_file,
version_lt, version_gte
version_lt, version_gte, decode_json_header
) # flake8: noqa
from .types import Ulimit, LogConfig # flake8: noqa
......
......@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import base64
import io
import os
import os.path
......@@ -66,6 +67,13 @@ def mkbuildcontext(dockerfile):
return f
def decode_json_header(header):
data = base64.b64decode(header)
if six.PY3:
data = data.decode('utf-8')
return json.loads(data)
def tar(path, exclude=None, dockerfile=None):
f = tempfile.NamedTemporaryFile()
t = tarfile.open(mode='w', fileobj=f)
......
......@@ -165,6 +165,8 @@ non-running ones
## copy
Identical to the `docker cp` command. Get files/folders from the container.
**Deprecated for API version >= 1.20** &ndash; Consider using
[`get_archive`](#get_archive) **instead.**
**Params**:
......@@ -376,6 +378,27 @@ Export the contents of a filesystem as a tar archive to STDOUT.
**Returns** (str): The filesystem tar archive as a str
## get_archive
Retrieve a file or folder from a container in the form of a tar archive.
**Params**:
* container (str): The container where the file is located
* path (str): Path to the file or folder to retrieve
**Returns** (tuple): First element is a raw tar data stream. Second element is
a dict containing `stat` information on the specified `path`.
```python
>>> import docker
>>> c = docker.Client()
>>> ctnr = c.create_container('busybox', 'true')
>>> strm, stat = c.get_archive(ctnr, '/bin/sh')
>>> print(stat)
{u'linkTarget': u'', u'mode': 493, u'mtime': u'2015-09-16T12:34:23-07:00', u'name': u'sh', u'size': 962860}
```
## get_image
Get an image from the docker daemon. Similar to the `docker save` command.
......@@ -712,6 +735,20 @@ command.
yourname/app/tags/latest}"}\\n']
```
## put_archive
Insert a file or folder in an existing container using a tar archive as source.
**Params**:
* container (str): The container where the file(s) will be extracted
* path (str): Path inside the container where the file(s) will be extracted.
Must exist.
* data (bytes): tar data to be extracted
**Returns** (bool): True if the call succeeds. `docker.errors.APIError` will
be raised if an error occurs.
## remove_container
Remove a container. Similar to the `docker rm` command.
......
import os
import os.path
import tarfile
import tempfile
......@@ -14,3 +15,23 @@ def make_tree(dirs, files):
f.write("content")
return base
def simple_tar(path):
f = tempfile.NamedTemporaryFile()
t = tarfile.open(mode='w', fileobj=f)
abs_path = os.path.abspath(path)
t.add(abs_path, arcname=os.path.basename(path), recursive=False)
t.close()
f.seek(0)
return f
def untar_file(tardata, filename):
with tarfile.open(mode='r', fileobj=tardata) as t:
f = t.extractfile(filename)
result = f.read()
f.close()
return result
......@@ -37,6 +37,7 @@ import docker
from docker.errors import APIError, NotFound
from docker.utils import kwargs_from_env
from . import helpers
from .base import requires_api_version
from .test import Cleanup
......@@ -427,6 +428,90 @@ class CreateContainerWithLogConfigTest(BaseTestCase):
self.assertEqual(container_log_config['Config'], {})
@requires_api_version('1.20')
class GetArchiveTest(BaseTestCase):
def test_get_file_archive_from_container(self):
data = 'The Maid and the Pocket Watch of Blood'
ctnr = self.client.create_container(
BUSYBOX, 'sh -c "echo {0} > /vol1/data.txt"'.format(data),
volumes=['/vol1']
)
self.tmp_containers.append(ctnr)
self.client.start(ctnr)
self.client.wait(ctnr)
with tempfile.NamedTemporaryFile() as destination:
strm, stat = self.client.get_archive(ctnr, '/vol1/data.txt')
for d in strm:
destination.write(d)
destination.seek(0)
retrieved_data = helpers.untar_file(destination, 'data.txt')
if six.PY3:
retrieved_data = retrieved_data.decode('utf-8')
self.assertEqual(data, retrieved_data.strip())
def test_get_file_stat_from_container(self):
data = 'The Maid and the Pocket Watch of Blood'
ctnr = self.client.create_container(
BUSYBOX, 'sh -c "echo -n {0} > /vol1/data.txt"'.format(data),
volumes=['/vol1']
)
self.tmp_containers.append(ctnr)
self.client.start(ctnr)
self.client.wait(ctnr)
strm, stat = self.client.get_archive(ctnr, '/vol1/data.txt')
self.assertIn('name', stat)
self.assertEqual(stat['name'], 'data.txt')
self.assertIn('size', stat)
self.assertEqual(stat['size'], len(data))
@requires_api_version('1.20')
class PutArchiveTest(BaseTestCase):
def test_copy_file_to_container(self):
data = b'Deaf To All But The Song'
with tempfile.NamedTemporaryFile() as test_file:
test_file.write(data)
test_file.seek(0)
ctnr = self.client.create_container(
BUSYBOX,
'cat {0}'.format(
os.path.join('/vol1', os.path.basename(test_file.name))
),
volumes=['/vol1']
)
self.tmp_containers.append(ctnr)
with helpers.simple_tar(test_file.name) as test_tar:
self.client.put_archive(ctnr, '/vol1', test_tar)
self.client.start(ctnr)
self.client.wait(ctnr)
logs = self.client.logs(ctnr)
if six.PY3:
logs = logs.decode('utf-8')
data = data.decode('utf-8')
self.assertEqual(logs.strip(), data)
def test_copy_directory_to_container(self):
files = ['a.py', 'b.py', 'foo/b.py']
dirs = ['foo', 'bar']
base = helpers.make_tree(dirs, files)
ctnr = self.client.create_container(
BUSYBOX, 'ls -p /vol1', volumes=['/vol1']
)
self.tmp_containers.append(ctnr)
with docker.utils.tar(base) as test_tar:
self.client.put_archive(ctnr, '/vol1', test_tar)
self.client.start(ctnr)
self.client.wait(ctnr)
logs = self.client.logs(ctnr)
if six.PY3:
logs = logs.decode('utf-8')
results = logs.strip().split()
self.assertIn('a.py', results)
self.assertIn('b.py', results)
self.assertIn('foo/', results)
self.assertIn('bar/', results)
class TestCreateContainerReadOnlyFs(BaseTestCase):
def runTest(self):
if not exec_driver_is_native():
......
# -*- coding: utf-8 -*-
import base64
import json
import os
import os.path
import shutil
......@@ -14,7 +16,7 @@ from docker.errors import DockerException
from docker.utils import (
parse_repository_tag, parse_host, convert_filters, kwargs_from_env,
create_host_config, Ulimit, LogConfig, parse_bytes, parse_env_file,
exclude_paths, convert_volume_binds,
exclude_paths, convert_volume_binds, decode_json_header
)
from docker.utils.ports import build_port_bindings, split_port
from docker.auth import resolve_repository_name, resolve_authconfig
......@@ -370,6 +372,16 @@ class UtilsTest(base.BaseTestCase):
for filters, expected in tests:
self.assertEqual(convert_filters(filters), expected)
def test_decode_json_header(self):
obj = {'a': 'b', 'c': 1}
data = None
if six.PY3:
data = base64.b64encode(bytes(json.dumps(obj), 'utf-8'))
else:
data = base64.b64encode(json.dumps(obj))
decoded_data = decode_json_header(data)
self.assertEqual(obj, decoded_data)
def test_resolve_repository_name(self):
# docker hub library image
self.assertEqual(
......
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