import sys
import argparse
import json
import traceback
import requests
import requests.auth
import dockerreg.exceptions as ex
from dockerreg.util.applicable \
import Applicable, ApplicableClass, ApplicableMethod
from dockerreg.api import v1 as apiv1, v2 as apiv2
from dockerreg.models import v1 as modelv1, v2 as modelv2
from dockerreg.auth import DockerConfigAuth,BearerToken
import logging
from dockerreg.log import LOG
#
# Here's the flow. User creates a Client() class (which abstracts v1/v2
# somehow, probably by having a ._client field that points to a V1/V2
# client). User invokes operations, gets models (objects) in return.
# User can further invoke operations on those objects, if they make
# sense.
#
# Right now we really only have two endpoints, the overall registry, and
# each repository.
#
# We need a credential cache, as well as a token cache (for those
# credentials). These caches should be indexed by overall registry, or
# by repository. This might mean that we have an AuthManager class that
# takes in credentials, and knows how to annotate requests with auth
# info, AND can handle a WWW-authenticate rejection after the first auth
# attempt with the reg, and can then re-annotate the original request.
# AuthManager needs to expire tokens from its cache as necessary; that
# should be generic.
#
#
# There will be a GetRegistry() that returns a BaseRegistry after
# detecting version (i.e., a RegistryV1 or RegistryV2). Thus if users
# want the MVC style API, they can have that; or if they want a direct
# API client directly, we can provide that too.
#
## def Registry(host,url_prefix=None,username=None,auth=None,auth_url=None,
## verify=True,cert=None,version=None,**kwargs):
## api = ApiClient(host=host,url_prefix=url_prefix,username=username,
## auth=auth,auth_url=auth_url,verify=verify,cert=cert,**kwargs)
## if isinstance(api,apiv1.ApiClientV1):
## return modelv1.RegistryV1(api,name=host)
## elif isinstance(api,apiv2.ApiClientV2):
## return modelv2.RegistryV2(api,name=host)
## if version:
## raise ex.UnsupportedRegistryVersion(str(version))
## else:
## raise ex.UnsupportedRegistryVersion("version autodetect failed!")
[docs]def ApiClient(host,url_prefix=None,username=None,auth=None,auth_url=None,
verify=True,cert=None,key=None,version=None,cache=None,
source_address=None,**kwargs):
if version == 2:
return apiv2.ApiClientV2(
host,url_prefix=url_prefix,username=username,
auth=auth,auth_url=auth_url,verify=verify,cert=cert,key=key,
cache=cache,source_address=source_address,**kwargs)
elif version == 1:
return apiv1.ApiClientV1(
host,url_prefix=url_prefix,username=username,
auth=auth,auth_url=auth_url,verify=verify,cert=cert,key=key,
cache=cache,source_address=source_address,**kwargs)
elif version == None:
try:
return apiv2.ApiClientV2(
host,url_prefix=url_prefix,username=username,
auth=auth,auth_url=auth_url,verify=verify,cert=cert,key=key,
cache=cache,source_address=source_address,**kwargs)
except:
LOG.debug(traceback.format_exc())
try:
return apiv1.ApiClientV1(
host,url_prefix=url_prefix,username=username,
auth=auth,auth_url=auth_url,verify=verify,cert=cert,key=key,
cache=cache,source_address=source_address,**kwargs)
except:
LOG.debug(traceback.format_exc())
pass
if version:
raise ex.UnsupportedRegistryVersionError(str(version))
else:
raise ex.UnsupportedRegistryVersionError("version autodetect failed!")
[docs]class DockerRegistryAuth(requests.auth.AuthBase):
[docs] def __init__(self,client):
self.client = client
def __call__(self,r):
# modify and return the request
return
pass
#@ApplicableClass()
[docs]class RegistryClient(object):
[docs] def __init__(self,host,url_prefix=None,version=None,
username=None,auth=None,auth_url=None,
verify=True,cert=None,key=None):
self._proto = "https"
self._host = host
self._url_prefix = url_prefix
self._auth = auth
self._auth_url = auth_url
self._verify = verify
self._cert = cert
self._key = key
self._api_version = version
self._api_url = None
self._username = username
if self._url_prefix:
self._base_url = self._url_prefix
if not self._base_url.startswith("/"):
self._base_url = "/" + self._base_url
if not self._base_url.endswith("/"):
self._base_url += "/"
else:
self._base_url = "/"
@property
def baseurl(self):
return self._proto + "://" + self._host + self._base_url
@property
def apiurl(self):
if self._api_url is not None:
return self._api_url
if self._api_version == None:
self.version()
if self._api_version == 2:
self._api_url = self.baseurl + "v2/"
# XXX: only support v2 for now
#elif self._api_version == 1:
# self._api_url = self.baseurl + "v2/"
else:
raise ex.DockerRegistryUnsupportedVersionError(self._api_version)
return self._api_url
def _handle_http_error(self,resp):
if resp.status_code != 200:
raise ex.DockerRegistryAPIError(resp.status_code)
[docs] def handle_www_authenticate(self,hdr):
LOG.debug(hdr)
if hdr.startswith("Basic"):
authvalue = self._auth.get_auth(self._host,username=self._username)
if not authvalue:
raise ex.MissingCredentialsError(self._host)
return authvalue
elif hdr.startswith("Bearer"):
(realm,service,scope) = (None,None,None)
bkeys = hdr[len("Bearer"):].split(",")
for key in bkeys:
key = key.lstrip().rstrip()
arr = key.split("=",1)
if len(arr) != 2:
continue
key = arr[0]
val = arr[1]
if val[0] == '"' and val[-1] == '"':
val = val[1:-1]
elif val[0] == "'" and val[-1] == "'":
val = val[1:-1]
if key in ["Realm","realm"]:
realm = val
elif key in ["Service","service"]:
service = val
elif key in ["Scope","scope"]:
scope = val
if realm is None or service is None:
raise ex.BearerRedirectError(
"missing Bearer redirect field in"
" Www-authenticate header '%s'" % (hdr))
LOG.debug("Bearer redirect: %s,%s,%s" % (realm,service,scope))
authvalue = self._auth.get_auth(self._host,username=self._username)
if not authvalue:
raise ex.MissingCredentialsError(self._host)
headers = { 'authorization':"%s %s" % (authvalue.scheme(),authvalue.token()) }
params = { "service":service }
if scope is not None:
params["scope"] = scope
resp = requests.get(realm,headers=headers,params=params)
self._handle_http_error(resp)
return BearerToken.from_json_http_response(resp.json())
else:
raise ex.UnsupportedAuthorizationTypeError(hdr)
[docs] def get(self,suburl,headers={}):
url = self.apiurl + suburl
LOG.debug("%s: headers=%s" % (url,str(headers)))
resp = requests.get(url,verify=self._verify,headers=headers)
if resp.status_code == 401:
if "www-authenticate" in resp.headers \
and not "authorization" in headers:
authvalue = self.handle_www_authenticate(
resp.headers['www-authenticate'])
authstr = "%s %s" % (authvalue.scheme(),authvalue.token())
nh = dict(**headers)
if 'authorization' in nh:
del nh['authorization']
nh['authorization'] = authstr
kwargs = { 'headers':nh }
resp = self.get(suburl,**kwargs)
return resp
else:
raise ex.AuthorizationRequiredError() #resp.status_code,resp.json()["errors"])
return resp
#@ApplicableMethod()
[docs] def version(self):
if self._api_version is not None:
return self._api_version
ret = requests.get(self.baseurl + "v2/",verify=self._verify)
LOG.debug("version ret headers: %r" % (repr(ret.headers)))
LOG.debug("version ret body: %r" % (repr(ret.json())))
if ret.status_code in [200,401] \
and ret.headers['Docker-Distribution-Api-Version'] == "registry/2.0":
self._api_version = 2
return self._api_version
ret = requests.get(self.baseurl + "v1/")
if ret.status_code in [200,401]:
self._api_version = 1
return self._api_version
raise ex.DockerRegistryUnsupportedVersionError(None)
#@ApplicableMethod()
[docs] def authversion(self):
ret = self.get("")
LOG.debug("version ret headers: %r" % (repr(ret.headers)))
LOG.debug("version ret body: %r" % (repr(ret.json())))
if ret.status_code in [200,401] \
and ret.headers['Docker-Distribution-Api-Version'] == "registry/2.0":
self._api_version = 2
return self._api_version
ret = requests.get(self.baseurl + "v1/")
if ret.status_code in [200,401]:
self._api_version = 1
return self._api_version
raise ex.DockerRegistryUnsupportedVersionError("?")
pass