2017-01-03 12:34:51 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# -*- coding:utf-8 -*-
|
|
|
|
# bug-report: feilengcui008@gmail.com
|
|
|
|
|
|
|
|
""" api for docker registry """
|
|
|
|
|
|
|
|
import urllib2
|
|
|
|
import urllib
|
|
|
|
import json
|
|
|
|
import base64
|
|
|
|
|
|
|
|
|
|
|
|
class RegistryException(Exception):
|
|
|
|
""" registry api related exception """
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class RegistryApi(object):
|
|
|
|
""" interact with docker registry and harbor """
|
|
|
|
def __init__(self, username, password, registry_endpoint):
|
|
|
|
self.username = username
|
|
|
|
self.password = password
|
2017-01-04 08:11:16 +01:00
|
|
|
self.basic_token = base64.encodestring("%s:%s" % (str(username), str(password)))[0:-1]
|
2017-01-03 12:34:51 +01:00
|
|
|
self.registry_endpoint = registry_endpoint.rstrip('/')
|
|
|
|
auth = self.pingRegistry("%s/v2/_catalog" % (self.registry_endpoint,))
|
|
|
|
if auth is None:
|
|
|
|
raise RegistryException("get token realm and service failed")
|
|
|
|
self.token_endpoint = auth[0]
|
|
|
|
self.service = auth[1]
|
|
|
|
|
|
|
|
def pingRegistry(self, registry_endpoint):
|
|
|
|
""" ping v2 registry and get realm and service """
|
|
|
|
headers = dict()
|
|
|
|
try:
|
|
|
|
res = urllib2.urlopen(registry_endpoint)
|
|
|
|
except urllib2.HTTPError as e:
|
|
|
|
headers = e.hdrs.dict
|
|
|
|
try:
|
|
|
|
(realm, service, _) = headers['www-authenticate'].split(',')
|
|
|
|
return (realm[14:-1:], service[9:-1])
|
|
|
|
except Exception as e:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def getBearerTokenForScope(self, scope):
|
|
|
|
""" get bearer token from harbor """
|
|
|
|
payload = urllib.urlencode({'service': self.service, 'scope': scope})
|
|
|
|
url = "%s?%s" % (self.token_endpoint, payload)
|
|
|
|
req = urllib2.Request(url)
|
|
|
|
req.add_header('Authorization', 'Basic %s' % (self.basic_token,))
|
|
|
|
try:
|
|
|
|
response = urllib2.urlopen(req)
|
|
|
|
return json.loads(response.read())["token"]
|
|
|
|
except Exception as e:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def getRepositoryList(self, n=None):
|
|
|
|
""" get repository list """
|
|
|
|
scope = "registry:catalog:*"
|
|
|
|
bear_token = self.getBearerTokenForScope(scope)
|
|
|
|
if bear_token is None:
|
|
|
|
return None
|
|
|
|
url = "%s/v2/_catalog" % (self.registry_endpoint,)
|
|
|
|
if n is not None:
|
|
|
|
url = "%s?n=%s" % (url, str(n))
|
|
|
|
req = urllib2.Request(url)
|
|
|
|
req.add_header('Authorization', r'Bearer %s' % (bear_token,))
|
|
|
|
try:
|
|
|
|
response = urllib2.urlopen(req)
|
|
|
|
return json.loads(response.read())
|
|
|
|
except Exception as e:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def getTagList(self, repository):
|
|
|
|
""" get tag list for repository """
|
|
|
|
scope = "repository:%s:pull" % (repository,)
|
|
|
|
bear_token = self.getBearerTokenForScope(scope)
|
|
|
|
if bear_token is None:
|
|
|
|
return None
|
|
|
|
url = "%s/v2/%s/tags/list" % (self.registry_endpoint, repository)
|
|
|
|
req = urllib2.Request(url)
|
|
|
|
req.add_header('Authorization', r'Bearer %s' % (bear_token,))
|
|
|
|
try:
|
|
|
|
response = urllib2.urlopen(req)
|
|
|
|
return json.loads(response.read())
|
|
|
|
except Exception as e:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def getManifest(self, repository, reference="latest", v1=False):
|
|
|
|
""" get manifest for tag or digest """
|
|
|
|
scope = "repository:%s:pull" % (repository,)
|
|
|
|
bear_token = self.getBearerTokenForScope(scope)
|
|
|
|
if bear_token is None:
|
|
|
|
return None
|
|
|
|
url = "%s/v2/%s/manifests/%s" % (self.registry_endpoint, repository, reference)
|
|
|
|
req = urllib2.Request(url)
|
|
|
|
req.get_method = lambda: 'GET'
|
|
|
|
req.add_header('Authorization', r'Bearer %s' % (bear_token,))
|
|
|
|
req.add_header('Accept', 'application/vnd.docker.distribution.manifest.v2+json')
|
|
|
|
if v1:
|
|
|
|
req.add_header('Accept', 'application/vnd.docker.distribution.manifest.v1+json')
|
|
|
|
try:
|
|
|
|
response = urllib2.urlopen(req)
|
|
|
|
return json.loads(response.read())
|
|
|
|
except Exception as e:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def existManifest(self, repository, reference, v1=False):
|
|
|
|
""" check to see it manifest exist """
|
|
|
|
scope = "repository:%s:pull" % (repository,)
|
|
|
|
bear_token = self.getBearerTokenForScope(scope)
|
|
|
|
if bear_token is None:
|
|
|
|
raise RegistryException("manifestExist failed due to token error")
|
|
|
|
url = "%s/v2/%s/manifests/%s" % (self.registry_endpoint, repository, reference)
|
|
|
|
req = urllib2.Request(url)
|
|
|
|
req.get_method = lambda: 'HEAD'
|
|
|
|
req.add_header('Authorization', r'Bearer %s' % (bear_token,))
|
|
|
|
req.add_header('Accept', 'application/vnd.docker.distribution.manifest.v2+json')
|
|
|
|
if v1:
|
|
|
|
req.add_header('Accept', 'application/vnd.docker.distribution.manifest.v1+json')
|
|
|
|
try:
|
|
|
|
response = urllib2.urlopen(req)
|
|
|
|
return (True, response.headers.dict["docker-content-digest"])
|
|
|
|
except Exception as e:
|
|
|
|
return (False, None)
|
|
|
|
|
|
|
|
def deleteManifest(self, repository, reference):
|
|
|
|
""" delete manifest by tag """
|
|
|
|
(is_exist, digest) = self.existManifest(repository, reference)
|
|
|
|
if not is_exist:
|
|
|
|
raise RegistryException("manifest not exist")
|
|
|
|
scope = "repository:%s:pull,push" % (repository,)
|
|
|
|
bear_token = self.getBearerTokenForScope(scope)
|
|
|
|
if bear_token is None:
|
|
|
|
raise RegistryException("delete manifest failed due to token error")
|
|
|
|
url = "%s/v2/%s/manifests/%s" % (self.registry_endpoint, repository, digest)
|
|
|
|
req = urllib2.Request(url)
|
|
|
|
req.get_method = lambda: 'DELETE'
|
|
|
|
req.add_header('Authorization', r'Bearer %s' % (bear_token,))
|
|
|
|
try:
|
|
|
|
urllib2.urlopen(req)
|
|
|
|
except Exception as e:
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2017-01-04 08:11:16 +01:00
|
|
|
def getManifestWithConf(self, repository, reference="latest"):
|
2017-01-03 12:34:51 +01:00
|
|
|
""" get manifest for tag or digest """
|
2017-01-04 08:11:16 +01:00
|
|
|
manifest = self.getManifest(repository, reference)
|
2017-01-03 12:34:51 +01:00
|
|
|
if manifest is None:
|
|
|
|
raise RegistryException("manifest for %s %s not exist" % (repository, reference))
|
|
|
|
config_digest = manifest["config"]["digest"]
|
|
|
|
scope = "repository:%s:pull" % (repository,)
|
|
|
|
bear_token = self.getBearerTokenForScope(scope)
|
|
|
|
if bear_token is None:
|
|
|
|
return None
|
|
|
|
url = "%s/v2/%s/blobs/%s" % (self.registry_endpoint, repository, config_digest)
|
|
|
|
req = urllib2.Request(url)
|
|
|
|
req.get_method = lambda: 'GET'
|
|
|
|
req.add_header('Authorization', r'Bearer %s' % (bear_token,))
|
|
|
|
req.add_header('Accept', 'application/vnd.docker.distribution.manifest.v2+json')
|
|
|
|
try:
|
|
|
|
response = urllib2.urlopen(req)
|
|
|
|
manifest["configContent"] = json.loads(response.read())
|
|
|
|
return manifest
|
|
|
|
except Exception as e:
|
|
|
|
return None
|