feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Base client for Eventarc API interaction."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.api_lib.util import exceptions
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.core import resources
class EventarcClientBase(object):
"""Base Client for interaction with of Eventarc API."""
def __init__(self, api_name, api_version, resource_label):
client = apis.GetClientInstance(api_name, api_version)
self._operation_service = client.projects_locations_operations
self._resource_label = resource_label
def WaitFor(self, operation, operation_type, resource_ref, loading_msg=''):
"""Waits until the given long-running operation is complete.
Args:
operation: the long-running operation to wait for.
operation_type: str, the type of operation (Creating, Updating or
Deleting).
resource_ref: Resource to reference.
loading_msg: str, the message prompt to the user for a long-running
operation.
Returns:
The long-running operation's response.
Raises:
HttpException: when failing to pull the long-running operation's status.
"""
poller = waiter.CloudOperationPollerNoResources(self._operation_service)
operation_ref = resources.REGISTRY.Parse(
operation.name, collection='eventarc.projects.locations.operations')
resource_name = resource_ref.Name()
project_name = resource_ref.Parent().Parent().Name()
location_name = resource_ref.Parent().Name()
message = ('{} {} [{}] in project [{}], '
'location [{}]').format(operation_type, self._resource_label,
resource_name, project_name,
location_name)
if loading_msg:
message = '{}, {}'.format(message, loading_msg)
try:
return waiter.WaitFor(
poller, operation_ref, message, wait_ceiling_ms=20000
)
except apitools_exceptions.HttpForbiddenError as e:
desc_cmd = 'gcloud eventarc {} describe {} --location={}'.format(
self._resource_label_plural, resource_name, location_name
)
error_message = (
'Failed to poll status of the operation, but the operation may have '
'succeeded. {status_message} After fixing the permission issue, '
'either check the %s by running `%s`, or rerun the original '
'command.'
) % (self._resource_label, desc_cmd)
raise exceptions.HttpException(e, error_format=error_message)
@property
def _resource_label_plural(self):
return '{}s'.format(self._resource_label)

View File

@@ -0,0 +1,162 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for Eventarc channel connections 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 common
from googlecloudsdk.api_lib.eventarc import common_publishing
from googlecloudsdk.api_lib.eventarc.base import EventarcClientBase
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import resources
def GetChannelConnectionsURI(resource):
channel_connections = resources.REGISTRY.ParseRelativeName(
resource.name,
collection='eventarc.projects.locations.channelConnections')
return channel_connections.SelfLink()
class ChannelConnectionClientV1(EventarcClientBase):
"""Channel connections client for Eventarc API V1."""
def __init__(self):
super(ChannelConnectionClientV1,
self).__init__(common.API_NAME, common.API_VERSION_1,
'Channel Connection')
# Eventarc Client
client = apis.GetClientInstance(common.API_NAME, common.API_VERSION_1)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations_channelConnections
# Eventarc Publishing client
publishing_client = apis.GetClientInstance(common_publishing.API_NAME,
common_publishing.API_VERSION_1)
self._publishing_messages = publishing_client.MESSAGES_MODULE
self._publishing_service = publishing_client.projects_locations_channelConnections
def Create(self, channel_connection_ref, channel_connection_message):
"""Creates a new Channel Connection.
Args:
channel_connection_ref: Resource, the Channel connection to create.
channel_connection_message: Channel connection, the channel connection
message that holds channel's reference, activation token, etc.
Returns:
A long-running operation for create.
"""
create_req = self._messages.EventarcProjectsLocationsChannelConnectionsCreateRequest(
parent=channel_connection_ref.Parent().RelativeName(),
channelConnection=channel_connection_message,
channelConnectionId=channel_connection_ref.Name())
return self._service.Create(create_req)
def Delete(self, channel_connection_ref):
"""Deletes the specified Channel Connection.
Args:
channel_connection_ref: Resource, the Channel Connection to delete.
Returns:
A long-running operation for delete.
"""
delete_req = self._messages.EventarcProjectsLocationsChannelConnectionsDeleteRequest(
name=channel_connection_ref.RelativeName())
return self._service.Delete(delete_req)
def Get(self, channel_connection_ref):
"""Gets the requested Channel Connection.
Args:
channel_connection_ref: Resource, the Channel Connection to get.
Returns:
The Channel Connection message.
"""
get_req = self._messages.EventarcProjectsLocationsChannelConnectionsGetRequest(
name=channel_connection_ref.RelativeName())
return self._service.Get(get_req)
def List(self, location_ref, limit, page_size):
"""List available channel connections in location.
Args:
location_ref: Resource, the location to list Channel Connections 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 Channel Connections in the location.
"""
list_req = self._messages.EventarcProjectsLocationsChannelConnectionsListRequest(
parent=location_ref.RelativeName(), pageSize=page_size)
return list_pager.YieldFromList(
service=self._service,
request=list_req,
field='channelConnections',
limit=limit,
batch_size=page_size,
batch_size_attribute='pageSize')
def Publish(self, channel_connection_ref, cloud_event):
"""Publish to a Channel Conenction.
Args:
channel_connection_ref: Resource, the channel connection to publish from.
cloud_event: A CloudEvent representation to be passed as the request body.
"""
# Format to CloudEvents v1.0
events_value_list_entry = common_publishing.TransformEventsForPublishing(
self._publishing_messages
.GoogleCloudEventarcPublishingV1PublishChannelConnectionEventsRequest
.EventsValueListEntry, cloud_event)
publish_events_request = self._publishing_messages.GoogleCloudEventarcPublishingV1PublishChannelConnectionEventsRequest(
events=[events_value_list_entry]
)
publish_req = self._publishing_messages.EventarcpublishingProjectsLocationsChannelConnectionsPublishEventsRequest(
channelConnection=channel_connection_ref.RelativeName(),
googleCloudEventarcPublishingV1PublishChannelConnectionEventsRequest=publish_events_request
)
self._publishing_service.PublishEvents(publish_req)
def BuildChannelConnection(self, channel_connection_ref, channel,
activation_token, labels):
channel_connection_labels = None
if labels is not None:
channel_connection_labels = self._messages.ChannelConnection.LabelsValue(
additionalProperties=[
self._messages.ChannelConnection.LabelsValue.AdditionalProperty(
key=key,
value=value,
)
for key, value in labels.items()
]
)
return self._messages.ChannelConnection(
name=channel_connection_ref.RelativeName(),
channel=channel,
activationToken=activation_token,
labels=channel_connection_labels,
)

View File

@@ -0,0 +1,205 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for Eventarc Channels 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 common
from googlecloudsdk.api_lib.eventarc import common_publishing
from googlecloudsdk.api_lib.eventarc.base import EventarcClientBase
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import resources
class NoFieldsSpecifiedError(exceptions.Error):
"""Error when no fields were specified for a Patch operation."""
def GetChannelURI(resource):
channels = resources.REGISTRY.ParseRelativeName(
resource.name, collection='eventarc.projects.locations.channels')
return channels.SelfLink()
class ChannelClientV1(EventarcClientBase):
"""Channel Client for interaction with v1 of Eventarc Channels API."""
def __init__(self):
super(ChannelClientV1, self).__init__(common.API_NAME, common.API_VERSION_1,
'channel')
# Eventarc Client
client = apis.GetClientInstance(common.API_NAME, common.API_VERSION_1)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations_channels
# Eventarc Publishing client
publishing_client = apis.GetClientInstance(common_publishing.API_NAME,
common_publishing.API_VERSION_1)
self._publishing_messages = publishing_client.MESSAGES_MODULE
self._publishing_service = publishing_client.projects_locations_channels
def Create(self, channel_ref, channel_message, dry_run=False):
"""Creates a new Channel.
Args:
channel_ref: Resource, the Channel to create.
channel_message: Channel, the channel message that holds channel's name,
provider, etc.
dry_run: If set, the changes will not be commited, only validated
Returns:
A long-running operation for create.
"""
create_req = self._messages.EventarcProjectsLocationsChannelsCreateRequest(
parent=channel_ref.Parent().RelativeName(),
channel=channel_message,
channelId=channel_ref.Name(),
validateOnly=dry_run)
return self._service.Create(create_req)
def Delete(self, channel_ref):
"""Deletes the specified Channel.
Args:
channel_ref: Resource, the Channel to delete.
Returns:
A long-running operation for delete.
"""
delete_req = self._messages.EventarcProjectsLocationsChannelsDeleteRequest(
name=channel_ref.RelativeName())
return self._service.Delete(delete_req)
def Get(self, channel_ref):
"""Gets the requested Channel.
Args:
channel_ref: Resource, the Channel to get.
Returns:
The Channel message.
"""
get_req = self._messages.EventarcProjectsLocationsChannelsGetRequest(
name=channel_ref.RelativeName())
return self._service.Get(get_req)
def List(self, location_ref, limit, page_size):
"""List available channels in location.
Args:
location_ref: Resource, the location to list Channels 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 Channels in the location.
"""
list_req = self._messages.EventarcProjectsLocationsChannelsListRequest(
parent=location_ref.RelativeName(), pageSize=page_size)
return list_pager.YieldFromList(
service=self._service,
request=list_req,
field='channels',
limit=limit,
batch_size=page_size,
batch_size_attribute='pageSize')
def Patch(self, channel_ref, channel_message, update_mask):
"""Updates the specified Channel.
Args:
channel_ref: Resource, the Channel to update.
channel_message: Channel, the channel message that holds channel's name,
provider, etc.
update_mask: str, a comma-separated list of Channel fields to update.
Returns:
A long-running operation for update.
"""
patch_req = self._messages.EventarcProjectsLocationsChannelsPatchRequest(
name=channel_ref.RelativeName(),
channel=channel_message,
updateMask=update_mask)
return self._service.Patch(patch_req)
def Publish(self, channel_ref, cloud_event):
"""Publish to a Channel.
Args:
channel_ref: Resource, the channel to publish to.
cloud_event: A CloudEvent representation to be passed as the request body.
"""
# Format to CloudEvents v1.0
events_value_list_entry = common_publishing.TransformEventsForPublishing(
self._publishing_messages
.GoogleCloudEventarcPublishingV1PublishEventsRequest
.EventsValueListEntry, cloud_event)
publish_events_request = self._publishing_messages.GoogleCloudEventarcPublishingV1PublishEventsRequest(
events=[events_value_list_entry])
publish_req = self._publishing_messages.EventarcpublishingProjectsLocationsChannelsPublishEventsRequest(
channel=channel_ref.RelativeName(),
googleCloudEventarcPublishingV1PublishEventsRequest=publish_events_request
)
# GoogleCloudEventarcPublishingV1PublishEventsResponse
self._publishing_service.PublishEvents(publish_req)
def BuildChannel(self, channel_ref, provider_ref, crypto_key_name, labels):
return self._messages.Channel(
name=channel_ref.RelativeName(),
cryptoKeyName=crypto_key_name,
provider=provider_ref
if provider_ref is None else provider_ref.RelativeName(),
labels=labels,
)
def BuildUpdateMask(self, crypto_key, clear_crypto_key, labels):
"""Builds an update mask for updating a channel.
Args:
crypto_key: bool, whether to update the crypto key.
clear_crypto_key: bool, whether to clear the crypto key.
labels: bool, whether to update the labels.
Returns:
The update mask as a string.
Raises:
NoFieldsSpecifiedError: No fields are being updated.
"""
update_mask = []
if crypto_key:
update_mask.append('cryptoKeyName')
if clear_crypto_key:
update_mask.append('cryptoKeyName')
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 LabelsValueCls(self):
return self._messages.Channel.LabelsValue

View File

@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*- #
# Copyright 2020 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Common utilities for the Eventarc API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.calliope import base
import six.moves.urllib.parse
API_NAME = 'eventarc'
API_VERSION_1 = 'v1'
def GetApiVersion(release_track):
if release_track == base.ReleaseTrack.GA:
return API_VERSION_1
else:
return None
def GetApiServiceName(api_version):
"""Gets the service name based on the configured API endpoint."""
endpoint = apis.GetEffectiveApiEndpoint(API_NAME, api_version)
return six.moves.urllib.parse.urlparse(endpoint).hostname

View File

@@ -0,0 +1,92 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for Eventarc Publishing API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import json
from apitools.base.py import extra_types
from googlecloudsdk.core.util import times
API_NAME = "eventarcpublishing"
API_VERSION_1 = "v1"
def TransformEventsForPublishing(events_value_list_entry, cloud_event):
"""Format events value list entry into CloudEvent.
Args:
events_value_list_entry: A EventsValueListEntry object.
cloud_event: A CloudEvent representation to be passed as the request body.
Returns:
The CloudEvents v1.0 events to publish.
"""
# From Json string to Json Object
proto_json = extra_types.JsonProtoDecoder(json.dumps(cloud_event))
additional_properties = [
events_value_list_entry.AdditionalProperty(key=obj.key, value=obj.value)
for obj in proto_json.properties
]
return events_value_list_entry(additionalProperties=additional_properties)
def CreateCloudEvent(event_id, event_type, event_source, event_data,
event_attributes):
"""Transform args to a valid cloud event.
Args:
event_id: The id of a published event.
event_type: The event type of a published event.
event_source: The event source of a published event.
event_data: The event data of a published event.
event_attributes: The event attributes of a published event. It can be
repeated to add more attributes.
Returns:
valid CloudEvent.
"""
cloud_event = {
"@type": "type.googleapis.com/io.cloudevents.v1.CloudEvent",
"id": event_id,
"source": event_source,
"specVersion": "1.0",
"type": event_type,
"attributes": {
"time": {
"ceTimestamp":
times.FormatDateTime(times.Now())
},
"datacontenttype": {
"ceString": "application/json"
},
},
"textData": event_data
}
# Event attributes could be zero or more
# So it must be serialized into a dictionary
if event_attributes is not None:
for key, value in event_attributes.items():
cloud_event["attributes"][key] = {"ceString": value}
return cloud_event

View File

@@ -0,0 +1,192 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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 Enrollments 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.core import exceptions
from googlecloudsdk.core import resources
class NoFieldsSpecifiedError(exceptions.Error):
"""Error when no fields were specified for a Patch operation."""
def GetEnrollmentURI(resource):
enrollments = resources.REGISTRY.ParseRelativeName(
resource.name, collection='eventarc.projects.locations.enrollments'
)
return enrollments.SelfLink()
class EnrollmentClientV1(base.EventarcClientBase):
"""Enrollment Client for interaction with v1 of Eventarc Enrollments API."""
def __init__(self):
super(EnrollmentClientV1, self).__init__(
common.API_NAME, common.API_VERSION_1, 'enrollment'
)
# Eventarc Client
client = apis.GetClientInstance(common.API_NAME, common.API_VERSION_1)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations_enrollments
def Create(self, enrollment_ref, enrollment_message, dry_run=False):
"""Creates a new Enrollment.
Args:
enrollment_ref: Resource, the Enrollment to create.
enrollment_message: Enrollment, the enrollment message that holds
enrollment's name, cel_match, message_bus, destination, etc.
dry_run: If set, the changes will not be committed, only validated
Returns:
A long-running operation for create.
"""
create_req = (
self._messages.EventarcProjectsLocationsEnrollmentsCreateRequest(
parent=enrollment_ref.Parent().RelativeName(),
enrollment=enrollment_message,
enrollmentId=enrollment_ref.Name(),
validateOnly=dry_run,
)
)
return self._service.Create(create_req)
def Get(self, enrollment_ref):
"""Gets the requested Enrollment.
Args:
enrollment_ref: Resource, the Enrollment to get.
Returns:
The Enrollment message.
"""
get_req = self._messages.EventarcProjectsLocationsEnrollmentsGetRequest(
name=enrollment_ref.RelativeName()
)
return self._service.Get(get_req)
def List(self, location_ref, limit, page_size):
"""List available enrollments in location.
Args:
location_ref: Resource, the location to list Enrollments 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 Enrollments in the location.
"""
list_req = self._messages.EventarcProjectsLocationsEnrollmentsListRequest(
parent=location_ref.RelativeName(), pageSize=page_size
)
return list_pager.YieldFromList(
service=self._service,
request=list_req,
field='enrollments',
limit=limit,
batch_size=page_size,
batch_size_attribute='pageSize',
)
def Patch(self, enrollment_ref, enrollment_message, update_mask):
"""Updates the specified Enrollment.
Args:
enrollment_ref: Resource, the Enrollment to update.
enrollment_message: Enrollment, the enrollment message that holds
enrollment's name, cel_match, message_bus, destination, etc.
update_mask: str, a comma-separated list of Enrollment fields to update.
Returns:
A long-running operation for update.
"""
patch_req = self._messages.EventarcProjectsLocationsEnrollmentsPatchRequest(
name=enrollment_ref.RelativeName(),
enrollment=enrollment_message,
updateMask=update_mask,
)
return self._service.Patch(patch_req)
def Delete(self, enrollment_ref):
"""Deletes the specified Enrollment.
Args:
enrollment_ref: Resource, the Enrollment to delete.
Returns:
A long-running operation for delete.
"""
delete_req = (
self._messages.EventarcProjectsLocationsEnrollmentsDeleteRequest(
name=enrollment_ref.RelativeName()
)
)
return self._service.Delete(delete_req)
def BuildEnrollment(
self, enrollment_ref, cel_match, message_bus_ref, destination_ref, labels
):
return self._messages.Enrollment(
name=enrollment_ref.RelativeName(),
celMatch=cel_match,
messageBus=message_bus_ref.RelativeName()
if message_bus_ref is not None
else '',
destination=destination_ref.RelativeName()
if destination_ref is not None
else '',
labels=labels,
)
def BuildUpdateMask(self, cel_match, destination, labels):
"""Builds an update mask for updating a enrollment.
Args:
cel_match: bool, whether to update the cel_match.
destination: bool, whether to update the destination.
labels: bool, whether to update the labels.
Returns:
The update mask as a string.
Raises:
NoFieldsSpecifiedError: No fields are being updated.
"""
update_mask = []
if cel_match:
update_mask.append('celMatch')
if destination:
update_mask.append('destination')
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 LabelsValueClass(self):
return self._messages.Enrollment.LabelsValue

View File

@@ -0,0 +1,172 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for Eventarc gke-destinations command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from apitools.base.py import exceptions as apitools_exceptions
from googlecloudsdk.api_lib.cloudresourcemanager import projects_api
from googlecloudsdk.api_lib.eventarc import common
from googlecloudsdk.api_lib.services import serviceusage
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.command_lib.projects import util as projects_util
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.console import console_io
from googlecloudsdk.core.util import retry
_LOCATION = 'us-central1'
_TRIGGER_ID = 'fake-trigger-id'
_ROLES = ('roles/container.developer', 'roles/iam.serviceAccountAdmin',
'roles/compute.viewer')
# Wait till service account is available for setIamPolicy
_MAX_WAIT_TIME_IN_MS = 20 * 1000
class GKEDestinationInitializationError(exceptions.InternalError):
"""Error when failing to initialize project for Cloud Run for Anthos/GKE destinations."""
def _ShouldRetryHttpError(exc_type, exc_value, exc_traceback, state):
"""Whether to retry the request when receiving errors.
Args:
exc_type: type of the raised exception.
exc_value: the instance of the raise the exception.
exc_traceback: Traceback, traceback encapsulating the call stack at the the
point where the exception occurred.
state: RetryerState, state of the retryer.
Returns:
True if exception and is due to NOT_FOUND or INVALID_ARGUMENT.
"""
del exc_value, exc_traceback, state
return (exc_type == apitools_exceptions.HttpBadRequestError or
exc_type == apitools_exceptions.HttpNotFoundError)
def _GetOrCreateP4SA(service_name):
"""Gets (or creates) the P4SA for Eventarc in the given project.
If the P4SA does not exist for this project, it will be created. Otherwise,
the email address of the existing P4SA will be returned.
Args:
service_name: str, name of the service for the P4SA, e.g.
eventarc.googleapis.com
Returns:
Email address of the Eventarc P4SA for the given project.
"""
project_name = properties.VALUES.core.project.Get(required=True)
response = serviceusage.GenerateServiceIdentity(project_name, service_name)
return response['email']
class GKEDestinationsClient(object):
"""Wrapper client for setting up Eventarc Cloud Run for Anthos/GKE destinations."""
def __init__(self, release_track):
self._api_version = common.GetApiVersion(release_track)
client = apis.GetClientInstance(common.API_NAME, self._api_version)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations_triggers
def InitServiceAccount(self):
"""Force create the Eventarc P4SA, and grant IAM roles to it.
1) First, trigger the P4SA JIT provision by trying to create an empty
trigger, ignore the HttpBadRequestError exception, then call
GenerateServiceIdentity to verify that P4SA creation is completed.
2) Then grant necessary roles needed to the P4SA for creating GKE triggers.
Raises:
GKEDestinationInitializationError: P4SA failed to be created.
"""
try:
self._CreateEmptyTrigger()
except apitools_exceptions.HttpBadRequestError:
pass
service_name = common.GetApiServiceName(self._api_version)
p4sa_email = _GetOrCreateP4SA(service_name)
if not p4sa_email:
raise GKEDestinationInitializationError(
'Failed to initialize project for Cloud Run for Anthos/GKE destinations.'
)
self._BindRolesToServiceAccount(p4sa_email, _ROLES)
def _CreateEmptyTrigger(self):
"""Attempt to create an empty trigger in us-central1 to kick off P4SA JIT provision.
The create request will always fail due to the empty trigger message
payload, but it will trigger the P4SA JIT provision.
Returns:
A long-running operation for create.
"""
project = properties.VALUES.core.project.Get(required=True)
parent = 'projects/{}/locations/{}'.format(project, _LOCATION)
req = self._messages.EventarcProjectsLocationsTriggersCreateRequest(
parent=parent, triggerId=_TRIGGER_ID)
return self._service.Create(req)
def _BindRolesToServiceAccount(self, sa_email, roles):
"""Binds roles to the provided service account.
Args:
sa_email: str, the service account to bind roles to.
roles: iterable, the roles to be bound to the service account.
"""
formatted_roles = '\n'.join(['- {}'.format(role) for role in sorted(roles)])
log.status.Print(
'To use Eventarc with Cloud Run for Anthos/GKE destinations, Eventarc Service Agent [{}] '
'needs to be bound to the following required roles:\n{}'.format(
sa_email, formatted_roles))
console_io.PromptContinue(
default=False,
throw_if_unattended=True,
prompt_string='\nWould you like to bind these roles?',
cancel_on_no=True)
project_ref = projects_util.ParseProject(
properties.VALUES.core.project.Get(required=True))
member_str = 'serviceAccount:{}'.format(sa_email)
member_roles = [(member_str, role) for role in roles]
self._AddIamPolicyBindingsWithRetry(project_ref, member_roles)
log.status.Print('Roles successfully bound.')
@retry.RetryOnException(
max_retrials=10,
max_wait_ms=_MAX_WAIT_TIME_IN_MS,
exponential_sleep_multiplier=1.6,
sleep_ms=100,
should_retry_if=_ShouldRetryHttpError)
def _AddIamPolicyBindingsWithRetry(self, project_ref, member_roles):
"""Adds iam bindings to project_ref's iam policy, with retry.
Args:
project_ref: The project for the binding
member_roles: List of 2-tuples of the form [(member, role), ...].
Returns:
The updated IAM Policy
"""
return projects_api.AddIamPolicyBindings(project_ref, member_roles)

View File

@@ -0,0 +1,275 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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 GoogleAPISources 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.core import exceptions
from googlecloudsdk.core import resources
class NoFieldsSpecifiedError(exceptions.Error):
"""Error when no fields were specified for a Patch operation."""
class NoProjectSubscriptionsSpecifiedError(exceptions.Error):
"""Error when no project subscriptions were specified."""
def GetGoogleAPISourceURI(resource):
google_api_sources = resources.REGISTRY.ParseRelativeName(
resource.name, collection='eventarc.projects.locations.googleApiSources'
)
return google_api_sources.SelfLink()
class GoogleApiSourceClientV1(base.EventarcClientBase):
"""GoogleApiSource Client for interaction with v1 of Eventarc GoogleApiSources API."""
def __init__(self):
super(GoogleApiSourceClientV1, self).__init__(
common.API_NAME, common.API_VERSION_1, 'Google API source'
)
# Eventarc Client
client = apis.GetClientInstance(common.API_NAME, common.API_VERSION_1)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations_googleApiSources
def Create(
self, google_api_source_ref, google_api_source_message, dry_run=False
):
"""Creates a new GoogleAPISource.
Args:
google_api_source_ref: Resource, the GoogleAPISource to create.
google_api_source_message: GoogleAPISource, the googleApiSource message
that holds googleApiSource's name, destination message bus, logging
config, crypto key name, etc.
dry_run: If set, the changes will not be committed, only validated
Returns:
A long-running operation for create.
"""
create_req = (
self._messages.EventarcProjectsLocationsGoogleApiSourcesCreateRequest(
parent=google_api_source_ref.Parent().RelativeName(),
googleApiSource=google_api_source_message,
googleApiSourceId=google_api_source_ref.Name(),
validateOnly=dry_run,
)
)
return self._service.Create(create_req)
def Get(self, google_api_source_ref):
"""Gets the requested GoogleApiSource.
Args:
google_api_source_ref: Resource, the GoogleApiSource to get.
Returns:
The GoogleApiSource message.
"""
get_req = (
self._messages.EventarcProjectsLocationsGoogleApiSourcesGetRequest(
name=google_api_source_ref.RelativeName()
)
)
return self._service.Get(get_req)
def List(self, location_ref, limit, page_size):
"""List available googleApiSources in location.
Args:
location_ref: Resource, the location to list GoogleApiSources 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 GoogleApiSources in the location.
"""
list_req = (
self._messages.EventarcProjectsLocationsGoogleApiSourcesListRequest(
parent=location_ref.RelativeName(), pageSize=page_size
)
)
return list_pager.YieldFromList(
service=self._service,
request=list_req,
field='googleApiSources',
limit=limit,
batch_size=page_size,
batch_size_attribute='pageSize',
)
def Patch(
self, google_api_source_ref, google_api_source_message, update_mask
):
"""Updates the specified GoogleApiSource.
Args:
google_api_source_ref: Resource, the GoogleApiSource to update.
google_api_source_message: GoogleApiSource, the googleApiSource message
that holds googleApiSource's name, destination message bus, logging
config, crypto key name, etc.
update_mask: str, a comma-separated list of GoogleApiSource fields to
update.
Returns:
A long-running operation for update.
"""
patch_req = (
self._messages.EventarcProjectsLocationsGoogleApiSourcesPatchRequest(
name=google_api_source_ref.RelativeName(),
googleApiSource=google_api_source_message,
updateMask=update_mask,
)
)
return self._service.Patch(patch_req)
def Delete(self, google_api_source_ref):
"""Deletes the specified GoogleApiSource.
Args:
google_api_source_ref: Resource, the GoogleApiSource to delete.
Returns:
A long-running operation for delete.
"""
delete_req = (
self._messages.EventarcProjectsLocationsGoogleApiSourcesDeleteRequest(
name=google_api_source_ref.RelativeName()
)
)
return self._service.Delete(delete_req)
def BuildGoogleApiSource(
self,
google_api_source_ref,
destination_ref,
logging_config,
crypto_key_name,
labels,
organization_subscription,
project_subscriptions,
):
logging_config_enum = None
if logging_config is not None:
logging_config_enum = self._messages.LoggingConfig(
logSeverity=self._messages.LoggingConfig.LogSeverityValueValuesEnum(
logging_config
),
)
google_api_source = self._messages.GoogleApiSource(
name=google_api_source_ref.RelativeName(),
destination=destination_ref.RelativeName()
if destination_ref is not None
else '',
loggingConfig=logging_config_enum,
cryptoKeyName=crypto_key_name,
labels=labels,
)
if organization_subscription is not None:
if organization_subscription:
google_api_source.organizationSubscription = (
self._messages.OrganizationSubscription(
enabled=True,
)
)
else:
google_api_source.organizationSubscription = (
self._messages.OrganizationSubscription(
enabled=False,
)
)
elif project_subscriptions:
google_api_source.projectSubscriptions = (
self._BuildProjectSubscriptionsList(project_subscriptions)
)
return google_api_source
def BuildUpdateMask(
self,
destination,
logging_config,
crypto_key,
clear_crypto_key,
labels,
organization_subscription,
project_subscriptions,
clear_project_subscriptions,
):
"""Builds an update mask for updating a GoogleApiSource.
Args:
destination: bool, whether to update the destination.
logging_config: bool, whether to update the logging config.
crypto_key: bool, whether to update the crypto key.
clear_crypto_key: bool, whether to clear the crypto key.
labels: bool, whether to update the labels.
organization_subscription: bool, whether to update the organization
subscription.
project_subscriptions: bool, whether to update the project subscriptions.
clear_project_subscriptions: bool, whether to clear the project
subscriptions.
Returns:
The update mask as a string.
Raises:
NoFieldsSpecifiedError: No fields are being updated.
"""
update_mask = []
if destination:
update_mask.append('destination')
if logging_config:
update_mask.append('loggingConfig')
if crypto_key or clear_crypto_key:
update_mask.append('cryptoKeyName')
if labels:
update_mask.append('labels')
if organization_subscription:
update_mask.append('organizationSubscription')
if project_subscriptions or clear_project_subscriptions:
update_mask.append('projectSubscriptions')
if not update_mask:
raise NoFieldsSpecifiedError('Must specify at least one field to update.')
return ','.join(update_mask)
def LabelsValueClass(self):
"""Returns the labels value class."""
return self._messages.GoogleApiSource.LabelsValue
def _BuildProjectSubscriptionsList(self, project_subscriptions):
if not project_subscriptions:
raise NoProjectSubscriptionsSpecifiedError(
'Must specify at least one project number or project ID in the'
' project subscriptions.'
)
return self._messages.ProjectSubscriptions(list=list(project_subscriptions))
@property
def _resource_label_plural(self):
return 'google-api-sources'

View File

@@ -0,0 +1,127 @@
# -*- coding: utf-8 -*- #
# Copyright 2022 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for Eventarc Channels API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.eventarc import common
from googlecloudsdk.api_lib.eventarc.base import EventarcClientBase
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import properties
class NoFieldsSpecifiedError(exceptions.Error):
"""Error when no fields were specified for a Patch operation."""
def GetProject(args):
"""Gets project resource from either argument flag or attribute."""
return args.project or properties.VALUES.core.project.GetOrFail()
def GetLocation(args):
"""Gets location resource from either argument flag or attribute."""
return args.location or properties.VALUES.eventarc.location.GetOrFail()
class GoogleChannelConfigClientV1(EventarcClientBase):
"""Google Channel Client for interaction with v1 of Eventarc Channels API."""
def __init__(self):
super(GoogleChannelConfigClientV1,
self).__init__(common.API_NAME, common.API_VERSION_1,
'GoogleChannelConfig')
# Eventarc Client
client = apis.GetClientInstance(common.API_NAME, common.API_VERSION_1)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations
def Get(self, google_channel_config_name):
"""Gets the requested GoogleChannelConfig.
Args:
google_channel_config_name: str, the name of GoogleChannelConfig to get.
Returns:
The GoogleChannelConfig message.
"""
get_req = self._messages.EventarcProjectsLocationsGetGoogleChannelConfigRequest(
name=google_channel_config_name)
return self._service.GetGoogleChannelConfig(get_req)
def Update(self, google_channel_config_name, google_channel_config_message,
update_mask):
"""Updates the specified Channel.
Args:
google_channel_config_name: str, the name of GoogleChannelConfig to
update.
google_channel_config_message: GoogleChannelConfig, the config message
that holds KMS encryption info.
update_mask: str, a comma-separated list of GoogleChannelConfig fields to
update.
Returns:
Response for update.
"""
update_req = self._messages.EventarcProjectsLocationsUpdateGoogleChannelConfigRequest(
name=google_channel_config_name,
googleChannelConfig=google_channel_config_message,
updateMask=update_mask)
return self._service.UpdateGoogleChannelConfig(update_req)
def BuildGoogleChannelConfig(
self, google_channel_config_name, crypto_key_name, labels
):
return self._messages.GoogleChannelConfig(
name=google_channel_config_name,
cryptoKeyName=crypto_key_name,
labels=labels,
)
def BuildUpdateMask(self, crypto_key, clear_crypto_key, labels):
"""Builds an update mask for updating a channel.
Args:
crypto_key: bool, whether to update the crypto key.
clear_crypto_key: bool, whether to clear the crypto key.
labels: bool, whether to update the labels.
Returns:
The update mask as a string.
Raises:
NoFieldsSpecifiedError: No fields are being updated.
"""
update_mask = []
if crypto_key:
update_mask.append('cryptoKeyName')
if clear_crypto_key:
update_mask.append('cryptoKeyName')
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 LabelsValueClass(self):
"""Returns the labels value class."""
return self._messages.GoogleChannelConfig.LabelsValue

View File

@@ -0,0 +1,409 @@
# -*- coding: utf-8 -*- #
# Copyright 2025 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for Eventarc KafkaSources API."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import uuid
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.core import exceptions
from googlecloudsdk.core import resources
class NoFieldsSpecifiedError(exceptions.Error):
"""Error when no fields were specified for a Patch operation."""
class InvalidNetworkConfigArgumentError(exceptions.Error):
"""Error when the Kafka Source's network configuration arguments are invalid."""
class InvalidDestinationArgumentError(exceptions.Error):
"""Error when the Kafka Source's destination argument is invalid."""
class InvalidAuthenticationMethodArgumentError(exceptions.Error):
"""Error when the Kafka Source's authentication arguments are invalid."""
class InvalidBrokerUrisArgumentError(exceptions.Error):
"""Error when the Kafka Source's bootstrap_servers argument is invalid."""
class InvalidTopicsArgumentError(exceptions.Error):
"""Error when the Kafka Source's topics argument is invalid."""
class InvalidInitialOffsetArgumentError(exceptions.Error):
"""Error when the Kafka Source's initial offset argument is invalid."""
def GetKafkaSourceURI(resource):
kafka_sources = resources.REGISTRY.ParseRelativeName(
resource.name, collection='eventarc.projects.locations.kafkaSources'
)
return kafka_sources.SelfLink()
class KafkaSourceClientV1(base.EventarcClientBase):
"""Kafka Source Client for interaction with v1 of Eventarc Kafka Sources API."""
def __init__(self):
super(KafkaSourceClientV1, self).__init__(
common.API_NAME, common.API_VERSION_1, 'kafkaSource'
)
# Eventarc Client
client = apis.GetClientInstance(common.API_NAME, common.API_VERSION_1)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations_kafkaSources
def Create(self, kafka_source_ref, kafka_source_message, dry_run=False):
"""Creates a Kafka Source.
Args:
kafka_source_ref: Resource, the Kafka Source to create.
kafka_source_message: Kafka Source, the Kafka Source message that holds
Kafka source's name, destinations, mediations, input payload format,
logging config, retry policy, crypto key name, etc.
dry_run: If set, the changes will not be committed, only validated
Returns:
A long-running operation for create.
"""
create_req = (
self._messages.EventarcProjectsLocationsKafkaSourcesCreateRequest(
parent=kafka_source_ref.Parent().RelativeName(),
kafkaSource=kafka_source_message,
kafkaSourceId=kafka_source_ref.Name(),
validateOnly=dry_run,
)
)
return self._service.Create(create_req)
def List(self, location_ref, limit, page_size):
"""List available Kafka Sources in location.
Args:
location_ref: Resource, the location to list Kafka Sources 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 Kafka Sources in the location.
"""
list_req = self._messages.EventarcProjectsLocationsKafkaSourcesListRequest(
parent=location_ref.RelativeName(), pageSize=page_size
)
return list_pager.YieldFromList(
service=self._service,
request=list_req,
field='kafkaSources',
limit=limit,
batch_size=page_size,
batch_size_attribute='pageSize',
)
def Get(self, kafka_source_ref):
"""Gets the requested Kafka Source.
Args:
kafka_source_ref: Resource, the Kafka Source to get.
Returns:
The Kafka Source message.
"""
get_req = self._messages.EventarcProjectsLocationsKafkaSourcesGetRequest(
name=kafka_source_ref.RelativeName()
)
return self._service.Get(get_req)
def Patch(self, kafka_source_ref, kafka_source_message, update_mask):
"""Updates the specified Kafka Source.
Args:
kafka_source_ref: Resource, the Kafka Source to update.
kafka_source_message: Kafka Source, the Kafka Source message that holds
Kafka Source's name, destinations, mediations, input payload format,
logging config, retry policy, crypto key name, etc.
update_mask: str, a comma-separated list of Kafka Source fields to update.
Returns:
A long-running operation for update.
"""
patch_req = (
self._messages.EventarcProjectsLocationsKafkaSourcesPatchRequest(
name=kafka_source_ref.RelativeName(),
kafka_source=kafka_source_message,
updateMask=update_mask,
)
)
return self._service.Patch(patch_req)
def Delete(self, kafka_source_ref):
"""Deletes the specified Kafka Source.
Args:
kafka_source_ref: Resource, the Kafka Source to delete.
Returns:
A long-running operation for delete.
"""
delete_req = (
self._messages.EventarcProjectsLocationsKafkaSourcesDeleteRequest(
name=kafka_source_ref.RelativeName()
)
)
return self._service.Delete(delete_req)
def BuildKafkaSource(
self,
kafka_source_ref,
bootstrap_servers,
consumer_group_id,
topics,
sasl_mechanism,
sasl_username,
sasl_password,
tls_client_certificate,
tls_client_key,
network_attachment,
message_bus,
initial_offset,
logging_config,
labels,
):
return self._messages.KafkaSource(
name=kafka_source_ref.RelativeName(),
brokerUris=self._BuildBrokerUris(bootstrap_servers),
consumerGroupId=self._BuildConsumerGroupID(consumer_group_id),
topics=self._BuildTopics(topics),
authenticationConfig=self._BuildAuthenticationConfig(
sasl_mechanism,
sasl_username,
sasl_password,
tls_client_certificate,
tls_client_key,
),
networkConfig=self._BuildNetworkConfig(
kafka_source_ref, network_attachment
),
destination=self._BuildDestination(kafka_source_ref, message_bus),
initialOffset=self._BuildInitialOffset(initial_offset),
loggingConfig=self._BuildLoggingConfig(logging_config),
labels=labels,
)
def BuildUpdateMask(
self,
destinations,
input_payload_format_json,
input_payload_format_avro_schema_definition,
input_payload_format_protobuf_schema_definition,
mediations,
logging_config,
max_retry_attempts,
min_retry_delay,
max_retry_delay,
crypto_key,
clear_crypto_key,
labels,
):
"""Builds an update mask for updating a Kafka Source.
Args:
destinations: bool, whether to update the destinations.
input_payload_format_json: bool, whether to update the
input_payload_format_json.
input_payload_format_avro_schema_definition: bool, whether to update the
input_payload_format_avro_schema_definition.
input_payload_format_protobuf_schema_definition: bool, whether to update
the input_payload_format_protobuf_schema_definition.
mediations: bool, whether to update the mediations.
logging_config: bool, whether to update the logging_config.
max_retry_attempts: bool, whether to update the max_retry_attempts.
min_retry_delay: bool, whether to update the min_retry_delay.
max_retry_delay: bool, whether to update the max_retry_delay.
crypto_key: bool, whether to update the crypto_key.
clear_crypto_key: bool, whether to clear the crypto_key.
labels: bool, whether to update the labels.
Returns:
The update mask as a string.
Raises:
NoFieldsSpecifiedError: No fields are being updated.
"""
update_mask = []
if destinations:
update_mask.append('destinations')
if (
input_payload_format_json
or input_payload_format_avro_schema_definition
or input_payload_format_protobuf_schema_definition
):
update_mask.append('inputPayloadFormat')
if mediations:
update_mask.append('mediations')
if logging_config:
update_mask.append('loggingConfig')
if (
max_retry_attempts
or max_retry_attempts
or min_retry_delay
or max_retry_delay
):
update_mask.append('retryPolicy')
if crypto_key or clear_crypto_key:
update_mask.append('cryptoKeyName')
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 LabelsValueClass(self):
return self._messages.KafkaSource.LabelsValue
def _BuildBrokerUris(self, bootstrap_servers):
if bootstrap_servers is None:
raise InvalidBrokerUrisArgumentError(
'Must specify at least one bootstrap server.'
)
return list(bootstrap_servers)
def _BuildTopics(self, topics):
if topics is None:
raise InvalidTopicsArgumentError('Must specify at least one topic.')
return list(topics)
def _BuildDestination(self, kafka_source_ref, message_bus):
if message_bus is None:
raise InvalidDestinationArgumentError('message_bus must be set')
return f'projects/{kafka_source_ref.projectsId}/locations/{kafka_source_ref.locationsId}/messageBuses/{message_bus}'
def _BuildNetworkConfig(self, kafka_source_ref, network_attachment):
if network_attachment is None:
raise InvalidNetworkConfigArgumentError('network_attachment must be set')
return self._messages.NetworkConfig(
networkAttachment=f'projects/{kafka_source_ref.projectsId}/regions/{kafka_source_ref.locationsId}/networkAttachments/{network_attachment}',
)
def _BuildConsumerGroupID(self, consumer_group_id):
if consumer_group_id is None:
return f'eventarc-{uuid.uuid4()}'
return consumer_group_id
def _BuildInitialOffset(self, initial_offset):
if initial_offset is None:
return 'newest'
if initial_offset != 'newest' and initial_offset != 'oldest':
raise InvalidInitialOffsetArgumentError(
'initial_offset must be one of newest or oldest'
)
return initial_offset
def _BuildAuthenticationConfig(
self,
sasl_mechanism,
sasl_username,
sasl_password,
tls_client_certificate,
tls_client_key,
):
num_args_sasl = (
(sasl_mechanism is not None)
+ (sasl_username is not None)
+ (sasl_password is not None)
)
num_args_mtls = (tls_client_certificate is not None) + (
tls_client_key is not None
)
if num_args_sasl > 0 and num_args_mtls > 0:
raise InvalidAuthenticationMethodArgumentError(
'Exactly one of the following authentication methods must be set:\n'
' - SASL Authentication (--sasl-mechanism, --sasl-username,'
' --sasl-password)\n'
' - TLS Authentication (--tls-client-certificate,'
' --tls-client-key)'
)
if num_args_sasl > 0:
if num_args_sasl != 3:
raise InvalidAuthenticationMethodArgumentError(
'When using SASL Authentication, all three arguments'
' sasl_mechanism, sasl_username, and sasl_password must be set'
)
return self._messages.AuthenticationConfig(
saslAuth=self._messages.SaslAuthConfig(
mechanism=self._ConvertSaslMechanismToEnum(sasl_mechanism),
usernameSecret=sasl_username,
passwordSecret=sasl_password,
),
mutualTlsAuth=None,
)
if num_args_mtls > 0:
if num_args_mtls != 2:
raise InvalidAuthenticationMethodArgumentError(
'When using TLS Authentication, both tls_client_certificate and'
' tls_client_key must be set'
)
return self._messages.AuthenticationConfig(
saslAuth=None,
mutualTlsAuth=self._messages.MutualTlsAuthConfig(
secretManagerResources=self._messages.MutualTlsSecrets(
clientCertificate=tls_client_certificate,
clientKey=tls_client_key,
)
),
)
raise InvalidAuthenticationMethodArgumentError(
'Exactly one of the following authentication methods must be set:\n'
' - SASL Authentication (--sasl-mechanism, --sasl-username,'
' --sasl-password)\n'
' - TLS Authentication (--tls-client-certificate,'
' --tls-client-key)'
)
def _BuildLoggingConfig(self, logging_config):
if logging_config is None:
return None
return self._messages.LoggingConfig(
logSeverity=self._messages.LoggingConfig.LogSeverityValueValuesEnum(
logging_config
),
)
def _ConvertSaslMechanismToEnum(self, mechanism):
"""Convert human-readable mechanism to enum."""
if mechanism == 'PLAIN':
return self._messages.SaslAuthConfig.MechanismValueValuesEnum('PLAIN')
if mechanism == 'SCRAM-SHA-256':
return self._messages.SaslAuthConfig.MechanismValueValuesEnum('SHA_256')
if mechanism == 'SCRAM-SHA-512':
return self._messages.SaslAuthConfig.MechanismValueValuesEnum('SHA_512')
raise InvalidAuthenticationMethodArgumentError(
'sasl_mechanism must be one of PLAIN, SCRAM_SHA_256, or'
' SCRAM_SHA_512'
)

View File

@@ -0,0 +1,65 @@
# -*- 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 Locations 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 common
from googlecloudsdk.api_lib.runtime_config import util
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
def GetLocationsURI(resource):
location = resources.REGISTRY.ParseRelativeName(
resource.name, collection='eventarc.projects.locations')
return location.SelfLink()
class LocationsClient(object):
"""Client for locations in Eventarc API."""
def __init__(self, release_track):
api_version = common.GetApiVersion(release_track)
client = apis.GetClientInstance(common.API_NAME, api_version)
self.messages = client.MESSAGES_MODULE
self._service = client.projects_locations
def List(self, limit, page_size):
"""List locations in the Eventarc API.
Args:
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:
Generator of locations.
"""
project_resource_relname = util.ProjectPath(
properties.VALUES.core.project.Get(required=True))
list_req = self.messages.EventarcProjectsLocationsListRequest(
name=project_resource_relname)
return list_pager.YieldFromList(
self._service,
list_req,
field='locations',
batch_size=page_size,
limit=limit,
batch_size_attribute='pageSize')

View File

@@ -0,0 +1,308 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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 MessageBuses 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.eventarc import common_publishing
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import resources
class NoFieldsSpecifiedError(exceptions.Error):
"""Error when no fields were specified for a Patch operation."""
def GetMessageBusURI(resource):
message_buses = resources.REGISTRY.ParseRelativeName(
resource.name, collection='eventarc.projects.locations.messageBuses'
)
return message_buses.SelfLink()
class MessageBusClientV1(base.EventarcClientBase):
"""MessageBus Client for interaction with v1 of Eventarc MessageBuses API."""
def __init__(self):
super(MessageBusClientV1, self).__init__(
common.API_NAME, common.API_VERSION_1, 'message bus'
)
# Eventarc Client
client = apis.GetClientInstance(common.API_NAME, common.API_VERSION_1)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations_messageBuses
# Eventarc Publishing client
publishing_client = apis.GetClientInstance(
common_publishing.API_NAME, common_publishing.API_VERSION_1
)
self._publishing_messages = publishing_client.MESSAGES_MODULE
self._publishing_service = publishing_client.projects_locations_messageBuses
def Create(self, message_bus_ref, message_bus_message, dry_run=False):
"""Creates a new MessageBus.
Args:
message_bus_ref: Resource, the MessageBus to create.
message_bus_message: MessageBus, the messageBus message that holds
messageBus' name, crypto key name, etc.
dry_run: If set, the changes will not be committed, only validated
Returns:
A long-running operation for create.
"""
create_req = (
self._messages.EventarcProjectsLocationsMessageBusesCreateRequest(
parent=message_bus_ref.Parent().RelativeName(),
messageBus=message_bus_message,
messageBusId=message_bus_ref.Name(),
validateOnly=dry_run,
)
)
return self._service.Create(create_req)
def Get(self, message_bus_ref):
"""Gets the requested MessageBus.
Args:
message_bus_ref: Resource, the MessageBus to get.
Returns:
The MessageBus message.
"""
get_req = self._messages.EventarcProjectsLocationsMessageBusesGetRequest(
name=message_bus_ref.RelativeName()
)
return self._service.Get(get_req)
def List(self, location_ref, limit, page_size):
"""List available messageBuses in location.
Args:
location_ref: Resource, the location to list MessageBuses 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 MessageBuses in the location.
"""
list_req = self._messages.EventarcProjectsLocationsMessageBusesListRequest(
parent=location_ref.RelativeName(), pageSize=page_size
)
return list_pager.YieldFromList(
service=self._service,
request=list_req,
field='messageBuses',
limit=limit,
batch_size=page_size,
batch_size_attribute='pageSize',
)
def Patch(self, message_bus_ref, message_bus_message, update_mask):
"""Updates the specified MessageBus.
Args:
message_bus_ref: Resource, the MessageBus to update.
message_bus_message: MessageBus, the messageBus message that holds
messageBus' name, crypto key name, etc.
update_mask: str, a comma-separated list of MessageBus fields to update.
Returns:
A long-running operation for update.
"""
patch_req = (
self._messages.EventarcProjectsLocationsMessageBusesPatchRequest(
name=message_bus_ref.RelativeName(),
messageBus=message_bus_message,
updateMask=update_mask,
)
)
return self._service.Patch(patch_req)
def Delete(self, message_bus_ref):
"""Deletes the specified MessageBus.
Args:
message_bus_ref: Resource, the MessageBus to delete.
Returns:
A long-running operation for delete.
"""
delete_req = (
self._messages.EventarcProjectsLocationsMessageBusesDeleteRequest(
name=message_bus_ref.RelativeName()
)
)
return self._service.Delete(delete_req)
def Publish(
self,
message_bus_ref,
json_message,
avro_message,
event_id,
event_type,
event_source,
event_data,
event_attributes,
destination_enrollment_ref,
):
"""Publish a Cloud Event to a MessageBus.
Args:
message_bus_ref: Resource, the message bus to publish to.
json_message: str, the json string to publish.
avro_message: byte, the avro payload to publish.
event_id: str, the id of the event.
event_type: str, the type of the event.
event_source: str, the source of the event.
event_data: str, the data of the event.
event_attributes: dict, the attributes of the event.
destination_enrollment_ref: Resource, the enrollment to deliver the event
to.
"""
publish_req = self._publishing_messages.EventarcpublishingProjectsLocationsMessageBusesPublishRequest(
messageBus=message_bus_ref.RelativeName(),
googleCloudEventarcPublishingV1PublishRequest=self._publishing_messages.GoogleCloudEventarcPublishingV1PublishRequest(
protoMessage=self._BuildCloudEventProtoMessage(
event_id, event_type, event_source, event_data, event_attributes
),
avroMessage=avro_message,
jsonMessage=json_message,
destinationEnrollment=(
destination_enrollment_ref.RelativeName()
if destination_enrollment_ref
else None
),
),
)
# GoogleCloudEventarcPublishingV1PublishEventsResponse
self._publishing_service.Publish(publish_req)
def ListEnrollments(self, message_bus_ref, limit, page_size):
"""List available enrollments attached to the specified messageBus."""
list_req = self._messages.EventarcProjectsLocationsMessageBusesListEnrollmentsRequest(
parent=message_bus_ref.RelativeName(), pageSize=page_size
)
return list_pager.YieldFromList(
service=self._service,
method='ListEnrollments',
request=list_req,
field='enrollments',
limit=limit,
batch_size=page_size,
batch_size_attribute='pageSize',
)
def BuildMessageBus(
self, message_bus_ref, logging_config, crypto_key_name, labels
):
logging_config_enum = None
if logging_config is not None:
logging_config_enum = self._messages.LoggingConfig(
logSeverity=self._messages.LoggingConfig.LogSeverityValueValuesEnum(
logging_config
),
)
return self._messages.MessageBus(
name=message_bus_ref.RelativeName(),
loggingConfig=logging_config_enum,
cryptoKeyName=crypto_key_name,
labels=labels,
)
def BuildUpdateMask(
self, logging_config, crypto_key, clear_crypto_key, labels
):
"""Builds an update mask for updating a MessageBus.
Args:
logging_config: bool, whether to update the logging config.
crypto_key: bool, whether to update the crypto key.
clear_crypto_key: bool, whether to clear the crypto key.
labels: bool, whether to update the labels.
Returns:
The update mask as a string.
Raises:
NoFieldsSpecifiedError: No fields are being updated.
"""
update_mask = []
if logging_config:
update_mask.append('loggingConfig')
if crypto_key or clear_crypto_key:
update_mask.append('cryptoKeyName')
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 LabelsValueClass(self):
"""Returns the labels value class."""
return self._messages.MessageBus.LabelsValue
def _BuildCloudEventProtoMessage(
self, event_id, event_type, event_source, event_data, event_attributes
):
if (
event_id is None
or event_type is None
or event_source is None
or event_data is None
):
return None
return self._publishing_messages.GoogleCloudEventarcPublishingV1CloudEvent(
id=event_id,
type=event_type,
source=event_source,
specVersion='1.0',
textData=event_data,
attributes=self._BuildCloudEventAttributes(event_attributes),
)
def _BuildCloudEventAttributes(self, event_attributes):
if event_attributes is None:
return None
return self._publishing_messages.GoogleCloudEventarcPublishingV1CloudEvent.AttributesValue(
additionalProperties=[
self._publishing_messages.GoogleCloudEventarcPublishingV1CloudEvent.AttributesValue.AdditionalProperty(
key=key,
value=self._publishing_messages.GoogleCloudEventarcPublishingV1CloudEventCloudEventAttributeValue(
ceString=value
),
)
for key, value in event_attributes.items()
]
)
@property
def _resource_label_plural(self):
return 'message-buses'

View File

@@ -0,0 +1,530 @@
# -*- coding: utf-8 -*- #
# Copyright 2024 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 MessageBuses 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.core import exceptions
from googlecloudsdk.core import log
from googlecloudsdk.core import resources
NO_NA_FOR_HTTP_ENDPOINTS_WARNING = """\
Specifying a network attachment for a pipeline with an HTTP endpoint is not GA
and the pipeline will be limited to pre-GA features.
"""
class NoFieldsSpecifiedError(exceptions.Error):
"""Error when no fields were specified for a Patch operation."""
class InvalidDestinationsArgumentError(exceptions.Error):
"""Error when the pipeline's destinations argument is invalid."""
def GetPipelineURI(resource):
pipelines = resources.REGISTRY.ParseRelativeName(
resource.name, collection='eventarc.projects.locations.pipelines'
)
return pipelines.SelfLink()
class PipelineClientV1(base.EventarcClientBase):
"""Pipeline Client for interaction with v1 of Eventarc Pipelines API."""
def __init__(self):
super(PipelineClientV1, self).__init__(
common.API_NAME, common.API_VERSION_1, 'pipeline'
)
# Eventarc Client
client = apis.GetClientInstance(common.API_NAME, common.API_VERSION_1)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations_pipelines
def Create(self, pipeline_ref, pipeline_message, dry_run=False):
"""Creates a Pipeline.
Args:
pipeline_ref: Resource, the Pipeline to create.
pipeline_message: Pipeline, the pipeline message that holds pipeline's
name, destinations, mediations, input payload format, logging config,
retry policy, crypto key name, etc.
dry_run: If set, the changes will not be committed, only validated
Returns:
A long-running operation for create.
"""
create_req = self._messages.EventarcProjectsLocationsPipelinesCreateRequest(
parent=pipeline_ref.Parent().RelativeName(),
pipeline=pipeline_message,
pipelineId=pipeline_ref.Name(),
validateOnly=dry_run,
)
return self._service.Create(create_req)
def List(self, location_ref, limit, page_size):
"""List available pipelines in location.
Args:
location_ref: Resource, the location to list Pipelines 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 Pipelines in the location.
"""
list_req = self._messages.EventarcProjectsLocationsPipelinesListRequest(
parent=location_ref.RelativeName(), pageSize=page_size
)
return list_pager.YieldFromList(
service=self._service,
request=list_req,
field='pipelines',
limit=limit,
batch_size=page_size,
batch_size_attribute='pageSize',
)
def Get(self, pipeline_ref):
"""Gets the requested Pipeline.
Args:
pipeline_ref: Resource, the Pipeline to get.
Returns:
The Pipeline message.
"""
get_req = self._messages.EventarcProjectsLocationsPipelinesGetRequest(
name=pipeline_ref.RelativeName()
)
return self._service.Get(get_req)
def Patch(self, pipeline_ref, pipeline_message, update_mask):
"""Updates the specified Pipeline.
Args:
pipeline_ref: Resource, the Pipeline to update.
pipeline_message: Pipeline, the pipeline message that holds pipeline's
name, destinations, mediations, input payload format, logging config,
retry policy, crypto key name, etc.
update_mask: str, a comma-separated list of Pipeline fields to update.
Returns:
A long-running operation for update.
"""
patch_req = self._messages.EventarcProjectsLocationsPipelinesPatchRequest(
name=pipeline_ref.RelativeName(),
pipeline=pipeline_message,
updateMask=update_mask,
)
return self._service.Patch(patch_req)
def Delete(self, pipeline_ref):
"""Deletes the specified Pipeline.
Args:
pipeline_ref: Resource, the Pipeline to delete.
Returns:
A long-running operation for delete.
"""
delete_req = self._messages.EventarcProjectsLocationsPipelinesDeleteRequest(
name=pipeline_ref.RelativeName()
)
return self._service.Delete(delete_req)
def BuildPipeline(
self,
pipeline_ref,
destinations,
input_payload_format_json,
input_payload_format_avro_schema_definition,
input_payload_format_protobuf_schema_definition,
mediations,
logging_config,
max_retry_attempts,
min_retry_delay,
max_retry_delay,
crypto_key_name,
labels,
):
return self._messages.Pipeline(
name=pipeline_ref.RelativeName(),
destinations=self._BuildDestinations(pipeline_ref, destinations),
inputPayloadFormat=self._BuildInputPayloadFormat(
input_payload_format_json,
input_payload_format_avro_schema_definition,
input_payload_format_protobuf_schema_definition,
),
mediations=self._BuildMediations(mediations),
loggingConfig=self._BuildLoggingConfig(logging_config),
retryPolicy=self._BuildRetryPolicy(
max_retry_attempts, min_retry_delay, max_retry_delay
),
cryptoKeyName=crypto_key_name,
labels=labels,
)
def BuildUpdateMask(
self,
destinations,
input_payload_format_json,
input_payload_format_avro_schema_definition,
input_payload_format_protobuf_schema_definition,
mediations,
logging_config,
max_retry_attempts,
min_retry_delay,
max_retry_delay,
crypto_key,
clear_crypto_key,
labels,
):
"""Builds an update mask for updating a pipeline.
Args:
destinations: bool, whether to update the destinations.
input_payload_format_json: bool, whether to update the
input_payload_format_json.
input_payload_format_avro_schema_definition: bool, whether to update the
input_payload_format_avro_schema_definition.
input_payload_format_protobuf_schema_definition: bool, whether to update
the input_payload_format_protobuf_schema_definition.
mediations: bool, whether to update the mediations.
logging_config: bool, whether to update the logging_config.
max_retry_attempts: bool, whether to update the max_retry_attempts.
min_retry_delay: bool, whether to update the min_retry_delay.
max_retry_delay: bool, whether to update the max_retry_delay.
crypto_key: bool, whether to update the crypto_key.
clear_crypto_key: bool, whether to clear the crypto_key.
labels: bool, whether to update the labels.
Returns:
The update mask as a string.
Raises:
NoFieldsSpecifiedError: No fields are being updated.
"""
update_mask = []
if destinations:
update_mask.append('destinations')
if (
input_payload_format_json
or input_payload_format_avro_schema_definition
or input_payload_format_protobuf_schema_definition
):
update_mask.append('inputPayloadFormat')
if mediations:
update_mask.append('mediations')
if logging_config:
update_mask.append('loggingConfig')
if max_retry_attempts or min_retry_delay or max_retry_delay:
update_mask.append('retryPolicy')
if crypto_key or clear_crypto_key:
update_mask.append('cryptoKeyName')
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 LabelsValueClass(self):
return self._messages.Pipeline.LabelsValue
def _BuildDestinations(self, pipeline_ref, destinations):
if destinations is None:
return []
return [self._BuildDestination(pipeline_ref, d) for d in destinations]
def _BuildDestination(self, pipeline_ref, destination):
http_endpoint = self._BuildDestinationHttpEndpoint(destination)
workflow = self._BuildDestinationWorkflow(pipeline_ref, destination)
message_bus = self._BuildDestinationMessageBus(pipeline_ref, destination)
pubsub_topic = self._BuildDestinationPubsubTopic(pipeline_ref, destination)
if (http_endpoint is not None) + (workflow is not None) + (
message_bus is not None
) + (pubsub_topic is not None) != 1:
raise InvalidDestinationsArgumentError(
'Exactly one of http_endpoint_uri, workflow, message_bus, or'
' pubsub_topic must be set'
)
if destination.get(
'http_endpoint_message_binding_template'
) is not None and (workflow or message_bus or pubsub_topic):
raise InvalidDestinationsArgumentError(
'http_endpoint_message_binding_template cannot be set when workflow,'
' message_bus, or pubsub_topic is set'
)
if destination.get('http_endpoint_uri') and destination.get('project'):
raise InvalidDestinationsArgumentError(
'project cannot be set when http_endpoint_uri is set'
)
if destination.get('http_endpoint_uri') and destination.get('location'):
raise InvalidDestinationsArgumentError(
'location cannot be set when http_endpoint_uri is set'
)
return self._messages.GoogleCloudEventarcV1PipelineDestination(
httpEndpoint=http_endpoint,
workflow=workflow,
messageBus=message_bus,
topic=pubsub_topic,
networkConfig=self._BuildDestinationNetworkConfig(
pipeline_ref, destination
),
authenticationConfig=self._BuildDestinationAuthenticationConfig(
destination
),
outputPayloadFormat=self._BuildDestinationOutputPayloadFormat(
destination
),
)
def _BuildDestinationHttpEndpoint(self, destination):
if destination.get('http_endpoint_uri') is None:
return None
return self._messages.GoogleCloudEventarcV1PipelineDestinationHttpEndpoint(
uri=destination.get('http_endpoint_uri'),
messageBindingTemplate=destination.get(
'http_endpoint_message_binding_template'
),
)
def _BuildDestinationWorkflow(self, pipeline_ref, destination):
if destination.get('workflow') is None:
return None
project = destination.get('project') or pipeline_ref.projectsId
location = destination.get('location') or pipeline_ref.locationsId
return f'projects/{project}/locations/{location}/workflows/{destination.get("workflow")}'
def _BuildDestinationMessageBus(self, pipeline_ref, destination):
if destination.get('message_bus') is None:
return None
project = destination.get('project') or pipeline_ref.projectsId
location = destination.get('location') or pipeline_ref.locationsId
return f'projects/{project}/locations/{location}/messageBuses/{destination.get("message_bus")}'
def _BuildDestinationPubsubTopic(self, pipeline_ref, destination):
if destination.get('pubsub_topic') is None:
return None
project = destination.get('project') or pipeline_ref.projectsId
return f'projects/{project}/topics/{destination.get("pubsub_topic")}'
def _BuildDestinationNetworkConfig(self, pipeline_ref, destination):
if destination.get('http_endpoint_uri') is not None:
# Network attachments are optional for HTTP destinations.
if destination.get('network_attachment') is not None:
log.warning(NO_NA_FOR_HTTP_ENDPOINTS_WARNING)
return self._messages.GoogleCloudEventarcV1PipelineDestinationNetworkConfig(
networkAttachment=f'projects/{pipeline_ref.projectsId}/regions/{pipeline_ref.locationsId}/networkAttachments/{destination.get("network_attachment")}',
)
return None
# Workflows, Pub/Sub topic and Message Bus destinations cannot have a
# network attachment.
if destination.get('network_attachment') is not None:
raise InvalidDestinationsArgumentError(
'network_attachment must not be set'
)
return None
def _BuildDestinationAuthenticationConfig(self, destination):
google_oidc = self._BuildDestinationAuthenticationGoogleOidc(destination)
oauth_token = self._BuildDestinationAuthenticationOauthToken(destination)
if (google_oidc is not None) + (oauth_token is not None) > 1:
raise InvalidDestinationsArgumentError(
'At most one of google_oidc_authentication_service_account or'
' oauth_token_authentication_service_account can be set'
)
if destination.get('oauth_token_authentication_scope'):
if google_oidc:
raise InvalidDestinationsArgumentError(
'oauth_token_authentication_scope cannot be set when'
' google_oidc_authentication_service_account is set'
)
if oauth_token is None:
raise InvalidDestinationsArgumentError(
'oauth_token_authentication_scope cannot be set when'
' oauth_token_authentication_service_account is not set'
)
if destination.get('google_oidc_authentication_audience'):
if oauth_token:
raise InvalidDestinationsArgumentError(
'google_oidc_authentication_audience cannot be set when'
' oauth_token_authentication_service_account is set'
)
if google_oidc is None:
raise InvalidDestinationsArgumentError(
'google_oidc_authentication_audience cannot be set when'
' google_oidc_authentication_service_account is not set'
)
if google_oidc is None and oauth_token is None:
return None
return self._messages.GoogleCloudEventarcV1PipelineDestinationAuthenticationConfig(
googleOidc=google_oidc,
oauthToken=oauth_token,
)
def _BuildDestinationAuthenticationGoogleOidc(self, destination):
if destination.get('google_oidc_authentication_service_account') is None:
return None
return self._messages.GoogleCloudEventarcV1PipelineDestinationAuthenticationConfigOidcToken(
serviceAccount=destination.get(
'google_oidc_authentication_service_account'
),
audience=destination.get('google_oidc_authentication_audience'),
)
def _BuildDestinationAuthenticationOauthToken(self, destination):
if destination.get('oauth_token_authentication_service_account') is None:
return None
return self._messages.GoogleCloudEventarcV1PipelineDestinationAuthenticationConfigOAuthToken(
serviceAccount=destination.get(
'oauth_token_authentication_service_account'
),
scope=destination.get('oauth_token_authentication_scope'),
)
def _BuildDestinationOutputPayloadFormat(self, destination):
json = self._BuildDestinationOutputPayloadFormatJsonFormat(destination)
avro = self._BuildDestinationOutputPayloadFormatAvroFormat(destination)
protobuf = self._BuildDestinationOutputPayloadFormatProtobufFormat(
destination
)
if (json is not None) + (avro is not None) + (protobuf is not None) > 1:
raise InvalidDestinationsArgumentError(
'At most one of output_payload_format_json,'
' output_payload_format_avro_schema_definition, or'
' output_payload_format_protobuf_schema_definition can be set'
)
if json is None and avro is None and protobuf is None:
return None
return self._messages.GoogleCloudEventarcV1PipelineMessagePayloadFormat(
json=json,
avro=avro,
protobuf=protobuf,
)
def _BuildDestinationOutputPayloadFormatAvroFormat(self, destination):
if destination.get('output_payload_format_avro_schema_definition') is None:
return None
return self._messages.GoogleCloudEventarcV1PipelineMessagePayloadFormatAvroFormat(
schemaDefinition=destination.get(
'output_payload_format_avro_schema_definition'
)
)
def _BuildDestinationOutputPayloadFormatProtobufFormat(self, destination):
if (
destination.get('output_payload_format_protobuf_schema_definition')
is None
):
return None
return self._messages.GoogleCloudEventarcV1PipelineMessagePayloadFormatProtobufFormat(
schemaDefinition=destination.get(
'output_payload_format_protobuf_schema_definition'
)
)
def _BuildDestinationOutputPayloadFormatJsonFormat(self, destination):
if 'output_payload_format_json' not in destination:
return None
return (
self._messages.GoogleCloudEventarcV1PipelineMessagePayloadFormatJsonFormat()
)
def _BuildInputPayloadFormat(
self, json, avro_schema_definition, protobuf_schema_definition
):
if (
json is None
and avro_schema_definition is None
and protobuf_schema_definition is None
):
return None
return self._messages.GoogleCloudEventarcV1PipelineMessagePayloadFormat(
json=self._BuildInputPayloadFormatJsonFormat(json),
avro=self._BuildInputPayloadFormatAvroFormat(avro_schema_definition),
protobuf=self._BuildInputPayloadFormatProtobufFormat(
protobuf_schema_definition
),
)
def _BuildInputPayloadFormatAvroFormat(self, schema_definition):
if schema_definition is None:
return None
return self._messages.GoogleCloudEventarcV1PipelineMessagePayloadFormatAvroFormat(
schemaDefinition=schema_definition
)
def _BuildInputPayloadFormatProtobufFormat(self, schema_definition):
if schema_definition is None:
return None
return self._messages.GoogleCloudEventarcV1PipelineMessagePayloadFormatProtobufFormat(
schemaDefinition=schema_definition
)
def _BuildInputPayloadFormatJsonFormat(self, json):
if json is None:
return None
return (
self._messages.GoogleCloudEventarcV1PipelineMessagePayloadFormatJsonFormat()
)
def _BuildMediations(self, mediations):
if mediations is None:
return []
return [
self._messages.GoogleCloudEventarcV1PipelineMediation(
transformation=self._messages.GoogleCloudEventarcV1PipelineMediationTransformation(
transformationTemplate=m.get('transformation_template'),
),
)
for m in mediations
]
def _BuildLoggingConfig(self, logging_config):
if logging_config is None:
return None
return self._messages.LoggingConfig(
logSeverity=self._messages.LoggingConfig.LogSeverityValueValuesEnum(
logging_config
),
)
def _BuildRetryPolicy(
self, max_retry_attempts, min_retry_delay, max_retry_delay
):
if (
max_retry_attempts is None
and min_retry_delay is None
and max_retry_delay is None
):
return None
return self._messages.GoogleCloudEventarcV1PipelineRetryPolicy(
maxAttempts=max_retry_attempts,
minRetryDelay=min_retry_delay,
maxRetryDelay=max_retry_delay,
)

View File

@@ -0,0 +1,76 @@
# -*- coding: utf-8 -*- #
# Copyright 2021 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for Eventarc Providers 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 common
from googlecloudsdk.api_lib.util import apis
from googlecloudsdk.core import resources
def GetProvidersURI(resource):
provider = resources.REGISTRY.ParseRelativeName(
resource.name, collection='eventarc.projects.locations.providers')
return provider.SelfLink()
class ProvidersClient(object):
"""Client for event providers in Eventarc API."""
def __init__(self, release_track):
api_version = common.GetApiVersion(release_track)
client = apis.GetClientInstance(common.API_NAME, api_version)
self._messages = client.MESSAGES_MODULE
self._service = client.projects_locations_providers
def List(self, location, limit, page_size):
"""Lists event providers in a given location.
Args:
location: str, the relative name of the location to list event providers
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 event providers in the location.
"""
list_req = self._messages.EventarcProjectsLocationsProvidersListRequest(
parent=location, pageSize=page_size)
return list_pager.YieldFromList(
self._service,
list_req,
field='providers',
batch_size=page_size,
limit=limit,
batch_size_attribute='pageSize')
def Get(self, provider_ref):
"""Gets a Provider.
Args:
provider_ref: Resource, the Provider to get.
Returns:
The Provider message.
"""
get_req = self._messages.EventarcProjectsLocationsProvidersGetRequest(
name=provider_ref.RelativeName())
return self._service.Get(get_req)

View File

@@ -0,0 +1,449 @@
# -*- 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