github.com/ahjdzx/deis@v1.1.1/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, 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 # FIXME: update API calls for docker 0.10.0+ 91 base_headers = {'user-agent': 'docker/0.9.0'} 92 r = None 93 if len(headers) > 0: 94 for header, value in headers.iteritems(): 95 base_headers[header] = value 96 if request_type == 'GET': 97 r = requests.get(endpoint, headers=base_headers) 98 elif request_type == 'PUT': 99 r = requests.put(endpoint, data=data, headers=base_headers, cookies=cookies) 100 else: 101 raise AttributeError("request type not supported: {}".format(request_type)) 102 return r 103 104 105 def _get_tag(repository, tag): 106 path = "/v1/repositories/{repository}/tags/{tag}".format(**locals()) 107 url = urlparse.urljoin(settings.REGISTRY_URL, path) 108 r = _api_call(url) 109 if not r.status_code == 200: 110 raise RuntimeError("GET Image Error ({}: {})".format(r.status_code, r.text)) 111 return r.json() 112 113 114 def _get_image(image_id): 115 path = "/v1/images/{image_id}/json".format(**locals()) 116 url = urlparse.urljoin(settings.REGISTRY_URL, path) 117 r = _api_call(url) 118 if not r.status_code == 200: 119 raise RuntimeError("GET Image Error ({}: {})".format(r.status_code, r.text)) 120 return r.json() 121 122 123 def _put_image(image): 124 path = "/v1/images/{id}/json".format(**image) 125 url = urlparse.urljoin(settings.REGISTRY_URL, path) 126 r = _api_call(url, data=json.dumps(image), request_type='PUT') 127 if not r.status_code == 200: 128 raise RuntimeError("PUT Image Error ({}: {})".format(r.status_code, r.text)) 129 return r.json() 130 131 132 def _put_layer(image_id, layer_fileobj): 133 path = "/v1/images/{image_id}/layer".format(**locals()) 134 url = urlparse.urljoin(settings.REGISTRY_URL, path) 135 r = _api_call(url, data=layer_fileobj.read(), request_type='PUT') 136 if not r.status_code == 200: 137 raise RuntimeError("PUT Layer Error ({}: {})".format(r.status_code, r.text)) 138 return r.cookies 139 140 141 def _put_checksum(image, cookies): 142 path = "/v1/images/{id}/checksum".format(**image) 143 url = urlparse.urljoin(settings.REGISTRY_URL, path) 144 tarsum = TarSum(json.dumps(image)).compute() 145 headers = {'X-Docker-Checksum': tarsum} 146 r = _api_call(url, headers=headers, cookies=cookies, request_type='PUT') 147 if not r.status_code == 200: 148 raise RuntimeError("PUT Checksum Error ({}: {})".format(r.status_code, r.text)) 149 print r.json() 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 print r.json() 159 160 161 # utility functions 162 163 def _construct_env(env, config): 164 "Update current environment with latest config" 165 new_env = [] 166 # see if we need to update existing ENV vars 167 for e in env: 168 k, v = e.split('=', 1) 169 if k in config: 170 # update values defined by config 171 v = config.pop(k) 172 new_env.append("{}={}".format(encode(k), encode(v))) 173 # add other config ENV items 174 for k, v in config.items(): 175 new_env.append("{}={}".format(encode(k), encode(v))) 176 return new_env 177 178 179 def _new_id(): 180 "Return 64-char UUID for use as Image ID" 181 return ''.join(uuid.uuid4().hex * 2) 182 183 184 def _empty_tar_archive(): 185 "Return an empty tar archive (in memory)" 186 data = cStringIO.StringIO() 187 tar = tarfile.open(mode="w", fileobj=data) 188 tar.close() 189 data.seek(0) 190 return data 191 192 193 # 194 # Below adapted from https://github.com/dotcloud/docker-registry/blob/master/lib/checksums.py 195 # 196 197 def sha256_file(fp, data=None): 198 h = hashlib.sha256(data or '') 199 if not fp: 200 return h.hexdigest() 201 while True: 202 buf = fp.read(4096) 203 if not buf: 204 break 205 h.update(buf) 206 return h.hexdigest() 207 208 209 def sha256_string(s): 210 return hashlib.sha256(s).hexdigest() 211 212 213 class TarSum(object): 214 215 def __init__(self, json_data): 216 self.json_data = json_data 217 self.hashes = [] 218 self.header_fields = ('name', 'mode', 'uid', 'gid', 'size', 'mtime', 219 'type', 'linkname', 'uname', 'gname', 'devmajor', 220 'devminor') 221 222 def append(self, member, tarobj): 223 header = '' 224 for field in self.header_fields: 225 value = getattr(member, field) 226 if field == 'type': 227 field = 'typeflag' 228 elif field == 'name': 229 if member.isdir() and not value.endswith('/'): 230 value += '/' 231 header += '{0}{1}'.format(field, value) 232 h = None 233 try: 234 if member.size > 0: 235 f = tarobj.extractfile(member) 236 h = sha256_file(f, header) 237 else: 238 h = sha256_string(header) 239 except KeyError: 240 h = sha256_string(header) 241 self.hashes.append(h) 242 243 def compute(self): 244 self.hashes.sort() 245 data = self.json_data + ''.join(self.hashes) 246 tarsum = 'tarsum+sha256:{0}'.format(sha256_string(data)) 247 return tarsum