# -*- 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. """Command for updating service attachments.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from apitools.base.py import encoding from googlecloudsdk.api_lib.compute import base_classes from googlecloudsdk.calliope import base from googlecloudsdk.command_lib.compute import flags as compute_flags from googlecloudsdk.command_lib.compute import scope as compute_scope from googlecloudsdk.command_lib.compute.networks.subnets import flags as subnetwork_flags from googlecloudsdk.command_lib.compute.service_attachments import flags from googlecloudsdk.command_lib.compute.service_attachments import service_attachments_utils def _DetailedHelp(): return { 'brief': 'Update a Google Compute Engine service attachment.', 'DESCRIPTION': """\ *{command}* is used to update service attachments. A service producer creates service attachments to make a service available to consumers. Service consumers use Private Service Connect endpoints to privately forward traffic to the service attachment. """, 'EXAMPLES': """\ To update the connection policy of a service attachment to be ACCEPT_MANUAL, run: $ {command} SERVICE_ATTACHMENT_NAME --region=us-central1 --connection-preference=ACCEPT_MANUAL To update all supported fields of a service attachment, run: $ {command} SERVICE_ATTACHMENT_NAME --region=us-central1 --connection-preference=ACCEPT_AUTOMATIC --nat-subnets=MY_SUBNET1,MY_SUBNET2 --enable-proxy-protocol --consumer-reject-list=PROJECT_ID1,PROJECT_ID2 --consumer-accept-list=PROJECT_ID3=10,PROJECT_ID4=20 """, } class UpdateHelper(object): """Update a Google Compute Engine service attachment.""" SERVICE_ATTACHMENT_ARG = None NAT_SUBNETWORK_ARG = None def __init__( self, holder, support_target_service_arg, support_endpoint_based_security_arg, ): self._holder = holder self._support_target_service_arg = support_target_service_arg self._support_endpoint_based_security_arg = ( support_endpoint_based_security_arg ) @classmethod def Args( cls, parser, support_target_service_arg, support_endpoint_based_security_arg, ): """Create a Google Compute Engine service attachment. Args: parser: the parser that parses the input from the user. support_target_service_arg: Whether to add arguments for producer forwarding rule. support_endpoint_based_security_arg: Whether to support endpoint based security. cls: Hold onto the definition of a complex argument so it can be used later to process the user's input. flags: Define and register command-line arguments with the argument parser. """ cls.SERVICE_ATTACHMENT_ARG = flags.ServiceAttachmentArgument() cls.SERVICE_ATTACHMENT_ARG.AddArgument(parser, operation_type='update') cls.NAT_SUBNETWORK_ARG = ( subnetwork_flags.SubnetworkArgumentForServiceAttachment(required=False) ) cls.NAT_SUBNETWORK_ARG.AddArgument(parser) flags.AddDescription(parser) if support_target_service_arg: flags.AddTargetServiceArgsForUpdate(parser) flags.AddConnectionPreference(parser, is_update=True) flags.AddEnableProxyProtocolForUpdate(parser) flags.AddReconcileConnectionsForUpdate(parser) flags.AddConsumerRejectList(parser) if support_endpoint_based_security_arg: flags.AddConsumerAcceptList(parser) else: flags.AddConsumerAcceptListOld(parser) flags.AddPropagatedConnectionLimit(parser) def _GetConsumerAcceptList(self, args, holder): if self._support_endpoint_based_security_arg: return service_attachments_utils.GetConsumerAcceptListWithEndpointBasedSecurity( args, holder.client.messages ) return service_attachments_utils.GetConsumerAcceptList( args, holder.client.messages ) def _GetProjectOrNetworkOrEndpointBasedOnArg(self, consumer_limit): if self._support_endpoint_based_security_arg: return self._GetProjectOrNetworkOrEndpoint(consumer_limit) return self._GetProjectOrNetwork(consumer_limit) def _GetProjectOrNetwork(self, consumer_limit): if consumer_limit.projectIdOrNum is not None: return (consumer_limit.projectIdOrNum, consumer_limit.connectionLimit) return (consumer_limit.networkUrl, consumer_limit.connectionLimit) def _GetProjectOrNetworkOrEndpoint(self, consumer_limit): if consumer_limit.projectIdOrNum is not None: return (consumer_limit.projectIdOrNum, consumer_limit.connectionLimit) elif consumer_limit.endpointUrl is not None: return (consumer_limit.endpointUrl, consumer_limit.connectionLimit) return (consumer_limit.networkUrl, consumer_limit.connectionLimit) def _GetOldResource(self, client, service_attachment_ref): """Returns the existing ServiceAttachment resource.""" request = client.messages.ComputeServiceAttachmentsGetRequest( **service_attachment_ref.AsDict()) collection = client.apitools_client.serviceAttachments return client.MakeRequests([(collection, 'Get', request)])[0] def _GetPatchRequest(self, client, service_attachment_ref, replacement): """Returns a request to update the service attachment.""" return (client.apitools_client.serviceAttachments, 'Patch', client.messages.ComputeServiceAttachmentsPatchRequest( project=service_attachment_ref.project, region=service_attachment_ref.region, serviceAttachment=service_attachment_ref.Name(), serviceAttachmentResource=replacement)) def _GetNatSubnets(self, holder, args): """Returns nat subnetwork urls from the argument.""" nat_subnetwork_refs = self.NAT_SUBNETWORK_ARG.ResolveAsResource( args, holder.resources, default_scope=compute_scope.ScopeEnum.REGION, scope_lister=compute_flags.GetDefaultScopeLister(holder.client)) nat_subnetworks = [ nat_subnetwork_ref.SelfLink() for nat_subnetwork_ref in nat_subnetwork_refs ] return nat_subnetworks def _Modify(self, holder, args, old_resource, cleared_fields): """Returns the updated service attachment.""" replacement = encoding.CopyProtoMessage(old_resource) is_updated = False if self._support_target_service_arg and args.IsSpecified('target_service'): is_updated = True replacement.targetService = args.target_service if args.IsSpecified('description'): if args.description != old_resource.description: replacement.description = args.description is_updated = True if args.IsSpecified('connection_preference'): new_connection_preference = ( service_attachments_utils.GetConnectionPreference( args, holder.client.messages ) ) if new_connection_preference != old_resource.connectionPreference: replacement.connectionPreference = new_connection_preference is_updated = True if args.IsSpecified('enable_proxy_protocol'): if args.enable_proxy_protocol != old_resource.enableProxyProtocol: replacement.enableProxyProtocol = args.enable_proxy_protocol is_updated = True if args.IsSpecified('nat_subnets'): new_nat_subnets = sorted(self._GetNatSubnets(holder, args)) if old_resource.natSubnets is None or new_nat_subnets != sorted( old_resource.natSubnets): replacement.natSubnets = new_nat_subnets is_updated = True if args.IsSpecified('consumer_reject_list'): new_reject_list = sorted(args.consumer_reject_list) if old_resource.consumerRejectLists is None or new_reject_list != sorted( old_resource.consumerRejectLists): replacement.consumerRejectLists = new_reject_list is_updated = True if not new_reject_list: # The user can clear up the reject list cleared_fields.append('consumerRejectLists') if args.IsSpecified('consumer_accept_list'): consumer_accept_list = self._GetConsumerAcceptList(args, holder) new_accept_list = sorted( consumer_accept_list, key=self._GetProjectOrNetworkOrEndpointBasedOnArg, ) if old_resource.consumerAcceptLists is None or new_accept_list != sorted( old_resource.consumerAcceptLists, key=self._GetProjectOrNetworkOrEndpointBasedOnArg, ): replacement.consumerAcceptLists = new_accept_list is_updated = True if not new_accept_list: # The user can clear up the accept list cleared_fields.append('consumerAcceptLists') if args.IsSpecified('reconcile_connections'): if args.reconcile_connections != old_resource.reconcileConnections: replacement.reconcileConnections = args.reconcile_connections is_updated = True if args.IsSpecified('propagated_connection_limit'): if ( args.propagated_connection_limit != old_resource.propagatedConnectionLimit ): replacement.propagatedConnectionLimit = args.propagated_connection_limit is_updated = True if is_updated: return replacement return None def Run(self, args): """Issue a service attachment PATCH request.""" client = self._holder.client service_attachment_ref = self.SERVICE_ATTACHMENT_ARG.ResolveAsResource( args, self._holder.resources, default_scope=compute_scope.ScopeEnum.REGION, ) old_resource = self._GetOldResource(client, service_attachment_ref) cleared_fields = [] replacement = self._Modify(self._holder, args, old_resource, cleared_fields) if replacement is None: return old_resource with client.apitools_client.IncludeFields(cleared_fields): return client.MakeRequests( [self._GetPatchRequest(client, service_attachment_ref, replacement)]) @base.UniverseCompatible @base.ReleaseTracks(base.ReleaseTrack.GA) class Update(base.UpdateCommand): """Update a Google Compute Engine service attachment.""" _support_target_service_arg = False _support_endpoint_based_security_arg = False detailed_help = _DetailedHelp() @classmethod def Args(cls, parser): UpdateHelper.Args( parser, cls._support_target_service_arg, cls._support_endpoint_based_security_arg, ) def Run(self, args): """Issue a service attachment PATCH request.""" holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) return UpdateHelper( holder, self._support_target_service_arg, self._support_endpoint_based_security_arg, ).Run(args) @base.ReleaseTracks(base.ReleaseTrack.BETA) class UpdateBeta(Update): """Update a Google Compute Engine service attachment.""" _support_endpoint_based_security_arg = True detailed_help = _DetailedHelp() @base.ReleaseTracks(base.ReleaseTrack.ALPHA) class UpdateAlpha(UpdateBeta): """Update a Google Compute Engine service attachment.""" _support_target_service_arg = True detailed_help = _DetailedHelp()