# -*- 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. """Helpers for parsing resource arguments.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from googlecloudsdk.api_lib.privateca import base from googlecloudsdk.api_lib.privateca import locations from googlecloudsdk.calliope import exceptions from googlecloudsdk.calliope.concepts import concepts from googlecloudsdk.calliope.concepts import deps from googlecloudsdk.calliope.concepts import handlers from googlecloudsdk.calliope.concepts import util from googlecloudsdk.command_lib.kms import resource_args as kms_args from googlecloudsdk.command_lib.privateca import completers as privateca_completers from googlecloudsdk.command_lib.privateca import exceptions as privateca_exceptions from googlecloudsdk.command_lib.util.concepts import concept_parsers from googlecloudsdk.core import properties import six # Flag fallthroughs that rely on properties. LOCATION_PROPERTY_FALLTHROUGH = deps.PropertyFallthrough( properties.VALUES.privateca.location ) PROJECT_PROPERTY_FALLTHROUGH = deps.PropertyFallthrough( properties.VALUES.core.project ) def CertificateTemplateAttributeConfig(): # TODO(b/186143764): GA Autocompleters return concepts.ResourceParameterAttributeConfig(name='certificate template') def CaPoolAttributeConfig(display_name='pool', fallthroughs=None): # TODO(b/186143764): GA Autocompleters return concepts.ResourceParameterAttributeConfig( name=display_name, help_text='The parent CA Pool of the {resource}.', fallthroughs=fallthroughs or [], ) def CertAttributeConfig(fallthroughs=None): # TODO(b/186143764): GA Autocompleters # Certificate is always an anchor attribute so help_text is unused. return concepts.ResourceParameterAttributeConfig( name='certificate', fallthroughs=fallthroughs or [] ) def CertAuthorityAttributeConfig( arg_name='certificate_authority', fallthroughs=None ): fallthroughs = fallthroughs or [] return concepts.ResourceParameterAttributeConfig( name=arg_name, help_text='The issuing certificate authority of the {resource}.', fallthroughs=fallthroughs, ) def LocationAttributeConfig(arg_name='location', fallthroughs=None): fallthroughs = fallthroughs or [LOCATION_PROPERTY_FALLTHROUGH] return concepts.ResourceParameterAttributeConfig( name=arg_name, help_text='The location of the {resource}.', completer=privateca_completers.LocationsCompleter, fallthroughs=fallthroughs, ) def ProjectAttributeConfig(arg_name='project', fallthroughs=None): """DO NOT USE THIS for most flags. This config is only useful when you want to provide an explicit project fallthrough. For most cases, prefer concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG. Args: arg_name: Name of the flag used to specify this attribute. Defaults to 'project'. fallthroughs: List of deps.Fallthrough objects to provide project values. Returns: A concepts.ResourceParameterAttributeConfig for a project. """ return concepts.ResourceParameterAttributeConfig( name=arg_name, help_text='The project containing the {resource}.', fallthroughs=fallthroughs or [], ) def CreateKmsKeyVersionResourceSpec(): """Creates a resource spec for a KMS CryptoKeyVersion. Defaults to the location and project of the CA, specified through flags or properties. Returns: A concepts.ResourceSpec for a CryptoKeyVersion. """ return concepts.ResourceSpec( 'cloudkms.projects.locations.keyRings.cryptoKeys.cryptoKeyVersions', resource_name='key version', cryptoKeyVersionsId=kms_args.KeyVersionAttributeConfig(kms_prefix=True), cryptoKeysId=kms_args.KeyAttributeConfig(kms_prefix=True), keyRingsId=kms_args.KeyringAttributeConfig(kms_prefix=True), locationsId=LocationAttributeConfig( 'kms-location', [deps.ArgFallthrough('location'), LOCATION_PROPERTY_FALLTHROUGH], ), projectsId=ProjectAttributeConfig( 'kms-project', [deps.ArgFallthrough('project'), PROJECT_PROPERTY_FALLTHROUGH], ), ) def CreateCertAuthorityResourceSpec( display_name, certificate_authority_attribute='certificate_authority', location_attribute='location', location_fallthroughs=None, pool_id_fallthroughs=None, ca_id_fallthroughs=None, ): # TODO(b/186143764): GA Autocompleters return concepts.ResourceSpec( 'privateca.projects.locations.caPools.certificateAuthorities', api_version='v1', # This will be formatted and used as {resource} in the help text. resource_name=display_name, certificateAuthoritiesId=CertAuthorityAttributeConfig( certificate_authority_attribute, fallthroughs=ca_id_fallthroughs ), caPoolsId=CaPoolAttributeConfig(fallthroughs=pool_id_fallthroughs), locationsId=LocationAttributeConfig( location_attribute, fallthroughs=location_fallthroughs ), projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, disable_auto_completers=True, ) def CreateCaPoolResourceSpec( display_name, location_attribute='location', pool_id_fallthroughs=None, location_fallthroughs=None, ): # TODO(b/186143764): GA Autocompleters return concepts.ResourceSpec( 'privateca.projects.locations.caPools', api_version='v1', # This will be formatted and used as {resource} in the help text. resource_name=display_name, caPoolsId=CaPoolAttributeConfig(fallthroughs=pool_id_fallthroughs), locationsId=LocationAttributeConfig( location_attribute, fallthroughs=location_fallthroughs ), projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, disable_auto_completers=True, ) def CreateCertResourceSpec(display_name, id_fallthroughs=None): return concepts.ResourceSpec( 'privateca.projects.locations.caPools.certificates', # This will be formatted and used as {resource} in the help text. api_version='v1', resource_name=display_name, certificatesId=CertAttributeConfig(fallthroughs=id_fallthroughs or []), caPoolsId=CaPoolAttributeConfig('issuer-pool'), locationsId=LocationAttributeConfig('issuer-location'), projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, disable_auto_completers=False, ) def CreateCertificateTemplateResourceSpec(display_name): # TODO(b/186143764): GA Autocompleters return concepts.ResourceSpec( 'privateca.projects.locations.certificateTemplates', api_version='v1', # This will be formatted and used as {resource} in the help text. resource_name=display_name, certificateTemplatesId=CertificateTemplateAttributeConfig(), locationsId=LocationAttributeConfig(), projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, disable_auto_completers=True, ) def AddCertAuthorityPositionalResourceArg(parser, verb): """Add a positional resource argument for a GA Certificate Authority. NOTE: Must be used only if it's the only resource arg in the command. Args: parser: the parser for the command. verb: str, the verb to describe the resource, such as 'to update'. """ arg_name = 'CERTIFICATE_AUTHORITY' concept_parsers.ConceptParser.ForResource( arg_name, CreateCertAuthorityResourceSpec(arg_name), 'The certificate authority {}.'.format(verb), required=True, ).AddToParser(parser) def AddCaPoolPositionalResourceArg(parser, verb): """Add a positional resource argument for a CA Pool. NOTE: Must be used only if it's the only resource arg in the command. Args: parser: the parser for the command. verb: str, the verb to describe the resource, such as 'to update'. """ arg_name = 'CA_POOL' concept_parsers.ConceptParser.ForResource( arg_name, CreateCaPoolResourceSpec(arg_name), 'The ca pool {}.'.format(verb), required=True, ).AddToParser(parser) def AddCertPositionalResourceArg(parser, verb): """Add a positional resource argument for a GA Certificate. NOTE: Must be used only if it's the only resource arg in the command. Args: parser: the parser for the command. verb: str, the verb to describe the resource, such as 'to update'. """ arg_name = 'CERTIFICATE' concept_parsers.ConceptParser.ForResource( arg_name, CreateCertResourceSpec(arg_name), 'The certificate {}.'.format(verb), required=True, ).AddToParser(parser) def AddCertificateTemplatePositionalResourceArg(parser, verb): """Add a positional resource argument for a certificate template. NOTE: Must be used only if it's the only resource arg in the command. Args: parser: the parser for the command. verb: str, the verb to describe the resource, such as 'to update'. """ arg_name = 'CERTIFICATE_TEMPLATE' concept_parsers.ConceptParser.ForResource( arg_name, CreateCertificateTemplateResourceSpec(arg_name), 'The template {}.'.format(verb), required=True, ).AddToParser(parser) # Resource validation. def ValidateResourceLocation(resource_ref, arg_name, version='v1'): """Raises an exception if the given resource is in an unsupported location.""" supported_locations = locations.GetSupportedLocations(version=version) if resource_ref.locationsId not in supported_locations: raise exceptions.InvalidArgumentException( arg_name, 'Resource is in an unsupported location. Supported locations are: {}.' .format(', '.join(sorted(supported_locations))), ) def CheckExpectedCAType(expected_type, ca, version='v1'): """Raises an exception if the Certificate Authority type is not expected_type. Args: expected_type: The expected type. ca: The ca object to check. version: The version of the API to check against. """ ca_type_enum = base.GetMessagesModule( api_version=version ).CertificateAuthority.TypeValueValuesEnum if expected_type == ca_type_enum.SUBORDINATE and ca.type != expected_type: raise privateca_exceptions.InvalidCertificateAuthorityTypeError( 'Cannot perform subordinates command on Root CA. Please use the' ' `privateca roots` command group instead.' ) elif expected_type == ca_type_enum.SELF_SIGNED and ca.type != expected_type: raise privateca_exceptions.InvalidCertificateAuthorityTypeError( 'Cannot perform roots command on Subordinate CA. Please use the' ' `privateca subordinates` command group instead.' ) def ValidateResourceIsCompleteIfSpecified(args, resource_arg_name): """Raises a ParseError if the given resource_arg_name is partially specified.""" if not hasattr(args.CONCEPTS, resource_arg_name): return concept_info = args.CONCEPTS.ArgNameToConceptInfo(resource_arg_name) associated_args = [ util.NamespaceFormat(arg) for arg in concept_info.attribute_to_args_map.values() ] # If none of the relevant args are specified, we're good. if not [arg for arg in associated_args if args.IsSpecified(arg)]: return try: # Re-parse this concept, but treat it as required even if it originally # wasn't. This will trigger a meaningful user error if it's underspecified. concept_info.ClearCache() concept_info.allow_empty = False concept_info.Parse(args) except concepts.InitializationError as e: raise handlers.ParseError(resource_arg_name, six.text_type(e))