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