github.com/spg/deis@v1.7.3/controller/registry/private.py (about)

     1  import cStringIO
     2  import hashlib
     3  import json
     4  import requests
     5  import tarfile
     6  import urlparse
     7  import uuid
     8  
     9  from django.conf import settings
    10  from docker.utils import utils
    11  
    12  from api.utils import encode
    13  
    14  
    15  def publish_release(source, config, target):
    16      """
    17      Publish a new release as a Docker image
    18  
    19      Given a source image and dictionary of last-mile configuration,
    20      create a target Docker image on the registry.
    21  
    22      For example::
    23  
    24          publish_release('registry.local:5000/gabrtv/myapp:v22',
    25                          {'ENVVAR': 'values'},
    26                          'registry.local:5000/gabrtv/myapp:v23')
    27  
    28      results in a new Docker image at 'registry.local:5000/gabrtv/myapp:v23' which
    29      contains the new configuration as ENV entries.
    30      """
    31      try:
    32          repo, tag = utils.parse_repository_tag(source)
    33          src_image = repo
    34          src_tag = tag if tag is not None else 'latest'
    35  
    36          nameparts = repo.rsplit('/', 1)
    37          if len(nameparts) == 2:
    38              if '/' in nameparts[0]:
    39                  # strip the hostname and just use the app name
    40                  src_image = '{}/{}'.format(nameparts[0].rsplit('/', 1)[1],
    41                                             nameparts[1])
    42              elif '.' in nameparts[0]:
    43                  # we got a name like registry.local:5000/registry
    44                  src_image = nameparts[1]
    45  
    46          target_image = target.rsplit(':', 1)[0]
    47          target_tag = target.rsplit(':', 1)[1]
    48          image_id = _get_tag(src_image, src_tag)
    49      except RuntimeError:
    50          if src_tag == 'latest':
    51              # no image exists yet, so let's build one!
    52              _put_first_image(src_image)
    53              image_id = _get_tag(src_image, src_tag)
    54          else:
    55              raise
    56      image = _get_image(image_id)
    57      # construct the new image
    58      image['parent'] = image['id']
    59      image['id'] = _new_id()
    60      config['DEIS_APP'] = target_image
    61      config['DEIS_RELEASE'] = target_tag
    62      image['config']['Env'] = _construct_env(image['config']['Env'], config)
    63      # update and tag the new image
    64      _commit(target_image, image, _empty_tar_archive(), target_tag)
    65  
    66  
    67  # registry access
    68  
    69  
    70  def _commit(repository_path, image, layer, tag):
    71      _put_image(image)
    72      cookies = _put_layer(image['id'], layer)
    73      _put_checksum(image, layer, cookies)
    74      _put_tag(image['id'], repository_path, tag)
    75  
    76  
    77  def _put_first_image(repository_path):
    78      image = {
    79          'id': _new_id(),
    80          'parent': '',
    81          'config': {
    82              'Env': []
    83          }
    84      }
    85      # tag as v0 in the registry
    86      _commit(repository_path, image, _empty_tar_archive(), 'v0')
    87  
    88  
    89  def _api_call(endpoint, data=None, headers={}, cookies=None, request_type='GET'):
    90      base_headers = {'user-agent': 'docker/1.0.0'}
    91      r = None
    92      if len(headers) > 0:
    93          for header, value in headers.viewitems():
    94              base_headers[header] = value
    95      if request_type == 'GET':
    96          r = requests.get(endpoint, headers=base_headers)
    97      elif request_type == 'PUT':
    98          r = requests.put(endpoint, data=data, headers=base_headers, cookies=cookies)
    99      else:
   100          raise AttributeError("request type not supported: {}".format(request_type))
   101      return r
   102  
   103  
   104  def _get_tag(repository, tag):
   105      path = "/v1/repositories/{repository}/tags/{tag}".format(**locals())
   106      url = urlparse.urljoin(settings.REGISTRY_URL, path)
   107      r = _api_call(url)
   108      if not r.status_code == 200:
   109          raise RuntimeError("GET Image Error ({}: {})".format(r.status_code, r.text))
   110      return r.json()
   111  
   112  
   113  def _get_image(image_id):
   114      path = "/v1/images/{image_id}/json".format(**locals())
   115      url = urlparse.urljoin(settings.REGISTRY_URL, path)
   116      r = _api_call(url)
   117      if not r.status_code == 200:
   118          raise RuntimeError("GET Image Error ({}: {})".format(r.status_code, r.text))
   119      return r.json()
   120  
   121  
   122  def _put_image(image):
   123      path = "/v1/images/{id}/json".format(**image)
   124      url = urlparse.urljoin(settings.REGISTRY_URL, path)
   125      r = _api_call(url, data=json.dumps(image), request_type='PUT')
   126      if not r.status_code == 200:
   127          raise RuntimeError("PUT Image Error ({}: {})".format(r.status_code, r.text))
   128      return r.json()
   129  
   130  
   131  def _put_layer(image_id, layer_fileobj):
   132      path = "/v1/images/{image_id}/layer".format(**locals())
   133      url = urlparse.urljoin(settings.REGISTRY_URL, path)
   134      r = _api_call(url, data=layer_fileobj.read(), request_type='PUT')
   135      if not r.status_code == 200:
   136          raise RuntimeError("PUT Layer Error ({}: {})".format(r.status_code, r.text))
   137      return r.cookies
   138  
   139  
   140  def _put_checksum(image, layer, cookies):
   141      path = "/v1/images/{id}/checksum".format(**image)
   142      url = urlparse.urljoin(settings.REGISTRY_URL, path)
   143      h = hashlib.sha256(json.dumps(image) + '\n')
   144      h.update(layer.getvalue())
   145      layer_checksum = "sha256:{0}".format(h.hexdigest())
   146      headers = {'X-Docker-Checksum-Payload': layer_checksum}
   147      r = _api_call(url, headers=headers, cookies=cookies, request_type='PUT')
   148      if not r.status_code == 200:
   149          raise RuntimeError("PUT Checksum Error ({}: {})".format(r.status_code, r.text))
   150  
   151  
   152  def _put_tag(image_id, repository_path, tag):
   153      path = "/v1/repositories/{repository_path}/tags/{tag}".format(**locals())
   154      url = urlparse.urljoin(settings.REGISTRY_URL, path)
   155      r = _api_call(url, data=json.dumps(image_id), request_type='PUT')
   156      if not r.status_code == 200:
   157          raise RuntimeError("PUT Tag Error ({}: {})".format(r.status_code, r.text))
   158  
   159  
   160  # utility functions
   161  
   162  def _construct_env(env, config):
   163      "Update current environment with latest config"
   164      new_env = []
   165      # see if we need to update existing ENV vars
   166      for e in env:
   167          k, v = e.split('=', 1)
   168          if k in config:
   169              # update values defined by config
   170              v = config.pop(k)
   171          new_env.append("{}={}".format(encode(k), encode(v)))
   172      # add other config ENV items
   173      for k, v in config.viewitems():
   174          new_env.append("{}={}".format(encode(k), encode(v)))
   175      return new_env
   176  
   177  
   178  def _new_id():
   179      "Return 64-char UUID for use as Image ID"
   180      return ''.join(uuid.uuid4().hex * 2)
   181  
   182  
   183  def _empty_tar_archive():
   184      "Return an empty tar archive (in memory)"
   185      data = cStringIO.StringIO()
   186      tar = tarfile.open(mode="w", fileobj=data)
   187      tar.close()
   188      data.seek(0)
   189      return data