diff --git a/sdk/containerregistry/azure-containerregistry/CHANGELOG.md b/sdk/containerregistry/azure-containerregistry/CHANGELOG.md index 172c7f4a5750..98827808aa51 100644 --- a/sdk/containerregistry/azure-containerregistry/CHANGELOG.md +++ b/sdk/containerregistry/azure-containerregistry/CHANGELOG.md @@ -6,6 +6,7 @@ * Rename `TagProperties` to `ArtifactTagProperties` * Rename `ContentPermissions` to `ContentProperties` * Rename `content_permissions` attributes on `TagProperties`, `RepositoryProperties`, and `RegistryArtifactProperties` to `writeable_properties`. +* Adds anonymous access capabilities to client by passing in `None` to credential. ## 1.0.0b1 (2021-04-06) * First release of the Azure Container Registry library for Python diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_anonymous_exchange_client.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_anonymous_exchange_client.py new file mode 100644 index 000000000000..6ad07c0e8f9b --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_anonymous_exchange_client.py @@ -0,0 +1,75 @@ +# coding=utf-8 +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from typing import TYPE_CHECKING, Dict, Any + +from ._exchange_client import ExchangeClientAuthenticationPolicy +from ._generated import ContainerRegistry +from ._generated.models._container_registry_enums import TokenGrantType +from ._helpers import _parse_challenge +from ._user_agent import USER_AGENT + +if TYPE_CHECKING: + from azure.core.credentials import TokenCredential + + +class AnonymousACRExchangeClient(object): + """Class for handling oauth authentication requests + + :param endpoint: Azure Container Registry endpoint + :type endpoint: str + :param credential: Credential which provides tokens to authenticate requests + :type credential: :class:`~azure.core.credentials.TokenCredential` + """ + + def __init__(self, endpoint, **kwargs): # pylint: disable=missing-client-constructor-parameter-credential + # type: (str, Dict[str, Any]) -> None + if not endpoint.startswith("https://") and not endpoint.startswith("http://"): + endpoint = "https://" + endpoint + self._endpoint = endpoint + self.credential_scope = "https://management.core.windows.net/.default" + self._client = ContainerRegistry( + credential=None, + url=endpoint, + sdk_moniker=USER_AGENT, + authentication_policy=ExchangeClientAuthenticationPolicy(), + credential_scopes=kwargs.pop("credential_scopes", self.credential_scope), + **kwargs + ) + + def get_acr_access_token(self, challenge, **kwargs): + # type: (str, Dict[str, Any]) -> str + parsed_challenge = _parse_challenge(challenge) + parsed_challenge["grant_type"] = TokenGrantType.PASSWORD + return self.exchange_refresh_token_for_access_token( + None, + service=parsed_challenge["service"], + scope=parsed_challenge["scope"], + grant_type=TokenGrantType.PASSWORD, + **kwargs + ) + + def exchange_refresh_token_for_access_token( + self, refresh_token=None, service=None, scope=None, grant_type=TokenGrantType.PASSWORD, **kwargs + ): + # type: (str, str, str, str, Dict[str, Any]) -> str + access_token = self._client.authentication.exchange_acr_refresh_token_for_acr_access_token( + service=service, scope=scope, refresh_token=refresh_token, grant_type=grant_type, **kwargs + ) + return access_token.access_token + + def __enter__(self): + self._client.__enter__() + return self + + def __exit__(self, *args): + self._client.__exit__(*args) + + def close(self): + # type: () -> None + """Close sockets opened by the client. + Calling this method is unnecessary when using the client as a context manager. + """ + self._client.close() diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_authentication_policy.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_authentication_policy.py index 327f610e7885..352697e5cd53 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_authentication_policy.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_authentication_policy.py @@ -8,6 +8,7 @@ from azure.core.pipeline.policies import HTTPPolicy +from ._anonymous_exchange_client import AnonymousACRExchangeClient from ._exchange_client import ACRExchangeClient from ._helpers import _enforce_https @@ -24,7 +25,10 @@ def __init__(self, credential, endpoint): # type: (TokenCredential, str) -> None super(ContainerRegistryChallengePolicy, self).__init__() self._credential = credential - self._exchange_client = ACRExchangeClient(endpoint, self._credential) + if self._credential is None: + self._exchange_client = AnonymousACRExchangeClient(endpoint) + else: + self._exchange_client = ACRExchangeClient(endpoint, self._credential) def on_request(self, request): # type: (PipelineRequest) -> None diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_base_client.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_base_client.py index 9f6aa5554ea0..4cfeff6132e7 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_base_client.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_base_client.py @@ -4,7 +4,7 @@ # Licensed under the MIT License. # ------------------------------------ from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Dict, Any from azure.core.pipeline.transport import HttpTransport diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_exchange_client.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_exchange_client.py index 54ae6aff97c3..c24f25a14043 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_exchange_client.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_exchange_client.py @@ -3,7 +3,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -import re import time from typing import TYPE_CHECKING @@ -40,9 +39,6 @@ class ACRExchangeClient(object): :type credential: :class:`~azure.core.credentials.TokenCredential` """ - BEARER = "Bearer" - AUTHENTICATION_CHALLENGE_PARAMS_PATTERN = re.compile('(?:(\\w+)="([^""]*)")+') - def __init__(self, endpoint, credential, **kwargs): # type: (str, TokenCredential, Dict[str, Any]) -> None if not endpoint.startswith("https://") and not endpoint.startswith("http://"): diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/aio/operations/_authentication_operations.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/aio/operations/_authentication_operations.py index 12b0140cf763..6f4f87d64e7d 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/aio/operations/_authentication_operations.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/aio/operations/_authentication_operations.py @@ -107,7 +107,7 @@ async def exchange_acr_refresh_token_for_acr_access_token( service: str, scope: str, refresh_token: str, - grant_type: Union[str, "_models.Enum2"] = "refresh_token", + grant_type: Union[str, "_models.TokenGrantType"] = "refresh_token", **kwargs ) -> "_models.AcrAccessToken": """Exchange ACR Refresh token for an ACR Access Token. @@ -121,7 +121,7 @@ async def exchange_acr_refresh_token_for_acr_access_token( :param refresh_token: Must be a valid ACR refresh token. :type refresh_token: str :param grant_type: Grant type is expected to be refresh_token. - :type grant_type: str or ~container_registry.models.Enum2 + :type grant_type: str or ~container_registry.models.TokenGrantType :keyword callable cls: A custom type or function that will be passed the direct response :return: AcrAccessToken, or the result of cls(response) :rtype: ~container_registry.models.AcrAccessToken diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/__init__.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/__init__.py index 32c235c4c0d9..5ad081a95029 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/__init__.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/__init__.py @@ -84,9 +84,9 @@ from ._container_registry_enums import ( ArtifactArchitecture, ArtifactOperatingSystem, - Enum2, ManifestOrderBy, TagOrderBy, + TokenGrantType, ) __all__ = [ @@ -129,7 +129,7 @@ 'V2Manifest', 'ArtifactArchitecture', 'ArtifactOperatingSystem', - 'Enum2', 'ManifestOrderBy', 'TagOrderBy', + 'TokenGrantType', ] diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_container_registry_enums.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_container_registry_enums.py index a762e31e61d4..8ca5cfea37c1 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_container_registry_enums.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_container_registry_enums.py @@ -57,13 +57,6 @@ class ArtifactOperatingSystem(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum SOLARIS = "solaris" WINDOWS = "windows" -class Enum2(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): - """Grant type is expected to be refresh_token - """ - - REFRESH_TOKEN = "refresh_token" - PASSWORD = "password" - class ManifestOrderBy(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): """Sort options for ordering manifests in a collection. """ @@ -83,3 +76,10 @@ class TagOrderBy(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): LAST_UPDATED_ON_DESCENDING = "timedesc" #: Order tags by LastUpdatedOn field, from least recently updated to most recently updated. LAST_UPDATED_ON_ASCENDING = "timeasc" + +class TokenGrantType(with_metaclass(_CaseInsensitiveEnumMeta, str, Enum)): + """Grant type is expected to be refresh_token + """ + + REFRESH_TOKEN = "refresh_token" + PASSWORD = "password" diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_models.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_models.py index f80170b90c38..6bec2ba19f41 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_models.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_models.py @@ -953,7 +953,7 @@ class PathsV3R3RxOauth2TokenPostRequestbodyContentApplicationXWwwFormUrlencodedS :param grant_type: Required. Grant type is expected to be refresh_token. Possible values include: "refresh_token", "password". - :type grant_type: str or ~container_registry.models.Enum2 + :type grant_type: str or ~container_registry.models.TokenGrantType :param service: Required. Indicates the name of your Azure container registry. :type service: str :param scope: Required. Which is expected to be a valid scope, and can be specified more than diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_models_py3.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_models_py3.py index 6fcca5dbf362..58f62e7b5d59 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_models_py3.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/models/_models_py3.py @@ -1060,7 +1060,7 @@ class PathsV3R3RxOauth2TokenPostRequestbodyContentApplicationXWwwFormUrlencodedS :param grant_type: Required. Grant type is expected to be refresh_token. Possible values include: "refresh_token", "password". - :type grant_type: str or ~container_registry.models.Enum2 + :type grant_type: str or ~container_registry.models.TokenGrantType :param service: Required. Indicates the name of your Azure container registry. :type service: str :param scope: Required. Which is expected to be a valid scope, and can be specified more than @@ -1088,7 +1088,7 @@ class PathsV3R3RxOauth2TokenPostRequestbodyContentApplicationXWwwFormUrlencodedS def __init__( self, *, - grant_type: Union[str, "Enum2"] = "refresh_token", + grant_type: Union[str, "TokenGrantType"] = "refresh_token", service: str, scope: str, acr_refresh_token: str, diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/operations/_authentication_operations.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/operations/_authentication_operations.py index b48fd9f932e3..901dd98205ee 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/operations/_authentication_operations.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/_generated/operations/_authentication_operations.py @@ -112,7 +112,7 @@ def exchange_acr_refresh_token_for_acr_access_token( service, # type: str scope, # type: str refresh_token, # type: str - grant_type="refresh_token", # type: Union[str, "_models.Enum2"] + grant_type="refresh_token", # type: Union[str, "_models.TokenGrantType"] **kwargs # type: Any ): # type: (...) -> "_models.AcrAccessToken" @@ -127,7 +127,7 @@ def exchange_acr_refresh_token_for_acr_access_token( :param refresh_token: Must be a valid ACR refresh token. :type refresh_token: str :param grant_type: Grant type is expected to be refresh_token. - :type grant_type: str or ~container_registry.models.Enum2 + :type grant_type: str or ~container_registry.models.TokenGrantType :keyword callable cls: A custom type or function that will be passed the direct response :return: AcrAccessToken, or the result of cls(response) :rtype: ~container_registry.models.AcrAccessToken diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_anonymous_exchange_client.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_anonymous_exchange_client.py new file mode 100644 index 000000000000..0d9ba0e8463f --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_anonymous_exchange_client.py @@ -0,0 +1,74 @@ +# coding=utf-8 +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +from typing import TYPE_CHECKING, Dict, List, Any + +from ._async_exchange_client import ExchangeClientAuthenticationPolicy +from .._generated.aio import ContainerRegistry +from .._generated.models._container_registry_enums import TokenGrantType +from .._helpers import _parse_challenge +from .._user_agent import USER_AGENT + +if TYPE_CHECKING: + from azure.core.credentials_async import AsyncTokenCredential + + +class AnonymousACRExchangeClient(object): + """Class for handling oauth authentication requests + + :param endpoint: Azure Container Registry endpoint + :type endpoint: str + """ + + def __init__(self, endpoint: str, **kwargs: Dict[str, Any]) -> None: # pylint: disable=missing-client-constructor-parameter-credential + if not endpoint.startswith("https://") and not endpoint.startswith("http://"): + endpoint = "https://" + endpoint + self._endpoint = endpoint + self._credential_scope = "https://management.core.windows.net/.default" + self._client = ContainerRegistry( + credential=None, + url=endpoint, + sdk_moniker=USER_AGENT, + authentication_policy=ExchangeClientAuthenticationPolicy(), + credential_scopes=kwargs.pop("credential_scopes", self._credential_scope), + **kwargs + ) + + async def get_acr_access_token(self, challenge: str, **kwargs: Dict[str, Any]) -> str: + parsed_challenge = _parse_challenge(challenge) + parsed_challenge["grant_type"] = TokenGrantType.PASSWORD + return await self.exchange_refresh_token_for_access_token( + None, + service=parsed_challenge["service"], + scope=parsed_challenge["scope"], + grant_type=TokenGrantType.PASSWORD, + **kwargs + ) + + async def exchange_refresh_token_for_access_token( + self, + refresh_token: str = None, + service: str = None, + scope: str = None, + grant_type: str = TokenGrantType.PASSWORD, + **kwargs: Any + ) -> str: + access_token = await self._client.authentication.exchange_acr_refresh_token_for_acr_access_token( + service=service, scope=scope, refresh_token=refresh_token, grant_type=grant_type, **kwargs + ) + return access_token.access_token + + async def __aenter__(self): + self._client.__aenter__() + return self + + async def __aexit__(self, *args): + self._client.__aexit__(*args) + + async def close(self) -> None: + """Close sockets opened by the client. + Calling this method is unnecessary when using the client as a context manager. + """ + await self._client.close() diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_authentication_policy.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_authentication_policy.py index cb54e3a895b0..70766b12e8b4 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_authentication_policy.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_authentication_policy.py @@ -6,6 +6,7 @@ from azure.core.pipeline.policies import AsyncHTTPPolicy +from ._async_anonymous_exchange_client import AnonymousACRExchangeClient from ._async_exchange_client import ACRExchangeClient from .._helpers import _enforce_https @@ -21,7 +22,10 @@ class ContainerRegistryChallengePolicy(AsyncHTTPPolicy): def __init__(self, credential: "AsyncTokenCredential", endpoint: str) -> None: super().__init__() self._credential = credential - self._exchange_client = ACRExchangeClient(endpoint, self._credential) + if self._credential is None: + self._exchange_client = AnonymousACRExchangeClient(endpoint) + else: + self._exchange_client = ACRExchangeClient(endpoint, self._credential) async def on_request(self, request): # type: (PipelineRequest) -> None diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_exchange_client.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_exchange_client.py index 4aad909ec0b1..af0a44fa2ac8 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_exchange_client.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_exchange_client.py @@ -3,7 +3,6 @@ # Copyright (c) Microsoft Corporation. # Licensed under the MIT License. # ------------------------------------ -import re import time from typing import TYPE_CHECKING, Dict, List, Any @@ -37,9 +36,6 @@ class ACRExchangeClient(object): :type credential: :class:`azure.core.credentials.TokenCredential` """ - BEARER = "Bearer" - AUTHENTICATION_CHALLENGE_PARAMS_PATTERN = re.compile('(?:(\\w+)="([^""]*)")+') - def __init__(self, endpoint: str, credential: "AsyncTokencredential", **kwargs: Dict[str, Any]) -> None: if not endpoint.startswith("https://") and not endpoint.startswith("http://"): endpoint = "https://" + endpoint diff --git a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_registry_artifact.py b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_registry_artifact.py index 77c000a0ae67..0d7a2638440f 100644 --- a/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_registry_artifact.py +++ b/sdk/containerregistry/azure-containerregistry/azure/containerregistry/aio/_async_registry_artifact.py @@ -159,7 +159,8 @@ async def get_tag_properties(self, tag: str, **kwargs: Dict[str, Any]) -> Artifa tag_properties = await client.get_tag_properties(tag.name) """ return ArtifactTagProperties._from_generated( # pylint: disable=protected-access - await self._client.container_registry.get_tag_properties(self.repository, tag, **kwargs) + await self._client.container_registry.get_tag_properties(self.repository, tag, **kwargs), + repository=self.repository, ) @distributed_trace diff --git a/sdk/containerregistry/azure-containerregistry/swagger/README.md b/sdk/containerregistry/azure-containerregistry/swagger/README.md index 7b3fb681094a..ec6bf3b12cb5 100644 --- a/sdk/containerregistry/azure-containerregistry/swagger/README.md +++ b/sdk/containerregistry/azure-containerregistry/swagger/README.md @@ -2,23 +2,27 @@ ### Settings ``` yaml -input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/5f46ed2f7b443e16d6ee8286f62e686063d08c05/specification/containerregistry/data-plane/Azure.ContainerRegistry/preview/2019-08-15-preview/containerregistry.json +input-file: https://raw.githubusercontent.com/Azure/azure-rest-api-specs/73800e82e3afb8f2966c2caa1dd8dc451a7c03e5/specification/containerregistry/data-plane/Azure.ContainerRegistry/preview/2019-08-15-preview/containerregistry.json output-folder: "../azure/containerregistry/_generated" no-namespace-folders: true python: true clear-output-folder: true ``` - + +### Correct Security to be separately defined + +``` yaml +directive: + from: swagger-document + where: $ + transform: > + $.security = [ + { + "registry_oauth2": [] + }, + { + "registry_auth": [] + } + ] +``` diff --git a/sdk/containerregistry/azure-containerregistry/tests/asynctestcase.py b/sdk/containerregistry/azure-containerregistry/tests/asynctestcase.py index 1e6091024de6..c3c83dc983b6 100644 --- a/sdk/containerregistry/azure-containerregistry/tests/asynctestcase.py +++ b/sdk/containerregistry/azure-containerregistry/tests/asynctestcase.py @@ -54,3 +54,6 @@ def create_container_repository(self, endpoint, name, **kwargs): credential=self.get_credential(), **kwargs, ) + + def create_anon_client(self, endpoint, **kwargs): + return ContainerRegistryClient(endpoint=endpoint, credential=None, **kwargs) diff --git a/sdk/containerregistry/azure-containerregistry/tests/preparer.py b/sdk/containerregistry/azure-containerregistry/tests/preparer.py index 3e1252124671..fa485da7a4dd 100644 --- a/sdk/containerregistry/azure-containerregistry/tests/preparer.py +++ b/sdk/containerregistry/azure-containerregistry/tests/preparer.py @@ -12,4 +12,5 @@ "containerregistry", containerregistry_endpoint="fake_url.azurecr.io", containerregistry_resource_group="fake_rg", + containerregistry_anonregistry_endpoint="fake_url.azurecr.io", ) diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_get_properties.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_get_properties.yaml new file mode 100644 index 000000000000..9c635e45fd5d --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_get_properties.yaml @@ -0,0 +1,130 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "library/hello-world", "Action": "metadata_read"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '222' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:47 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: grant_type=password&service=seankaneanon.azurecr.io&scope=repository%3Alibrary%2Fhello-world%3Ametadata_read + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '108' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:48 GMT + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world + response: + body: + string: '{"registry": "fake_url.azurecr.io", "imageName": "library/hello-world", + "createdTime": "2021-05-05T18:00:19.7101132Z", "lastUpdateTime": "2021-05-05T18:00:17.5345755Z", + "manifestCount": 10, "tagCount": 5, "changeableAttributes": {"deleteEnabled": + true, "writeEnabled": true, "readEnabled": true, "listEnabled": true, "teleportEnabled": + false}}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '326' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:48 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_manifests.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_manifests.yaml new file mode 100644 index 000000000000..1b9dc66f97f5 --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_manifests.yaml @@ -0,0 +1,176 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_manifests + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "library/hello-world", "Action": "metadata_read"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '222' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:50 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: grant_type=password&service=seankaneanon.azurecr.io&scope=repository%3Alibrary%2Fhello-world%3Ametadata_read + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '108' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:50 GMT + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_manifests + response: + body: + string: '{"registry": "fake_url.azurecr.io", "imageName": "library/hello-world", + "manifests": [{"digest": "sha256:1b26826f602946860c279fce658f31050cff2c596583af237d971f4629b57792", + "imageSize": 525, "createdTime": "2021-05-05T18:00:19.9237969Z", "lastUpdateTime": + "2021-05-05T18:00:19.9237969Z", "architecture": "amd64", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:50b8560ad574c779908da71f7ce370c0a2471c098d44d1c8f6b513c5a55eeeb1", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.0894003Z", "lastUpdateTime": + "2021-05-05T18:00:20.0894003Z", "architecture": "arm", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:88b2e00179bd6c4064612403c8d42a13de7ca809d61fee966ce9e129860a8a90", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.9702265Z", "lastUpdateTime": + "2021-05-05T18:00:20.9702265Z", "architecture": "mips64le", "os": "linux", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:963612c5503f3f1674f315c67089dee577d8cc6afc18565e0b4183ae355fb343", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.7515462Z", "lastUpdateTime": + "2021-05-05T18:00:20.7515462Z", "architecture": "arm64", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:bb7ab0fa94fdd78aca84b27a1bd46c4b811051f9b69905d81f5f267fc6546a9d", + "imageSize": 525, "createdTime": "2021-05-05T18:00:21.9872183Z", "lastUpdateTime": + "2021-05-05T18:00:21.9872183Z", "architecture": "ppc64le", "os": "linux", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:cb55d8f7347376e1ba38ca740904b43c9a52f66c7d2ae1ef1a0de1bc9f40df98", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.5796932Z", "lastUpdateTime": + "2021-05-05T18:00:20.5796932Z", "architecture": "386", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:e49abad529e5d9bd6787f3abeab94e09ba274fe34731349556a850b9aebbf7bf", + "imageSize": 525, "createdTime": "2021-05-05T18:00:19.8200236Z", "lastUpdateTime": + "2021-05-05T18:00:19.8200236Z", "architecture": "s390x", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:e5785cb0c62cebbed4965129bae371f0589cadd6d84798fb58c2c5f9e237efd9", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.0067665Z", "lastUpdateTime": + "2021-05-05T18:00:20.0067665Z", "architecture": "arm", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:ea0cfb27fd41ea0405d3095880c1efa45710f5bcdddb7d7d5a7317ad4825ae14", + "imageSize": 1125, "createdTime": "2021-05-05T18:00:28.6381496Z", "lastUpdateTime": + "2021-05-05T18:00:28.6381496Z", "architecture": "amd64", "os": "windows", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "imageSize": 5325, "createdTime": "2021-05-05T18:00:19.8821305Z", "lastUpdateTime": + "2021-05-05T18:00:19.8821305Z", "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "tags": ["latest", "v1", "v2", "v3", "v4"], "changeableAttributes": {"deleteEnabled": + true, "writeEnabled": true, "readEnabled": true, "listEnabled": true}}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:51 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_repository_names.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_repository_names.yaml new file mode 100644 index 000000000000..b40ba31ecae4 --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_repository_names.yaml @@ -0,0 +1,126 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '196' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:52 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: grant_type=password&service=seankaneanon.azurecr.io&scope=registry%3Acatalog%3A%2A + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '82' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:52 GMT + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"repositories": ["library/alpine", "library/busybox", "library/hello-world"]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '76' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:53 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_repository_names_by_page.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_repository_names_by_page.yaml new file mode 100644 index 000000000000..2a494f581dbf --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_repository_names_by_page.yaml @@ -0,0 +1,252 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog?n=2 + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '196' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:53 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: grant_type=password&service=seankaneanon.azurecr.io&scope=registry%3Acatalog%3A%2A + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '82' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:54 GMT + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog?n=2 + response: + body: + string: '{"repositories": ["library/alpine", "library/busybox"]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '54' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:54 GMT + docker-distribution-api-version: + - registry/2.0 + link: + - ; rel="next" + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog?last=library%2Fbusybox&n=2&orderby= + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '196' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:54 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: grant_type=password&service=seankaneanon.azurecr.io&scope=registry%3Acatalog%3A%2A + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '82' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:54 GMT + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog?last=library%2Fbusybox&n=2&orderby= + response: + body: + string: '{"repositories": ["library/hello-world"]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '41' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:54 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_tags.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_tags.yaml new file mode 100644 index 000000000000..e36dc3ebf49e --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_list_tags.yaml @@ -0,0 +1,146 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_tags + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "library/hello-world", "Action": "metadata_read"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '222' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:55 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: grant_type=password&service=seankaneanon.azurecr.io&scope=repository%3Alibrary%2Fhello-world%3Ametadata_read + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '108' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:56 GMT + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_tags + response: + body: + string: '{"registry": "fake_url.azurecr.io", "imageName": "library/hello-world", + "tags": [{"name": "latest", "digest": "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:20.8799521Z", "lastUpdateTime": "2021-05-05T18:00:20.8799521Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}, {"name": "v1", "digest": + "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:24.1188128Z", "lastUpdateTime": "2021-05-05T18:00:24.1188128Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}, {"name": "v2", "digest": + "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:21.3154368Z", "lastUpdateTime": "2021-05-05T18:00:21.3154368Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}, {"name": "v3", "digest": + "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:20.4944944Z", "lastUpdateTime": "2021-05-05T18:00:20.4944944Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}, {"name": "v4", "digest": + "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:20.3500586Z", "lastUpdateTime": "2021-05-05T18:00:20.3500586Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '1631' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:56 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_transport_closed_only_once.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_transport_closed_only_once.yaml new file mode 100644 index 000000000000..8aa8191da23a --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access.test_transport_closed_only_once.yaml @@ -0,0 +1,250 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '196' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:57 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: grant_type=password&service=seankaneanon.azurecr.io&scope=registry%3Acatalog%3A%2A + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '82' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:57 GMT + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"repositories": ["library/alpine", "library/busybox", "library/hello-world"]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '76' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:57 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '196' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:58 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + www-authenticate: + - Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: + - nosniff + status: + code: 401 + message: Unauthorized +- request: + body: grant_type=password&service=seankaneanon.azurecr.io&scope=registry%3Acatalog%3A%2A + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + Content-Length: + - '82' + Content-Type: + - application/x-www-form-urlencoded + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: + - keep-alive + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:58 GMT + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + transfer-encoding: + - chunked + status: + code: 200 + message: OK +- request: + body: null + headers: + Accept: + - application/json + Accept-Encoding: + - gzip, deflate + Connection: + - keep-alive + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"repositories": ["library/alpine", "library/busybox", "library/hello-world"]}' + headers: + access-control-expose-headers: + - Docker-Content-Digest + - WWW-Authenticate + - Link + - X-Ms-Correlation-Request-Id + connection: + - keep-alive + content-length: + - '76' + content-type: + - application/json; charset=utf-8 + date: + - Sun, 09 May 2021 18:55:58 GMT + docker-distribution-api-version: + - registry/2.0 + server: + - openresty + strict-transport-security: + - max-age=31536000; includeSubDomains + - max-age=31536000; includeSubDomains + x-content-type-options: + - nosniff + status: + code: 200 + message: OK +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_get_properties.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_get_properties.yaml new file mode 100644 index 000000000000..f79128390f7a --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_get_properties.yaml @@ -0,0 +1,87 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "library/hello-world", "Action": "metadata_read"}]}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '222' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:55:59 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: nosniff + status: + code: 401 + message: Unauthorized + url: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world +- request: + body: + grant_type: password + scope: repository:library/hello-world:metadata_read + service: fake_url.azurecr.io + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: keep-alive + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:55:59 GMT + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + transfer-encoding: chunked + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/oauth2/token +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world + response: + body: + string: '{"registry": "fake_url.azurecr.io", "imageName": "library/hello-world", + "createdTime": "2021-05-05T18:00:19.7101132Z", "lastUpdateTime": "2021-05-05T18:00:17.5345755Z", + "manifestCount": 10, "tagCount": 5, "changeableAttributes": {"deleteEnabled": + true, "writeEnabled": true, "readEnabled": true, "listEnabled": true, "teleportEnabled": + false}}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '326' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:55:59 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_manifests.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_manifests.yaml new file mode 100644 index 000000000000..8be62e7d3a89 --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_manifests.yaml @@ -0,0 +1,133 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_manifests + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "library/hello-world", "Action": "metadata_read"}]}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '222' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:00 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: nosniff + status: + code: 401 + message: Unauthorized + url: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_manifests +- request: + body: + grant_type: password + scope: repository:library/hello-world:metadata_read + service: fake_url.azurecr.io + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: keep-alive + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:00 GMT + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + transfer-encoding: chunked + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/oauth2/token +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_manifests + response: + body: + string: '{"registry": "fake_url.azurecr.io", "imageName": "library/hello-world", + "manifests": [{"digest": "sha256:1b26826f602946860c279fce658f31050cff2c596583af237d971f4629b57792", + "imageSize": 525, "createdTime": "2021-05-05T18:00:19.9237969Z", "lastUpdateTime": + "2021-05-05T18:00:19.9237969Z", "architecture": "amd64", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:50b8560ad574c779908da71f7ce370c0a2471c098d44d1c8f6b513c5a55eeeb1", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.0894003Z", "lastUpdateTime": + "2021-05-05T18:00:20.0894003Z", "architecture": "arm", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:88b2e00179bd6c4064612403c8d42a13de7ca809d61fee966ce9e129860a8a90", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.9702265Z", "lastUpdateTime": + "2021-05-05T18:00:20.9702265Z", "architecture": "mips64le", "os": "linux", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:963612c5503f3f1674f315c67089dee577d8cc6afc18565e0b4183ae355fb343", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.7515462Z", "lastUpdateTime": + "2021-05-05T18:00:20.7515462Z", "architecture": "arm64", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:bb7ab0fa94fdd78aca84b27a1bd46c4b811051f9b69905d81f5f267fc6546a9d", + "imageSize": 525, "createdTime": "2021-05-05T18:00:21.9872183Z", "lastUpdateTime": + "2021-05-05T18:00:21.9872183Z", "architecture": "ppc64le", "os": "linux", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:cb55d8f7347376e1ba38ca740904b43c9a52f66c7d2ae1ef1a0de1bc9f40df98", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.5796932Z", "lastUpdateTime": + "2021-05-05T18:00:20.5796932Z", "architecture": "386", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:e49abad529e5d9bd6787f3abeab94e09ba274fe34731349556a850b9aebbf7bf", + "imageSize": 525, "createdTime": "2021-05-05T18:00:19.8200236Z", "lastUpdateTime": + "2021-05-05T18:00:19.8200236Z", "architecture": "s390x", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:e5785cb0c62cebbed4965129bae371f0589cadd6d84798fb58c2c5f9e237efd9", + "imageSize": 525, "createdTime": "2021-05-05T18:00:20.0067665Z", "lastUpdateTime": + "2021-05-05T18:00:20.0067665Z", "architecture": "arm", "os": "linux", "mediaType": + "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:ea0cfb27fd41ea0405d3095880c1efa45710f5bcdddb7d7d5a7317ad4825ae14", + "imageSize": 1125, "createdTime": "2021-05-05T18:00:28.6381496Z", "lastUpdateTime": + "2021-05-05T18:00:28.6381496Z", "architecture": "amd64", "os": "windows", + "mediaType": "application/vnd.docker.distribution.manifest.v2+json", "changeableAttributes": + {"deleteEnabled": true, "writeEnabled": true, "readEnabled": true, "listEnabled": + true, "quarantineState": "Passed"}}, {"digest": "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "imageSize": 5325, "createdTime": "2021-05-05T18:00:19.8821305Z", "lastUpdateTime": + "2021-05-05T18:00:19.8821305Z", "mediaType": "application/vnd.docker.distribution.manifest.list.v2+json", + "tags": ["latest", "v1", "v2", "v3", "v4"], "changeableAttributes": {"deleteEnabled": + true, "writeEnabled": true, "readEnabled": true, "listEnabled": true}}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:01 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + transfer-encoding: chunked + x-content-type-options: nosniff + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_manifests +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_repository_names.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_repository_names.yaml new file mode 100644 index 000000000000..8fcd4be7be2c --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_repository_names.yaml @@ -0,0 +1,83 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '196' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:01 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: nosniff + status: + code: 401 + message: Unauthorized + url: https://fake_url.azurecr.io/acr/v1/_catalog +- request: + body: + grant_type: password + scope: registry:catalog:* + service: fake_url.azurecr.io + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: keep-alive + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:02 GMT + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + transfer-encoding: chunked + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/oauth2/token +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"repositories": ["library/alpine", "library/busybox", "library/hello-world"]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '76' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:02 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/acr/v1/_catalog +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_repository_names_by_page.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_repository_names_by_page.yaml new file mode 100644 index 000000000000..e7fa72e94305 --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_repository_names_by_page.yaml @@ -0,0 +1,165 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog?n=2 + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '196' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:02 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: nosniff + status: + code: 401 + message: Unauthorized + url: https://fake_url.azurecr.io/acr/v1/_catalog?n=2 +- request: + body: + grant_type: password + scope: registry:catalog:* + service: fake_url.azurecr.io + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: keep-alive + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:03 GMT + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + transfer-encoding: chunked + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/oauth2/token +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog?n=2 + response: + body: + string: '{"repositories": ["library/alpine", "library/busybox"]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '54' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:03 GMT + docker-distribution-api-version: registry/2.0 + link: ; rel="next" + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/acr/v1/_catalog?n=2 +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog?last=library/busybox&n=2&orderby= + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '196' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:04 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: nosniff + status: + code: 401 + message: Unauthorized + url: https://fake_url.azurecr.io/acr/v1/_catalog?last=library/busybox&n=2&orderby= +- request: + body: + grant_type: password + scope: registry:catalog:* + service: fake_url.azurecr.io + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: keep-alive + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:04 GMT + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + transfer-encoding: chunked + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/oauth2/token +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog?last=library/busybox&n=2&orderby= + response: + body: + string: '{"repositories": ["library/hello-world"]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '41' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:04 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/acr/v1/_catalog?last=library/busybox&n=2&orderby= +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_tags.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_tags.yaml new file mode 100644 index 000000000000..cf901497c6cf --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_list_tags.yaml @@ -0,0 +1,103 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_tags + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "repository", "Name": "library/hello-world", "Action": "metadata_read"}]}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '222' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:05 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: nosniff + status: + code: 401 + message: Unauthorized + url: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_tags +- request: + body: + grant_type: password + scope: repository:library/hello-world:metadata_read + service: fake_url.azurecr.io + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: keep-alive + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:06 GMT + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + transfer-encoding: chunked + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/oauth2/token +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_tags + response: + body: + string: '{"registry": "fake_url.azurecr.io", "imageName": "library/hello-world", + "tags": [{"name": "latest", "digest": "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:20.8799521Z", "lastUpdateTime": "2021-05-05T18:00:20.8799521Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}, {"name": "v1", "digest": + "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:24.1188128Z", "lastUpdateTime": "2021-05-05T18:00:24.1188128Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}, {"name": "v2", "digest": + "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:21.3154368Z", "lastUpdateTime": "2021-05-05T18:00:21.3154368Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}, {"name": "v3", "digest": + "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:20.4944944Z", "lastUpdateTime": "2021-05-05T18:00:20.4944944Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}, {"name": "v4", "digest": + "sha256:f2266cbfc127c960fd30e76b7c792dc23b588c0db76233517e1891a4e357d519", + "createdTime": "2021-05-05T18:00:20.3500586Z", "lastUpdateTime": "2021-05-05T18:00:20.3500586Z", + "signed": false, "changeableAttributes": {"deleteEnabled": true, "writeEnabled": + true, "readEnabled": true, "listEnabled": true}}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '1631' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:06 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/acr/v1/library%2Fhello-world/_tags +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_transport_closed_only_once.yaml b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_transport_closed_only_once.yaml new file mode 100644 index 000000000000..67a14fba0fab --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/recordings/test_anon_access_async.test_transport_closed_only_once.yaml @@ -0,0 +1,164 @@ +interactions: +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '196' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:07 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: nosniff + status: + code: 401 + message: Unauthorized + url: https://fake_url.azurecr.io/acr/v1/_catalog +- request: + body: + grant_type: password + scope: registry:catalog:* + service: fake_url.azurecr.io + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: keep-alive + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:08 GMT + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + transfer-encoding: chunked + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/oauth2/token +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"repositories": ["library/alpine", "library/busybox", "library/hello-world"]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '76' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:08 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/acr/v1/_catalog +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"errors": [{"code": "UNAUTHORIZED", "message": "authentication required, + visit https://aka.ms/acr/authorization for more information.", "detail": [{"Type": + "registry", "Name": "catalog", "Action": "*"}]}]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '196' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:08 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + www-authenticate: Bearer realm="https://fake_url.azurecr.io/oauth2/token",service="fake_url.azurecr.io",scope="fake_scope",error="invalid_token" + x-content-type-options: nosniff + status: + code: 401 + message: Unauthorized + url: https://fake_url.azurecr.io/acr/v1/_catalog +- request: + body: + grant_type: password + scope: registry:catalog:* + service: fake_url.azurecr.io + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: POST + uri: https://fake_url.azurecr.io/oauth2/token + response: + body: + string: '{"access_token": "REDACTED"}' + headers: + connection: keep-alive + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:08 GMT + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + transfer-encoding: chunked + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/oauth2/token +- request: + body: null + headers: + Accept: + - application/json + User-Agent: + - azsdk-python-azure-containerregistry/1.0.0b2 Python/3.9.0rc1 (Windows-10-10.0.19041-SP0) + method: GET + uri: https://fake_url.azurecr.io/acr/v1/_catalog + response: + body: + string: '{"repositories": ["library/alpine", "library/busybox", "library/hello-world"]}' + headers: + access-control-expose-headers: X-Ms-Correlation-Request-Id + connection: keep-alive + content-length: '76' + content-type: application/json; charset=utf-8 + date: Sun, 09 May 2021 18:56:08 GMT + docker-distribution-api-version: registry/2.0 + server: openresty + strict-transport-security: max-age=31536000; includeSubDomains + x-content-type-options: nosniff + status: + code: 200 + message: OK + url: https://fake_url.azurecr.io/acr/v1/_catalog +version: 1 diff --git a/sdk/containerregistry/azure-containerregistry/tests/test_anon_access.py b/sdk/containerregistry/azure-containerregistry/tests/test_anon_access.py new file mode 100644 index 000000000000..2782942e9160 --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/test_anon_access.py @@ -0,0 +1,139 @@ +# coding=utf-8 +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import six +from azure import containerregistry + +from azure.containerregistry import ( + ArtifactTagProperties, + RepositoryProperties, + ArtifactManifestProperties, + RegistryArtifact, +) + +from azure.core.paging import ItemPaged +from azure.core.pipeline.transport import RequestsTransport + +from testcase import ContainerRegistryTestClass +from constants import HELLO_WORLD +from preparer import acr_preparer + + +class TestContainerRegistryClient(ContainerRegistryTestClass): + @acr_preparer() + def test_list_repository_names(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + repositories = client.list_repository_names() + assert isinstance(repositories, ItemPaged) + + count = 0 + prev = None + for repo in repositories: + count += 1 + assert isinstance(repo, six.string_types) + assert prev != repo + prev = repo + + assert count > 0 + + @acr_preparer() + def test_list_repository_names_by_page(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + results_per_page = 2 + total_pages = 0 + + repository_pages = client.list_repository_names(results_per_page=results_per_page) + + prev = None + for page in repository_pages.by_page(): + page_count = 0 + for repo in page: + assert isinstance(repo, six.string_types) + assert prev != repo + prev = repo + page_count += 1 + assert page_count <= results_per_page + total_pages += 1 + + assert total_pages > 1 + + @acr_preparer() + def test_transport_closed_only_once(self, containerregistry_anonregistry_endpoint): + transport = RequestsTransport() + client = self.create_anon_client(containerregistry_anonregistry_endpoint, transport=transport) + assert client._credential is None + with client: + for r in client.list_repository_names(): + pass + assert transport.session is not None + + with client.get_repository(HELLO_WORLD) as repo_client: + assert repo_client._credential is None + assert transport.session is not None + + for r in client.list_repository_names(): + pass + assert transport.session is not None + + @acr_preparer() + def test_get_properties(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + container_repository = client.get_repository(HELLO_WORLD) + assert container_repository._credential is None + + properties = container_repository.get_properties() + + assert isinstance(properties, RepositoryProperties) + assert properties.name == HELLO_WORLD + + @acr_preparer() + def test_list_manifests(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + container_repository = client.get_repository(HELLO_WORLD) + assert container_repository._credential is None + + count = 0 + for manifest in container_repository.list_manifests(): + assert isinstance(manifest, ArtifactManifestProperties) + count += 1 + assert count > 0 + + @acr_preparer() + def test_get_artifact(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + container_repository = client.get_repository(HELLO_WORLD) + assert container_repository._credential is None + + registry_artifact = container_repository.get_artifact("latest") + assert registry_artifact._credential is None + + assert isinstance(registry_artifact, RegistryArtifact) + + @acr_preparer() + def test_list_tags(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + container_repository = client.get_repository(HELLO_WORLD) + assert container_repository._credential is None + + registry_artifact = container_repository.get_artifact("latest") + assert registry_artifact._credential is None + + count = 0 + for tag in registry_artifact.list_tags(): + count += 1 + assert isinstance(tag, ArtifactTagProperties) + assert count > 0 diff --git a/sdk/containerregistry/azure-containerregistry/tests/test_anon_access_async.py b/sdk/containerregistry/azure-containerregistry/tests/test_anon_access_async.py new file mode 100644 index 000000000000..0f109df99e26 --- /dev/null +++ b/sdk/containerregistry/azure-containerregistry/tests/test_anon_access_async.py @@ -0,0 +1,135 @@ +# coding=utf-8 +# ------------------------------------ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +# ------------------------------------ +import six + +from azure.containerregistry import ( + RepositoryProperties, + ArtifactManifestProperties, + ArtifactTagProperties, +) +from azure.containerregistry.aio import RegistryArtifact + +from azure.core.pipeline.transport import AioHttpTransport + +from asynctestcase import AsyncContainerRegistryTestClass +from constants import HELLO_WORLD +from preparer import acr_preparer + + +class TestContainerRegistryClient(AsyncContainerRegistryTestClass): + @acr_preparer() + async def test_list_repository_names(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + count = 0 + prev = None + async for repo in client.list_repository_names(): + count += 1 + assert isinstance(repo, six.string_types) + assert prev != repo + prev = repo + + assert count > 0 + + @acr_preparer() + async def test_list_repository_names_by_page(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + results_per_page = 2 + total_pages = 0 + + repository_pages = client.list_repository_names(results_per_page=results_per_page) + + prev = None + async for page in repository_pages.by_page(): + page_count = 0 + async for repo in page: + assert isinstance(repo, six.string_types) + assert prev != repo + prev = repo + page_count += 1 + assert page_count <= results_per_page + total_pages += 1 + + assert total_pages >= 1 + + @acr_preparer() + async def test_transport_closed_only_once(self, containerregistry_anonregistry_endpoint): + transport = AioHttpTransport() + client = self.create_anon_client(containerregistry_anonregistry_endpoint, transport=transport) + assert client._credential is None + + async with client: + async for r in client.list_repository_names(): + pass + assert transport.session is not None + + repo_client = client.get_repository(HELLO_WORLD) + async with repo_client: + assert transport.session is not None + + async for r in client.list_repository_names(): + pass + assert transport.session is not None + + @acr_preparer() + async def test_get_properties(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + container_repository = client.get_repository(HELLO_WORLD) + assert container_repository._credential is None + + properties = await container_repository.get_properties() + + assert isinstance(properties, RepositoryProperties) + assert properties.name == HELLO_WORLD + + @acr_preparer() + async def test_list_manifests(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + container_repository = client.get_repository(HELLO_WORLD) + assert container_repository._credential is None + + count = 0 + async for manifest in container_repository.list_manifests(): + assert isinstance(manifest, ArtifactManifestProperties) + count += 1 + assert count > 0 + + @acr_preparer() + async def test_get_artifact(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + container_repository = client.get_repository(HELLO_WORLD) + assert container_repository._credential is None + + registry_artifact = container_repository.get_artifact("latest") + assert registry_artifact._credential is None + + assert isinstance(registry_artifact, RegistryArtifact) + + @acr_preparer() + async def test_list_tags(self, containerregistry_anonregistry_endpoint): + client = self.create_anon_client(containerregistry_anonregistry_endpoint) + assert client._credential is None + + container_repository = client.get_repository(HELLO_WORLD) + assert container_repository._credential is None + + registry_artifact = container_repository.get_artifact("latest") + assert registry_artifact._credential is None + + count = 0 + async for tag in registry_artifact.list_tags(): + count += 1 + assert isinstance(tag, ArtifactTagProperties) + assert count > 0 diff --git a/sdk/containerregistry/azure-containerregistry/tests/testcase.py b/sdk/containerregistry/azure-containerregistry/tests/testcase.py index c3132a83d94e..db0b4e33b629 100644 --- a/sdk/containerregistry/azure-containerregistry/tests/testcase.py +++ b/sdk/containerregistry/azure-containerregistry/tests/testcase.py @@ -31,13 +31,13 @@ ) from azure.identity import DefaultAzureCredential +from devtools_testutils import AzureTestCase, is_live from azure_devtools.scenario_tests import ( GeneralNameReplacer, RequestUrlNormalizer, AuthenticationMetadataFilter, RecordingProcessor, ) -from devtools_testutils import AzureTestCase from azure_devtools.scenario_tests import ( GeneralNameReplacer, RequestUrlNormalizer, @@ -122,6 +122,11 @@ def process_request(self, request): if "seankane.azurecr.io" in request.url: request.url = request.url.replace("seankane.azurecr.io", "fake_url.azurecr.io") + if "seankaneanon.azurecr.io" in request.uri: + request.uri = request.uri.replace("seankaneanon.azurecr.io", "fake_url.azurecr.io") + if "seankaneanon.azurecr.io" in request.url: + request.url = request.url.replace("seankaneanon.azurecr.io", "fake_url.azurecr.io") + return request def process_response(self, response): @@ -142,6 +147,9 @@ def process_response(self, response): if "seankane.azurecr.io" in body["string"]: body["string"] = body["string"].replace("seankane.azurecr.io", "fake_url.azurecr.io") + if "seankaneanon.azurecr.io" in body["string"]: + body["string"] = body["string"].replace("seankaneanon.azurecr.io", "fake_url.azurecr.io") + refresh = json.loads(body["string"]) if "refresh_token" in refresh.keys(): refresh["refresh_token"] = REDACTED @@ -250,6 +258,9 @@ def create_registry_client(self, endpoint, **kwargs): def create_container_repository(self, endpoint, name, **kwargs): return ContainerRepository(endpoint=endpoint, name=name, credential=self.get_credential(), **kwargs) + def create_anon_client(self, endpoint, **kwargs): + return ContainerRegistryClient(endpoint=endpoint, credential=None, **kwargs) + def assert_content_permission(self, content_perm, content_perm2): assert isinstance(content_perm, ContentProperties) assert isinstance(content_perm2, ContentProperties) @@ -312,6 +323,8 @@ def import_image(repository, tags): @pytest.fixture(scope="session") def load_registry(): + if not is_live(): + return repos = [ "library/hello-world", "library/alpine", diff --git a/sdk/containerregistry/test-resources.json b/sdk/containerregistry/test-resources.json index 5fe3abd98e38..025f14e2f826 100644 --- a/sdk/containerregistry/test-resources.json +++ b/sdk/containerregistry/test-resources.json @@ -1,70 +1,73 @@ { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "baseName": { - "type": "string", - "defaultValue": "[resourceGroup().name]", - "metadata": { - "description": "The base resource name." - } - }, - "tenantId": { - "type": "string", - "metadata": { - "description": "The tenant ID to which the application and resources belong." - } - }, - "testApplicationOid": { - "type": "string", - "metadata": { - "description": "The client OID to grant access to test resources." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "The location of the resource. By default, this is the same as the resource group." - } + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "baseName": { + "type": "string", + "defaultValue": "[resourceGroup().name]", + "metadata": { + "description": "The base resource name." } }, - "variables": { - "apiVersion": "2019-05-01", - "endpointValue": "[format('https://{0}.azurecr.io', parameters('baseName'))]" - }, - "resources": [ - { - "type": "Microsoft.ContainerRegistry/registries", - "apiVersion": "[variables('apiVersion')]", - "name": "[parameters('baseName')]", - "location": "[parameters('location')]", - "properties": { - "endpoint": "[variables('endpointValue')]", - "adminUserEnabled": true - }, - "sku": { - "name": "Basic", - "tier": "Basic" - } + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "The location of the resource. By default, this is the same as the resource group." } - ], - "outputs": { - "CONTAINERREGISTRY_REGISTRY_NAME": { - "type": "string", - "value": "[parameters('baseName')]" - }, - "CONTAINERREGISTRY_ENDPOINT": { - "type": "string", - "value": "[variables('endpointValue')]" + } + }, + "variables": { + "apiVersion": "2020-11-01-preview", + "endpointValue": "[format('https://{0}.azurecr.io', parameters('baseName'))]", + "anonRegistryName": "[format('{0}anon', parameters('baseName'))]", + "anonEndpointValue": "[format('https://{0}.azurecr.io', variables('anonRegistryName'))]" + }, + "resources": [ + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "[variables('apiVersion')]", + "name": "[parameters('baseName')]", + "location": "[parameters('location')]", + "properties": { + "endpoint": "[variables('endpointValue')]", }, - "CONTAINERREGISTRY_USERNAME": { - "type": "string", - "value": "[listCredentials(parameters('baseName'), variables('apiVersion')).username]" + "sku": { + "name": "Basic", + "tier": "Basic" + } + }, + { + "type": "Microsoft.ContainerRegistry/registries", + "apiVersion": "[variables('apiVersion')]", + "name": "[variables('anonRegistryName')]", + "location": "[parameters('location')]", + "properties": { + "endpoint": "[variables('anonEndpointValue')]", + "anonymousPullEnabled": true }, - "CONTAINERREGISTRY_PASSWORD": { - "type": "string", - "value": "[listCredentials(parameters('baseName'), variables('apiVersion')).passwords[0].value]" + "sku": { + "name": "Standard", + "tier": "Standard" } } - } \ No newline at end of file + ], + "outputs": { + "CONTAINERREGISTRY_REGISTRY_NAME": { + "type": "string", + "value": "[parameters('baseName')]" + }, + "CONTAINERREGISTRY_ENDPOINT": { + "type": "string", + "value": "[variables('endpointValue')]" + }, + "CONTAINERREGISTRY_ANONREGISTRY_NAME": { + "type": "string", + "value": "[variables('anonRegistryName')]" + }, + "CONTAINERREGISTRY_ANONREGISTRY_ENDPOINT": { + "type": "string", + "value": "[variables('anonEndpointValue')]" + }, + } +} \ No newline at end of file