# -*- coding: utf-8 -*- # # Copyright 2020 Google LLC. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Utilities for Eventarc Triggers API.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from apitools.base.py import list_pager from googlecloudsdk.api_lib.eventarc import base from googlecloudsdk.api_lib.eventarc import common from googlecloudsdk.api_lib.util import apis from googlecloudsdk.command_lib.eventarc import types from googlecloudsdk.core import exceptions from googlecloudsdk.core import resources from googlecloudsdk.core.util import iso_duration from googlecloudsdk.core.util import times MAX_ACTIVE_DELAY_MINUTES = 2 class NoFieldsSpecifiedError(exceptions.Error): """Error when no fields were specified for a Patch operation.""" def GetTriggerURI(resource): trigger = resources.REGISTRY.ParseRelativeName( resource.name, collection='eventarc.projects.locations.triggers') return trigger.SelfLink() def TriggerActiveTime(event_type, update_time): """Computes the time by which the trigger will become active. Args: event_type: str, the trigger's event type. update_time: str, the time when the trigger was last modified. Returns: The active time as a string, or None if the trigger is already active. """ if not types.IsAuditLogType(event_type): # The delay only applies to Audit Log triggers. return None update_dt = times.ParseDateTime(update_time) delay = iso_duration.Duration(minutes=MAX_ACTIVE_DELAY_MINUTES) active_dt = times.GetDateTimePlusDuration(update_dt, delay) if times.Now() >= active_dt: return None return times.FormatDateTime(active_dt, fmt='%H:%M:%S', tzinfo=times.LOCAL) class _BaseTriggersClient(base.EventarcClientBase): """Base Triggers Client.""" def __init__(self): super(_BaseTriggersClient, self).__init__(common.API_NAME, 'v1', 'trigger') client = apis.GetClientInstance(common.API_NAME, 'v1') self._messages = client.MESSAGES_MODULE self._service = client.projects_locations_triggers self._operation_service = client.projects_locations_operations def Create(self, trigger_ref, trigger_message): """Creates a new Trigger. Args: trigger_ref: Resource, the Trigger to create. trigger_message: Trigger, the trigger message that holds trigger's event_filters, service account, destination, transport, etc. Returns: A long-running operation for create. """ create_req = self._messages.EventarcProjectsLocationsTriggersCreateRequest( parent=trigger_ref.Parent().RelativeName(), trigger=trigger_message, triggerId=trigger_ref.Name()) return self._service.Create(create_req) def Delete(self, trigger_ref): """Deletes a Trigger. Args: trigger_ref: Resource, the Trigger to delete. Returns: A long-running operation for delete. """ delete_req = self._messages.EventarcProjectsLocationsTriggersDeleteRequest( name=trigger_ref.RelativeName()) return self._service.Delete(delete_req) def Get(self, trigger_ref): """Gets a Trigger. Args: trigger_ref: Resource, the Trigger to get. Returns: The Trigger message. """ get_req = self._messages.EventarcProjectsLocationsTriggersGetRequest( name=trigger_ref.RelativeName()) return self._service.Get(get_req) def List(self, location_ref, limit, page_size): """Lists Triggers in a given location. Args: location_ref: Resource, the location to list Triggers in. limit: int or None, the total number of results to return. page_size: int, the number of entries in each batch (affects requests made, but not the yielded results). Returns: A generator of Triggers in the location. """ list_req = self._messages.EventarcProjectsLocationsTriggersListRequest( parent=location_ref.RelativeName(), pageSize=page_size) return list_pager.YieldFromList( self._service, list_req, field='triggers', batch_size=page_size, limit=limit, batch_size_attribute='pageSize') def Patch(self, trigger_ref, trigger_message, update_mask): """Updates a Trigger. Args: trigger_ref: Resource, the Trigger to update. trigger_message: Trigger, the trigger message that holds trigger's event_filters, service account, destination, transport, etc. update_mask: str, a comma-separated list of Trigger fields to update. Returns: A long-running operation for update. """ patch_req = self._messages.EventarcProjectsLocationsTriggersPatchRequest( name=trigger_ref.RelativeName(), trigger=trigger_message, updateMask=update_mask) return self._service.Patch(patch_req) class TriggersClientV1(_BaseTriggersClient): """Client for Triggers service in the Eventarc GA API.""" def BuildTriggerMessage( self, trigger_ref, event_filters, event_filters_path_pattern, event_data_content_type, service_account, destination_message, transport_topic_ref, channel_ref, max_retry_attempts, labels, ): """Builds a Trigger message with the given data. Args: trigger_ref: Resource, the Trigger to create. event_filters: dict or None, the Trigger's event filters. event_filters_path_pattern: dict or None, the Trigger's event filters in path pattern format. event_data_content_type: str or None, the data content type of the Trigger's event. service_account: str or None, the Trigger's service account. destination_message: Destination message or None, the Trigger's destination. transport_topic_ref: Resource or None, the user-provided transport topic. channel_ref: Resource or None, the channel for 3p events max_retry_attempts: int or None, the Trigger's max retry attempts. labels: dict or None, the Trigger's labels. Returns: A Trigger message with a destination service. """ filter_messages = [] if event_filters is not None: filter_messages = [ self._messages.EventFilter(attribute=key, value=value) for key, value in event_filters.items() ] if event_filters_path_pattern is not None: for key, value in event_filters_path_pattern.items(): filter_messages.append( self._messages.EventFilter( attribute=key, value=value, operator='match-path-pattern')) transport_topic_name = transport_topic_ref.RelativeName( ) if transport_topic_ref else None pubsub = self._messages.Pubsub(topic=transport_topic_name) transport = self._messages.Transport(pubsub=pubsub) channel = channel_ref.RelativeName() if channel_ref else None return self._messages.Trigger( name=trigger_ref.RelativeName(), eventFilters=filter_messages, eventDataContentType=event_data_content_type, serviceAccount=service_account, destination=destination_message, transport=transport, channel=channel, retryPolicy=self._BuildRetryPolicy(max_retry_attempts), labels=labels, ) def _BuildRetryPolicy(self, max_retry_attempts): """Builds a RetryPolicy message with the given data. Args: max_retry_attempts: int or None, the Trigger's max retry attempts. Returns: A RetryPolicy message with the given data. """ if max_retry_attempts is None: return None return self._messages.RetryPolicy(maxAttempts=max_retry_attempts) def BuildCloudRunDestinationMessage(self, destination_run_service, destination_run_job, destination_run_path, destination_run_region): """Builds a Destination message for a destination Cloud Run service. Args: destination_run_service: str or None, the destination Cloud Run service. destination_run_job: str or None, the destination Cloud Run job. destination_run_path: str or None, the path on the destination Cloud Run service. destination_run_region: str or None, the destination Cloud Run service's region. Returns: A Destination message for a destination Cloud Run service. """ # Because the flags for service and job are in a mutually exclusive group, # we can set both here under the assumption one of them will be None. run_message = self._messages.CloudRun( service=destination_run_service, job=destination_run_job, path=destination_run_path, region=destination_run_region) return self._messages.Destination(cloudRun=run_message) def BuildGKEDestinationMessage(self, destination_gke_cluster, destination_gke_location, destination_gke_namespace, destination_gke_service, destination_gke_path): """Builds a Destination message for a destination GKE service. Args: destination_gke_cluster: str or None, the destination GKE service's cluster. destination_gke_location: str or None, the location of the destination GKE service's cluster. destination_gke_namespace: str or None, the destination GKE service's namespace. destination_gke_service: str or None, the destination GKE service. destination_gke_path: str or None, the path on the destination GKE service. Returns: A Destination message for a destination GKE service. """ gke_message = self._messages.GKE( cluster=destination_gke_cluster, location=destination_gke_location, namespace=destination_gke_namespace, service=destination_gke_service, path=destination_gke_path) return self._messages.Destination(gke=gke_message) def BuildWorkflowDestinationMessage(self, project_id, destination_workflow, destination_workflow_location): """Builds a Workflow Destination message with the given data. Args: project_id: the ID of the project. destination_workflow: str or None, the Trigger's destination Workflow ID. destination_workflow_location: str or None, the location of the Trigger's destination Workflow. It defaults to the Trigger's location. Returns: A Destination message with a Workflow destination. """ workflow_message = 'projects/{}/locations/{}/workflows/{}'.format( project_id, destination_workflow_location, destination_workflow) return self._messages.Destination(workflow=workflow_message) def BuildFunctionDestinationMessage(self, project_id, destination_function, destination_function_location): """Builds a Function Destination message with the given data. Args: project_id: the ID of the project. destination_function: str or None, the Trigger's destination Function ID. destination_function_location: str or None, the location of the Trigger's destination Function. It defaults to the Trigger's location. Returns: A Destination message with a Function destination. """ function_message = 'projects/{}/locations/{}/functions/{}'.format( project_id, destination_function_location, destination_function) return self._messages.Destination(cloudFunction=function_message) def BuildHTTPEndpointDestinationMessage( self, destination_http_endpoint_uri, network_attachment ): """Builds a HTTP Endpoint Destination message with the given data. Args: destination_http_endpoint_uri: str or None, the Trigger's destination uri. network_attachment: str or None, the Trigger's destination network attachment. Returns: A Destination message with a HTTP Endpoint destination. """ http_endpoint_message = self._messages.HttpEndpoint( uri=destination_http_endpoint_uri, ) network_config_message = self._messages.NetworkConfig( networkAttachment=network_attachment ) return self._messages.Destination( httpEndpoint=http_endpoint_message, networkConfig=network_config_message ) def BuildUpdateMask( self, event_filters, event_filters_path_pattern, event_data_content_type, service_account, destination_run_service, destination_run_job, destination_run_path, destination_run_region, destination_gke_namespace, destination_gke_service, destination_gke_path, destination_workflow, destination_workflow_location, destination_function, destination_function_location, max_retry_attempts, labels, ): """Builds an update mask for updating a Cloud Run trigger. Args: event_filters: bool, whether to update the event filters. event_filters_path_pattern: bool, whether to update the event filters with path pattern syntax. event_data_content_type: bool, whether to update the event data content type. service_account: bool, whether to update the service account. destination_run_service: bool, whether to update the destination Cloud Run service. destination_run_job: bool, whether to update the desintation Cloud Run job. destination_run_path: bool, whether to update the destination Cloud Run path. destination_run_region: bool, whether to update the destination Cloud Run region. destination_gke_namespace: bool, whether to update the destination GKE service namespace. destination_gke_service: bool, whether to update the destination GKE service name. destination_gke_path: bool, whether to update the destination GKE path. destination_workflow: bool, whether to update the destination workflow. destination_workflow_location: bool, whether to update the destination workflow location. destination_function: bool, whether to update the destination function. destination_function_location: bool, whether to update the destination function location. max_retry_attempts: bool, whether to update the max retry attempts. labels: bool, whether to update the labels. Returns: The update mask as a string. Raises: NoFieldsSpecifiedError: No fields are being updated. """ update_mask = [] if destination_run_path: update_mask.append('destination.cloudRun.path') if destination_run_region: update_mask.append('destination.cloudRun.region') if destination_run_service: update_mask.append('destination.cloudRun.service') if destination_run_job: update_mask.append('destination.cloudRun.job') if destination_gke_namespace: update_mask.append('destination.gke.namespace') if destination_gke_service: update_mask.append('destination.gke.service') if destination_gke_path: update_mask.append('destination.gke.path') if destination_workflow or destination_workflow_location: update_mask.append('destination.workflow') if destination_function or destination_function_location: update_mask.append('destination.cloudFunction') if event_filters or event_filters_path_pattern: update_mask.append('eventFilters') if service_account: update_mask.append('serviceAccount') if event_data_content_type: update_mask.append('eventDataContentType') if max_retry_attempts: update_mask.append('retryPolicy') if labels: update_mask.append('labels') if not update_mask: raise NoFieldsSpecifiedError('Must specify at least one field to update.') return ','.join(update_mask) def GetEventType(self, trigger_message): """Gets the Trigger's event type.""" return types.EventFiltersMessageToType(trigger_message.eventFilters) def LabelsValueClass(self): """Returns the labels value class.""" return self._messages.Trigger.LabelsValue