11import logging
2- from requests import get , put , delete
2+ from requests import codes , get , head , post , put , delete
33from requests .exceptions import HTTPError
44import json
55from .AuthorizationService import AuthorizationService
6+ from .digest import docker_digest
67from .manifest import sign as sign_manifest
78
9+ try :
10+ import urllib .parse as urlparse
11+ from urllib .parse import urlencode
12+ except ImportError :
13+ from urllib import urlencode
14+ import urlparse
15+
816# urllib3 throws some ssl warnings with older versions of python
917# they're probably ok for the registry client to ignore
1018import warnings
@@ -139,6 +147,14 @@ def __init__(self, content, type, digest):
139147 self ._type = type
140148 self ._digest = digest
141149
150+ @classmethod
151+ def from_file (cls , fpath = None , fobj = None ):
152+ digest = docker_digest (fpath , fobj )
153+ if fobj is None :
154+ fobj = open (fpath , 'rb' )
155+ return cls (json .loads (fobj .read ().decode ()),
156+ 'application/json' , digest )
157+
142158
143159BASE_CONTENT_TYPE = 'application/vnd.docker.distribution.manifest'
144160
@@ -147,6 +163,7 @@ class BaseClientV2(CommonBaseClient):
147163 LIST_TAGS = '/v2/{name}/tags/list'
148164 MANIFEST = '/v2/{name}/manifests/{reference}'
149165 BLOB = '/v2/{name}/blobs/{digest}'
166+ BLOB_UPLOAD = '/v2/{name}/blobs/uploads/'
150167 schema_1_signed = BASE_CONTENT_TYPE + '.v1+prettyjws'
151168 schema_1 = BASE_CONTENT_TYPE + '.v1+json'
152169 schema_2 = BASE_CONTENT_TYPE + '.v2+json'
@@ -208,6 +225,31 @@ def put_manifest(self, name, reference, manifest):
208225 name = name , reference = reference ,
209226 )
210227
228+ def put_blob (self , name , fpath = None , fobj = None ):
229+ self .auth .desired_scope = 'repository:%s:*' % name
230+ digest = docker_digest (fpath , fobj )
231+ try :
232+ self ._http_call (self .BLOB , head ,
233+ name = name , digest = digest )
234+ return digest
235+ except HTTPError as exc :
236+ if exc .response .status_code != codes .not_found :
237+ raise
238+ if fobj is None :
239+ fobj = open (fpath , 'rb' )
240+ resp = self ._http_response (self .BLOB_UPLOAD , post ,
241+ name = name )
242+ parts = list (urlparse .urlparse (resp .headers ['Location' ]))
243+ query = urlparse .parse_qs (parts [4 ])
244+ query .update ({'digest' : digest })
245+ parts [0 ] = '' # scheme
246+ parts [1 ] = '' # netloc
247+ parts [4 ] = urlencode (query , True )
248+ self ._http_call (urlparse .urlunparse (parts ),
249+ put , bindata = fobj ,
250+ name = name , digest = digest ,
251+ content_type = 'application/octet-stream' )
252+
211253 def delete_manifest (self , name , digest ):
212254 self .auth .desired_scope = 'repository:%s:*' % name
213255 return self ._http_call (self .MANIFEST , delete ,
@@ -227,7 +269,7 @@ def _cache_manifest_digest(self, name, reference, response=None):
227269 self ._manifest_digests [(name , reference )] = untrusted_digest
228270
229271 def _http_response (self , url , method , data = None , content_type = None ,
230- schema = None , ** kwargs ):
272+ bindata = None , schema = None , ** kwargs ):
231273 """url -> full target url
232274 method -> method from requests
233275 data -> request body
@@ -258,6 +300,8 @@ def _http_response(self, url, method, data=None, content_type=None,
258300
259301 if data and not content_type :
260302 data = json .dumps (data )
303+ if bindata :
304+ data = bindata
261305
262306 path = url .format (** kwargs )
263307 logger .debug ("%s %s" , method .__name__ .upper (), path )
0 commit comments