Skip to content

Commit

Permalink
[ACR] az acr manifest: Add new command group (Azure#21161)
Browse files Browse the repository at this point in the history
* add new manifest commands

* small refactor

* latest updates

* add fqdn support

* fix typo

* check input

* try raw formatting

* revert raw manifest output

* raw output again

* tryfix hash

* tryfix hashing

* tryfix hash

* try fix hash

* fix validator bug

* fix raw flag behavior

* move metadata under sub command group

* fix style

* write tests

* clean up code

* clean up code

* clean up code

* update help text

* clean up code

* fix typo

* add linter exclusions

* fix linter exclusions

* change variable names

* change help text
  • Loading branch information
mabenedi authored Feb 25, 2022
1 parent 99e1959 commit 6a6dbff
Show file tree
Hide file tree
Showing 10 changed files with 1,166 additions and 7 deletions.
37 changes: 36 additions & 1 deletion linter_exclusions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,41 @@ acr helm show:
chart:
rule_exclusions:
- no_positional_parameters
acr manifest delete:
parameters:
manifest_id:
rule_exclusions:
- no_positional_parameters
acr manifest list:
parameters:
repo_id:
rule_exclusions:
- no_positional_parameters
acr manifest list-referrers:
parameters:
manifest_id:
rule_exclusions:
- no_positional_parameters
acr manifest metadata list:
parameters:
repo_id:
rule_exclusions:
- no_positional_parameters
acr manifest metadata show:
parameters:
manifest_id:
rule_exclusions:
- no_positional_parameters
acr manifest metadata update:
parameters:
manifest_id:
rule_exclusions:
- no_positional_parameters
acr manifest show:
parameters:
manifest_id:
rule_exclusions:
- no_positional_parameters
acr pack build:
parameters:
source_location:
Expand Down Expand Up @@ -261,7 +296,7 @@ aks create:
- option_length_too_long
enable_encryption_at_host:
rule_exclusions:
- option_length_too_long
- option_length_too_long
assign_kubelet_identity:
rule_exclusions:
- option_length_too_long
Expand Down
23 changes: 22 additions & 1 deletion src/azure-cli/azure/cli/command_modules/acr/_docker_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class RepoAccessTokenPermission(Enum):
DELETE = 'delete'
META_WRITE_META_READ = '{},{}'.format(METADATA_WRITE, METADATA_READ)
DELETE_META_READ = '{},{}'.format(DELETE, METADATA_READ)
PULL = 'pull'
PULL_META_READ = '{},{}'.format(PULL, METADATA_READ)


class HelmAccessTokenPermission(Enum):
Expand Down Expand Up @@ -484,6 +486,17 @@ def get_authorization_header(username, password):
return {'Authorization': auth}


def get_manifest_authorization_header(username, password):
if username == EMPTY_GUID:
auth = _get_bearer_auth_str(password)
else:
auth = _get_basic_auth_str(username, password)
return {'Authorization': auth,
'Accept': '*/*, application/vnd.cncf.oras.artifact.manifest.v1+json'
', application/vnd.oci.image.manifest.v1+json'}


# pylint: disable=too-many-statements
def request_data_from_registry(http_method,
login_server,
path,
Expand All @@ -493,6 +506,8 @@ def request_data_from_registry(http_method,
json_payload=None,
file_payload=None,
params=None,
manifest_headers=False,
raw=False,
retry_times=3,
retry_interval=5,
timeout=300):
Expand All @@ -509,7 +524,11 @@ def request_data_from_registry(http_method,
raise ValueError("Non-empty payload is required for http method: {}".format(http_method))

url = 'https://{}{}'.format(login_server, path)
headers = get_authorization_header(username, password)

if manifest_headers:
headers = get_manifest_authorization_header(username, password)
else:
headers = get_authorization_header(username, password)

for i in range(0, retry_times):
errorMessage = None
Expand Down Expand Up @@ -538,6 +557,8 @@ def request_data_from_registry(http_method,

log_registry_response(response)

if manifest_headers and raw and response.status_code == 200:
return response.content.decode('utf-8'), None
if response.status_code == 200:
result = response.json()[result_index] if result_index else response.json()
next_link = response.headers['link'] if 'link' in response.headers else None
Expand Down
23 changes: 23 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acr/_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,29 @@ def connected_registry_list_output_format(result):
return _output_format(result_list_format, _connected_registry_list_format_group)


def list_referrers_output_format(result):
manifests = []
for manifest in result['references']:
manifests.append(OrderedDict([
('Digest', _get_value(manifest, 'digest')),
('ArtifactType', _get_value(manifest, 'artifactType')),
('MediaType', _get_value(manifest, 'mediaType')),
('Size', _get_value(manifest, 'size'))
]))
return manifests


def manifest_output_format(result):
manifests = []
for manifest in result:
manifests.append(OrderedDict([
('MediaType', _get_value(manifest, 'mediaType')),
('ArtifactType', _get_value(manifest, 'artifactType')),
('SubjectDigest', _get_value(manifest, 'subject', 'digest'))
]))
return manifests


def _recursive_format_list_acr_childs(family_tree, connected_registry_id):
connected_registry = family_tree[connected_registry_id]
childs = connected_registry['childs']
Expand Down
92 changes: 92 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acr/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,98 @@
text: az acr repository update -n MyRegistry --image hello-world@sha256:abc123 --write-enabled false
"""

helps['acr manifest'] = """
type: group
short-summary: Manage artifact manifests in Azure Container Registries.
"""

helps['acr manifest show'] = """
type: command
short-summary: Get a manifest in an Azure Container Registry.
examples:
- name: Get the manifest of the artifact 'hello-world:latest'.
text: az acr manifest show -r MyRegistry -n hello-world:latest
- name: Get the manifest of the artifact 'hello-world:latest'.
text: az acr manifest show MyRegistry.azurecr.io/hello-world:latest
- name: Get the manifest of the artifact referenced by digest 'hello-world@sha256:abc123'.
text: az acr manifest show -r MyRegistry -n hello-world@sha256:abc123
- name: Get the raw, unformatted manifest of the artifact 'hello-world:latest'.
text: az acr manifest show -r MyRegistry -n hello-world:latest --raw
"""

helps['acr manifest list'] = """
type: command
short-summary: List the manifests in a repository in an Azure Container Registry.
examples:
- name: List the manifests of the repository 'hello-world'.
text: az acr manifest list -r MyRegistry -n hello-world
- name: List the manifests of the repository 'hello-world'.
text: az acr manifest list MyRegistry.azurecr.io/hello-world
"""

helps['acr manifest delete'] = """
type: command
short-summary: Delete a manifest in an Azure Container Registry.
examples:
- name: Delete the manifest of the artifact 'hello-world:latest'.
text: az acr manifest delete -r MyRegistry -n hello-world:latest
- name: Delete the manifest of the artifact 'hello-world:latest'.
text: az acr manifest delete MyRegistry.azurecr.io/hello-world:latest
- name: Delete the manifest of the artifact referenced by digest 'hello-world@sha256:abc123'.
text: az acr manifest delete -r MyRegistry -n hello-world@sha256:abc123
"""

helps['acr manifest list-referrers'] = """
type: command
short-summary: List the ORAS referrers to a manifest in an Azure Container Registry.
examples:
- name: List the referrers to the manifest of the artifact 'hello-world:latest'.
text: az acr manifest list-referrers -r MyRegistry -n hello-world:latest
- name: List the referrers to the manifest of the artifact 'hello-world:latest'.
text: az acr manifest list-referrers MyRegistry.azurecr.io/hello-world:latest
- name: List the referrers to the manifest of the artifact referenced by digest 'hello-world@sha256:abc123'.
text: az acr manifest list-referrers -r MyRegistry -n hello-world@sha256:abc123
"""

helps['acr manifest metadata'] = """
type: group
short-summary: Manage artifact manifest metadata in Azure Container Registries.
"""

helps['acr manifest metadata show'] = """
type: command
short-summary: Get the metadata of an artifact in an Azure Container Registry.
examples:
- name: Get the metadata of the tag 'hello-world:latest'.
text: az acr manifest metadata show -r MyRegistry -n hello-world:latest
- name: Get the metadata of the tag 'hello-world:latest'.
text: az acr manifest metadata show MyRegistry.azurecr.io/hello-world:latest
- name: Get the metadata of the manifest referenced by digest 'hello-world@sha256:abc123'.
text: az acr manifest metadata show -r MyRegistry -n hello-world@sha256:abc123
"""

helps['acr manifest metadata list'] = """
type: command
short-summary: List the metadata of the manifests in a repository in an Azure Container Registry.
examples:
- name: List the metadata of the manifests in the repository 'hello-world'.
text: az acr manifest metadata list -r MyRegistry -n hello-world
- name: List the metadata of the manifests in the repository 'hello-world'.
text: az acr manifest metadata list MyRegistry.azurecr.io/hello-world
"""

helps['acr manifest metadata update'] = """
type: command
short-summary: Update the manifest metadata of an artifact in an Azure Container Registry.
examples:
- name: Update the metadata of the tag 'hello-world:latest'.
text: az acr manifest metadata update -r MyRegistry -n hello-world:latest --write-enabled false
- name: Update the metadata of the tag 'hello-world:latest'.
text: az acr manifest metadata update MyRegistry.azurecr.io/hello-world:latest --write-enabled false
- name: Update the metadata of the artifact referenced by digest 'hello-world@sha256:abc123'.
text: az acr manifest metadata update -r MyRegistry -n hello-world@sha256:abc123 --write-enabled false
"""

helps['acr run'] = """
type: command
short-summary: Queues a quick run providing streamed logs for an Azure Container Registry.
Expand Down
55 changes: 54 additions & 1 deletion src/azure-cli/azure/cli/command_modules/acr/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,27 @@
validate_set_secret,
validate_retention_days,
validate_registry_name,
validate_expiration_time
validate_expiration_time,
validate_manifest_id,
validate_repo_id,
validate_repository
)
from .scope_map import RepoScopeMapActions, GatewayScopeMapActions

repo_id_type = CLIArgumentType(
nargs='*',
default=None,
validator=validate_repo_id,
help="A fully qualified repository specifier such as 'MyRegistry.azurecr.io/hello-world'."
)

manifest_id_type = CLIArgumentType(
nargs='*',
default=None,
validator=validate_manifest_id,
help="A fully qualified manifest specifier such as 'MyRegistry.azurecr.io/hello-world:latest'."
)

image_by_tag_or_digest_type = CLIArgumentType(
options_list=['--image', '-t'],
help="The name of the image. May include a tag in the format 'name:tag' or digest in the format 'name@digest'."
Expand Down Expand Up @@ -135,6 +152,42 @@ def load_arguments(self, _): # pylint: disable=too-many-statements
c.argument('read_enabled', help='Indicates whether read operation is allowed.', arg_type=get_three_state_flag())
c.argument('write_enabled', help='Indicates whether write or delete operation is allowed.', arg_type=get_three_state_flag())

with self.argument_context('acr manifest') as c:
c.argument('registry_name', options_list=['--registry', '-r'], help='The name of the container registry. You can configure the default registry name using `az configure --defaults acr=<registry name>`', completer=get_resource_name_completion_list(REGISTRY_RESOURCE_TYPE), configured_default='acr', validator=validate_registry_name)
c.argument('top', type=int, help='Limit the number of items in the results.')
c.argument('orderby', help='Order the items in the results. Default to alphabetical order of names.', arg_type=get_enum_type(['time_asc', 'time_desc']))
c.argument('delete_enabled', help='Indicate whether delete operation is allowed.', arg_type=get_three_state_flag())
c.argument('list_enabled', help='Indicate whether this item shows in list operation results.', arg_type=get_three_state_flag())
c.argument('read_enabled', help='Indicate whether read operation is allowed.', arg_type=get_three_state_flag())
c.argument('write_enabled', help='Indicate whether write or delete operation is allowed.', arg_type=get_three_state_flag())
c.argument('repository', help='The name of the repository.', options_list=['--name', '-n'], validator=validate_repository)
c.argument('manifest_spec', help="The name of the artifact. May include a tag in the format 'name:tag' or digest in the format 'name@digest'.", options_list=['--name', '-n'])

# Positional arguments must be specified on each individual command, they cannot be assigned to a command group
with self.argument_context('acr manifest show') as c:
c.positional('manifest_id', arg_type=manifest_id_type)
c.argument('raw_output', help='Output the raw manifest text with no formatting.', options_list=['--raw'], action='store_true')

with self.argument_context('acr manifest list') as c:
c.positional('repo_id', arg_type=repo_id_type)

with self.argument_context('acr manifest delete') as c:
c.positional('manifest_id', arg_type=manifest_id_type)

with self.argument_context('acr manifest list-referrers') as c:
c.positional('manifest_id', arg_type=manifest_id_type)
c.argument('artifact_type', help='Filter referrers based on artifact type.')
c.argument('recursive', help='Recursively include referrer artifacts.', action='store_true')

with self.argument_context('acr manifest metadata show') as c:
c.positional('manifest_id', arg_type=manifest_id_type)

with self.argument_context('acr manifest metadata list') as c:
c.positional('repo_id', arg_type=repo_id_type)

with self.argument_context('acr manifest metadata update') as c:
c.positional('manifest_id', arg_type=manifest_id_type)

with self.argument_context('acr repository untag') as c:
c.argument('image', options_list=['--image', '-t'], help="The name of the image. May include a tag in the format 'name:tag'.")

Expand Down
28 changes: 28 additions & 0 deletions src/azure-cli/azure/cli/command_modules/acr/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@
import os
from knack.util import CLIError
from knack.log import get_logger
from azure.cli.core.azclierror import InvalidArgumentValueError

BAD_REPO_FQDN = "The positional parameter 'repo_id' must be a fully qualified repository specifier such"\
" as 'MyRegistry.azurecr.io/hello-world'."
BAD_MANIFEST_FQDN = "The positional parameter 'manifest_id' must be a fully qualified"\
" manifest specifier such as 'MyRegistry.azurecr.io/hello-world:latest' or"\
" 'MyRegistry.azurecr.io/hello-world@sha256:abc123'."

logger = get_logger(__name__)

Expand Down Expand Up @@ -112,3 +119,24 @@ def validate_expiration_time(namespace):
except ValueError:
raise CLIError("Input '{}' is not valid datetime. Valid example: 2025-12-31T12:59:59Z".format(
namespace.expiration))


def validate_repo_id(namespace):
if namespace.repo_id:
repo_id = namespace.repo_id[0]
if '.' not in repo_id or '/' not in repo_id:
raise InvalidArgumentValueError(BAD_REPO_FQDN)


def validate_manifest_id(namespace):
if namespace.manifest_id:
manifest_id = namespace.manifest_id[0]
if '.' not in manifest_id or '/' not in manifest_id:
raise InvalidArgumentValueError(BAD_MANIFEST_FQDN)


def validate_repository(namespace):
if namespace.repository:
if ':' in namespace.repository:
raise InvalidArgumentValueError("Parameter 'name' refers to a repository and"
" should not include a tag or digest.")
Loading

0 comments on commit 6a6dbff

Please sign in to comment.