5454from proot_distro .helpers .docker .refs import parse_image_ref
5555from proot_distro .helpers .docker .transport import (
5656 auth_note ,
57- auth_opener ,
5857 get_auth_token ,
58+ opener ,
5959 push_denied_msg ,
60- registry_base_url ,
6160 _ua ,
6261)
6362
@@ -76,18 +75,17 @@ def _resolve_upload_url(base: str, location: str) -> str:
7675
7776
7877def _blob_exists (
79- repo : str , digest : str , token : str , registry : str = "" ,
78+ repo : str , digest : str , token : str , base : str , insecure : bool = False ,
8079) -> bool :
8180 """Return True iff blob *digest* already exists on the registry."""
82- base = registry_base_url (registry )
8381 url = f"{ base } /v2/{ repo } /blobs/{ digest } "
8482 headers = {** _ua ()}
8583 if token :
8684 headers ["Authorization" ] = f"Bearer { token } "
8785 req = urllib .request .Request (url , method = "HEAD" , headers = headers )
8886
8987 def _attempt ():
90- with auth_opener ( ).open (req ) as resp :
88+ with opener ( insecure ).open (req ) as resp :
9189 return 200 <= resp .status < 300
9290
9391 try :
@@ -136,7 +134,7 @@ def read(self, size=-1):
136134
137135def _upload_blob_bytes (
138136 repo : str , digest : str , data : bytes , token : str ,
139- registry : str = "" ,
137+ base : str , insecure : bool = False ,
140138) -> None :
141139 """Upload a small in-memory blob (POST + monolithic PUT).
142140
@@ -145,7 +143,6 @@ def _upload_blob_bytes(
145143 the *content* of the blob and is set separately when the manifest
146144 itself is uploaded.
147145 """
148- base = registry_base_url (registry )
149146 headers = {** _ua ()}
150147 if token :
151148 headers ["Authorization" ] = f"Bearer { token } "
@@ -160,7 +157,7 @@ def _attempt():
160157 method = "POST" ,
161158 headers = {** headers , "Content-Length" : "0" },
162159 )
163- with auth_opener ( ).open (post_req ) as resp :
160+ with opener ( insecure ).open (post_req ) as resp :
164161 location = resp .headers .get ("Location" , "" )
165162 put_url = _resolve_upload_url (base , location )
166163 sep = "&" if "?" in put_url else "?"
@@ -175,7 +172,7 @@ def _attempt():
175172 "Content-Length" : str (len (data )),
176173 },
177174 )
178- with auth_opener ( ).open (put_req ) as resp :
175+ with opener ( insecure ).open (put_req ) as resp :
179176 if not 200 <= resp .status < 300 :
180177 raise RuntimeError (
181178 f"Blob upload failed for { digest } : HTTP { resp .status } "
@@ -186,10 +183,9 @@ def _attempt():
186183
187184def _upload_blob_file (
188185 repo : str , digest : str , file_path : str , token : str ,
189- registry : str = "" , label : str = "" ,
186+ base : str , insecure : bool = False , label : str = "" ,
190187) -> None :
191188 """Upload a blob from *file_path* (streamed, POST + monolithic PUT)."""
192- base = registry_base_url (registry )
193189 headers = {** _ua ()}
194190 if token :
195191 headers ["Authorization" ] = f"Bearer { token } "
@@ -204,7 +200,7 @@ def _attempt():
204200 method = "POST" ,
205201 headers = {** headers , "Content-Length" : "0" },
206202 )
207- with auth_opener ( ).open (post_req ) as resp :
203+ with opener ( insecure ).open (post_req ) as resp :
208204 location = resp .headers .get ("Location" , "" )
209205 put_url = _resolve_upload_url (base , location )
210206 sep = "&" if "?" in put_url else "?"
@@ -222,7 +218,7 @@ def _attempt():
222218 "Content-Length" : str (size ),
223219 },
224220 )
225- with auth_opener ( ).open (put_req ) as resp :
221+ with opener ( insecure ).open (put_req ) as resp :
226222 if not 200 <= resp .status < 300 :
227223 raise RuntimeError (
228224 f"Blob upload failed for { digest } : HTTP { resp .status } "
@@ -235,11 +231,10 @@ def _attempt():
235231
236232def _put_manifest (
237233 repo : str , reference : str , body : bytes , media_type : str ,
238- token : str , registry : str = "" ,
234+ token : str , base : str , insecure : bool = False ,
239235) -> str :
240236 """PUT a manifest at <reference> (tag or digest). Returns the registry
241237 digest from the Docker-Content-Digest header, if provided."""
242- base = registry_base_url (registry )
243238 url = f"{ base } /v2/{ repo } /manifests/{ reference } "
244239 headers = {
245240 ** _ua (),
@@ -251,7 +246,7 @@ def _put_manifest(
251246 req = urllib .request .Request (url , data = body , method = "PUT" , headers = headers )
252247
253248 def _attempt ():
254- with auth_opener ( ).open (req ) as resp :
249+ with opener ( insecure ).open (req ) as resp :
255250 if not 200 <= resp .status < 300 :
256251 raise RuntimeError (
257252 f"Manifest upload failed: HTTP { resp .status } "
@@ -270,13 +265,21 @@ def _strip_private_keys(d: dict) -> dict:
270265 return {k : v for k , v in d .items () if not k .startswith ("_" )}
271266
272267
273- def push_image (image_ref : str , arch : str ) -> dict :
268+ def push_image (image_ref : str , arch : str , insecure : bool = False ) -> dict :
274269 """Push a built image (resolved from the manifest cache) to its registry.
275270
276271 The image must have been produced by `proot-distro build` under
277272 exactly this *image_ref* and *arch* — `build` stores the manifest
278273 in MANIFEST_CACHE_DIR and the layer + config blobs in
279274 LAYER_CACHE_DIR using the same digests we transmit here.
275+
276+ Registry traffic uses verified HTTPS unless *insecure* is set. With
277+ *insecure* a custom registry is reached over HTTPS with certificate
278+ verification disabled, falling back to plain HTTP when the registry only
279+ speaks HTTP (Docker Hub stays verified-HTTPS regardless). When enforcing
280+ HTTPS, an untrusted certificate or an HTTP-only registry surfaces a
281+ RuntimeError pointing the user at ``--allow-insecure`` — the same handling
282+ as the install command.
280283 """
281284 manifest , repo , image_config = load_manifest_cache (image_ref , arch )
282285 if manifest is None :
@@ -321,8 +324,13 @@ def push_image(image_ref: str, arch: str) -> dict:
321324
322325 log_info (f"Authenticating with registry{ auth_note ()} ..." )
323326 try :
324- # push always uses verified HTTPS; the resolved base is ignored.
325- token , _base = get_auth_token (repo , registry , actions = "pull,push" )
327+ # Resolve the scheme/base for this registry. Under --allow-insecure a
328+ # bad certificate is tolerated and an HTTP-only registry falls back to
329+ # http://; otherwise a cert/plaintext failure raises a RuntimeError
330+ # pointing at --allow-insecure (handled inside get_auth_token).
331+ token , base = get_auth_token (
332+ repo , registry , actions = "pull,push" , insecure = insecure ,
333+ )
326334 except urllib .error .HTTPError as exc :
327335 if exc .code in (401 , 403 ):
328336 raise RuntimeError (push_denied_msg (image_ref , exc .code )) from exc
@@ -338,15 +346,15 @@ def push_image(image_ref: str, arch: str) -> dict:
338346 size = os .path .getsize (path )
339347
340348 try :
341- if _blob_exists (repo , digest , token , registry ):
349+ if _blob_exists (repo , digest , token , base , insecure ):
342350 log_info (f"{ short_id } : Layer { i + 1 } /{ n_layers } already "
343351 f"exists on registry, skipping upload." )
344352 continue
345353
346354 log_info (f"{ short_id } : Uploading layer { i + 1 } /{ n_layers } "
347355 f"({ fmt_size (size )} )..." )
348356 _upload_blob_file (
349- repo , digest , path , token , registry , label = short_id ,
357+ repo , digest , path , token , base , insecure , label = short_id ,
350358 )
351359 bytes_uploaded += size
352360 except urllib .error .HTTPError as exc :
@@ -358,14 +366,14 @@ def push_image(image_ref: str, arch: str) -> dict:
358366
359367 cfg_short = expected_cfg_digest .split (":" )[- 1 ][:12 ]
360368 try :
361- if _blob_exists (repo , expected_cfg_digest , token , registry ):
369+ if _blob_exists (repo , expected_cfg_digest , token , base , insecure ):
362370 log_info (f"{ cfg_short } : Image config already exists on "
363371 f"registry, skipping upload." )
364372 else :
365373 log_info (f"{ cfg_short } : Uploading image config "
366374 f"({ fmt_size (len (config_bytes ))} )..." )
367375 _upload_blob_bytes (
368- repo , expected_cfg_digest , config_bytes , token , registry ,
376+ repo , expected_cfg_digest , config_bytes , token , base , insecure ,
369377 )
370378 bytes_uploaded += len (config_bytes )
371379 except urllib .error .HTTPError as exc :
@@ -379,7 +387,7 @@ def push_image(image_ref: str, arch: str) -> dict:
379387 f"({ fmt_size (len (manifest_bytes ))} )..." )
380388 try :
381389 registry_digest = _put_manifest (
382- repo , tag , manifest_bytes , manifest_media , token , registry ,
390+ repo , tag , manifest_bytes , manifest_media , token , base , insecure ,
383391 )
384392 except urllib .error .HTTPError as exc :
385393 if exc .code in (401 , 403 ):
0 commit comments