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