# -*- 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. """Flags for serverless local development setup.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from googlecloudsdk.calliope import arg_parsers from googlecloudsdk.calliope import base from googlecloudsdk.command_lib.util.args import map_util from googlecloudsdk.core import exceptions import six class FlagDef(object): """Object that holds a flag definition and adds it to a parser.""" def __init__(self, name, **kwargs): self.name = name self.kwargs = kwargs def __eq__(self, other): return self.name == other.name def __ne__(self, other): return self.name != other.name def __hash__(self): return hash(self.name) def ConfigureParser(self, parser): parser.add_argument(self.name, **self.kwargs) class FlagDefs(object): """Base type for all flag builders.""" def __init__(self): self._operations = set() def _AddFlag(self, name, **kwargs): self._AddOperation(FlagDef(name, **kwargs)) def _AddOperation(self, operation): self._operations.add(operation) def ConfigureParser(self, parser): for operation in self._operations: operation.ConfigureParser(parser) class MutuallyExclusiveGroupDef(FlagDefs): """Flag builder where all flags are added to a mutually exclusive group.""" def ConfigureParser(self, parser): group = parser.add_mutually_exclusive_group(required=False) for op in self._operations: op.ConfigureParser(group) class BuilderFlags(MutuallyExclusiveGroupDef): """Flags for builder settings.""" def AddDockerfile(self): self._AddFlag( '--dockerfile', default='Dockerfile', help='Dockerfile for the service image.') def AddBuilder(self): self._AddFlag( '--builder', help='Build with a given Cloud Native Computing Foundation Buildpack ' 'builder.') class CredentialFlags(MutuallyExclusiveGroupDef): def AddServiceAccount(self): self._AddFlag( '--service-account', help='When connecting to Google Cloud Platform services, use a service ' 'account key.') def AddApplicationDefaultCredential(self): self._AddFlag( '--application-default-credential', action='store_true', default=False, help='When connecting to Google Cloud Platform services, use the ' 'application default credential.') class EnvVarFlags(MutuallyExclusiveGroupDef): """Environment variable flags.""" def AddEnvVars(self): self._AddFlag( '--env-vars', metavar='KEY=VALUE', action=arg_parsers.UpdateAction, type=arg_parsers.ArgDict( key_type=six.text_type, value_type=six.text_type), help='List of key-value pairs to set as environment variables.') def AddEnvVarsFile(self): self._AddFlag( '--env-vars-file', metavar='FILE_PATH', type=map_util.ArgDictFile( key_type=six.text_type, value_type=six.text_type), help='Path to a local YAML file with definitions for all environment ' 'variables.') class CommonFlags(FlagDefs): """Flags that are common between the gcloud code dev commands.""" def __init__(self): super(CommonFlags, self).__init__() self._group_cache = {} def AddLocalPort(self): self._AddFlag( '--local-port', type=int, help='Local port to which the service connection is forwarded. If this ' 'flag is not set, then a random port is chosen.') def AddSource(self): self._AddFlag( '--source', help='The directory containing the source to build. ' 'If not specified, the current directory is used.') def AddServiceName(self): self._AddFlag('--service-name', required=False, help='Name of the service.') def AddImage(self): self._AddFlag('--image', required=False, help='Name for the built image.') def AddMemory(self): self._AddFlag( '--memory', type=arg_parsers.BinarySize(default_unit='B'), help='Container memory limit. Limit is expressed either as an integer ' 'representing the number of bytes or an integer followed by a unit ' 'suffix. Valid unit suffixes are "B", "KB", "MB", "GB", "TB", "KiB", ' '"MiB", "GiB", "TiB", or "PiB".') def AddCpu(self): self._AddFlag( '--cpu', type=arg_parsers.BoundedFloat(lower_bound=0.0), help='Container CPU limit. Limit is expressed as a number of CPUs. ' 'Fractional CPU limits are allowed (e.g. 1.5).') def AddCloudsqlInstances(self): self._AddFlag( '--cloudsql-instances', type=arg_parsers.ArgList(), metavar='CLOUDSQL_INSTANCE', help='Cloud SQL instance connection strings. Must be in the form ' '::.') def AddReadinessProbe(self): # This flag launches the readiness probe feature. It is currently # default off. It will be moved to default on when ready and then # the feature will be always on. self._AddFlag( '--readiness-probe', default=False, action='store_true', hidden=True, help='Add a readiness probe to the list of containers that delays ' 'deployment stabilization until the application app has bound to $PORT') def AddServiceConfigPositionalArg(self, include_app_engine_docs=False): """_AddFlag for service_config, which has two possible help strings. Args: include_app_engine_docs: Add paragraph that says app.yaml is allowed. """ help_text = ( 'service.yaml filename override. Defaults to the first file ' 'matching ```*service.dev.yaml``` then ```*service.yaml```, if any ' 'exist. This path is relative to the --source dir.') if include_app_engine_docs: help_text += ( '\n' 'An App Engine config path (typically ```app.yaml```) may also be ' 'provided here, and we will build with a Cloud Native Computing ' 'Foundation Buildpack builder selected from ' 'gcr.io/gae-runtimes/buildpacks, according to the App Engine ' '```runtime``` specified in app.yaml.') self._AddFlag( 'service_config', metavar='SERVICE_CONFIG', nargs='?', help=help_text, ) def AddAllowSecretManagerFlag(self): self._AddFlag( '--allow-secret-manager', action=arg_parsers.StoreTrueFalseAction, help=('Suppress warnings if secrets need to be pulled from secret ' 'manager')) def AddSecrets(self): self._AddFlag( '--secrets', metavar='KEY=VALUE', action=arg_parsers.UpdateAction, type=arg_parsers.ArgDict( key_type=six.text_type, value_type=six.text_type), help='List of key-value pairs to set as secrets.') def AddCloud(self): self._AddFlag( '--cloud', default=False, action='store_true', hidden=True, help='deploy code to Cloud Run') self._AddFlag( '--region', help='region to deploy the dev service', hidden=True) def _GetGroup(self, klass): if klass not in self._group_cache: group = klass() self._group_cache[klass] = group self._AddOperation(group) return self._group_cache[klass] def CredentialsGroup(self): return self._GetGroup(CredentialFlags) def EnvVarsGroup(self): return self._GetGroup(EnvVarFlags) def BuildersGroup(self): return self._GetGroup(BuilderFlags) def AddAlphaAndBetaFlags(self, release_track): self._AddBetaFlags() if release_track == base.ReleaseTrack.ALPHA: self._AddAlphaFlags() # See AssembleSettings for where we decide how to parse service_config args # based on release track. appyaml_support = release_track == base.ReleaseTrack.ALPHA self.AddServiceConfigPositionalArg(include_app_engine_docs=appyaml_support) def _AddBetaFlags(self): """Set up flags that are for alpha and beta tracks.""" self.BuildersGroup().AddDockerfile() self.AddSource() self.AddLocalPort() self.CredentialsGroup().AddServiceAccount() self.CredentialsGroup().AddApplicationDefaultCredential() self.AddReadinessProbe() self.AddAllowSecretManagerFlag() self.AddSecrets() self.BuildersGroup().AddBuilder() def _AddAlphaFlags(self): """Set up flags that are for alpha track only.""" self.AddCloudsqlInstances() self.AddServiceName() self.AddImage() self.AddMemory() self.AddCpu() self.EnvVarsGroup().AddEnvVars() self.EnvVarsGroup().AddEnvVarsFile() self.AddCloud() class InvalidFlagError(exceptions.Error): """Flag settings are illegal.""" def Validate(namespace): """Validate flag requirements that cannot be handled by argparse.""" if ('cloudsql_instances' in namespace and namespace.IsSpecified('cloudsql_instances') and not (namespace.IsSpecified('service_account') or namespace.IsSpecified('application_default_credential'))): raise InvalidFlagError('--cloudsql-instances requires --service-account or ' '--application-default-credential to be specified.')