431 lines
16 KiB
Python
431 lines
16 KiB
Python
# -*- 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 URL maps 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 as compute_exceptions
|
|
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.url_maps import flags
|
|
from googlecloudsdk.command_lib.compute.url_maps import url_maps_utils
|
|
from googlecloudsdk.command_lib.export import util as export_util
|
|
from googlecloudsdk.core import log
|
|
from googlecloudsdk.core import yaml_validator
|
|
from googlecloudsdk.core.console import console_io
|
|
|
|
|
|
def _DetailedHelp():
|
|
return {
|
|
'brief':
|
|
'Import a URL map.',
|
|
'DESCRIPTION':
|
|
"""\
|
|
Imports a URL map's configuration from a file.
|
|
""",
|
|
'EXAMPLES':
|
|
"""\
|
|
A URL map can be imported by running:
|
|
|
|
$ {command} NAME --source=<path-to-file>
|
|
"""
|
|
}
|
|
|
|
|
|
def _GetApiVersion(release_track):
|
|
"""Returns the API version based on the release track."""
|
|
if release_track == base.ReleaseTrack.ALPHA:
|
|
return 'alpha'
|
|
elif release_track == base.ReleaseTrack.BETA:
|
|
return 'beta'
|
|
return 'v1'
|
|
|
|
|
|
def _GetSchemaPath(release_track, for_help=False):
|
|
"""Returns the resource schema path."""
|
|
return export_util.GetSchemaPath(
|
|
'compute', _GetApiVersion(release_track), 'UrlMap', for_help=for_help)
|
|
|
|
|
|
def _SendPatchRequest(client, resources, url_map_ref, replacement):
|
|
"""Sends a URL map patch request and waits for the operation to finish.
|
|
|
|
Args:
|
|
client: The API client.
|
|
resources: The resource parser.
|
|
url_map_ref: The URL map reference.
|
|
replacement: The URL map to patch with.
|
|
|
|
Returns:
|
|
The operation result.
|
|
"""
|
|
if url_map_ref.Collection() == 'compute.regionUrlMaps':
|
|
service = client.apitools_client.regionUrlMaps
|
|
operation = client.apitools_client.regionUrlMaps.Patch(
|
|
client.messages.ComputeRegionUrlMapsPatchRequest(
|
|
project=url_map_ref.project,
|
|
region=url_map_ref.region,
|
|
urlMap=url_map_ref.Name(),
|
|
urlMapResource=replacement))
|
|
else:
|
|
service = client.apitools_client.urlMaps
|
|
operation = client.apitools_client.urlMaps.Patch(
|
|
client.messages.ComputeUrlMapsPatchRequest(
|
|
project=url_map_ref.project,
|
|
urlMap=url_map_ref.Name(),
|
|
urlMapResource=replacement))
|
|
|
|
return url_maps_utils.WaitForOperation(resources, service, operation,
|
|
url_map_ref, 'Updating URL map')
|
|
|
|
|
|
def _SendInsertRequest(client, resources, url_map_ref, url_map):
|
|
"""Sends a URL map insert request and waits for the operation to finish.
|
|
|
|
Args:
|
|
client: The API client.
|
|
resources: The resource parser.
|
|
url_map_ref: The URL map reference.
|
|
url_map: The URL map to insert.
|
|
|
|
Returns:
|
|
The operation result.
|
|
"""
|
|
if url_map_ref.Collection() == 'compute.regionUrlMaps':
|
|
service = client.apitools_client.regionUrlMaps
|
|
operation = client.apitools_client.regionUrlMaps.Insert(
|
|
client.messages.ComputeRegionUrlMapsInsertRequest(
|
|
project=url_map_ref.project,
|
|
region=url_map_ref.region,
|
|
urlMap=url_map))
|
|
else:
|
|
service = client.apitools_client.urlMaps
|
|
operation = client.apitools_client.urlMaps.Insert(
|
|
client.messages.ComputeUrlMapsInsertRequest(
|
|
project=url_map_ref.project, urlMap=url_map))
|
|
|
|
return url_maps_utils.WaitForOperation(resources, service, operation,
|
|
url_map_ref, 'Creating URL map')
|
|
|
|
|
|
def _GetClearedFieldsForDuration(duration, field_prefix):
|
|
"""Gets a list of fields cleared by the user for Duration."""
|
|
cleared_fields = []
|
|
if hasattr(duration, 'seconds'):
|
|
cleared_fields.append(field_prefix + 'seconds')
|
|
if hasattr(duration, 'nanos'):
|
|
cleared_fields.append(field_prefix + 'nanos')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForUrlRewrite(url_rewrite, field_prefix):
|
|
"""Gets a list of fields cleared by the user for UrlRewrite."""
|
|
cleared_fields = []
|
|
if not url_rewrite.pathPrefixRewrite:
|
|
cleared_fields.append(field_prefix + 'pathPrefixRewrite')
|
|
if not url_rewrite.hostRewrite:
|
|
cleared_fields.append(field_prefix + 'hostRewrite')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForRetryPolicy(retry_policy, field_prefix):
|
|
"""Gets a list of fields cleared by the user for RetryPolicy."""
|
|
cleared_fields = []
|
|
if not retry_policy.retryConditions:
|
|
cleared_fields.append(field_prefix + 'retryConditions')
|
|
if hasattr(retry_policy, 'numRetries'):
|
|
cleared_fields.append(field_prefix + 'numRetries')
|
|
if not retry_policy.perTryTimeout:
|
|
cleared_fields.append(field_prefix + 'perTryTimeout')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForDuration(
|
|
retry_policy.perTryTimeout, field_prefix + 'perTryTimeout.')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForRequestMirrorPolicy(mirror_policy, field_prefix):
|
|
"""Gets a list of fields cleared by the user for RequestMirrorPolicy."""
|
|
cleared_fields = []
|
|
if not mirror_policy.backendService:
|
|
cleared_fields.append(field_prefix + 'backendService')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForCorsPolicy(cors_policy, field_prefix):
|
|
"""Gets a list of fields cleared by the user for CorsPolicy."""
|
|
cleared_fields = []
|
|
if not cors_policy.allowOrigins:
|
|
cleared_fields.append(field_prefix + 'allowOrigins')
|
|
if not cors_policy.allowOriginRegexes:
|
|
cleared_fields.append(field_prefix + 'allowOriginRegexes')
|
|
if not cors_policy.allowMethods:
|
|
cleared_fields.append(field_prefix + 'allowMethods')
|
|
if not cors_policy.allowHeaders:
|
|
cleared_fields.append(field_prefix + 'allowHeaders')
|
|
if not cors_policy.exposeHeaders:
|
|
cleared_fields.append(field_prefix + 'exposeHeaders')
|
|
if not cors_policy.maxAge:
|
|
cleared_fields.append(field_prefix + 'maxAge')
|
|
if not cors_policy.allowCredentials:
|
|
cleared_fields.append(field_prefix + 'allowCredentials')
|
|
if not cors_policy.disabled:
|
|
cleared_fields.append(field_prefix + 'disabled')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForFaultDelay(fault_delay, field_prefix):
|
|
"""Gets a list of fields cleared by the user for HttpFaultDelay."""
|
|
cleared_fields = []
|
|
if not fault_delay.fixedDelay:
|
|
cleared_fields.append(field_prefix + 'fixedDelay')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForDuration(
|
|
fault_delay.fixedDelay, field_prefix + 'fixedDelay.')
|
|
if not fault_delay.percentage:
|
|
cleared_fields.append(field_prefix + 'percentage')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForFaultAbort(fault_abort, field_prefix):
|
|
"""Gets a list of fields cleared by the user for HttpFaultAbort."""
|
|
cleared_fields = []
|
|
if not fault_abort.httpStatus:
|
|
cleared_fields.append(field_prefix + 'httpStatus')
|
|
if not fault_abort.percentage:
|
|
cleared_fields.append(field_prefix + 'percentage')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForFaultInjectionPolicy(fault_injection_policy,
|
|
field_prefix):
|
|
"""Gets a list of fields cleared by the user for FaultInjectionPolicy."""
|
|
cleared_fields = []
|
|
if not fault_injection_policy.delay:
|
|
cleared_fields.append(field_prefix + 'delay')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForFaultDelay(
|
|
fault_injection_policy.delay, field_prefix + 'delay.')
|
|
if not fault_injection_policy.abort:
|
|
cleared_fields.append(field_prefix + 'abort')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForFaultAbort(
|
|
fault_injection_policy.abort, field_prefix + 'abort.')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForRoutAction(route_action, field_prefix):
|
|
"""Gets a list of fields cleared by the user for HttpRouteAction."""
|
|
cleared_fields = []
|
|
if not route_action.weightedBackendServices:
|
|
cleared_fields.append(field_prefix + 'weightedBackendServices')
|
|
if not route_action.urlRewrite:
|
|
cleared_fields.append(field_prefix + 'urlRewrite')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForUrlRewrite(
|
|
route_action.urlRewrite, field_prefix + 'urlRewrite.')
|
|
if not route_action.timeout:
|
|
cleared_fields.append(field_prefix + 'timeout')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForDuration(
|
|
route_action.timeout, field_prefix + 'timeout.')
|
|
if not route_action.retryPolicy:
|
|
cleared_fields.append(field_prefix + 'retryPolicy')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForRetryPolicy(
|
|
route_action.retryPolicy, field_prefix + 'retryPolicy.')
|
|
if not route_action.requestMirrorPolicy:
|
|
cleared_fields.append(field_prefix + 'requestMirrorPolicy')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForRequestMirrorPolicy(
|
|
route_action.requestMirrorPolicy, field_prefix + 'requestMirrorPolicy.')
|
|
if not route_action.corsPolicy:
|
|
cleared_fields.append(field_prefix + 'corsPolicy')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForCorsPolicy(
|
|
route_action.corsPolicy, field_prefix + 'corsPolicy.')
|
|
if not route_action.faultInjectionPolicy:
|
|
cleared_fields.append(field_prefix + 'faultInjectionPolicy')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForFaultInjectionPolicy(
|
|
route_action.faultInjectionPolicy,
|
|
field_prefix + 'faultInjectionPolicy.')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForCustomErrorResponsePolicy(
|
|
custom_error_response_policy, field_prefix
|
|
):
|
|
"""Gets a list of fields cleared by the user for CustomErrorResponsePolicy."""
|
|
cleared_fields = []
|
|
if not custom_error_response_policy.errorResponseRules:
|
|
cleared_fields.append(field_prefix + 'errorResponseRules')
|
|
if not custom_error_response_policy.errorService:
|
|
cleared_fields.append(field_prefix + 'errorService')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForUrlRedirect(url_redirect, field_prefix):
|
|
"""Gets a list of fields cleared by the user for UrlRedirect."""
|
|
cleared_fields = []
|
|
if not url_redirect.hostRedirect:
|
|
cleared_fields.append(field_prefix + 'hostRedirect')
|
|
if not url_redirect.pathRedirect:
|
|
cleared_fields.append(field_prefix + 'pathRedirect')
|
|
if not url_redirect.prefixRedirect:
|
|
cleared_fields.append(field_prefix + 'prefixRedirect')
|
|
if not url_redirect.redirectResponseCode:
|
|
cleared_fields.append(field_prefix + 'redirectResponseCode')
|
|
if not url_redirect.httpsRedirect:
|
|
cleared_fields.append(field_prefix + 'httpsRedirect')
|
|
if not url_redirect.stripQuery:
|
|
cleared_fields.append(field_prefix + 'stripQuery')
|
|
return cleared_fields
|
|
|
|
|
|
def _GetClearedFieldsForHeaderAction(header_action, field_prefix):
|
|
"""Gets a list of fields cleared by the user for HeaderAction."""
|
|
cleared_fields = []
|
|
if not header_action.requestHeadersToRemove:
|
|
cleared_fields.append(field_prefix + 'requestHeadersToRemove')
|
|
if not header_action.requestHeadersToAdd:
|
|
cleared_fields.append(field_prefix + 'requestHeadersToAdd')
|
|
if not header_action.responseHeadersToRemove:
|
|
cleared_fields.append(field_prefix + 'responseHeadersToRemove')
|
|
if not header_action.responseHeadersToAdd:
|
|
cleared_fields.append(field_prefix + 'responseHeadersToAdd')
|
|
return cleared_fields
|
|
|
|
|
|
def _Run(args, holder, url_map_arg, release_track):
|
|
"""Issues requests necessary to import URL maps."""
|
|
client = holder.client
|
|
resources = holder.resources
|
|
|
|
url_map_ref = url_map_arg.ResolveAsResource(
|
|
args,
|
|
resources,
|
|
default_scope=compute_scope.ScopeEnum.GLOBAL,
|
|
scope_lister=compute_flags.GetDefaultScopeLister(client))
|
|
|
|
data = console_io.ReadFromFileOrStdin(args.source or '-', binary=False)
|
|
|
|
try:
|
|
url_map = export_util.Import(
|
|
message_type=client.messages.UrlMap,
|
|
stream=data,
|
|
schema_path=_GetSchemaPath(release_track))
|
|
except yaml_validator.ValidationError as e:
|
|
raise compute_exceptions.ValidationError(str(e))
|
|
|
|
if url_map.name != url_map_ref.Name():
|
|
# Replace warning and raise error after 10/01/2021
|
|
log.warning('The name of the Url Map must match the value of the ' +
|
|
'\'name\' attribute in the YAML file. Future versions of ' +
|
|
'gcloud will fail with an error.')
|
|
# Get existing URL map.
|
|
try:
|
|
url_map_old = url_maps_utils.SendGetRequest(client, url_map_ref)
|
|
except apitools_exceptions.HttpError as error:
|
|
if error.status_code != 404:
|
|
raise error
|
|
# Url Map does not exist, create a new one.
|
|
return _SendInsertRequest(client, resources, url_map_ref, url_map)
|
|
|
|
# No change, do not send requests to server.
|
|
if url_map_old == url_map:
|
|
return
|
|
|
|
console_io.PromptContinue(
|
|
message=('Url Map [{0}] will be overwritten.').format(url_map_ref.Name()),
|
|
cancel_on_no=True)
|
|
|
|
# Populate id and fingerprint fields when YAML files don't contain them.
|
|
if not url_map.id:
|
|
url_map.id = url_map_old.id
|
|
if url_map.fingerprint:
|
|
# Replace warning and raise error after 10/01/2021
|
|
log.warning('An up-to-date fingerprint must be provided to ' +
|
|
'update the Url Map. Future versions of gcloud will fail ' +
|
|
'with an error \'412 conditionNotMet\'')
|
|
url_map.fingerprint = url_map_old.fingerprint
|
|
# Unspecified fields are assumed to be cleared.
|
|
# TODO(b/182287403) Replace with proto reflection and update scenario tests.
|
|
cleared_fields = []
|
|
if not url_map.description:
|
|
cleared_fields.append('description')
|
|
if not url_map.hostRules:
|
|
cleared_fields.append('hostRules')
|
|
if not url_map.pathMatchers:
|
|
cleared_fields.append('pathMatchers')
|
|
if not url_map.tests:
|
|
cleared_fields.append('tests')
|
|
if not url_map.defaultService:
|
|
cleared_fields.append('defaultService')
|
|
if not url_map.defaultCustomErrorResponsePolicy:
|
|
cleared_fields.append('defaultCustomErrorResponsePolicy')
|
|
else:
|
|
cleared_fields = (
|
|
cleared_fields
|
|
+ _GetClearedFieldsForCustomErrorResponsePolicy(
|
|
url_map.defaultCustomErrorResponsePolicy,
|
|
'defaultCustomErrorResponsePolicy.',
|
|
)
|
|
)
|
|
if not url_map.defaultRouteAction:
|
|
cleared_fields.append('defaultRouteAction')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForRoutAction(
|
|
url_map.defaultRouteAction, 'defaultRouteAction.')
|
|
if not url_map.defaultUrlRedirect:
|
|
cleared_fields.append('defaultUrlRedirect')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForUrlRedirect(
|
|
url_map.defaultUrlRedirect, 'defaultUrlRedirect.')
|
|
if not url_map.headerAction:
|
|
cleared_fields.append('headerAction')
|
|
else:
|
|
cleared_fields = cleared_fields + _GetClearedFieldsForHeaderAction(
|
|
url_map.headerAction, 'headerAction.')
|
|
|
|
with client.apitools_client.IncludeFields(cleared_fields):
|
|
return _SendPatchRequest(client, resources, url_map_ref, url_map)
|
|
|
|
|
|
@base.ReleaseTracks(
|
|
base.ReleaseTrack.GA, base.ReleaseTrack.BETA, base.ReleaseTrack.ALPHA
|
|
)
|
|
@base.UniverseCompatible
|
|
class Import(base.UpdateCommand):
|
|
"""Import a URL map."""
|
|
|
|
detailed_help = _DetailedHelp()
|
|
URL_MAP_ARG = None
|
|
|
|
@classmethod
|
|
def Args(cls, parser):
|
|
cls.URL_MAP_ARG = flags.UrlMapArgument()
|
|
cls.URL_MAP_ARG.AddArgument(parser, operation_type='import')
|
|
export_util.AddImportFlags(
|
|
parser, _GetSchemaPath(cls.ReleaseTrack(), for_help=True))
|
|
|
|
def Run(self, args):
|
|
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
|
|
return _Run(args, holder, self.URL_MAP_ARG, self.ReleaseTrack())
|