Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
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