Example #1
0
def SetFunctionSourceProps(function,
                           function_ref,
                           source_arg,
                           stage_bucket,
                           ignore_file=None,
                           update_date=False):
    """Add sources to function.

  Args:
    function: The function to add a source to.
    function_ref: The reference to the function.
    source_arg: Location of source code to deploy.
    stage_bucket: The name of the Google Cloud Storage bucket where source code
        will be stored.
    ignore_file: custom ignore_file name.
        Override .gcloudignore file to customize files to be skipped.
    update_date: update file date in archive if it is older than 1980.
  Returns:
    A list of fields on the function that have been changed.
  """
    function.sourceArchiveUrl = None
    function.sourceRepository = None
    function.sourceUploadUrl = None

    messages = api_util.GetApiMessagesModule()

    if source_arg is None:
        source_arg = '.'
    source_arg = source_arg or '.'
    if source_arg.startswith('gs://'):
        if not source_arg.endswith('.zip'):
            # Users may have .zip archives with unusual names, and we don't want to
            # prevent those from being deployed; the deployment should go through so
            # just warn here.
            log.warning(
                '[{}] does not end with extension `.zip`. '
                'The `--source` argument must designate the zipped source archive '
                'when providing a Google Cloud Storage URI.'.format(
                    source_arg))
        function.sourceArchiveUrl = source_arg
        return ['sourceArchiveUrl']
    elif source_arg.startswith('https://'):
        function.sourceRepository = messages.SourceRepository(
            url=_AddDefaultBranch(source_arg))
        return ['sourceRepository']
    with file_utils.TemporaryDirectory() as tmp_dir:
        zip_file = _CreateSourcesZipFile(tmp_dir,
                                         source_arg,
                                         ignore_file,
                                         update_date=update_date)
        service = api_util.GetApiClientInstance().projects_locations_functions

        upload_url = UploadFile(zip_file, stage_bucket, messages, service,
                                function_ref)
        if upload_url.startswith('gs://'):
            function.sourceArchiveUrl = upload_url
            return ['sourceArchiveUrl']
        else:
            function.sourceUploadUrl = upload_url
            return ['sourceUploadUrl']
Example #2
0
    def _PrepareFunctionWithoutSources(self, name, entry_point, timeout_sec,
                                       trigger_http, trigger_params):
        """Creates a function object without filling in the sources properties.

    Args:
      name: str, name of the function (resource).
      entry_point: str, name of the function (in deployed code) to be executed.
      timeout_sec: int, maximum time allowed for function execution, in seconds.
      trigger_http: bool, indicates whether function should have a HTTPS
                    trigger; when truthy trigger_params argument is ignored.
      trigger_params: None or dict from str to str, the dict is assmed to
                      contain exactly the following keys: trigger_provider,
                      trigger_event, trigger_resource.

    Returns:
      The specified function with its description and configured filter.
    """
        messages = util.GetApiMessagesModule()
        function = messages.CloudFunction()
        function.name = name
        if entry_point:
            function.entryPoint = entry_point
        if timeout_sec:
            function.timeout = str(timeout_sec) + 's'
        if trigger_http:
            function.httpsTrigger = messages.HTTPSTrigger()
        else:
            function.eventTrigger = self._EventTrigger(**trigger_params)
        return function
Example #3
0
def _ApplyEnvVarsArgsToFunction(function, args):
    """Determines if environment variables have to be updated.

  It compares the cli args with the existing environment variables to compute
  the resulting build environment variables.

  Args:
    function: CloudFunction message to be checked and filled with env vars based
      on the flags
    args: all cli args

  Returns:
    updated_fields: update mask containing the list of fields that are
    considered for updating based on the cli args and existing variables
  """

    updated_fields = []
    old_env_vars = env_vars_api_util.GetEnvVarsAsDict(
        function.environmentVariables)
    env_var_flags = map_util.GetMapFlagsFromArgs('env-vars', args)
    new_env_vars = map_util.ApplyMapFlags(old_env_vars, **env_var_flags)
    if old_env_vars != new_env_vars:
        env_vars_type_class = api_util.GetApiMessagesModule(
        ).CloudFunction.EnvironmentVariablesValue
        function.environmentVariables = env_vars_api_util.DictToEnvVarsProperty(
            env_vars_type_class, new_env_vars)
        updated_fields.append('environmentVariables')
    return updated_fields
Example #4
0
def CreateEventTrigger(trigger_provider, trigger_event, trigger_resource):
    messages = api_util.GetApiMessagesModule()
    event_trigger = messages.EventTrigger()
    event_trigger.eventType = trigger_event
    event_trigger.resource = (ConvertTriggerArgsToRelativeName(
        trigger_provider, trigger_event, trigger_resource))
    return event_trigger
Example #5
0
    def _ApplyNonSourceArgsToFunction(self, function, name, entry_point,
                                      timeout_sec, trigger_http,
                                      trigger_params, retry, memory):
        """Modifies a function object without touching in the sources properties.

    Args:
      function: message, function resource to be modified.
      name: str, name of the function (resource).
      entry_point: str, name of the function (in deployed code) to be executed.
      timeout_sec: int, maximum time allowed for function execution, in seconds.
      trigger_http: bool, indicates whether function should have a HTTPS
                    trigger; when truthy trigger_params argument is ignored.
      trigger_params: None or dict from str to str, the dict is assumed to
                      contain exactly the following keys: trigger_provider,
                      trigger_event, trigger_resource.
      retry: bool, indicates if function should retry.
      memory: int, memory limit for the function in bytes.
    """
        messages = util.GetApiMessagesModule()
        function.name = name
        if entry_point:
            function.entryPoint = entry_point
        if timeout_sec:
            function.timeout = str(timeout_sec) + 's'
        if trigger_http:
            function.httpsTrigger = messages.HTTPSTrigger()
        elif trigger_params:
            function.eventTrigger = self._EventTrigger(**trigger_params)
        if retry:
            function.eventTrigger.failurePolicy = messages.FailurePolicy()
            function.eventTrigger.failurePolicy.retry = messages.Retry()
        elif function.eventTrigger:
            function.eventTrigger.failurePolicy = None
        if memory:
            function.availableMemoryMb = utils.BytesToMb(memory)
Example #6
0
  def _ApplyArgsToFunction(
      self, base_function, is_new_function, trigger_params, name, args):
    """Apply values from args to base_function.

    Args:
        base_function: function message to modify
        is_new_function: bool, indicates if this is a new function (and source
                         code for it must be deployed) or an existing function
                         (so it may keep its old source code).
        trigger_params: parameters for creating functions trigger.
        name: relative name of the function.
        args: commandline args specyfying how to modify the function.
    """
    if args.IsSpecified('retry'):
      retry = args.retry
    else:
      retry = None
    self._ApplyNonSourceArgsToFunction(
        base_function, name, args.entry_point, args.timeout,
        args.trigger_http, trigger_params, retry, args.memory)
    if args.source:
      deploy_util.AddSourceToFunction(
          base_function, args.source, args.include_ignored_files, args.name,
          args.stage_bucket)
    elif args.source_url:
      messages = util.GetApiMessagesModule()
      source_path = args.source_path
      source_branch = args.source_branch or 'master'
      base_function.sourceRepository = messages.SourceRepository(
          tag=args.source_tag, branch=source_branch,
          revision=args.source_revision, repositoryUrl=args.source_url,
          sourcePath=source_path)
    elif is_new_function or args.local_path or args.stage_bucket:
      # Do not change source of existing function unless instructed to.
      base_function.sourceArchiveUrl = self._PrepareSourcesOnGcs(args)
Example #7
0
def SetFunctionLabels(function, update_labels, remove_labels, clear_labels):
  """Set the labels on a function based on args.

  Args:
    function: the function to set the labels on
    update_labels: a dict of <label-name>-<label-value> pairs for the labels to
        be updated, from --update-labels
    remove_labels: a list of the labels to be removed, from --remove-labels
    clear_labels: a bool representing whether or not to clear all labels,
        from --clear-labels
  Returns:
    A bool indicating whether or not any labels were updated on the function.
  """
  labels_to_update = update_labels or {}
  labels_to_update['deployment-tool'] = 'cli-gcloud'
  labels_diff = args_labels_util.Diff(additions=labels_to_update,
                                      subtractions=remove_labels,
                                      clear=clear_labels)
  messages = api_util.GetApiMessagesModule()
  labels_update = labels_diff.Apply(messages.CloudFunction.LabelsValue,
                                    function.labels)
  if labels_update.needs_update:
    function.labels = labels_update.labels
    return True
  return False
Example #8
0
 def _DeployFunction(self, name, location, args, deploy_method,
                     trigger_params):
     function = self._PrepareFunctionWithoutSources(name, args.entry_point,
                                                    args.timeout,
                                                    args.trigger_http,
                                                    trigger_params)
     if args.source:
         deploy_util.AddSourceToFunction(function, args.source,
                                         args.include_ignored_files,
                                         args.name, args.stage_bucket)
     elif args.source_url:
         messages = util.GetApiMessagesModule()
         source_path = args.source_path
         source_branch = args.source_branch or 'master'
         function.sourceRepository = messages.SourceRepository(
             tag=args.source_tag,
             branch=source_branch,
             revision=args.source_revision,
             repositoryUrl=args.source_url,
             sourcePath=source_path)
     else:
         function.sourceArchiveUrl = self._PrepareSourcesOnGcs(args)
     memory_mb = utils.BytesToMb(args.memory)
     if memory_mb:
         function.availableMemoryMb = memory_mb
     return deploy_method(location, function)
Example #9
0
  def Run(self, args):
    client = util.GetApiClientInstance()
    messages = util.GetApiMessagesModule()
    locations = args.regions or [properties.VALUES.functions.region.GetOrFail()]
    project = properties.VALUES.core.project.GetOrFail()
    limit = args.limit

    return self._YieldFromLocations(locations, project, limit, messages, client)
Example #10
0
    def _ApplyArgsToFunction(self, base_function, is_new_function,
                             trigger_params, name, args, project):
        """Apply values from args to base_function.

    Args:
        base_function: function message to modify
        is_new_function: bool, indicates if this is a new function (and source
                         code for it must be deployed) or an existing function
                         (so it may keep its old source code).
        trigger_params: parameters for creating functions trigger.
        name: relative name of the function.
        args: commandline args specyfying how to modify the function.
        project: project of the function.
    """
        if args.IsSpecified('retry'):
            retry = args.retry
        else:
            retry = None
        self._ApplyNonSourceArgsToFunction(base_function, name,
                                           args.entry_point, args.timeout,
                                           args.trigger_http, trigger_params,
                                           retry, args.memory)
        messages = util.GetApiMessagesModule()
        if args.source:
            deploy_util.AddSourceToFunction(base_function, args.source,
                                            args.include_ignored_files,
                                            args.name, args.stage_bucket,
                                            messages)
        elif args.source_url:
            deploy_util.CleanOldSourceInfo(base_function)
            source_path = args.source_path
            source_branch = args.source_branch or 'master'
            base_function.sourceRepository = messages.SourceRepository(
                url=self._GetSourceUrlFromArgs(project,
                                               tag=args.source_tag,
                                               branch=source_branch,
                                               revision=args.source_revision,
                                               source_url=args.source_url,
                                               source_path=source_path))

        elif args.stage_bucket:
            # Do not change source of existing function unless instructed to.
            deploy_util.CleanOldSourceInfo(base_function)
            base_function.sourceArchiveUrl = self._PrepareSourcesOnGcs(args)
        elif is_new_function or args.local_path:
            raise exceptions.FunctionsError(
                'argument --stage-bucket: required when the function is deployed '
                'from a local directory.')
        # Set information about deplouyment tool.
        labels_to_update = args.update_labels or {}
        labels_to_update['deployment-tool'] = 'cli-gcloud'
        updated_labels = labels_util.UpdateLabels(
            base_function.labels,
            messages.CloudFunction.LabelsValue,
            update_labels=labels_to_update,
            remove_labels=args.remove_labels)
        if updated_labels is not None:
            base_function.labels = updated_labels
Example #11
0
 def _EventTrigger(self, trigger_provider, trigger_event, trigger_resource):
     messages = util.GetApiMessagesModule()
     event_trigger = messages.EventTrigger()
     event_type_ref = resources.REGISTRY.Parse(
         None,
         params={
             'triggerProvider': trigger_provider,
             'triggerEvent': trigger_event
         },
         collection='cloudfunctions.providers.event_types')
     event_trigger.eventType = event_type_ref.RelativeName()
     event_trigger.resource = (deploy_util.ConvertTriggerArgsToRelativeName(
         trigger_provider, trigger_event, trigger_resource))
     return event_trigger
Example #12
0
    def testDictToEnvVarsProperty(self):
        messages = api_util.GetApiMessagesModule()
        env_vars_class = messages.CloudFunction.EnvironmentVariablesValue
        expected = (env_vars_class(additionalProperties=[
            env_vars_class.AdditionalProperty(key='BAZ', value='BOO'),
            env_vars_class.AdditionalProperty(key='FOO', value='BAR'),
        ]))

        env_vars_dict = {
            'FOO': 'BAR',
            'BAZ': 'BOO',
        }
        actual = env_vars.DictToEnvVarsProperty(env_vars_dict)
        self.assertEqual(expected, actual)
Example #13
0
    def Run(self, args):
        client = util.GetApiClientInstance()
        messages = util.GetApiMessagesModule()
        locations = []
        if args.regions:
            locations = args.regions
        if args.region:
            locations += [args.region]
        if not locations:
            locations = ['-']
        project = properties.VALUES.core.project.GetOrFail()
        limit = args.limit

        return self._YieldFromLocations(locations, project, limit, messages,
                                        client)
Example #14
0
def DictToEnvVarsProperty(env_vars=None):
  """Set function environment variables.

  Args:
    env_vars: a dict of environment variables
  Returns:
    An EnvironmentVariablesValue with the env vars from env_vars
  """
  if not env_vars:
    return None
  messages = util.GetApiMessagesModule()
  env_vars_class = messages.CloudFunction.EnvironmentVariablesValue
  return env_vars_class(additionalProperties=[
      env_vars_class.AdditionalProperty(key=key, value=value)
      for key, value in sorted(env_vars.items())])
Example #15
0
    def testGetFunctionEnvVarsAsDict(self):
        expected = {
            'FOO': 'BAR',
            'BAZ': 'BOO',
        }

        messages = api_util.GetApiMessagesModule()
        env_vars_class = messages.CloudFunction.EnvironmentVariablesValue
        function = messages.CloudFunction()
        function.environmentVariables = (env_vars_class(additionalProperties=[
            env_vars_class.AdditionalProperty(key='BAZ', value='BOO'),
            env_vars_class.AdditionalProperty(key='FOO', value='BAR'),
        ]))

        actual = env_vars.GetFunctionEnvVarsAsDict(function)
        self.assertEqual(expected, actual)
Example #16
0
    def Run(self, args):
        """This is what gets called when the user runs this command.

    Args:
      args: an argparse namespace. All the arguments that were provided to this
        command invocation.

    Returns:
      The specified function with its description and configured filter.

    Raises:
      FunctionsError if command line parameters are not valid.
    """
        self._ValidateLabelsFlags(args)
        trigger_params = deploy_util.DeduceAndCheckArgs(args)
        project = properties.VALUES.core.project.Get(required=True)
        location_ref = resources.REGISTRY.Parse(
            properties.VALUES.functions.region.Get(),
            params={'projectsId': project},
            collection='cloudfunctions.projects.locations')
        location = location_ref.RelativeName()
        function_ref = resources.REGISTRY.Parse(
            args.name,
            params={
                'projectsId': project,
                'locationsId': properties.VALUES.functions.region.Get()
            },
            collection='cloudfunctions.projects.locations.functions')
        function_url = function_ref.RelativeName()

        function = self._GetExistingFunction(function_url)
        self._ValidateAfterCheckingFunctionsExistence(function, args)
        is_new_function = function is None
        if is_new_function:
            messages = util.GetApiMessagesModule()
            function = messages.CloudFunction()
        function, update_mask = self._ApplyArgsToFunction(
            function, is_new_function, trigger_params, function_ref, args,
            project)
        if is_new_function:
            return self._CreateFunction(location, function)
        else:
            if update_mask:
                return self._PatchFunction(function, update_mask)
            else:
                log.status.Print('Nothing to update.')
Example #17
0
    def testGetEnvVarsAsDict_BuildEnvVar(self):
        expected = {
            'FOO': 'BAR',
            'BAZ': 'BOO',
        }

        messages = api_util.GetApiMessagesModule()
        env_vars_class = self._BUILD_ENV_VARS_TYPE_CLASS
        function = messages.CloudFunction()
        function.buildEnvironmentVariables = (env_vars_class(
            additionalProperties=[
                env_vars_class.AdditionalProperty(key='BAZ', value='BOO'),
                env_vars_class.AdditionalProperty(key='FOO', value='BAR'),
            ]))

        actual = env_vars.GetEnvVarsAsDict(function.buildEnvironmentVariables)
        self.assertEqual(expected, actual)
Example #18
0
  def BuildRequest(self, args):
    """This method creates a ListRequest message to be send to GCF.

    Args:
      args: an argparse namespace. All the arguments that were provided to this
        command invocation.

    Returns:
      A ListRequest message.
    """
    messages = util.GetApiMessagesModule()
    project = properties.VALUES.core.project.GetOrFail()
    location_ref = resources.REGISTRY.Parse(
        properties.VALUES.functions.region.Get(),
        params={'projectsId': project},
        collection='cloudfunctions.projects.locations')
    return messages.CloudfunctionsProjectsLocationsFunctionsListRequest(
        location=location_ref.RelativeName())
Example #19
0
    def _ApplyArgsToFunction(self, function, is_new_function, trigger_params,
                             function_ref, args, project):
        """Apply values from args to base_function.

    Args:
        function: function message to modify
        is_new_function: bool, indicates if this is a new function (and source
                         code for it must be deployed) or an existing function
                         (so it may keep its old source code).
        trigger_params: parameters for creating functions trigger.
        function_ref: reference to function.
        args: commandline args specyfying how to modify the function.
        project: project of the function.
    Returns:
      Pair of function and update mask.
    """
        update_mask = []

        messages = util.GetApiMessagesModule()
        client = util.GetApiClientInstance()

        self._ApplyNonSourceArgsToFunction(function, function_ref, update_mask,
                                           messages, args, trigger_params)
        # Only Add source to function if its explicitly provided, a new function,
        # using a stage budget or deploy of an existing function that previously
        # used local source
        if (args.source or args.stage_bucket or is_new_function
                or function.sourceUploadUrl):  #
            deploy_util.AddSourceToFunction(
                function, function_ref, update_mask, args.source,
                args.stage_bucket, messages,
                client.projects_locations_functions)
        # Set information about deployment tool.
        labels_to_update = args.update_labels or {}
        labels_to_update['deployment-tool'] = 'cli-gcloud'
        labels_diff = labels_util.Diff(additions=labels_to_update,
                                       subtractions=args.remove_labels,
                                       clear=args.clear_labels)
        labels_update = labels_diff.Apply(messages.CloudFunction.LabelsValue,
                                          function.labels)
        if labels_update.needs_update:
            function.labels = labels_update.labels
            update_mask.append('labels')
        return function, ','.join(sorted(update_mask))
Example #20
0
def CreateEventTrigger(trigger_provider, trigger_event, trigger_resource):
    """Create event trigger message.

  Args:
    trigger_provider: str, trigger provider label.
    trigger_event: str, trigger event label.
    trigger_resource: str, trigger resource name.

  Returns:
    A EventTrigger protobuf message.
  """
    messages = api_util.GetApiMessagesModule()
    event_trigger = messages.EventTrigger()
    event_trigger.eventType = trigger_event
    if trigger_provider == triggers.UNADVERTISED_PROVIDER_LABEL:
        event_trigger.resource = trigger_resource
    else:
        event_trigger.resource = (ConvertTriggerArgsToRelativeName(
            trigger_provider, trigger_event, trigger_resource))
    return event_trigger
Example #21
0
def SetFunctionSourceProps(function, function_ref, source_arg, stage_bucket):
    """Add sources to function.

  Args:
    function: The function to add a source to.
    function_ref: The reference to the function.
    source_arg: Location of source code to deploy.
    stage_bucket: The name of the Google Cloud Storage bucket where source code
        will be stored.
  Returns:
    A list of fields on the function that have been changed.
  """
    function.sourceArchiveUrl = None
    function.sourceRepository = None
    function.sourceUploadUrl = None

    messages = api_util.GetApiMessagesModule()

    if source_arg is None:
        source_arg = '.'
    source_arg = source_arg or '.'
    if source_arg.startswith('gs://'):
        function.sourceArchiveUrl = source_arg
        return ['sourceArchiveUrl']
    elif source_arg.startswith('https://'):
        function.sourceRepository = messages.SourceRepository(
            url=_AddDefaultBranch(source_arg))
        return ['sourceRepository']
    with file_utils.TemporaryDirectory() as tmp_dir:
        zip_file = _CreateSourcesZipFile(tmp_dir, source_arg)
        service = api_util.GetApiClientInstance().projects_locations_functions

        upload_url = UploadFile(zip_file, stage_bucket, messages, service,
                                function_ref)
        if upload_url.startswith('gs://'):
            function.sourceArchiveUrl = upload_url
            return ['sourceArchiveUrl']
        else:
            function.sourceUploadUrl = upload_url
            return ['sourceUploadUrl']
Example #22
0
def _Run(args,
         track=None,
         enable_runtime=True,
         enable_build_worker_pool=False):
    """Run a function deployment with the given args."""
    # Check for labels that start with `deployment`, which is not allowed.
    labels_util.CheckNoDeploymentLabels('--remove-labels', args.remove_labels)
    labels_util.CheckNoDeploymentLabels('--update-labels', args.update_labels)

    # Check that exactly one trigger type is specified properly.
    trigger_util.ValidateTriggerArgs(args.trigger_event, args.trigger_resource,
                                     args.IsSpecified('retry'),
                                     args.IsSpecified('trigger_http'))
    trigger_params = trigger_util.GetTriggerEventParams(
        args.trigger_http, args.trigger_bucket, args.trigger_topic,
        args.trigger_event, args.trigger_resource)

    function_ref = args.CONCEPTS.name.Parse()
    function_url = function_ref.RelativeName()

    messages = api_util.GetApiMessagesModule(track)

    # Get an existing function or create a new one.
    function = api_util.GetFunction(function_url)
    is_new_function = function is None
    had_vpc_connector = bool(
        function.vpcConnector) if not is_new_function else False
    if is_new_function:
        trigger_util.CheckTriggerSpecified(args)
        function = messages.CloudFunction()
        function.name = function_url
    elif trigger_params:
        # If the new deployment would implicitly change the trigger_event type
        # raise error
        trigger_util.CheckLegacyTriggerUpdate(function.eventTrigger,
                                              trigger_params['trigger_event'])

    # Keep track of which fields are updated in the case of patching.
    updated_fields = []

    # Populate function properties based on args.
    if args.entry_point:
        function.entryPoint = args.entry_point
        updated_fields.append('entryPoint')
    if args.timeout:
        function.timeout = '{}s'.format(args.timeout)
        updated_fields.append('timeout')
    if args.memory:
        function.availableMemoryMb = utils.BytesToMb(args.memory)
        updated_fields.append('availableMemoryMb')
    if args.service_account:
        function.serviceAccountEmail = args.service_account
        updated_fields.append('serviceAccountEmail')
    if (args.IsSpecified('max_instances')
            or args.IsSpecified('clear_max_instances')):
        max_instances = 0 if args.clear_max_instances else args.max_instances
        function.maxInstances = max_instances
        updated_fields.append('maxInstances')
    if enable_runtime:
        if args.IsSpecified('runtime'):
            function.runtime = args.runtime
            updated_fields.append('runtime')
            if args.runtime in ['nodejs6']:
                log.warning(
                    'The Node.js 6 runtime is deprecated on Cloud Functions. '
                    'Please migrate to Node.js 8 (--runtime=nodejs8) or Node.js 10 '
                    '(--runtime=nodejs10). '
                    'See https://cloud.google.com/functions/docs/migrating/nodejs-runtimes'
                )
        elif is_new_function:
            raise exceptions.RequiredArgumentException(
                'runtime', 'Flag `--runtime` is required for new functions.')
    if args.vpc_connector or args.clear_vpc_connector:
        function.vpcConnector = ('' if args.clear_vpc_connector else
                                 args.vpc_connector)
        updated_fields.append('vpcConnector')
    if args.IsSpecified('egress_settings'):
        will_have_vpc_connector = ((had_vpc_connector
                                    and not args.clear_vpc_connector)
                                   or args.vpc_connector)
        if not will_have_vpc_connector:
            raise exceptions.RequiredArgumentException(
                'vpc-connector', 'Flag `--vpc-connector` is '
                'required for setting `egress-settings`.')
        egress_settings_enum = arg_utils.ChoiceEnumMapper(
            arg_name='egress_settings',
            message_enum=function.VpcConnectorEgressSettingsValueValuesEnum,
            custom_mappings=flags.EGRESS_SETTINGS_MAPPING).GetEnumForChoice(
                args.egress_settings)
        function.vpcConnectorEgressSettings = egress_settings_enum
        updated_fields.append('vpcConnectorEgressSettings')
    if args.IsSpecified('ingress_settings'):
        ingress_settings_enum = arg_utils.ChoiceEnumMapper(
            arg_name='ingress_settings',
            message_enum=function.IngressSettingsValueValuesEnum,
            custom_mappings=flags.INGRESS_SETTINGS_MAPPING).GetEnumForChoice(
                args.ingress_settings)
        function.ingressSettings = ingress_settings_enum
        updated_fields.append('ingressSettings')
    if enable_build_worker_pool:
        if args.build_worker_pool or args.clear_build_worker_pool:
            function.buildWorkerPool = ('' if args.clear_build_worker_pool else
                                        args.build_worker_pool)
            updated_fields.append('buildWorkerPool')
    # Populate trigger properties of function based on trigger args.
    if args.trigger_http:
        function.httpsTrigger = messages.HttpsTrigger()
        function.eventTrigger = None
        updated_fields.extend(['eventTrigger', 'httpsTrigger'])
    if trigger_params:
        function.eventTrigger = trigger_util.CreateEventTrigger(
            **trigger_params)
        function.httpsTrigger = None
        updated_fields.extend(['eventTrigger', 'httpsTrigger'])
    if args.IsSpecified('retry'):
        updated_fields.append('eventTrigger.failurePolicy')
        if args.retry:
            function.eventTrigger.failurePolicy = messages.FailurePolicy()
            function.eventTrigger.failurePolicy.retry = messages.Retry()
        else:
            function.eventTrigger.failurePolicy = None
    elif function.eventTrigger:
        function.eventTrigger.failurePolicy = None

    # Populate source properties of function based on source args.
    # Only Add source to function if its explicitly provided, a new function,
    # using a stage bucket or deploy of an existing function that previously
    # used local source.
    if (args.source or args.stage_bucket or is_new_function
            or function.sourceUploadUrl):
        updated_fields.extend(
            source_util.SetFunctionSourceProps(function, function_ref,
                                               args.source, args.stage_bucket,
                                               args.ignore_file))

    # Apply label args to function
    if labels_util.SetFunctionLabels(function, args.update_labels,
                                     args.remove_labels, args.clear_labels):
        updated_fields.append('labels')

    # Apply environment variables args to function
    updated_fields.extend(_ApplyEnvVarsArgsToFunction(function, args))

    ensure_all_users_invoke = flags.ShouldEnsureAllUsersInvoke(args)
    deny_all_users_invoke = flags.ShouldDenyAllUsersInvoke(args)

    if is_new_function:
        if (not ensure_all_users_invoke and not deny_all_users_invoke and
                api_util.CanAddFunctionIamPolicyBinding(_GetProject(args))):
            ensure_all_users_invoke = console_io.PromptContinue(prompt_string=(
                'Allow unauthenticated invocations of new function [{}]?'.
                format(args.NAME)),
                                                                default=False)

        op = api_util.CreateFunction(function,
                                     function_ref.Parent().RelativeName())
        if (not ensure_all_users_invoke and not deny_all_users_invoke):
            template = ('Function created with limited-access IAM policy. '
                        'To enable unauthorized access consider "%s"')
            log.warning(template %
                        _CreateBindPolicyCommand(args.NAME, args.region))
            deny_all_users_invoke = True

    elif updated_fields:
        op = api_util.PatchFunction(function, updated_fields)

    else:
        op = None  # Nothing to wait for
        if not ensure_all_users_invoke and not deny_all_users_invoke:
            log.status.Print('Nothing to update.')
            return

    stop_trying_perm_set = [False]

    # The server asyncrhonously sets allUsers invoker permissions some time after
    # we create the function. That means, to remove it, we need do so after the
    # server adds it. We can remove this mess after the default changes.
    # TODO(b/139026575): Remove the "remove" path, only bother adding. Remove the
    # logic from the polling loop. Remove the ability to add logic like this to
    # the polling loop.
    def TryToSetInvokerPermission():
        """Try to make the invoker permission be what we said it should.

    This is for executing in the polling loop, and will stop trying as soon as
    it succeeds at making a change.
    """
        if stop_trying_perm_set[0]:
            return
        try:
            if ensure_all_users_invoke:
                api_util.AddFunctionIamPolicyBinding(function.name)
                stop_trying_perm_set[0] = True
            elif deny_all_users_invoke:
                stop_trying_perm_set[0] = (
                    api_util.RemoveFunctionIamPolicyBindingIfFound(
                        function.name))
        except exceptions.HttpException:
            stop_trying_perm_set[0] = True
            log.warning('Setting IAM policy failed, try "%s"' %
                        _CreateBindPolicyCommand(args.NAME, args.region))

    if op:
        api_util.WaitForFunctionUpdateOperation(
            op, do_every_poll=TryToSetInvokerPermission)
    return api_util.GetFunction(function.name)
Example #23
0
class EnvVarsTest(parameterized.TestCase):
    """Test environment variable api utility functions.
  """

    _BUILD_ENV_VARS_TYPE_CLASS = api_util.GetApiMessagesModule(
    ).CloudFunction.BuildEnvironmentVariablesValue
    _ENV_VARS_TYPE_CLASS = api_util.GetApiMessagesModule(
    ).CloudFunction.EnvironmentVariablesValue

    def testGetEnvVarsAsDict_BuildEnvVar_None(self):
        messages = api_util.GetApiMessagesModule()
        function = messages.CloudFunction()
        actual = env_vars.GetEnvVarsAsDict(function.buildEnvironmentVariables)
        self.assertEqual({}, actual)

    def testGetEnvVarsAsDict_EnvVar_None(self):
        messages = api_util.GetApiMessagesModule()
        function = messages.CloudFunction()
        actual = env_vars.GetEnvVarsAsDict(function.environmentVariables)
        self.assertEqual({}, actual)

    def testGetEnvVarsAsDict_BuildEnvVar(self):
        expected = {
            'FOO': 'BAR',
            'BAZ': 'BOO',
        }

        messages = api_util.GetApiMessagesModule()
        env_vars_class = self._BUILD_ENV_VARS_TYPE_CLASS
        function = messages.CloudFunction()
        function.buildEnvironmentVariables = (env_vars_class(
            additionalProperties=[
                env_vars_class.AdditionalProperty(key='BAZ', value='BOO'),
                env_vars_class.AdditionalProperty(key='FOO', value='BAR'),
            ]))

        actual = env_vars.GetEnvVarsAsDict(function.buildEnvironmentVariables)
        self.assertEqual(expected, actual)

    def testGetEnvVarsAsDict_EnvVar(self):
        expected = {
            'FOO': 'BAR',
            'BAZ': 'BOO',
        }

        messages = api_util.GetApiMessagesModule()
        env_vars_class = self._ENV_VARS_TYPE_CLASS
        function = messages.CloudFunction()
        function.environmentVariables = (env_vars_class(additionalProperties=[
            env_vars_class.AdditionalProperty(key='BAZ', value='BOO'),
            env_vars_class.AdditionalProperty(key='FOO', value='BAR'),
        ]))

        actual = env_vars.GetEnvVarsAsDict(function.environmentVariables)
        self.assertEqual(expected, actual)

    @parameterized.parameters((_BUILD_ENV_VARS_TYPE_CLASS),
                              (_ENV_VARS_TYPE_CLASS))
    def testDictToEnvVarsPropertyNone(self, env_vars_type_class):
        actual = env_vars.DictToEnvVarsProperty(env_vars_type_class, None)
        self.assertIsNone(actual)

    @parameterized.parameters((_BUILD_ENV_VARS_TYPE_CLASS),
                              (_ENV_VARS_TYPE_CLASS))
    def testDictToEnvVarsProperty(self, env_vars_type_class):
        expected = (env_vars_type_class(additionalProperties=[
            env_vars_type_class.AdditionalProperty(key='BAZ', value='BOO'),
            env_vars_type_class.AdditionalProperty(key='FOO', value='BAR'),
        ]))

        env_vars_dict = {
            'FOO': 'BAR',
            'BAZ': 'BOO',
        }
        actual = env_vars.DictToEnvVarsProperty(env_vars_type_class,
                                                env_vars_dict)
        self.assertEqual(expected, actual)
Example #24
0
def _Run(args,
         track=None,
         enable_runtime=True,
         enable_max_instances=False,
         enable_connected_vpc=False):
    """Run a function deployment with the given args."""
    # Check for labels that start with `deployment`, which is not allowed.
    labels_util.CheckNoDeploymentLabels('--remove-labels', args.remove_labels)
    labels_util.CheckNoDeploymentLabels('--update-labels', args.update_labels)

    # Check that exactly one trigger type is specified properly.
    trigger_util.ValidateTriggerArgs(args.trigger_event, args.trigger_resource,
                                     args.IsSpecified('retry'),
                                     args.IsSpecified('trigger_http'))

    trigger_params = trigger_util.GetTriggerEventParams(
        args.trigger_http, args.trigger_bucket, args.trigger_topic,
        args.trigger_event, args.trigger_resource)

    function_ref = args.CONCEPTS.name.Parse()
    function_url = function_ref.RelativeName()

    messages = api_util.GetApiMessagesModule(track)

    # Get an existing function or create a new one.
    function = api_util.GetFunction(function_url)
    is_new_function = function is None
    if is_new_function:
        trigger_util.CheckTriggerSpecified(args)
        function = messages.CloudFunction()
        function.name = function_url
    elif trigger_params:
        # If the new deployment would implicitly change the trigger_event type
        # raise error
        trigger_util.CheckLegacyTriggerUpdate(function.eventTrigger,
                                              trigger_params['trigger_event'])

    # Keep track of which fields are updated in the case of patching.
    updated_fields = []

    # Populate function properties based on args.
    if args.entry_point:
        function.entryPoint = args.entry_point
        updated_fields.append('entryPoint')
    if args.timeout:
        function.timeout = '{}s'.format(args.timeout)
        updated_fields.append('timeout')
    if args.memory:
        function.availableMemoryMb = utils.BytesToMb(args.memory)
        updated_fields.append('availableMemoryMb')
    if args.service_account:
        function.serviceAccountEmail = args.service_account
        updated_fields.append('serviceAccountEmail')
    if enable_runtime:
        if args.IsSpecified('runtime'):
            function.runtime = args.runtime
            updated_fields.append('runtime')
        elif is_new_function:
            log.warning('Flag `--runtime` will become a required flag soon. '
                        'Please specify the value for this flag.')
    if enable_max_instances:
        if (args.IsSpecified('max_instances')
                or args.IsSpecified('clear_max_instances')):
            max_instances = 0 if args.clear_max_instances else args.max_instances
            function.maxInstances = max_instances
            updated_fields.append('maxInstances')
    if enable_connected_vpc:
        if args.connected_vpc:
            function.network = args.connected_vpc
            updated_fields.append('network')
        if args.vpc_connector:
            function.vpcConnector = args.vpc_connector
            updated_fields.append('vpcConnector')

    # Populate trigger properties of function based on trigger args.
    if args.trigger_http:
        function.httpsTrigger = messages.HttpsTrigger()
        function.eventTrigger = None
        updated_fields.extend(['eventTrigger', 'httpsTrigger'])
    if trigger_params:
        function.eventTrigger = trigger_util.CreateEventTrigger(
            **trigger_params)
        function.httpsTrigger = None
        updated_fields.extend(['eventTrigger', 'httpsTrigger'])
    if args.IsSpecified('retry'):
        updated_fields.append('eventTrigger.failurePolicy')
        if args.retry:
            function.eventTrigger.failurePolicy = messages.FailurePolicy()
            function.eventTrigger.failurePolicy.retry = messages.Retry()
        else:
            function.eventTrigger.failurePolicy = None
    elif function.eventTrigger:
        function.eventTrigger.failurePolicy = None

    # Populate source properties of function based on source args.
    # Only Add source to function if its explicitly provided, a new function,
    # using a stage bucket or deploy of an existing function that previously
    # used local source.
    if (args.source or args.stage_bucket or is_new_function
            or function.sourceUploadUrl):
        updated_fields.extend(
            source_util.SetFunctionSourceProps(function, function_ref,
                                               args.source, args.stage_bucket))

    # Apply label args to function
    if labels_util.SetFunctionLabels(function, args.update_labels,
                                     args.remove_labels, args.clear_labels):
        updated_fields.append('labels')

    # Apply environment variables args to function
    updated_fields.extend(_ApplyEnvVarsArgsToFunction(function, args))

    if is_new_function:
        return api_util.CreateFunction(function,
                                       function_ref.Parent().RelativeName())
    if updated_fields:
        return api_util.PatchFunction(function, updated_fields)
    log.status.Print('Nothing to update.')
Example #25
0
 def testGetEnvVarsAsDict_EnvVar_None(self):
     messages = api_util.GetApiMessagesModule()
     function = messages.CloudFunction()
     actual = env_vars.GetEnvVarsAsDict(function.environmentVariables)
     self.assertEqual({}, actual)
Example #26
0
 def _BuildRequest(self):
     messages = util.GetApiMessagesModule()
     project = properties.VALUES.core.project.GetOrFail()
     return messages.CloudfunctionsProjectsLocationsListRequest(
         name='projects/' + project, )
Example #27
0
def _Run(args,
         track=None,
         enable_runtime=True,
         enable_max_instances=False,
         enable_vpc_connector=False,
         enable_traffic_control=False,
         enable_allow_unauthenticated=False):
    """Run a function deployment with the given args."""
    # Check for labels that start with `deployment`, which is not allowed.
    labels_util.CheckNoDeploymentLabels('--remove-labels', args.remove_labels)
    labels_util.CheckNoDeploymentLabels('--update-labels', args.update_labels)

    # Check that exactly one trigger type is specified properly.
    trigger_util.ValidateTriggerArgs(args.trigger_event, args.trigger_resource,
                                     args.IsSpecified('retry'),
                                     args.IsSpecified('trigger_http'))

    trigger_params = trigger_util.GetTriggerEventParams(
        args.trigger_http, args.trigger_bucket, args.trigger_topic,
        args.trigger_event, args.trigger_resource)

    function_ref = args.CONCEPTS.name.Parse()
    function_url = function_ref.RelativeName()

    messages = api_util.GetApiMessagesModule(track)

    # Get an existing function or create a new one.
    function = api_util.GetFunction(function_url)
    is_new_function = function is None
    if is_new_function:
        trigger_util.CheckTriggerSpecified(args)
        function = messages.CloudFunction()
        function.name = function_url
    elif trigger_params:
        # If the new deployment would implicitly change the trigger_event type
        # raise error
        trigger_util.CheckLegacyTriggerUpdate(function.eventTrigger,
                                              trigger_params['trigger_event'])

    # Keep track of which fields are updated in the case of patching.
    updated_fields = []

    # Populate function properties based on args.
    if args.entry_point:
        function.entryPoint = args.entry_point
        updated_fields.append('entryPoint')
    if args.timeout:
        function.timeout = '{}s'.format(args.timeout)
        updated_fields.append('timeout')
    if args.memory:
        function.availableMemoryMb = utils.BytesToMb(args.memory)
        updated_fields.append('availableMemoryMb')
    if args.service_account:
        function.serviceAccountEmail = args.service_account
        updated_fields.append('serviceAccountEmail')
    if enable_runtime:
        if args.IsSpecified('runtime'):
            function.runtime = args.runtime
            updated_fields.append('runtime')
            if args.runtime in ['nodejs',
                                'nodejs6']:  # nodejs is nodejs6 alias
                log.warning(
                    'The Node.js 6 runtime is deprecated on Cloud Functions. '
                    'Please migrate to Node.js 8 (--runtime=nodejs8) or Node.js 10 '
                    '(--runtime=nodejs10). '
                    'See https://cloud.google.com/functions/docs/migrating/nodejs-runtimes'
                )
        elif is_new_function:
            raise exceptions.RequiredArgumentException(
                'runtime', 'Flag `--runtime` is required for new functions.')
    if enable_max_instances:
        if (args.IsSpecified('max_instances')
                or args.IsSpecified('clear_max_instances')):
            max_instances = 0 if args.clear_max_instances else args.max_instances
            function.maxInstances = max_instances
            updated_fields.append('maxInstances')
    if enable_vpc_connector:
        if args.IsSpecified('vpc_connector'):
            function.vpcConnector = args.vpc_connector
            updated_fields.append('vpcConnector')
            if enable_traffic_control:
                if args.IsSpecified('egress_settings'):
                    egress_settings_enum = \
                        arg_utils.ChoiceEnumMapper(
                            arg_name='egress_settings',
                            message_enum=function.VpcConnectorEgressSettingsValueValuesEnum,
                            custom_mappings=flags.EGRESS_SETTINGS_MAPPING,).GetEnumForChoice(
                                args.egress_settings)
                    function.vpcConnectorEgressSettings = egress_settings_enum
                    updated_fields.append('vpcConnectorEgressSettings')
                if (args.IsSpecified('egress_settings')
                        and not args.IsSpecified('vpc_connector')):
                    raise exceptions.RequiredArgumentException(
                        'vpc-connector', 'Flag `--vpc-connector` is '
                        'required for setting egress_settings.')
    if enable_traffic_control:
        if args.IsSpecified('ingress_settings'):
            ingress_settings_enum = \
              arg_utils.ChoiceEnumMapper(
                  arg_name='ingress_settings',
                  message_enum=function.IngressSettingsValueValuesEnum,
                  custom_mappings=flags.INGRESS_SETTINGS_MAPPING,).GetEnumForChoice(
                      args.ingress_settings)
            function.ingressSettings = ingress_settings_enum
            updated_fields.append('ingressSettings')
    # Populate trigger properties of function based on trigger args.
    if args.trigger_http:
        function.httpsTrigger = messages.HttpsTrigger()
        function.eventTrigger = None
        updated_fields.extend(['eventTrigger', 'httpsTrigger'])
    if trigger_params:
        function.eventTrigger = trigger_util.CreateEventTrigger(
            **trigger_params)
        function.httpsTrigger = None
        updated_fields.extend(['eventTrigger', 'httpsTrigger'])
    if args.IsSpecified('retry'):
        updated_fields.append('eventTrigger.failurePolicy')
        if args.retry:
            function.eventTrigger.failurePolicy = messages.FailurePolicy()
            function.eventTrigger.failurePolicy.retry = messages.Retry()
        else:
            function.eventTrigger.failurePolicy = None
    elif function.eventTrigger:
        function.eventTrigger.failurePolicy = None

    # Populate source properties of function based on source args.
    # Only Add source to function if its explicitly provided, a new function,
    # using a stage bucket or deploy of an existing function that previously
    # used local source.
    if (args.source or args.stage_bucket or is_new_function
            or function.sourceUploadUrl):
        updated_fields.extend(
            source_util.SetFunctionSourceProps(function, function_ref,
                                               args.source, args.stage_bucket,
                                               args.ignore_file))

    # Apply label args to function
    if labels_util.SetFunctionLabels(function, args.update_labels,
                                     args.remove_labels, args.clear_labels):
        updated_fields.append('labels')

    # Apply environment variables args to function
    updated_fields.extend(_ApplyEnvVarsArgsToFunction(function, args))

    ensure_all_users_invoke = (enable_allow_unauthenticated
                               and flags.ShouldEnsureAllUsersInvoke(args))
    deny_all_users_invoke = (enable_allow_unauthenticated
                             and flags.ShouldDenyAllUsersInvoke(args))

    if is_new_function:
        if (enable_allow_unauthenticated and not ensure_all_users_invoke
                and not deny_all_users_invoke and
                api_util.CanAddFunctionIamPolicyBinding(_GetProject(args))):
            ensure_all_users_invoke = console_io.PromptContinue(prompt_string=(
                'Allow unauthenticated invocations of new function [{}]?'.
                format(args.NAME)),
                                                                default=False)

        op = api_util.CreateFunction(function,
                                     function_ref.Parent().RelativeName())
        if (enable_allow_unauthenticated and not ensure_all_users_invoke
                and not deny_all_users_invoke):
            template = ('Function created with default IAM policy. '
                        'To enable unauthorized access consider "%s"')
            log.warning(template %
                        _CreateBindPolicyCommand(args.NAME, args.region))

    elif updated_fields:
        op = api_util.PatchFunction(function, updated_fields)

    else:
        op = None  # Nothing to wait for
        if not ensure_all_users_invoke and not deny_all_users_invoke:
            log.status.Print('Nothing to update.')
            return

    try:
        if ensure_all_users_invoke:
            api_util.AddFunctionIamPolicyBinding(function.name)
        elif deny_all_users_invoke:
            api_util.RemoveFunctionIamPolicyBindingIfFound(function.name)
    except exceptions.HttpException:
        log.warning('Setting IAM policy failed, try "%s"' %
                    _CreateBindPolicyCommand(args.NAME, args.region))

    if op:
        api_util.WaitForFunctionUpdateOperation(op)
    return api_util.GetFunction(function.name)
Example #28
0
 def testGetFunctionEnvVarsAsDictNone(self):
     messages = api_util.GetApiMessagesModule()
     function = messages.CloudFunction()
     actual = env_vars.GetFunctionEnvVarsAsDict(function)
     self.assertEqual({}, actual)
Example #29
0
def _Run(args, enable_runtime=False):
    """Run a function deployment with the given args."""
    # Check for labels that start with `deployment`, which is not allowed.
    labels_util.CheckNoDeploymentLabels('--remove-labels', args.remove_labels)
    labels_util.CheckNoDeploymentLabels('--update-labels', args.update_labels)

    # Check that exactly one trigger type is specified properly.
    trigger_util.ValidateTriggerArgs(args.trigger_event, args.trigger_resource,
                                     args.IsSpecified('retry'),
                                     args.IsSpecified('trigger_http'))

    trigger_params = trigger_util.GetTriggerEventParams(
        args.trigger_http, args.trigger_bucket, args.trigger_topic,
        args.trigger_event, args.trigger_resource)

    function_ref = api_util.GetFunctionRef(args.name)
    function_url = function_ref.RelativeName()

    messages = api_util.GetApiMessagesModule()

    # Get an existing function or create a new one.
    function = api_util.GetFunction(function_url)
    is_new_function = function is None
    if is_new_function:
        trigger_util.CheckTriggerSpecified(args)
        function = messages.CloudFunction()
        function.name = function_url
    elif trigger_params:
        # If the new deployment would implicitly change the trigger_event type
        # raise error
        trigger_util.CheckLegacyTriggerUpdate(function.eventTrigger,
                                              trigger_params['trigger_event'])

    # Keep track of which fields are updated in the case of patching.
    updated_fields = []

    # Populate function properties based on args.
    if args.entry_point:
        function.entryPoint = args.entry_point
        updated_fields.append('entryPoint')
    if args.timeout:
        function.timeout = '{}s'.format(args.timeout)
        updated_fields.append('timeout')
    if args.memory:
        function.availableMemoryMb = utils.BytesToMb(args.memory)
        updated_fields.append('availableMemoryMb')
    if enable_runtime:
        if args.IsSpecified('runtime'):
            function.runtime = args.runtime
            updated_fields.append('runtime')

    # Populate trigger properties of function based on trigger args.
    if args.trigger_http:
        function.httpsTrigger = messages.HttpsTrigger()
        function.eventTrigger = None
        updated_fields.extend(['eventTrigger', 'httpsTrigger'])
    if trigger_params:
        function.eventTrigger = trigger_util.CreateEventTrigger(
            **trigger_params)
        function.httpsTrigger = None
        updated_fields.extend(['eventTrigger', 'httpsTrigger'])
    if args.IsSpecified('retry'):
        updated_fields.append('eventTrigger.failurePolicy')
        if args.retry:
            function.eventTrigger.failurePolicy = messages.FailurePolicy()
            function.eventTrigger.failurePolicy.retry = messages.Retry()
        else:
            function.eventTrigger.failurePolicy = None
    elif function.eventTrigger:
        function.eventTrigger.failurePolicy = None

    # Populate source properties of function based on source args.
    # Only Add source to function if its explicitly provided, a new function,
    # using a stage budget or deploy of an existing function that previously
    # used local source.
    if (args.source or args.stage_bucket or is_new_function
            or function.sourceUploadUrl):
        updated_fields.extend(
            source_util.SetFunctionSourceProps(function, function_ref,
                                               args.source, args.stage_bucket))

    # Apply label args to function
    if labels_util.SetFunctionLabels(function, args.update_labels,
                                     args.remove_labels, args.clear_labels):
        updated_fields.append('labels')

    if is_new_function:
        return api_util.CreateFunction(function)
    if updated_fields:
        return api_util.PatchFunction(function, updated_fields)
    log.status.Print('Nothing to update.')