1# -*- coding: utf-8 -*- # 2# Copyright 2018 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Provides common arguments for the Run command surface.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import print_function 20from __future__ import unicode_literals 21 22import enum 23import os 24import re 25from apitools.base.py import exceptions as apitools_exceptions 26 27from googlecloudsdk.api_lib.container import kubeconfig 28from googlecloudsdk.api_lib.run import container_resource 29from googlecloudsdk.api_lib.run import global_methods 30from googlecloudsdk.api_lib.run import k8s_object 31from googlecloudsdk.api_lib.run import revision 32from googlecloudsdk.api_lib.run import service 33from googlecloudsdk.api_lib.run import traffic 34from googlecloudsdk.api_lib.services import enable_api 35from googlecloudsdk.api_lib.services import exceptions as services_exceptions 36from googlecloudsdk.calliope import actions 37from googlecloudsdk.calliope import arg_parsers 38from googlecloudsdk.calliope import base 39from googlecloudsdk.command_lib.functions.v2.deploy import env_vars_util 40from googlecloudsdk.command_lib.run import config_changes 41from googlecloudsdk.command_lib.run import exceptions as serverless_exceptions 42from googlecloudsdk.command_lib.run import platforms 43from googlecloudsdk.command_lib.run import pretty_print 44from googlecloudsdk.command_lib.run import resource_args 45from googlecloudsdk.command_lib.util.args import labels_util 46from googlecloudsdk.command_lib.util.args import map_util 47from googlecloudsdk.command_lib.util.args import repeated 48from googlecloudsdk.command_lib.util.concepts import concept_parsers 49from googlecloudsdk.core import config 50from googlecloudsdk.core import exceptions 51from googlecloudsdk.core import log 52from googlecloudsdk.core import properties 53from googlecloudsdk.core.console import console_io 54from googlecloudsdk.core.util import encoding 55from googlecloudsdk.core.util import files 56 57_VISIBILITY_MODES = { 58 'internal': 'Visible only within the cluster.', 59 'external': 'Visible from outside the cluster.', 60} 61 62_INGRESS_MODES = { 63 'all': 'Inbound requests from all sources are allowed.', 64 'internal': 65 'For Cloud Run (fully managed), only inbound requests from VPC networks' 66 ' in the same project are allowed. For Cloud Run for Anthos, only ' 67 'inbound requests from the same cluster are allowed.', 68 'internal-and-cloud-load-balancing': 69 'Only supported for Cloud Run (fully managed). Only inbound requests' 70 ' from VPC networks in the same project or from Google Cloud Load ' 71 'Balancing are allowed.' 72} 73 74_SANDBOX_CHOICES = { 75 'gvisor': 'Run the application in a gVisor sandbox.', 76 'minivm': 'Run the application in a mini VM sandbox.', 77} 78 79_DEFAULT_KUBECONFIG_PATH = '~/.kube/config' 80 81_FIFTEEN_MINUTES = 15 * 60 82 83 84def _StripKeys(d): 85 return {k.strip(): v for k, v in d.items()} 86 87 88def _MapLStrip(seq): 89 return [elem.lstrip() for elem in seq] 90 91 92class KubeconfigError(exceptions.Error): 93 pass 94 95 96class Product(enum.Enum): 97 RUN = 'Run' 98 EVENTS = 'Events' 99 100 101def AddImageArg(parser, required=True): 102 """Add an image resource arg.""" 103 parser.add_argument( 104 '--image', 105 required=required, 106 help='Name of the container image to deploy (e.g. ' 107 '`gcr.io/cloudrun/hello:latest`).') 108 109 110def AddConfigFlags(parser): 111 """Add config flags.""" 112 build_config = parser.add_mutually_exclusive_group() 113 build_config.add_argument( 114 '--image', 115 help='Name of the container image to deploy (e.g. ' 116 '`gcr.io/cloudrun/hello:latest`).') 117 build_config.add_argument( 118 '--config', 119 hidden=True, 120 default='cloudbuild.yaml', # By default, find this in the current dir 121 help='The YAML or JSON file to use as the build configuration file.') 122 build_config.add_argument( 123 '--pack', 124 hidden=True, 125 type=arg_parsers.ArgDict( 126 spec={ 127 'image': str, 128 'builder': str, 129 'env': str 130 }, 131 required_keys=['image']), 132 action='append', 133 help='Uses CNCF buildpack (https://buildpacks.io/) to create image. ' 134 'The "image" key/value must be provided. The image name must be in the ' 135 'gcr.io/*, *.gcr.io, or pkg.dev namespaces. By default ' 136 'gcr.io/buildpacks/builder will be used. To specify your own builder ' 137 'image use the optional "builder" key/value argument. To pass ' 138 'environment variables to the builder use the optional "env" key/value ' 139 'argument where value is a list of key values using ' 140 'escaping (https://cloud.google.com/sdk/gcloud/reference/topic/escaping) ' 141 'if neccessary.') 142 143 144_ARG_GROUP_HELP_TEXT = ('Only applicable if connecting to {platform_desc}. ' 145 'Specify {platform} to use:') 146 147 148def _GetOrAddArgGroup(parser, help_text): 149 """Create a new arg group or return existing group with given help text.""" 150 for arg in parser.arguments: 151 if arg.is_group and arg.help == help_text: 152 return arg 153 return parser.add_argument_group(help_text) 154 155 156def GetManagedArgGroup(parser): 157 """Get an arg group for managed CR-only flags.""" 158 return _GetOrAddArgGroup( 159 parser, 160 _ARG_GROUP_HELP_TEXT.format( 161 platform='`--platform={}`'.format(platforms.PLATFORM_MANAGED), 162 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 163 platforms.PLATFORM_MANAGED])) 164 165 166def GetGkeArgGroup(parser): 167 """Get an arg group for CRoGKE-only flags.""" 168 return _GetOrAddArgGroup( 169 parser, 170 _ARG_GROUP_HELP_TEXT.format( 171 platform='`--platform={}`'.format(platforms.PLATFORM_GKE), 172 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 173 platforms.PLATFORM_GKE])) 174 175 176def GetKubernetesArgGroup(parser): 177 """Get an arg group for --platform=kubernetes only flags.""" 178 return _GetOrAddArgGroup( 179 parser, 180 _ARG_GROUP_HELP_TEXT.format( 181 platform='`--platform={}`'.format(platforms.PLATFORM_KUBERNETES), 182 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 183 platforms.PLATFORM_KUBERNETES])) 184 185 186def GetClusterArgGroup(parser): 187 """Get an arg group for any generic cluster flags.""" 188 return _GetOrAddArgGroup( 189 parser, 190 _ARG_GROUP_HELP_TEXT.format( 191 platform='`--platform={}` or `--platform={}`'.format( 192 platforms.PLATFORM_GKE, platforms.PLATFORM_KUBERNETES), 193 platform_desc='{} or {}'.format( 194 platforms.PLATFORM_SHORT_DESCRIPTIONS[platforms.PLATFORM_GKE], 195 platforms.PLATFORM_SHORT_DESCRIPTIONS[ 196 platforms.PLATFORM_KUBERNETES]))) 197 198 199def AddPlatformAndLocationFlags(parser, managed_only=False, anthos_only=False): 200 """Adds flags used to determine the platform and the location of resource.""" 201 assert not (managed_only and anthos_only) 202 AddPlatformArg(parser, managed_only, anthos_only) 203 204 if managed_only: 205 AddRegionArg(parser) 206 return None 207 208 # When multiple platforms are supported, add a arg group that covers the 209 # various ways to specify region/zone/cluster. 210 platform_helpers_group = parser.add_mutually_exclusive_group( 211 help='Arguments to locate resources, depending on the platform used.') 212 213 if not anthos_only: 214 # Add --region flag 215 managed_group = GetManagedArgGroup(platform_helpers_group) 216 AddRegionArg(managed_group) 217 218 # Add --cluster and --cluster-location (plus properties) 219 gke_group = GetGkeArgGroup(platform_helpers_group) 220 concept_parsers.ConceptParser([resource_args.CLUSTER_PRESENTATION 221 ]).AddToParser(gke_group) 222 223 # Add --kubeconfig and --context 224 kubernetes_group = GetKubernetesArgGroup(platform_helpers_group) 225 AddKubeconfigFlags(kubernetes_group) 226 227 228def AddAllowUnauthenticatedFlag(parser): 229 """Add the --allow-unauthenticated flag.""" 230 parser.add_argument( 231 '--allow-unauthenticated', 232 action=arg_parsers.StoreTrueFalseAction, 233 help='Whether to enable allowing unauthenticated access to the service. ' 234 'This may take a few moments to take effect.') 235 236 237def AddAsyncFlag(parser, default_async_for_cluster=False): 238 """Add an async flag.""" 239 if default_async_for_cluster: 240 modified_async_flag = base.Argument( 241 '--async', 242 action=arg_parsers.StoreTrueFalseAction, 243 dest='async_', 244 help="""\ 245 Return immediately, without waiting for the operation in progress to 246 complete. Defaults to --no-async for Cloud Run (fully managed) and --async 247 for Cloud Run for Anthos.""") 248 modified_async_flag.AddToParser(parser) 249 else: 250 base.ASYNC_FLAG.AddToParser(parser) 251 252 253def AddEndpointVisibilityEnum(parser, deprecated=False): 254 """Add the --connectivity=[external|internal] flag.""" 255 action = None 256 if deprecated: 257 action = actions.DeprecationAction( 258 '--connectivity', 259 warn='The {flag_name} flag is deprecated and will be removed in an ' 260 'upcoming release. Please use the --ingress flag instead.') 261 parser.add_argument( 262 '--connectivity', 263 choices=_VISIBILITY_MODES, 264 help=('Defaults to \'external\'. If \'external\', the service can be ' 265 'invoked through the internet, in addition to through the cluster ' 266 'network.'), 267 action=action) 268 269 270def AddIngressFlag(parser): 271 """Adds the --ingress flag.""" 272 parser.add_argument( 273 '--ingress', 274 choices=_INGRESS_MODES, 275 help='Set the ingress traffic sources allowed to call the service. For ' 276 'Cloud Run (fully managed) the `--[no-]allow-unauthenticated` flag ' 277 'separately controls the identities allowed to call the service.', 278 default='all') 279 280 281def AddServiceFlag(parser): 282 """Add a service resource flag.""" 283 parser.add_argument( 284 '--service', 285 required=False, 286 help='Limit matched revisions to the given service.') 287 288 289def AddRegionArg(parser): 290 """Add a region arg.""" 291 parser.add_argument( 292 '--region', 293 help='Region in which the resource can be found. ' 294 'Alternatively, set the property [run/region].') 295 296 297def AddFunctionArg(parser): 298 """Add a function resource arg.""" 299 parser.add_argument( 300 '--function', 301 hidden=True, 302 help="""\ 303 Specifies that the deployed object is a function. If a value is 304 provided, that value is used as the entrypoint. 305 """) 306 307 308def AddNoTrafficFlag(parser): 309 """Add flag to deploy a revision with no traffic.""" 310 parser.add_argument( 311 '--no-traffic', 312 default=False, 313 action='store_true', 314 help='True to avoid sending traffic to the revision being deployed. ' 315 'Setting this flag assigns any traffic assigned to the LATEST revision ' 316 'to the specific revision bound to LATEST before the deployment. The ' 317 'effect is that the revision being deployed will not receive traffic.\n\n' 318 'After a deployment with this flag the LATEST revision will not receive ' 319 'traffic on future deployments. To restore sending traffic to the LATEST ' 320 'revision by default, run the `gcloud run services update-traffic` ' 321 'command with `--to-latest`.') 322 323 324def AddDeployTagFlag(parser): 325 """Add flag to specify a tag for the new revision.""" 326 parser.add_argument( 327 '--tag', help='Traffic tag to assign to the newly created revision.') 328 329 330def AddTrafficTagsFlags(parser): 331 """Add flags for updating traffic tags for a service.""" 332 AddMapFlagsNoFile( 333 parser, 334 group_help=('Specify traffic tags. Traffic tags can be ' 335 'assigned to a revision by name or to the ' 336 'latest ready revision. Assigning a tag to a ' 337 'revision generates a URL prefixed with the ' 338 'tag that allows addressing that revision ' 339 'directly, regardless of the percent traffic ' 340 'specified. Keys are tags. Values are revision names or ' 341 '"LATEST" for the latest ready revision. For example, ' 342 '--set-tags=candidate=LATEST,current=' 343 'myservice-v1 assigns the tag "candidate" ' 344 'to the latest ready revision and the tag' 345 ' "current" to the revision with name ' 346 '"myservice-v1" and clears any existing tags. ' 347 'Changing tags does not ' 348 'affect the traffic percentage assigned to ' 349 'revisions. When using a tags flag and ' 350 'one or more of --to-latest and --to-revisions in the same ' 351 'command, the tags change occurs first then the traffic ' 352 'percentage change occurs.'), 353 flag_name='tags', 354 key_metavar='TAG', 355 value_metavar='REVISION') 356 357 358def AddUpdateTrafficFlags(parser, release_track): 359 """Add flags for updating traffic assignments for a service.""" 360 361 @staticmethod 362 def TrafficTargetKey(key): 363 return key 364 365 @staticmethod 366 def TrafficPercentageValue(value): 367 """Type validation for traffic percentage flag values.""" 368 try: 369 result = int(value) 370 except (TypeError, ValueError): 371 raise serverless_exceptions.ArgumentError( 372 'Traffic percentage value %s is not an integer.' % value) 373 374 if result < 0 or result > 100: 375 raise serverless_exceptions.ArgumentError( 376 'Traffic percentage value %s is not between 0 and 100.' % value) 377 return result 378 379 group = parser.add_mutually_exclusive_group() 380 381 group.add_argument( 382 '--to-revisions', 383 metavar='REVISION-NAME=PERCENTAGE', 384 action=arg_parsers.UpdateAction, 385 type=arg_parsers.ArgDict( 386 key_type=TrafficTargetKey.__func__, 387 value_type=TrafficPercentageValue.__func__), 388 help='Comma separated list of traffic assignments in the form ' 389 'REVISION-NAME=PERCENTAGE. REVISION-NAME must be the name for a ' 390 'revision for the service as returned by \'gcloud beta run list ' 391 'revisions\'. PERCENTAGE must be an integer percentage between ' 392 '0 and 100 inclusive. Ex service-nw9hs=10,service-nw9hs=20 ' 393 'Up to 100 percent of traffic may be assigned. If 100 percent ' 394 'of traffic is assigned, the Service traffic is updated as ' 395 'specified. If under 100 percent of traffic is assigned, the ' 396 'Service traffic is updated as specified for revisions with ' 397 'assignments and traffic is scaled up or down down proportionally ' 398 'as needed for revision that are currently serving traffic but that do ' 399 'not have new assignments. For example assume revision-1 is serving ' 400 '40 percent of traffic and revision-2 is serving 60 percent. If ' 401 'revision-1 is assigned 45 percent of traffic and no assignment is ' 402 'made for revision-2, the service is updated with revsion-1 assigned ' 403 '45 percent of traffic and revision-2 scaled down to 55 percent. ' 404 'You can use "LATEST" as a special revision name to always put the given ' 405 'percentage of traffic on the latest ready revision.') 406 407 if release_track and (base.ReleaseTrack.BETA == release_track or 408 base.ReleaseTrack.ALPHA == release_track): 409 group.add_argument( 410 '--to-tags', 411 metavar='TAG=PERCENTAGE', 412 action=arg_parsers.UpdateAction, 413 type=arg_parsers.ArgDict( 414 key_type=TrafficTargetKey.__func__, 415 value_type=TrafficPercentageValue.__func__), 416 help='Comma separated list of traffic assignments in the form ' 417 'TAG=PERCENTAGE. TAG must match a traffic tag on a revision of the ' 418 'service. It may match a previously-set tag, or one assigned using' 419 ' the `--set-tags` or `--update-tags` flags on this command. ' 420 'PERCENTAGE must be an integer percentage between ' 421 '0 and 100 inclusive. ' 422 'Up to 100 percent of traffic may be assigned. If 100 percent ' 423 'of traffic is assigned, the service traffic is updated as ' 424 'specified. If under 100 percent of traffic is assigned, the ' 425 'service traffic is updated as specified to the given tags, and other ' 426 'traffic is scaled up or down proportionally. For example, assume ' 427 'the revision tagged `next` is serving 40 percent of traffic and the ' 428 'revision tagged `current` is serving 60 percent. If ' 429 '`next` is assigned 45 percent of traffic and no assignment is ' 430 'made for `current`, the service is updated with `next` assigned ' 431 '45 percent of traffic and `current` scaled down to 55 percent. ') 432 433 group.add_argument( 434 '--to-latest', 435 default=False, 436 action='store_true', 437 help='True to assign 100 percent of traffic to the \'latest\' ' 438 'revision of this service. Note that when a new revision is ' 439 'created, it will become the \'latest\' and traffic will be ' 440 'directed to it. Defaults to False. Synonymous with ' 441 '\'--to-revisions=LATEST=100\'.') 442 443 444def AddSetCloudSQLFlag(parser): 445 """Add only the --set-cloudsql-instances flag.""" 446 parser.add_argument( 447 '--set-cloudsql-instances', 448 type=arg_parsers.ArgList(), 449 metavar='CLOUDSQL-INSTANCES', 450 help="""You can specify a name of a Cloud SQL instance if it's in the same 451 project and region as your Cloud Run resource; otherwise specify 452 <project>:<region>:<instance> for the instance.""") 453 454 455def AddCloudSQLFlags(parser): 456 """Add flags for setting CloudSQL stuff.""" 457 repeated.AddPrimitiveArgs( 458 parser, 459 'Service', 460 'cloudsql-instances', 461 'Cloud SQL instances', 462 auto_group_help=False, 463 additional_help="""\ 464 These flags modify the Cloud SQL instances this Service connects to. 465 You can specify a name of a Cloud SQL instance if it's in the same 466 project and region as your Cloud Run service; otherwise specify 467 <project>:<region>:<instance> for the instance.""") 468 469 470def AddMapFlagsNoFile(parser, 471 flag_name, 472 group_help='', 473 long_name=None, 474 key_type=None, 475 value_type=None, 476 key_metavar='KEY', 477 value_metavar='VALUE'): 478 """Add flags like map_util.AddUpdateMapFlags but without the file one. 479 480 Args: 481 parser: The argument parser 482 flag_name: The name for the property to be used in flag names 483 group_help: Help text for the group of flags 484 long_name: The name for the property to be used in help text 485 key_type: A function to apply to map keys. 486 value_type: A function to apply to map values. 487 key_metavar: Metavariable to list for the key. 488 value_metavar: Metavariable to list for the value. 489 """ 490 if not long_name: 491 long_name = flag_name 492 493 group = parser.add_mutually_exclusive_group(group_help) 494 update_remove_group = group.add_argument_group( 495 help=('Only --update-{0} and --remove-{0} can be used together. If both ' 496 'are specified, --remove-{0} will be applied first.' 497 ).format(flag_name)) 498 map_util.AddMapUpdateFlag( 499 update_remove_group, 500 flag_name, 501 long_name, 502 key_type=key_type, 503 value_type=value_type, 504 key_metavar=key_metavar, 505 value_metavar=value_metavar) 506 map_util.AddMapRemoveFlag( 507 update_remove_group, 508 flag_name, 509 long_name, 510 key_type=key_type, 511 key_metavar=key_metavar) 512 map_util.AddMapClearFlag(group, flag_name, long_name) 513 map_util.AddMapSetFlag( 514 group, 515 flag_name, 516 long_name, 517 key_type=key_type, 518 value_type=value_type, 519 key_metavar=key_metavar, 520 value_metavar=value_metavar) 521 522 523def AddSetEnvVarsFlag(parser): 524 """Add only the --set-env-vars flag.""" 525 parser.add_argument( 526 '--set-env-vars', 527 metavar='KEY=VALUE', 528 action=arg_parsers.UpdateAction, 529 type=arg_parsers.ArgDict( 530 key_type=env_vars_util.EnvVarKeyType, 531 value_type=env_vars_util.EnvVarValueType), 532 help='List of key-value pairs to set as environment variables.') 533 534 535def AddMutexEnvVarsFlags(parser): 536 """Add flags for creating updating and deleting env vars.""" 537 # TODO(b/119837621): Use env_vars_util.AddUpdateEnvVarsFlags when 538 # `gcloud run` supports an env var file. 539 AddMapFlagsNoFile( 540 parser, 541 flag_name='env-vars', 542 long_name='environment variables', 543 key_type=env_vars_util.EnvVarKeyType, 544 value_type=env_vars_util.EnvVarValueType) 545 546 547def AddMemoryFlag(parser): 548 parser.add_argument('--memory', help='Set a memory limit. Ex: 1Gi, 512Mi.') 549 550 551def AddCpuFlag(parser, managed_only=False): 552 help_msg = ('Set a CPU limit in Kubernetes cpu units.\n\n' 553 'Cloud Run (fully managed) supports values 1, 2 and 4.' 554 ' For Cloud Run (fully managed), 4 cpus also requires a minimum ' 555 '2Gi `--memory` value. Examples 2, 2.0, 2000m') 556 if not managed_only: 557 help_msg += ('\n\nCloud Run for Anthos and Knative-compatible Kubernetes ' 558 'clusters support fractional values. Examples .5, 500m, 2') 559 parser.add_argument('--cpu', help=help_msg) 560 561 562def _ConcurrencyValue(value): 563 """Returns True if value is an int > 0 or 'default'.""" 564 try: 565 return value == 'default' or int(value) > 0 566 except ValueError: 567 return False 568 569 570def AddConcurrencyFlag(parser): 571 parser.add_argument( 572 '--concurrency', 573 type=arg_parsers.CustomFunctionValidator( 574 _ConcurrencyValue, 'must be an integer greater than 0 or "default".'), 575 help='Set the maximum number of concurrent requests allowed per ' 576 'container instance. If concurrency is unspecified, ' 577 'any number of concurrent requests are allowed. To unset ' 578 'this field, provide the special value `default`.') 579 580 581def AddTimeoutFlag(parser): 582 parser.add_argument( 583 '--timeout', 584 type=arg_parsers.Duration(lower_bound='1s'), 585 help='Set the maximum request execution time (timeout). It is specified ' 586 'as a duration; for example, "10m5s" is ten minutes, and five seconds. ' 587 'If you don\'t specify a unit, seconds is assumed. For example, "10" is ' 588 '10 seconds.') 589 590 591def AddServiceAccountFlag(parser): 592 parser.add_argument( 593 '--service-account', 594 help='Service account associated with the revision of the service. ' 595 'The service account represents the identity of ' 596 'the running revision, and determines what permissions the revision has. ' 597 'For the {} platform, this is the email address of an IAM ' 598 'service account. For the Kubernetes-based platforms ({}, {}), this is ' 599 'the name of a Kubernetes service account in the same namespace as the ' 600 'service. If not provided, the revision will use the default service ' 601 'account of the project, or default Kubernetes namespace service account ' 602 'respectively.'.format(platforms.PLATFORM_MANAGED, platforms.PLATFORM_GKE, 603 platforms.PLATFORM_KUBERNETES)) 604 605 606def AddPlatformArg(parser, managed_only=False, anthos_only=False): 607 """Add a platform arg.""" 608 assert not (managed_only and anthos_only) 609 choices = platforms.PLATFORMS 610 if managed_only: 611 choices = platforms.PLATFORMS_MANAGED_ONLY 612 if anthos_only: 613 choices = platforms.PLATFORMS_ANTHOS_ONLY 614 parser.add_argument( 615 '--platform', 616 choices=choices, 617 action=actions.StoreProperty(properties.VALUES.run.platform), 618 help='Target platform for running commands. ' 619 'Alternatively, set the property [run/platform]. ' 620 'If not specified, the user will be prompted to choose a platform.') 621 622 623def AddKubeconfigFlags(parser): 624 parser.add_argument( 625 '--kubeconfig', 626 help='The absolute path to your kubectl config file. If not specified, ' 627 'the colon- or semicolon-delimited list of paths specified by ' 628 '$KUBECONFIG will be used. If $KUBECONFIG is unset, this defaults to ' 629 '`{}`.'.format(_DEFAULT_KUBECONFIG_PATH)) 630 parser.add_argument( 631 '--context', 632 help='The name of the context in your kubectl config file to use for ' 633 'connecting.') 634 635 636def AddRevisionSuffixArg(parser): 637 parser.add_argument( 638 '--revision-suffix', 639 help='Specify the suffix of the revision name. Revision names always ' 640 'start with the service name automatically. For example, specifying ' 641 '[--revision-suffix=v1] for a service named \'helloworld\', ' 642 'would lead to a revision named \'helloworld-v1\'.') 643 644 645def AddSandboxArg(parser): 646 parser.add_argument( 647 '--sandbox', 648 choices=_SANDBOX_CHOICES, 649 help='Selects the sandbox where the application will run.') 650 651 652def AddVpcConnectorArg(parser): 653 parser.add_argument( 654 '--vpc-connector', help='Set a VPC connector for this resource.') 655 656 657def AddVpcConnectorArgs(parser): 658 AddVpcConnectorArg(parser) 659 parser.add_argument( 660 '--clear-vpc-connector', 661 action='store_true', 662 help='Remove the VPC connector for this resource.') 663 664 665def AddEgressSettingsFlag(parser): 666 """Adds a flag for configuring VPC egress for fully-managed.""" 667 parser.add_argument( 668 '--vpc-egress', 669 help='The outbound traffic to send through the VPC connector' 670 ' for this resource. This resource must have a VPC connector to set' 671 ' VPC egress.', 672 choices={ 673 container_resource.EGRESS_SETTINGS_PRIVATE_RANGES_ONLY: 674 'Default option. Sends outbound traffic to private IP addresses ' 675 'defined by RFC1918 through the VPC connector.', 676 container_resource.EGRESS_SETTINGS_ALL: 677 'Sends all outbound traffic through the VPC connector.' 678 }) 679 680 681def AddSecretsFlags(parser): 682 """Adds flags for creating, updating, and deleting secrets.""" 683 AddMapFlagsNoFile( 684 parser, 685 group_help=('Specify secrets to mount or provide as environment ' 686 "variables. Keys starting with a forward slash '/' are mount " 687 'paths. All other keys correspond to environment variables. ' 688 'The values associated with each of these should be in the ' 689 'form SECRET_NAME:KEY_IN_SECRET; you may omit the ' 690 'key within the secret to specify a mount of all keys ' 691 'within the secret. For example: ' 692 "'--update-secrets=/my/path=mysecret," 693 "ENV=othersecret:key.json' " 694 "will create a volume with secret 'mysecret' " 695 "and mount that volume at '/my/path'. Because no secret " 696 "key was specified, all keys in 'mysecret' will be included. " 697 'An environment variable named ENV will also be created ' 698 "whose value is the value of 'key.json' in 'othersecret'."), 699 flag_name='secrets') 700 701 702def AddConfigMapsFlags(parser): 703 """Adds flags for creating, updating, and deleting config maps.""" 704 AddMapFlagsNoFile( 705 parser, 706 group_help=('Specify config map to mount or provide as environment ' 707 "variables. Keys starting with a forward slash '/' are mount " 708 'paths. All other keys correspond to environment variables. ' 709 'The values associated with each of these should be in the ' 710 'form CONFIG_MAP_NAME:KEY_IN_CONFIG_MAP; you may omit the ' 711 'key within the config map to specify a mount of all keys ' 712 'within the config map. For example: ' 713 "'--update-config-maps=/my/path=myconfig," 714 "ENV=otherconfig:key.json' " 715 "will create a volume with config map 'myconfig' " 716 "and mount that volume at '/my/path'. Because no config map " 717 "key was specified, all keys in 'myconfig' will be included. " 718 'An environment variable named ENV will also be created ' 719 "whose value is the value of 'key.json' in 'otherconfig'."), 720 flag_name='config-maps') 721 722 723def AddLabelsFlag(parser, extra_message=''): 724 """Add only the --labels flag.""" 725 labels_util.GetCreateLabelsFlag( 726 extra_message=extra_message, validate_keys=False, 727 validate_values=False).AddToParser(parser) 728 729 730def AddLabelsFlags(parser): 731 """Adds update command labels flags to an argparse parser. 732 733 Args: 734 parser: The argparse parser to add the flags to. 735 """ 736 group = parser.add_group() 737 add_group = group.add_mutually_exclusive_group() 738 AddLabelsFlag(add_group, 'An alias to --update-labels.') 739 labels_util.GetUpdateLabelsFlag( 740 '', validate_keys=False, validate_values=False).AddToParser(add_group) 741 remove_group = group.add_mutually_exclusive_group() 742 labels_util.GetClearLabelsFlag().AddToParser(remove_group) 743 labels_util.GetRemoveLabelsFlag('').AddToParser(remove_group) 744 745 746class _ScaleValue(object): 747 """Type for min/max-instances flag values.""" 748 749 def __init__(self, value): 750 self.restore_default = value == 'default' 751 if not self.restore_default: 752 try: 753 self.instance_count = int(value) 754 except (TypeError, ValueError): 755 raise serverless_exceptions.ArgumentError( 756 'Instance count value %s is not an integer ' 757 'or \'default\'.' % value) 758 759 if self.instance_count < 0: 760 raise serverless_exceptions.ArgumentError( 761 'Instance count value %s is negative.' % value) 762 763 764def AddMinInstancesFlag(parser): 765 """Add min scaling flag.""" 766 parser.add_argument( 767 '--min-instances', 768 type=_ScaleValue, 769 help=('The minimum number of container instances of the Service to run ' 770 "or 'default' to remove any minimum.")) 771 772 773def AddMaxInstancesFlag(parser): 774 """Add max scaling flag.""" 775 parser.add_argument( 776 '--max-instances', 777 type=_ScaleValue, 778 help=('The maximum number of container instances of the Service to run. ' 779 "Use 'default' to unset the limit and use the platform default.")) 780 781 782def AddCommandFlag(parser): 783 """Add flags for specifying container's startup command.""" 784 parser.add_argument( 785 '--command', 786 metavar='COMMAND', 787 type=arg_parsers.ArgList(), 788 action=arg_parsers.UpdateAction, 789 help='Entrypoint for the container image. If not specified, the ' 790 'container image\'s default Entrypoint is run. ' 791 'To reset this field to its default, pass an empty string.') 792 793 794def AddArgsFlag(parser): 795 """Add flags for specifying container's startup args.""" 796 parser.add_argument( 797 '--args', 798 metavar='ARG', 799 type=arg_parsers.ArgList(), 800 action=arg_parsers.UpdateAction, 801 help='Comma-separated arguments passed to the command run by the ' 802 'container image. If not specified and no \'--command\' is provided, the ' 803 'container image\'s default Cmd is used. Otherwise, if not specified, no ' 804 'arguments are passed. ' 805 'To reset this field to its default, pass an empty string.') 806 807 808def AddClientNameAndVersionFlags(parser): 809 """Add flags for specifying the client name and version annotations.""" 810 parser.add_argument( 811 '--client-name', 812 hidden=True, 813 help="Name of the client handling the deployment. Defaults to ``global'' " 814 'if this and --client-version are both unspecified.') 815 parser.add_argument( 816 '--client-version', 817 hidden=True, 818 help='Version of the client handling the deployment. Defaults to the ' 819 'current gcloud version if this and --client-name are both unspecified.') 820 821 822def _PortValue(value): 823 """Returns True if port value is an int within range or 'default'.""" 824 try: 825 return value == 'default' or (1 <= int(value) <= 65535) 826 except ValueError: 827 return False 828 829 830def AddPortFlag(parser): 831 """Add port flag to override $PORT.""" 832 parser.add_argument( 833 '--port', 834 type=arg_parsers.CustomFunctionValidator( 835 _PortValue, 836 'must be an integer between 1 and 65535, inclusive, or "default".'), 837 help='Container port to receive requests at. Also sets the $PORT ' 838 'environment variable. Must be a number between 1 and 65535, inclusive. ' 839 'To unset this field, pass the special value "default".') 840 841 842def AddHttp2Flag(parser): 843 """Add http/2 flag to set the port name.""" 844 parser.add_argument( 845 '--use-http2', 846 action=arg_parsers.StoreTrueFalseAction, 847 help='Whether to use HTTP/2 for connections to the service.') 848 849 850def AddParallelismFlag(parser): 851 """Add job parallelism/concurrency flag.""" 852 parser.add_argument( 853 '--parallelism', 854 type=arg_parsers.BoundedInt(lower_bound=1), 855 default=1, 856 help='Number of instances that may run concurrently. ' 857 'Must be less than or equal to the number of completions.') 858 859 860def AddCompletionsFlag(parser): 861 """Add job number of completions flag.""" 862 parser.add_argument( 863 '--completions', 864 type=arg_parsers.BoundedInt(lower_bound=1), 865 default=1, 866 help='Number of instances that must run to completion for the job to be ' 867 'considered done. Use this flag to trigger multiple runs of the job.') 868 869 870def AddMaxAttemptsFlag(parser): 871 """Add job max attempts flag to specify number of instance restarts.""" 872 parser.add_argument( 873 '--max-attempts', 874 type=arg_parsers.BoundedInt(lower_bound=1), 875 default=6, 876 help='Number of times an instance will be allowed to run in case of ' 877 'failure before being failed permanently. This applies per-instance, not ' 878 'per-job. If set to 1, instances will only run once and never be ' 879 'restarted on failure. If set to any other positive integer N, instances ' 880 'will be allowed to restart N-1 times.') 881 882 883def AddWaitForCompletionFlag(parser): 884 """Add job flag to poll until completion on create.""" 885 parser.add_argument( 886 '--wait-for-completion', 887 default=False, 888 action='store_true', 889 help='Wait until the job has completed running before finishing polling. ' 890 'If not set, polling completes when the job has started.') 891 892 893def AddBinAuthzPolicyFlags(parser, with_clear=True): 894 """Add flags for BinAuthz.""" 895 policy_group = parser 896 if with_clear: 897 policy_group = parser.add_mutually_exclusive_group() 898 policy_group.add_argument( 899 '--clear-binary-authorization', 900 default=False, 901 action='store_true', 902 help='Remove any previously set Binary Authorization policy.') 903 policy_group.add_argument( 904 '--binary-authorization', 905 metavar='POLICY', 906 # Don't actually validate the value here, let that happen server-side 907 # so the future change to support named policies will be backwards 908 # compatible with older gcloud versions. 909 help='Binary Authorization policy to check against. This must be set to ' 910 '"default".') 911 912 913def AddBinAuthzBreakglassFlag(parser): 914 parser.add_argument( 915 '--breakglass', 916 metavar='JUSTIFICATION', 917 help='Justification to bypass Binary Authorization policy constraints ' 918 'and allow the operation. See ' 919 'https://cloud.google.com/binary-authorization/docs/using-breakglass ' 920 'for more information.') 921 922 923def _HasChanges(args, flags): 924 """True iff any of the passed flags are set.""" 925 return any(FlagIsExplicitlySet(args, flag) for flag in flags) 926 927 928def _HasEnvChanges(args): 929 """True iff any of the env var flags are set.""" 930 env_flags = [ 931 'update_env_vars', 'set_env_vars', 'remove_env_vars', 'clear_env_vars' 932 ] 933 return _HasChanges(args, env_flags) 934 935 936def _HasCloudSQLChanges(args): 937 """True iff any of the cloudsql flags are set.""" 938 instances_flags = [ 939 'add_cloudsql_instances', 'set_cloudsql_instances', 940 'remove_cloudsql_instances', 'clear_cloudsql_instances' 941 ] 942 return _HasChanges(args, instances_flags) 943 944 945def _EnabledCloudSqlApiRequired(args): 946 """True iff flags that add or set cloud sql instances are set.""" 947 instances_flags = ( 948 'add_cloudsql_instances', 949 'set_cloudsql_instances', 950 ) 951 return _HasChanges(args, instances_flags) 952 953 954def _HasLabelChanges(args): 955 """True iff any of the label flags are set.""" 956 label_flags = ['labels', 'update_labels', 'clear_labels', 'remove_labels'] 957 return _HasChanges(args, label_flags) 958 959 960def _HasSecretsChanges(args): 961 """True iff any of the secret flags are set.""" 962 secret_flags = [ 963 'update_secrets', 'set_secrets', 'remove_secrets', 'clear_secrets' 964 ] 965 return _HasChanges(args, secret_flags) 966 967 968def _HasConfigMapsChanges(args): 969 """True iff any of the config maps flags are set.""" 970 config_maps_flags = [ 971 'update_config_maps', 'set_config_maps', 'remove_config_maps', 972 'clear_config_maps' 973 ] 974 return _HasChanges(args, config_maps_flags) 975 976 977def _HasTrafficTagsChanges(args): 978 """True iff any of the traffic tags flags are set.""" 979 tags_flags = ['update_tags', 'set_tags', 'remove_tags', 'clear_tags'] 980 return _HasChanges(args, tags_flags) 981 982 983def _HasTrafficChanges(args): 984 """True iff any of the traffic flags are set.""" 985 traffic_flags = ['to_revisions', 'to_tags', 'to_latest'] 986 return _HasChanges(args, traffic_flags) or _HasTrafficTagsChanges(args) 987 988 989def _GetEnvChanges(args): 990 """Return config_changes.EnvVarLiteralChanges for given args.""" 991 return config_changes.EnvVarLiteralChanges( 992 updates=_StripKeys( 993 getattr(args, 'update_env_vars', None) or args.set_env_vars or {}), 994 removes=_MapLStrip(getattr(args, 'remove_env_vars', None) or []), 995 clear_others=bool(args.set_env_vars or args.clear_env_vars)) 996 997 998def _GetScalingChanges(args): 999 """Returns the list of changes for scaling for given args.""" 1000 result = [] 1001 if 'min_instances' in args and args.min_instances is not None: 1002 scale_value = args.min_instances 1003 if scale_value.restore_default or scale_value.instance_count == 0: 1004 result.append( 1005 config_changes.DeleteTemplateAnnotationChange( 1006 revision.MIN_SCALE_ANNOTATION)) 1007 else: 1008 result.append( 1009 config_changes.SetTemplateAnnotationChange( 1010 revision.MIN_SCALE_ANNOTATION, str(scale_value.instance_count))) 1011 if 'max_instances' in args and args.max_instances is not None: 1012 scale_value = args.max_instances 1013 if scale_value.restore_default: 1014 result.append( 1015 config_changes.DeleteTemplateAnnotationChange( 1016 revision.MAX_SCALE_ANNOTATION)) 1017 else: 1018 result.append( 1019 config_changes.SetTemplateAnnotationChange( 1020 revision.MAX_SCALE_ANNOTATION, str(scale_value.instance_count))) 1021 return result 1022 1023 1024def _IsVolumeMountKey(key): 1025 """Returns True if the key refers to a volume mount.""" 1026 return key.startswith('/') 1027 1028 1029def _GetSecretsChanges(args): 1030 """Return secret env var and volume changes for given args.""" 1031 volume_kwargs = {} 1032 env_kwargs = {} 1033 1034 updates = _StripKeys( 1035 getattr(args, 'update_secrets', None) or args.set_secrets or {}) 1036 volume_kwargs['updates'] = { 1037 k: v for k, v in updates.items() if _IsVolumeMountKey(k) 1038 } 1039 env_kwargs['updates'] = { 1040 k: v for k, v in updates.items() if not _IsVolumeMountKey(k) 1041 } 1042 1043 removes = _MapLStrip(getattr(args, 'remove_secrets', None) or []) 1044 volume_kwargs['removes'] = [k for k in removes if _IsVolumeMountKey(k)] 1045 env_kwargs['removes'] = [k for k in removes if not _IsVolumeMountKey(k)] 1046 1047 clear_others = bool(args.set_secrets or args.clear_secrets) 1048 env_kwargs['clear_others'] = clear_others 1049 volume_kwargs['clear_others'] = clear_others 1050 1051 secret_changes = [] 1052 if any(env_kwargs.values()): 1053 secret_changes.append(config_changes.SecretEnvVarChanges(**env_kwargs)) 1054 if any(volume_kwargs.values()): 1055 secret_changes.append(config_changes.SecretVolumeChanges(**volume_kwargs)) 1056 return secret_changes 1057 1058 1059def _GetConfigMapsChanges(args): 1060 """Return config map env var and volume changes for given args.""" 1061 volume_kwargs = {} 1062 env_kwargs = {} 1063 1064 updates = _StripKeys( 1065 getattr(args, 'update_config_maps', None) or args.set_config_maps or {}) 1066 volume_kwargs['updates'] = { 1067 k: v for k, v in updates.items() if _IsVolumeMountKey(k) 1068 } 1069 env_kwargs['updates'] = { 1070 k: v for k, v in updates.items() if not _IsVolumeMountKey(k) 1071 } 1072 1073 removes = _MapLStrip(getattr(args, 'remove_config_maps', None) or []) 1074 volume_kwargs['removes'] = [k for k in removes if _IsVolumeMountKey(k)] 1075 env_kwargs['removes'] = [k for k in removes if not _IsVolumeMountKey(k)] 1076 1077 clear_others = bool(args.set_config_maps or args.clear_config_maps) 1078 env_kwargs['clear_others'] = clear_others 1079 volume_kwargs['clear_others'] = clear_others 1080 1081 config_maps_changes = [] 1082 if any(env_kwargs.values()): 1083 config_maps_changes.append( 1084 config_changes.ConfigMapEnvVarChanges(**env_kwargs)) 1085 if any(volume_kwargs.values()): 1086 config_maps_changes.append( 1087 config_changes.ConfigMapVolumeChanges(**volume_kwargs)) 1088 return config_maps_changes 1089 1090 1091def PromptToEnableApi(service_name): 1092 """Prompts to enable the API and throws if the answer is no. 1093 1094 Args: 1095 service_name: str, The service token of the API to prompt for. 1096 """ 1097 if not properties.VALUES.core.should_prompt_to_enable_api.GetBool(): 1098 return 1099 1100 project = properties.VALUES.core.project.Get(required=True) 1101 # Don't prompt to enable an already enabled API 1102 if not enable_api.IsServiceEnabled(project, service_name): 1103 if console_io.PromptContinue( 1104 default=False, 1105 cancel_on_no=True, 1106 prompt_string=('API [{}] not enabled on project [{}]. ' 1107 'Would you like to enable and retry (this will take a ' 1108 'few minutes)?').format(service_name, project)): 1109 enable_api.EnableService(project, service_name) 1110 1111 1112_CLOUD_SQL_API_SERVICE_TOKEN = 'sql-component.googleapis.com' 1113_CLOUD_SQL_ADMIN_API_SERVICE_TOKEN = 'sqladmin.googleapis.com' 1114 1115 1116def _CheckCloudSQLApiEnablement(): 1117 if not properties.VALUES.core.should_prompt_to_enable_api.GetBool(): 1118 return 1119 try: 1120 PromptToEnableApi(_CLOUD_SQL_API_SERVICE_TOKEN) 1121 PromptToEnableApi(_CLOUD_SQL_ADMIN_API_SERVICE_TOKEN) 1122 except (services_exceptions.GetServicePermissionDeniedException, 1123 apitools_exceptions.HttpError): 1124 log.status.Print('Skipped validating Cloud SQL API and Cloud SQL Admin API' 1125 ' enablement due to an issue contacting the Service Usage ' 1126 ' API. Please ensure the Cloud SQL API and Cloud SQL Admin' 1127 ' API are activated (see ' 1128 'https://console.cloud.google.com/apis/dashboard).') 1129 1130 1131def _GetTrafficChanges(args): 1132 """Returns a changes for traffic assignment based on the flags.""" 1133 # Check if args has tags changes again in case args does not include tags 1134 # flags. Tags will launch in the alpha release track only. 1135 if _HasTrafficTagsChanges(args): 1136 update_tags = args.update_tags or args.set_tags 1137 remove_tags = args.remove_tags 1138 clear_other_tags = bool(args.set_tags) or args.clear_tags 1139 else: 1140 update_tags = None 1141 remove_tags = None 1142 clear_other_tags = False 1143 by_tag = False 1144 if args.to_latest: 1145 # Mutually exlcusive flag with to-revisions, to-tags 1146 new_percentages = {traffic.LATEST_REVISION_KEY: 100} 1147 elif args.to_revisions: 1148 new_percentages = args.to_revisions 1149 elif FlagIsExplicitlySet(args, 'to_tags'): 1150 new_percentages = args.to_tags 1151 by_tag = True 1152 else: 1153 new_percentages = {} 1154 1155 return config_changes.TrafficChanges(new_percentages, by_tag, update_tags, 1156 remove_tags, clear_other_tags) 1157 1158 1159def _GetIngressChanges(args): 1160 """Returns changes to ingress traffic allowed based on the flags.""" 1161 platform = platforms.GetPlatform() 1162 if platform == platforms.PLATFORM_MANAGED: 1163 return config_changes.SetAnnotationChange(service.INGRESS_ANNOTATION, 1164 args.ingress) 1165 elif args.ingress == service.INGRESS_INTERNAL: 1166 return config_changes.EndpointVisibilityChange(True) 1167 elif args.ingress == service.INGRESS_ALL: 1168 return config_changes.EndpointVisibilityChange(False) 1169 else: 1170 raise serverless_exceptions.ConfigurationError( 1171 'Ingress value `{}` is not supported on platform `{}`.'.format( 1172 args.ingress, platform)) 1173 1174 1175def GetConfigurationChanges(args): 1176 """Returns a list of changes to Configuration, based on the flags set.""" 1177 changes = [] 1178 1179 # Set client name and version regardless of whether or not it was specified. 1180 if 'client_name' in args: 1181 is_either_specified = ( 1182 args.IsSpecified('client_name') or args.IsSpecified('client_version')) 1183 changes.append( 1184 config_changes.SetClientNameAndVersionAnnotationChange( 1185 args.client_name if is_either_specified else 'gcloud', 1186 args.client_version 1187 if is_either_specified else config.CLOUD_SDK_VERSION)) 1188 1189 # FlagIsExplicitlySet can't be used here because args.image is also set from 1190 # code in deploy.py. 1191 if hasattr(args, 'image') and args.image is not None: 1192 changes.append(config_changes.ImageChange(args.image)) 1193 1194 changes.extend(_GetScalingChanges(args)) 1195 if _HasEnvChanges(args): 1196 changes.append(_GetEnvChanges(args)) 1197 1198 if _HasTrafficChanges(args): 1199 changes.append(_GetTrafficChanges(args)) 1200 1201 if _HasCloudSQLChanges(args): 1202 region = GetRegion(args) 1203 project = ( 1204 getattr(args, 'project', None) or 1205 properties.VALUES.core.project.Get(required=True)) 1206 if _EnabledCloudSqlApiRequired(args): 1207 _CheckCloudSQLApiEnablement() 1208 changes.append(config_changes.CloudSQLChanges(project, region, args)) 1209 1210 if _HasSecretsChanges(args): 1211 changes.extend(_GetSecretsChanges(args)) 1212 1213 if _HasConfigMapsChanges(args): 1214 changes.extend(_GetConfigMapsChanges(args)) 1215 1216 if 'no_traffic' in args and args.no_traffic: 1217 changes.append(config_changes.NoTrafficChange()) 1218 1219 if 'cpu' in args and args.cpu: 1220 changes.append(config_changes.ResourceChanges(cpu=args.cpu)) 1221 if 'memory' in args and args.memory: 1222 changes.append(config_changes.ResourceChanges(memory=args.memory)) 1223 if 'concurrency' in args and args.concurrency: 1224 changes.append( 1225 config_changes.ConcurrencyChanges(concurrency=args.concurrency)) 1226 if 'timeout' in args and args.timeout: 1227 changes.append(config_changes.TimeoutChanges(timeout=args.timeout)) 1228 if 'service_account' in args and args.service_account: 1229 changes.append( 1230 config_changes.ServiceAccountChanges( 1231 service_account=args.service_account)) 1232 if _HasLabelChanges(args): 1233 additions = ( 1234 args.labels 1235 if FlagIsExplicitlySet(args, 'labels') else args.update_labels) 1236 diff = labels_util.Diff( 1237 additions=additions, 1238 subtractions=args.remove_labels if 'remove_labels' in args else [], 1239 clear=args.clear_labels if 'clear_labels' in args else False) 1240 if diff.MayHaveUpdates(): 1241 changes.append(config_changes.LabelChanges(diff)) 1242 if 'revision_suffix' in args and args.revision_suffix: 1243 changes.append(config_changes.RevisionNameChanges(args.revision_suffix)) 1244 if 'sandbox' in args and args.sandbox: 1245 changes.append(config_changes.SandboxChange(args.sandbox)) 1246 if 'vpc_connector' in args and args.vpc_connector: 1247 changes.append(config_changes.VpcConnectorChange(args.vpc_connector)) 1248 if FlagIsExplicitlySet(args, 'vpc_egress'): 1249 changes.append( 1250 config_changes.SetTemplateAnnotationChange( 1251 container_resource.EGRESS_SETTINGS_ANNOTATION, args.vpc_egress)) 1252 if 'clear_vpc_connector' in args and args.clear_vpc_connector: 1253 # MUST be after 'vpc_egress' change. 1254 changes.append(config_changes.ClearVpcConnectorChange()) 1255 if 'connectivity' in args and args.connectivity: 1256 if args.connectivity == 'internal': 1257 changes.append(config_changes.EndpointVisibilityChange(True)) 1258 elif args.connectivity == 'external': 1259 changes.append(config_changes.EndpointVisibilityChange(False)) 1260 if FlagIsExplicitlySet(args, 'ingress'): 1261 changes.append(_GetIngressChanges(args)) 1262 if 'command' in args and args.command is not None: 1263 # Allow passing an empty string here to reset the field 1264 changes.append(config_changes.ContainerCommandChange(args.command)) 1265 if 'args' in args and args.args is not None: 1266 # Allow passing an empty string here to reset the field 1267 changes.append(config_changes.ContainerArgsChange(args.args)) 1268 if FlagIsExplicitlySet(args, 'port'): 1269 changes.append(config_changes.ContainerPortChange(port=args.port)) 1270 if FlagIsExplicitlySet(args, 'use_http2'): 1271 changes.append(config_changes.ContainerPortChange(use_http2=args.use_http2)) 1272 if FlagIsExplicitlySet(args, 'tag'): 1273 # MUST be after 'revision_suffix' change 1274 changes.append(config_changes.TagOnDeployChange(args.tag)) 1275 if FlagIsExplicitlySet(args, 'parallelism'): 1276 changes.append(config_changes.SpecChange('parallelism', args.parallelism)) 1277 if FlagIsExplicitlySet(args, 'completions'): 1278 changes.append(config_changes.SpecChange('completions', args.completions)) 1279 if FlagIsExplicitlySet(args, 'max_attempts'): 1280 changes.append(config_changes.JobMaxAttemptsChange(args.max_attempts)) 1281 if FlagIsExplicitlySet(args, 'binary_authorization'): 1282 changes.append( 1283 config_changes.SetAnnotationChange( 1284 k8s_object.BINAUTHZ_POLICY_ANNOTATION, 1285 args.binary_authorization)) 1286 if FlagIsExplicitlySet(args, 'clear_binary_authorization'): 1287 changes.append( 1288 config_changes.DeleteAnnotationChange( 1289 k8s_object.BINAUTHZ_POLICY_ANNOTATION)) 1290 if FlagIsExplicitlySet(args, 'breakglass'): 1291 changes.append( 1292 config_changes.SetAnnotationChange( 1293 k8s_object.BINAUTHZ_BREAKGLASS_ANNOTATION, 1294 args.breakglass)) 1295 return changes 1296 1297 1298def ValidateResource(resource_ref): 1299 """Validate resource name.""" 1300 # Valid resource names comprise only alphanumeric characters and dashes. Must 1301 # not begin or end with a dash, and must not contain more than 63 characters. 1302 # Must be lowercase. 1303 k8s_resource_name_regex = re.compile( 1304 r'(?=^[a-z0-9-]{1,63}$)(?!^\-.*)(?!.*\-$)') 1305 if not k8s_resource_name_regex.match(resource_ref.Name()): 1306 raise serverless_exceptions.ArgumentError( 1307 'Invalid resource name [{}]. The name must use only lowercase ' 1308 'alphanumeric characters and dashes, cannot begin or end with a dash, ' 1309 'and cannot be longer than 63 characters.'.format(resource_ref.Name())) 1310 1311 1312def PromptForRegion(): 1313 """Prompt for region from list of available regions. 1314 1315 This method is referenced by the declaritive iam commands as a fallthrough 1316 for getting the region. 1317 1318 Returns: 1319 The region specified by the user, str 1320 """ 1321 if console_io.CanPrompt(): 1322 client = global_methods.GetServerlessClientInstance() 1323 all_regions = global_methods.ListRegions(client) 1324 idx = console_io.PromptChoice( 1325 all_regions, message='Please specify a region:\n', cancel_option=True) 1326 region = all_regions[idx] 1327 log.status.Print('To make this the default region, run ' 1328 '`gcloud config set run/region {}`.\n'.format(region)) 1329 return region 1330 1331 1332def GetRegion(args, prompt=False): 1333 """Prompt for region if not provided. 1334 1335 Region is decided in the following order: 1336 - region argument; 1337 - run/region gcloud config; 1338 - prompt user. 1339 1340 Args: 1341 args: Namespace, The args namespace. 1342 prompt: bool, whether to attempt to prompt. 1343 1344 Returns: 1345 A str representing region. 1346 """ 1347 if getattr(args, 'region', None): 1348 return args.region 1349 if properties.VALUES.run.region.IsExplicitlySet(): 1350 return properties.VALUES.run.region.Get() 1351 if prompt: 1352 region = PromptForRegion() 1353 if region: 1354 # set the region on args, so we're not embarassed the next time we call 1355 # GetRegion 1356 args.region = region 1357 return region 1358 1359 1360def GetAllowUnauthenticated(args, client=None, service_ref=None, prompt=False): 1361 """Return bool for the explicit intent to allow unauth invocations or None. 1362 1363 If --[no-]allow-unauthenticated is set, return that value. If not set, 1364 prompt for value if desired. If prompting not necessary or doable, 1365 return None, indicating that no action needs to be taken. 1366 1367 Args: 1368 args: Namespace, The args namespace 1369 client: from googlecloudsdk.command_lib.run import serverless_operations 1370 serverless_operations.ServerlessOperations object 1371 service_ref: service resource reference (e.g. args.CONCEPTS.service.Parse()) 1372 prompt: bool, whether to attempt to prompt. 1373 1374 Returns: 1375 bool indicating whether to allow/unallow unauthenticated or None if N/A 1376 """ 1377 if getattr(args, 'allow_unauthenticated', None) is not None: 1378 return args.allow_unauthenticated 1379 1380 if prompt: 1381 # Need to check if the user has permissions before we prompt 1382 assert client is not None and service_ref is not None 1383 if client.CanSetIamPolicyBinding(service_ref): 1384 return console_io.PromptContinue( 1385 prompt_string=('Allow unauthenticated invocations ' 1386 'to [{}]'.format(service_ref.servicesId)), 1387 default=False) 1388 else: 1389 pretty_print.Info( 1390 'This service will require authentication to be invoked.') 1391 return None 1392 1393 1394def GetKubeconfig(file_path=None): 1395 """Get config from kubeconfig file. 1396 1397 Get config from potentially 3 different places, falling back to the next 1398 option as necessary: 1399 1. file_path specified as argument by the user 1400 2. List of file paths specified in $KUBECONFIG 1401 3. Default config path (~/.kube/config) 1402 1403 Args: 1404 file_path: str, the path to the kubeconfig if provided by the user 1405 1406 Returns: 1407 dict: config object 1408 1409 Raises: 1410 KubeconfigError: if $KUBECONFIG is set but contains no valid paths 1411 """ 1412 if file_path: 1413 return kubeconfig.Kubeconfig.LoadFromFile(files.ExpandHomeDir(file_path)) 1414 if encoding.GetEncodedValue(os.environ, 'KUBECONFIG'): 1415 config_paths = encoding.GetEncodedValue(os.environ, 1416 'KUBECONFIG').split(os.pathsep) 1417 kube_config = None 1418 # Merge together all valid paths into single config 1419 for path in config_paths: 1420 try: 1421 other_config = kubeconfig.Kubeconfig.LoadFromFile( 1422 files.ExpandHomeDir(path)) 1423 if not kube_config: 1424 kube_config = other_config 1425 else: 1426 kube_config.Merge(other_config) 1427 except kubeconfig.Error: 1428 pass 1429 if not kube_config: 1430 raise KubeconfigError('No valid file paths found in $KUBECONFIG') 1431 return kube_config 1432 return kubeconfig.Kubeconfig.LoadFromFile( 1433 files.ExpandHomeDir(_DEFAULT_KUBECONFIG_PATH)) 1434 1435 1436def FlagIsExplicitlySet(args, flag): 1437 """Return True if --flag is explicitly passed by the user.""" 1438 # hasattr check is to allow the same code to work for release tracks that 1439 # don't have the args at all yet. 1440 return hasattr(args, flag) and args.IsSpecified(flag) 1441 1442 1443def VerifyManagedFlags(args, release_track, product): 1444 """Raise ConfigurationError if args aren't valid for managed Cloud Run.""" 1445 1446 if product == Product.EVENTS: 1447 raise serverless_exceptions.ConfigurationError( 1448 'The flag --platform={0} is not supported. ' 1449 'Instead of using the flag --platform={0} in "gcloud events", ' 1450 'run "gcloud eventarc".' 1451 .format(platforms.PLATFORM_MANAGED)) 1452 1453 error_msg = ('The `{flag}` flag is not supported on the fully managed ' 1454 'version of Cloud Run. Specify `--platform {platform}` or run ' 1455 '`gcloud config set run/platform {platform}` to work with ' 1456 '{platform_desc}.') 1457 1458 if FlagIsExplicitlySet(args, 'connectivity'): 1459 raise serverless_exceptions.ConfigurationError( 1460 error_msg.format( 1461 flag='--connectivity=[internal|external]', 1462 platform=platforms.PLATFORM_GKE, 1463 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1464 platforms.PLATFORM_GKE])) 1465 1466 if FlagIsExplicitlySet(args, 'namespace'): 1467 raise serverless_exceptions.ConfigurationError( 1468 error_msg.format( 1469 flag='--namespace', 1470 platform=platforms.PLATFORM_GKE, 1471 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1472 platforms.PLATFORM_GKE])) 1473 1474 if FlagIsExplicitlySet(args, 'cluster'): 1475 raise serverless_exceptions.ConfigurationError( 1476 error_msg.format( 1477 flag='--cluster', 1478 platform=platforms.PLATFORM_GKE, 1479 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1480 platforms.PLATFORM_GKE])) 1481 1482 if FlagIsExplicitlySet(args, 'cluster_location'): 1483 raise serverless_exceptions.ConfigurationError( 1484 error_msg.format( 1485 flag='--cluster-location', 1486 platform=platforms.PLATFORM_GKE, 1487 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1488 platforms.PLATFORM_GKE])) 1489 1490 if _HasSecretsChanges(args) and release_track != base.ReleaseTrack.ALPHA: 1491 raise serverless_exceptions.ConfigurationError( 1492 'The `--[update|set|remove|clear]-secrets` flags are only supported ' 1493 'in the alpha release track on the fully managed version of Cloud ' 1494 'Run. Use `gcloud alpha` to enable these flags.') 1495 1496 if _HasConfigMapsChanges(args): 1497 raise serverless_exceptions.ConfigurationError( 1498 error_msg.format( 1499 flag='--[update|set|remove|clear]-config-maps', 1500 platform=platforms.PLATFORM_GKE, 1501 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1502 platforms.PLATFORM_GKE])) 1503 1504 if FlagIsExplicitlySet(args, 1505 'use_http2') and release_track == base.ReleaseTrack.GA: 1506 raise serverless_exceptions.ConfigurationError( 1507 'The `--use-http2` flag is only supported in the beta release ' 1508 'track on the fully managed version of Cloud Run. Use `gcloud alpha` ' 1509 'to set `--use-http2` on Cloud Run (fully managed). Alternatively, ' 1510 'specify `--platform gke` or run ' 1511 '`gcloud config set run/platform gke`.') 1512 1513 if FlagIsExplicitlySet(args, 'broker'): 1514 raise serverless_exceptions.ConfigurationError( 1515 error_msg.format( 1516 flag='--broker', 1517 platform=platforms.PLATFORM_GKE, 1518 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1519 platforms.PLATFORM_GKE])) 1520 1521 if FlagIsExplicitlySet(args, 'custom_type') and product == Product.EVENTS: 1522 raise serverless_exceptions.ConfigurationError( 1523 error_msg.format( 1524 flag='--custom-type', 1525 platform=platforms.PLATFORM_GKE, 1526 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1527 platforms.PLATFORM_GKE])) 1528 1529 if FlagIsExplicitlySet(args, 'kubeconfig'): 1530 raise serverless_exceptions.ConfigurationError( 1531 error_msg.format( 1532 flag='--kubeconfig', 1533 platform=platforms.PLATFORM_KUBERNETES, 1534 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1535 platforms.PLATFORM_KUBERNETES])) 1536 1537 if FlagIsExplicitlySet(args, 'context'): 1538 raise serverless_exceptions.ConfigurationError( 1539 error_msg.format( 1540 flag='--context', 1541 platform=platforms.PLATFORM_KUBERNETES, 1542 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1543 platforms.PLATFORM_KUBERNETES])) 1544 1545 if (FlagIsExplicitlySet(args, 'timeout') and 1546 release_track == base.ReleaseTrack.GA): 1547 if args.timeout > _FIFTEEN_MINUTES: 1548 raise serverless_exceptions.ConfigurationError( 1549 'Timeout duration must be less than 15m. Timeouts above 15m are in ' 1550 'Beta. Use "gcloud beta run ..." to set timeouts above 15m.') 1551 1552 if FlagIsExplicitlySet(args, 'trigger_filters'): 1553 raise serverless_exceptions.ConfigurationError( 1554 error_msg.format( 1555 flag='--trigger-filters', 1556 platform=platforms.PLATFORM_GKE, 1557 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1558 platforms.PLATFORM_GKE])) 1559 1560 1561def VerifyGKEFlags(args, release_track, product): 1562 """Raise ConfigurationError if args includes OnePlatform only arguments.""" 1563 error_msg = ('The `{flag}` flag is not supported with Cloud Run for Anthos ' 1564 'deployed on Google Cloud. Specify `--platform {platform}` or ' 1565 'run `gcloud config set run/platform {platform}` to work with ' 1566 '{platform_desc}.') 1567 1568 if FlagIsExplicitlySet(args, 'allow_unauthenticated'): 1569 raise serverless_exceptions.ConfigurationError( 1570 'The `--[no-]allow-unauthenticated` flag is not supported with ' 1571 'Cloud Run for Anthos deployed on Google Cloud. All deployed ' 1572 'services allow unauthenticated requests. The `--connectivity` ' 1573 'flag can limit which network a service is available on to reduce ' 1574 'access.') 1575 1576 if (FlagIsExplicitlySet(args, 'connectivity') and 1577 FlagIsExplicitlySet(args, 'ingress')): 1578 raise serverless_exceptions.ConfigurationError( 1579 'Cannot specify both the `--connectivity` and `--ingress` flags.' 1580 ' `--connectivity` is deprecated in favor of `--ingress`.') 1581 1582 if FlagIsExplicitlySet(args, 'region'): 1583 raise serverless_exceptions.ConfigurationError( 1584 error_msg.format( 1585 flag='--region', 1586 platform=platforms.PLATFORM_MANAGED, 1587 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1588 platforms.PLATFORM_MANAGED])) 1589 1590 if FlagIsExplicitlySet(args, 'sandbox'): 1591 raise serverless_exceptions.ConfigurationError( 1592 error_msg.format( 1593 flag='--sandbox', 1594 platform=platforms.PLATFORM_MANAGED, 1595 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1596 platforms.PLATFORM_MANAGED])) 1597 1598 if FlagIsExplicitlySet(args, 'vpc_connector'): 1599 raise serverless_exceptions.ConfigurationError( 1600 error_msg.format( 1601 flag='--vpc-connector', 1602 platform=platforms.PLATFORM_MANAGED, 1603 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1604 platforms.PLATFORM_MANAGED])) 1605 1606 if FlagIsExplicitlySet(args, 'clear_vpc_connector'): 1607 raise serverless_exceptions.ConfigurationError( 1608 error_msg.format( 1609 flag='--clear-vpc-connector', 1610 platform=platforms.PLATFORM_MANAGED, 1611 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1612 platforms.PLATFORM_MANAGED])) 1613 1614 if FlagIsExplicitlySet(args, 'vpc_egress'): 1615 raise serverless_exceptions.ConfigurationError( 1616 error_msg.format( 1617 flag='--vpc-egress', 1618 platform=platforms.PLATFORM_MANAGED, 1619 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1620 platforms.PLATFORM_MANAGED])) 1621 1622 if FlagIsExplicitlySet(args, 'binary_authorization'): 1623 raise serverless_exceptions.ConfigurationError( 1624 error_msg.format( 1625 flag='--binary-authorization', 1626 platform=platforms.PLATFORM_MANAGED, 1627 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1628 platforms.PLATFORM_MANAGED])) 1629 1630 if FlagIsExplicitlySet(args, 'clear_binary_authorization'): 1631 raise serverless_exceptions.ConfigurationError( 1632 error_msg.format( 1633 flag='--clear-binary-authorization', 1634 platform=platforms.PLATFORM_MANAGED, 1635 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1636 platforms.PLATFORM_MANAGED])) 1637 1638 if FlagIsExplicitlySet(args, 'breakglass'): 1639 raise serverless_exceptions.ConfigurationError( 1640 error_msg.format( 1641 flag='--breakglass', 1642 platform=platforms.PLATFORM_MANAGED, 1643 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1644 platforms.PLATFORM_MANAGED])) 1645 1646 if FlagIsExplicitlySet(args, 'kubeconfig'): 1647 raise serverless_exceptions.ConfigurationError( 1648 error_msg.format( 1649 flag='--kubeconfig', 1650 platform=platforms.PLATFORM_KUBERNETES, 1651 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1652 platforms.PLATFORM_KUBERNETES])) 1653 1654 if FlagIsExplicitlySet(args, 'context'): 1655 raise serverless_exceptions.ConfigurationError( 1656 error_msg.format( 1657 flag='--context', 1658 platform=platforms.PLATFORM_KUBERNETES, 1659 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1660 platforms.PLATFORM_KUBERNETES])) 1661 1662 1663def VerifyKubernetesFlags(args, release_track, product): 1664 """Raise ConfigurationError if args includes OnePlatform or GKE only arguments.""" 1665 error_msg = ('The `{flag}` flag is not supported with Cloud Run for Anthos ' 1666 'deployed on VMware. Specify `--platform {platform}` or run ' 1667 '`gcloud config set run/platform {platform}` to work with ' 1668 '{platform_desc}.') 1669 1670 if FlagIsExplicitlySet(args, 'allow_unauthenticated'): 1671 raise serverless_exceptions.ConfigurationError( 1672 'The `--[no-]allow-unauthenticated` flag is not supported with ' 1673 'Cloud Run for Anthos deployed on VMware. All deployed ' 1674 'services allow unauthenticated requests. The `--connectivity` ' 1675 'flag can limit which network a service is available on to reduce ' 1676 'access.') 1677 1678 if (FlagIsExplicitlySet(args, 'connectivity') and 1679 FlagIsExplicitlySet(args, 'ingress')): 1680 raise serverless_exceptions.ConfigurationError( 1681 'Cannot specify both the `--connectivity` and `--ingress` flags.' 1682 ' `--connectivity` is deprecated in favor of `--ingress`.') 1683 1684 if FlagIsExplicitlySet(args, 'region'): 1685 raise serverless_exceptions.ConfigurationError( 1686 error_msg.format( 1687 flag='--region', 1688 platform=platforms.PLATFORM_MANAGED, 1689 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1690 platforms.PLATFORM_MANAGED])) 1691 1692 if FlagIsExplicitlySet(args, 'sandbox'): 1693 raise serverless_exceptions.ConfigurationError( 1694 error_msg.format( 1695 flag='--sandbox', 1696 platform=platforms.PLATFORM_MANAGED, 1697 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1698 platforms.PLATFORM_MANAGED])) 1699 1700 if FlagIsExplicitlySet(args, 'vpc_connector'): 1701 raise serverless_exceptions.ConfigurationError( 1702 error_msg.format( 1703 flag='--vpc-connector', 1704 platform='managed', 1705 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS['managed'])) 1706 1707 if FlagIsExplicitlySet(args, 'clear_vpc_connector'): 1708 raise serverless_exceptions.ConfigurationError( 1709 error_msg.format( 1710 flag='--clear-vpc-connector', 1711 platform=platforms.PLATFORM_MANAGED, 1712 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1713 platforms.PLATFORM_MANAGED])) 1714 1715 if FlagIsExplicitlySet(args, 'vpc_egress'): 1716 raise serverless_exceptions.ConfigurationError( 1717 error_msg.format( 1718 flag='--vpc-egress', 1719 platform=platforms.PLATFORM_MANAGED, 1720 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1721 platforms.PLATFORM_MANAGED])) 1722 1723 if FlagIsExplicitlySet(args, 'binary_authorization'): 1724 raise serverless_exceptions.ConfigurationError( 1725 error_msg.format( 1726 flag='--binary-authorization', 1727 platform=platforms.PLATFORM_MANAGED, 1728 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1729 platforms.PLATFORM_MANAGED])) 1730 1731 if FlagIsExplicitlySet(args, 'clear_binary_authorization'): 1732 raise serverless_exceptions.ConfigurationError( 1733 error_msg.format( 1734 flag='--clear-binary-authorization', 1735 platform=platforms.PLATFORM_MANAGED, 1736 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1737 platforms.PLATFORM_MANAGED])) 1738 1739 if FlagIsExplicitlySet(args, 'breakglass'): 1740 raise serverless_exceptions.ConfigurationError( 1741 error_msg.format( 1742 flag='--breakglass', 1743 platform=platforms.PLATFORM_MANAGED, 1744 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1745 platforms.PLATFORM_MANAGED])) 1746 1747 if FlagIsExplicitlySet(args, 'cluster'): 1748 raise serverless_exceptions.ConfigurationError( 1749 error_msg.format( 1750 flag='--cluster', 1751 platform=platforms.PLATFORM_GKE, 1752 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1753 platforms.PLATFORM_GKE])) 1754 1755 if FlagIsExplicitlySet(args, 'cluster_location'): 1756 raise serverless_exceptions.ConfigurationError( 1757 error_msg.format( 1758 flag='--cluster-location', 1759 platform=platforms.PLATFORM_GKE, 1760 platform_desc=platforms.PLATFORM_SHORT_DESCRIPTIONS[ 1761 platforms.PLATFORM_GKE])) 1762 1763 1764def GetAndValidatePlatform(args, release_track, product, allow_empty=False): 1765 """Returns the platform to run on and validates specified flags. 1766 1767 A given command may support multiple platforms, but not every flag is 1768 supported by every platform. This method validates that all specified flags 1769 are supported by the specified platform. 1770 1771 Args: 1772 args: Namespace, The args namespace. 1773 release_track: base.ReleaseTrack, calliope release track. 1774 product: Product, which product the command was executed for (e.g. Run or 1775 Events). 1776 allow_empty: bool, if True, allows the platform property to be unset and 1777 will not prompt. 1778 1779 Raises: 1780 ArgumentError if an unknown platform type is found. 1781 """ 1782 platform = platforms.GetPlatform(prompt_if_unset=not allow_empty) 1783 if platform == platforms.PLATFORM_MANAGED: 1784 VerifyManagedFlags(args, release_track, product) 1785 elif platform == platforms.PLATFORM_GKE: 1786 VerifyGKEFlags(args, release_track, product) 1787 elif platform == platforms.PLATFORM_KUBERNETES: 1788 VerifyKubernetesFlags(args, release_track, product) 1789 elif allow_empty and platform is None: 1790 # No platform is allowed for commands that only support a single platform. 1791 # It's assumed that only valid flags exist for these commands, so verifying 1792 # supported flags are set is not necessary 1793 return platform 1794 if platform not in platforms.PLATFORMS: 1795 raise serverless_exceptions.ArgumentError( 1796 'Invalid target platform specified: [{}].\n' 1797 'Available platforms:\n{}'.format( 1798 platform, '\n'.join([ 1799 '- {}: {}'.format(k, v) for k, v in platforms.PLATFORMS.items() 1800 ]))) 1801 return platform 1802 1803 1804# TODO(b/165145546): Remove advanced build flags for 'gcloud run deploy' 1805def AddBuildTimeoutFlag(parser): 1806 parser.add_argument( 1807 '--build-timeout', 1808 hidden=True, 1809 help='Set the maximum request execution time (timeout) to build the ' 1810 'resource. It is specified as a duration; for example, "10m5s" is ten ' 1811 'minutes, and five seconds. If you don\'t specify a unit, seconds is ' 1812 'assumed. For example, "10" is 10 seconds.', 1813 action=actions.StoreProperty(properties.VALUES.builds.timeout)) 1814 1815 1816def AddSourceFlag(parser): 1817 """Add deploy source flags, an image or a source for build.""" 1818 parser.add_argument( 1819 '--source', 1820 help='The location of the source to build. The location can be a ' 1821 'directory on a local disk or a gzipped archive file (.tar.gz) in ' 1822 'Google Cloud Storage. If the source is a local directory, this ' 1823 'command skips the files specified in the `--ignore-file`. If ' 1824 '`--ignore-file` is not specified, use`.gcloudignore` file. If a ' 1825 '`.gcloudignore` file is absent and a `.gitignore` file is present in ' 1826 'the local source directory, gcloud will use a generated Git-compatible ' 1827 '`.gcloudignore` file that respects your .gitignored files. The global ' 1828 '`.gitignore` is not respected. For more information on `.gcloudignore`, ' 1829 'see `gcloud topic gcloudignore`.', 1830 ) 1831