feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class BuildArtifacts(base.Group):
"""Manage Artifact Registry resources."""
category = base.CI_CD_CATEGORY
def Filter(self, context, args):
# TODO(b/190524964): Determine if command group works with project number
base.RequireProjectID(args)
del context, args

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry packages."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Apt(base.Group):
"""Manage Artifact Registry Debian packages.
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to import Debian packages into a repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.core import resources
class Import(base.Command):
"""Import one or more Debian packages into an artifact repository."""
api_version = 'v1'
@staticmethod
def Args(parser):
"""Set up arguements for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetRepoArg().AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
parser.add_argument(
'--gcs-source',
metavar='GCS_SOURCE',
required=True,
type=arg_parsers.ArgList(),
help="""\
The Google Cloud Storage location of a package to import.
To import multiple packages, use wildcards at the end of the path.
""")
def Run(self, args):
"""Run package import command."""
client = apis.GetClientInstance('artifactregistry', self.api_version)
messages = client.MESSAGES_MODULE
for gcs_source in args.gcs_source:
if '*' in gcs_source and not gcs_source.endswith('*'):
raise exceptions.InvalidArgumentException(
'GCS_SOURCE', 'Wildcards must be at the end of the GCS path.')
repo_ref = args.CONCEPTS.repository.Parse()
gcs_source = messages.ImportAptArtifactsGcsSource(
uris=args.gcs_source,
useWildcards=True)
import_request = messages.ImportAptArtifactsRequest(
gcsSource=gcs_source)
request = messages.ArtifactregistryProjectsLocationsRepositoriesAptArtifactsImportRequest(
importAptArtifactsRequest=import_request,
parent=repo_ref.RelativeName())
op = client.projects_locations_repositories_aptArtifacts.Import(request)
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations')
if args.async_:
return op_ref
else:
result = waiter.WaitFor(
waiter.CloudOperationPollerNoResources(
client.projects_locations_operations),
op_ref, 'Importing package(s)')
return result
Import.detailed_help = {
'brief': 'Import one or more Debian packages into an artifact repository.',
'DESCRIPTION': """
*{command}* imports Debian packages from Google Cloud Storage into the specified
artifact repository.
""",
'EXAMPLES': """
To import the package `my-package.deb` from Google Cloud Storage into
`my-repo`, run:
$ {0} my-repo --location=us-central1 --gcs-source={1}
To import the packages `my-package.deb` and `other-package.deb` into
`my-repo`, run:
$ {0} my-repo --location=us-central1 --gcs-source={1},{2}
To import all packages from `my-directory` into `my-repo`, run:
$ {0} my-repo --location=us-central1 --gcs-source={3}
To import all packages in all subdirectories from a Google Cloud
Storage bucket into `my-repo`, run:
$ {0} my-repo --location=us-central1 --gcs-source={4}
""".format('{command}', 'gs://my-bucket/path/to/my-package.deb',
'gs://my-bucket/path/to/other-package.deb',
'gs://my-bucket/my-directory/*',
'gs://my-bucket/**')
}

View File

@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to upload Debian packages to a repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import transfer
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.core import resources
class Upload(base.Command):
"""Upload a Debian package to an artifact repository."""
api_version = 'v1'
@staticmethod
def Args(parser):
"""Set up arguements for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetRepoArg().AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
parser.add_argument(
'--source',
metavar='SOURCE',
required=True,
help="""\
The path of a package to upload.""")
def Run(self, args):
"""Run package import command."""
client = apis.GetClientInstance('artifactregistry', self.api_version)
messages = client.MESSAGES_MODULE
client.additional_http_headers['X-Goog-Upload-Protocol'] = 'multipart'
repo_ref = args.CONCEPTS.repository.Parse()
upload_req = messages.UploadAptArtifactRequest
upload_request = upload_req()
request = messages.ArtifactregistryProjectsLocationsRepositoriesAptArtifactsUploadRequest(
uploadAptArtifactRequest=upload_request,
parent=repo_ref.RelativeName())
upload = transfer.Upload.FromFile(
args.source, mime_type='application/vnd.debian.binary-package')
op_obj = client.projects_locations_repositories_aptArtifacts.Upload(
request, upload=upload)
op = op_obj.operation
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations')
if args.async_:
return op_ref
else:
result = waiter.WaitFor(
waiter.CloudOperationPollerNoResources(
client.projects_locations_operations),
op_ref, 'Uploading package')
return result
Upload.detailed_help = {
'brief': 'Upload a Debian package to an artifact repository.',
'DESCRIPTION': """
*{command}* uploads a Debian package to the specified artifact repository.
""",
'EXAMPLES': """
To upload the package `my-package.deb` to `my-repo`, run:
$ {0} my-repo --location=us-central1 --source={1}
""".format('{command}', 'my-package.deb')
}

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry attachments."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class Attachments(base.Group):
"""Manage Artifact Registry attachments.
## EXAMPLES
To list all attachments in the current project, repository and
location,
run:
$ {command} list
To list attachments under repository `my-repo` in the current project and
location,
run:
$ {command} list --repository=my-repo
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,244 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to create nand upload attachments to a repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import hashlib
import os
from apitools.base.py import transfer
from googlecloudsdk.api_lib.artifacts import exceptions as ar_exceptions
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import docker_util
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import requests
from googlecloudsdk.command_lib.artifacts import util
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.util import files
from googlecloudsdk.core.util import scaled_integer
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Create(base.Command):
"""Creates an Artifact Registry attachment in a repository."""
api_version = 'v1'
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To create an attachment for target `projects/myproject/locations/us-central1/packages/mypackage/versions/sha256:123` using a file located in `/path/to/file/sbom.json`:
$ {command} --target=projects/myproject/locations/us-central1/packages/mypackage/versions/sha256:123
--files=/path/to/file/sbom.json
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetRequiredAttachmentFlag().AddToParser(parser)
parser.add_argument(
'--target',
metavar='TARGET',
required=True,
help='Target of the attachment, should be fully qualified version name',
)
parser.add_argument(
'--attachment-type',
metavar='ATTACHMENT_TYPE',
required=True,
help='Type of the attachment',
)
parser.add_argument(
'--attachment-namespace',
metavar='ATTACHMENT_NAMESPACE',
required=False,
help='Namespace of the attachment',
)
parser.add_argument(
'--files',
metavar='FILES',
required=True,
type=arg_parsers.ArgList(),
help='Comma-seperated list of files that are part of this attachment',
)
def Run(self, args):
"""Run the attachment create command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
Result of CreateAttachment operation.
Raises:
InvalidInputValueError: when target and attachment
project/location/repository match.
"""
client = requests.GetClient()
messages = client.MESSAGES_MODULE
attachment_ref = args.CONCEPTS.attachment.Parse()
docker_version = docker_util.ParseDockerVersionStr(args.target)
if docker_version.image.docker_repo.project != attachment_ref.projectsId:
raise ar_exceptions.InvalidInputValueError(
'Attachment {} must be in the same project as target {}.'.format(
attachment_ref.RelativeName(), docker_version.GetVersionName()
)
)
loc = docker_util.RemoveEndpointPrefix(
docker_version.image.docker_repo.location
)
if loc != attachment_ref.locationsId:
raise ar_exceptions.InvalidInputValueError(
'Attachment {} must be in the same location as target {}.'.format(
attachment_ref.RelativeName(), docker_version.GetVersionName()
)
)
if docker_version.image.docker_repo.repo != attachment_ref.repositoriesId:
raise ar_exceptions.InvalidInputValueError(
'Attachment {} must be in the same repository as target {}.'.format(
attachment_ref.RelativeName(), docker_version.GetVersionName()
)
)
file_names = []
for file in args.files:
file_name = self.upload_file(
file, client, messages, attachment_ref.Parent()
)
file_names.append(file_name)
create_request = messages.ArtifactregistryProjectsLocationsRepositoriesAttachmentsCreateRequest(
attachment=messages.Attachment(
target=docker_version.GetVersionName(),
type=args.attachment_type,
attachmentNamespace=args.attachment_namespace,
files=file_names,
),
parent=attachment_ref.Parent().RelativeName(),
attachmentId=attachment_ref.attachmentsId,
)
op_obj = client.projects_locations_repositories_attachments.Create(
create_request
)
op_ref = resources.REGISTRY.ParseRelativeName(
op_obj.name, collection='artifactregistry.projects.locations.operations'
)
# Handle the operation.
result = waiter.WaitFor(
waiter.CloudOperationPollerNoResources(
client.projects_locations_operations
),
op_ref,
'Creating Attachment',
)
return result
def upload_file(self, file_path, client, messages, repo_ref):
# Default chunk size to be consistent for uploading to clouds.
chunksize = scaled_integer.ParseInteger(
properties.VALUES.storage.upload_chunk_size.Get()
)
request = messages.ArtifactregistryProjectsLocationsRepositoriesFilesUploadRequest(
uploadFileRequest=messages.UploadFileRequest(),
parent=repo_ref.RelativeName(),
)
mime_type = util.GetMimetype(file_path)
result_file_name = None
try:
upload = transfer.Upload.FromFile(
file_path, mime_type=mime_type, chunksize=chunksize
)
op_obj = client.projects_locations_repositories_files.Upload(
request, upload=upload
)
op = op_obj.operation
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations'
)
# Handle the operation.
result = waiter.WaitFor(
waiter.CloudOperationPoller(
client.projects_locations_repositories_files,
client.projects_locations_operations,
),
op_ref,
'Uploading file: {}'.format(file_path),
)
result_file_ref = resources.REGISTRY.ParseRelativeName(
result.name,
collection='artifactregistry.projects.locations.repositories.files',
)
result_file_name = result_file_ref.RelativeName()
except waiter.OperationError as e:
if 'already exists' in str(e):
log.info(f'File {file_path} already exists'.format(file_path))
digest = self.computeSha256OfFile(file_path)
repo_relative_name = repo_ref.RelativeName()
result_file_name = f'{repo_relative_name}/files/{digest}'
# Try to update the file with file_name annotation.
if result_file_name:
self.update_file_name_annotation(
result_file_name, os.path.basename(file_path), client, messages
)
return result_file_name
def update_file_name_annotation(
self, file_resource_name, file_name, client, messages
):
update_request = messages.ArtifactregistryProjectsLocationsRepositoriesFilesPatchRequest(
name=file_resource_name,
googleDevtoolsArtifactregistryV1File=messages.GoogleDevtoolsArtifactregistryV1File(
annotations=messages.GoogleDevtoolsArtifactregistryV1File.AnnotationsValue(
additionalProperties=[
messages.GoogleDevtoolsArtifactregistryV1File.AnnotationsValue.AdditionalProperty(
key='artifactregistry.googleapis.com/file_name',
value=file_name,
)
]
)
),
updateMask='annotations',
)
client.projects_locations_repositories_files.Patch(update_request)
def computeSha256OfFile(self, file_path):
sha256 = hashlib.sha256()
data = files.ReadBinaryFileContents(file_path)
sha256.update(data)
return 'sha256:' + sha256.hexdigest()

View File

@@ -0,0 +1,23 @@
- release_tracks: [GA]
help_text:
brief: |
Delete an Artifact Registry attachment.
description: |
Delete an Artifact Registry attachment.
examples: |
To delete an attachment `my-attachment` under the current project, repository, and
location, run:
$ {command} my-attachment
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:attachment
help_text: |
The Artifact Registry attachment to delete.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.attachments

View File

@@ -0,0 +1,25 @@
- release_tracks: [GA]
help_text:
brief: |
Describe an Artifact Registry attachment.
description: |
Describe an Artifact Registry attachment.
This command can fail for the following reasons:
* The specified attachment does not exist.
* The active account does not have permission to view attachments.
examples: |
To describe an attachment named `my-attachment` under the current project, repository, and location, run:
$ {command} my-attachment
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:attachment
help_text: |
The Artifact Registry attachment to describe.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.attachments

View File

@@ -0,0 +1,157 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to download attachments from a repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from googlecloudsdk.api_lib.artifacts import exceptions as ar_exceptions
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import attachment_util
from googlecloudsdk.command_lib.artifacts import download_util
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import requests
from googlecloudsdk.core import log
from six.moves.urllib.parse import unquote
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Download(base.Command):
"""Download an Artifact Registry attachment from a repository."""
api_version = 'v1'
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To download the attachment `my-attachment` to `/path/to/destination/`:
$ {command} my-attachment --destination=/path/to/destination/
To download the attachment `my-attachment` in 8000 byte chunks to `/path/to/destination/`:
$ {command} my-attachment --destination=/path/to/destination/ \
--chunk-size=8000
To download the attachment `my-attachment` using parallel multipart download with 4 threads:
$ {command} my-attachment --destination=/path/to/destination/ \
--parallelism=4
For Docker-format repositories only: to download the attachment stored in the OCI version `projects/my-project/locations/us/repositories/my-repo/packages/my-package/versions/sha256:123` to `/path/to/destination/`:
$ {command} --oci-version-name=projects/my-project/locations/us/repositories/my-repo/packages/my-package/versions/sha256:123 --destination=/path/to/destination/
For Docker-format repositories only: to download the attachment stored in the OCI version with URI `us-docker.pkg.dev/my-project/my-repo/my-package@sha256:123` to `/path/to/destination/`:
$ {command} --oci-version-name=us-docker.pkg.dev/my-project/my-repo/my-package@sha256:123 --destination=/path/to/destination/
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentParser.
"""
flags.GetOptionalAttachmentFlag().AddToParser(parser)
flags.GetChunkSize().AddToParser(parser)
parser.add_argument(
'--oci-version-name',
metavar='OCI_VERSION_NAME',
required=False,
help=(
'For Docker-format repositories only. The version name of the OCI'
' artifact to download.'
),
)
parser.add_argument(
'--destination',
metavar='DESTINATION',
required=True,
help='Path where you want to save the downloaded attachment files.',
)
parser.add_argument(
'--parallelism',
metavar='PARALLELISM',
help=(
'Specifies the number of threads to use for downloading the'
' attachment files in parallel.'
),
)
def Run(self, args):
"""Runs the attachment download command."""
args.destination = os.path.expanduser(args.destination)
if not os.path.exists(args.destination):
raise ar_exceptions.DirectoryNotExistError(
'Destination directory does not exist: ' + args.destination
)
if not os.path.isdir(args.destination):
raise ar_exceptions.PathNotDirectoryError(
'Destination is not a directory: ' + args.destination
)
# Get the attachment.
attachment = attachment_util.GetAttachmentToDownload(args)
self.download_files(args, attachment.files)
def download_files(self, args, files):
default_chunk_size = 3 * 1024 * 1024
chunk_size = args.chunk_size or default_chunk_size
parallelism = args.parallelism or 1
for file in files:
# Extract just the file id.
# ...files/sha256:123 -> 123
# ...files/sha256:pkg%2Fv1.0 -> pkg/v1.0
file_id = os.path.basename(file)
try:
default_file_name = unquote(file_id.rsplit(':', 1)[1])
except IndexError:
default_file_name = file_id
file_name = self.get_file_name(file, default_file_name)
final_path = os.path.join(args.destination, file_name)
download_util.Download(
final_path,
file,
file_name,
False,
int(chunk_size),
parallelism=int(parallelism),
)
log.status.Print(
'Successfully downloaded the file to {}'.format(args.destination)
)
def get_file_name(self, file, default_file_name):
client = requests.GetClient()
messages = requests.GetMessages()
request = (
messages.ArtifactregistryProjectsLocationsRepositoriesFilesGetRequest(
name=file
)
)
resp = client.projects_locations_repositories_files.Get(request)
if resp.annotations is not None:
for e in resp.annotations.additionalProperties:
if e.key == 'artifactregistry.googleapis.com/file_name':
return e.value
return default_file_name

View File

@@ -0,0 +1,38 @@
- release_tracks: [GA]
help_text:
brief: |
List Artifact Registry attachments.
description: |
List all Artifact Registry attachments in the specified repository and project.
To specify the maximum number of attachments to list, use the `--limit` flag.
examples: |
The following command lists a maximum of five attachments:
$ {command} --limit=5
The following command lists attachments with target `projects/my-project/locations/us/repositories/my-repo/packages/my-package/versions/sha256:123`:
$ {command} --target=projects/my-project/locations/us/repositories/my-repo/packages/my-package/versions/sha256:123
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository
help_text: |
Parent repository for the list of attachments.
params:
- arg_name: target
help_text: |
Target for the list of attachments.
- arg_name: attachment-type
hidden: true
help_text: |
Type for the list of attachments.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.attachments
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:AddTargetForAttachments
- googlecloudsdk.command_lib.artifacts.util:AddTypeForAttachments

View File

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry container images and tags."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
class Docker(base.Group):
"""Manage Artifact Registry container images and tags.
To list images under repository `my-repo`, project `my-project`, in
`us-central1`:
$ {command} images list us-central1-docker.pkg.dev/my-project/my-repo
To delete image `busy-box` in `us-west1` and all of its digests and tags:
$ {command} images delete
us-west1-docker.pkg.dev/my-project/my-repository/busy-box
To add tag `my-tag` to image `busy-box` referenced by digest `abcxyz` in
`us-west1`:
$ {command} tags add
us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz
us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
To delete tag `my-tag` from image `busy-box` in `us-west1`:
$ {command} tags delete
us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
To list tags for image `busy-box` in `us-west1`:
$ {command} tags list
us-west1-docker.pkg.dev/my-project/my-repository/busy-box
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry container images."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
class Images(base.Group):
"""Manage Artifact Registry container images.
To list images under the current project, repository, and location:
$ {command} list
To list images under repository `my-repo`, project `my-project`, in
`us-central1`:
$ {command} list us-central1-docker.pkg.dev/my-project/my-repo
To list images with tags, under repository `my-repo`, project `my-project`
across all locations:
$ {command} list docker.pkg.dev/my-project/my-repo --include-tags
To list all images under image `busy-box`, in repository `my-repo`, project
`my-project` across all locations:
$ {command} list docker.pkg.dev/my-project/my-repo/busy-box
To delete image `busy-box` in `us-west1` and all of its digests and tags:
$ {command} delete
us-west1-docker.pkg.dev/my-project/my-repository/busy-box
To delete image digest `abcxyz` under image `busy-box`:
$ {command} delete
us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz
To delete image digest `abcxyz` under image `busy-box` while there're some
other tags associate with the digest:
$ {command} delete
us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz
--delete-tags
To delete an image digest and its only tag `my-tag` under image `busy-box`:
$ {command} delete
us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,99 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Delete an Artifact Registry container image."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import docker_util
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Delete(base.Command):
"""Delete an Artifact Registry container image.
A valid container image has the format of
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE
A valid container image that can be referenced by tag or digest, has the
format of
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE:tag
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE@sha256:digest
This command can fail for the following reasons:
* Trying to delete an image by digest when the image is still tagged. Add
--delete-tags to delete the digest and the tags.
* Trying to delete an image by tag when the image has other tags. Add
--delete-tags to delete all tags.
* A valid repository format was not provided.
* The specified image does not exist.
* The active account does not have permission to delete images.
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To delete image `busy-box` in `us-west1` and all of its digests and tags:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box
To delete image digest `abcxyz` under image `busy-box`:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz
To delete image digest `abcxyz` under image `busy-box` while there're other tags associate with the digest:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz --delete-tags
To delete an image digest and its only tag `my-tag` under image `busy-box`:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
""",
}
@staticmethod
def Args(parser):
base.ASYNC_FLAG.AddToParser(parser)
flags.GetDeleteTagsFlag().AddToParser(parser)
flags.GetImageRequiredArg().AddToParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
DeleteVersion operation.
"""
op = docker_util.DeleteDockerImage(args)
if args.async_:
log.status.Print(
'Delete request issued.\nCheck operation [{}] for status.'.format(
op.name))
else:
log.status.Print('Delete request issued.')
docker_util.WaitForOperation(
op, 'Waiting for operation [{}] to complete'.format(op.name))

View File

@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Delete an Artifact Registry container image."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import docker_util
from googlecloudsdk.command_lib.artifacts import flags
@base.DefaultUniverseOnly
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Describe(base.DescribeCommand):
"""Describe an Artifact Registry container image.
Reference an image by tag or digest using the format:
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE:tag
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE@sha256:digest
This command can fail for the following reasons:
* The repository format is invalid.
* The specified image does not exist.
* The active account does not have permission to run the command
(`roles/artifactregistry.reader`, `roles/containeranalysis.admin` and
`roles/serviceusage.serviceUsageViewer`).
"""
detailed_help = {
"DESCRIPTION": "{description}",
"EXAMPLES": """\
To describe an image digest `abcxyz` under image `busy-box`:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz
To describe an image `busy-box` with tag `my-tag`:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat("yaml")
flags.GetImageRequiredArg().AddToParser(parser)
flags.GetShowAllMetadataFlag().AddToParser(parser)
flags.GetMetadataFilterFlag().AddToParser(parser)
flags.GetShowBuildDetailsFlag().AddToParser(parser)
flags.GetShowPackageVulnerabilityFlag().AddToParser(parser)
flags.GetShowImageBasisFlag().AddToParser(parser)
flags.GetShowDeploymentFlag().AddToParser(parser)
flags.GetShowProvenanceFlag().AddToParser(parser)
flags.GetShowSbomReferencesFlag().AddToParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Raises:
InvalidImageNameError: If the user specified an invalid image name.
Returns:
Some value that we want to have printed later.
"""
return docker_util.DescribeDockerImage(args)

View File

@@ -0,0 +1,23 @@
- release_tracks: [BETA, GA]
help_text:
brief: Get an On-Demand Scanning operation.
description: |
Get an On-Demand Scanning operation.
examples: |
The following command gets an On-Demand Scanning operation.
$ {command} projects/my-project/locations/europe/operations/ddf40882-0d55-4214-a619-c1c36df5040c
command_type: DESCRIBE
request:
collection: ondemandscanning.projects.locations.operations
BETA:
api_version: v1beta1
GA:
api_version: v1
arguments:
resource:
help_text: The scan operation to get.
spec: !REF googlecloudsdk.command_lib.container.images.resources:operation
is_positional: true

View File

@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""List Artifact Registry container images."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import heapq
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import containeranalysis_util as ca_util
from googlecloudsdk.command_lib.artifacts import docker_util
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import format_util
from googlecloudsdk.core import log
DEFAULT_LIST_FORMAT = """\
table(
package:label=IMAGE,
version:label=DIGEST,
createTime.date(tz=LOCAL),
updateTime.date(tz=LOCAL),
metadata.imageSizeBytes:label=SIZE,
{}
)""".format(format_util.CONTAINER_ANALYSIS_METADATA_FORMAT)
EXTENDED_LIST_FORMAT = """\
table(
package:label=IMAGE,
version:label=DIGEST,
tags.list(),
createTime.date(tz=LOCAL),
updateTime.date(tz=LOCAL),
metadata.imageSizeBytes:label=SIZE,
{}
)""".format(format_util.CONTAINER_ANALYSIS_METADATA_FORMAT)
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
@base.UniverseCompatible
class List(base.ListCommand):
"""List Artifact Registry container images.
List all Artifact Registry container images in the specified repository or
image path.
A valid Docker repository has the format of
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID
A valid image has the format of
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE_PATH
To specify the maximum number of images to list, use the --limit flag.
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list images under the current project, repository, and location:
$ {command}
To list images with tags under the current project, repository, and location:
$ {command} --include-tags
To list images under repository `my-repo`, project `my-project`, in `us-central1`:
$ {command} us-central1-docker.pkg.dev/my-project/my-repo
The following command lists a maximum of five images:
$ {command} docker.pkg.dev/my-project/my-repo --limit=5
""",
}
@staticmethod
def Args(parser):
flags.GetIncludeTagsFlag().AddToParser(parser)
base.URI_FLAG.RemoveFromParser(parser)
flags.GetImagePathOptionalArg().AddToParser(parser)
flags.GetShowOccurrencesFlag().AddToParser(parser)
flags.GetShowOccurrencesFromFlag().AddToParser(parser)
flags.GetOccurrenceFilterFlag().AddToParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A list of Docker images.
"""
if _IncludeMetadata(args):
log.status.Print(
"Note: The '--format' flag can be used to change the output format."
)
else:
if args.include_tags:
args.GetDisplayInfo().AddFormat(EXTENDED_LIST_FORMAT)
else:
args.GetDisplayInfo().AddFormat(DEFAULT_LIST_FORMAT)
# Retrieve images.
repo_or_image = docker_util.ParseDockerImagePath(args.IMAGE_PATH)
images = docker_util.GetDockerImages(repo_or_image, args)
# Retrieve containeranalysis metadata for images.
most_recent_images = []
if _IncludeMetadata(args):
if args.show_occurrences_from:
images = heapq.nlargest(
args.show_occurrences_from,
images,
key=lambda img: img['createTime'])
most_recent_images = [
'{}@{}'.format(img['package'], img['version'])
for img in images
]
metadata = ca_util.GetContainerAnalysisMetadataForImages(
repo_or_image, args.occurrence_filter, most_recent_images)
for image in images:
image_path = 'https://{}@{}'.format(image['package'], image['version'])
img_metadata = metadata[image_path].ImagesListView()
image.update(img_metadata)
return images
def _IncludeMetadata(args):
default_occ_filter = (
'kind="BUILD" OR kind="IMAGE" OR kind="DISCOVERY" OR'
' kind="SBOM_REFERENCE"'
)
return args.show_occurrences or (
# Assume the user wants to see occurrences if they explicitly filter.
args.occurrence_filter and args.occurrence_filter != default_occ_filter
)

View File

@@ -0,0 +1,32 @@
- release_tracks: [BETA, GA]
help_text:
brief: List On-Demand Scanning vulnerabilities.
description: |
List On-Demand Scanning vulnerabilities from a completed scan.
examples: |
The following command lists vulnerabilities from a completed On-Demand
Scanning scan.
$ {command} projects/my-project/locations/europe/scans/fff66882-0z55-4333-l619-z1z00df6040c
command_type: LIST
request:
collection: ondemandscanning.projects.locations.scans.vulnerabilities
BETA:
api_version: v1beta1
GA:
api_version: v1
arguments:
resource:
help_text: The scan resource to list vulnerabilites for.
spec: !REF googlecloudsdk.command_lib.container.images.resources:scan
is_positional: true
# This is needed because the request path ends with "/vulnerabilities" and
# doesn't match the Scan resource name, but they both refer to the same
# actual resource object (vulnerability occurrences).
override_resource_collection: true
# These aren't supported at this time.
exclude: ['filter', 'sort-by']

View File

@@ -0,0 +1,331 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Scan a container image using the On-Demand Scanning API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
from googlecloudsdk.api_lib.ondemandscanning import util as api_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import ondemandscanning_util as ods_util
from googlecloudsdk.command_lib.util.anthos import binary_operations
from googlecloudsdk.command_lib.util.apis import arg_utils
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import progress_tracker
from googlecloudsdk.core.updater import local_state
from googlecloudsdk.core.updater import update_manager
from googlecloudsdk.core.util import platforms
import six
# Extract stage messages to constants for convenience.
SCAN_MESSAGE = 'Scanning container image'
EXTRACT_MESSAGE = ('Locally extracting packages and versions from {} '
'container image')
RPC_MESSAGE = 'Remotely initiating analysis of packages and versions'
POLL_MESSAGE = 'Waiting for analysis operation to complete'
# Error messages used to fill in for unknown error cases.
EXTRACTION_KILLED_ERROR_TEMPLATE = (
'Extraction failed: image extraction was either stopped or crashed '
'(possibly due to a lack of available memory) with exit code '
'{exit_code}')
UNKNOWN_EXTRACTION_ERROR_TEMPLATE = (
'Extraction failed: unknown error (exit code: {exit_code})')
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class ScanBeta(base.Command):
"""Perform a vulnerability scan on a container image.
You can scan a container image in a Google Cloud registry (Artifact Registry
or Container Registry), or a local container image.
Reference an image by tag or digest using any of the formats:
Artifact Registry:
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE[:tag]
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE@sha256:digest
Container Registry:
[LOCATION.]gcr.io/PROJECT-ID/REPOSITORY-ID/IMAGE[:tag]
[LOCATION.]gcr.io/PROJECT-ID/REPOSITORY-ID/IMAGE@sha256:digest
Local:
IMAGE[:tag]
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
Start a scan of a container image stored in Artifact Registry:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz --remote
Start a scan of a container image stored in the Container Registry, and perform the analysis in Europe:
$ {command} eu.gcr.io/my-project/my-repository/my-image:latest --remote --location=europe
Start a scan of a container image stored locally, and perform the analysis in Asia:
$ {command} ubuntu:latest --location=asia
"""
}
@staticmethod
def Args(parser):
flags.GetResourceURIArg().AddToParser(parser)
flags.GetRemoteFlag().AddToParser(parser)
flags.GetOnDemandScanningFakeExtractionFlag().AddToParser(parser)
flags.GetOnDemandScanningLocationFlag().AddToParser(parser)
flags.GetAdditionalPackageTypesFlag().AddToParser(parser)
flags.GetExperimentalPackageTypesFlag().AddToParser(parser)
flags.GetSkipPackageTypesFlag().AddToParser(parser)
flags.GetVerboseErrorsFlag().AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
def Run(self, args):
"""Runs local extraction then calls ODS with the results.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
AnalyzePackages operation.
Raises:
UnsupportedOS: when the command is run on a Windows machine.
"""
if platforms.OperatingSystem.IsWindows():
raise ods_util.UnsupportedOS(
'On-Demand Scanning is not supported on Windows')
# Verify that the local-extract component is installed, and prompt the user
# to install it if it's not.
try:
# If the user has access to the gcloud components manager, this will
# prompt the user to install it. If they do not have access, it will
# instead print the command to install it using a package manager.
update_manager.UpdateManager.EnsureInstalledAndRestart(['local-extract'])
except update_manager.MissingRequiredComponentsError:
# Two possibilities with this error:
# 1. The user has access to the gcloud components manager but decided
# against intalling it.
# 2. The user does not have access to the gcloud components manager. A
# message was printed to the user with the command to install the
# component using their package manager (e.g. apt-get).
raise
except local_state.InvalidSDKRootError:
# This happens when gcloud is run locally, but not when distributed.
pass
# Construct the object which invokes the `local-extract` component. This
# might still fail if the binary is run locally.
cmd = Command()
# TODO(b/173619679): Validate RESOURCE_URI argument.
# Dynamically construct the stages based on the --async flag; when
# --async=true, we do not need a separate poll stage.
stages = [
progress_tracker.Stage(
EXTRACT_MESSAGE.format('remote' if args.remote else 'local'),
key='extract'),
progress_tracker.Stage(RPC_MESSAGE, key='rpc')
]
if not args.async_:
stages += [progress_tracker.Stage(POLL_MESSAGE, key='poll')]
messages = self.GetMessages()
with progress_tracker.StagedProgressTracker(
SCAN_MESSAGE, stages=stages) as tracker:
# Stage 1) Extract.
tracker.StartStage('extract')
operation_result = cmd(
resource_uri=args.RESOURCE_URI,
remote=args.remote,
fake_extraction=args.fake_extraction,
additional_package_types=args.additional_package_types,
experimental_package_types=args.experimental_package_types,
skip_package_types=args.skip_package_types,
verbose_errors=args.verbose_errors,
)
if operation_result.exit_code:
# Filter out any log messages on std err and only include any actual
# extraction errors.
extraction_error = None
if operation_result.stderr:
extraction_error = '\n'.join([
line for line in operation_result.stderr.splitlines()
if line.startswith('Extraction failed')
])
if not extraction_error:
if operation_result.exit_code < 0:
extraction_error = EXTRACTION_KILLED_ERROR_TEMPLATE.format(
exit_code=operation_result.exit_code,)
else:
extraction_error = UNKNOWN_EXTRACTION_ERROR_TEMPLATE.format(
exit_code=operation_result.exit_code,)
tracker.FailStage('extract',
ods_util.ExtractionFailedError(extraction_error))
return
# Parse stdout for the JSON-ified PackageData protos.
pkgs = []
for pkg in json.loads(operation_result.stdout):
pkg_data = messages.PackageData(
package=pkg['package'],
version=pkg['version'],
cpeUri=pkg['cpe_uri'],
)
if 'package_type' in pkg:
pkg_data.packageType = arg_utils.ChoiceToEnum(
pkg['package_type'],
messages.PackageData.PackageTypeValueValuesEnum)
if 'hash_digest' in pkg:
pkg_data.hashDigest = pkg['hash_digest']
pkgs += [pkg_data]
tracker.CompleteStage('extract')
# Stage 2) Make the RPC to the On-Demand Scanning API.
tracker.StartStage('rpc')
op = self.AnalyzePackages(args, pkgs)
tracker.CompleteStage('rpc')
# Stage 3) Poll the operation if requested.
response = None
if not args.async_:
tracker.StartStage('poll')
tracker.UpdateStage('poll', '[{}]'.format(op.name))
response = self.WaitForOperation(op)
tracker.CompleteStage('poll')
if args.async_:
log.status.Print('Check operation [{}] for status.'.format(op.name))
return op
return response
def AnalyzePackages(self, args, pkgs):
return api_util.AnalyzePackagesBeta(
properties.VALUES.core.project.Get(required=True),
args.location,
args.RESOURCE_URI,
pkgs)
def GetMessages(self):
return api_util.GetMessages('v1beta1')
def WaitForOperation(self, op):
return ods_util.WaitForOperation(op, 'v1beta1')
@base.ReleaseTracks(base.ReleaseTrack.GA)
class ScanGA(ScanBeta):
"""Perform a vulnerability scan on a container image.
You can scan a container image in a Google Cloud registry (Artifact Registry
or Container Registry), or a local container image.
Reference an image by tag or digest using any of the formats:
Artifact Registry:
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE[:tag]
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE@sha256:digest
Container Registry:
[LOCATION.]gcr.io/PROJECT-ID/REPOSITORY-ID/IMAGE[:tag]
[LOCATION.]gcr.io/PROJECT-ID/REPOSITORY-ID/IMAGE@sha256:digest
Local:
IMAGE[:tag]
"""
def AnalyzePackages(self, args, pkgs):
return api_util.AnalyzePackagesGA(
properties.VALUES.core.project.Get(required=True),
args.location,
args.RESOURCE_URI,
pkgs)
def GetMessages(self):
return api_util.GetMessages('v1')
def WaitForOperation(self, op):
return ods_util.WaitForOperation(op, 'v1')
class Command(binary_operations.BinaryBackedOperation):
"""Wrapper for call to the Go binary."""
def __init__(self, **kwargs):
super(Command, self).__init__(binary='local-extract', **kwargs)
def _ParseArgsForCommand(
self,
resource_uri,
remote,
fake_extraction,
additional_package_types,
experimental_package_types,
skip_package_types,
verbose_errors,
**kwargs
):
args = [
'--resource_uri=' + resource_uri,
'--remote=' + six.text_type(remote),
'--provide_fake_results=' + six.text_type(fake_extraction),
# Due to backwards compatibility issues between the gcloud command and
# the local-extract binary, provide a list of all flags to --undefok
# which were introduced after the first launch. In this way, new
# versions of the command can invoke old versions of the binary.
'--undefok='
+ ','.join([
'additional_package_types',
'skip_package_types',
'verbose_errors',
'use_scalibr',
]),
]
package_types = []
if additional_package_types:
package_types += additional_package_types
if experimental_package_types:
package_types += experimental_package_types
if package_types:
args.append('--additional_package_types=' +
six.text_type(','.join(package_types)))
if skip_package_types:
args.append(
'--skip_package_types=' + six.text_type(','.join(skip_package_types))
)
if verbose_errors:
args.append('--verbose_errors=' + six.text_type(verbose_errors))
args.append('--use_scalibr')
return args

View File

@@ -0,0 +1,51 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry container tags."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
class Tags(base.Group):
# pylint: disable=line-too-long
"""Manage Artifact Registry container image tags.
To add tag `my-tag` to image `busy-box` referenced by digest `abcxyz` in
`us-west1`:
$ {command} add us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
To add tag `my-tag` to image `busy-box` referenced by tag `latest` in
`us-west1`:
$ {command} add us-west1-docker.pkg.dev/my-project/my-repository/busy-box:latest us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
To delete tag `my-tag` from image `busy-box` in `us-west1`:
$ {command} delete us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
To list all tags in repository `my-repository` in `us-west1`:
$ {command} list us-west1-docker.pkg.dev/my-project/my-repository
To list tags for image `busy-box` in `us-west1`:
$ {command} list us-west1-docker.pkg.dev/my-project/my-repository/busy-box
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Add a tag to a container image in Artifact Registry."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import docker_util
from googlecloudsdk.command_lib.artifacts import flags
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Add(base.Command):
"""Add a tag to a container image in Artifact Registry.
Create or update a tag for a container image in Artifact Registry.
A valid Docker tag has the format of
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE:tag
A valid container image that can be referenced by tag or digest, has the
format of
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE:tag
LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE@sha256:digest
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To add tag `my-tag` to image `busy-box` referenced by digest `abcxyz` in `us-west1`:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
To add tag `my-tag` to image `busy-box` referenced by tag `latest` in `us-west1`:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box:latest us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
""",
}
@staticmethod
def Args(parser):
flags.GetDockerImageRequiredArg().AddToParser(parser)
flags.GetTagRequiredArg().AddToParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
"""
docker_util.AddDockerTag(args)

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Delete a tag from a container image in Artifact Registry."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import docker_util
from googlecloudsdk.command_lib.artifacts import flags
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Delete(base.Command):
"""Delete a tag from a container image in Artifact Registry.
A valid Docker tag has the format of
[<location>-]docker.pkg.dev/PROJECT_ID/REPOSITORY-ID/IMAGE_PATH:tag
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To delete tag `my-tag` from image `busy-box` in `us-west1`:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box:my-tag
""",
}
@staticmethod
def Args(parser):
flags.GetTagRequiredArg().AddToParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
"""
docker_util.DeleteDockerTag(args)

View File

@@ -0,0 +1,89 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""List all tags associated with a container image in Artifact Registry."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import docker_util
from googlecloudsdk.command_lib.artifacts import flags
TAG_LIST_FORMAT = """\
table(
tag.basename(),
image,
version.basename():label=DIGEST
)"""
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List all tags associated with a container image in Artifact Registry.
A valid Docker top layer image has the format of
[<location>-]docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE_PATH
A valid container image can be referenced by tag or digest, has the format of
[<location>-]docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE_PATH:tag
[<location>-]docker.pkg.dev/PROJECT-ID/REPOSITORY-ID/IMAGE_PATH@sha256:digest
To specify the maximum number of repositories to list, use the --limit flag.
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list all tags under the current project, repository, and location:
$ {command}
To list all tags under the `my-project`, `my-repository`, across all locations:
$ {command} docker.pkg.dev/my-project/my-repository
To list all tags in repository `my-repository` in `us-west1`:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository
To list tags for image `busy-box` in `us-west1`:
$ {command} us-west1-docker.pkg.dev/my-project/my-repository/busy-box
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat(TAG_LIST_FORMAT)
base.URI_FLAG.RemoveFromParser(parser)
flags.GetImagePathOptionalArg().AddToParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A list of Docker tags, sorted by Docker image name.
"""
return docker_util.ListDockerTags(args)

View File

@@ -0,0 +1,44 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Container Registry to Artifact Registry upgrade."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Upgrade(base.Group):
r"""Commands to support Container Registry to Artifact Registry upgrade.
To print an equivalent Artifact Registry IAM policy for 'gcr.io/my-project':
$ {command} print-iam-policy gcr.io --project=my-project
To migrate a project from Container Registry to Artifact Registry using gcr.io
repos:
$ {command} migrate --projects=my-project
To migrate a project from Container Registry to Artifact Registry using
pkg.dev repos:
$ {command} migrate --from-gcr-io=gcr.io/from-project \
--to-pkg-dev=to-project/to-repo
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,83 @@
- release_tracks: [GA]
help_text:
brief: |
Migrate projects from Container Registry to Artifact Registry
description: |
Migrate projects from Container Registry to Artifact Registry
examples: |
To migrate a project `my-project` using gcr.io repositories:
$ {command} --projects=my-project
To migrate several projects using gcr.io repositories:
$ {command} --projects=my-project1,my-project2,my-project3
To migrate a project using pkg.dev repositories:
$ {command} --from-gcr=gcr.io/project1 --to-pkg-dev=project2/repo_name
arguments:
params:
- arg_name: projects
help_text: >
Comma seperated list of Container Registry projects to migrate to Artifact Registry gcr.io repositories.
- arg_name: recent-images
metavar: NUM_DAYS
type: int
help_text: Only copy images pulled or pushed in the last NUM_DAYS days. NUM_DAYS must be between 30 and 90 inclusive.
- arg_name: from-gcr
metavar: GCR_HOST/PROJECT_ID
help_text: >
Container Registry host + project to copy from. This flag is only used when migrating to pkg.dev repos. Example: gcr.io/my-project
- arg_name: to-pkg-dev
metavar: PROJECT_ID/REPOSITORY_ID
help_text: >
Artifact Registry pkg.dev project ID and repository ID to copy to. Example: my-project/my-repo
- arg_name: copy-only
type: bool
help_text: "Only perform image copying"
- arg_name: canary-reads
metavar: PERCENT
type: int
help_text: "Send only a percent of reads to Artifact Registry. The rest of reads and all writes are sent to Container Registry."
- arg_name: max-threads
type: int
default: 8
help_text: "Max number of images to copy simultaneously. Consider quota usage when increasing this"
- arg_name: skip-iam-update
type: bool
help_text: >
Migrate without changing iam-policy. Users without Artifact Registry permissions will not have access to migrated images.
- arg_name: last-uploaded-versions
metavar: N
type: int
help_text: >
Only copy the N most recently uploaded versions of each image. More than N images may be copied if new images are uploaded during migration.
- arg_name: output-iam-policy-dir
metavar: DIRECTORY
help_text: >
Outputs Artifact Registry-equivalent bindings to this directory during IAM update step and then exits the tool. After any neccesary modifications are made, the tool can be rerun with --input-iam-policy-dir to continue migration with the generated bindings.
- arg_name: input-iam-policy-dir
metavar: DIRECTORY
help_text: >
During the IAM update step, the tool applies all iam policies in the given directory.
- arg_name: pkg-dev-location
help_text: >
The location of the pkg-dev repository you are migrating to. If not specified, migration is always done to the same multi-region as GCR. Setting this flag can cause cross-regional copying and lead to billing charges.
- arg_name: skip-pre-copy
type: bool
help_text: >
Skip the initial copy of recent images before enabling redirection.
- arg_name: use-analyze-iam
type: bool
default: true
help_text: >
Use analyzeIAMPolicy to get IAM bindings. If false, tooling iterates through IAM bindings itself, which is slower, but doesn't require anlayzeIAMPolicy quota.
request:
api_version: v1
disable_resource_check: true
collection: artifactregistry.projects
method: updateProjectSettings
issue_request_hook: googlecloudsdk.command_lib.artifacts.util:MigrateToArtifactRegistry

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Print an Artifact Registry IAM policy for Container Registry to Artifact Registry upgrade."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import upgrade_util
from googlecloudsdk.command_lib.artifacts import util
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class PrintIamPolicy(base.Command):
"""Print an Artifact Registry IAM policy for Container Registry to Artifact Registry upgrade.
Print an Artifact Registry IAM policy that is equivalent to the IAM policy
applied to the storage bucket for the specified Container Registry hostname.
Apply the returned policy to the Artifact Registry repository that will
replace the specified host. If the project has an organization, this command
analyzes IAM policies at the organization level. Otherwise, this command
analyzes IAM policies at the project level. See required permissions at
https://cloud.google.com/policy-intelligence/docs/analyze-iam-policies#required-permissions.
"""
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To print an equivalent Artifact Registry IAM policy for 'gcr.io/my-project':
$ {command} upgrade print-iam-policy gcr.io --project=my-project
""",
}
@staticmethod
def Args(parser):
flags.GetGCRDomainArg().AddToParser(parser)
def Run(self, args):
"""Runs the command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
An iam.Policy.
"""
domain = args.DOMAIN
project = util.GetProject(args)
return upgrade_util.iam_policy(domain, project)

View File

@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry files."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.UniverseCompatible
class Files(base.Group):
"""Manage Artifact Registry files.
## EXAMPLES
To list all files in the current project and `artifacts/repository` and
`artifacts/location` properties are set,
run:
$ {command} list
To list files under repository my-repo in the current project and location,
run:
$ {command} list --repository=my-repo
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,33 @@
- release_tracks: [GA]
universe_compatible: false
help_text:
brief: |
Delete an Artifact Registry file.
description: |
Delete an Artifact Registry file.
This command can fail for the following reasons:
* The specified file does not exist.
* The active account does not have permission to delete files.
* The repository is not a Generic repository.
examples: |
To delete a file named `pkg:1.0.0:file1.txt` under the current project, repository, and location, run:
$ {command} pkg:v0.0.1:file1.txt
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:file
help_text: |
The Artifact Registry file to delete.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.files
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.file_util:EscapeFileNameHook
async:
collection: artifactregistry.projects.locations.operations

View File

@@ -0,0 +1,33 @@
- release_tracks: [GA]
help_text:
brief: |
Describe an Artifact Registry file.
description: |
Describe an Artifact Registry file.
The file hashes are displayed as hex strings.
This command can fail for the following reasons:
* The specified file does not exist.
* The active account does not have permission to view file.
examples: |
To describe a file named `my-file.txt` under the current project, repository, and location, run:
$ {command} my-file.txt
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:file
help_text: |
The Artifact Registry file to describe.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.files
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.file_util:EscapeFileNameHook
response:
modify_response_hooks:
- googlecloudsdk.command_lib.artifacts.file_util:ConvertFileHashes

View File

@@ -0,0 +1,127 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Download Artifact Registry files."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from googlecloudsdk.api_lib.artifacts import exceptions as ar_exceptions
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import download_util
from googlecloudsdk.command_lib.artifacts import file_util
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.core import log
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Download(base.Command):
"""Download an Artifact Registry file.
Downloads an Artifact Registry file based on file name.
"""
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To download a file named `myfile` in project `my-project` under repository `my-repo` in `us-central1` to the local path `~/`:
$ {command} --location=us-central1 --project=my-project --repository=my-repo --destination=~/ myfile
To download a file named `myfile` in project `my-project` under repository `my-repo` in `us-central1` to the local path `~/` using parallel multipart download with 4 threads:
$ {command} --location=us-central1 --project=my-project --repository=my-repo --destination=~/ --parallelism=4 myfile
To download a file named `myfile` in project `my-project` under repository `my-repo` in `us-central1` to the local path `~/` with file overwriting enabled:
$ {command} --location=us-central1 --project=my-project --repository=my-repo --destination=~/ myfile --allow-overwrite
""",
}
@staticmethod
def Args(parser):
flags.GetRequiredFileFlag().AddToParser(parser)
flags.GetAllowOverwriteFlag().AddToParser(parser)
parser.add_argument(
'--destination',
metavar='DESTINATION',
required=True,
help="""\
The path where you want to download the file.""",
)
parser.add_argument(
'--local-filename',
metavar='LOCAL_FILENAME',
help=(
'If specified, the name of the downloaded file on the local system'
' is set to the value you use for LOCAL_FILENAME. Otherwise the'
' name of the downloaded file is based on the file name in the'
' registry.'
),
)
parser.add_argument(
'--parallelism',
metavar='PARALLELISM',
help=(
'Specifies the number of threads to use for downloading the file in'
' parallel.'
),
)
def Run(self, args):
"""Run the file download command."""
# Escape slashes in the filesId.
file_escaped = file_util.EscapeFileName(args.CONCEPTS.file.Parse())
filename = (
args.local_filename
if args.local_filename
else self.os_friendly_filename(file_escaped.filesId)
)
parallelism = args.parallelism or 1
final_path = os.path.join(args.destination, filename)
final_path = os.path.expanduser(final_path)
dest_dir = os.path.dirname(final_path)
if not os.path.exists(dest_dir):
raise ar_exceptions.DirectoryNotExistError(
'Destination directory does not exist: ' + dest_dir
)
if not os.path.isdir(dest_dir):
raise ar_exceptions.PathNotDirectoryError(
'Destination is not a directory: ' + dest_dir
)
default_chunk_size = 3 * 1024 * 1024
download_util.Download(
final_path,
file_escaped.RelativeName(),
filename,
args.allow_overwrite,
default_chunk_size,
int(parallelism),
)
log.status.Print('Successfully downloaded the file to ' + args.destination)
def os_friendly_filename(self, file_id):
filename = file_id.replace(':', '%3A')
filename = filename.replace('\\', '%5C')
filename = filename.replace('*', '%3F')
filename = filename.replace('?', '%22')
filename = filename.replace('<', '%3C')
filename = filename.replace('>', '%2E')
filename = filename.replace('|', '%7C')
return filename

View File

@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""List Artifact Registry files."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import file_util
from googlecloudsdk.command_lib.artifacts import flags
DEFAULT_LIST_FORMAT = """\
table(
name.basename().sub("%2F", "/").sub("%2B", "+").sub("%5E", "^"):label=FILE,
createTime.date(tz=LOCAL),
updateTime.date(tz=LOCAL),
sizeBytes.size(zero='0',precision=3,units_out=M):label="SIZE (MB)",
owner:label=OWNER,
annotations
)"""
@base.DefaultUniverseOnly
class List(base.ListCommand):
"""List Artifact Registry files.
List all Artifact Registry files in the specified repository and location.
To specify the maximum number of files to list, use the --limit flag.
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list files in the current project under repository `my-repo` in `us`:
$ {command} --repository=my-repo --location=us
The following command lists a maximum of five files:
$ {command} --repository=my-repo --location=us --limit=5
To list files in the current project under repository `my-repo` in `us` owned by package `my-package`:
$ {command} --repository=my-repo --location=us --package=my-package
To list files in the current project under repository `my-repo` in `us` owned by package `my-package` and version `1.0.0`:
$ {command} --repository=my-repo --location=us --package=my-package --version=1.0.0
To list files in the current project under repository `my-repo` in `us` owned by package `my-package` and tag name `my-tag`:
$ {command} --repository=my-repo --location=us --package=my-package --tag=my-tag
To list files with name as `my-file`:
$ {command} --filter='name="projects/my-project/locations/us/repositories/my-repo/files/my-file"'
To list files with a given partial name, use `*` to match any character in name:
$ {command} --filter='name="projects/my-project/locations/us/repositories/my-repo/files/*file"'
$ {command} --filter='name="projects/my-project/locations/us/repositories/my-repo/files/my-*"'
To list files that have annotations:
$ {command} --filter=annotations:*
To list files with annotations pair as [annotation_key: annotation_value]
$ {command} --filter='annotations.annotation_key:annotation_value'
To list files with annotations containing key as `my_key`:
$ {command} --filter='annotations.my_key'
If the key or value contains special characters, such as `my.key` and `my.value`, backtick("`") is required:
$ {command} --filter='annotations.`my.key`'
$ {command} --filter='annotations.`my.key`:`my.value`'
To list files with given partial annotation key or value, use `*` to match any character:
$ {command} --filter='annotations.*key:`*.value`'
To list files in the current project under repository `my-repo` in `us`, ordering by create_time:
$ {command} --repository=my-repo --location=us --sort-by=create_time
To list files in the current project under repository `my-repo` in `us`, ordering by update_time reversely:
$ {command} --repository=my-repo --location=us --sort-by=~update_time
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat(DEFAULT_LIST_FORMAT)
base.URI_FLAG.RemoveFromParser(parser)
flags.GetRepoFlag().AddToParser(parser)
parser.add_argument(
'--package',
required=False,
help='List all files in a specified artifact, such as a container image or a language package. If you do not use --tag or --version in the command, the command lists files in all versions of the artifact.'
)
parser.add_argument(
'--version',
required=False,
help='List all files in the specified artifact version. Use the --package flag to specify the artifact.'
)
parser.add_argument(
'--tag',
required=False,
help='List all files in the artifact version with the specified tag. This flag only works with formats that use tags, such as container images. Use the --package flag to specify the artifact.'
)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A list of files.
"""
return file_util.ListFiles(args)

View File

@@ -0,0 +1,40 @@
- release_tracks: [GA]
help_text:
brief: |
Update annotations on an Artifact Registry file.
description: |
Update annotations on an Artifact Registry file.
examples: |
To update annotations on a file named `my-file.txt` when the project ID, repository and location defaults are set, run the following command:
CAUTION: This command will overwrite any existing annotations on the file.
$ {command} my-file.txt --annotations=key1=value1,key2=value2
To clear all annotations on the file run the following command:
$ {command} my-file.txt --annotations={}
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:file
help_text: |
The Artifact Registry file to update.
params:
- arg_name: annotations
metavar: KEY=VALUE
api_field: googleDevtoolsArtifactregistryV1File.annotations
spec:
- api_field: key
- api_field: value
help_text: |
List of annotations in the format of KEY=VALUE pairs to add, update, or remove.
Duplicate keys will be overwritten. For more details on annotations,
see https://google.aip.dev/148#annotations.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.files
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.file_util:EscapeFileNameHook

View File

@@ -0,0 +1,177 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Upload files to Artifact Registry."""
import os
from apitools.base.py import transfer
from googlecloudsdk.api_lib.artifacts import exceptions as ar_exceptions
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import util
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.util import scaled_integer
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
class Upload(base.Command):
"""Uploads files to Artifact Registry."""
api_version = 'v1'
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To upload a file located in /path/to/file/ to a repository in "us-central1":
$ {command} --location=us-central1 --project=myproject --repository=myrepo \
--file=myfile --source=/path/to/file/
To upload all files located in directory /path/to/file/ to a repository in "us-central1":
$ {command} --location=us-central1 --project=myproject --repository=myrepo \
--source-directory=/path/to/file/
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetRequiredRepoFlag().AddToParser(parser)
flags.GetSkipExistingFlag().AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
group = parser.add_group(mutex=True, required=True)
parser.add_argument(
'--file',
metavar='FILE',
required=False,
help=(
'The name under which the file will be uploaded. '
'If not specified, the name of the local file is used.'
),
)
group.add_argument(
'--source',
metavar='SOURCE',
help='The path to the file you are uploading.',
)
group.add_argument(
'--source-directory',
metavar='SOURCE_DIRECTORY',
help='The directory you are uploading.',
)
def Run(self, args):
"""Run the file upload command."""
client = apis.GetClientInstance('artifactregistry', self.api_version)
messages = client.MESSAGES_MODULE
source_dir = args.source_directory
source_file = args.source
if source_file and args.skip_existing:
raise ar_exceptions.InvalidInputValueError(
'Skip existing is not supported for single file uploads.'
)
if source_dir and args.async_:
raise ar_exceptions.InvalidInputValueError(
'Asynchronous uploads not supported for directories.'
)
if source_dir and args.file:
raise ar_exceptions.InvalidInputValueError(
'File name is not supported for directory uploads.'
)
# Uploading a single file
if source_file:
return self.uploadArtifact(args, source_file, client, messages)
# Uploading a directory
elif source_dir:
# If source_dir was specified, expand, normalize and traverse
# through the directory sending one upload request per file found.
args.source_directory = os.path.normpath(os.path.expanduser(source_dir))
if not os.path.isdir(args.source_directory):
raise ar_exceptions.InvalidInputValueError(
'Specified path is not an existing directory.'
)
log.status.Print('Uploading directory: {}'.format(source_dir))
for path, _, files in os.walk(args.source_directory):
for file in files:
try:
self.uploadArtifact(
args, (os.path.join(path, file)), client, messages
)
except waiter.OperationError as e:
if args.skip_existing and 'already exists' in str(e):
log.warning('File with the same ID already exists.')
continue
raise
def uploadArtifact(self, args, file_path, client, messages):
# Default chunk size to be consistent for uploading to clouds.
chunksize = scaled_integer.ParseInteger(
properties.VALUES.storage.upload_chunk_size.Get()
)
repo_ref = args.CONCEPTS.repository.Parse()
# If file name was not specified in the arguments, take the last portion
# of the file path as the file name.
# ie. file path is folder1/folder2/file.txt, the file name is file.txt
file_name = os.path.basename(file_path)
if args.file:
file_name = args.file
request = messages.ArtifactregistryProjectsLocationsRepositoriesFilesUploadRequest(
uploadFileRequest=messages.UploadFileRequest(fileId=file_name),
parent=repo_ref.RelativeName(),
)
mime_type = util.GetMimetype(file_path)
upload = transfer.Upload.FromFile(
file_path, mime_type=mime_type, chunksize=chunksize
)
op_obj = client.projects_locations_repositories_files.Upload(
request, upload=upload
)
op = op_obj.operation
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations'
)
# Handle the operation.
if args.async_:
return op_ref
else:
result = waiter.WaitFor(
waiter.CloudOperationPollerNoResources(
client.projects_locations_operations
),
op_ref,
'Uploading file: {}'.format(file_name),
)
return result

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry packages."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Generic(base.Group):
"""Manage Artifact Registry generic artifacts."""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,171 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to download generic artifacts from a repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from googlecloudsdk.api_lib.artifacts import exceptions as ar_exceptions
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import download_util
from googlecloudsdk.command_lib.artifacts import file_util
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.core import log
@base.DefaultUniverseOnly
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Download(base.Command):
"""Download a generic artifact from a generic artifact repository."""
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To download version v0.1.0 of myfile.txt located in a repository in "us-central1" to /path/to/destination/:
$ {command} --location=us-central1 --project=myproject --repository=myrepo \
--package=mypackage --version=v0.1.0 --destination=/path/to/destination/ \
--name=myfile.txt
To download version v0.1.0 of myfile.txt located in a repository in "us-central1" to /path/to/destination/ using parallel multipart download with 4 threads:
$ {command} --location=us-central1 --project=myproject --repository=myrepo \
--package=mypackage --version=v0.1.0 --destination=/path/to/destination/ \
--name=myfile.txt --parallelism=4
To download version v0.1.0 of myfile.txt in 8000 byte chunks located in a repository in "us-central1" to /path/to/destination/:
$ {command} --location=us-central1 --project=myproject --repository=myrepo \
--package=mypackage --version=v0.1.0 --destination=/path/to/destination/ \
--name=myfile.txt --chunk-size=8000
To download all files of version v0.1.0 and package mypackage located in a repository in "us-central1" to /path/to/destination/
while maintaining the folder hierarchy:
$ {command} --location=us-central1 --project=myproject --repository=myrepo \
--package=mypackage --version=v0.1.0 --destination=/path/to/destination/
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentParser.
"""
flags.GetRequiredRepoFlag().AddToParser(parser)
flags.GetChunkSize().AddToParser(parser)
parser.add_argument(
'--destination',
metavar='DESTINATION',
required=True,
help='The path where you want to save the downloaded file.',
)
parser.add_argument(
'--package',
metavar='ARTIFACT',
required=True,
help='The artifact to download.',
)
parser.add_argument(
'--version',
metavar='VERSION',
required=True,
help='The version of the artifact to download.',
)
parser.add_argument(
'--name',
metavar='NAME',
help='If specified, the file name within the artifact to download.'
)
parser.add_argument(
'--parallelism',
metavar='PARALLELISM',
help=(
'Specifies the number of threads to use for downloading the file in'
' parallel.'
),
)
def Run(self, args):
"""Run the generic artifact download command."""
repo_ref = args.CONCEPTS.repository.Parse()
args.destination = os.path.expanduser(args.destination)
if not os.path.exists(args.destination):
raise ar_exceptions.DirectoryNotExistError(
'Destination directory does not exist: ' + args.destination
)
if not os.path.isdir(args.destination):
raise ar_exceptions.PathNotDirectoryError(
'Destination is not a directory: ' + args.destination
)
# Get the file name when given a file path
if args.name:
file_name = os.path.basename(args.name)
file_id = '{}:{}:{}'.format(args.package, args.version, args.name)
self.downloadGenericArtifact(args, repo_ref, file_id, file_name)
else:
# file name was not specified, download all files in the given version.
list_files = file_util.ListGenericFiles(args)
if not list_files:
raise ar_exceptions.ArtifactRegistryError(
'No files found for package: {} version: {}'.format(
args.package, args.version
)
)
self.batchDownloadFiles(args, repo_ref, list_files)
def downloadGenericArtifact(self, args, repo_ref, file_id, file_name):
final_path = os.path.join(args.destination, file_name)
file_escaped = file_util.EscapeFileNameFromIDs(
repo_ref.projectsId,
repo_ref.locationsId,
repo_ref.repositoriesId,
file_id,
)
default_chunk_size = 3 * 1024 * 1024
chunk_size = args.chunk_size or default_chunk_size
parallelism = args.parallelism or 1
download_util.Download(
final_path,
file_escaped.RelativeName(),
file_name,
False,
int(chunk_size),
int(parallelism),
)
log.status.Print(
'Successfully downloaded the file to {}'.format(args.destination)
)
def batchDownloadFiles(self, args, repo_ref, list_files):
for files in list_files:
# Extract just the file id.
file_id = os.path.basename(files.name)
file_name = file_id.rsplit(':', 1)[1].replace('%2F', '/')
# Create the directory structure.
if '/' in file_name:
d = os.path.dirname(file_name)
os.makedirs(os.path.join(args.destination, d), exist_ok=True)
self.downloadGenericArtifact(args, repo_ref, file_id, file_name)

View File

@@ -0,0 +1,201 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to upload Generic artifacts to a repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
from apitools.base.py import transfer
from googlecloudsdk.api_lib.artifacts import exceptions as ar_exceptions
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import util
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.util import scaled_integer
@base.DefaultUniverseOnly
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Upload(base.Command):
"""Uploads an artifact to a generic repository."""
api_version = 'v1'
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To upload version v0.1.0 of a generic artifact located in /path/to/file/ to a repository in "us-central1":
$ {command} --location=us-central1 --project=myproject --repository=myrepo \
--package=mypackage --version=v0.1.0 --source=/path/to/file/
To upload version v0.1.0 of a generic artifact located in /path/to/file/ to a repository in "us-central1" within a folder structure:
$ {command} --location=us-central1 --project=myproject --repository=myrepo \
--package=mypackage --version=v0.1.0 --source=/path/to/file/ --destination-path=folder/file
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetRequiredRepoFlag().AddToParser(parser)
flags.GetSkipExistingFlag().AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
group = parser.add_group(mutex=True, required=True)
parser.add_argument(
'--package',
metavar='PACKAGE',
required=True,
help='The package to upload.')
parser.add_argument(
'--version',
metavar='VERSION',
required=True,
help=(
'The version of the package. '
'You cannot overwrite an existing version in the repository.'
),
)
parser.add_argument(
'--destination-path',
metavar='DESTINATION_PATH',
required=False,
help=(
'Use to specify the path to upload a generic '
'artifact to within a folder structure.'
),
)
group.add_argument(
'--source',
metavar='SOURCE',
help='The path to the file you are uploading.')
group.add_argument(
'--source-directory',
metavar='SOURCE_DIRECTORY',
help='The directory you are uploading.')
def Run(self, args):
"""Run the generic artifact upload command."""
client = apis.GetClientInstance('artifactregistry', self.api_version)
messages = client.MESSAGES_MODULE
source_dir = args.source_directory
source_file = args.source
if source_dir and args.async_:
raise ar_exceptions.InvalidInputValueError(
'Asynchronous uploads not supported for directories.'
)
if source_file and args.skip_existing:
raise ar_exceptions.InvalidInputValueError(
'Skip existing is not supported for single file uploads.'
)
# Uploading a single file
if source_file:
return self.uploadArtifact(args, source_file, client, messages)
# Uploading a directory
elif source_dir:
# If source_dir was specified, expand, normalize and traverse
# through the directory sending one upload request per file found,
# preserving the folder structure.
args.source_directory = os.path.normpath(os.path.expanduser(source_dir))
if not os.path.isdir(args.source_directory):
raise ar_exceptions.InvalidInputValueError(
'Specified path is not an existing directory.'
)
log.status.Print('Uploading directory: {}'.format(source_dir))
for path, _, files in os.walk(args.source_directory):
for file in files:
try:
self.uploadArtifact(
args, (os.path.join(path, file)), client, messages
)
except waiter.OperationError as e:
if args.skip_existing and 'already exists' in str(e):
log.warning(
'File with the same package and version already exists.'
)
continue
raise
def uploadArtifact(self, args, file_path, client, messages):
# Default chunk size to be consistent for uploading to clouds.
chunksize = scaled_integer.ParseInteger(
properties.VALUES.storage.upload_chunk_size.Get()
)
repo_ref = args.CONCEPTS.repository.Parse()
# If destination_path was not specified,
# take the last portion of the file path as the the file name.
# ie. file path is folder1/folder2/file.txt, the file name is file.txt
if args.source:
file_name = os.path.basename(file_path)
if args.destination_path:
path = os.path.normpath(args.destination_path)
file_name = os.path.join(path, os.path.basename(file_path))
else:
# ie: "/usr/Desktop/test_generic_folder"
# remove the prefix from the full file path
# /usr/Desktop/test_generic_folder/test.txt
# to get 'test.txt'
file_name = file_path[len(args.source_directory)+1:]
if args.destination_path:
path = os.path.normpath(args.destination_path)
file_name = os.path.join(path, file_name)
# Windows uses "\" as its path separator, replace it with "/" to standardize
# all file resource names.
file_name = file_name.replace(os.sep, '/')
request = messages.ArtifactregistryProjectsLocationsRepositoriesGenericArtifactsUploadRequest(
uploadGenericArtifactRequest=messages.UploadGenericArtifactRequest(
packageId=args.package,
versionId=args.version,
filename=file_name),
parent=repo_ref.RelativeName())
mime_type = util.GetMimetype(file_path)
upload = transfer.Upload.FromFile(
file_path, mime_type=mime_type, chunksize=chunksize)
op_obj = client.projects_locations_repositories_genericArtifacts.Upload(
request, upload=upload)
op = op_obj.operation
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations')
# Handle the operation.
if args.async_:
return op_ref
else:
result = waiter.WaitFor(
waiter.CloudOperationPollerNoResources(
client.projects_locations_operations), op_ref,
'Uploading file: {}'.format(file_name))
return result

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry packages."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.DefaultUniverseOnly
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Go(base.Group):
"""Manage Artifact Registry Go modules."""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements commands for the GOAUTH environment variable."""
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import endpoint_util
from googlecloudsdk.command_lib.artifacts import go_util
from googlecloudsdk.core import log
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Auth(base.Command):
"""Print authentication commands for the GOAUTH environment variable.
This command implements the GOAUTH credential provider command introduced in
Go 1.24. For more details about the GOAUTH environment variable, see
https://pkg.go.dev/cmd/go#hdr-GOAUTH_environment_variable. When you configure
the GOAUTH environment variable for repositories, Artifact Registry looks for
credentials in the following order:
* Application Default Credentials (ADC)
* Credentials provided by the Google Cloud CLI, including user credentials
from the command `gcloud auth application-default login`.
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To configure the GOAUTH environment variable for repositories in `us-central1` and use your credentials:
$ export GOAUTH="{command} --location=us-central1"
To configure the GOAUTH environment variable for repositories in `us-central1` and use the credentials from a service account:
$ export GOAUTH="{command} --location=us-central1 --json-key=path/to/key.json"
""",
}
@staticmethod
def Args(parser):
"""Set up arguements for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
# Go may or may not pass the URL to authenticate against, so we need
# to support an optional positional argument. This argument is currently
# unused.
parser.add_argument(
'url',
nargs='*',
help='A URL generated by Go to set up authentication.',
)
parser.add_argument(
'--location',
metavar='LOCATION',
required=True,
help='The location of the repository to print commands for.',
)
parser.add_argument(
'--json-key',
metavar='JSON_KEY',
help=(
'The path to the JSON key file to use for authentication. If not'
' specified, the authentication commands printed will use the token'
' from the logged in user.'
),
)
def Run(self, args):
"""Run the go goauth command."""
# TODO(b/399155579): Add support for regional endpoints.
url_line = endpoint_util.ArtifactRegistryDomainEndpoint(
args.location, 'go', rep=False, protocol='https'
)
header_line = go_util.AuthorizationHeader(args.json_key)
log.out.Print(url_line)
log.out.Print('')
log.out.Print(header_line)
log.out.Print('')

View File

@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to upload Go modules to a repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import os
import tempfile
from apitools.base.py import transfer
from googlecloudsdk.api_lib.artifacts import exceptions
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import go_util
from googlecloudsdk.core import resources
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Upload(base.Command):
"""Upload a Go module to an artifact repository."""
api_version = 'v1'
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To upload version v0.1.0 of a Go module located in /path/to/code/ to a repository in "us-central1":
$ {command} --location=us-central1 --project=myproject --repository=myrepo \
--module-path=the/module/path --version=v0.1.0 --source=/path/to/code
""",
}
@staticmethod
def Args(parser):
"""Set up arguements for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetRequiredRepoFlag().AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
parser.add_argument(
'--source',
metavar='SOURCE',
required=False,
default='.',
help='The root directory of the go module source code, '
'defaults to the current directory.')
parser.add_argument(
'--module-path',
metavar='MODULE_PATH',
required=True,
help='The module path of the Go module.')
parser.add_argument(
'--version',
metavar='VERSION',
required=True,
help='The version of the Go module.')
def Run(self, args):
"""Run the go module upload command."""
client = apis.GetClientInstance('artifactregistry', self.api_version)
client.additional_http_headers['X-Goog-Upload-Protocol'] = 'multipart'
messages = client.MESSAGES_MODULE
tempdir = tempfile.mkdtemp()
# Create the go.zip file.
zip_path = os.path.join(tempdir, 'go.zip')
pack = go_util.PackOperation()
pack_result = pack(
module_path=args.module_path,
version=args.version,
source=args.source,
output=zip_path)
if pack_result.exit_code:
raise exceptions.InvalidGoModuleError(
'failed to package the go module: ' + pack_result.stderr)
# Upload the go.zip.
repo_ref = args.CONCEPTS.repository.Parse()
request = messages.ArtifactregistryProjectsLocationsRepositoriesGoModulesUploadRequest(
uploadGoModuleRequest=messages.UploadGoModuleRequest(),
parent=repo_ref.RelativeName())
upload = transfer.Upload.FromFile(zip_path, mime_type='application/zip')
op_obj = client.projects_locations_repositories_goModules.Upload(
request, upload=upload)
op = op_obj.operation
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations')
# Handle the operation.
if args.async_:
return op_ref
else:
result = waiter.WaitFor(
waiter.CloudOperationPollerNoResources(
client.projects_locations_operations), op_ref,
'Uploading package')
return result

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry packages."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.Hidden
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class GooGet(base.Group):
"""Manage Artifact Registry GooGet packages.
"""

View File

@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to import GooGet packages into a repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.core import resources
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Import(base.Command):
"""Import one or more GooGet packages into an artifact repository."""
@staticmethod
def Args(parser):
"""Set up arguements for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetRepoArgFromBeta().AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
parser.add_argument(
'--gcs-source',
metavar='GCS_SOURCE',
required=True,
type=arg_parsers.ArgList(),
help="""\
The Google Cloud Storage location of a package to import.
Wildcards may be added at the end to import multiple packages.""")
def Run(self, args):
"""Run package import command."""
client = apis.GetClientInstance('artifactregistry', 'v1')
messages = client.MESSAGES_MODULE
use_wildcard = False
for gcs_source in args.gcs_source:
if '*' in gcs_source:
use_wildcard = True
if not gcs_source.endswith('*'):
raise exceptions.InvalidArgumentException(
'GCS_SOURCE', 'Wildcards must be at the end of the GCS path.')
repo_ref = args.CONCEPTS.repository.Parse()
request = messages.ArtifactregistryProjectsLocationsRepositoriesGoogetArtifactsImportRequest(
importGoogetArtifactsRequest=messages.ImportGoogetArtifactsRequest(
gcsSource=messages.ImportGoogetArtifactsGcsSource(
uris=args.gcs_source,
useWildcards=use_wildcard,
),
),
parent=repo_ref.RelativeName())
op = client.projects_locations_repositories_googetArtifacts.Import(request)
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations')
if args.async_:
return op_ref
else:
result = waiter.WaitFor(
waiter.CloudOperationPollerNoResources(
client.projects_locations_operations),
op_ref, 'Importing package(s)')
return result
Import.detailed_help = {
'brief': 'Import one or more GooGet packages into an artifact repository.',
'DESCRIPTION': """
*{command}* imports GooGet packages from Google Cloud Storage into the specified
artifact repository.
""",
'EXAMPLES': """
To import the package `my-package.goo` from Google Cloud Storage into
`my-repo`, run:
$ {0} my-repo --location=us-central1 --gcs-source={1}
To import the packages `my-package.goo` and `other-package.goo` into
`my-repo`, run:
$ {0} my-repo --location=us-central1 --gcs-source={1},{2}
To import all packages from `my-directory` into `my-repo`, run:
$ {0} my-repo --location=us-central1 --gcs-source={3}
To import all packages in all subdirectories from a Google Cloud
Storage bucket into `my-repo`, run:
$ {0} my-repo --location=us-central1 --gcs-source={4}
""".format('{command}', 'gs://my-bucket/path/to/my-package.goo',
'gs://my-bucket/path/to/other-package.goo',
'gs://my-bucket/my-directory/*',
'gs://my-bucket/**')
}

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to upload GooGet packages to a repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import transfer
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.core import resources
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Upload(base.Command):
"""Upload a GooGet package to an artifact repository."""
@staticmethod
def Args(parser):
"""Set up arguements for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetRepoArgFromBeta().AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
parser.add_argument(
'--source',
metavar='SOURCE',
required=True,
help="""\
The path of a package to upload.""")
def Run(self, args):
"""Run package import command."""
client = apis.GetClientInstance('artifactregistry', 'v1')
messages = client.MESSAGES_MODULE
client.additional_http_headers['X-Goog-Upload-Protocol'] = 'multipart'
repo_ref = args.CONCEPTS.repository.Parse()
request = messages.ArtifactregistryProjectsLocationsRepositoriesGoogetArtifactsUploadRequest(
parent=repo_ref.RelativeName())
upload = transfer.Upload.FromFile(args.source, mime_type='application/gzip')
response = client.projects_locations_repositories_googetArtifacts.Upload(
request, upload=upload)
op = response.operation
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations')
if args.async_:
return op_ref
else:
result = waiter.WaitFor(
waiter.CloudOperationPollerNoResources(
client.projects_locations_operations),
op_ref, 'Uploading package')
return result
Upload.detailed_help = {
'brief': 'Upload a GooGet package to an artifact repository.',
'DESCRIPTION': """
*{command}* uploads a GooGet package to the specified artifact repository.
""",
'EXAMPLES': """
To upload the package `my-package.goo` to `my-repo`, run:
$ {0} my-repo --location=us-central1 --source={1}
""".format('{command}', 'my-package.goo')
}

View File

@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry locations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.UniverseCompatible
class Locations(base.Group):
"""Manage Artifact Registry resource locations.
## EXAMPLES
To list all supported locations, run:
$ {command} list
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""List Artifact Registry resource locations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import util
DEFAULT_LIST_FORMAT = """\
table(
name:label=LOCATIONS
)"""
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List all Artifact Registry supported locations."""
detailed_help = {
"DESCRIPTION":
"{description}",
"EXAMPLES":
"""\
To list all supported locations:
$ {command}
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat(DEFAULT_LIST_FORMAT)
base.URI_FLAG.RemoveFromParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided
to this command invocation.
Returns:
A list of all supported locations.
"""
return [{"name": loc} for loc in util.GetLocationList(args)]

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry long-running operations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.UniverseCompatible
class Operations(base.Group):
"""Manage Artifact Registry long-running operations.
## EXAMPLES
To describe a Artifact Registry operation given a valid operation id
and `artifacts/location` property is set, run:
$ {command} describe 13166c87-a9c0-4b5f-8ccf-c5343a93eb2b
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,26 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Describe an Artifact Registry operation.
description: |
Describe an Artifact Registry operation given the operation id.
This command can fail for the following reasons:
* The operation specified does not exist.
* The active account does not have permission to access the given
operation.
examples: |
The following command describes an operation with id `06d2817d-6566-47c3-88a0-295ef51eb434`:
$ {command} 06d2817d-6566-47c3-88a0-295ef51eb434
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:operation
help_text: |
The Artifact Registry operation to describe.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.operations

View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry packages."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.UniverseCompatible
class Packages(base.Group):
"""Manage Artifact Registry packages.
## EXAMPLES
To list all packages in the current project and `artifacts/repository` and
`artifacts/location` properties are set,
run:
$ {command} list
To list packages under repository my-repo in the current project and location,
run:
$ {command} list --repository=my-repo
To delete package `my-pkg` under repository my-repo in the current project and
location, run:
$ {command} delete my-pkg --repository=my-repo
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,31 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Delete an Artifact Registry package.
description: |
Delete an Artifact Registry package.
This command can fail for the following reasons:
* The specified package does not exist.
* The active account does not have permission to delete packages.
examples: |
To delete a package named `my-pkg` under the current project, repository, and location, run:
$ {command} my-pkg
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:package
help_text: |
The Artifact Registry package to delete.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.packages
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:EscapePackageNameHook
async:
collection: artifactregistry.projects.locations.operations

View File

@@ -0,0 +1,27 @@
- release_tracks: [GA]
help_text:
brief: |
Describe an Artifact Registry package.
description: |
Describe an Artifact Registry package.
This command can fail for the following reasons:
* The specified package does not exist.
* The active account does not have permission to view packages.
examples: |
To describe a package named `my-pkg` under the current project, repository, and location, run:
$ {command} my-pkg
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:package
help_text: |
The Artifact Registry package to describe.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.packages
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:EscapePackageNameHook

View File

@@ -0,0 +1,110 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""List Artifact Registry packages."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import package_util
DEFAULT_LIST_FORMAT = """\
table(
name.sub("%5E", "^"):label=PACKAGE,
createTime.date(tz=LOCAL),
updateTime.date(tz=LOCAL),
annotations
)"""
@base.DefaultUniverseOnly
class List(base.ListCommand):
"""List Artifact Registry packages.
List all Artifact Registry packages in the specified repository and project.
To specify the maximum number of packages to list, use the --limit flag.
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
The following command lists a maximum of five packages:
$ {command} --limit=5
To list packages with name as `my-pkg`:
$ {command} --filter='name="projects/my-project/locations/us/repositories/my-repo/packages/my-pkg"
To list packages with a given partial name, use `*` to match any character in name:
$ {command} --filter='name="projects/my-project/locations/us/repositories/my-repo/packages/*pkg"'
$ {command} --filter='name="projects/my-project/locations/us/repositories/my-repo/packages/my*"'
To list files that have annotations:
$ {command} --filter=annotations:*
To list packages with annotations pair as [annotation_key: annotation_value]:
$ {command} --filter='annotations.annotation_key:annotation_value'
To list packages with annotations containing key as `my_key`:
$ {command} --filter='annotations.my_key'
If the key or value contains special characters, such as `my.key` or `my.value`, backtick("`") is required:
$ {command} --filter='annotations.`my.key`'
$ {command} --filter='annotations.`my.key`:`my.value`'
To list packages with given partial annotation key or value, use `*` to match any character:
$ {command} --filter='annotations.my_*:`*.value`'
To list packages ordered by create_time:
$ {command} --sort-by=create_time
To list packages ordered by update_time reversely:
$ {command} --sort-by=~update_time
"""
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat(DEFAULT_LIST_FORMAT)
base.URI_FLAG.RemoveFromParser(parser)
flags.GetRepoFlag().AddToParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A list of packages.
"""
return package_util.ListPackages(args)

View File

@@ -0,0 +1,40 @@
- release_tracks: [GA]
help_text:
brief: |
Update annotations on an Artifact Registry package.
description: |
Update annotations on an Artifact Registry package.
examples: |
To update annotations on a package named `my-pkg` when the project ID, repository and location defaults are set, run the following command:
CAUTION: This command will overwrite any existing annotations on the package.
$ {command} my-pkg --annotations=key1=value1,key2=value2
To clear all annotations on the package run the following command:
$ {command} my-pkg --annotations={}
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:package
help_text: |
The Artifact Registry package to update.
params:
- arg_name: annotations
api_field: package.annotations
required: false
help_text: |
List of annotations in the format of KEY=VALUE pairs to add, update, or remove.
Duplicate keys will be overwritten. For more details on annotations,
see https://google.aip.dev/148#annotations.
spec:
- api_field: key
- api_field: value
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.packages
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:EscapePackageNameHook

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry platform logs."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
@base.UniverseCompatible
class PlatformLogs(base.Group):
"""Manage Artifact Registry Platform Logs configurations."""
category = base.TOOLS_CATEGORY

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to clear Artifact Registry Platform Logs configuration."""
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import platformlogs_util
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
@base.UniverseCompatible
class Clear(base.UpdateCommand):
"""Clear the Platform Logs configuration."""
api_version = 'v1'
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """
To clear the Platform Logs configuration for the project 'my-project' in us-west1 (move project to default state):
$ {command} --project=my-project --location=us-west1
To clear the Platform Logs configuration for the repository 'my-repo' in us-west1 (fall back to project-level logging configuration):
$ {command} --project=my-project --location=us-west1 --repository=my-repo
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetPlainRepoFlag().AddToParser(parser)
flags.GetPlainLocationFlag().AddToParser(parser)
def Run(self, args):
"""Run the clear command."""
client = apis.GetClientInstance('artifactregistry', self.api_version)
messages = client.MESSAGES_MODULE
platform_logs_config = messages.PlatformLogsConfig()
response = platformlogs_util.UpdatePlatformLogsConfig(
args, client, messages, platform_logs_config
)
log.status.Print(
'Cleared Platform Logs Config for [{}].'.format(response.name)
)
return response

View File

@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to describe Artifact Registry Platform Logs configuration."""
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import platformlogs_util
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
@base.UniverseCompatible
class Describe(base.DescribeCommand):
"""Describe the current Platform Logs configuration."""
api_version = 'v1'
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To describe the Platform Logs configuration for the project 'my-project' in us-west1:
$ {command} --project=my-project --location=us-west1
To describe the Platform Logs configuration for the repository 'my-repo' in us-west1:
$ {command} --project=my-project --location=us-west1 --repository=my-repo
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetPlainRepoFlag().AddToParser(parser)
flags.GetPlainLocationFlag().AddToParser(parser)
def Run(self, args):
"""Run the describe command."""
client = apis.GetClientInstance('artifactregistry', self.api_version)
messages = client.MESSAGES_MODULE
return platformlogs_util.GetPlatformLogsConfig(args, client, messages)

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to disable Artifact Registry Platform Logs configuration."""
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import platformlogs_util
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
@base.UniverseCompatible
class Disable(base.UpdateCommand):
"""Disable the Platform Logs configuration."""
api_version = 'v1'
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """
To disable Platform Logs for the project 'my-project' in us-west1:
$ {command} --project=my-project --location=us-west1
To disable Platform Logs for the repository 'my-repo' in us-west1:
$ {command} --project=my-project --location=us-west1 --repository=my-repo
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetPlainRepoFlag().AddToParser(parser)
flags.GetPlainLocationFlag().AddToParser(parser)
def Run(self, args):
"""Run the disable command."""
client = apis.GetClientInstance('artifactregistry', self.api_version)
messages = client.MESSAGES_MODULE
platform_logs_config = messages.PlatformLogsConfig(
loggingState=messages.PlatformLogsConfig.LoggingStateValueValuesEnum.DISABLED,
)
response = platformlogs_util.UpdatePlatformLogsConfig(
args, client, messages, platform_logs_config
)
log.status.Print('Disabled Platform Logs for [{}].'.format(response.name))
return response

View File

@@ -0,0 +1,88 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to enable Artifact Registry Platform Logs configuration."""
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import platformlogs_util
from googlecloudsdk.core import log
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
@base.UniverseCompatible
class Enable(base.UpdateCommand):
"""Enable the Platform Logs configuration."""
api_version = 'v1'
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """
To enable Platform Logs for the project 'my-project' in us-west1 for all severity levels:
$ {command} --project=my-project --location=us-west1
To enable Platform Logs for the project 'my-project' in us-west1 for INFO level and above:
$ {command} --project=my-project --location=us-west1 --severity=INFO
To enable Platform Logs for the repository 'my-repo' in us-west1 for all severity levels:
$ {command} --project=my-project --location=us-west1 --repository=my-repo
To enable Platform Logs for the repository 'my-repo' in us-west1 for INFO level and above:
$ {command} --project=my-project --location=us-west1 --repository=my-repo --severity=INFO
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
flags.GetPlainRepoFlag().AddToParser(parser)
flags.GetPlainLocationFlag().AddToParser(parser)
flags.GetSeverityFlag().AddToParser(parser)
def Run(self, args):
"""Run the enable command."""
client = apis.GetClientInstance('artifactregistry', self.api_version)
messages = client.MESSAGES_MODULE
platform_logs_config = messages.PlatformLogsConfig(
loggingState=messages.PlatformLogsConfig.LoggingStateValueValuesEnum.ENABLED
)
if args.IsSpecified('severity'):
platform_logs_config.severityLevel = (
messages.PlatformLogsConfig.SeverityLevelValueValuesEnum(
args.severity
)
)
response = platformlogs_util.UpdatePlatformLogsConfig(
args, client, messages, platform_logs_config
)
severity_msg = ' for all severity levels'
if args.IsSpecified('severity'):
severity_msg = ' for severity {} and above'.format(args.severity)
log.status.Print('Enabled Platform Logs for [{}]{}.'.format(
response.name, severity_msg))
return response

View File

@@ -0,0 +1,53 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry print-settings."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
class PrintSettings(base.Group):
"""Print snippets to add to native tools settings files.
The snippets provide a credentials placeholder and configurations to allow
native tools to interact with Artifact Registry repositories.
## EXAMPLES
To print a snippet to add a repository to the Gradle build.gradle file for
repository my-repo in the current project, run:
$ {command} gradle --repository=my-repo
To print a snippet to add to the Maven pom.xml file for repository my-repo in
the current project, run:
$ {command} mvn --repository=my-repo
To print a snippet to add to the npm .npmrc file for repository my-repo in
the current project, run:
$ {command} npm --repository=my-repo
To print a snippet to add to the Python .pypirc and pip.comf files for
repository my-repo in the current project, run:
$ {command} python --repository=my-repo
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Print credential settings to add to the sources.list file."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts.print_settings import settings_util
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class Apt(base.Command):
"""Print settings to add to the sources.list.d directory.
Print settings to add to the sources.list.d directory for connecting to an Apt
repository.
"""
detailed_help = {
"DESCRIPTION":
"{description}",
"EXAMPLES":
"""\
To print a snippet for the repository set in the `artifacts/repository`
property in the default location:
$ {command}
To print a snippet for repository `my-repository` in the default location:
$ {command} --repository="my-repository"
""",
}
@staticmethod
def Args(parser):
flags.GetRepoFlag().AddToParser(parser)
parser.display_info.AddFormat("value(apt)")
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
An Apt settings snippet.
"""
return {"apt": settings_util.GetAptSettingsSnippet(args)}

View File

@@ -0,0 +1,69 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Print a snippet to add a repository to the Gradle build.gradle file."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts.print_settings import settings_util
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Gradle(base.Command):
"""Print a snippet to add a repository to the Gradle build.gradle file."""
detailed_help = {
"DESCRIPTION":
"{description}",
"EXAMPLES":
"""\
To print a snippet for the repository set in the `artifacts/repository`
property in the default location:
$ {command}
To print a snippet for repository `my-repository` in the default location:
$ {command} --repository="my-repository"
To print a snippet using service account key:
$ {command} --json-key=path/to/key.json
""",
}
@staticmethod
def Args(parser):
flags.GetRepoFlag().AddToParser(parser)
flags.GetJsonKeyFlag("gradle").AddToParser(parser)
parser.display_info.AddFormat("value(gradle)")
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A maven gradle snippet.
"""
return {"gradle": settings_util.GetGradleSnippet(args)}

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Print a snippet to add a Maven repository to the pom.xml file."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts.print_settings import settings_util
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Maven(base.Command):
"""Print a snippet to add a Maven repository to the pom.xml file."""
detailed_help = {
"DESCRIPTION":
"{description}",
"EXAMPLES":
"""\
To print a snippet for the repository set in the `artifacts/repository`
property in the default location:
$ {command}
To print a snippet for repository `my-repository` in the default location:
$ {command} --repository="my-repository"
To print a snippet using service account key:
$ {command} --json-key=path/to/key.json
""",
}
@staticmethod
def Args(parser):
flags.GetRepoFlag().AddToParser(parser)
flags.GetJsonKeyFlag("maven").AddToParser(parser)
parser.display_info.AddFormat("value(mvn)")
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A maven pom snippet.
"""
return {"mvn": settings_util.GetMavenSnippet(args)}

View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Print credential settings to add to the .npmrc file."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts.print_settings import settings_util
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Npm(base.Command):
"""Print credential settings to add to the .npmrc file.
Print credential settings to add to the .npmrc file for connecting to an npm
repository.
"""
detailed_help = {
"DESCRIPTION":
"{description}",
"EXAMPLES":
"""\
To print a snippet for the repository set in the `artifacts/repository`
property in the default location:
$ {command}
To print a snippet for repository `my-repository` in the default location:
$ {command} --repository="my-repository"
To print a snippet using service account key:
$ {command} --json-key=path/to/key.json
To print a snippet for the repository set in the `artifacts/repository`
property with scope @my-company:
$ {command} --scope=@my-company
""",
}
@staticmethod
def Args(parser):
flags.GetRepoFlag().AddToParser(parser)
flags.GetJsonKeyFlag("npm").AddToParser(parser)
flags.GetScopeFlag().AddToParser(parser)
parser.display_info.AddFormat("value(npm)")
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
An npm settings snippet.
"""
return {"npm": settings_util.GetNpmSettingsSnippet(args)}

View File

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Print credential settings to add to the .pypirc and pip.conf file."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts.print_settings import settings_util
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
class Python(base.Command):
"""Print credential settings to add to the .pypirc and pip.conf files.
Print credential settings to add to the .pypirc and pip.conf files for
connecting to a Python package repository.
"""
detailed_help = {
"DESCRIPTION":
"{description}",
"EXAMPLES":
"""\
To print a snippet for the repository set in the `artifacts/repository`
property in the default location:
$ {command}
To print a snippet for repository `my-repository` in the default location:
$ {command} --repository="my-repository"
To print a snippet using service account key:
$ {command} --json-key=path/to/key.json
""",
}
@staticmethod
def Args(parser):
flags.GetRepoFlag().AddToParser(parser)
flags.GetJsonKeyFlag("python").AddToParser(parser)
parser.display_info.AddFormat("value(python)")
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A python settings snippet.
"""
return {"python": settings_util.GetPythonSettingsSnippet(args)}

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Print credential settings to add to the yum.repos.d directory."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts.print_settings import settings_util
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class Yum(base.Command):
"""Print settings to add to the yum.repos.d directory.
Print settings to add to the yum.repos.d directory for connecting to a Yum
repository.
"""
detailed_help = {
"DESCRIPTION":
"{description}",
"EXAMPLES":
"""\
To print a snippet for the repository set in the `artifacts/repository`
property in the default location:
$ {command}
To print a snippet for repository `my-repository` in the default location:
$ {command} --repository="my-repository"
""",
}
@staticmethod
def Args(parser):
flags.GetRepoFlag().AddToParser(parser)
parser.display_info.AddFormat("value(yum)")
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A Yum settings snippet.
"""
return {"yum": settings_util.GetYumSettingsSnippet(args)}

View File

@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry repositories."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.UniverseCompatible
class Repositories(base.Group):
"""Manage Artifact Registry repositories.
## EXAMPLES
To create a repository with the name `my-repo`, run:
$ {command} create my-repo
To delete a repository with the name `my-repo`, run:
$ {command} delete my-repo
To describe a repository with the name `my-repo`, run:
$ {command} describe my-repo
To list all Artifact Registry repositories, run:
$ {command} list
To set an IAM policy for repository `my-repo`, run:
$ {command} set-iam-policy my-repo policy.json
To get an IAM policy for repository `my-repo`, run:
$ {command} get-iam-policy my-repo
To add an IAM policy binding for the role of 'roles/editor' for the user
'test-user@gmail.com' on repository `my-repo`, run:
$ {command} add-iam-policy-binding my-repo
--member='user:test-user@gmail.com' --role='roles/editor'
To remove an IAM policy binding for the role of 'roles/editor' for the user
'test-user@gmail.com' on repository `my-repo`, run:
$ {command} remove-iam-policy-binding my-repo
--member='user:test-user@gmail.com' --role='roles/editor'
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,34 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Add an IAM policy binding to the IAM policy of an Artifact Registry repository.
description: |
*{command}* adds an IAM policy binding to the IAM policy of an Artifact Registry
repository. One binding consists of a member, a role, and an optional condition.
This command can fail for the following reasons:
* The repository specified does not exist.
* The active account does not have permission to access the given
repository's IAM policies.
examples: |
To add an IAM policy binding for the role of 'roles/editor' for the user
'test-user@gmail.com' with repository 'my-repo', run:
$ {command} my-repo --member='user:test-user@gmail.com' --role='roles/editor'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
Name of the Artifact Registry repository.
iam:
enable_condition: true

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for managing Artifact Registry repository configurations."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Config(base.Group):
"""Manage Artifact Registry repository configurations."""

View File

@@ -0,0 +1,38 @@
release_tracks: [ALPHA]
command_type: CONFIG_EXPORT
help_text:
brief: Export the configuration for a Artifact Registry repository.
description: |
*{command}* exports the configuration for a Artifact Registry repository.
Repository configurations can be exported in
Kubernetes Resource Model (krm) or Terraform HCL formats. The
default format is `krm`.
Specifying `--all` allows you to export the configurations for all
repositories within the project.
Specifying `--path` allows you to export the configuration(s) to
a local directory.
examples: |
To export the configuration for a repository, run:
$ {command} my-repository
To export the configuration for a repository to a file, run:
$ {command} my-repository --path=/path/to/dir/
To export the configuration for a repository in Terraform
HCL format, run:
$ {command} my-repository --resource-format=terraform
To export the configurations for all repositories within a
project, run:
$ {command} --all
arguments:
resource:
help_text: Repository to export the configuration for.
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property

View File

@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Copy an Artifact Registry repository."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import requests
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
from googlecloudsdk.core.console import progress_tracker
from googlecloudsdk.core.util import retry
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Hidden
class Copy(base.Command):
"""Copy an Artifact Registry repository."""
detailed_help = {
'BRIEF': 'Copy an Artifact Registry repository.',
'DESCRIPTION': """
Copy all artifacts within an Artifact Registry repository to another repository.
Note that this is pull-based, so default arguments will apply to the destination repository.
""",
'EXAMPLES': """\
To copy artifacts from `repo1` in project `proj1` and location `us` to `repo2` in project `proj2` and location `asia`:
$ {command} repo2 --project=proj2 --location=asia --source-repo=projects/proj1/locations/us/repositories/repo1
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentParser.
"""
flags.GetRepoArg().AddToParser(parser)
base.ASYNC_FLAG.AddToParser(parser)
parser.add_argument(
'--source-repo',
metavar='SOURCE_REPOSITORY',
required=True,
help='The source repository path to copy artifacts from.',
)
def Run(self, args):
"""Run the repository copy command."""
# Call CopyRepository API.
client = requests.GetClient()
repo_ref = args.CONCEPTS.repository.Parse()
op = requests.CopyRepository(args.source_repo, repo_ref.RelativeName())
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations'
)
# Log operation details and return if not waiting.
log.status.Print(
'Copy request issued from [{}] to [{}].\nCreated operation [{}].'
.format(
args.source_repo, repo_ref.RelativeName(), op_ref.RelativeName()
)
)
if args.async_: # Return early if --async is specified.
return None
# Progress tracker for polling loop.
spinner_message = 'Copying artifacts'
progress_info = {'copied': 0, 'total': 0}
with progress_tracker.ProgressTracker(
spinner_message,
detail_message_callback=lambda: self._DetailMessage(progress_info),
autotick=True,
):
# Before polling, let user know to press CTRL-C to stop.
log.status.Print('Press CTRL-C to stop waiting on the operation.')
# Poll operation indefinitely.
poller = waiter.CloudOperationPollerNoResources(
client.projects_locations_operations
)
retryer = retry.Retryer(max_wait_ms=None)
try:
operation = retryer.RetryOnResult(
lambda: self._PollOperation(poller, op_ref, progress_info),
should_retry_if=lambda op, state: op and not op.done,
sleep_ms=2000,
)
if operation.error:
log.status.Print(
'\nOperation [{}] failed: {}'.format(
op_ref.RelativeName(), operation.error.message
)
)
return None
except retry.WaitException:
# This block should not be reached with max_wait_ms=None
log.error('Error: Copy operation wait unexpectedly timed out.')
return None
except Exception as e: # pylint: disable=broad-exception-caught
log.fatal('An unexpected error occurred: {}'.format(e))
return None
def _DetailMessage(self, progress_info):
"""Callback to update the progress tracker message."""
copied = progress_info['copied']
total = progress_info['total']
if total == 0:
return ' operation metadata not yet available'
progress = copied / total * 100
return ' {:.1f}% copied ({} of {} versions)'.format(progress, copied, total)
def _PollOperation(self, poller, op_ref, progress_info):
"""Polls the operation and updates progress_info from metadata."""
try:
operation = poller.Poll(op_ref)
except waiter.PollException as e:
# Handle potential polling errors if necessary
log.debug('Polling error: %s', e)
return None # Continue polling
if (
operation
and operation.metadata
and operation.metadata.additionalProperties
):
props = {p.key: p.value for p in operation.metadata.additionalProperties}
if 'versionsCopiedCount' in props:
progress_info['copied'] = props['versionsCopiedCount'].integer_value
if 'totalVersionsCount' in props:
progress_info['total'] = props['totalVersionsCount'].integer_value
return operation

View File

@@ -0,0 +1,257 @@
- release_tracks: [GA]
help_text:
brief: |
Create an Artifact Registry repository.
description: |
Create a new Artifact Registry repository.
This command can fail for the following reasons:
* A repository with the same name already exists.
* The active account does not have permission to create repositories.
* A valid repository format was not provided.
examples: |
To create a docker repository with the name `my-repo` in the default project and location, run the following command:
$ {command} my-repo --repository-format=docker
To create a docker repository `my-repo` with a KMS key
`projects/my-project/locations/us/keyRings/my-kr/cryptoKeys/my-key` in the default project and location, run the following command:
$ {command} my-repo --repository-format=docker --kms-key=projects/my-project/locations/us/keyRings/my-kr/cryptoKeys/my-key
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
The Artifact Registry repository to create.
params:
- arg_name: description
api_field: repository.description
help_text: |
Description for the repository.
- arg_name: kms-key
api_field: repository.kmsKeyName
help_text: |
Name of the encryption key that's used for encrypting the contents of the repository.
- arg_name: allow-snapshot-overwrites
type: bool
default: null
api_field: repository.mavenConfig.allowSnapshotOverwrites
help_text: |
(Maven only) Allow repository users to publish a snapshot that overwrites the same snapshot version in the repository.
- arg_name: version-policy
api_field: repository.mavenConfig.versionPolicy
help_text: |
(Maven only) The package versions that the repository will store.
choices:
- arg_value: NONE
enum_value: VERSION_POLICY_UNSPECIFIED
help_text: (Maven only) The repository doesn't validate the version type.
- arg_value: RELEASE
enum_value: RELEASE
help_text: (Maven only) The repository accepts release versions only.
- arg_value: SNAPSHOT
enum_value: SNAPSHOT
help_text: (Maven only) The repository accepts snapshot versions only.
- arg_name: mode
api_field: repository.mode
default: NONE
help_text: |
Mode is the type of the repository - Standard, Virtual or Remote.
choices:
- arg_value: NONE
enum_value: MODE_UNSPECIFIED
help_text: Repository mode not specified.
- arg_value: STANDARD-REPOSITORY
enum_value: STANDARD_REPOSITORY
help_text: Standard repository mode - should be possible to write/read data to this repo.
- arg_value: VIRTUAL-REPOSITORY
enum_value: VIRTUAL_REPOSITORY
help_text: Virtual repository mode - aggregates data from several upstreams.
- arg_value: REMOTE-REPOSITORY
enum_value: REMOTE_REPOSITORY
help_text: Remote repository mode - fetches data from upstream and caches it.
- arg_name: remote-repo-config-desc
api_field: repository.remoteRepositoryConfig.description
help_text: |
The description for the remote repository config.
- arg_name: immutable-tags
type: bool
api_field: repository.dockerConfig.immutableTags
default: null
help_text: |
(Docker only) Prevent changes to tagged images in the repository. Tags cannot be deleted or moved to a different image digest, and tagged images cannot be deleted.
- arg_name: remote-apt-repo-path
api_field: repository.remoteRepositoryConfig.aptRepository.publicRepository.repositoryPath
help_text: |
(Apt only) Remaining URL path to apt remote repository.
- arg_name: remote-yum-repo-path
api_field: repository.remoteRepositoryConfig.yumRepository.publicRepository.repositoryPath
help_text: |
(Yum only) Remaining URL path to yum remote repository.
- arg_name: disable-remote-validation
type: bool
api_field: repository.remoteRepositoryConfig.disableUpstreamValidation
default: null
help_text: |
Do not make an HTTP request to validate the remote upstream. Not recommended when setting a custom remote upstream unless you are absolutely sure your upstream URI and any auth is valid.
- arg_name: enable-ingestion-attestation
type: bool
api_field: repository.remoteRepositoryConfig.enableIngestionAttestation
default: null
hidden: true
help_text: |
Enable generating attestation with verifiable signature on imported files in remote repositories.
- arg_name: alternative-hostname
api_field: repository.networkConfig.alternativeHostname
hidden: true
default: null
help_text: |
An alternative hostname that a repository can be accessed through.
- arg_name: alternative-hostname-path-prefix
api_field: repository.networkConfig.prefix
hidden: true
default: null
help_text: |
An alternative hostname path prefix that a repository can be accessed through.
- arg_name: alternative-hostname-default
api_field: repository.networkConfig.isDefault
hidden: true
default: null
help_text: |
Whether this is the default repository for the alternative hostname if no repository matches the path prefix.
- group:
mutex: true
params:
- arg_name: allow-vulnerability-scanning
api_field: repository.vulnerabilityScanningConfig.enablementConfig
default: null
action: store_true
help_text: |
Allow vulnerability scanning on the repository.
choices:
- arg_value: true
enum_value: INHERITED
help_text: Vulnerability scanning enablement config set to inherited.
- arg_name: disable-vulnerability-scanning
api_field: repository.vulnerabilityScanningConfig.enablementConfig
default: null
action: store_true
help_text: |
Disable vulnerability scanning on the repository.
choices:
- arg_value: true
enum_value: DISABLED
help_text: Vulnerability scanning enablement config set to disabled.
- group:
mutex: true
hidden: true
params:
- arg_name: allow-sbom-generation
api_field: repository.sbomConfig.enablementConfig
default: null
action: store_true
hidden: true
help_text: |
Allow SBOM generation on the repository.
choices:
- arg_value: true
enum_value: INHERITED
help_text: SBOM generation enablement config set to inherited.
- arg_name: disable-sbom-generation
api_field: repository.sbomConfig.enablementConfig
default: null
action: store_true
hidden: true
help_text: |
Disable SBOM generation on the repository.
choices:
- arg_value: true
enum_value: DISABLED
help_text: SBOM generation enablement config set to disabled.
labels:
api_field: repository.labels
additional_arguments_hook: googlecloudsdk.command_lib.artifacts.util:AddAdditionalArgs
async:
collection: artifactregistry.projects.locations.operations
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:CheckServiceAccountPermission
- googlecloudsdk.command_lib.artifacts.util:AppendRepoDataToRequest
- googlecloudsdk.command_lib.artifacts.util:AppendUpstreamPoliciesToRequest
- googlecloudsdk.command_lib.artifacts.util:SanitizeRemoteRepositoryConfig
- release_tracks: [ALPHA, BETA]
help_text:
brief: |
Create an Artifact Registry repository.
description: |
Create a new Artifact Registry repository.
This command can fail for the following reasons:
* A repository with the same name already exists.
* The active account does not have permission to create repositories.
* A valid repository format was not provided.
examples: |
To create a repository with the name `my-repo` under the current project, run:
$ {command} my-repo
To create repository `my-repo` with a KMS key
`projects/my-project/locations/us/keyRings/my-kr/cryptoKeys/my-key`, run:
$ {command} my-repo --kms-key=projects/my-project/locations/us/keyRings/my-kr/cryptoKeys/my-key
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
The Artifact Registry repository to create.
params:
- arg_name: description
api_field: repository.description
help_text: |
Description for the repository.
- arg_name: kms-key
api_field: repository.kmsKeyName
help_text: |
Name of the encryption key that's used for encrypting the contents of the repository.
- arg_name: allow-snapshot-overwrites
type: bool
default: null
api_field: repository.mavenConfig.allowSnapshotOverwrites
help_text: |
(Maven only) Allow repository users to publish a snapshot that overwrites the same snapshot version in the repository.
- arg_name: version-policy
api_field: repository.mavenConfig.versionPolicy
help_text: |
(Maven only) The package versions that the repository will store.
choices:
- arg_value: NONE
enum_value: VERSION_POLICY_UNSPECIFIED
help_text: (Maven only) The repository doesn't validate the version type.
- arg_value: RELEASE
enum_value: RELEASE
help_text: (Maven only) The repository accepts release versions only.
- arg_value: SNAPSHOT
enum_value: SNAPSHOT
help_text: (Maven only) The repository accepts snapshot versions only.
labels:
api_field: repository.labels
additional_arguments_hook: googlecloudsdk.command_lib.artifacts.util:AddRepositoryFormatArgBeta
async:
collection: artifactregistry.projects.locations.operations
request:
api_version: v1beta2
collection: artifactregistry.projects.locations.repositories
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:CheckServiceAccountPermission
- googlecloudsdk.command_lib.artifacts.util:AppendRepoDataToRequest

View File

@@ -0,0 +1,29 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Delete an Artifact Registry repository.
description: |
Delete an Artifact Registry repository. Before you delete a repository, ensure that any
active dependencies on this repository are adjusted to use a new location.
This command can fail for the following reasons:
* The specified repository does not exist.
* The active account does not have permission to delete repositories.
examples: |
To delete repository named `my-repo` under the current project, run:
$ {command} my-repo
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
The Artifact Registry repository to delete.
async:
collection: artifactregistry.projects.locations.operations
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories

View File

@@ -0,0 +1,46 @@
- release_tracks: [GA]
universe_compatible: false
help_text:
brief: |
Delete cleanup policies from an Artifact Registry repository.
description: |
Delete cleanup policies from an Artifact Registry repository.
This command can fail for the following reasons:
* The given repository does not exist.
* The active account does not have permission to update repositories.
examples: |
To delete a cleanup policy named `policy_a` from the repository `my-repo`, run:
$ {command} my-repo --policynames=policy_a
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
The Artifact Registry repository to update.
params:
- arg_name: policynames
required: true
help_text: |
Comma-separated list of cleanup policy names to delete.
command_type: UPDATE
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.cleanup_policy_util:DeleteCleanupPolicyFields
update:
read_modify_update: true
response:
modify_response_hooks:
- googlecloudsdk.command_lib.artifacts.cleanup_policy_util:RepositoryToCleanupPoliciesResponse
output:
format: json

View File

@@ -0,0 +1,27 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Describe an Artifact Registry repository.
description: |
Describe an Artifact Registry repository given the repository name.
examples: |
To describe a repository named `my-repo` under the current project in
`us-west1`, run:
$ {command} my-repo --location=us-west1
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
The Artifact Registry repository to describe.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories
response:
modify_response_hooks:
- googlecloudsdk.command_lib.artifacts.util:AddEncryptionLogToRepositoryInfo
- googlecloudsdk.command_lib.artifacts.util:AddRegistryBaseToRepositoryInfo
- googlecloudsdk.command_lib.artifacts.util:ConvertBytesToMB

View File

@@ -0,0 +1,29 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Get IAM policy for an Artifact Registry repository.
description: |
*{command}* displays the IAM policy associated with an Artifact Registry repository.
The output includes an "etag" identifier that is used to check for concurrent policy
updates. An edited policy should include the same etag so that *set-iam-policy* applies
the changes to the correct policy version.
This command can fail for the following reasons:
* The repository specified does not exist.
* The active account does not have permission to access the given
repository's IAM policies.
examples: |
To print the IAM policy for repository `my-repo`, run:
$ {command} my-repo
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
Name of the Artifact Registry repository.

View File

@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""List Artifact Registry and Container Registry repositories."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import util
DEFAULT_LIST_FORMAT = """\
table[title="ARTIFACT_REGISTRY"](
name.basename():label=REPOSITORY,
format:label=FORMAT,
mode.basename(undefined=STANDARD_REPOSITORY):label=MODE,
description:label=DESCRIPTION,
name.segment(3):label=LOCATION,
labels.list():label=LABELS,
kmsKeyName.yesno(yes='Customer-managed key', no='Google-managed key'):label=ENCRYPTION,
createTime.date(tz=LOCAL),
updateTime.date(tz=LOCAL),
sizeBytes.size(zero='0',precision=3,units_out=M):label="SIZE (MB)"
)"""
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA,
base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class List(base.ListCommand):
"""List repositories in the specified project.
List all Artifact Registry repositories in the specified project.
To specify the maximum number of repositories to list, use the --limit flag.
"""
detailed_help = {
"DESCRIPTION":
"{description}",
"EXAMPLES":
"""\
The following command lists a maximum of five repositories:
$ {command} --limit=5
To list repositories with name as `my_repo`:
$ {command} --filter='name="projects/my-project/locations/us/repositories/my_repo"'
To list repositories with a given partial name, use `*` to match any character in name:
$ {command} --filter='name="projects/my-project/locations/us/repositories/*repo"'
$ {command} --filter='name="projects/my-project/locations/us/repositories/my_*"'
To list files that have annotations:
$ {command} --filter=annotations:*
To list repositories with annotations pair as [annotation_key: annotation_value]
$ {command} --filter='annotations.annotation_key:annotation_value'
To list repositories with annotations containing key as `my_key`:
$ {command} --filter='annotations.my_key'
If the key or value contains special characters, such as `my.key` or `my.value`, backtick("`") is required:
$ {command} --filter='annotations.`my.key`'
$ {command} --filter='annotations.`my.key`:`my.value`'
To list repositories with given partial annotation key or value, use `*` to match any character:
$ {command} --filter='annotations.*key:`*.value`'
To list repositories ordered by create_time:
$ {command} --sort-by=create_time
To list repositories ordered by update_time reversely:
$ {command}--sort-by=~update_time
""",
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat(DEFAULT_LIST_FORMAT)
base.URI_FLAG.RemoveFromParser(parser)
flags.GetOptionalLocationFlag().AddToParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A list of Repositories.
"""
return util.ListRepositories(args)

View File

@@ -0,0 +1,36 @@
- release_tracks: [GA]
universe_compatible: false
help_text:
brief: |
List cleanup policies of an Artifact Registry repository.
description: |
List cleanup policies of an Artifact Registry repository.
This command can fail for the following reasons:
* The specified repository does not exist.
* The active account does not have permission to list cleanup policies.
examples: |
To list cleanup policies for the repository `my-repo`, run:
$ {command} my-repo
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
The parent Artifact Registry repository for the list of cleanup policies.
command_type: DESCRIBE
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories
response:
modify_response_hooks:
- googlecloudsdk.command_lib.artifacts.util:AppendParentInfoToListPackagesResponse
- googlecloudsdk.command_lib.artifacts.cleanup_policy_util:RepositoryToCleanupPoliciesResponse
output:
format: json

View File

@@ -0,0 +1,34 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Remove an IAM policy binding from the IAM policy of an Artifact Registry repository.
description: |
*{command}* removes an IAM policy binding from the IAM policy of an Artifact Registry
repository. One binding consists of a member, a role, and an optional condition.
This command can fail for the following reasons:
* The repository specified does not exist.
* The active account does not have permission to access the given
repository's IAM policies.
examples: |
To remove an IAM policy binding for the role of 'roles/editor' for the user
'test-user@gmail.com' with repository 'my-repo', run:
$ {command} my-repo --member='user:test-user@gmail.com' --role='roles/editor'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
Name of the Artifact Registry repository.
iam:
enable_condition: true

View File

@@ -0,0 +1,68 @@
- release_tracks: [GA]
universe_compatible: false
help_text:
brief: |
Set or update cleanup policies for an Artifact Registry repository.
description: |
Set or update cleanup policies for an Artifact Registry repository.
This command can fail for the following reasons:
* The given repository does not exist.
* The active account does not have permission to update repositories.
* A valid cleanup policy format was not provided.
* The repository exceeds the maximum number of cleanup policies.
See https://cloud.google.com/artifact-registry/docs/repositories/cleanup-policy
for details of the cleanup policy file format and contents.
examples: |
To create a cleanup policy from a file with the name `policy.json` in the
repository `my-repo`, run:
$ {command} my-repo --policy=policy.json
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
The parent Artifact Registry repository for the list of cleanup policies.
params:
- group:
required: true
params:
- arg_name: dry-run
api_field: repository.cleanupPolicyDryRun
type: bool
default: null
help_text: |
Disable deleting images according to cleanup policies.
- arg_name: policy
api_field: repository.cleanupPolicies
type: googlecloudsdk.command_lib.artifacts.cleanup_policy_util:ParseCleanupPolicy
help_text: |
Path to a local JSON formatted file containing valid cleanup policies.
- arg_name: overwrite
help_text: |
Delete existing policies and replace with the specified set of policies.
hidden: true
action:
deprecated:
removed: true
error: Flag {flag_name} is obsolete and should be omitted.
command_type: UPDATE
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories
update:
read_modify_update: true
response:
modify_response_hooks:
- googlecloudsdk.command_lib.artifacts.cleanup_policy_util:RepositoryToCleanupPoliciesResponse
output:
format: json

View File

@@ -0,0 +1,30 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Set the IAM policy for an Artifact Registry repository.
description: |
Set the IAM policy associated with an Artifact Registry repository.
This command can fail for the following reasons:
* The repository specified does not exist.
* The active account does not have permission to access the given
repository's IAM policies.
examples: |
To set the IAM policy for `my-repository`, run:
$ {command} my-repo policy.json
See https://cloud.google.com/iam/docs/managing-policies for details of the
policy file format and contents.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
Name of the Artifact Registry repository.

View File

@@ -0,0 +1,172 @@
- release_tracks: [GA]
help_text:
brief: |
Update an Artifact Registry repository.
description: |
Update the description or labels for an Artifact Registry repository.
This command can fail for the following reasons:
* A repository with this name does not exist.
* The active account does not have permission to update repositories.
examples: |
To update a repository with the name `my-repo` under the current project, run:
$ {command} my-repo --description="New description"
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
The Artifact Registry repository to update.
params:
- arg_name: description
api_field: repository.description
help_text: |
Description for the repository.
- arg_name: immutable-tags
type: bool
default: null
api_field: repository.dockerConfig.immutableTags
help_text: |
(Docker only) Prevent changes to tagged images in the repository. Tags cannot be deleted or moved to a different image digest, and tagged images cannot be deleted.
- arg_name: remote-username
api_field: repository.remoteRepositoryConfig.upstreamCredentials.usernamePasswordCredentials.username
default: null
help_text: |
Remote Repository upstream registry username.
- arg_name: remote-password-secret-version
api_field: repository.remoteRepositoryConfig.upstreamCredentials.usernamePasswordCredentials.passwordSecretVersion
default: null
help_text: |
Secret Manager secret version that contains password for the remote repository upstream.
- group:
mutex: true
params:
- arg_name: allow-vulnerability-scanning
api_field: repository.vulnerabilityScanningConfig.enablementConfig
default: null
action: store_true
help_text: |
Allow vulnerability scanning on the repository.
choices:
- arg_value: true
enum_value: INHERITED
help_text: Vulnerability scanning enablement config set to inherited.
- arg_name: disable-vulnerability-scanning
api_field: repository.vulnerabilityScanningConfig.enablementConfig
default: null
action: store_true
help_text: |
Disable vulnerability scanning on the repository.
choices:
- arg_value: true
enum_value: DISABLED
help_text: Vulnerability scanning enablement config set to disabled.
- arg_name: disable-remote-validation
type: bool
api_field: repository.remoteRepositoryConfig.disableUpstreamValidation
default: null
help_text: |
Do not make an HTTP request to validate the remote upstream. Not recommended when setting a custom remote upstream unless you are absolutely sure your upstream URI and any auth is valid.
- arg_name: enable-ingestion-attestation
api_field: repository.remoteRepositoryConfig.enableIngestionAttestation
action: store_true
hidden: true
default: null
type: bool
help_text: |
Enable generating attestation with verifiable signature on imported files in remote repositories.
- arg_name: alternative-hostname
api_field: repository.networkConfig.alternativeHostname
hidden: true
default: null
help_text: |
An alternative hostname that a repository can be accessed through.
- arg_name: alternative-hostname-path-prefix
api_field: repository.networkConfig.prefix
hidden: true
default: null
help_text: |
An alternative hostname path prefix that a repository can be accessed through.
- arg_name: alternative-hostname-default
api_field: repository.networkConfig.isDefault
hidden: true
default: null
help_text: |
Whether this is the default repository for the alternative hostname if no repository matches the path prefix.
- group:
mutex: true
hidden: true
params:
- arg_name: allow-sbom-generation
api_field: repository.sbomConfig.enablementConfig
default: null
action: store_true
hidden: true
help_text: |
Allow SBOM generation on the repository.
choices:
- arg_value: true
enum_value: INHERITED
help_text: SBOM generation enablement config set to inherited.
- arg_name: disable-sbom-generation
api_field: repository.sbomConfig.enablementConfig
default: null
action: store_true
hidden: true
help_text: |
Disable SBOM generation on the repository.
choices:
- arg_value: true
enum_value: DISABLED
help_text: SBOM generation enablement config set to disabled.
labels:
api_field: repository.labels
additional_arguments_hook: googlecloudsdk.command_lib.artifacts.util:UpstreamsArgs
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:AppendUpstreamPoliciesToRequest
update:
read_modify_update: true
- release_tracks: [ALPHA, BETA]
help_text:
brief: |
Update an Artifact Registry repository.
description: |
Update the description or labels for an Artifact Registry repository.
This command can fail for the following reasons:
* A repository with this name does not exist.
* The active account does not have permission to update repositories.
examples: |
To update a repository with the name `my-repo` under the current project, run:
$ {command} my-repo --description="New description"
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository_without_property
help_text: |
The Artifact Registry repository to update.
params:
- arg_name: description
api_field: repository.description
help_text: |
Description for the repository.
labels:
api_field: repository.labels
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories
update:
read_modify_update: true

View File

@@ -0,0 +1,47 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry rules."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class Rules(base.Group):
"""Manage Artifact Registry rules.
## EXAMPLES
To list all rules in the current project and `artifacts/repository` and
`artifacts/location` properties are set,
run:
$ {command} list
To list rules under repository my-repo in the current project and location,
run:
$ {command} list --repository=my-repo
To delete rule `my-rule` under repository my-repo in the current project and
location, run:
$ {command} delete my-rule --repository=my-repo
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,55 @@
- release_tracks: [GA]
help_text:
brief: |
Create an Artifact Registry rule.
description: |
Create a new Artifact Registry rule.
This command can fail for the following reasons:
* A rule with the same name already exists.
* The active account does not have permission to create repositories.
* A rule with given package already exists.
examples: |
To create a rule with the name `my-rule` for package `my-pkg` with action deny
under the current project, repository, run:
$ {command} my-rule --package=my-pkg --action=deny
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:rule
help_text: |
The Artifact Registry rule to create.
params:
- arg_name: action
required: true
api_field: googleDevtoolsArtifactregistryV1Rule.action
help_text: |
The action the rule would make, can only be DENY or ALLOW.
choices:
- arg_value: ALLOW
enum_value: ALLOW
- arg_value: DENY
enum_value: DENY
- arg_name: operation
api_field: googleDevtoolsArtifactregistryV1Rule.operation
default: DOWNLOAD
choices:
- arg_value: DOWNLOAD
enum_value: DOWNLOAD
help_text: |
The operation the rule applies to.
- arg_name: package
api_field: googleDevtoolsArtifactregistryV1Rule.packageId
help_text: |
The package the rule applies to. Empty means the rule is set for the entire repository.
- arg_name: condition
api_field: googleDevtoolsArtifactregistryV1Rule.condition.expression
help_text: |
The CEL expression for the rule.
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories.rules
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:AppendRuleDataToRequest

View File

@@ -0,0 +1,26 @@
- release_tracks: [GA]
help_text:
brief: |
Delete an Artifact Registry rule.
description: |
Delete an Artifact Registry rule.
This command can fail for the following reasons:
* The specified rule does not exist.
* The active account does not have permission to delete rules.
examples: |
To delete a rule named `my-rule` under the current project, repository, and location, run:
$ {command} my-repo
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:rule
help_text: |
The Artifact Registry rule to delete.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.rules

View File

@@ -0,0 +1,26 @@
- release_tracks: [GA]
help_text:
brief: |
Describe an Artifact Registry rule.
description: |
Describe an Artifact Registry rule.
This command can fail for the following reasons:
* The specified rule does not exist.
* The active account does not have permission to view rules.
examples: |
To describe a rule named `my-rule` under the current project, repository, and location, run:
$ {command} my-rule
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:rule
help_text: |
The Artifact Registry rule to describe.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.rules

View File

@@ -0,0 +1,41 @@
- release_tracks: [GA]
help_text:
brief: |
List Artifact Registry rules.
description: |
List all Artifact Registry rules for the specified repository.
This command can fail for the following reasons:
* The specified repository does not exist.
* The active account does not have permission to list rules.
To specify the maximum number of rules to list, use the --limit flag.
examples: |
The following command lists a maximum of five rules for repository `my-repo`:
$ {command} --limit=5
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:repository
help_text: |
The parent repository for the list of rules.
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories.rules
response:
modify_response_hooks:
- googlecloudsdk.command_lib.artifacts.util:AppendParentInfoToListRulesResponse
output:
format: |
table(
name.basename():label=RULE,
packageId:label=PACKAGE,
action:label=ACTION,
operation:label=OPERATION,
condition.expression:label=CONDITION
)

View File

@@ -0,0 +1,53 @@
- release_tracks: [GA]
help_text:
brief: |
Update an Artifact Registry rule.
description: |
Update an Artifact Registry rule.
This command can fail for the following reasons:
* The rule does not exist.
* A rule with the same name already exists.
* The active account does not have permission to create repositories.
* A rule with given package already exists.
examples: |
To create a rule with the name `my-rule` for package `my-pkg` with action deny
under the current project, repository, run:
$ {command} my-rule --package=my-pkg --action=deny
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:rule
help_text: |
The Artifact Registry rule to update.
params:
- arg_name: action
api_field: googleDevtoolsArtifactregistryV1Rule.action
help_text: |
The action the rule would make, can only be DENY or ALLOW.
choices:
- arg_value: ALLOW
enum_value: ALLOW
- arg_value: DENY
enum_value: DENY
- arg_name: operation
api_field: googleDevtoolsArtifactregistryV1Rule.operation
choices:
- arg_value: DOWNLOAD
enum_value: DOWNLOAD
help_text: |
The operation the rule applies to.
- arg_name: package
api_field: googleDevtoolsArtifactregistryV1Rule.packageId
help_text: |
The package the rule applies to.
- arg_name: condition
api_field: googleDevtoolsArtifactregistryV1Rule.condition.expression
help_text: |
The CEL expression for the rule.
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories.rules

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry packages."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Sbom(base.Group):
"""Manage Artifact SBOMs."""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,71 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to export SBOM files."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import endpoint_util
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import sbom_util
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Export(base.Command):
"""Export SBOM files to Google Cloud Storage."""
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To export an SBOM file for the Artifact Registry image with URI "us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz":
$ {command} --uri=us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
parser.display_info.AddFormat('yaml')
parser.add_argument(
'--uri',
required=True,
help=(
'The URI of the Artifact Registry image the SBOM is exported for. A'
" 'gcr.io' image can also be used if redirection is enabled in"
' Artifact Registry. Make sure'
" 'artifactregistry.projectsettings.get' permission is granted to"
' the current gcloud user to verify the redirection status.'
),
)
flags.GetOptionalAALocationFlag().AddToParser(parser)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: An argparse namespace. All the arguments that were provided to this
command invocation.
"""
location = args.location
with endpoint_util.WithRegion(location):
sbom_util.ExportSbom(args)

View File

@@ -0,0 +1,118 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to list SBOM file references."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import endpoint_util
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import sbom_printer
from googlecloudsdk.command_lib.artifacts import sbom_util
from googlecloudsdk.command_lib.artifacts import util
from googlecloudsdk.core.resource import resource_printer
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.DefaultUniverseOnly
class List(base.ListCommand):
"""List SBOM file references."""
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To list SBOM file references:
$ {command}
To list SBOM file references related to the image with the tag "us-east1-docker.pkg.dev/project/repo/my-image:1.0":
$ {command} --resource="us-east1-docker.pkg.dev/project/repo/my-image:1.0"
To list SBOM file references related to the image with the digest "us-east1-docker.pkg.dev/project/repo/my-image@sha256:88b205d7995332e10e836514fbfd59ecaf8976fc15060cd66e85cdcebe7fb356":
$ {command} --resource="us-east1-docker.pkg.dev/project/repo/my-image@sha256:88b205d7995332e10e836514fbfd59ecaf8976fc15060cd66e85cdcebe7fb356"
To list SBOM file references related to the images with the resource path prefix "us-east1-docker.pkg.dev/project/repo":
$ {command} --resource-prefix="us-east1-docker.pkg.dev/project/repo"
To list SBOM file references generated when the images were pushed to Artifact Registry and related to the installed package dependency "perl":
$ {command} --dependency="perl"
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
resource_printer.RegisterFormatter(
sbom_printer.SBOM_PRINTER_FORMAT,
sbom_printer.SbomPrinter)
parser.display_info.AddFormat(sbom_printer.SBOM_PRINTER_FORMAT)
base.SORT_BY_FLAG.SetDefault(parser, 'occ.create_time')
base.URI_FLAG.RemoveFromParser(parser)
flags.GetOptionalAALocationFlag().AddToParser(parser)
group = parser.add_group(mutex=True)
group.add_argument(
'--dependency',
required=False,
help=(
'List SBOM file references generated when the images were pushed to'
' Artifact Registry and related to the installed package'
' dependency. See'
' https://cloud.google.com/container-analysis/docs/scanning-types'
' for supported packages.'
),
)
group.add_argument(
'--resource',
required=False,
help='List SBOM file references related to the image resource uri.',
)
group.add_argument(
'--resource-prefix',
required=False,
help=(
'List SBOM file references related to the resource uri with the'
' resource path prefix.'
),
)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A list of SBOM references.
"""
location = args.location
with endpoint_util.WithRegion(location):
if location is not None:
project = util.GetProject(args)
args.project = util.GetParent(project, args.location)
return sbom_util.ListSbomReferences(args)

View File

@@ -0,0 +1,191 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Implements the command to upload an SBOM file."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.artifacts import endpoint_util
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import sbom_util
from googlecloudsdk.command_lib.artifacts import util
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Load(base.Command):
"""Upload an SBOM file and create a reference occurrence."""
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To upload an SBOM file at /path/to/sbom.json for a Docker image in Artifact Registry:
$ {command} --source=/path/to/sbom.json \
--uri=us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz
To upload an SBOM file at /path/to/sbom.json for a Docker image with a KMS key version to sign the created SBOM reference:
$ {command} --source=/path/to/sbom.json \
--uri=us-west1-docker.pkg.dev/my-project/my-repository/busy-box@sha256:abcxyz \
--kms-key-version=projects/my-project/locations/us-west1/keyRings/my-key-ring/cryptoKeys/my-key/cryptoKeyVersions/1
To upload an SBOM file at /path/to/sbom.json for a Docker image from a Docker registry:
$ {command} --source=/path/to/sbom.json \
--uri=my-docker-registry/my-image@sha256:abcxyz \
--destination=gs://my-cloud-storage-bucket
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.ArgumentPaser.
"""
parser.add_argument(
'--source',
metavar='SOURCE',
required=True,
default='.',
help='The SBOM file for uploading.',
)
parser.add_argument(
'--uri',
metavar='ARTIFACT_URI',
required=True,
help="""\
The URI of the artifact the SBOM is generated from.
The URI can be a Docker image from any Docker registries. A URI provided with a tag (e.g. `[IMAGE]:[TAG]`) will be resolved into a URI with a digest (`[IMAGE]@sha256:[DIGEST]`).
When passing an image which is not from Artifact Registry or Container Registry with a tag, only public images can be resolved.
Also, when passing an image which is not from Artifact Registry or Container Registry, the `--destination` flag is required.
""",
)
parser.add_argument(
'--kms-key-version',
default=None,
help="""\
Cloud KMS key version to sign the SBOM reference.
The key version provided should be the resource ID in the format of
`projects/[KEY_PROJECT_ID]/locations/[LOCATION]/keyRings/[RING_NAME]/cryptoKeys/[KEY_NAME]/cryptoKeyVersions/[KEY_VERSION]`.
""",
required=False,
type=arg_parsers.RegexpValidator(
r'^projects/[^/]+/locations/[^/]+/keyRings/[^/]+/cryptoKeys/[^/]+/cryptoKeyVersions/[^/]+$',
(
'Must be in format of'
" 'projects/[KEY_PROJECT_ID]/locations/[LOCATION]/keyRings/[RING_NAME]/cryptoKeys/[KEY_NAME]/cryptoKeyVersions/[KEY_VERSION]'"
),
),
)
parser.add_argument(
'--destination',
metavar='DESTINATION',
default=None,
required=False,
help="""\
The storage path will be used to store the SBOM file.
Currently only supports Cloud Storage paths start with 'gs://'.
""",
type=arg_parsers.RegexpValidator(
r'^gs://.*$', 'Must be in format of gs://[STORAGE_PATH]'
),
)
flags.GetOptionalAALocationFlag().AddToParser(parser)
def Run(self, args):
"""Run the load command."""
# Parse file and get the version.
s = sbom_util.ParseJsonSbom(args.source)
log.info(
'Successfully loaded the SBOM file. Format: {0}-{1}.'.format(
s.sbom_format, s.version
)
)
if not args.uri:
raise exceptions.InvalidArgumentException(
'ARTIFACT_URI',
'--uri is required.',
)
# Get information from the artifact.
a = sbom_util.ProcessArtifact(args.uri)
log.info(
(
'Processed artifact. '
+ 'Project: {0}, Location: {1}, URI: {2}, Digest {3}.'
).format(
a.project, a.location, a.resource_uri, a.digests.get('sha256', '')
)
)
if (
sbom_util.ARTIFACT_TYPE_OTHER == a.artifact_type
and not args.destination
):
raise exceptions.InvalidArgumentException(
'DESTINATION',
(
'--destination is required for images not in Artifact Registry or'
' Container Registry.'
),
)
# Upload SBOM to a GCS bucket.
remote_path = sbom_util.UploadSbomToGCS(
source=args.source,
artifact=a,
sbom=s,
gcs_path=args.destination,
)
log.info('Uploaded the SBOM file at {0}'.format(remote_path))
# Create occurrence in the same project as the artifact's project.
# If artifact doesn't have a project (e.g. non AR/GCR images), we will use
# the current working project.
project_id = a.project
if not project_id:
project_id = args.project or properties.VALUES.core.project.GetOrFail()
log.info(
(
'Failed to get project_id from the artifact URI {0}. Using'
' project {1} for occurrence.'
).format(args.uri, project_id)
)
parent = util.GetParent(project_id, args.location)
# Write reference occurrence.
with endpoint_util.WithRegion(args.location):
occurrence_id = sbom_util.WriteReferenceOccurrence(
artifact=a,
project_id=parent,
storage=remote_path,
sbom=s,
kms_key_version=args.kms_key_version,
)
log.info('Wrote reference occurrence {0}.'.format(occurrence_id))
log.status.Print(
'Uploaded the SBOM file under the resource URI {}.'.format(
a.GetOccurrenceResourceUri()
)
)

View File

@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry versions."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Settings(base.Group):
"""Manage Artifact Registry project settings."""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,25 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
List all Artifact Registry project settings.
description: |
List all Artifact Registry project settings.
examples: |
To list project settings for project `my-project`:
$ {command} --project=my-package
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:project
is_positional: false
help_text: |
The project to read the settings from.
request:
api_version: v1
disable_resource_check: true
collection: artifactregistry.projects
method: getProjectSettings
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:UpdateSettingsResource

View File

@@ -0,0 +1,24 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Disables redirection from Container Registry to Artifact Registry.
description: |
Disables redirection from Container Registry to Artifact Registry.
examples: |
To disable redirection for project `my-project`:
$ {command} --project=my-project
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:project
is_positional: false
help_text: |
The project to read the settings from.
request:
api_version: v1
disable_resource_check: true
collection: artifactregistry.projects
method: updateProjectSettings
issue_request_hook: googlecloudsdk.command_lib.artifacts.util:DisableUpgradeRedirection

View File

@@ -0,0 +1,28 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Enables redirection from Container Registry to Artifact Registry.
description: |
Enables redirection from Container Registry to Artifact Registry.
examples: |
To enable redirection for project `my-project`:
$ {command} --project=my-project
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:project
is_positional: false
help_text: |
The project to read the settings from.
params:
- arg_name: dry-run
type: bool
help_text: Validate the project setup, but do not enable redirection
request:
api_version: v1
disable_resource_check: true
collection: artifactregistry.projects
method: updateProjectSettings
issue_request_hook: googlecloudsdk.command_lib.artifacts.util:EnableUpgradeRedirection

View File

@@ -0,0 +1,50 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry repositories."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.UniverseCompatible
class Repositories(base.Group):
"""Manage Artifact Registry tags.
## EXAMPLES
To create tag with the name `my-tag` for version `1.0.0` of package `my-pkg`
in the current project with `artifacts/repository` and `artifacts/location`
properties are set, run:
$ {command} create my-tag --package=my-pkg --version=1.0.0
To list all tags under package `my-pkg`, run:
$ {command} list --package=my-pkg
To update tag `my-tag` from a different version to version `1.0.0` of package
`my-pkg`, run:
$ {command} update my-tag --version=1.0.0 --package=my-pkg
To delete tag `my-tag` of package `my-pkg`, run:
$ {command} delete my-tag --package=my-pkg
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,35 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Create an Artifact Registry tag.
description: |
Create a new Artifact Registry tag.
This command can fail for the following reasons:
* A tag with the same name already exists.
* The specified version or package does not exist.
* The active account does not have permission to create tags.
* The specified package format doesn't support tag operations (e.g. maven).
examples: |
To create a tag with the name `my-tag` for version `1.0.0` of package `my-pkg`
under the current project, repository, and location, run:
$ {command} my-tag --version=1.0.0 --package=my-pkg
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:tag
help_text: |
The Artifact Registry tag to create.
params:
- arg_name: version
required: true
help_text: |
The version associated with the tag.
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories.packages.tags
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:AppendTagDataToRequest

View File

@@ -0,0 +1,30 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Delete an Artifact Registry tag.
description: |
Delete an Artifact Registry tag.
This command can fail for the following reasons:
* The specified tag does not exist.
* The specified version or package does not exist.
* The active account does not have permission to delete tags.
* The specified package format doesn't support tag operations (e.g. maven).
examples: |
To delete tag `my-tag` in package `my-pkg` under the current project,
repository, and location, run:
$ {command} my-tag --package=my-pkg
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:tag
help_text: |
The Artifact Registry tag to delete.
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories.packages.tags
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:EscapeTagNameHook

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Export an Artifact Registry tag."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import requests
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Export(base.Command):
"""Export an Artifact Registry package version by tag.
Export files of an Artifact Registry package version by tag to a Google Cloud
Storage path.
"""
detailed_help = {
'DESCRIPTION': '{description}',
'EXAMPLES': """\
To export by tag `t1` of package `my-pkg` to a Google Cloud Storage path `gs://my-bucket/sub-folder` under the current project, repository, and location, run:
$ {command} t1 --package=my-pkg --gcs-destination=gs://my-bucket/sub-folder
""",
}
@staticmethod
def Args(parser):
"""Set up arguments for this command.
Args:
parser: An argparse.Parser.
"""
flags.GetRequiredTagFlag().AddToParser(parser)
parser.add_argument(
'--gcs-destination',
metavar='GCS_DESTINATION',
required=True,
help='Google Cloud Storage path to export the artifact to.',
)
def Run(self, args):
"""Run the export command."""
tag_ref = args.CONCEPTS.tag.Parse()
# ExportArtifact takes the gcs_destination path without the "gs://" prefix.
# We allow the "gs://" prefix here to match yum/apt/googet import commands.
gcs_destination = args.gcs_destination.removeprefix('gs://')
op = requests.ExportArtifact(None, tag_ref, gcs_destination)
op_ref = resources.REGISTRY.ParseRelativeName(
op.name, collection='artifactregistry.projects.locations.operations'
)
log.status.Print(
'Export request issued from [{}] to [{}].\nCreated operation [{}].'
.format(
tag_ref.RelativeName(),
args.gcs_destination,
op_ref.RelativeName(),
)
)

View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""List Artifact Registry tags."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.artifacts import flags
from googlecloudsdk.command_lib.artifacts import tag_util
DEFAULT_LIST_FORMAT = """\
table(
name.basename():label=TAG,
version.basename():label=VERSION
)"""
@base.UniverseCompatible
class List(base.ListCommand):
"""List Artifact Registry tags.
List all Artifact Registry tags in the specified package.
This command can fail for the following reasons:
* The specified version or package does not exist.
* The active account does not have permission to list tags.
* The specified package format doesn't support tag operations (e.g. maven).
To specify the maximum number of tags to list, use the --limit flag.
"""
detailed_help = {
'DESCRIPTION':
'{description}',
'EXAMPLES':
"""\
To list tags for package `my-package`:
$ {command} --package=my-package
The following command lists a maximum of five tags for package `my-package`:
$ {command} --package=my-package --limit=5
To list tags of package `my-package` with name as `my-tag`:
$ {command} --package=my-package --filter='name="projects/my-project/locations/us/repositories/my-repo/packages/my-package/tags/my-tag"'
To list tags of package `my-package` with a given partial name, use `*` to match any character in name:
$ {command} --package=my-package --filter='name="projects/my-project/locations/us/repositories/my-repo/packages/my-package/tags/my*"'
$ {command} --package=my-package --filter='name="projects/my-project/locations/us/repositories/my-repo/packages/my-package/tags/*tag"'
"""
}
@staticmethod
def Args(parser):
parser.display_info.AddFormat(DEFAULT_LIST_FORMAT)
base.URI_FLAG.RemoveFromParser(parser)
flags.GetRepoFlag().AddToParser(parser)
parser.add_argument(
'--package',
required=True,
help="""List all tags in a specified artifact, such as a container
image or a language package."""
)
def Run(self, args):
"""This is what gets called when the user runs this command.
Args:
args: an argparse namespace. All the arguments that were provided to this
command invocation.
Returns:
A list of package tags.
"""
return tag_util.ListTags(args)

View File

@@ -0,0 +1,35 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Update an Artifact Registry tag.
description: |
Update an Artifact Registry tag.
This command can fail for the following reasons:
* The tag does not exist.
* The specified version or package does not exist.
* The active account does not have permission to update tags.
* The specified package format doesn't support tag operations (e.g. maven).
examples: |
To update a tag with the name `my-tag` to version `1.0.0` of package `my-pkg`
from another version, run:
$ {command} my-tag --version=1.0.0 --package=my-pkg
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:tag
help_text: |
The Artifact Registry tag to update.
params:
- arg_name: version
required: true
help_text: |
The version associated with the tag.
request:
api_version: v1
collection: artifactregistry.projects.locations.repositories.packages.tags
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:SetTagUpdateMask

View File

@@ -0,0 +1,45 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command group for Artifact Registry versions."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.UniverseCompatible
class Versions(base.Group):
"""Manage Artifact Registry package versions.
## EXAMPLES
To list all versions for a package when the `artifacts/repository` and
`artifacts/location` properties are set to a repository in the current
project, run:
$ {command} list --package=my-pkg
To delete version `1.0.0` under package `my-pkg`, run:
$ {command} delete 1.0.0 --package=my-pkg
To delete version `1.0.0` under package `my-pkg` with its tags, run:
$ {command} delete 1.0.0 --package=my-pkg --delete-tags
"""
category = base.CI_CD_CATEGORY

View File

@@ -0,0 +1,38 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: |
Delete an Artifact Registry package version.
description: |
Delete an Artifact Registry package version.
This command can fail for the following reasons:
* The specified package version does not exist.
* The active account does not have permission to delete package versions.
examples: |
To delete version `1.0.0` of `my-pkg` under the current project, repository, and location, run:
$ {command} 1.0.0 --package=my-pkg
arguments:
resource:
spec: !REF googlecloudsdk.command_lib.artifacts.resources:version
help_text: |
The Artifact Registry package version to delete.
params:
- arg_name: delete-tags
type: bool
default: false
help_text: |
If specified, all tags associated with the version are deleted.
request: &request
api_version: v1
collection: artifactregistry.projects.locations.repositories.packages.versions
modify_request_hooks:
- googlecloudsdk.command_lib.artifacts.util:EscapeVersionNameHook
- googlecloudsdk.command_lib.artifacts.util:DeleteVersionTags
async:
collection: artifactregistry.projects.locations.operations

Some files were not shown because too many files have changed in this diff Show More