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,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