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,50 @@
# -*- 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.
"""The gcloud run worker-pools group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import platforms
from googlecloudsdk.core import properties
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class WorkerPools(base.Group):
"""View and manage your Cloud Run worker pools.
This set of commands can be used to view and manage your Cloud Run worker
pools.
"""
detailed_help = {
'EXAMPLES': """
To list your existing worker-pools, run:
$ {command} list
""",
}
def Filter(self, context, args):
"""Runs before command.Run and validates platform with passed args."""
# Ensures a platform set on the run/platform property is 'managed' and
# all other passed args are valid for this platform and release track.
flags.ValidateManagedPlatform(args, self.ReleaseTrack(), flags.Product.RUN)
return context

View File

@@ -0,0 +1,36 @@
- release_tracks: [ALPHA, BETA]
help_text:
brief: Add IAM policy binding to a Cloud Run worker pool.
description: |
Add an IAM policy binding to the IAM policy of a Cloud Run worker pool. One binding consists of a member,
and a role.
examples: |
To add an IAM policy binding for the role of 'roles/run.developer' for the user 'test-user@gmail.com'
with worker pool 'my-worker-pool' and region 'us-central1', run:
$ {command} my-worker-pool --region='us-central1' --member='user:test-user@gmail.com' --role='roles/run.developer'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: run.projects.locations.workerpools
modify_request_hooks:
- googlecloudsdk.command_lib.run.platforms:ValidatePlatformIsManaged
arguments:
resource:
help_text: The worker pool for which to add IAM policy binding to.
spec: !REF googlecloudsdk.command_lib.run.resources:workerpool
ALPHA:
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion
BETA:
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,113 @@
# -*- 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 for deleting a worker-pool."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import pretty_print
from googlecloudsdk.command_lib.run import resource_args
from googlecloudsdk.command_lib.run.v2 import deletion
from googlecloudsdk.command_lib.run.v2 import worker_pools_operations
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class Delete(base.Command):
"""Delete a worker-pool."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To delete a worker-pool:
$ {command} <worker-pool-name>
""",
}
@staticmethod
def CommonArgs(parser):
worker_pool_presentation = presentation_specs.ResourcePresentationSpec(
'WORKER_POOL',
resource_args.GetV2WorkerPoolResourceSpec(),
'WorkerPool to delete.',
required=True,
prefixes=False,
)
concept_parsers.ConceptParser([worker_pool_presentation]).AddToParser(
parser
)
flags.AddAsyncFlag(parser)
@staticmethod
def Args(parser):
Delete.CommonArgs(parser)
def Run(self, args):
"""Delete a worker-pool."""
def DeriveRegionalEndpoint(endpoint):
region = args.CONCEPTS.worker_pool.Parse().locationsId
return region + '-' + endpoint
worker_pool_ref = args.CONCEPTS.worker_pool.Parse()
flags.ValidateResource(worker_pool_ref)
console_io.PromptContinue(
message='WorkerPool [{worker_pool}] will be deleted.'.format(
worker_pool=worker_pool_ref.workerPoolsId
),
throw_if_unattended=True,
cancel_on_no=True,
)
run_client = apis.GetGapicClientInstance(
'run', 'v2', address_override_func=DeriveRegionalEndpoint
)
worker_pools_client = worker_pools_operations.WorkerPoolsOperations(
run_client
)
def DeleteWithExistenceCheck(worker_pool_ref):
response = worker_pools_client.DeleteWorkerPool(worker_pool_ref)
if not response:
raise exceptions.ArgumentError(
'Cannot find worker pool [{}]'.format(worker_pool_ref.workerPoolsId)
)
# TODO: b/390067647 - Use response.result() once the issue is fixed
deletion.Delete(
worker_pool_ref,
worker_pools_client.GetWorkerPool,
DeleteWithExistenceCheck,
args.async_,
)
if args.async_:
pretty_print.Success(
'Worker pool [{}] is being deleted.'.format(
worker_pool_ref.workerPoolsId
)
)
else:
log.DeletedResource(worker_pool_ref.workerPoolsId, 'worker pool')

View File

@@ -0,0 +1,416 @@
# -*- 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.
"""Deploy a container to Cloud Run that will handle workloads that are not ingress based."""
import enum
import os.path
from googlecloudsdk.api_lib.run import api_enabler
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions as c_exceptions
from googlecloudsdk.command_lib.artifacts import docker_util
from googlecloudsdk.command_lib.run import artifact_registry
from googlecloudsdk.command_lib.run import connection_context
from googlecloudsdk.command_lib.run import container_parser
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import messages_util
from googlecloudsdk.command_lib.run import pretty_print
from googlecloudsdk.command_lib.run import resource_args
from googlecloudsdk.command_lib.run import resource_name_conversion
from googlecloudsdk.command_lib.run import stages
from googlecloudsdk.command_lib.run.v2 import config_changes as config_changes_mod
from googlecloudsdk.command_lib.run.v2 import flags_parser
from googlecloudsdk.command_lib.run.v2 import worker_pools_operations
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.console import progress_tracker
class BuildType(enum.Enum):
DOCKERFILE = 'Dockerfile'
BUILDPACKS = 'Buildpacks'
def ContainerArgGroup(release_track=base.ReleaseTrack.GA):
"""Returns an argument group with all container deploy args."""
help_text = """
Container Flags
The following flags apply to the container.
"""
group = base.ArgumentGroup(help=help_text)
group.AddArgument(flags.SourceAndImageFlags())
group.AddArgument(flags.MutexEnvVarsFlags(release_track=release_track))
group.AddArgument(flags.MemoryFlag())
group.AddArgument(flags.CpuFlag())
group.AddArgument(flags.ArgsFlag())
group.AddArgument(flags_parser.SecretsFlags())
group.AddArgument(flags.DependsOnFlag())
group.AddArgument(flags.CommandFlag())
group.AddArgument(flags.AddVolumeMountFlag())
group.AddArgument(flags.RemoveVolumeMountFlag())
group.AddArgument(flags.ClearVolumeMountsFlag())
# ALPHA and BETA features
if (
release_track == base.ReleaseTrack.ALPHA
or release_track == base.ReleaseTrack.BETA
):
group.AddArgument(flags.GpuFlag())
return group
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class Deploy(base.Command):
"""Create or update a Cloud Run worker-pool."""
detailed_help = {
'DESCRIPTION': """\
Creates or updates a Cloud Run worker-pool.
""",
'EXAMPLES': """\
To deploy a container to the worker-pool `my-backend` on Cloud Run:
$ {command} my-backend --image=us-docker.pkg.dev/project/image
You may also omit the worker-pool name. Then a prompt will be displayed
with a suggested default value:
$ {command} --image=us-docker.pkg.dev/project/image
""",
}
@classmethod
def CommonArgs(cls, parser):
flags.AddBinAuthzPolicyFlags(parser)
flags.AddBinAuthzBreakglassFlag(parser)
flags_parser.AddCloudSQLFlags(parser)
flags.AddCmekKeyFlag(parser)
flags.AddCmekKeyRevocationActionTypeFlag(parser)
flags.AddDescriptionFlag(parser)
flags.AddEgressSettingsFlag(parser)
flags.AddEncryptionKeyShutdownHoursFlag(parser)
flags.AddRevisionSuffixArg(parser)
flags.AddRuntimeFlag(parser)
flags.AddVolumesFlags(parser, cls.ReleaseTrack())
flags.AddScalingFlag(
parser, release_track=cls.ReleaseTrack(), resource_kind='worker'
)
flags.AddVpcNetworkGroupFlagsForUpdate(parser, resource_kind='worker')
flags.RemoveContainersFlag().AddToParser(parser)
flags.AddAsyncFlag(parser)
flags.AddLabelsFlags(parser)
flags.AddGeneralAnnotationFlags(parser)
flags.AddServiceAccountFlag(parser)
flags.AddClientNameAndVersionFlags(parser)
flags.AddNoPromoteFlag(parser)
flags.AddGpuTypeFlag(parser)
flags.GpuZonalRedundancyFlag(parser)
worker_pool_presentation = presentation_specs.ResourcePresentationSpec(
'WORKER_POOL',
resource_args.GetV2WorkerPoolResourceSpec(prompt=True),
'WorkerPool to deploy to.',
required=True,
prefixes=False,
)
concept_parsers.ConceptParser([worker_pool_presentation]).AddToParser(
parser
)
# No output by default, can be overridden by --format
parser.display_info.AddFormat('none')
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
container_args = ContainerArgGroup(cls.ReleaseTrack())
container_parser.AddContainerFlags(
parser, container_args, cls.ReleaseTrack()
)
def _GetBaseChanges(self, args):
"""Returns the worker pool config changes with some default settings."""
changes = flags_parser.GetWorkerPoolConfigurationChanges(
args, self.ReleaseTrack()
)
changes.insert(
0,
config_changes_mod.BinaryAuthorizationChange(
breakglass_justification=None
),
)
changes.append(config_changes_mod.SetLaunchStageChange(self.ReleaseTrack()))
return changes
def _ValidateAndGetContainers(self, args):
if flags.FlagIsExplicitlySet(args, 'containers'):
containers = args.containers
else:
containers = {'': args}
if len(containers) > 10:
raise c_exceptions.InvalidArgumentException(
'--container', 'Worker pools may include at most 10 containers'
)
return containers
def _ValidateAndGetBuildFromSource(self, containers):
build_from_source = {
name: container
for name, container in containers.items()
if (
not container.IsSpecified('image')
or flags.FlagIsExplicitlySet(container, 'source')
)
}
if len(build_from_source) > 1:
needs_image = [
name
for name, container in build_from_source.items()
if not flags.FlagIsExplicitlySet(container, 'source')
]
if needs_image:
raise exceptions.RequiredImageArgumentException(needs_image)
raise c_exceptions.InvalidArgumentException(
'--container', 'At most one container can be deployed from source.'
)
for name, container in build_from_source.items():
if not flags.FlagIsExplicitlySet(container, 'source'):
if console_io.CanPrompt():
container.source = flags.PromptForDefaultSource(name)
else:
if name:
message = (
'Container {} requires a container image to deploy (e.g.'
' `gcr.io/cloudrun/hello:latest`) if no build source is'
' provided.'.format(name)
)
else:
message = (
'Requires a container image to deploy (e.g.'
' `gcr.io/cloudrun/hello:latest`) if no build source is'
' provided.'
)
raise c_exceptions.RequiredArgumentException(
'--image',
message,
)
return build_from_source
def _GetRequiredApis(self):
return [api_enabler.get_run_api()]
def _BuildFromSource(
self,
args,
build_from_source,
already_activated_services,
worker_pool_ref,
):
# Only one container can deployed from source
name, container_args = next(iter(build_from_source.items()))
pack = None
build_type = None
repo_to_create = None
source = container_args.source
ar_repo = docker_util.DockerRepo(
project_id=properties.VALUES.core.project.Get(required=True),
location_id=artifact_registry.RepoRegion(args),
repo_id='cloud-run-source-deploy',
)
if artifact_registry.ShouldCreateRepository(
ar_repo, skip_activation_prompt=already_activated_services
):
repo_to_create = ar_repo
# The image is built with latest tag. After build, the image digest
# from the build result will be added to the image of the worker pool spec.
container_args.image = '{repo}/{worker_pool}'.format(
repo=ar_repo.GetDockerString(),
worker_pool=worker_pool_ref.workerPoolsId,
)
docker_file = source + '/Dockerfile'
if os.path.exists(docker_file):
build_type = BuildType.DOCKERFILE
else:
pack = _CreateBuildPack(container_args, self.ReleaseTrack())
build_type = BuildType.BUILDPACKS
image = None if pack else container_args.image
operation_message = (
'Building using {build_type} and deploying container to'
).format(build_type=build_type.value)
return (
image,
pack,
source,
operation_message,
repo_to_create,
name,
)
def Run(self, args):
"""Deploy a WorkerPool container to Cloud Run."""
containers = self._ValidateAndGetContainers(args)
build_from_source = self._ValidateAndGetBuildFromSource(containers)
worker_pool_ref = args.CONCEPTS.worker_pool.Parse()
flags.ValidateResource(worker_pool_ref)
required_apis = self._GetRequiredApis()
if build_from_source:
required_apis.append('artifactregistry.googleapis.com')
required_apis.append('cloudbuild.googleapis.com')
already_activated_services = api_enabler.check_and_enable_apis(
properties.VALUES.core.project.Get(), required_apis
)
# Obtaining the connection context prompts the user to select a region if
# one hasn't been provided. We want to do this prior to preparing a source
# deploy so that we can use that region for the Artifact Registry repo.
conn_context = connection_context.GetConnectionContext(
args,
flags.Product.RUN,
self.ReleaseTrack(),
)
def DeriveRegionalEndpoint(endpoint):
region = args.CONCEPTS.worker_pool.Parse().locationsId
return region + '-' + endpoint
run_client = apis.GetGapicClientInstance(
'run', 'v2', address_override_func=DeriveRegionalEndpoint
)
worker_pools_client = worker_pools_operations.WorkerPoolsOperations(
run_client
)
# pre-fetch the worker pool in case it already exists.
worker_pool = worker_pools_client.GetWorkerPool(worker_pool_ref)
messages_util.MaybeLogDefaultGpuTypeMessageForV2Resource(args, worker_pool)
build_image = None
build_pack = None
build_source = None
operation_message = 'Deploying container to'
repo_to_create = None
# Name of the container to be deployed from source.
container_name = None
if build_from_source:
(
build_image,
build_pack,
build_source,
operation_message,
repo_to_create,
container_name,
) = self._BuildFromSource(
args, build_from_source, already_activated_services, worker_pool_ref
)
pretty_print.Info(
messages_util.GetStartDeployMessage(
conn_context,
worker_pool_ref,
operation_message,
resource_kind_lower='worker pool',
)
)
config_changes = self._GetBaseChanges(args)
header = 'Deploying'
if worker_pool is None:
header += ' new worker pool'
header += '...'
with progress_tracker.StagedProgressTracker(
header,
stages.WorkerPoolStages(
include_build=bool(build_from_source),
include_create_repo=repo_to_create is not None,
),
failure_message='Deployment failed',
suppress_output=args.async_,
) as tracker:
# TODO: b/432102851 - Add retry logic with zonal redundancy off.
response = worker_pools_client.ReleaseWorkerPool(
worker_pool_ref,
config_changes,
self.ReleaseTrack(),
tracker=tracker,
prefetch=worker_pool,
build_image=build_image,
build_pack=build_pack,
build_source=build_source,
build_from_source_container_name=container_name,
repo_to_create=repo_to_create,
already_activated_services=already_activated_services,
force_new_revision=True,
)
if not response:
raise exceptions.ArgumentError(
'Cannot deploy worker pool [{}]'.format(
worker_pool_ref.workerPoolsId
)
)
if args.async_:
pretty_print.Success(
'Worker pool [{{bold}}{worker_pool}{{reset}}] is being deployed '
'asynchronously.'.format(worker_pool=worker_pool_ref.workerPoolsId)
)
else:
response.result() # Wait for the operation to complete.
msg = 'Worker pool [{{bold}}{worker_pool}{{reset}}]'.format(
worker_pool=worker_pool_ref.workerPoolsId
)
if response.metadata and response.metadata.latest_created_revision:
rev = resource_name_conversion.GetNameFromFullChildName(
response.metadata.latest_created_revision
)
msg += ' revision [{{bold}}{rev}{{reset}}]'.format(rev=rev)
pretty_print.Success(msg + ' has been deployed.')
def _CreateBuildPack(container, release_track=base.ReleaseTrack.GA):
"""A helper method to cofigure buildpack."""
pack = [{'image': container.image}]
if release_track != base.ReleaseTrack.GA:
command_arg = getattr(container, 'command', None)
if command_arg is not None:
command = ' '.join(command_arg)
pack[0].update(
{'envs': ['GOOGLE_ENTRYPOINT="{command}"'.format(command=command)]}
)
return pack
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class AlphaDeploy(Deploy):
"""Create or update a Cloud Run worker-pool."""
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
flags.AddWorkerPoolMinInstancesFlag(parser)
flags.AddWorkerPoolMaxInstancesFlag(parser)
container_args = ContainerArgGroup(cls.ReleaseTrack())
container_parser.AddContainerFlags(
parser, container_args, cls.ReleaseTrack()
)
AlphaDeploy.__doc__ = Deploy.__doc__

View File

@@ -0,0 +1,105 @@
# -*- 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 for obtaining details about a given worker-pool."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import connection_context
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import resource_args
from googlecloudsdk.command_lib.run import serverless_operations
from googlecloudsdk.command_lib.run.printers import export_printer
from googlecloudsdk.command_lib.run.printers import worker_pool_printer
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core.resource import resource_printer
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class Describe(base.Command):
"""Obtain details about a given worker-pool."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To obtain details about a given worker-pool:
$ {command} <worker-pool-name>
To get those details in the YAML format:
$ {command} <worker-pool-name> --format=yaml
To get them in YAML format suited to export (omitting metadata
specific to this deployment and status info):
$ {command} <worker-pool-name> --format=export
""",
}
@staticmethod
def CommonArgs(parser):
flags.AddRegionArg(parser)
worker_pool_presentation = presentation_specs.ResourcePresentationSpec(
'WORKER_POOL',
resource_args.GetV1WorkerPoolResourceSpec(),
'WorkerPool to describe.',
required=True,
prefixes=False,
)
concept_parsers.ConceptParser([worker_pool_presentation]).AddToParser(
parser
)
resource_printer.RegisterFormatter(
worker_pool_printer.WORKER_POOL_PRINTER_FORMAT,
worker_pool_printer.WorkerPoolPrinter,
)
parser.display_info.AddFormat(
worker_pool_printer.WORKER_POOL_PRINTER_FORMAT
)
resource_printer.RegisterFormatter(
export_printer.EXPORT_PRINTER_FORMAT,
export_printer.ExportPrinter,
)
@staticmethod
def Args(parser):
Describe.CommonArgs(parser)
def _ConnectionContext(self, args):
return connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack()
)
def Run(self, args):
"""Obtain details about a given worker-pool."""
conn_context = self._ConnectionContext(args)
worker_pool_ref = args.CONCEPTS.worker_pool.Parse()
flags.ValidateResource(worker_pool_ref)
with serverless_operations.Connect(conn_context) as client:
worker_pool = client.GetWorkerPool(worker_pool_ref)
if not worker_pool:
raise exceptions.ArgumentError('Cannot find worker pool [{}]'.format(
worker_pool_ref.workerpoolsId))
return worker_pool

View File

@@ -0,0 +1,34 @@
- release_tracks: [ALPHA, BETA]
help_text:
brief: Get the IAM policy for a Cloud Run worker pool.
description: |
This command gets the IAM policy for a worker pool. If
formatted as JSON, the output can be edited and used as a policy
file for *set-iam-policy*. The output includes an "etag" field
identifying the version emitted and allowing detection of
concurrent policy updates; see
$ gcloud alpha run registries set-iam-policy for additional details.
examples: |
To print the IAM policy for a given worker pool, run:
$ {command} --region=us-central1 my-worker-pool
request:
collection: run.projects.locations.workerpools
modify_request_hooks:
- googlecloudsdk.command_lib.run.platforms:ValidatePlatformIsManaged
arguments:
resource:
help_text: The worker pool for which to display the IAM policy.
spec: !REF googlecloudsdk.command_lib.run.resources:workerpool
ALPHA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion
BETA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,98 @@
# -*- 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 for listing available worker-pools."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.run import global_methods
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import commands
from googlecloudsdk.command_lib.run import connection_context
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import pretty_print
from googlecloudsdk.command_lib.run import resource_args
from googlecloudsdk.command_lib.run import serverless_operations
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class List(commands.List):
"""List available worker-pools."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To list available worker-pools:
$ {command}
""",
}
@classmethod
def CommonArgs(cls, parser):
# Flags specific to connecting to a cluster
project_presentation = presentation_specs.ResourcePresentationSpec(
'--project',
resource_args.GetNamespaceResourceSpec(),
'Project to list worker-pools in.',
required=True,
prefixes=False,
hidden=True,
)
flags.AddRegionArg(parser)
concept_parsers.ConceptParser([project_presentation]).AddToParser(parser)
parser.display_info.AddFormat(
'table('
'{ready_column},'
'name:label=WORKER_POOL,'
'region:label=REGION,'
'last_modifier:label="LAST DEPLOYED BY",'
'last_transition_time:label="LAST DEPLOYED AT",'
'author:label="CREATED BY",'
'creation_timestamp:label=CREATED):({alias})'.format(
ready_column=pretty_print.READY_COLUMN,
alias=commands.SATISFIES_PZS_ALIAS,
)
)
parser.display_info.AddUriFunc(cls._GetResourceUri)
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
def Run(self, args):
"""List available worker-pools."""
# Use the mixer for global request if there's no --region flag.
project_ref = args.CONCEPTS.project.Parse()
if not args.IsSpecified('region'):
client = global_methods.GetServerlessClientInstance(api_version='v1')
self.SetPartialApiEndpoint(client.url)
# Don't consider region property here, we'll default to all regions
return commands.SortByName(
global_methods.ListWorkerPools(client, project_ref)
)
conn_context = connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack()
)
with serverless_operations.Connect(conn_context) as client:
self.SetCompleteApiEndpoint(conn_context.endpoint)
return commands.SortByName(client.ListWorkerPools(project_ref))

View File

@@ -0,0 +1,40 @@
# -*- 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.
"""Group definition for worker pools logs."""
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)
class Logs(base.Group):
"""Read logs for Cloud Run worker pools."""
detailed_help = {
'EXAMPLES': """
To tail logs for a worker pool, run:
$ {command} tail my-worker-pool
To read logs for a worker pool, run:
$ {command} read my-worker-pool
""",
}

View File

@@ -0,0 +1,99 @@
# -*- 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 read logs for a worker pool."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from googlecloudsdk.api_lib.logging import common
from googlecloudsdk.api_lib.logging import formatter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.logs import read as read_logs_lib
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.core import log
FormatLog = formatter.FormatLog
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Read(base.Command):
"""Read logs for a Cloud Run worker pool."""
detailed_help = {
'DESCRIPTION': """\
{command} reads log entries. Log entries matching *--log-filter* are
returned according to the specified --order.
If the log entries come from multiple logs, then entries from
different logs might be intermingled in the results.
""",
'EXAMPLES': """\
To read log entries from for a Cloud Run Worker Pool, run:
$ {command} my-worker-pool
To read log entries with severity ERROR or higher, run:
$ {command} my-worker-pool --log-filter="severity>=ERROR"
To read log entries written in a specific time window, run:
$ {command} my-worker-pool --log-filter='timestamp<="2015-05-31T23:59:59Z" AND timestamp>="2015-05-31T00:00:00Z"'
To read up to 10 log entries in your worker pool payloads that include the
word `SearchText` and format the output in `JSON` format, run:
$ {command} my-worker-pool --log-filter="textPayload:SearchText" --limit=10 --format=json
Detailed information about filters can be found at:
[](https://cloud.google.com/logging/docs/view/advanced_filters)
""",
}
@staticmethod
def Args(parser):
parser.add_argument('worker_pool', help='Name for a Cloud Run worker pool.')
read_logs_lib.LogFilterArgs(parser)
read_logs_lib.LoggingReadArgs(parser)
def Run(self, args):
filters = [args.log_filter] if args.IsSpecified('log_filter') else []
filters.append('resource.type = %s \n' % 'cloud_run_worker_pool')
filters.append(
'resource.labels.worker_pool_name = %s \n' % args.worker_pool
)
filters.append(
'resource.labels.location = %s \n' % flags.GetRegion(args, prompt=True)
)
filters.append('severity >= DEFAULT \n')
filters += read_logs_lib.MakeTimestampFilters(args)
lines = []
logs = common.FetchLogs(
read_logs_lib.JoinFilters(filters),
order_by=args.order,
limit=args.limit,
)
for log_line in logs:
output_log = FormatLog(log_line)
if output_log:
lines.append(output_log)
for line in reversed(lines):
log.out.Print(line)

View File

@@ -0,0 +1,77 @@
# -*- 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 tail logs for a worker pool."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.logs import read as read_logs_lib
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import streaming
from googlecloudsdk.core import properties
from googlecloudsdk.core.credentials import store
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Tail(base.BinaryBackedCommand):
"""Tail logs for a Cloud Run worker pool."""
detailed_help = {
'DESCRIPTION': """\
{command} tails log-entries for a particular
Cloud Run worker pool in real time. The log entries are formatted for
consumption in a terminal.
""",
'EXAMPLES': """\
To tail log entries for a Cloud Run Worker Pool, run:
$ {command} my-worker-pool
To tail log entries with severity ERROR or higher, run:
$ {command} my-worker-pool --log-filter="severity>=ERROR"
Detailed information about filters can be found at:
[](https://cloud.google.com/logging/docs/view/advanced_filters)
""",
}
@staticmethod
def Args(parser):
parser.add_argument('worker_pool', help='Name for a Cloud Run worker pool.')
read_logs_lib.LogFilterArgs(parser)
def Run(self, args):
filters = []
if args.IsSpecified('log_filter'):
filters.append(args.log_filter)
filters.append('resource.type=%s' % 'cloud_run_worker_pool')
filters.append('resource.labels.worker_pool_name=%s' % args.worker_pool)
filters.append('resource.labels.location=%s' %
flags.GetRegion(args, prompt=True))
filters.append('severity>=DEFAULT')
project_id = properties.VALUES.core.project.Get(required=True)
filter_str = ' '.join(filters)
command_executor = streaming.LogStreamingWrapper()
response = command_executor(
project_id=project_id,
log_format='run',
log_filter=filter_str,
token=store.GetFreshAccessTokenIfEnabled(),
)
return self._DefaultOperationResponseHandler(response)

View File

@@ -0,0 +1,36 @@
- release_tracks: [ALPHA, BETA]
help_text:
brief: Remove IAM policy binding of a Cloud Run worker pool.
description: |
Remove an IAM policy binding from the IAM policy of a worker pool. One binding consists of a member,
and a role.
examples: |
To remove an IAM policy binding for the role of 'roles/run.developer' for the user 'test-user@gmail.com'
with worker pool 'my-worker-pool' and region 'us-central1', run:
$ {command} my-worker-pool --region='us-central1' --member='user:test-user@gmail.com' --role='roles/run.developer'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: run.projects.locations.workerpools
modify_request_hooks:
- googlecloudsdk.command_lib.run.platforms:ValidatePlatformIsManaged
arguments:
resource:
help_text: The service for which to remove the IAM policy binding.
spec: !REF googlecloudsdk.command_lib.run.resources:workerpool
ALPHA:
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion
BETA:
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,241 @@
# -*- 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 for updating env vars and other configuration info."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from googlecloudsdk.api_lib.run import global_methods
from googlecloudsdk.api_lib.run import worker_pool
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import messages as messages_util
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.projects import util as projects_util
from googlecloudsdk.command_lib.run import config_changes
from googlecloudsdk.command_lib.run import connection_context
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import messages_util as run_messages_util
from googlecloudsdk.command_lib.run import pretty_print
from googlecloudsdk.command_lib.run import serverless_operations
from googlecloudsdk.command_lib.run import stages
from googlecloudsdk.core import config
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.console import progress_tracker
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class Replace(base.Command):
"""Create or replace a worker-pool from a YAML worker-pool specification."""
detailed_help = {
'DESCRIPTION': """\
Creates or replaces a worker-pool from a YAML worker-pool specification.
""",
'EXAMPLES': """\
To replace the specification for a worker-pool defined in my-worker-pool.yaml
$ {command} my-worker-pool.yaml
""",
}
@classmethod
def CommonArgs(cls, parser):
# Flags not specific to any platform
flags.AddAsyncFlag(parser)
flags.AddClientNameAndVersionFlags(parser)
flags.AddDryRunFlag(parser)
parser.add_argument(
'FILE',
action='store',
type=arg_parsers.YAMLFileContents(),
help=(
'The absolute path to the YAML file with a Cloud Run worker-pool '
'definition for the worker-pool to update or create.'
),
)
# No output by default, can be overridden by --format
parser.display_info.AddFormat('none')
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
def _ConnectionContext(self, args, region_label):
return connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack(), region_label=region_label
)
def _GetBaseChanges(
self, new_worker_pool, args
): # used by child - pylint: disable=unused-argument
is_either_specified = args.IsSpecified('client_name') or args.IsSpecified(
'client_version'
)
return [
config_changes.ReplaceWorkerPoolChange(new_worker_pool),
config_changes.SetLaunchStageAnnotationChange(self.ReleaseTrack()),
config_changes.SetClientNameAndVersionAnnotationChange(
args.client_name if is_either_specified else 'gcloud',
args.client_version
if is_either_specified
else config.CLOUD_SDK_VERSION,
set_on_template=True,
),
]
def _PrintSuccessMessage(self, worker_pool_obj, dry_run, args):
if args.async_:
pretty_print.Success(
'New configuration for [{{bold}}{worker_pool}{{reset}}] is being'
' applied asynchronously.'.format(worker_pool=worker_pool_obj.name)
)
elif dry_run:
pretty_print.Success(
'New configuration has been validated for worker pool '
'[{{bold}}{worker_pool}{{reset}}].'.format(
worker_pool=worker_pool_obj.name
)
)
else:
pretty_print.Success(
'New configuration has been applied to worker pool '
'[{{bold}}{worker_pool}{{reset}}].'.format(
worker_pool=worker_pool_obj.name
)
)
def Run(self, args):
"""Create or Update service from YAML."""
run_messages = apis.GetMessagesModule(
global_methods.SERVERLESS_API_NAME,
global_methods.SERVERLESS_API_VERSION,
)
worker_pool_dict = dict(args.FILE)
# Clear the status field since it is ignored by Cloud Run APIs and can cause
# issues trying to convert to a message.
if 'status' in worker_pool_dict:
del worker_pool_dict['status']
if (
'spec' not in worker_pool_dict
or 'template' not in worker_pool_dict['spec']
):
raise exceptions.ConfigurationError(
'spec.template is required but missing. '
'Please check the content in your yaml file.'
)
# If spec.template.metadata is not set, add an empty one so that client
# annotations can be added.
if 'metadata' not in worker_pool_dict['spec']['template']:
worker_pool_dict['spec']['template']['metadata'] = {}
# For cases where YAML contains the project number as metadata.namespace,
# preemptively convert them to a string to avoid validation failures.
namespace = worker_pool_dict.get('metadata', {}).get('namespace', None)
if namespace is not None and not isinstance(namespace, str):
worker_pool_dict['metadata']['namespace'] = str(namespace)
new_worker_pool = None # this avoids a lot of errors.
try:
raw_worker_pool = messages_util.DictToMessageWithErrorCheck(
worker_pool_dict, run_messages.WorkerPool
)
new_worker_pool = worker_pool.WorkerPool(raw_worker_pool, run_messages)
except messages_util.ScalarTypeMismatchError as e:
exceptions.MaybeRaiseCustomFieldMismatch(
e,
help_text=(
'Please make sure that the YAML file matches the Cloud Run '
'worker pool definition spec in'
' https://cloud.google.com/run/docs/reference/rest/v1/namespaces.workerpools#WorkerPool'
),
)
# Namespace must match project (or will default to project if not
# specified).
namespace = properties.VALUES.core.project.Get()
if new_worker_pool.metadata.namespace is not None:
project = namespace
project_number = projects_util.GetProjectNumber(namespace)
namespace = new_worker_pool.metadata.namespace
if namespace != project and namespace != str(project_number):
raise exceptions.ConfigurationError(
'Namespace must be project ID [{}] or quoted number [{}] for '
'Cloud Run (fully managed).'.format(project, project_number)
)
new_worker_pool.metadata.namespace = namespace
changes = self._GetBaseChanges(new_worker_pool, args)
worker_pool_ref = resources.REGISTRY.Parse(
new_worker_pool.metadata.name,
params={
'namespacesId': new_worker_pool.metadata.namespace,
},
collection='run.namespaces.workerpools',
)
region_label = (
new_worker_pool.region if new_worker_pool.is_managed else None
)
conn_context = self._ConnectionContext(args, region_label)
dry_run = args.dry_run if hasattr(args, 'dry_run') else False
action = (
'Validating new configuration for'
if dry_run
else 'Applying new configuration to'
)
with serverless_operations.Connect(conn_context) as client:
worker_pool_obj = client.GetWorkerPool(worker_pool_ref)
pretty_print.Info(
run_messages_util.GetStartDeployMessage(
conn_context,
worker_pool_ref,
operation=action,
resource_kind_lower='workerpool',
)
)
deployment_stages = stages.WorkerPoolStages()
header = (
'Deploying...' if worker_pool_obj else 'Deploying new worker pool...'
)
if dry_run:
header = 'Validating...'
with progress_tracker.StagedProgressTracker(
header,
deployment_stages,
failure_message='Deployment failed',
suppress_output=args.async_ or dry_run,
) as tracker:
worker_pool_obj = client.ReplaceWorkerPool(
worker_pool_ref,
changes,
tracker,
asyn=args.async_,
dry_run=dry_run,
)
self._PrintSuccessMessage(worker_pool_obj, dry_run, args)
return worker_pool_obj

View File

@@ -0,0 +1,40 @@
# -*- 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.
"""The gcloud run worker-pools revisions group."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import flags
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class Revisions(base.Group):
"""View and manage your Cloud Run WorkerPools revisions.
This set of commands can be used to view and manage your existing Cloud Run
WorkerPools revisions.
"""
detailed_help = {
'EXAMPLES': """
To list your existing worker pools revisions, run:
$ {command} list
""",
}

View File

@@ -0,0 +1,118 @@
# -*- 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 for deleting a worker pool revision."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import pretty_print
from googlecloudsdk.command_lib.run import resource_args
from googlecloudsdk.command_lib.run.v2 import deletion
from googlecloudsdk.command_lib.run.v2 import worker_pools_operations
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class Delete(base.Command):
"""Delete a worker pool revision."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To delete a revision `rev1` of a worker pool `worker1` in us-central1:
$ {command} rev1 --region=us-central1 --workerpool=worker1
""",
}
@staticmethod
def CommonArgs(parser):
revision_presentation = presentation_specs.ResourcePresentationSpec(
'WORKER_POOL_REVISION',
resource_args.GetV2WorkerPoolRevisionResourceSpec(prompt=True),
'Worker pool revision to delete.',
required=True,
prefixes=False,
)
concept_parsers.ConceptParser([revision_presentation]).AddToParser(parser)
# TODO(b/366115709): Add WorkerPoolRevision printer.
flags.AddAsyncFlag(parser)
@staticmethod
def Args(parser):
Delete.CommonArgs(parser)
def Run(self, args):
"""Delete a worker pool revision."""
def DeriveRegionalEndpoint(endpoint):
region = args.CONCEPTS.worker_pool_revision.Parse().locationsId
return region + '-' + endpoint
worker_pool_revision_ref = args.CONCEPTS.worker_pool_revision.Parse()
flags.ValidateResource(worker_pool_revision_ref)
console_io.PromptContinue(
message='Revision [{revision}] will be deleted.'.format(
revision=worker_pool_revision_ref.revisionsId
),
throw_if_unattended=True,
cancel_on_no=True,
)
run_client = apis.GetGapicClientInstance(
'run', 'v2', address_override_func=DeriveRegionalEndpoint
)
worker_pools_client = worker_pools_operations.WorkerPoolsOperations(
run_client
)
def DeleteWithExistenceCheck(worker_pool_revision_ref):
response = worker_pools_client.DeleteRevision(worker_pool_revision_ref)
if not response:
raise exceptions.ArgumentError(
'Cannot find revision [{revision}] under worker pool'
' [{worker_pool}] in region [{region}]'.format(
revision=worker_pool_revision_ref.revisionsId,
worker_pool=worker_pool_revision_ref.workerPoolsId,
region=worker_pool_revision_ref.locationsId,
)
)
# TODO: b/390067647 - Use response.result() once the issue is fixed
deletion.Delete(
worker_pool_revision_ref,
worker_pools_client.GetRevision,
DeleteWithExistenceCheck,
args.async_,
)
if args.async_:
pretty_print.Success(
'Revision [{}] is being deleted.'.format(
worker_pool_revision_ref.revisionsId
)
)
else:
log.DeletedResource(worker_pool_revision_ref.revisionsId, 'revision')

View File

@@ -0,0 +1,90 @@
# -*- 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 for obtaining details about a given worker pool revision."""
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import connection_context
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import resource_args
from googlecloudsdk.command_lib.run import serverless_operations
from googlecloudsdk.command_lib.run.printers import export_printer
from googlecloudsdk.command_lib.run.printers import worker_pool_revision_printer
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core.resource import resource_printer
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class Describe(base.DescribeCommand):
"""Obtain details about a given worker pool revision."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To describe a revision `rev.1` of a worker pool `worker1` in us-central1:
$ {command} rev.1 --region=us-central1 --workerpool=worker1
""",
}
@staticmethod
def CommonArgs(parser):
flags.AddRegionArg(parser)
revision_presentation = presentation_specs.ResourcePresentationSpec(
'WORKER_POOL_REVISION',
resource_args.GetRevisionResourceSpec(is_worker_pool_revision=True),
'Worker pool revision to describe.',
required=True,
prefixes=False,
)
concept_parsers.ConceptParser([revision_presentation]).AddToParser(parser)
resource_printer.RegisterFormatter(
worker_pool_revision_printer.REVISION_PRINTER_FORMAT,
worker_pool_revision_printer.WorkerPoolRevisionPrinter,
)
parser.display_info.AddFormat(
worker_pool_revision_printer.REVISION_PRINTER_FORMAT
)
resource_printer.RegisterFormatter(
export_printer.EXPORT_PRINTER_FORMAT,
export_printer.ExportPrinter,
)
@staticmethod
def Args(parser):
Describe.CommonArgs(parser)
def Run(self, args):
"""Show details about a revision."""
conn_context = connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack()
)
revision_ref = args.CONCEPTS.worker_pool_revision.Parse()
with serverless_operations.Connect(conn_context) as client:
wrapped_revision = client.GetRevision(revision_ref)
# If the revision is not a worker pool revision, we should not show it.
if not wrapped_revision or wrapped_revision.worker_pool_name is None:
raise exceptions.ArgumentError(
'Cannot find revision [{}]'.format(revision_ref.revisionsId)
)
return wrapped_revision

View File

@@ -0,0 +1,127 @@
# -*- 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 for listing available worker pool revisions."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import commands
from googlecloudsdk.command_lib.run import connection_context
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import platforms
from googlecloudsdk.command_lib.run import pretty_print
from googlecloudsdk.command_lib.run import resource_args
from googlecloudsdk.command_lib.run import serverless_operations
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core import log
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class List(commands.List):
"""List available worker pool revisions."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To list all revisions in a worker pool `foo`:
$ {command} --worker-pool=foo
""",
}
@classmethod
def CommonArgs(cls, parser):
worker_pool_presentation = presentation_specs.ResourcePresentationSpec(
'--namespace',
resource_args.GetNamespaceResourceSpec(),
'Namespace to list revisions in.',
required=True,
prefixes=False,
hidden=True,
)
concept_parsers.ConceptParser([worker_pool_presentation]).AddToParser(
parser
)
flags.AddWorkerPoolFlag(parser)
flags.AddRegionArg(parser)
parser.display_info.AddFormat(
'table('
'{ready_column},'
'name:label=REVISION,'
'active.yesno(yes="yes", no=""),'
'worker_pool_name:label=WORKER_POOL:sort=1,'
'creation_timestamp.date("%Y-%m-%d %H:%M:%S %Z"):'
'label=DEPLOYED:sort=2:reverse,'
'author:label="DEPLOYED BY"):({alias})'.format(
ready_column=pretty_print.READY_COLUMN,
alias=commands.SATISFIES_PZS_ALIAS,
)
)
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
def _FilterServiceRevisions(self, revisions):
"""Filters out revisions that are service revisions.
Per discussion with jmahood@, we want to make sure that all resources are
self-contained, so none of the describe/list commands should mix the
resource type.
Args:
revisions: List of revisions to filter.
Returns:
List of revisions that are worker pool revisions.
"""
return list(filter(lambda rev: rev.worker_pool_name is not None, revisions))
def Run(self, args):
"""List available revisions."""
label_selector = None
worker_pool_name = args.worker_pool
conn_context = connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack()
)
namespace_ref = args.CONCEPTS.namespace.Parse()
with serverless_operations.Connect(conn_context) as client:
self.SetCompleteApiEndpoint(conn_context.endpoint)
if platforms.GetPlatform() != platforms.PLATFORM_MANAGED:
location_msg = ' in [{}]'.format(conn_context.cluster_location)
log.status.Print(
'For cluster [{cluster}]{zone}:'.format(
cluster=conn_context.cluster_name,
zone=location_msg if conn_context.cluster_location else '',
)
)
if worker_pool_name is not None:
label_selector = 'run.googleapis.com/workerPool = {}'.format(
worker_pool_name
)
for rev in self._FilterServiceRevisions(
client.ListRevisions(
namespace_ref, label_selector, args.limit, args.page_size
)
):
yield rev

View File

@@ -0,0 +1,39 @@
# -*- 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.
"""Group definition for worker pools revisions logs."""
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)
class Logs(base.Group):
"""Read logs for Cloud Run worker pools revisions."""
detailed_help = {
'EXAMPLES': """
To tail logs for a worker pool revision, run:
$ {command} tail my-worker-pool-revision
To read logs for a worker pool revision, run:
$ {command} read my-worker-pool-revision
""",
}

View File

@@ -0,0 +1,98 @@
# -*- 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 read logs for a worker pool revision."""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
from googlecloudsdk.api_lib.logging import common
from googlecloudsdk.api_lib.logging.formatter import FormatLog
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.logs import read as read_logs_lib
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.core import log
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Read(base.Command):
"""Read logs for a Cloud Run worker pool revision."""
detailed_help = {
'DESCRIPTION': """\
{command} reads log entries. Log entries matching *--log-filter* are
returned in order of decreasing timestamps, most-recent entries first.
If the log entries come from multiple logs, then entries from
different logs might be intermingled in the results.
""",
'EXAMPLES': """\
To read log entries from for a Cloud Run worker pool revision, run:
$ {command} my-revision
To read log entries with severity ERROR or higher, run:
$ {command} my-revision --log-filter="severity>=ERROR"
To read log entries written in a specific time window, run:
$ {command} my-revision --log-filter='timestamp<="2015-05-31T23:59:59Z" AND timestamp>="2015-05-31T00:00:00Z"'
To read up to 10 log entries in your revision payloads that include
the word `SearchText` and format the output in `JSON` format, run:
$ {command} my-revision --log-filter="textPayload:SearchText" --limit=10 --format=json
Detailed information about filters can be found at:
[](https://cloud.google.com/logging/docs/view/advanced_filters)
""",
}
@staticmethod
def Args(parser):
read_logs_lib.LogFilterArgs(parser)
read_logs_lib.LoggingReadArgs(parser)
parser.add_argument(
'revision', help='Name for a Cloud Run worker pool revision.'
)
def Run(self, args):
filters = [args.log_filter] if args.IsSpecified('log_filter') else []
filters.append('resource.labels.revision_name = %s' % args.revision)
filters.append('resource.type = %s \n' % 'cloud_run_worker_pool')
filters.append(
'resource.labels.location = %s \n' % flags.GetRegion(args, prompt=True)
)
filters.append('severity >= DEFAULT \n')
filters += read_logs_lib.MakeTimestampFilters(args)
lines = []
logs = common.FetchLogs(
read_logs_lib.JoinFilters(filters),
order_by=args.order,
limit=args.limit,
)
for log_line in logs:
output_log = FormatLog(log_line)
if output_log:
lines.append(output_log)
for line in reversed(lines):
log.out.Print(line)

View File

@@ -0,0 +1,80 @@
# -*- 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 tail logs for a worker pool revision."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.logs import read as read_logs_lib
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import streaming
from googlecloudsdk.core import properties
from googlecloudsdk.core.credentials import store
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Tail(base.BinaryBackedCommand):
"""Tail logs for a Cloud Run worker pool revision."""
detailed_help = {
'DESCRIPTION': """\
{command} tails log-entries for a particular
Cloud Run worker pool revision in real time. The log entries are formatted for
consumption in a terminal.
""",
'EXAMPLES': """\
To tail log entries for a Cloud Run worker pool revision, run:
$ {command} my-revision
To tail log entries with severity ERROR or higher, run:
$ {command} my-revision --log-filter="severity>=ERROR"
Detailed information about filters can be found at:
[](https://cloud.google.com/logging/docs/view/advanced_filters)
""",
}
@staticmethod
def Args(parser):
parser.add_argument(
'revision', help='Name for a Cloud Run worker pool revision.'
)
read_logs_lib.LogFilterArgs(parser)
def Run(self, args):
filters = []
if args.IsSpecified('log_filter'):
filters.append(args.log_filter)
filters.append('resource.type=%s' % 'cloud_run_worker_pool')
filters.append('resource.labels.revision_name=%s' % args.revision)
filters.append(
'resource.labels.location=%s' % flags.GetRegion(args, prompt=True)
)
filters.append('severity>=DEFAULT')
project_id = properties.VALUES.core.project.Get(required=True)
filter_str = ' '.join(filters)
command_executor = streaming.LogStreamingWrapper()
response = command_executor(
project_id=project_id,
log_format='run',
log_filter=filter_str,
token=store.GetFreshAccessTokenIfEnabled(),
)
return self._DefaultOperationResponseHandler(response)

View File

@@ -0,0 +1,40 @@
- release_tracks: [ALPHA, BETA]
help_text:
brief: Set the IAM policy for a worker pool.
description: |
This command replaces the existing IAM policy for a worker pool, given a worker pool
and a file encoded in JSON or YAML that contains the IAM policy. If the
given policy file specifies an "etag" value, then the replacement will
succeed only if the policy already in place matches that etag. (An etag
obtain via `get-iam-policy` will prevent the replacement if the policy
for the worker pool has been subsequently updated.) A policy file that does not
contain an etag value will replace any existing policy for the worker pool.
examples: |
The following command will read an IAM policy defined in a JSON file
'policy.json' and set it for a worker pool with identifier
'my-worker-pool'
$ {command} --region=us-central1 my-worker-pool policy.json
See https://cloud.google.com/iam/docs/managing-policies for details of the
policy file format and contents.
request:
collection: run.projects.locations.workerpools
modify_request_hooks:
- googlecloudsdk.command_lib.run.platforms:ValidatePlatformIsManaged
arguments:
resource:
help_text: The service for which to set the IAM policy.
spec: !REF googlecloudsdk.command_lib.run.resources:workerpool
ALPHA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion
BETA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,249 @@
# -*- 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 for updating env vars and other configuration info."""
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import config_changes as config_changes_mod
from googlecloudsdk.command_lib.run import container_parser
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import messages_util
from googlecloudsdk.command_lib.run import pretty_print
from googlecloudsdk.command_lib.run import resource_args
from googlecloudsdk.command_lib.run import resource_name_conversion
from googlecloudsdk.command_lib.run import stages
from googlecloudsdk.command_lib.run.v2 import config_changes as v2_config_changes_mod
from googlecloudsdk.command_lib.run.v2 import flags_parser
from googlecloudsdk.command_lib.run.v2 import worker_pools_operations
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core.console import progress_tracker
def ContainerArgGroup(release_track=base.ReleaseTrack.GA):
"""Returns an argument group with all container update args."""
help_text = """
Container Flags
The following flags apply to the container.
"""
group = base.ArgumentGroup(help=help_text)
group.AddArgument(flags.ImageArg(required=False))
group.AddArgument(flags.MutexEnvVarsFlags(release_track=release_track))
group.AddArgument(flags.MemoryFlag())
group.AddArgument(flags.CpuFlag())
group.AddArgument(flags.CommandFlag())
group.AddArgument(flags.ArgsFlag())
group.AddArgument(flags_parser.SecretsFlags())
group.AddArgument(flags.DependsOnFlag())
group.AddArgument(flags.AddVolumeMountFlag())
group.AddArgument(flags.RemoveVolumeMountFlag())
group.AddArgument(flags.ClearVolumeMountsFlag())
# ALPHA and BETA features
if (
release_track == base.ReleaseTrack.ALPHA
or release_track == base.ReleaseTrack.BETA
):
group.AddArgument(flags.GpuFlag())
return group
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class Update(base.Command):
"""Update Cloud Run environment variables and other configuration settings."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To update one or more env vars:
$ {command} myworkerpool --update-env-vars=KEY1=VALUE1,KEY2=VALUE2
""",
}
input_flags = (
'`--update-env-vars`, `--memory`, `--concurrency`, `--timeout`,'
' `--connectivity`, `--image`'
)
@classmethod
def CommonArgs(cls, parser):
flags.AddBinAuthzPolicyFlags(parser)
flags.AddBinAuthzBreakglassFlag(parser)
flags_parser.AddCloudSQLFlags(parser)
flags.AddCmekKeyFlag(parser)
flags.AddCmekKeyRevocationActionTypeFlag(parser)
flags.AddDescriptionFlag(parser)
flags.AddEgressSettingsFlag(parser)
flags.AddEncryptionKeyShutdownHoursFlag(parser)
flags.AddRevisionSuffixArg(parser)
flags.AddRuntimeFlag(parser)
flags.AddVolumesFlags(parser, cls.ReleaseTrack())
flags.AddScalingFlag(
parser, release_track=cls.ReleaseTrack(), resource_kind='worker'
)
flags.AddVpcNetworkGroupFlagsForUpdate(parser, resource_kind='worker')
flags.RemoveContainersFlag().AddToParser(parser)
flags.AddAsyncFlag(parser)
flags.AddLabelsFlags(parser)
flags.AddGeneralAnnotationFlags(parser)
flags.AddServiceAccountFlag(parser)
flags.AddClientNameAndVersionFlags(parser)
flags.AddNoPromoteFlag(parser)
flags.AddGpuTypeFlag(parser)
flags.GpuZonalRedundancyFlag(parser)
worker_pool_presentation = presentation_specs.ResourcePresentationSpec(
'WORKER_POOL',
resource_args.GetV2WorkerPoolResourceSpec(prompt=True),
'WorkerPool to update the configuration of.',
required=True,
prefixes=False,
)
concept_parsers.ConceptParser([worker_pool_presentation]).AddToParser(
parser
)
# No output by default, can be overridden by --format
parser.display_info.AddFormat('none')
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
container_args = ContainerArgGroup(cls.ReleaseTrack())
container_parser.AddContainerFlags(
parser, container_args, cls.ReleaseTrack()
)
def _AssertChanges(self, changes, flags_text, ignore_empty):
if ignore_empty:
return
if not changes or (
len(changes) == 1
and isinstance(
changes[0],
v2_config_changes_mod.SetClientNameAndVersionChange,
)
):
raise exceptions.NoConfigurationChangeError(
'No configuration change requested. '
'Did you mean to include the flags {}?'.format(flags_text)
)
def _GetBaseChanges(self, args, ignore_empty=False):
"""Returns the worker pool config changes with some default settings."""
changes = flags_parser.GetWorkerPoolConfigurationChanges(
args, self.ReleaseTrack()
)
self._AssertChanges(changes, self.input_flags, ignore_empty)
changes.insert(
0,
v2_config_changes_mod.BinaryAuthorizationChange(
breakglass_justification=None
),
)
changes.append(
v2_config_changes_mod.SetLaunchStageChange(self.ReleaseTrack())
)
return changes
def Run(self, args):
"""Update the worker-pool resource."""
worker_pool_ref = args.CONCEPTS.worker_pool.Parse()
flags.ValidateResource(worker_pool_ref)
def DeriveRegionalEndpoint(endpoint):
region = args.CONCEPTS.worker_pool.Parse().locationsId
return region + '-' + endpoint
run_client = apis.GetGapicClientInstance(
'run', 'v2', address_override_func=DeriveRegionalEndpoint
)
worker_pools_client = worker_pools_operations.WorkerPoolsOperations(
run_client
)
worker_pool = worker_pools_client.GetWorkerPool(worker_pool_ref)
messages_util.MaybeLogDefaultGpuTypeMessageForV2Resource(args, worker_pool)
config_changes = self._GetBaseChanges(args)
if worker_pool:
header = 'Updating...'
failure_message = 'Update failed'
result_message = 'updating'
else:
header = 'Deploying new worker pool...'
failure_message = 'Deployment failed'
result_message = 'deploying'
creates_revision = config_changes_mod.AdjustsTemplate(config_changes)
with progress_tracker.StagedProgressTracker(
header,
stages.WorkerPoolStages(include_create_revision=creates_revision),
failure_message=failure_message,
suppress_output=args.async_,
):
# TODO: b/432102851 - Add retry logic with zonal redundancy off.
response = worker_pools_client.ReleaseWorkerPool(
worker_pool_ref, config_changes, prefetch=worker_pool
)
if not response:
raise exceptions.ArgumentError(
'Cannot update worker pool [{}]'.format(
worker_pool_ref.workerPoolsId
)
)
if args.async_:
pretty_print.Success(
'Worker pool [{{bold}}{worker_pool}{{reset}}] is {result_message} '
'asynchronously.'.format(
worker_pool=worker_pool_ref.workerPoolsId,
result_message=result_message,
)
)
else:
response.result() # Wait for the operation to complete.
msg = 'Worker pool [{{bold}}{worker_pool}{{reset}}]'.format(
worker_pool=worker_pool_ref.workerPoolsId
)
if response.metadata and response.metadata.latest_created_revision:
rev = resource_name_conversion.GetNameFromFullChildName(
response.metadata.latest_created_revision
)
msg += ' revision [{{bold}}{rev}{{reset}}]'.format(rev=rev)
if worker_pool and not creates_revision:
pretty_print.Success(msg + ' has been updated.')
else:
pretty_print.Success(msg + ' has been deployed.')
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class AlphaUpdate(Update):
"""Update a Cloud Run worker-pool."""
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
flags.AddWorkerPoolMinInstancesFlag(parser)
flags.AddWorkerPoolMaxInstancesFlag(parser)
container_args = ContainerArgGroup(cls.ReleaseTrack())
container_parser.AddContainerFlags(
parser, container_args, cls.ReleaseTrack()
)
AlphaUpdate.__doc__ = Update.__doc__

View File

@@ -0,0 +1,150 @@
# -*- 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 for updating instances split for worker-pool resource."""
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import exceptions as serverless_exceptions
from googlecloudsdk.command_lib.run import flags
from googlecloudsdk.command_lib.run import pretty_print
from googlecloudsdk.command_lib.run import resource_args
from googlecloudsdk.command_lib.run import stages
from googlecloudsdk.command_lib.run.printers.v2 import instance_split_printer
from googlecloudsdk.command_lib.run.v2 import config_changes as config_changes_mod
from googlecloudsdk.command_lib.run.v2 import flags_parser
from googlecloudsdk.command_lib.run.v2 import instance_split
from googlecloudsdk.command_lib.run.v2 import worker_pools_operations
from googlecloudsdk.command_lib.util.concepts import concept_parsers
from googlecloudsdk.command_lib.util.concepts import presentation_specs
from googlecloudsdk.core.console import progress_tracker
from googlecloudsdk.core.resource import resource_printer
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
class AdjustInstanceSplit(base.Command):
"""Adjust the instance assignments for a Cloud Run worker-pool."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To assign 10% of instances to revision my-worker-pool-s5sxn and
90% of instances to revision my-worker-pool-cp9kw run:
$ {command} my-worker-pool --to-revisions=my-worker-pool-s5sxn=10,my-worker-pool-cp9kw=90
To increase the instances to revision my-worker-pool-s5sxn to 20% and
by reducing the instances to revision my-worker-pool-cp9kw to 80% run:
$ {command} my-worker-pool --to-revisions=my-worker-pool-s5sxn=20
To rollback to revision my-worker-pool-cp9kw run:
$ {command} my-worker-pool --to-revisions=my-worker-pool-cp9kw=100
To assign 100% of instances to the current or future LATEST revision
run:
$ {command} my-worker-pool --to-latest
You can also refer to the current or future LATEST revision in
--to-revisions by the string "LATEST". For example, to set 10% of
instances to always float to the latest revision:
$ {command} my-worker-pool --to-revisions=LATEST=10
""",
}
@classmethod
def CommonArgs(cls, parser):
worker_pool_presentation = presentation_specs.ResourcePresentationSpec(
'WORKER_POOL',
resource_args.GetV2WorkerPoolResourceSpec(prompt=True),
'WorkerPool to update instance split of.',
required=True,
prefixes=False,
)
concept_parsers.ConceptParser([worker_pool_presentation]).AddToParser(
parser
)
flags.AddAsyncFlag(parser)
flags.AddUpdateInstanceSplitFlags(parser)
flags.AddBinAuthzBreakglassFlag(parser)
resource_printer.RegisterFormatter(
instance_split_printer.INSTANCE_SPLIT_PRINTER_FORMAT,
instance_split_printer.InstanceSplitPrinter,
)
parser.display_info.AddFormat(
instance_split_printer.INSTANCE_SPLIT_PRINTER_FORMAT
)
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
def _GetBaseChanges(self, args):
"""Returns the worker pool config changes with some default settings."""
changes = flags_parser.GetWorkerPoolConfigurationChanges(
args, self.ReleaseTrack()
)
if not changes:
raise serverless_exceptions.NoConfigurationChangeError(
'No instance split configuration change requested.'
)
changes.insert(
0,
config_changes_mod.BinaryAuthorizationChange(
breakglass_justification=None
),
)
changes.append(config_changes_mod.SetLaunchStageChange(self.ReleaseTrack()))
return changes
def Run(self, args):
"""Update the instance split for the worker."""
worker_pool_ref = args.CONCEPTS.worker_pool.Parse()
flags.ValidateResource(worker_pool_ref)
def DeriveRegionalEndpoint(endpoint):
region = args.CONCEPTS.worker_pool.Parse().locationsId
return region + '-' + endpoint
run_client = apis.GetGapicClientInstance(
'run', 'v2', address_override_func=DeriveRegionalEndpoint
)
worker_pools_client = worker_pools_operations.WorkerPoolsOperations(
run_client
)
config_changes = self._GetBaseChanges(args)
with progress_tracker.StagedProgressTracker(
'Updating instance split...',
stages.UpdateInstanceSplitStages(),
failure_message='Updating instance split failed',
suppress_output=args.async_,
):
response = worker_pools_client.UpdateInstanceSplit(
worker_pool_ref,
config_changes,
)
if args.async_:
pretty_print.Success('Updating instance split asynchronously.')
else:
response.result() # Wait for the operation to complete.
return instance_split.GetInstanceSplitPairs(response.metadata)