Source code for dockerreg.client

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