Skip to content

Commit 6cd2744

Browse files
committed
fix(provider): load auth configs before making requests to container registry
- Add authentication config loading before making requests to container registry by using decorators. Now all user callable public methods have the ability to load authentication configs if it's needed. Signed-off-by: diverger <diverger@live.cn>
1 parent b63ede0 commit 6cd2744

File tree

5 files changed

+125
-12
lines changed

5 files changed

+125
-12
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ and **Merged pull requests**. Critical items to know are:
1414
The versions coincide with releases on pip. Only major versions will be released as tags on Github.
1515

1616
## [0.0.x](https://github.com/oras-project/oras-py/tree/main) (0.0.x)
17+
- Add authentication config loading before making requests to container registry (0.2.34)
1718
- fix 'get_manifest()' method with adding 'load_configs()' calling (0.2.33)
1819
- fix 'Provider' method signature to allow custom CA-Bundles (0.2.32)
1920
- initialize headers variable in do_request (0.2.31)

oras/auth/base.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import oras.auth.utils as auth_utils
1111
import oras.container
1212
import oras.decorator as decorator
13+
import oras.utils
1314
from oras.logger import logger
1415
from oras.types import container_type
1516

@@ -106,6 +107,24 @@ def load_configs(self, container: container_type, configs: Optional[list] = None
106107
if self._load_auth(registry):
107108
return
108109

110+
def ensure_auth_for_container(self, container: container_type):
111+
"""
112+
Ensure authentication is loaded for a specific container's registry.
113+
This assumes auths have already been loaded via load_configs or __init__.
114+
115+
:param container: the parsed container URI with components
116+
:type container: oras.container.Container
117+
"""
118+
# At this point, container should already be a Container object
119+
# since the decorators handle conversion
120+
if not isinstance(container, oras.container.Container):
121+
raise ValueError("Container must be a Container object when ensure_auth_for_container is called")
122+
123+
# Try to load auth for this container's registry
124+
for registry in oras.utils.iter_localhosts(container.registry): # type: ignore
125+
if self._load_auth(registry):
126+
return
127+
109128
def set_token_auth(self, token: str):
110129
"""
111130
Set token authentication.

oras/decorator.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,75 @@ def wrapper(cls, *args, **kwargs):
2828
return wrapper
2929

3030

31+
def ensure_auth(func):
32+
"""
33+
Ensure authentication is loaded for the container's registry.
34+
This decorator should be applied after @ensure_container.
35+
"""
36+
37+
@wraps(func)
38+
def wrapper(cls, *args, **kwargs):
39+
# Get the container from the first argument (after ensure_container processing)
40+
container = None
41+
if "container" in kwargs:
42+
container = kwargs["container"]
43+
elif args:
44+
container = args[0]
45+
46+
if container and hasattr(cls, "auth"):
47+
# Load auth for this specific container's registry without reloading configs
48+
cls.auth.ensure_auth_for_container(container)
49+
50+
return func(cls, *args, **kwargs)
51+
52+
return wrapper
53+
54+
55+
def ensure_container_second_arg(func):
56+
"""
57+
Ensure the second argument is a container, and not a string.
58+
This is for PUT/SET-style methods where the pattern is:
59+
method(self, data, container, ...)
60+
"""
61+
62+
@wraps(func)
63+
def wrapper(cls, *args, **kwargs):
64+
if "container" in kwargs:
65+
kwargs["container"] = cls.get_container(kwargs["container"])
66+
elif len(args) >= 2:
67+
# Convert the second argument (index 1) to a container
68+
container = cls.get_container(args[1])
69+
args = (args[0], container, *args[2:])
70+
return func(cls, *args, **kwargs)
71+
72+
return wrapper
73+
74+
75+
def ensure_auth_second_arg(func):
76+
"""
77+
Ensure authentication is loaded for the container's registry.
78+
This decorator should be applied after @ensure_container_second_arg.
79+
For methods where container is the second argument.
80+
"""
81+
82+
@wraps(func)
83+
def wrapper(cls, *args, **kwargs):
84+
# Get the container from the second argument (after ensure_container_second_arg processing)
85+
container = None
86+
if "container" in kwargs:
87+
container = kwargs["container"]
88+
elif len(args) >= 2:
89+
container = args[1]
90+
91+
if container and hasattr(cls, "auth"):
92+
# Load auth for this specific container's registry without reloading configs
93+
cls.auth.ensure_auth_for_container(container)
94+
95+
return func(cls, *args, **kwargs)
96+
97+
return wrapper
98+
99+
31100
def retry(attempts=5, timeout=2):
32101
"""
33102
A simple retry decorator

oras/provider.py

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import requests
1717

1818
import oras.auth
19+
import oras.auth.utils
1920
import oras.container
2021
import oras.decorator as decorator
2122
import oras.defaults
@@ -84,6 +85,10 @@ def __init__(
8485
auth_backend, self.session, insecure, tls_verify=tls_verify
8586
)
8687

88+
# Load all authentication configs once during initialization
89+
# This avoids re-reading the docker config file for each operation
90+
self.auth._auths = oras.auth.utils.load_configs()
91+
8792
def __repr__(self) -> str:
8893
return str(self)
8994

@@ -249,6 +254,8 @@ def _parse_manifest_ref(self, ref: str) -> Tuple[str, str]:
249254
path_content.content = oras.defaults.unknown_config_media_type
250255
return path_content.path, path_content.content
251256

257+
@decorator.ensure_container_second_arg
258+
@decorator.ensure_auth_second_arg
252259
def upload_blob(
253260
self,
254261
blob: str,
@@ -276,7 +283,6 @@ def upload_blob(
276283
:type chunk_size: int
277284
"""
278285
blob = os.path.abspath(blob)
279-
container = self.get_container(container)
280286

281287
if self.blob_exists(layer, container):
282288
logger.debug(f'layer already exists: {layer["digest"]}')
@@ -307,6 +313,7 @@ def upload_blob(
307313
return response
308314

309315
@decorator.ensure_container
316+
@decorator.ensure_auth
310317
def delete_tag(self, container: container_type, tag: str) -> bool:
311318
"""
312319
Delete a tag for a container.
@@ -341,6 +348,7 @@ def delete_tag(self, container: container_type, tag: str) -> bool:
341348
return True
342349

343350
@decorator.ensure_container
351+
@decorator.ensure_auth
344352
def get_tags(self, container: container_type, N=None) -> List[str]:
345353
"""
346354
Retrieve tags for a package.
@@ -405,6 +413,7 @@ def _do_paginated_request(
405413
url = urllib.parse.urljoin(base_url, link)
406414

407415
@decorator.ensure_container
416+
@decorator.ensure_auth
408417
def get_blob(
409418
self,
410419
container: container_type,
@@ -441,6 +450,7 @@ def get_container(self, name: container_type) -> oras.container.Container:
441450

442451
# Functions to be deprecated in favor of exposed ones
443452
@decorator.ensure_container
453+
@decorator.ensure_auth
444454
def _download_blob(
445455
self, container: container_type, digest: str, outfile: str
446456
) -> str:
@@ -486,6 +496,7 @@ def _upload_blob(
486496
return self.upload_blob(blob, container, layer, do_chunked)
487497

488498
@decorator.ensure_container
499+
@decorator.ensure_auth
489500
def download_blob(
490501
self, container: container_type, digest: str, outfile: str
491502
) -> str:
@@ -516,6 +527,8 @@ def download_blob(
516527
raise e
517528
return outfile
518529

530+
@decorator.ensure_container_second_arg
531+
@decorator.ensure_auth_second_arg
519532
def put_upload(
520533
self,
521534
blob: str,
@@ -561,6 +574,8 @@ def put_upload(
561574
)
562575
return response
563576

577+
@decorator.ensure_container_second_arg
578+
@decorator.ensure_auth_second_arg
564579
def blob_exists(self, layer: dict, container: oras.container.Container) -> bool:
565580
"""
566581
Check if a layer already exists in the registry.
@@ -600,6 +615,8 @@ def _get_location(
600615
session_url = f"{prefix}{session_url}"
601616
return session_url
602617

618+
@decorator.ensure_container_second_arg
619+
@decorator.ensure_auth_second_arg
603620
def chunked_upload(
604621
self,
605622
blob: str,
@@ -688,6 +705,8 @@ def _parse_response_errors(self, response: requests.Response):
688705
except Exception:
689706
pass
690707

708+
@decorator.ensure_container_second_arg
709+
@decorator.ensure_auth_second_arg
691710
def upload_manifest(
692711
self,
693712
manifest: dict,
@@ -751,9 +770,13 @@ def push(
751770
"""
752771
container = self.get_container(target)
753772
files = files or []
754-
self.auth.load_configs(
755-
container, configs=[config_path] if config_path else None
756-
)
773+
774+
# If a custom config path is provided, load those configs
775+
if config_path:
776+
self.auth.load_configs(container, configs=[config_path])
777+
else:
778+
# Use the already loaded auths with ensure_auth pattern
779+
self.auth.ensure_auth_for_container(container)
757780

758781
# Prepare a new manifest
759782
manifest = oras.oci.NewManifest()
@@ -891,9 +914,13 @@ def pull(
891914
:type target: str
892915
"""
893916
container = self.get_container(target)
894-
self.auth.load_configs(
895-
container, configs=[config_path] if config_path else None
896-
)
917+
918+
# If a custom config path is provided, load those configs
919+
if config_path:
920+
self.auth.load_configs(container, configs=[config_path])
921+
else:
922+
# Use the already loaded auths with ensure_auth pattern
923+
self.auth.ensure_auth_for_container(container)
897924
manifest = self.get_manifest(container, allowed_media_type)
898925
outdir = outdir or oras.utils.get_tmpdir()
899926
overwrite = overwrite
@@ -933,6 +960,7 @@ def pull(
933960
return files
934961

935962
@decorator.ensure_container
963+
@decorator.ensure_auth
936964
def get_manifest(
937965
self,
938966
container: container_type,
@@ -946,10 +974,6 @@ def get_manifest(
946974
:param allowed_media_type: one or more allowed media types
947975
:type allowed_media_type: str
948976
"""
949-
# Load authentication configs for the container's registry
950-
# This ensures credentials are available for authenticated registries
951-
self.auth.load_configs(container)
952-
953977
if not allowed_media_type:
954978
allowed_media_type = [oras.defaults.default_manifest_media_type]
955979
headers = {"Accept": ";".join(allowed_media_type)}

oras/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
__copyright__ = "Copyright The ORAS Authors."
33
__license__ = "Apache-2.0"
44

5-
__version__ = "0.2.33"
5+
__version__ = "0.2.34"
66
AUTHOR = "Vanessa Sochat"
77
EMAIL = "vsoch@users.noreply.github.com"
88
NAME = "oras"

0 commit comments

Comments
 (0)