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,53 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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 services 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
class Services(base.Group):
"""View and manage your Cloud Run services.
This set of commands can be used to view and manage your existing Cloud Run
services.
To create new deployments, use `{parent_command} deploy`.
"""
detailed_help = {
'EXAMPLES': """
To list your deployed services, run:
$ {command} list
""",
}
@staticmethod
def Args(parser):
"""Adds --platform and the various related args."""
flags.AddPlatformAndLocationFlags(parser)
def Filter(self, context, args):
"""Runs before command.Run and validates platform with passed args."""
# Ensures a platform is set on the run/platform property and
# all other passed args are valid for this platform and release track.
flags.GetAndValidatePlatform(args, self.ReleaseTrack(), flags.Product.RUN)
return context

View File

@@ -0,0 +1,46 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Add IAM policy binding to a Cloud Run service.
description: |
Add an IAM policy binding to the IAM policy of a Cloud Run service. One binding consists of a member,
and a role.
examples: |
To add an IAM policy binding for the role of 'roles/run.invoker' for the user 'test-user@gmail.com'
with service 'my-service' and region 'us-central1', run:
$ {command} my-service --region='us-central1' --member='user:test-user@gmail.com' --role='roles/run.invoker'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: run.projects.locations.services
modify_request_hooks:
- googlecloudsdk.command_lib.run.platforms:ValidatePlatformIsManaged
arguments:
resource:
help_text: The service for which to add IAM policy binding to.
spec: !REF googlecloudsdk.command_lib.run.resources:service
# The --region flag is specified at the group level, so don't try to add it here
removed_flags: ['region']
command_level_fallthroughs:
region:
- arg_name: 'region'
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
GA:
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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 service."""
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 deletion
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
from googlecloudsdk.core import log
from googlecloudsdk.core.console import console_io
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Delete(base.Command):
"""Delete a service."""
detailed_help = {
'DESCRIPTION':
"""\
{description}
""",
'EXAMPLES':
"""\
To delete a service:
$ {command} <service-name>
""",
}
@staticmethod
def CommonArgs(parser):
service_presentation = presentation_specs.ResourcePresentationSpec(
'SERVICE',
resource_args.GetServiceResourceSpec(),
'Service to delete.',
required=True,
prefixes=False)
concept_parsers.ConceptParser([service_presentation]).AddToParser(parser)
flags.AddAsyncFlag(parser, default_async_for_cluster=True)
@staticmethod
def Args(parser):
Delete.CommonArgs(parser)
def _ConnectionContext(self, args):
return connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack()
)
def Run(self, args):
"""Delete a service."""
conn_context = self._ConnectionContext(args)
service_ref = args.CONCEPTS.service.Parse()
flags.ValidateResource(service_ref)
console_io.PromptContinue(
message='Service [{service}] will be deleted.'.format(
service=service_ref.servicesId),
throw_if_unattended=True,
cancel_on_no=True)
async_ = deletion.AsyncOrDefault(args.async_)
with serverless_operations.Connect(conn_context) as client:
deletion.Delete(
service_ref, client.GetService, client.DeleteService, async_
)
if async_:
pretty_print.Success(
'Service [{}] is being deleted.'.format(service_ref.servicesId)
)
else:
log.DeletedResource(service_ref.servicesId, 'service')
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class AlphaDelete(Delete):
"""Delete a service."""
@staticmethod
def Args(parser):
Delete.CommonArgs(parser)
AlphaDelete.__doc__ = Delete.__doc__

View File

@@ -0,0 +1,121 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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 service."""
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 import threat_detection_util as crtd_util
from googlecloudsdk.command_lib.run.printers import export_printer
from googlecloudsdk.command_lib.run.printers import service_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
def _GetFormatter(is_multi_region, is_alpha):
if is_multi_region:
return service_printer.MultiRegionServicePrinter
elif is_alpha:
return service_printer.ServicePrinterAlpha
else:
return service_printer.ServicePrinter
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Describe(base.Command):
"""Obtain details about a given service."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To obtain details about a given service:
$ {command} <service-name>
To get those details in the YAML format:
$ {command} <service-name> --format=yaml
To get them in YAML format suited to export (omitting metadata
specific to this deployment and status info):
$ {command} <service-name> --format=export
""",
}
@staticmethod
def CommonArgs(parser, is_multi_region=False, is_alpha=False):
service_presentation = presentation_specs.ResourcePresentationSpec(
'SERVICE',
resource_args.GetServiceResourceSpec(),
'Service to describe.',
required=True,
prefixes=False,
)
concept_parsers.ConceptParser([service_presentation]).AddToParser(parser)
formatter = _GetFormatter(is_multi_region, is_alpha)
resource_printer.RegisterFormatter(
service_printer.SERVICE_PRINTER_FORMAT, formatter
)
parser.display_info.AddFormat(service_printer.SERVICE_PRINTER_FORMAT)
resource_printer.RegisterFormatter(
export_printer.EXPORT_PRINTER_FORMAT,
export_printer.ExportPrinter,
)
@staticmethod
def Args(parser):
Describe.CommonArgs(parser, is_alpha=False)
def _ConnectionContext(self, args):
return connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack()
)
def Run(self, args):
"""Obtain details about a given service."""
conn_context = self._ConnectionContext(args)
service_ref = args.CONCEPTS.service.Parse()
flags.ValidateResource(service_ref)
with serverless_operations.Connect(conn_context) as client:
serv = client.GetService(service_ref)
crtd_util.UpdateThreatDetectionState(serv, client)
if not serv:
raise exceptions.ArgumentError(
'Cannot find service [{}]'.format(service_ref.servicesId)
)
return serv
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class DescribeAlpha(Describe):
"""Obtain details about a given service."""
@staticmethod
def Args(parser):
Describe.CommonArgs(parser, is_alpha=True)

View File

@@ -0,0 +1,43 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Get the IAM policy for a Cloud Run service.
description: |
This command gets the IAM policy for a service. 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 service, run:
$ {command} --region=us-central1 my-service
request:
collection: run.projects.locations.services
modify_request_hooks:
- googlecloudsdk.command_lib.run.platforms:ValidatePlatformIsManaged
arguments:
resource:
help_text: The service for which to display the IAM policy.
spec: !REF googlecloudsdk.command_lib.run.resources:service
# The --region flag is specified at the group level, so don't try to add it here
removed_flags: ['region']
command_level_fallthroughs:
region:
- arg_name: 'region'
ALPHA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion
BETA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion
GA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*- #
# Copyright 2017 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 services."""
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 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.BETA, base.ReleaseTrack.GA)
class List(commands.List):
"""List available services."""
detailed_help = {
'DESCRIPTION':
"""\
{description}
""",
'EXAMPLES':
"""\
To list available services:
$ {command}
""",
}
@classmethod
def CommonArgs(cls, parser):
# Flags specific to connecting to a cluster
namespace_presentation = presentation_specs.ResourcePresentationSpec(
'--namespace',
resource_args.GetNamespaceResourceSpec(),
'Namespace to list services in.',
required=True,
prefixes=False,
hidden=True)
concept_parsers.ConceptParser([namespace_presentation
]).AddToParser(parser)
parser.display_info.AddUriFunc(cls._GetResourceUri)
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
def _SetFormat(self,
args,
show_region=False,
show_namespace=False,
show_description=False,
is_multi_region=False):
"""Set display format for output.
Args:
args: Namespace, the args namespace
show_region: bool, True to show region of listed services
show_namespace: bool, True to show namespace of listed services
show_description: bool, True to show description of listed services
is_multi_region: bool, True if the list is for multi-region services
"""
columns = [
pretty_print.READY_COLUMN,
'firstof(id,metadata.name):label=SERVICE',
]
if show_region:
columns.append('region:label={}'
.format('REGIONS' if is_multi_region else 'REGION'))
if show_namespace:
columns.append('namespace:label=NAMESPACE')
if show_description:
columns.append('description:label=DESCRIPTION')
columns.extend([
'domain:label=URL',
'last_modifier:label="LAST DEPLOYED BY"',
'last_transition_time:label="LAST DEPLOYED AT"',
])
args.GetDisplayInfo().AddFormat(
'table({columns}):({alias})'.format(
columns=','.join(columns), alias=commands.SATISFIES_PZS_ALIAS
)
)
def _GlobalList(self, client, args):
"""Provides the method to provide a regionless list."""
self._SetFormat(args, show_region=True)
return global_methods.ListServices(client)
def Run(self, args):
"""List available services."""
is_managed = platforms.GetPlatform() == platforms.PLATFORM_MANAGED
if is_managed and not args.IsSpecified('region'):
client = global_methods.GetServerlessClientInstance()
self.SetPartialApiEndpoint(client.url)
args.CONCEPTS.namespace.Parse() # Error if no proj.
# Don't consider region property here, we'll default to all regions
return commands.SortByName(self._GlobalList(client, args))
else:
conn_context = connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack())
self._SetFormat(
args, show_region=is_managed, show_namespace=(not is_managed))
namespace_ref = args.CONCEPTS.namespace.Parse()
with serverless_operations.Connect(conn_context) as client:
self.SetCompleteApiEndpoint(conn_context.endpoint)
if not is_managed:
location_msg = ''
project_msg = ''
if hasattr(conn_context, 'cluster_location'):
location_msg = ' in [{}]'.format(conn_context.cluster_location)
if hasattr(conn_context, 'cluster_project'):
project_msg = ' in project [{}]'.format(
conn_context.cluster_project)
log.status.Print('For cluster [{cluster}]{zone}{project}:'.format(
cluster=conn_context.cluster_name,
zone=location_msg,
project=project_msg))
return commands.SortByName(client.ListServices(namespace_ref))
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class AlphaList(List):
"""List available services."""
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
AlphaList.__doc__ = List.__doc__

View File

@@ -0,0 +1,40 @@
# -*- 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.
"""Group definition for logs."""
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 platforms
@base.UniverseCompatible
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Logs(base.Group):
"""Read logs for Cloud Run services."""
def _CheckPlatform(self):
if platforms.GetPlatform() != platforms.PLATFORM_MANAGED:
raise exceptions.PlatformError(
'This command group only supports reading logs for Cloud Run.'
)
def Filter(self, context, args):
self._CheckPlatform()
return context

View File

@@ -0,0 +1,95 @@
# -*- 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 to read logs for a service."""
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.UniverseCompatible
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA
)
class Read(base.Command):
"""Read logs for a Cloud Run service."""
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 Service, run:
$ {command} my-service
To read log entries with severity ERROR or higher, run:
$ {command} my-service --log-filter="severity>=ERROR"
To read log entries written in a specific time window, run:
$ {command} my-service --log-filter='timestamp<="2015-05-31T23:59:59Z" AND timestamp>="2015-05-31T00:00:00Z"'
To read up to 10 log entries in your service payloads that include the
word `SearchText` and format the output in `JSON` format, run:
$ {command} my-service --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('service', help='Name for a Cloud Run service.')
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_revision')
filters.append('resource.labels.service_name = %s \n' % args.service)
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 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command to tail logs for a service."""
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, base.ReleaseTrack.BETA)
class Tail(base.BinaryBackedCommand):
"""Tail logs for a Cloud Run service."""
detailed_help = {
'DESCRIPTION': """\
{command} tails log-entries for a particular
Cloud Run service in real time. The log entries are formatted for
consumption in a terminal.
""",
'EXAMPLES': """\
To tail log entries for a Cloud Run Service, run:
$ {command} my-service
To tail log entries with severity ERROR or higher, run:
$ {command} my-service --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('service', help='Name for a Cloud Run service.')
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_revision')
filters.append('resource.labels.service_name=%s' % args.service)
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,178 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command for proxying to a given service."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.config import config_helper
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
from googlecloudsdk.command_lib.run import platforms
from googlecloudsdk.command_lib.run import pretty_print
from googlecloudsdk.command_lib.run import proxy
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.credentials import store
@base.UniverseCompatible
class Proxy(base.BinaryBackedCommand):
"""Proxy a service to localhost authenticating as the active account or with the specified token.
Runs a server on localhost that proxies requests to the specified Cloud Run
Service with credentials attached.
You can use this to test services protected with IAM authentication.
The Cloud Run service must be reachable from the machine running this
command. For example, if the Cloud Run Service is configured to only allow
`internal` ingress, this command will not work from outside the service's
VPC network.
"""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To proxy the service 'my-service' at localhost port 8080:
$ {command} my-service --port=8080
To proxy the existing traffic tag 'my-tag' on the service 'my-service:
$ {command} my-service --tag=my-tag
""",
}
@staticmethod
def CommonArgs(parser):
service_presentation = presentation_specs.ResourcePresentationSpec(
'SERVICE',
resource_args.GetServiceResourceSpec(),
'Service to proxy locally.',
required=True,
prefixes=False,
)
flags.AddPortFlag(
parser,
help_text=(
'Local port number to expose the proxied service. '
'If not specified, it will be set to 8080.'
),
)
flags.AddTokenFlag(parser)
flags.AddDeployTagFlag(
parser,
help_text=(
'Traffic tag of the service to expose via the proxy. If not '
'specified, the default service URL will be proxied which may '
'serve different revisions based on traffic-splits. '
'Custom tags can be used to proxy specific revisions. Please see '
'https://cloud.google.com/run/docs/rollouts-rollbacks-traffic-migration#tags.'
),
)
concept_parsers.ConceptParser([service_presentation]).AddToParser(parser)
@staticmethod
def Args(parser):
Proxy.CommonArgs(parser)
def _CheckPlatform(self):
platform = platforms.GetPlatform()
if platform != platforms.PLATFORM_MANAGED:
raise exceptions.PlatformError(
'This command is only supported for fully managed Cloud Run.'
)
def Run(self, args):
self._CheckPlatform()
conn_context = connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack()
)
service_ref = args.CONCEPTS.service.Parse()
flags.ValidateResource(service_ref)
with serverless_operations.Connect(conn_context) as client:
serv = client.GetService(service_ref)
if not serv:
raise exceptions.ArgumentError(
messages_util.GetNotFoundMessage(conn_context, service_ref)
)
bind = '127.0.0.1:' + (args.port if args.port else '8080')
host = self._GetUrl(serv, args.tag, service_ref.servicesId)
command_executor = proxy.ProxyWrapper()
pretty_print.Info(
messages_util.GetStartDeployMessage(
conn_context,
service_ref,
'Proxying to',
)
)
pretty_print.Info('http://{} proxies to {}'.format(bind, host))
if args.token:
response = command_executor(host=host, token=args.token, bind=bind)
else:
# Keep restarting the proxy with fresh token before the token expires (1h)
# until hitting a failure.
while True:
response = command_executor(
host=host, token=_GetFreshIdToken(), bind=bind, duration='55m'
)
if response.failed:
break
return self._DefaultOperationResponseHandler(response)
def _GetUrl(self, serv, tag, serv_id):
if not serv.status:
raise exceptions.ArgumentError(
'Status of service [{}] is not ready'.format(serv_id)
)
if tag:
for t in serv.status.traffic:
if t.tag == tag:
if not t.url:
raise exceptions.ArgumentError(
'URL for tag [{}] in service [{}] is not ready'.format(
tag, serv_id
)
)
return t.url
raise exceptions.ArgumentError(
'Cannot find tag [{}] in service [{}].'.format(tag, serv_id)
)
# If not tag provided, use the default service URL.
if not serv.status.url:
raise exceptions.ArgumentError(
'URL for service [{}] is not ready'.format(serv_id)
)
return serv.status.url
def _GetFreshIdToken():
cred = store.LoadFreshCredential()
credential = config_helper.Credential(cred)
return credential.id_token

View File

@@ -0,0 +1,46 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Remove IAM policy binding of a Cloud Run service.
description: |
Remove an IAM policy binding from the IAM policy of a service. One binding consists of a member,
and a role.
examples: |
To remove an IAM policy binding for the role of 'roles/run.invoker' for the user 'test-user@gmail.com'
with service 'my-service' and region 'us-central1', run:
$ {command} my-service --region='us-central1' --member='user:test-user@gmail.com' --role='roles/run.invoker'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: run.projects.locations.services
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:service
# The --region flag is specified at the group level, so don't try to add it here
removed_flags: ['region']
command_level_fallthroughs:
region:
- arg_name: 'region'
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
GA:
iam:
enable_condition: true
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,244 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command 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 service
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 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.run import stages
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 import resources
from googlecloudsdk.core.console import progress_tracker
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Replace(base.Command):
"""Create or replace a service from a YAML service specification."""
detailed_help = {
'DESCRIPTION':
"""\
Creates or replaces a service from a YAML service specification.
""",
'EXAMPLES':
"""\
To replace the specification for a service defined in myservice.yaml
$ {command} myservice.yaml
""",
}
@classmethod
def CommonArgs(cls, parser):
# Flags specific to connecting to a cluster
namespace_presentation = presentation_specs.ResourcePresentationSpec(
'--namespace',
resource_args.GetNamespaceResourceSpec(),
'Namespace to replace service.',
required=True,
prefixes=False,
hidden=True)
concept_parsers.ConceptParser([namespace_presentation
]).AddToParser(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 Knative '
'service definition for the service to update or deploy.')
# 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_service, args): # used by child - pylint: disable=unused-argument
return [
config_changes.ReplaceServiceChange(new_service),
config_changes.SetLaunchStageAnnotationChange(self.ReleaseTrack()),
]
def _GetMultiRegionRegions(self, args, new_service, changes): # used by child - pylint: disable=unused-argument
return None
def _PrintSuccessMessage(self, service_obj, dry_run, args):
if args.async_:
pretty_print.Success(
'New configuration for [{{bold}}{serv}{{reset}}] is being applied '
'asynchronously.'.format(serv=service_obj.name)
)
elif dry_run:
pretty_print.Success(
'New configuration has been validated for service '
'[{{bold}}{serv}{{reset}}].'.format(serv=service_obj.name)
)
else:
pretty_print.Success(
'New configuration has been applied to service '
'[{{bold}}{serv}{{reset}}].\n'
'URL: {{bold}}{url}{{reset}}'.format(
serv=service_obj.name, url=service_obj.domain
)
)
def Run(self, args):
"""Create or Update service from YAML."""
run_messages = apis.GetMessagesModule(
global_methods.SERVERLESS_API_NAME,
global_methods.SERVERLESS_API_VERSION,
)
service_dict = dict(args.FILE)
# Clear the status to make migration from k8s deployments easier.
# Since a Deployment status will have several fields that Cloud Run doesn't
# support, trying to convert it to a message as-is will fail even though
# status is ignored by the server.
if 'status' in service_dict:
del service_dict['status']
# For cases where YAML contains the project number as metadata.namespace,
# preemptively convert them to a string to avoid validation failures.
namespace = service_dict.get('metadata', {}).get('namespace', None)
if namespace is not None and not isinstance(namespace, str):
service_dict['metadata']['namespace'] = str(namespace)
new_service = None # this avoids a lot of errors.
try:
raw_service = messages_util.DictToMessageWithErrorCheck(
service_dict, run_messages.Service)
new_service = service.Service(raw_service, run_messages)
except messages_util.ScalarTypeMismatchError as e:
exceptions.MaybeRaiseCustomFieldMismatch(
e,
help_text='Please make sure that the YAML file matches the Knative '
'service definition spec in https://kubernetes.io/docs/'
'reference/kubernetes-api/service-resources/service-v1/'
'#Service.')
# If managed, namespace must match project (or will default to project if
# not specified).
# If not managed, namespace simply must not conflict if specified in
# multiple places (or will default to "default" if not specified).
namespace = args.CONCEPTS.namespace.Parse().Name() # From flag or default
if new_service.metadata.namespace is not None:
if (args.IsSpecified('namespace') and
namespace != new_service.metadata.namespace):
raise exceptions.ConfigurationError(
'Namespace specified in file does not match passed flag.')
namespace = new_service.metadata.namespace
if platforms.GetPlatform() == platforms.PLATFORM_MANAGED:
project = properties.VALUES.core.project.Get()
project_number = projects_util.GetProjectNumber(project)
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_service.metadata.namespace = namespace
changes = self._GetBaseChanges(new_service, args)
service_ref = resources.REGISTRY.Parse(
new_service.metadata.name,
params={'namespacesId': new_service.metadata.namespace},
collection='run.namespaces.services')
region_label = new_service.region if new_service.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:
service_obj = client.GetService(service_ref)
regions = self._GetMultiRegionRegions(args, new_service, changes)
pretty_print.Info(
run_messages_util.GetStartDeployMessage(
conn_context, service_ref, operation=action
)
)
deployment_stages = stages.ServiceStages(regions_list=regions)
header = ('Deploying...' if service_obj else 'Deploying new service...')
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:
service_obj = client.ReleaseService(
service_ref,
changes,
self.ReleaseTrack(),
tracker,
asyn=args.async_,
allow_unauthenticated=None,
for_replace=True,
dry_run=dry_run,
multiregion_regions=regions,
)
self._PrintSuccessMessage(service_obj, dry_run, args)
return service_obj
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class AlphaReplace(Replace):
@classmethod
def Args(cls, parser):
Replace.CommonArgs(parser)
AlphaReplace.__doc__ = Replace.__doc__

View File

@@ -0,0 +1,49 @@
- release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Set the IAM policy for a service.
description: |
This command replaces the existing IAM policy for a service, given a service
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 service has been subsequently updated.) A policy file that does not
contain an etag value will replace any existing policy for the service.
examples: |
The following command will read an IAM policy defined in a JSON file
'policy.json' and set it for a service with identifier
'my-service'
$ {command} --region=us-central1 my-service 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.services
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:service
# The --region flag is specified at the group level, so don't try to add it here
removed_flags: ['region']
command_level_fallthroughs:
region:
- arg_name: 'region'
ALPHA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion
BETA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion
GA:
iam:
policy_version: 3
get_iam_policy_version_path: options_requestedPolicyVersion

View File

@@ -0,0 +1,70 @@
# -*- 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 SSH into a Cloud Run Service."""
from googlecloudsdk.api_lib.run import ssh as run_ssh
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import exceptions
from googlecloudsdk.command_lib.run import flags
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
@base.Hidden
@base.DefaultUniverseOnly
class Ssh(base.Command):
"""SSH into an instance."""
detailed_help = {
'DESCRIPTION': """\
Starts a secure, interactive shell session with a Cloud Run instance.
""",
'EXAMPLES': """\
To start an interactive shell session with a Cloud Run service:
$ {command} my-service --instance=my-instance-id
""",
}
@classmethod
def Args(cls, parser):
# Add flags for targeting a specific instance and container.
flags.AddInstanceArg(parser)
flags.AddContainerArg(parser)
# Add the service name as a required positional argument.
parser.add_argument(
'service',
help='The name of the service to SSH into.',
)
parser.add_argument(
'--iap-tunnel-url-override',
hidden=True,
help=(
'Allows for overriding the connection endpoint for integration '
'testing.'
),
)
def Run(self, args):
"""Connect to a running Cloud Run Service deployment."""
args.project = flags.GetProjectID(args)
args.region = flags.GetRegion(args, prompt=False)
if not args.region:
raise exceptions.ArgumentError(
'Missing required argument [region]. Set --region flag or set'
' run/region property.'
)
args.deployment_name = args.service
args.release_track = self.ReleaseTrack()
run_ssh.Ssh(args, run_ssh.Ssh.WorkloadType.SERVICE).Run()

View File

@@ -0,0 +1,356 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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.run import k8s_object
from googlecloudsdk.api_lib.run import traffic
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.run import config_changes
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_change_validators
from googlecloudsdk.command_lib.run import serverless_operations
from googlecloudsdk.command_lib.run import stages
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 progress_tracker
@base.UniverseCompatible
def ContainerArgGroup(release_track=base.ReleaseTrack.GA):
"""Returns an argument group with all per-container update args."""
help_text = """
Container Flags
If the --container or --remove-containers flag is specified the following
arguments may only be specified after a --container flag.
"""
group = base.ArgumentGroup(help=help_text)
group.AddArgument(flags.ImageArg(required=False))
group.AddArgument(flags.PortArg())
group.AddArgument(flags.Http2Flag())
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.SecretsFlags())
group.AddArgument(flags.DependsOnFlag())
group.AddArgument(flags.AddVolumeMountFlag())
group.AddArgument(flags.RemoveVolumeMountFlag())
group.AddArgument(flags.ClearVolumeMountsFlag())
group.AddArgument(flags.StartupProbeFlag())
group.AddArgument(flags.LivenessProbeFlag())
group.AddArgument(flags.GpuFlag())
return group
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.GA)
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} myservice --update-env-vars=KEY1=VALUE1,KEY2=VALUE2
""",
}
input_flags = (
'`--update-env-vars`, `--memory`, `--concurrency`, `--timeout`,'
' `--connectivity`, `--image`'
)
@classmethod
def CommonArgs(cls, parser):
# Flags specific to managed CR
flags.AddBinAuthzPolicyFlags(parser)
flags.AddBinAuthzBreakglassFlag(parser)
flags.AddCloudSQLFlags(parser)
flags.AddCmekKeyFlag(parser)
flags.AddCmekKeyRevocationActionTypeFlag(parser)
flags.AddCpuThrottlingFlag(parser)
flags.AddCustomAudiencesFlag(parser)
flags.AddDefaultUrlFlag(parser)
flags.AddDeployHealthCheckFlag(parser)
flags.AddEgressSettingsFlag(parser)
flags.AddEncryptionKeyShutdownHoursFlag(parser)
flags.AddGpuTypeFlag(parser)
flags.GpuZonalRedundancyFlag(parser)
flags.AddRevisionSuffixArg(parser)
flags.AddSandboxArg(parser)
flags.AddSessionAffinityFlag(parser)
flags.AddStartupCpuBoostFlag(parser)
flags.AddVpcConnectorArgs(parser)
flags.AddVpcNetworkGroupFlagsForUpdate(parser)
flags.RemoveContainersFlag().AddToParser(parser)
flags.AddVolumesFlags(parser, cls.ReleaseTrack())
flags.AddServiceMinMaxInstancesFlag(parser)
flags.AddInvokerIamCheckFlag(parser)
flags.AddScalingFlag(parser)
# Flags specific to connecting to a cluster
flags.AddEndpointVisibilityEnum(parser)
flags.CONFIG_MAP_FLAGS.AddToParser(parser)
# Flags not specific to any platform
service_presentation = presentation_specs.ResourcePresentationSpec(
'SERVICE',
resource_args.GetServiceResourceSpec(prompt=True),
'Service to update the configuration of.',
required=True,
prefixes=False,
)
flags.AddConcurrencyFlag(parser)
flags.AddTimeoutFlag(parser)
flags.AddAsyncFlag(parser)
flags.AddLabelsFlags(parser)
flags.AddGeneralAnnotationFlags(parser)
flags.AddMinInstancesFlag(parser)
flags.AddMaxInstancesFlag(parser)
flags.AddNoTrafficFlag(parser)
flags.AddDeployTagFlag(parser)
flags.AddServiceAccountFlag(parser)
flags.AddClientNameAndVersionFlags(parser)
flags.AddIngressFlag(parser)
concept_parsers.ConceptParser([service_presentation]).AddToParser(parser)
# No output by default, can be overridden by --format
parser.display_info.AddFormat('none')
@classmethod
def Args(cls, parser):
Update.CommonArgs(parser)
container_args = ContainerArgGroup(cls.ReleaseTrack())
container_parser.AddContainerFlags(
parser, container_args, cls.ReleaseTrack()
)
def _ConnectionContext(self, args):
return connection_context.GetConnectionContext(
args, flags.Product.RUN, self.ReleaseTrack()
)
def _AssertChanges(self, changes, flags_text, ignore_empty):
if ignore_empty:
return
if not changes or (
len(changes) == 1
and isinstance(
changes[0],
config_changes.SetClientNameAndVersionAnnotationChange,
)
):
raise exceptions.NoConfigurationChangeError(
'No configuration change requested. '
'Did you mean to include the flags {}?'.format(flags_text)
)
def _GetBaseChanges(self, args, existing_service=None, ignore_empty=False): # pylint: disable=unused-argument
changes = flags.GetServiceConfigurationChanges(args, self.ReleaseTrack())
self._AssertChanges(changes, self.input_flags, ignore_empty)
changes.insert(
0,
config_changes.DeleteAnnotationChange(
k8s_object.BINAUTHZ_BREAKGLASS_ANNOTATION
),
)
changes.append(
config_changes.SetLaunchStageAnnotationChange(self.ReleaseTrack())
)
return changes
def _IsMultiRegion(self):
return False
def _GetMultiRegionRegions(self, changes, service): # used by child - pylint: disable=unused-argument
return None
def _GetIap(self, args):
if flags.FlagIsExplicitlySet(args, 'iap'):
return args.iap
return None
def Run(self, args):
"""Update the service resource.
Different from `deploy` in that it can only update the service spec but
no IAM or Cloud build changes.
Args:
args: Args!
Returns:
googlecloudsdk.api_lib.run.Service, the updated service
"""
conn_context = self._ConnectionContext(args)
service_ref = args.CONCEPTS.service.Parse()
flags.ValidateResource(service_ref)
iap = self._GetIap(args)
with serverless_operations.Connect(conn_context) as client:
service = client.GetService(service_ref)
messages_util.MaybeLogDefaultGpuTypeMessage(args, service)
changes = self._GetBaseChanges(args, service)
resource_change_validators.ValidateClearVpcConnector(service, args)
has_latest = (
service is None or traffic.LATEST_REVISION_KEY in service.spec_traffic
)
creates_revision = config_changes.AdjustsTemplate(changes)
multiregion_regions = self._GetMultiRegionRegions(changes, service)
deployment_stages = stages.ServiceStages(
include_iam_policy_set=False,
include_route=creates_revision and has_latest,
include_create_revision=creates_revision,
regions_list=multiregion_regions,
include_iap=iap is not None,
)
if creates_revision:
progress_message = 'Deploying...'
failure_message = 'Deployment failed'
result_message = 'deploying'
else:
progress_message = 'Updating...'
failure_message = 'Update failed'
result_message = 'updating'
def _ReleaseService(changes_):
with progress_tracker.StagedProgressTracker(
progress_message,
deployment_stages,
failure_message=failure_message,
suppress_output=args.async_,
) as tracker:
return client.ReleaseService(
service_ref,
changes_,
self.ReleaseTrack(),
tracker,
asyn=args.async_,
prefetch=service,
generate_name=(
flags.FlagIsExplicitlySet(args, 'revision_suffix')
or flags.FlagIsExplicitlySet(args, 'tag')
),
is_verbose=properties.VALUES.core.verbosity.Get() == 'debug',
iap_enabled=iap,
multiregion_regions=multiregion_regions,
)
try:
service = _ReleaseService(changes)
except exceptions.HttpError as e:
if flags.ShouldRetryNoZonalRedundancy(args, str(e)):
changes.append(
config_changes.GpuZonalRedundancyChange(
gpu_zonal_redundancy=False
)
)
service = _ReleaseService(changes)
else:
raise e
if args.async_:
pretty_print.Success(
'Service [{{bold}}{serv}{{reset}}] is {result_message} '
'asynchronously.'.format(
serv=service.name, result_message=result_message
)
)
else:
if creates_revision:
pretty_print.Success(
messages_util.GetSuccessMessageForSynchronousDeploy(
service, args.no_traffic
)
)
else:
pretty_print.Success(
'Service [{{bold}}{serv}{{reset}}] has been updated.'.format(
serv=service.name
)
)
return service
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class BetaUpdate(Update):
"""Update Cloud Run environment variables and other configuration settings."""
input_flags = (
'`--update-env-vars`, `--memory`, `--concurrency`, `--timeout`,'
' `--connectivity`, `--image`, `--iap`'
)
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
# Flags specific to managed CR
flags.SERVICE_MESH_FLAG.AddToParser(parser)
flags.AddIapFlag(parser)
container_args = ContainerArgGroup(cls.ReleaseTrack())
container_args.AddArgument(flags.ReadinessProbeFlag())
container_parser.AddContainerFlags(
parser, container_args, cls.ReleaseTrack()
)
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class AlphaUpdate(BetaUpdate):
"""Update Cloud Run environment variables and other configuration settings."""
input_flags = (
'`--update-env-vars`, `--memory`, `--concurrency`, `--timeout`,'
' `--connectivity`, `--image`, `--iap`'
)
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
# Flags specific to managed CR
flags.AddIapFlag(parser)
flags.AddRuntimeFlag(parser)
flags.AddDescriptionFlag(parser)
flags.SERVICE_MESH_FLAG.AddToParser(parser)
flags.IDENTITY_FLAG.AddToParser(parser)
flags.IDENTITY_CERTIFICATE_FLAG.AddToParser(parser)
flags.IDENTITY_TYPE_FLAG.AddToParser(parser)
flags.MESH_DATAPLANE_FLAG.AddToParser(parser)
flags.AddOverflowScalingFlag(parser)
flags.AddCpuUtilizationFlag(parser)
flags.AddConcurrencyUtilizationFlag(parser)
flags.AddClearPresetFlag(parser)
container_args = ContainerArgGroup(cls.ReleaseTrack())
container_args.AddArgument(flags.ReadinessProbeFlag())
container_parser.AddContainerFlags(
parser, container_args, cls.ReleaseTrack()
)
AlphaUpdate.__doc__ = Update.__doc__

View File

@@ -0,0 +1,205 @@
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command 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 k8s_object
from googlecloudsdk.api_lib.run import traffic_pair
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import display
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 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.run import stages
from googlecloudsdk.command_lib.run.printers import traffic_printer
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 progress_tracker
from googlecloudsdk.core.resource import resource_printer
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.GA)
class AdjustTraffic(base.Command):
"""Adjust the traffic assignments for a Cloud Run service."""
detailed_help = {
'DESCRIPTION': """\
{description}
""",
'EXAMPLES': """\
To assign 10% of traffic to revision myservice-s5sxn and
90% of traffic to revision myservice-cp9kw run:
$ {command} myservice --to-revisions=myservice-s5sxn=10,myservice-cp9kw=90
To increase the traffic to revision myservice-s5sxn to 20% and
by reducing the traffic to revision myservice-cp9kw to 80% run:
$ {command} myservice --to-revisions=myservice-s5sxn=20
To rollback to revision myservice-cp9kw run:
$ {command} myservice --to-revisions=myservice-cp9kw=100
To assign 100% of traffic to the current or future LATEST revision
run:
$ {command} myservice --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
traffic to always float to the latest revision:
$ {command} myservice --to-revisions=LATEST=10
""",
}
@classmethod
def CommonArgs(cls, parser):
service_presentation = presentation_specs.ResourcePresentationSpec(
'SERVICE',
resource_args.GetServiceResourceSpec(prompt=True),
'Service to update the configuration of.',
required=True,
prefixes=False,
)
flags.AddAsyncFlag(parser)
flags.AddUpdateTrafficFlags(parser)
flags.AddTrafficTagsFlags(parser)
concept_parsers.ConceptParser([service_presentation]).AddToParser(parser)
resource_printer.RegisterFormatter(
traffic_printer.TRAFFIC_PRINTER_FORMAT,
traffic_printer.TrafficPrinter,
)
parser.display_info.AddFormat(traffic_printer.TRAFFIC_PRINTER_FORMAT)
def IsMultiRegion(self):
return False
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
def Run(self, args):
"""Update the traffic split for the service.
Args:
args: Args!
Returns:
List of traffic.TrafficTargetStatus instances reflecting the change.
"""
conn_context = connection_context.GetConnectionContext(
args,
flags.Product.RUN,
self.ReleaseTrack(),
is_multiregion=self.IsMultiRegion(),
)
service_ref = args.CONCEPTS.service.Parse()
flags.ValidateResource(service_ref)
changes = flags.GetServiceConfigurationChanges(args)
if not changes:
raise exceptions.NoConfigurationChangeError(
'No traffic configuration change requested.'
)
changes.insert(
0,
config_changes.DeleteAnnotationChange(
k8s_object.BINAUTHZ_BREAKGLASS_ANNOTATION
),
)
changes.append(
config_changes.SetLaunchStageAnnotationChange(self.ReleaseTrack())
)
is_managed = platforms.GetPlatform() == platforms.PLATFORM_MANAGED
with serverless_operations.Connect(conn_context) as client:
deployment_stages = stages.UpdateTrafficStages()
try:
with progress_tracker.StagedProgressTracker(
'Updating traffic...',
deployment_stages,
failure_message='Updating traffic failed',
suppress_output=args.async_,
) as tracker:
serv = client.UpdateTraffic(
service_ref,
changes,
tracker,
args.async_,
properties.VALUES.core.verbosity.Get() == 'debug',
self.ReleaseTrack(),
)
except:
serv = client.GetService(service_ref)
if serv:
resources = traffic_pair.GetTrafficTargetPairs(
serv.spec_traffic,
serv.status_traffic,
is_managed,
serv.status.latestReadyRevisionName,
serv.status.url,
)
display.Displayer(
self, args, resources, display_info=args.GetDisplayInfo()
).Display()
raise
if args.async_:
pretty_print.Success('Updating traffic asynchronously.')
else:
resources = traffic_pair.GetTrafficTargetPairs(
serv.spec_traffic,
serv.status_traffic,
is_managed,
serv.status.latestReadyRevisionName,
serv.status.url,
)
return resources
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class BetaAdjustTraffic(AdjustTraffic):
"""Adjust the traffic assignments for a Cloud Run service."""
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class AlphaAdjustTraffic(AdjustTraffic):
"""Adjust the traffic assignments for a Cloud Run service."""
@classmethod
def Args(cls, parser):
cls.CommonArgs(parser)
# Flags specific to managed CR
flags.AddBinAuthzBreakglassFlag(parser)