Skip to content

Commit 513463b

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 513463b

File tree

5 files changed

+128
-30
lines changed

5 files changed

+128
-30
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: 22 additions & 1 deletion
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

@@ -87,7 +88,7 @@ def _load_auth(self, hostname: str) -> bool:
8788
return True
8889
return False
8990

90-
@decorator.ensure_container
91+
@decorator.ensure_container()
9192
def load_configs(self, container: container_type, configs: Optional[list] = None):
9293
"""
9394
Load configs to discover credentials for a specific container.
@@ -106,6 +107,26 @@ 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(
122+
"Container must be a Container object when ensure_auth_for_container is called"
123+
)
124+
125+
# Try to load auth for this container's registry
126+
for registry in oras.utils.iter_localhosts(container.registry): # type: ignore
127+
if self._load_auth(registry):
128+
return
129+
109130
def set_token_auth(self, token: str):
110131
"""
111132
Set token authentication.

oras/decorator.py

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,73 @@
1111
from oras.logger import logger
1212

1313

14-
def ensure_container(func):
14+
def ensure_container(container_arg_index=0):
1515
"""
16-
Ensure the first argument is a container, and not a string.
16+
Ensure the specified argument is a container, and not a string.
17+
18+
:param container_arg_index: The index of the container argument (0 for first arg, 1 for second arg)
19+
:type container_arg_index: int
20+
21+
Usage examples:
22+
@ensure_container() # Container is first argument (default)
23+
@ensure_container(0) # Container is first argument (explicit)
24+
@ensure_container(1) # Container is second argument
25+
"""
26+
27+
def decorator(func):
28+
@wraps(func)
29+
def wrapper(cls, *args, **kwargs):
30+
if "container" in kwargs:
31+
kwargs["container"] = cls.get_container(kwargs["container"])
32+
elif len(args) > container_arg_index:
33+
# Convert the specified argument to a container
34+
container = cls.get_container(args[container_arg_index])
35+
# Rebuild args tuple with the converted container
36+
args = (
37+
args[:container_arg_index]
38+
+ (container,)
39+
+ args[container_arg_index + 1 :]
40+
)
41+
return func(cls, *args, **kwargs)
42+
43+
return wrapper
44+
45+
return decorator
46+
47+
48+
def ensure_auth(container_arg_index=0):
1749
"""
50+
Ensure authentication is loaded for the container's registry.
51+
This decorator should be applied after @ensure_container.
52+
53+
:param container_arg_index: The index of the container argument (0 for first arg, 1 for second arg)
54+
:type container_arg_index: int
55+
56+
Usage examples:
57+
@ensure_auth() # Container is first argument (default)
58+
@ensure_auth(0) # Container is first argument (explicit)
59+
@ensure_auth(1) # Container is second argument
60+
"""
61+
62+
def decorator(func):
63+
@wraps(func)
64+
def wrapper(cls, *args, **kwargs):
65+
# Get the container from the specified argument position
66+
container = None
67+
if "container" in kwargs:
68+
container = kwargs["container"]
69+
elif len(args) > container_arg_index:
70+
container = args[container_arg_index]
71+
72+
if container and hasattr(cls, "auth"):
73+
# Load auth for this specific container's registry without reloading configs
74+
cls.auth.ensure_auth_for_container(container)
1875

19-
@wraps(func)
20-
def wrapper(cls, *args, **kwargs):
21-
if "container" in kwargs:
22-
kwargs["container"] = cls.get_container(kwargs["container"])
23-
elif args:
24-
container = cls.get_container(args[0])
25-
args = (container, *args[1:])
26-
return func(cls, *args, **kwargs)
76+
return func(cls, *args, **kwargs)
2777

28-
return wrapper
78+
return wrapper
79+
80+
return decorator
2981

3082

3183
def retry(attempts=5, timeout=2):

oras/provider.py

Lines changed: 41 additions & 17 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(1)
258+
@decorator.ensure_auth(1)
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"]}')
@@ -306,7 +312,8 @@ def upload_blob(
306312
response.status_code = 200
307313
return response
308314

309-
@decorator.ensure_container
315+
@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.
@@ -340,7 +347,8 @@ def delete_tag(self, container: container_type, tag: str) -> bool:
340347
raise RuntimeError("Delete was not successful: {response.json()}")
341348
return True
342349

343-
@decorator.ensure_container
350+
@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.
@@ -404,7 +412,8 @@ def _do_paginated_request(
404412
# use link + base url to continue with next page
405413
url = urllib.parse.urljoin(base_url, link)
406414

407-
@decorator.ensure_container
415+
@decorator.ensure_container()
416+
@decorator.ensure_auth()
408417
def get_blob(
409418
self,
410419
container: container_type,
@@ -440,7 +449,8 @@ def get_container(self, name: container_type) -> oras.container.Container:
440449
return oras.container.Container(name, registry=self.hostname)
441450

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

488-
@decorator.ensure_container
498+
@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(1)
531+
@decorator.ensure_auth(1)
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(1)
578+
@decorator.ensure_auth(1)
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(1)
619+
@decorator.ensure_auth(1)
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(1)
709+
@decorator.ensure_auth(1)
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
@@ -932,7 +959,8 @@ def pull(
932959
files.append(outfile)
933960
return files
934961

935-
@decorator.ensure_container
962+
@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)