mirror of https://github.com/docker/docker-py.git
Cleanup, externalized into modules, fixed auth
This commit is contained in:
parent
dc3937faa8
commit
99c9eadc82
|
@ -1 +1,2 @@
|
|||
from .client import Client
|
||||
import auth
|
|
@ -0,0 +1,108 @@
|
|||
import base64
|
||||
import json
|
||||
import os
|
||||
|
||||
import six
|
||||
|
||||
|
||||
def swap_protocol(url):
|
||||
if url.startswith('http://'):
|
||||
return url.replace('http://', 'https://', 1)
|
||||
if url.startswith('https://'):
|
||||
return url.replace('https://', 'http://', 1)
|
||||
return url
|
||||
|
||||
|
||||
def expand_registry_url(hostname):
|
||||
if hostname.startswith('http:') or hostname.startswith('https:'):
|
||||
if '/' not in hostname[9:]:
|
||||
hostname = hostname + '/v1/'
|
||||
return hostname
|
||||
#FIXME: ping https then fallback to http
|
||||
return 'http://' + hostname + '/v1/'
|
||||
|
||||
|
||||
def resolve_repository_name(repo_name):
|
||||
if '://' in repo_name:
|
||||
raise ValueError('Repository name can not contain a'
|
||||
'scheme ({0})'.format(repo_name))
|
||||
parts = repo_name.split('/', 1)
|
||||
if not '.' in parts[0] and not ':' in parts[0] and parts[0] != 'localhost':
|
||||
# This is a docker index repo (ex: foo/bar or ubuntu)
|
||||
return 'https://index.docker.io/v1/', repo_name
|
||||
if len(parts) < 2:
|
||||
raise ValueError('Invalid repository name ({0})'.format(repo_name))
|
||||
|
||||
if 'index.docker.io' in parts[0]:
|
||||
raise ValueError('Invalid repository name,'
|
||||
'try "{0}" instead'.format(parts[1]))
|
||||
|
||||
return expand_registry_url(parts[0]), parts[1]
|
||||
|
||||
|
||||
def resolve_authconfig(authconfig, registry):
|
||||
if registry == 'https://index.docker.io/v1/' or registry == '':
|
||||
# default to the index server
|
||||
return authconfig['Configs']['https://index.docker.io/v1/']
|
||||
# if its not the index server there are three cases:
|
||||
#
|
||||
# 1. this is a full config url -> it should be used as is
|
||||
# 2. it could be a full url, but with the wrong protocol
|
||||
# 3. it can be the hostname optionally with a port
|
||||
#
|
||||
# as there is only one auth entry which is fully qualified we need to start
|
||||
# parsing and matching
|
||||
if '/' not in registry:
|
||||
registry = registry + '/v1/'
|
||||
if not registry.startswith('http:') and not registry.startswith('https:'):
|
||||
registry = 'https://' + registry
|
||||
|
||||
if registry in authconfig['Configs']:
|
||||
return authconfig['Configs'][registry]
|
||||
elif swap_protocol(registry) in authconfig['Configs']:
|
||||
return authconfig['Configs'][swap_protocol(registry)]
|
||||
return {}
|
||||
|
||||
|
||||
def decode_auth(auth):
|
||||
s = base64.b64decode(auth)
|
||||
login, pwd = s.split(':')
|
||||
return login, pwd
|
||||
|
||||
|
||||
def encode_header(auth):
|
||||
auth_json = json.dumps(auth)
|
||||
return base64.b64encode(auth_json)
|
||||
|
||||
|
||||
def load_config(root=None):
|
||||
if root is None:
|
||||
root = os.environ['HOME']
|
||||
config_file = {
|
||||
'Configs': {},
|
||||
'rootPath': root
|
||||
}
|
||||
f = open(os.path.join(root, '.dockercfg'))
|
||||
try:
|
||||
config_file['Configs'] = json.load(f)
|
||||
for k, conf in six.iteritems(config_file['Configs']):
|
||||
conf['Username'], conf['Password'] = decode_auth(conf['auth'])
|
||||
del conf['auth']
|
||||
config_file['Configs'][k] = conf
|
||||
except Exception:
|
||||
f.seek(0)
|
||||
buf = []
|
||||
for line in f:
|
||||
k, v = line.split(' = ')
|
||||
buf.append(v)
|
||||
if len(buf) < 2:
|
||||
raise Exception("The Auth config file is empty")
|
||||
user, pwd = decode_auth(buf[0])
|
||||
config_file['Configs']['https://index.docker.io/v1/'] = {
|
||||
'Username': user,
|
||||
'Password': pwd,
|
||||
'Email': buf[1]
|
||||
}
|
||||
finally:
|
||||
f.close()
|
||||
return config_file
|
143
docker/client.py
143
docker/client.py
|
@ -1,74 +1,26 @@
|
|||
import base64
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import six
|
||||
import shlex
|
||||
import tarfile
|
||||
import tempfile
|
||||
import six
|
||||
import httplib
|
||||
import socket
|
||||
|
||||
import requests
|
||||
from requests.exceptions import HTTPError
|
||||
from requests.adapters import HTTPAdapter
|
||||
from requests.packages.urllib3.connectionpool import HTTPConnectionPool
|
||||
import requests.exceptions
|
||||
import six
|
||||
|
||||
if six.PY3:
|
||||
from io import StringIO
|
||||
else:
|
||||
from StringIO import StringIO
|
||||
|
||||
class UnixHTTPConnection(httplib.HTTPConnection, object):
|
||||
def __init__(self, base_url, unix_socket):
|
||||
httplib.HTTPConnection.__init__(self, 'localhost')
|
||||
self.base_url = base_url
|
||||
self.unix_socket = unix_socket
|
||||
|
||||
def connect(self):
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(self.base_url.replace("unix:/",""))
|
||||
self.sock = sock
|
||||
|
||||
def _extract_path(self, url):
|
||||
#remove the base_url entirely..
|
||||
return url.replace(self.base_url, "")
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
url = self._extract_path(self.unix_socket)
|
||||
super(UnixHTTPConnection, self).request(method, url, **kwargs)
|
||||
|
||||
|
||||
class UnixHTTPConnectionPool(HTTPConnectionPool):
|
||||
def __init__(self, base_url, socket_path):
|
||||
self.socket_path = socket_path
|
||||
self.base_url = base_url
|
||||
super(UnixHTTPConnectionPool, self).__init__(self, 'localhost')
|
||||
|
||||
def _new_conn(self):
|
||||
return UnixHTTPConnection(self.base_url, self.socket_path)
|
||||
|
||||
|
||||
class UnixAdapter(HTTPAdapter):
|
||||
def __init__(self, base_url):
|
||||
self.base_url = base_url
|
||||
super(UnixAdapter, self).__init__()
|
||||
|
||||
def get_connection(self, socket_path, proxies=None):
|
||||
return UnixHTTPConnectionPool(self.base_url, socket_path)
|
||||
import auth
|
||||
import unixconn
|
||||
import utils
|
||||
|
||||
|
||||
class Client(requests.Session):
|
||||
def __init__(self, base_url="unix://var/run/docker.sock", version="1.4"):
|
||||
super(Client, self).__init__()
|
||||
self.mount('unix://', UnixAdapter(base_url))
|
||||
self.mount('unix://', unixconn.UnixAdapter(base_url))
|
||||
self.base_url = base_url
|
||||
self._version = version
|
||||
try:
|
||||
self._cfg = self._load_config()
|
||||
except:
|
||||
self._cfg = auth.load_config()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _url(self, path):
|
||||
|
@ -89,7 +41,7 @@ class Client(requests.Session):
|
|||
http_error_msg += ' "%s"' % response.content
|
||||
|
||||
if http_error_msg:
|
||||
raise HTTPError(http_error_msg, response=response)
|
||||
raise requests.exceptions.HTTPError(http_error_msg, response=response)
|
||||
|
||||
def _result(self, response, json=False):
|
||||
if response.status_code != 200 and response.status_code != 201:
|
||||
|
@ -123,27 +75,6 @@ class Client(requests.Session):
|
|||
'VolumesFrom': volumes_from,
|
||||
}
|
||||
|
||||
def _mkbuildcontext(self, dockerfile):
|
||||
f = tempfile.TemporaryFile()
|
||||
t = tarfile.open(mode='w', fileobj=f)
|
||||
if isinstance(dockerfile, StringIO):
|
||||
dfinfo = tarfile.TarInfo('Dockerfile')
|
||||
dfinfo.size = dockerfile.len
|
||||
else:
|
||||
dfinfo = t.gettarinfo(fileobj=dockerfile, arcname='Dockerfile')
|
||||
t.addfile(dfinfo, dockerfile)
|
||||
t.close()
|
||||
f.seek(0)
|
||||
return f
|
||||
|
||||
def _tar(self, path):
|
||||
f = tempfile.TemporaryFile()
|
||||
t = tarfile.open(mode='w', fileobj=f)
|
||||
t.add(path, arcname='.')
|
||||
t.close()
|
||||
f.seek(0)
|
||||
return f
|
||||
|
||||
def _post_json(self, url, data, **kwargs):
|
||||
# Go <1.1 can't unserialize null to a string
|
||||
# so we do this disgusting thing here.
|
||||
|
@ -158,43 +89,6 @@ class Client(requests.Session):
|
|||
kwargs['headers']['Content-Type'] = 'application/json'
|
||||
return self.post(url, json.dumps(data2), **kwargs)
|
||||
|
||||
def _decode_auth(self, auth):
|
||||
s = base64.b64decode(auth)
|
||||
login, pwd = s.split(':')
|
||||
return login, pwd
|
||||
|
||||
def _load_config(self, root=None):
|
||||
if root is None:
|
||||
root = os.environ['HOME']
|
||||
config_file = {
|
||||
'Configs': {},
|
||||
'rootPath': root
|
||||
}
|
||||
f = open(os.path.join(root, '.dockercfg'))
|
||||
try:
|
||||
config_file['Configs'] = json.load(f)
|
||||
for k, conf in six.iteritems(config_file['Configs']):
|
||||
conf['Username'], conf['Password'] = self._decode_auth(conf['auth'])
|
||||
del conf['auth']
|
||||
config_file['Configs'][k] = conf
|
||||
except:
|
||||
f.seek(0)
|
||||
buf = []
|
||||
for line in f:
|
||||
k, v = line.split(' = ')
|
||||
buf.append(v)
|
||||
if len(buf) < 2:
|
||||
raise Exception("The Auth config file is empty")
|
||||
user, pwd = self._decode_auth(buf[0])
|
||||
config_file['Configs']['https://index.docker.io/v1/'] = {
|
||||
'Username': user,
|
||||
'Password': pwd,
|
||||
'Email': buf[1]
|
||||
}
|
||||
finally:
|
||||
f.close()
|
||||
return config_file
|
||||
|
||||
def attach(self, container):
|
||||
params = {
|
||||
'stdout': 1,
|
||||
|
@ -221,12 +115,12 @@ class Client(requests.Session):
|
|||
raise Exception("Either path or fileobj needs to be provided.")
|
||||
|
||||
if fileobj is not None:
|
||||
context = self._mkbuildcontext(fileobj)
|
||||
context = utils.mkbuildcontext(fileobj)
|
||||
elif (path.startswith('http://') or path.startswith('https://') or
|
||||
path.startswith('git://') or path.startswith('github.com/')):
|
||||
remote = path
|
||||
else:
|
||||
context = self._tar(path)
|
||||
context = utils.tar(path)
|
||||
|
||||
u = self._url('/build')
|
||||
params = { 't': tag, 'remote': remote, 'q': quiet, 'nocache': nocache }
|
||||
|
@ -361,7 +255,7 @@ class Client(requests.Session):
|
|||
}
|
||||
res = self._result(self._post_json(url, req_data), True)
|
||||
try:
|
||||
self._cfg = self._load_config()
|
||||
self._cfg = auth.load_config()
|
||||
finally:
|
||||
return res
|
||||
|
||||
|
@ -399,14 +293,15 @@ class Client(requests.Session):
|
|||
return self._result(self.post(u, None, params=params))
|
||||
|
||||
def push(self, repository):
|
||||
if repository.count("/") < 1:
|
||||
raise ValueError("""Impossible to push a \"root\" repository.
|
||||
Please rename your repository in <user>/<repo>""")
|
||||
registry, repository = auth.resolve_repository_name(repository)
|
||||
if getattr(self, '_cfg', None) is None:
|
||||
self._cfg = self._load_config()
|
||||
self._cfg = auth.load_config()
|
||||
authcfg = auth.resolve_authconfig(self._cfg, registry)
|
||||
u = self._url("/images/{0}/push".format(repository))
|
||||
return self._result(
|
||||
self._post_json(u, self._cfg['Configs']['https://index.docker.io/v1/']))
|
||||
if utils.compare_version('1.5', self._version) >= 0:
|
||||
headers = { 'X-Registry-Auth': auth.encode_header(authcfg) }
|
||||
return self._result(self._post_json(u, None, headers=headers))
|
||||
return self._result(self._post_json(u, authcfg))
|
||||
|
||||
def remove_container(self, *args, **kwargs):
|
||||
params = {
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
import httplib
|
||||
import requests.adapters
|
||||
import requests.packages.urllib3.connectionpool
|
||||
import socket
|
||||
|
||||
HTTPConnectionPool = requests.packages.urllib3.connectionpool.HTTPConnectionPool
|
||||
|
||||
|
||||
class UnixHTTPConnection(httplib.HTTPConnection, object):
|
||||
def __init__(self, base_url, unix_socket):
|
||||
httplib.HTTPConnection.__init__(self, 'localhost')
|
||||
self.base_url = base_url
|
||||
self.unix_socket = unix_socket
|
||||
|
||||
def connect(self):
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
sock.connect(self.base_url.replace("unix:/",""))
|
||||
self.sock = sock
|
||||
|
||||
def _extract_path(self, url):
|
||||
#remove the base_url entirely..
|
||||
return url.replace(self.base_url, "")
|
||||
|
||||
def request(self, method, url, **kwargs):
|
||||
url = self._extract_path(self.unix_socket)
|
||||
super(UnixHTTPConnection, self).request(method, url, **kwargs)
|
||||
|
||||
|
||||
class UnixHTTPConnectionPool(HTTPConnectionPool):
|
||||
def __init__(self, base_url, socket_path):
|
||||
self.socket_path = socket_path
|
||||
self.base_url = base_url
|
||||
super(UnixHTTPConnectionPool, self).__init__(self, 'localhost')
|
||||
|
||||
def _new_conn(self):
|
||||
return UnixHTTPConnection(self.base_url, self.socket_path)
|
||||
|
||||
|
||||
class UnixAdapter(requests.adapters.HTTPAdapter):
|
||||
def __init__(self, base_url):
|
||||
self.base_url = base_url
|
||||
super(UnixAdapter, self).__init__()
|
||||
|
||||
def get_connection(self, socket_path, proxies=None):
|
||||
return UnixHTTPConnectionPool(self.base_url, socket_path)
|
|
@ -0,0 +1,34 @@
|
|||
import six
|
||||
import tarfile
|
||||
import tempfile
|
||||
|
||||
if six.PY3:
|
||||
from io import StringIO
|
||||
else:
|
||||
from StringIO import StringIO
|
||||
|
||||
|
||||
def mkbuildcontext(dockerfile):
|
||||
f = tempfile.TemporaryFile()
|
||||
t = tarfile.open(mode='w', fileobj=f)
|
||||
if isinstance(dockerfile, StringIO):
|
||||
dfinfo = tarfile.TarInfo('Dockerfile')
|
||||
dfinfo.size = dockerfile.len
|
||||
else:
|
||||
dfinfo = t.gettarinfo(fileobj=dockerfile, arcname='Dockerfile')
|
||||
t.addfile(dfinfo, dockerfile)
|
||||
t.close()
|
||||
f.seek(0)
|
||||
return f
|
||||
|
||||
|
||||
def tar(self, path):
|
||||
f = tempfile.TemporaryFile()
|
||||
t = tarfile.open(mode='w', fileobj=f)
|
||||
t.add(path, arcname='.')
|
||||
t.close()
|
||||
f.seek(0)
|
||||
return f
|
||||
|
||||
def compare_version(v1, v2):
|
||||
return float(v2) - float(v1)
|
|
@ -5,7 +5,6 @@ import tempfile
|
|||
import time
|
||||
import unittest
|
||||
|
||||
|
||||
import docker
|
||||
import six
|
||||
|
||||
|
@ -365,19 +364,17 @@ class TestLoadConfig(BaseTestCase):
|
|||
def runTest(self):
|
||||
folder = tempfile.mkdtemp()
|
||||
f = open(os.path.join(folder, '.dockercfg'), 'w')
|
||||
auth = base64.b64encode('sakuya:izayoi')
|
||||
f.write('auth = {0}\n'.format(auth))
|
||||
auth_ = base64.b64encode('sakuya:izayoi')
|
||||
f.write('auth = {0}\n'.format(auth_))
|
||||
f.write('email = sakuya@scarlet.net')
|
||||
f.close()
|
||||
cfg = self.client._load_config(folder)
|
||||
self.assertNotEqual(cfg['Configs']['index.docker.io'], None)
|
||||
cfg = cfg['Configs']['index.docker.io']
|
||||
cfg = docker.auth.load_config(folder)
|
||||
self.assertNotEqual(cfg['Configs']['https://index.docker.io/v1/'], None)
|
||||
cfg = cfg['Configs']['https://index.docker.io/v1/']
|
||||
self.assertEqual(cfg['Username'], 'sakuya')
|
||||
self.assertEqual(cfg['Password'], 'izayoi')
|
||||
self.assertEqual(cfg['Email'], 'sakuya@scarlet.net')
|
||||
self.assertEqual(cfg.get('Auth'), None)
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in New Issue