def _construct_basepath_mappings(self, basepaths, http_api): basepath_resource_list = [] if basepaths is None: basepath_mapping = ApiGatewayV2ApiMapping( self.logical_id + "ApiMapping", attributes=self.passthrough_resource_attributes ) basepath_mapping.DomainName = ref(self.domain.get("ApiDomainName")) basepath_mapping.ApiId = ref(http_api.logical_id) basepath_mapping.Stage = ref(http_api.logical_id + ".Stage") basepath_resource_list.extend([basepath_mapping]) else: for path in basepaths: # search for invalid characters in the path and raise error if there are invalid_regex = r"[^0-9a-zA-Z\/\-\_]+" if re.search(invalid_regex, path) is not None: raise InvalidResourceException(self.logical_id, "Invalid Basepath name provided.") # ignore leading and trailing `/` in the path name m = re.search(r"[a-zA-Z0-9]+[\-\_]?[a-zA-Z0-9]+", path) path = m.string[m.start(0) : m.end(0)] if path is None: raise InvalidResourceException(self.logical_id, "Invalid Basepath name provided.") logical_id = "{}{}{}".format(self.logical_id, re.sub(r"[\-\_]+", "", path), "ApiMapping") basepath_mapping = ApiGatewayV2ApiMapping(logical_id, attributes=self.passthrough_resource_attributes) basepath_mapping.DomainName = ref(self.domain.get("ApiDomainName")) basepath_mapping.ApiId = ref(http_api.logical_id) basepath_mapping.Stage = ref(http_api.logical_id + ".Stage") basepath_mapping.ApiMappingKey = path basepath_resource_list.extend([basepath_mapping]) return basepath_resource_list
def _set_default_authorizer(self, open_api_editor, authorizers, default_authorizer, api_authorizers): """ Sets the default authorizer if one is given in the template :param open_api_editor: editor object that contains the OpenApi definition :param authorizers: authorizer definitions converted from the API auth section :param default_authorizer: name of the default authorizer :param api_authorizers: API auth section authorizer defintions """ if not default_authorizer: return if is_intrinsic_no_value(default_authorizer): return if is_intrinsic(default_authorizer): raise InvalidResourceException( self.logical_id, "Unable to set DefaultAuthorizer because intrinsic functions are not supported for this field.", ) if not authorizers.get(default_authorizer): raise InvalidResourceException( self.logical_id, "Unable to set DefaultAuthorizer because '" + default_authorizer + "' was not defined in 'Authorizers'.", ) for path in open_api_editor.iter_on_path(): open_api_editor.set_path_default_authorizer( path, default_authorizer, authorizers=authorizers, api_authorizers=api_authorizers)
def _set_default_authorizer(self, swagger_editor, authorizers, default_authorizer, add_default_auth_to_preflight=True, api_authorizers=None): if not default_authorizer: return if not isinstance(default_authorizer, string_types): raise InvalidResourceException( self.logical_id, "DefaultAuthorizer is not a string.", ) if not authorizers.get( default_authorizer) and default_authorizer != "AWS_IAM": raise InvalidResourceException( self.logical_id, "Unable to set DefaultAuthorizer because '" + default_authorizer + "' was not defined in 'Authorizers'.", ) for path in swagger_editor.iter_on_path(): swagger_editor.set_path_default_authorizer( path, default_authorizer, authorizers=authorizers, add_default_auth_to_preflight=add_default_auth_to_preflight, api_authorizers=api_authorizers, )
def _get_retention_policy_value(self, intrinsics_resolver): """ Sets the deletion policy on this resource. The default is 'Retain'. :return: value for the DeletionPolicy attribute. """ if isinstance(self.RetentionPolicy, dict): self.RetentionPolicy = intrinsics_resolver.resolve_parameter_refs( self.RetentionPolicy) # If it's still not a string, throw an exception if not isinstance(self.RetentionPolicy, string_types): raise InvalidResourceException( self.logical_id, "Could not resolve parameter for '{}' or parameter is not a String." .format('RetentionPolicy')) if self.RetentionPolicy is None or self.RetentionPolicy.lower( ) == self.RETAIN.lower(): return self.RETAIN elif self.RetentionPolicy.lower() == self.DELETE.lower(): return self.DELETE elif self.RetentionPolicy.lower() not in self.retention_policy_options: raise InvalidResourceException( self.logical_id, "'{}' must be one of the following options: {}.".format( 'RetentionPolicy', [self.RETAIN, self.DELETE]))
def _validate_deployment_preference_and_add_update_policy( self, deployment_preference_collection, lambda_alias, intrinsics_resolver): if 'Enabled' in self.DeploymentPreference: self.DeploymentPreference[ 'Enabled'] = intrinsics_resolver.resolve_parameter_refs( self.DeploymentPreference['Enabled']) if isinstance(self.DeploymentPreference['Enabled'], dict): raise InvalidResourceException( self.logical_id, "'Enabled' must be a boolean value") if deployment_preference_collection is None: raise ValueError( 'deployment_preference_collection required for parsing the deployment preference' ) deployment_preference_collection.add(self.logical_id, self.DeploymentPreference) if deployment_preference_collection.get(self.logical_id).enabled: if self.AutoPublishAlias is None: raise InvalidResourceException( self.logical_id, "'DeploymentPreference' requires AutoPublishAlias property to be specified" ) if lambda_alias is None: raise ValueError( 'lambda_alias expected for updating it with the appropriate update policy' ) lambda_alias.set_resource_attribute( "UpdatePolicy", deployment_preference_collection.update_policy( self.logical_id).to_dict())
def from_dict(cls, logical_id, deployment_preference_dict): """ :param logical_id: the logical_id of the resource that owns this deployment preference :param deployment_preference_dict: the dict object taken from the SAM template :return: """ enabled = deployment_preference_dict.get('Enabled', True) if not enabled: return DeploymentPreference(None, None, None, None, False, None) if 'Type' not in deployment_preference_dict: raise InvalidResourceException( logical_id, "'DeploymentPreference' is missing required Property 'Type'") deployment_type = deployment_preference_dict['Type'] hooks = deployment_preference_dict.get('Hooks', dict()) if not isinstance(hooks, dict): raise InvalidResourceException( logical_id, "'Hooks' property of 'DeploymentPreference' must be a dictionary" ) pre_traffic_hook = hooks.get('PreTraffic', None) post_traffic_hook = hooks.get('PostTraffic', None) alarms = deployment_preference_dict.get('Alarms', None) role = deployment_preference_dict.get('Role', None) return DeploymentPreference(deployment_type, pre_traffic_hook, post_traffic_hook, alarms, enabled, role)
def construct_s3_location_object(location_uri, logical_id, property_name): """Constructs a Lambda `Code` or `Content` property, from the SAM `CodeUri` or `ContentUri` property. This follows the current scheme for Lambda Functions and LayerVersions. :param dict or string location_uri: s3 location dict or string :param string logical_id: logical_id of the resource calling this function :param string property_name: name of the property which is used as an input to this function. :returns: a Code dict, containing the S3 Bucket, Key, and Version of the Lambda layer code :rtype: dict """ if isinstance(location_uri, dict): if not location_uri.get("Bucket") or not location_uri.get("Key"): # location_uri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException( logical_id, "'{}' requires Bucket and Key properties to be " "specified".format(property_name)) s3_pointer = location_uri else: # location_uri is NOT a dictionary. Parse it as a string s3_pointer = parse_s3_uri(location_uri) if s3_pointer is None: raise InvalidResourceException( logical_id, "'{}' is not a valid S3 Uri of the form " '"s3://bucket/key" with optional versionId query ' "parameter.".format(property_name), ) code = {"S3Bucket": s3_pointer["Bucket"], "S3Key": s3_pointer["Key"]} if "Version" in s3_pointer: code["S3ObjectVersion"] = s3_pointer["Version"] return code
def _add_auth(self): """ Add Auth configuration to the OAS file, if necessary """ if not self.auth: return if self.auth and not self.definition_body: raise InvalidResourceException( self.logical_id, "Auth works only with inline OpenApi specified in the 'DefinitionBody' property." ) # Make sure keys in the dict are recognized if not all(key in AuthProperties._fields for key in self.auth.keys()): raise InvalidResourceException( self.logical_id, "Invalid value for 'Auth' property") if not OpenApiEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, "Unable to add Auth configuration because 'DefinitionBody' does not contain a valid OpenApi definition.", ) open_api_editor = OpenApiEditor(self.definition_body) auth_properties = AuthProperties(**self.auth) authorizers = self._get_authorizers(auth_properties.Authorizers, auth_properties.DefaultAuthorizer) # authorizers is guaranteed to return a value or raise an exception open_api_editor.add_authorizers_security_definitions(authorizers) self._set_default_authorizer(open_api_editor, authorizers, auth_properties.DefaultAuthorizer, auth_properties.Authorizers) self.definition_body = open_api_editor.openapi
def __init__( self, api_logical_id=None, name=None, authorization_scopes=[], jwt_configuration={}, id_source=None, ): """ Creates an authorizer for use in V2 Http Apis """ # Currently only one type of auth self.auth_type = "oauth2" self.api_logical_id = api_logical_id self.name = name self.authorization_scopes = authorization_scopes # Validate necessary parameters exist if not jwt_configuration: raise InvalidResourceException( api_logical_id, name + " Authorizer must define 'JwtConfiguration'.") self.jwt_configuration = jwt_configuration if not id_source: raise InvalidResourceException( api_logical_id, name + " Authorizer must define 'IdentitySource'.") self.id_source = id_source
def _construct_http_api(self): """Constructs and returns the ApiGatewayV2 HttpApi. :returns: the HttpApi to which this SAM Api corresponds :rtype: model.apigatewayv2.ApiGatewayHttpApi """ http_api = ApiGatewayV2HttpApi(self.logical_id, depends_on=self.depends_on, attributes=self.resource_attributes) if self.definition_uri and self.definition_body: raise InvalidResourceException( self.logical_id, "Specify either 'DefinitionUri' or 'DefinitionBody' property and not both" ) self._add_auth() if self.definition_uri: http_api.BodyS3Location = self._construct_body_s3_dict() elif self.definition_body: http_api.Body = self.definition_body else: raise InvalidResourceException( self.logical_id, "'DefinitionUri' or 'DefinitionBody' are required properties of an " "'AWS::Serverless::HttpApi'. Add a value for one of these properties or " "add a 'HttpApi' event to an 'AWS::Serverless::Function'", ) if self.tags is not None: http_api.Tags = get_tag_list(self.tags) return http_api
def _construct_definition_uri(self): """ Constructs the State Machine's `DefinitionS3 property`_, from the SAM State Machines's DefinitionUri property. :returns: a DefinitionUri dict, containing the S3 Bucket, Key, and Version of the State Machine definition. :rtype: dict """ if isinstance(self.definition_uri, dict): if not self.definition_uri.get( "Bucket", None) or not self.definition_uri.get( "Key", None): # DefinitionUri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException( self.logical_id, "'DefinitionUri' requires Bucket and Key properties to be specified." ) s3_pointer = self.definition_uri else: # DefinitionUri is a string s3_pointer = parse_s3_uri(self.definition_uri) if s3_pointer is None: raise InvalidResourceException( self.logical_id, "'DefinitionUri' is not a valid S3 Uri of the form " "'s3://bucket/key' with optional versionId query parameter.", ) definition_s3 = { "Bucket": s3_pointer["Bucket"], "Key": s3_pointer["Key"] } if "Version" in s3_pointer: definition_s3["Version"] = s3_pointer["Version"] return definition_s3
def _get_authorizers(self, authorizers_config, default_authorizer=None): authorizers = {} if default_authorizer == "AWS_IAM": authorizers[default_authorizer] = ApiGatewayAuthorizer( api_logical_id=self.logical_id, name=default_authorizer, is_aws_iam_authorizer=True ) if not authorizers_config: if "AWS_IAM" in authorizers: return authorizers return None if not isinstance(authorizers_config, dict): raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary.") for authorizer_name, authorizer in authorizers_config.items(): if not isinstance(authorizer, dict): raise InvalidResourceException( self.logical_id, "Authorizer %s must be a dictionary." % (authorizer_name) ) authorizers[authorizer_name] = ApiGatewayAuthorizer( api_logical_id=self.logical_id, name=authorizer_name, user_pool_arn=authorizer.get("UserPoolArn"), function_arn=authorizer.get("FunctionArn"), identity=authorizer.get("Identity"), function_payload_type=authorizer.get("FunctionPayloadType"), function_invoke_role=authorizer.get("FunctionInvokeRole"), authorization_scopes=authorizer.get("AuthorizationScopes"), ) return authorizers
def _add_tags(self): """ Adds tags to the Http Api, including a default SAM tag. """ if self.tags and not self.definition_body: raise InvalidResourceException( self.logical_id, "Tags works only with inline OpenApi specified in the 'DefinitionBody' property." ) if not self.definition_body: return if self.tags and not OpenApiEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, "Unable to add `Tags` because 'DefinitionBody' does not contain a valid OpenApi definition.", ) elif not OpenApiEditor.is_valid(self.definition_body): return if not self.tags: self.tags = {} self.tags[HttpApiTagName] = "SAM" open_api_editor = OpenApiEditor(self.definition_body) # authorizers is guaranteed to return a value or raise an exception open_api_editor.add_tags(self.tags) self.definition_body = open_api_editor.openapi
def validate_properties(self): """Validates that the required properties for this Resource have been populated, and that all properties have valid values. :returns: True if all properties are valid :rtype: bool :raises TypeError: if any properties are invalid """ for name, property_type in self.property_types.items(): value = getattr(self, name) # If the property value is an intrinsic function, any remaining validation has to be left to CloudFormation if property_type.supports_intrinsics and self._is_intrinsic_function(value): continue # If the property value has not been set, verify that the property is not required. if value is None: if property_type.required: raise InvalidResourceException( self.logical_id, "Missing required property '{property_name}'.".format(property_name=name)) # Otherwise, validate the value of the property. elif not property_type.validate(value, should_raise=False): raise InvalidResourceException( self.logical_id, "Type of property '{property_name}' is invalid.".format(property_name=name))
def _add_models(self): """ Add Model definitions to the Swagger file, if necessary :return: """ if not self.models: return if self.models and not self.definition_body: raise InvalidResourceException( self.logical_id, "Models works only with inline Swagger specified in " "'DefinitionBody' property." ) if not SwaggerEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, "Unable to add Models definitions because " "'DefinitionBody' does not contain a valid Swagger definition.", ) if not all(isinstance(model, dict) for model in self.models.values()): raise InvalidResourceException(self.logical_id, "Invalid value for 'Models' property") swagger_editor = SwaggerEditor(self.definition_body) swagger_editor.add_models(self.models) # Assign the Swagger back to template self.definition_body = self._openapi_postprocess(swagger_editor.swagger)
def _construct_code_dict_code_uri(self): """Constructs the Lambda function's `Code property`_, from the SAM function's CodeUri property. .. _Code property: \ http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-code.html :returns: a Code dict, containing the S3 Bucket, Key, and Version of the Lambda function code :rtype: dict """ if isinstance(self.CodeUri, dict): if not self.CodeUri.get("Bucket", None) or not self.CodeUri.get("Key", None): # CodeUri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException(self.logical_id, "'CodeUri' requires Bucket and Key properties to be specified") s3_pointer = self.CodeUri else: # CodeUri is NOT a dictionary. Parse it as a string s3_pointer = parse_s3_uri(self.CodeUri) if s3_pointer is None: raise InvalidResourceException(self.logical_id, '\'CodeUri\' is not a valid S3 Uri of the form ' '"s3://bucket/key" with optional versionId query parameter.') code = { 'S3Bucket': s3_pointer['Bucket'], 'S3Key': s3_pointer['Key'] } if 'Version' in s3_pointer: code['S3ObjectVersion'] = s3_pointer['Version'] return code
def from_dict(cls, logical_id, deployment_preference_dict): """ :param logical_id: the logical_id of the resource that owns this deployment preference :param deployment_preference_dict: the dict object taken from the SAM template :return: """ enabled = deployment_preference_dict.get("Enabled", True) if not enabled: return DeploymentPreference(None, None, None, None, False, None, None) if "Type" not in deployment_preference_dict: raise InvalidResourceException( logical_id, "'DeploymentPreference' is missing required Property 'Type'") deployment_type = deployment_preference_dict["Type"] hooks = deployment_preference_dict.get("Hooks", dict()) if not isinstance(hooks, dict): raise InvalidResourceException( logical_id, "'Hooks' property of 'DeploymentPreference' must be a dictionary" ) pre_traffic_hook = hooks.get("PreTraffic", None) post_traffic_hook = hooks.get("PostTraffic", None) alarms = deployment_preference_dict.get("Alarms", None) role = deployment_preference_dict.get("Role", None) trigger_configurations = deployment_preference_dict.get( "TriggerConfigurations", None) return DeploymentPreference(deployment_type, pre_traffic_hook, post_traffic_hook, alarms, enabled, role, trigger_configurations)
def _add_auth(self): """ Add Auth configuration to the Swagger file, if necessary """ if not self.auth: return if self.auth and not self.definition_body: raise InvalidResourceException( self.logical_id, "Auth works only with inline Swagger specified in " "'DefinitionBody' property") # Make sure keys in the dict are recognized if not all(key in AuthProperties._fields for key in self.auth.keys()): raise InvalidResourceException( self.logical_id, "Invalid value for 'Auth' property") if not SwaggerEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, "Unable to add Auth configuration because " "'DefinitionBody' does not contain a valid Swagger") swagger_editor = SwaggerEditor(self.definition_body) auth_properties = AuthProperties(**self.auth) authorizers = self._get_authorizers(auth_properties.Authorizers) if authorizers: swagger_editor.add_authorizers(authorizers) self._set_default_authorizer(swagger_editor, authorizers, auth_properties.DefaultAuthorizer) # Assign the Swagger back to template self.definition_body = swagger_editor.swagger
def _get_authorizers(self, authorizers_config, default_authorizer=None): """ Returns all authorizers for an API as an ApiGatewayV2Authorizer object :param authorizers_config: authorizer configuration from the API Auth section :param default_authorizer: name of the default authorizer """ authorizers = {} if not isinstance(authorizers_config, dict): raise InvalidResourceException(self.logical_id, "Authorizers must be a dictionary") for authorizer_name, authorizer in authorizers_config.items(): if not isinstance(authorizer, dict): raise InvalidResourceException( self.logical_id, "Authorizer %s must be a dictionary." % (authorizer_name) ) authorizers[authorizer_name] = ApiGatewayV2Authorizer( api_logical_id=self.logical_id, name=authorizer_name, open_id_connect_url=authorizer.get("OpenIdConnectUrl"), authorization_scopes=authorizer.get("AuthorizationScopes"), jwt_configuration=authorizer.get("JwtConfiguration"), id_source=authorizer.get("IdentitySource"), ) return authorizers
def _construct_body_s3_dict(self): """Constructs the RestApi's `BodyS3Location property`_, from the SAM Api's DefinitionUri property. :returns: a BodyS3Location dict, containing the S3 Bucket, Key, and Version of the Swagger definition :rtype: dict """ if isinstance(self.definition_uri, dict): if not self.definition_uri.get( "Bucket", None) or not self.definition_uri.get( "Key", None): # DefinitionUri is a dictionary but does not contain Bucket or Key property raise InvalidResourceException( self.logical_id, "'DefinitionUri' requires Bucket and Key properties to be specified" ) s3_pointer = self.definition_uri else: # DefinitionUri is a string s3_pointer = parse_s3_uri(self.definition_uri) if s3_pointer is None: raise InvalidResourceException( self.logical_id, '\'DefinitionUri\' is not a valid S3 Uri of the form ' '"s3://bucket/key" with optional versionId query parameter.' ) body_s3 = {'Bucket': s3_pointer['Bucket'], 'Key': s3_pointer['Key']} if 'Version' in s3_pointer: body_s3['Version'] = s3_pointer['Version'] return body_s3
def __init__( self, api_logical_id=None, name=None, open_id_connect_url=None, authorization_scopes=[], jwt_configuration={}, id_source=None, ): """ Creates an authorizer for use in V2 Http Apis """ # OIDC uses a connect url, oauth2 doesn't self.auth_type = "openIdConnect" if open_id_connect_url is None: self.auth_type = "oauth2" self.api_logical_id = api_logical_id self.name = name self.open_id_connect_url = open_id_connect_url self.authorization_scopes = authorization_scopes # Validate necessary parameters exist if not jwt_configuration: raise InvalidResourceException( api_logical_id, name + " Authorizer must define 'JwtConfiguration'") self.jwt_configuration = jwt_configuration if not id_source: raise InvalidResourceException( api_logical_id, name + " Authorizer must define 'IdentitySource'") self.id_source = id_source
def __init__(self, api_logical_id=None, name=None, user_pool_arn=None, function_arn=None, identity=None, function_payload_type=None, function_invoke_role=None, is_aws_iam_authorizer=False): if function_payload_type not in ApiGatewayAuthorizer._VALID_FUNCTION_PAYLOAD_TYPES: raise InvalidResourceException( api_logical_id, name + " Authorizer has invalid " "'FunctionPayloadType': " + function_payload_type) if function_payload_type == 'REQUEST' and self._is_missing_identity_source( identity): raise InvalidResourceException( api_logical_id, name + " Authorizer must specify Identity with at least one " "of Headers, QueryStrings, StageVariables, or Context.") self.api_logical_id = api_logical_id self.name = name self.user_pool_arn = user_pool_arn self.function_arn = function_arn self.identity = identity self.function_payload_type = function_payload_type self.function_invoke_role = function_invoke_role self.is_aws_iam_authorizer = is_aws_iam_authorizer
def _get_authorizers(self, authorizers_config, default_authorizer=None): """ Returns all authorizers for an API as an ApiGatewayV2Authorizer object :param authorizers_config: authorizer configuration from the API Auth section :param default_authorizer: name of the default authorizer """ authorizers = {} if not isinstance(authorizers_config, dict): raise InvalidResourceException( self.logical_id, "Authorizers must be a dictionary.") for authorizer_name, authorizer in authorizers_config.items(): if not isinstance(authorizer, dict): raise InvalidResourceException( self.logical_id, "Authorizer %s must be a dictionary." % (authorizer_name)) if "OpenIdConnectUrl" in authorizer: raise InvalidResourceException( self.logical_id, "'OpenIdConnectUrl' is no longer a supported property for authorizer '%s'. Please refer to the AWS SAM documentation." % (authorizer_name), ) authorizers[authorizer_name] = ApiGatewayV2Authorizer( api_logical_id=self.logical_id, name=authorizer_name, authorization_scopes=authorizer.get("AuthorizationScopes"), jwt_configuration=authorizer.get("JwtConfiguration"), id_source=authorizer.get("IdentitySource"), ) return authorizers
def _add_cors(self): """ Add CORS configuration to the Swagger file, if necessary """ INVALID_ERROR = "Invalid value for 'Cors' property" if not self.cors: return if self.cors and not self.definition_body: raise InvalidResourceException( self.logical_id, "Cors works only with inline Swagger specified in " "'DefinitionBody' property") if isinstance(self.cors, string_types) or is_instrinsic(self.cors): # Just set Origin property. Others will be defaults properties = CorsProperties(AllowOrigin=self.cors) elif isinstance(self.cors, dict): # Make sure keys in the dict are recognized if not all(key in CorsProperties._fields for key in self.cors.keys()): raise InvalidResourceException(self.logical_id, INVALID_ERROR) properties = CorsProperties(**self.cors) else: raise InvalidResourceException(self.logical_id, INVALID_ERROR) if not SwaggerEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, "Unable to add Cors configuration because " "'DefinitionBody' does not contain a valid Swagger", ) if properties.AllowCredentials is True and properties.AllowOrigin == _CORS_WILDCARD: raise InvalidResourceException( self.logical_id, "Unable to add Cors configuration because " "'AllowCredentials' can not be true when " "'AllowOrigin' is \"'*'\" or not set", ) editor = SwaggerEditor(self.definition_body) for path in editor.iter_on_path(): editor.add_cors( path, properties.AllowOrigin, properties.AllowHeaders, properties.AllowMethods, max_age=properties.MaxAge, allow_credentials=properties.AllowCredentials, ) # Assign the Swagger back to template self.definition_body = editor.swagger
def to_cloudformation(self): """ Constructs and returns the State Machine resource and any additional resources associated with it. :returns: a list of resources including the State Machine resource. :rtype: list """ resources = [self.state_machine] # Defaulting to {} will add the DefinitionSubstitutions field on the transform output even when it is not relevant if self.definition_substitutions: self.state_machine.DefinitionSubstitutions = self.definition_substitutions if self.definition and self.definition_uri: raise InvalidResourceException( self.logical_id, "Specify either 'Definition' or 'DefinitionUri' property and not both." ) elif self.definition: processed_definition = deepcopy(self.definition) substitutions = self._replace_dynamic_values_with_substitutions(processed_definition) if len(substitutions) > 0: if self.state_machine.DefinitionSubstitutions: self.state_machine.DefinitionSubstitutions.update(substitutions) else: self.state_machine.DefinitionSubstitutions = substitutions self.state_machine.DefinitionString = self._build_definition_string(processed_definition) elif self.definition_uri: self.state_machine.DefinitionS3Location = self._construct_definition_uri() else: raise InvalidResourceException( self.logical_id, "Either 'Definition' or 'DefinitionUri' property must be specified." ) if self.role and self.policies: raise InvalidResourceException( self.logical_id, "Specify either 'Role' or 'Policies' property and not both." ) elif self.role: self.state_machine.RoleArn = self.role elif self.policies: if not self.managed_policy_map: raise Exception("Managed policy map is empty, but should not be.") execution_role = self._construct_role() self.state_machine.RoleArn = execution_role.get_runtime_attr("arn") resources.append(execution_role) else: raise InvalidResourceException(self.logical_id, "Either 'Role' or 'Policies' property must be specified.") self.state_machine.StateMachineName = self.name self.state_machine.StateMachineType = self.type self.state_machine.LoggingConfiguration = self.logging self.state_machine.TracingConfiguration = self.tracing self.state_machine.Tags = self._construct_tag_list() event_resources = self._generate_event_resources() resources.extend(event_resources) return resources
def _construct_api_domain(self, http_api): """ Constructs and returns the ApiGateway Domain and BasepathMapping """ if self.domain is None: return None, None, None if self.domain.get("DomainName") is None or self.domain.get( "CertificateArn") is None: raise InvalidResourceException( self.logical_id, "Custom Domains only works if both DomainName and CertificateArn" " are provided.") self.domain["ApiDomainName"] = "{}{}".format( "ApiGatewayDomainNameV2", logical_id_generator.LogicalIdGenerator( "", self.domain.get("DomainName")).gen()) domain = ApiGatewayV2DomainName( self.domain.get("ApiDomainName"), attributes=self.passthrough_resource_attributes) domain_config = dict() domain.DomainName = self.domain.get("DomainName") domain.Tags = self.tags endpoint = self.domain.get("EndpointConfiguration") if endpoint is None: endpoint = "REGIONAL" # to make sure that default is always REGIONAL self.domain["EndpointConfiguration"] = "REGIONAL" elif endpoint not in ["REGIONAL"]: raise InvalidResourceException( self.logical_id, "EndpointConfiguration for Custom Domains must be one of {}.". format(["REGIONAL"]), ) domain_config["EndpointType"] = endpoint domain_config["CertificateArn"] = self.domain.get("CertificateArn") domain.DomainNameConfigurations = [domain_config] # Create BasepathMappings if self.domain.get("BasePath") and isinstance( self.domain.get("BasePath"), string_types): basepaths = [self.domain.get("BasePath")] elif self.domain.get("BasePath") and isinstance( self.domain.get("BasePath"), list): basepaths = self.domain.get("BasePath") else: basepaths = None basepath_resource_list = self._construct_basepath_mappings( basepaths, http_api) # Create the Route53 RecordSetGroup resource record_set_group = self._construct_route53_recordsetgroup() return domain, basepath_resource_list, record_set_group
def _construct_rest_api(self): """Constructs and returns the ApiGateway RestApi. :returns: the RestApi to which this SAM Api corresponds :rtype: model.apigateway.ApiGatewayRestApi """ rest_api = ApiGatewayRestApi(self.logical_id, depends_on=self.depends_on, attributes=self.resource_attributes) # NOTE: For backwards compatibility we need to retain BinaryMediaTypes on the CloudFormation Property # Removing this and only setting x-amazon-apigateway-binary-media-types results in other issues. rest_api.BinaryMediaTypes = self.binary_media rest_api.MinimumCompressionSize = self.minimum_compression_size if self.endpoint_configuration: self._set_endpoint_configuration(rest_api, self.endpoint_configuration) elif not RegionConfiguration.is_apigw_edge_configuration_supported(): # Since this region does not support EDGE configuration, we explicitly set the endpoint type # to Regional which is the only supported config. self._set_endpoint_configuration(rest_api, "REGIONAL") if self.definition_uri and self.definition_body: raise InvalidResourceException( self.logical_id, "Specify either 'DefinitionUri' or 'DefinitionBody' property and not both." ) if self.open_api_version: if not SwaggerEditor.safe_compare_regex_with_string( SwaggerEditor.get_openapi_versions_supported_regex(), self.open_api_version): raise InvalidResourceException( self.logical_id, "The OpenApiVersion value must be of the format '3.0.0'.") self._add_cors() self._add_auth() self._add_gateway_responses() self._add_binary_media_types() self._add_models() if self.definition_uri: rest_api.BodyS3Location = self._construct_body_s3_dict() elif self.definition_body: # # Post Process OpenApi Auth Settings self.definition_body = self._openapi_postprocess( self.definition_body) rest_api.Body = self.definition_body if self.name: rest_api.Name = self.name if self.description: rest_api.Description = self.description return rest_api
def _validate_jwt_authorizer(self): if not self.jwt_configuration: raise InvalidResourceException( self.api_logical_id, self.name + " OAuth2 Authorizer must define 'JwtConfiguration'.") if not self.id_source: raise InvalidResourceException( self.api_logical_id, self.name + " OAuth2 Authorizer must define 'IdentitySource'.")
def _validate_lambda_authorizer(self): if not self.function_arn: raise InvalidResourceException( self.api_logical_id, self.name + " Lambda Authorizer must define 'FunctionArn'.") if not self.authorizer_payload_format_version: raise InvalidResourceException( self.api_logical_id, self.name + " Lambda Authorizer must define 'AuthorizerPayloadFormatVersion'." )
def _add_auth(self): """ Add Auth configuration to the Swagger file, if necessary """ if not self.auth: return if self.auth and not self.definition_body: raise InvalidResourceException( self.logical_id, "Auth works only with inline Swagger specified in " "'DefinitionBody' property.") # Make sure keys in the dict are recognized if not all(key in AuthProperties._fields for key in self.auth.keys()): raise InvalidResourceException( self.logical_id, "Invalid value for 'Auth' property") if not SwaggerEditor.is_valid(self.definition_body): raise InvalidResourceException( self.logical_id, "Unable to add Auth configuration because " "'DefinitionBody' does not contain a valid Swagger definition.", ) swagger_editor = SwaggerEditor(self.definition_body) auth_properties = AuthProperties(**self.auth) authorizers = self._get_authorizers(auth_properties.Authorizers, auth_properties.DefaultAuthorizer) if authorizers: swagger_editor.add_authorizers_security_definitions(authorizers) self._set_default_authorizer( swagger_editor, authorizers, auth_properties.DefaultAuthorizer, auth_properties.AddDefaultAuthorizerToCorsPreflight, auth_properties.Authorizers, ) if auth_properties.ApiKeyRequired: swagger_editor.add_apikey_security_definition() self._set_default_apikey_required(swagger_editor) if auth_properties.ResourcePolicy: for path in swagger_editor.iter_on_path(): swagger_editor.add_resource_policy( auth_properties.ResourcePolicy, path, self.logical_id, self.stage_name) if auth_properties.ResourcePolicy.get("CustomStatements"): swagger_editor.add_custom_statements( auth_properties.ResourcePolicy.get("CustomStatements")) self.definition_body = self._openapi_postprocess( swagger_editor.swagger)