Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
d7bcfe6
changing ContainerRepositoryClient to ContainerRepository
seankane-msft Apr 27, 2021
7a0b598
renaming files
seankane-msft Apr 27, 2021
15bbaaf
re-recording, commenting out tests that are not necessary
seankane-msft Apr 27, 2021
b91302c
working sync registry artifact class
seankane-msft Apr 28, 2021
6f7c55c
async registry artifact
seankane-msft Apr 28, 2021
22ecb18
issue with recording infra that is removing an acr specific oauth path
seankane-msft Apr 28, 2021
d5986dd
pylint issues
seankane-msft Apr 28, 2021
1287136
recording and processors
seankane-msft Apr 28, 2021
145949b
removing commented out code
seankane-msft Apr 28, 2021
09f3636
undoing changes to cache
seankane-msft Apr 28, 2021
6e38bf1
more lint fixes
seankane-msft Apr 28, 2021
d9659c0
help with logging output
seankane-msft Apr 28, 2021
941473a
change to list_repository_names
seankane-msft Apr 28, 2021
44ceace
renaming for consistency
seankane-msft Apr 28, 2021
4e5dbc8
all changes made, plus recordings
seankane-msft Apr 28, 2021
f7caebd
fixing up more tests again!
seankane-msft Apr 28, 2021
6ced986
formatting
seankane-msft Apr 28, 2021
5417f1d
merge conflicts
seankane-msft Apr 29, 2021
83c0d90
fixing up merge issues
seankane-msft May 3, 2021
c0ed9ff
merge conflicts
seankane-msft May 3, 2021
ed3f937
more conflicts
seankane-msft May 3, 2021
ccec23b
undoing changes to gen code
seankane-msft May 3, 2021
afa0c1b
pylint issues
seankane-msft May 4, 2021
0e2e335
consistent naming
seankane-msft May 4, 2021
a05639a
working through massive merge conflicts
seankane-msft May 5, 2021
91f150f
renaming containerrepository.repository to name
seankane-msft May 5, 2021
4af08b9
renaming enums, dropping the 'By' off the end
seankane-msft May 5, 2021
992df39
adding pipeline wrappers
seankane-msft May 5, 2021
35eb6e5
lint fixes
seankane-msft May 5, 2021
fd701a4
removing unused import
seankane-msft May 6, 2021
cb3341e
Merge branch 'master' into acr-container-repo
seankane-msft May 6, 2021
c25fec2
Merge branch 'master' of https://github.com/Azure/azure-sdk-for-pytho…
seankane-msft May 6, 2021
91e9839
fixing an improper merge conflict in gen code
seankane-msft May 6, 2021
ba7d4da
checking out from master
seankane-msft May 6, 2021
8ef7421
adding the pipeline wrappers
seankane-msft May 7, 2021
2ae91f1
few comments
seankane-msft May 7, 2021
9c20c59
final newline misssing
seankane-msft May 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,30 @@
# --------------------------------------------------------------------------

from ._container_registry_client import ContainerRegistryClient
from ._container_repository_client import ContainerRepositoryClient
from ._container_repository import ContainerRepository
from ._models import (
DeleteRepositoryResult,
ContentProperties,
ManifestOrderBy,
ManifestOrder,
ArtifactManifestProperties,
RepositoryProperties,
TagOrderBy,
TagOrder,
ArtifactTagProperties,
)
from ._registry_artifact import RegistryArtifact
from ._version import VERSION

__version__ = VERSION

__all__ = [
"ContainerRegistryClient",
"ContainerRepositoryClient",
"DeleteRepositoryResult",
"ContainerRepository",
"ContentProperties",
"ManifestOrderBy",
"DeleteRepositoryResult",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What information do we get back from a delete operation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manifests and tags deleted as part of the repository delete operation

"RegistryArtifact",
"ManifestOrder",
"ArtifactManifestProperties",
"RepositoryProperties",
"TagOrderBy",
"TagOrder",
"ArtifactTagProperties",
]
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ContainerRegistryApiVersion(str, Enum):


class ContainerRegistryBaseClient(object):
"""Base class for ContainerRegistryClient and ContainerRepositoryClient
"""Base class for ContainerRegistryClient, ContainerRepository, and RegistryArtifact

:param str endpoint: Azure Container Registry endpoint
:param credential: AAD Token for authenticating requests with Azure
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@
from azure.core.tracing.decorator import distributed_trace

from ._base_client import ContainerRegistryBaseClient, TransportWrapper
from ._container_repository_client import ContainerRepositoryClient
from ._container_repository import ContainerRepository
from ._generated.models import AcrErrors
from ._helpers import _parse_next_link
from ._registry_artifact import RegistryArtifact
from ._models import DeleteRepositoryResult

if TYPE_CHECKING:
Expand Down Expand Up @@ -53,11 +54,11 @@ def __init__(self, endpoint, credential, **kwargs):
super(ContainerRegistryClient, self).__init__(endpoint=endpoint, credential=credential, **kwargs)

@distributed_trace
def delete_repository(self, repository, **kwargs):
def delete_repository(self, repository_name, **kwargs):
# type: (str, Dict[str, Any]) -> DeleteRepositoryResult
"""Delete a repository

:param str repository: The repository to delete
:param str repository_name: The repository to delete
:returns: Object containing information about the deleted repository
:rtype: :class:`~azure.containerregistry.DeleteRepositoryResult`
:raises: :class:`~azure.core.exceptions.ResourceNotFoundError`
Expand All @@ -72,11 +73,11 @@ def delete_repository(self, repository, **kwargs):
:caption: Delete a repository from the `ContainerRegistryClient`
"""
return DeleteRepositoryResult._from_generated( # pylint: disable=protected-access
self._client.container_registry.delete_repository(repository, **kwargs)
self._client.container_registry.delete_repository(repository_name, **kwargs)
)

@distributed_trace
def list_repositories(self, **kwargs):
def list_repository_names(self, **kwargs):
# type: (Dict[str, Any]) -> ItemPaged[str]
"""List all repositories

Expand Down Expand Up @@ -162,7 +163,7 @@ def extract_data(pipeline_response):
deserialized = self._client._deserialize( # pylint: disable=protected-access
"Repositories", pipeline_response
)
list_of_elem = deserialized.repositories
list_of_elem = deserialized.repositories or []
if cls:
list_of_elem = cls(list_of_elem)
link = None
Expand Down Expand Up @@ -192,12 +193,12 @@ def get_next(next_link=None):
return ItemPaged(get_next, extract_data)

@distributed_trace
def get_repository_client(self, repository, **kwargs):
# type: (str, Dict[str, Any]) -> ContainerRepositoryClient
"""Get a repository client
def get_repository(self, repository_name, **kwargs):
# type: (str, Any) -> ContainerRepository
"""Get a Container Repository object

:param str repository: The repository to create a client for
:returns: :class:`~azure.containerregistry.ContainerRepositoryClient`
:param str repository_name: The repository to create a client for
:returns: :class:`~azure.containerregistry.ContainerRepository`
:raises: None

Example
Expand All @@ -215,6 +216,29 @@ def get_repository_client(self, repository, **kwargs):
transport=TransportWrapper(self._client._client._pipeline._transport), # pylint: disable=protected-access
policies=self._client._client._pipeline._impl_policies, # pylint: disable=protected-access
)
return ContainerRepositoryClient(
self._endpoint, repository, credential=self._credential, pipeline=_pipeline, **kwargs
return ContainerRepository(
self._endpoint, repository_name, credential=self._credential, pipeline=_pipeline, **kwargs
)

@distributed_trace
def get_artifact(self, repository_name, tag_or_digest, **kwargs):
# type: (str, str, Dict[str, Any]) -> RegistryArtifact
"""Get a Registry Artifact object

:param str repository_name: Name of the repository
:param str tag_or_digest: The tag or digest of the artifact
:returns: :class:`~azure.containerregistry.RegistryArtifact`
:raises: None
"""
_pipeline = Pipeline(
transport=TransportWrapper(self._client._client._pipeline._transport), # pylint: disable=protected-access
policies=self._client._client._pipeline._impl_policies, # pylint: disable=protected-access
)
return RegistryArtifact(
self._endpoint,
repository_name,
tag_or_digest,
self._credential,
pipeline=_pipeline,
**kwargs
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
# coding=utf-8
# ------------------------------------
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
# ------------------------------------
from typing import TYPE_CHECKING

from azure.core.exceptions import (
ClientAuthenticationError,
ResourceNotFoundError,
ResourceExistsError,
HttpResponseError,
map_error,
)
from azure.core.paging import ItemPaged
from azure.core.pipeline import Pipeline
from azure.core.tracing.decorator import distributed_trace

from ._base_client import ContainerRegistryBaseClient, TransportWrapper
from ._generated.models import AcrErrors
from ._helpers import _parse_next_link
from ._models import (
DeleteRepositoryResult,
ArtifactManifestProperties,
RepositoryProperties,
)
from ._registry_artifact import RegistryArtifact

if TYPE_CHECKING:
from typing import Any, Dict
from azure.core.credentials import TokenCredential
from ._models import ContentProperties


class ContainerRepository(ContainerRegistryBaseClient):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this no longer a client? Is it because a user cannot instantiate it themselves?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A user could instantiate it themselves but it would look a little awkward. We received feedback from the service team that the two clients made it confusing on where a user should start. Krystof and Anne Thompson came up with a design where both ContainerRepository and RegistryArtifact have client-like properties (being able to make service calls) but are more like models. This produces a more intuitive starting point with ContainerRegistryClient.

def __init__(self, endpoint, name, credential, **kwargs):
# type: (str, str, TokenCredential, Dict[str, Any]) -> None
"""Create a ContainerRepository from an endpoint, repository name, and credential

:param str endpoint: An ACR endpoint
:param str name: The name of a repository
:param credential: The credential with which to authenticate
:type credential: :class:`~azure.core.credentials.TokenCredential`
:returns: None
:raises: None
"""
if not endpoint.startswith("https://") and not endpoint.startswith("http://"):
endpoint = "https://" + endpoint
self._endpoint = endpoint
self.name = name
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this also have something like the property on RegistryArtifact:

self.fully_qualified_name = self._endpoint + self.repository

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes adding it now.

self._credential = credential
self.fully_qualified_name = self._endpoint + self.name
super(ContainerRepository, self).__init__(endpoint=self._endpoint, credential=credential, **kwargs)

@distributed_trace
def delete(self, **kwargs):
# type: (Dict[str, Any]) -> DeleteRepositoryResult
"""Delete a repository

:returns: Object containing information about the deleted repository
:rtype: :class:`~azure.containerregistry.DeleteRepositoryResult`
:raises: :class:`~azure.core.exceptions.ResourceNotFoundError`
"""
return DeleteRepositoryResult._from_generated( # pylint: disable=protected-access
self._client.container_registry.delete_repository(self.name, **kwargs)
)

@distributed_trace
def get_properties(self, **kwargs):
# type: (Dict[str, Any]) -> RepositoryProperties
"""Get the properties of a repository

:returns: :class:`~azure.containerregistry.RepositoryProperties`
:raises: :class:`~azure.core.exceptions.ResourceNotFoundError`
"""
return RepositoryProperties._from_generated( # pylint: disable=protected-access
self._client.container_registry.get_properties(self.name, **kwargs)
)

@distributed_trace
def list_manifests(self, **kwargs):
# type: (Dict[str, Any]) -> ItemPaged[ArtifactManifestProperties]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the listed items here the same data that I would get from a get_manifest_properties call?
We're using the same response object for both - so just checking that they both include the same information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes they are, both calls return the same data.

"""List the artifacts for a repository

:keyword last: Query parameter for the last item in the previous call. Ensuing
call will return values after last lexically
:paramtype last: str
:keyword order_by: Query parameter for ordering by time ascending or descending
:paramtype order_by: :class:`~azure.containerregistry.ManifestOrder`
:keyword results_per_page: Number of repositories to return per page
:paramtype results_per_page: int
:return: ItemPaged[:class:`ArtifactManifestProperties`]
:rtype: :class:`~azure.core.paging.ItemPaged`
:raises: :class:`~azure.core.exceptions.ResourceNotFoundError`
"""
name = self.name
last = kwargs.pop("last", None)
n = kwargs.pop("results_per_page", None)
orderby = kwargs.pop("order_by", None)
cls = kwargs.pop(
"cls",
lambda objs: [
ArtifactManifestProperties._from_generated(x, repository_name=self.name) # pylint: disable=protected-access
for x in objs
],
)

error_map = {401: ClientAuthenticationError, 404: ResourceNotFoundError, 409: ResourceExistsError}
error_map.update(kwargs.pop("error_map", {}))
accept = "application/json"

def prepare_request(next_link=None):
# Construct headers
header_parameters = {} # type: Dict[str, Any]
header_parameters["Accept"] = self._client._serialize.header( # pylint: disable=protected-access
"accept", accept, "str"
)

if not next_link:
# Construct URL
url = "/acr/v1/{name}/_manifests"
path_format_arguments = {
"url": self._client._serialize.url( # pylint: disable=protected-access
"self._client._config.url",
self._client._config.url, # pylint: disable=protected-access
"str",
skip_quote=True,
),
"name": self._client._serialize.url("name", name, "str"), # pylint: disable=protected-access
}
url = self._client._client.format_url(url, **path_format_arguments) # pylint: disable=protected-access
# Construct parameters
query_parameters = {} # type: Dict[str, Any]
if last is not None:
query_parameters["last"] = self._client._serialize.query( # pylint: disable=protected-access
"last", last, "str"
)
if n is not None:
query_parameters["n"] = self._client._serialize.query( # pylint: disable=protected-access
"n", n, "int"
)
if orderby is not None:
query_parameters["orderby"] = self._client._serialize.query( # pylint: disable=protected-access
"orderby", orderby, "str"
)

request = self._client._client.get( # pylint: disable=protected-access
url, query_parameters, header_parameters
)
else:
url = next_link
query_parameters = {} # type: Dict[str, Any]
path_format_arguments = {
"url": self._client._serialize.url( # pylint: disable=protected-access
"self._client._config.url",
self._client._config.url, # pylint: disable=protected-access
"str",
skip_quote=True,
),
"name": self._client._serialize.url("name", name, "str"), # pylint: disable=protected-access
}
url = self._client._client.format_url(url, **path_format_arguments) # pylint: disable=protected-access
request = self._client._client.get( # pylint: disable=protected-access
url, query_parameters, header_parameters
)
return request

def extract_data(pipeline_response):
deserialized = self._client._deserialize( # pylint: disable=protected-access
"AcrManifests", pipeline_response
)
list_of_elem = deserialized.manifests
if cls:
list_of_elem = cls(list_of_elem)
link = None
if "Link" in pipeline_response.http_response.headers.keys():
link = _parse_next_link(pipeline_response.http_response.headers["Link"])
return link, iter(list_of_elem)

def get_next(next_link=None):
request = prepare_request(next_link)

pipeline_response = self._client._client._pipeline.run( # pylint: disable=protected-access
request, stream=False, **kwargs
)
response = pipeline_response.http_response

if response.status_code not in [200]:
error = self._client._deserialize.failsafe_deserialize( # pylint: disable=protected-access
AcrErrors, response
)
map_error(status_code=response.status_code, response=response, error_map=error_map)
raise HttpResponseError(response=response, model=error)

return pipeline_response

return ItemPaged(get_next, extract_data)

@distributed_trace
def set_properties(self, properties, **kwargs):
# type: (RepositoryProperties, Dict[str, Any]) -> RepositoryProperties
"""Set the properties of a repository

:returns: :class:`~azure.containerregistry.RepositoryProperties`
:raises: :class:`~azure.core.exceptions.ResourceNotFoundError`
"""
return RepositoryProperties._from_generated( # pylint: disable=protected-access
self._client.container_registry.set_properties(
self.name, properties._to_generated(), **kwargs # pylint: disable=protected-access
)
)

@distributed_trace
def get_artifact(self, tag_or_digest, **kwargs):
# type: (str, str, Dict[str, Any]) -> RegistryArtifact
"""Get a Registry Artifact object

:param str repository_name: Name of the repository
:param str tag_or_digest: The tag or digest of the artifact
:returns: :class:`~azure.containerregistry.RegistryArtifact`
:raises: None
"""
_pipeline = Pipeline(
transport=TransportWrapper(self._client._client._pipeline._transport), # pylint: disable=protected-access
policies=self._client._client._pipeline._impl_policies, # pylint: disable=protected-access
)
return RegistryArtifact(
self._endpoint, self.name, tag_or_digest, self._credential, pipeline=_pipeline, **kwargs
)
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def get_acr_access_token(self, challenge, **kwargs):
)

def get_refresh_token(self, service, **kwargs):
# type: (str, **Any) -> str
# type: (str, Dict[str, Any]) -> str
if not self._refresh_token or time.time() - self._last_refresh_time > 300:
self._refresh_token = self.exchange_aad_token_for_refresh_token(service, **kwargs)
self._last_refresh_time = time.time()
Expand Down
Loading