# -*- coding: utf-8 -*- # # Copyright 2019 Google LLC. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Import backend service 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.compute import base_classes from googlecloudsdk.calliope import base from googlecloudsdk.command_lib.compute import exceptions from googlecloudsdk.command_lib.compute import flags as compute_flags from googlecloudsdk.command_lib.compute.backend_services import backend_services_utils from googlecloudsdk.command_lib.compute.backend_services import flags from googlecloudsdk.command_lib.export import util as export_util from googlecloudsdk.core import yaml_validator from googlecloudsdk.core.console import console_io DETAILED_HELP = { 'DESCRIPTION': """\ Imports a backend service's configuration from a file. """, 'EXAMPLES': """\ A backend service can be imported by running: $ {command} NAME --source= --global """ } @base.ReleaseTracks(base.ReleaseTrack.GA) @base.UniverseCompatible class ImportGA(base.UpdateCommand): """Import a backend service. If the specified backend service already exists, it will be overwritten. Otherwise, a new backend service will be created. To edit a backend service you can export the backend service to a file, edit its configuration, and then import the new configuration. """ detailed_help = DETAILED_HELP _support_negative_cache = False @classmethod def GetApiVersion(cls): """Returns the API version based on the release track.""" if cls.ReleaseTrack() == base.ReleaseTrack.ALPHA: return 'alpha' elif cls.ReleaseTrack() == base.ReleaseTrack.BETA: return 'beta' return 'v1' @classmethod def GetSchemaPath(cls, for_help=False): """Returns the resource schema path.""" return export_util.GetSchemaPath( 'compute', cls.GetApiVersion(), 'BackendService', for_help=for_help) @classmethod def Args(cls, parser): flags.GLOBAL_REGIONAL_BACKEND_SERVICE_ARG.AddArgument( parser, operation_type='import') export_util.AddImportFlags(parser, cls.GetSchemaPath(for_help=True)) def SendPatchRequest(self, client, resources, backend_service_ref, replacement): """Sends a Backend Services patch request and waits for the operation to finish. Args: client: The API client. resources: The resource parser. backend_service_ref: The backend service reference. replacement: The backend service to patch with. Returns: The operation result. """ if backend_service_ref.Collection() == 'compute.regionBackendServices': service = client.apitools_client.regionBackendServices operation = client.apitools_client.regionBackendServices.Patch( client.messages.ComputeRegionBackendServicesPatchRequest( project=backend_service_ref.project, region=backend_service_ref.region, backendService=backend_service_ref.Name(), backendServiceResource=replacement)) else: service = client.apitools_client.backendServices operation = client.apitools_client.backendServices.Patch( client.messages.ComputeBackendServicesPatchRequest( project=backend_service_ref.project, backendService=backend_service_ref.Name(), backendServiceResource=replacement)) return backend_services_utils.WaitForOperation(resources, service, operation, backend_service_ref, 'Updating backend service') def SendInsertRequest(self, client, resources, backend_service_ref, backend_service): """Sends a Backend Services insert request and waits for the operation to finish. Args: client: The API client. resources: The resource parser. backend_service_ref: The backend service reference. backend_service: The backend service to insert. Returns: The operation result. """ if backend_service_ref.Collection() == 'compute.regionBackendServices': service = client.apitools_client.regionBackendServices operation = client.apitools_client.regionBackendServices.Insert( client.messages.ComputeRegionBackendServicesInsertRequest( project=backend_service_ref.project, region=backend_service_ref.region, backendService=backend_service)) else: service = client.apitools_client.backendServices operation = client.apitools_client.backendServices.Insert( client.messages.ComputeBackendServicesInsertRequest( project=backend_service_ref.project, backendService=backend_service)) return backend_services_utils.WaitForOperation(resources, service, operation, backend_service_ref, 'Creating backend service') def GetClearedFieldList(self, backend_service): """Retrieves a list of fields to clear for the backend service being inserted. Args: backend_service: The backend service being inserted. Returns: The the list of fields to clear for a GA resource. """ # Unspecified fields are assumed to be cleared. cleared_fields = [] # TODO(b/321258406) This entire section ought to use a library which # walks the schema and compares it to the resource being imported # and the utility must be tested to verify that deeply nested structures # are creating field masks appropriately. if not backend_service.securitySettings: cleared_fields.append('securitySettings') if not backend_service.localityLbPolicy: cleared_fields.append('localityLbPolicy') if not backend_service.localityLbPolicies: cleared_fields.append('localityLbPolicies') if not backend_service.circuitBreakers: cleared_fields.append('circuitBreakers') if not backend_service.consistentHash: cleared_fields.append('consistentHash') if not backend_service.outlierDetection: cleared_fields.append('outlierDetection') if not backend_service.customRequestHeaders: cleared_fields.append('customRequestHeaders') if not backend_service.customResponseHeaders: cleared_fields.append('customResponseHeaders') if backend_service.cdnPolicy: cdn_policy = backend_service.cdnPolicy if cdn_policy.defaultTtl is None: cleared_fields.append('cdnPolicy.defaultTtl') if cdn_policy.clientTtl is None: cleared_fields.append('cdnPolicy.clientTtl') if cdn_policy.maxTtl is None: cleared_fields.append('cdnPolicy.maxTtl') if not cdn_policy.negativeCachingPolicy: cleared_fields.append('cdnPolicy.negativeCachingPolicy') if not cdn_policy.bypassCacheOnRequestHeaders: cleared_fields.append('cdnPolicy.bypassCacheOnRequestHeaders') if cdn_policy.serveWhileStale is None: cleared_fields.append('cdnPolicy.serveWhileStale') if cdn_policy.requestCoalescing is None: cleared_fields.append('cdnPolicy.requestCoalescing') else: cleared_fields.append('cdnPolicy') if not backend_service.haPolicy: cleared_fields.append('haPolicy') else: ha_policy = backend_service.haPolicy if not ha_policy.fastIPMove: cleared_fields.append('haPolicy.fastIPMove') if not ha_policy.leader: cleared_fields.append('haPolicy.leader') else: leader = ha_policy.leader if not leader.backendGroup: cleared_fields.append('haPolicy.leader.backendGroup') if leader.networkEndpoint and not leader.networkEndpoint.instance: cleared_fields.append('haPolicy.leader.networkEndpoint.instance') elif not leader.networkEndpoint: cleared_fields.append('haPolicy.leader.networkEndpoint') return cleared_fields def Run(self, args): holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) client = holder.client resources = holder.resources backend_service_ref = ( flags.GLOBAL_REGIONAL_BACKEND_SERVICE_ARG.ResolveAsResource( args, resources, scope_lister=compute_flags.GetDefaultScopeLister(client))) data = console_io.ReadFromFileOrStdin(args.source or '-', binary=False) try: backend_service = export_util.Import( message_type=client.messages.BackendService, stream=data, schema_path=self.GetSchemaPath()) except yaml_validator.ValidationError as e: raise exceptions.ValidationError(str(e)) # Get existing backend service. try: backend_service_old = backend_services_utils.SendGetRequest( client, backend_service_ref) except apitools_exceptions.HttpError as error: if error.status_code != 404: raise error # Backend service does not exist, create a new one. return self.SendInsertRequest(client, resources, backend_service_ref, backend_service) # No change, do not send requests to server. if backend_service_old == backend_service: return console_io.PromptContinue( message=('Backend Service [{0}] will be overwritten.').format( backend_service_ref.Name()), cancel_on_no=True) # populate id and fingerprint fields. These two fields are manually # removed from the schema files. backend_service.id = backend_service_old.id backend_service.fingerprint = backend_service_old.fingerprint # Unspecified fields are assumed to be cleared. cleared_fields = self.GetClearedFieldList(backend_service) with client.apitools_client.IncludeFields(cleared_fields): return self.SendPatchRequest(client, resources, backend_service_ref, backend_service) @base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.ALPHA) class ImportAlphaBeta(ImportGA): """Import a backend service. If the specified backend service already exists, it will be overwritten. Otherwise, a new backend service will be created. To edit a backend service you can export the backend service to a file, edit its configuration, and then import the new configuration. """