how to extract my appsync resolver from my sam template - amazon-web-services

I'm implementing substacks in my AWS serverless app, I would like to know if there is a way to extract the from my template of appsync resolvers the content that is in the field RequestMappingTemplate, and my ResponseMappingTemplate
currently the resolver resource looks like this
AppSyncResolver:
Type: "AWS::AppSync::Resolver"
Properties:
TypeName: "Query"
FieldName: "my_resolver"
DataSourceName: !GetAtt AppSyncDataSource.Name
RequestMappingTemplate: |
#set($SCHEMA = $ctx.args.schema)
#set($limit = $ctx.args.limit)
#set($table = ".customers")
#set($customers = $SCHEMA$table_ordendscto_lote)
{
"version": "2018-05-29",
"statements": [
"SELECT * FROM $customers LIMIT $limit ;"
]
}
ResponseMappingTemplate: |
## Raise a GraphQL field error in case of a datasource invocation error
#if($ctx.error)
$utils.error($ctx.error.message, $ctx.error.type)
#end
$utils.toJson($utils.rds.toJsonObject($ctx.result)[0])
Kind: "UNIT"
ApiId: !GetAtt AppSyncGraphQLApi.ApiId
and I wanted something like this
AppSyncResolver:
Type: "AWS::AppSync::Resolver"
Properties:
TypeName: "Query"
FieldName: "my_resolver"
DataSourceName: !GetAtt AppSyncDataSource.Name
RequestMappingTemplate: src/resolvers/my-resolver
ResponseMappingTemplate: src/resolvers/my-resolver-response
Kind: "UNIT"
ApiId: !GetAtt AppSyncGraphQLApi.ApiId
is it possible?

Related

Use Fn::FindInMap in DashboardBody in AWS CloudFormation

I am creating an AWS CloudWatch dashboard using CloudFormation and trying to use Fn::FindInMap in the DashboardBody body. However, the value is not getting replaced successfully. Can someone please help?
Below is my code:
Mappings:
EnvSource:
Dev:
"SMSFailed": sns/us-east-1/XXX/DirectPublishToPhoneNumber/Failure
Resources:
MonitoringDashboard:
Type: "AWS::CloudWatch::Dashboard"
Properties:
DashboardName:
Monitoring-Test-Dashboard
DashboardBody:
!Sub "{\"widgets\":[{\"height\":6,\"width\":12,\"y\":1,\"x\":12,\"type\":\"log\",\"properties\":{\"query\":\"SOURCE 'Fn::FindInMap' : [ 'ABC', 'DomainParameters', 'DomainName'] | fields #timestamp, #message, delivery.providerResponse, status\\n| filter #message like //\\n|stats count(status) as ErrorCount by delivery.providerResponse\\n| sort #timestamp asc\",\"region\":\"us-east-1\",\"title\":\"SMS Failed\",\"view\":\"table\"}}]}"
Output:
You can use a list form of Sub:
Resources:
MonitoringDashboard:
Type: "AWS::CloudWatch::Dashboard"
Properties:
DashboardName:
Monitoring-Test-Dashboard
DashboardBody:
!Sub
- "{\"widgets\":[{\"height\":6,\"width\":12,\"y\":1,\"x\":12,\"type\":\"log\",\"properties\":{\"query\":\"SOURCE ${FindInMap} | fields #timestamp, #message, delivery.providerResponse, status\\n| filter #message like //\\n|stats count(status) as ErrorCount by delivery.providerResponse\\n| sort #timestamp asc\",\"region\":\"us-east-1\",\"title\":\"SMS Failed\",\"view\":\"table\"}}]}"
- DomainName: !FindInMap [ 'ABC', 'DomainParameters', 'DomainName']

Is there no setting for AWS API Gateway REST API to disable execute-api endpoint in CloudFormation template?

I have setup an API Gateway (v1, not v2) REST API resource using CloudFormation template. Recently I have noticed that the default execute-api endpoint is also created, which I can disable in the settings.
The type of this API is AWS::ApiGateway::RestApi.
Naturally, I would like this to be done through the template, so the question is: can this setting be defined in the CloudFormation template, rather than havign to be clicked manually in the AWS Console? This option is available for the APIGateway V2 API resource (AWS::ApiGatewayV2::Api) but not the APIGateway V1 REST API resource (AWS::ApiGateway::RestApi) in the CloudFormation templates, even though it can be changed manuall for the APIGateway V1 REST API in the console.
There is also a CLI way of doing this for the AWS::ApiGateway::RestApi.
Here are some links I have used to search for this setting:
AWS::ApiGatewayV2::API
AWS::ApiGateway::RestApi
Disabling default api-execute endpoint via CLI
Support for disabling the default execute-api endpoint has recently been added to AWS::ApiGateway::RestApi cloudformation: DisableExecuteApiEndpoint
MyRestApi:
Type: 'AWS::ApiGateway::RestApi'
Properties:
DisableExecuteApiEndpoint: true
You can disable it though a simple custom resource. Below is an example of such a fully working template that does that:
Resources:
MyRestApi:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Description: A test API
Name: MyRestAPI
LambdaBasicExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
Path: /
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
MyCustomResource:
Type: Custom::DisableDefaultApiEndpoint
Properties:
ServiceToken: !GetAtt 'MyCustomFunction.Arn'
APIId: !Ref 'MyRestApi'
MyCustomFunction:
Type: AWS::Lambda::Function
Properties:
Handler: index.lambda_handler
Description: "Disable default API endpoint"
Timeout: 30
Role: !GetAtt 'LambdaBasicExecutionRole.Arn'
Runtime: python3.7
Code:
ZipFile: |
import json
import logging
import cfnresponse
import boto3
logger = logging.getLogger()
logger.setLevel(logging.INFO)
client = boto3.client('apigateway')
def lambda_handler(event, context):
logger.info('got event {}'.format(event))
try:
responseData = {}
if event['RequestType'] in ["Create"]:
APIId = event['ResourceProperties']['APIId']
response = client.update_rest_api(
restApiId=APIId,
patchOperations=[
{
'op': 'replace',
'path': '/disableExecuteApiEndpoint',
'value': 'True'
}
]
)
logger.info(str(response))
cfnresponse.send(event, context,
cfnresponse.SUCCESS, responseData)
else:
logger.info('Unexpected RequestType!')
cfnresponse.send(event, context,
cfnresponse.SUCCESS, responseData)
except Exception as err:
logger.error(err)
responseData = {"Data": str(err)}
cfnresponse.send(event,context,
cfnresponse.FAILED,responseData)
return
In case anyone stumbles across this answer that is using CDK, this can be done concisely (without defining a Lambda function) using the AwsCustomResource construct:
const restApi = new apigw.RestApi(...);
const executeApiResource = new cr.AwsCustomResource(this, "execute-api-resource", {
functionName: "disable-execute-api-endpoint",
onCreate: {
service: "APIGateway",
action: "updateRestApi",
parameters: {
restApiId: restApi.restApiId,
patchOperations: [{
op: "replace",
path: "/disableExecuteApiEndpoint",
value: "True"
}]
},
physicalResourceId: cr.PhysicalResourceId.of("execute-api-resource")
},
policy: cr.AwsCustomResourcePolicy.fromStatements([new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["apigateway:PATCH"],
resources: ["arn:aws:apigateway:*::/*"],
})])
});
executeApiResource.node.addDependency(restApi);
You can disable it in AWS CDK. This is done by finding the CloudFormation resource and setting it to true.
const api = new apigateway.RestApi(this, 'api', );
(api.node.children[0] as apigateway.CfnRestApi).addPropertyOverride('DisableExecuteApiEndpoint','true')
Here is a Python variant of the answer provided by snorberhuis.
rest_api = apigateway.RestApi(self,...)
cfn_apigw = rest_api.node.default_child
cfn_apigw.add_property_override('DisableExecuteApiEndpoint', True)
Amazon's docs on "Abstractions and Escape Hatches" is very good for understanding what's going on here.

Receiving Error Property validation failure: [Value of property {/TableInput/ViewOriginalText} does not match type {String}] AWS cloudformation

I am trying to create external view using the data from the tables but receiving error Property validation failure: [Value of property {/TableInput/ViewOriginalText} does not match type {String}] while creating views in AWS glue using cloud formation.
Any idea about resolution would be appreciated. Below is the yaml file code snippet that I am using.
Thanks in Advance
---
AWSTemplateFormatVersion: 2010-09-09
Description: "Glue Athena database and table configuration"
Parameters:
MarketingAndSalesDatabaseName:
Type: String
MinLength: "4"
Default: "marketingandsales_qs"
Description: "Name of the AWS Glue database to contain this CloudFormation template's tables."
SalesPipelineTableName:
Type: String
MinLength: "4"
Default: "salespipeline_qs"
Description: "Name of the Sales Pipeline data table in AWS Glue."
Resources:
### AWS GLUE RESOURCES ###
MarketingAndSalesDatabase:
Type: "AWS::Glue::Database"
Properties:
DatabaseInput:
Description: "Marketing and Sales database (Amazon QuickSight Samples)."
Name: !Ref MarketingAndSalesDatabaseName
CatalogId: !Ref AWS::AccountId
SalesPipelineTable:
Type: "AWS::Glue::Table"
DependsOn: MarketingAndSalesDatabase
Properties:
TableInput:
Description: "Sales Pipeline table (Amazon QuickSight Sample)."
TableType: "VIRTUAL_VIEW"
Parameters: {
"CrawlerSchemaDeserializerVersion": "1.0",
"compressionType": "none",
"classification": "csv",
"recordCount": "16831",
"typeOfData": "file",
"CrawlerSchemaSerializerVersion": "1.0",
"columnsOrdered": "true",
"objectCount": "1",
"delimiter": ",",
"skip.header.line.count": "1",
"averageRecordSize": "119",
"sizeKey": "2002910",
"presto_view": "true"
}
StorageDescriptor:
StoredAsSubDirectories: False
Parameters: {
"CrawlerSchemaDeserializerVersion": "1.0",
"compressionType": "none",
"classification": "csv",
"recordCount": "16831",
"typeOfData": "file",
"CrawlerSchemaSerializerVersion": "1.0",
"columnsOrdered": "true",
"objectCount": "1",
"delimiter": ",",
"skip.header.line.count": "1",
"averageRecordSize": "119",
"sizeKey": "2002910"
}
InputFormat: "org.apache.hadoop.mapred.TextInputFormat"
OutputFormat: "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"
Columns:
- Type: string
Name: salesperson
- Type: string
Name: lead_name
ViewOriginalText:
'Fn::Sub':
- '/* Presto View: ${View} */'
- View:
'Fn::Base64': !sub '{"catalog": "awsdatacatalog",
"schema":"default",
"columns": [ { "name": "salesperson", "type": "varchar" }, { "name": "lead_name", "type": "varchar" }],
"originalSql": "SELECT salesperson AS salesperson, lead_name AS lead_name FROM marketing_qs_1"
}'
Retention: 0
Name: !Ref SalesPipelineTableName
DatabaseName: !Ref MarketingAndSalesDatabaseName
CatalogId: !Ref AWS::AccountId
These properties should be underneath StorageDescriptor instead of ViewOriginalText:
Compressed: False
Location: !Sub "s3://${DataBucketName}/sales/"
Recommend trying the CloudFormation Linter in VSCode to see some of these errors inline while authoring templates:
[cfn-lint] E3002: Property is an object instead of String at Resources/SalesPipelineTable/Properties/TableInput/ViewOriginalText
Sub also isn't required inside that Base64:
[cfn-lint] W1020: Fn::Sub isn't needed because there are no variables at Resources/SalesPipelineTable/Properties/TableInput/ViewOriginalText/Fn::Sub/1/View/Fn::Base64/Fn::Sub

!GetAtt not working with !Sub in cloudformation

I'm trying to create some resources using Cloudformation with serverless framework, In which I need to substitute resource name from another resource. Tried to use !Sub but still I couldn't get Arn of another resource created.
Tried all the approaches in this stackoverflow question How to use Sub and GetAtt functions at the same time in CloudFormation template? to no avail.
I appreciate any help.
Resources:
BasicParameter:
Type: AWS::SSM::Parameter
Properties:
Name: /data/config-name
Type: String
Value:
Fn::Base64:
!Sub |
{
"filter_configs": [{
"stream_name": !GetAtt tpRecordDeliveryStream.Arn,
"events": [
{
"name": "event_name1",
"stream": "streamname1"
},
{
"name": "event_name2"
}
]
}]
}
Description: Configuration for stream filters
Tags:
project: projectname
team: data
owner: owner_name
This was resolved by using serverless-pseudo-parameters serverless plugin. Serverless framework also uses ${} placeholder and it conflicts with Cloudformation placeholders. serverless-pseudo-parameters solves that by allowing us to replace those place holders with #{} which are replaced during sls deploy with cloud formation templates
Resources:
streamConfig:
Type: AWS::SSM::Parameter
Properties:
Name: config_name
Type: String
Value:
Fn::Base64: |
{
"filter_configs": [{
"firehose_stream_arn": "#{tpRecordDeliveryStream.Arn}",
"events": [
{
"name": "config0",
"filter1": "value1"
},
{
"name": "config1"
}
]
}]
}
Description: Configuration for stream filters
Since you have !Sub |, instead of
"stream_name": !GetAtt tpRecordDeliveryStream.Arn,
the following should be enough
"stream_name": "${tpRecordDeliveryStream.Arn}"
The alternative using !Sub in array notation:
Value:
Fn::Base64:
!Sub
- |
{
"filter_configs": [{
"stream_name": "${tpRecordDeliveryStreamArn}",
"events": [
{
"name": "event_name1",
"stream": "streamname1"
},
{
"name": "event_name2"
}
]
}]
}
- tpRecordDeliveryStreamArn: !GetAtt tpRecordDeliveryStream.Arn

Creating Cognito User Pool With Custom Domain name from AWS CDK

I'm trying to creating Cognito user pool with a custom domain name through AWS CDK. I manage to get everyting working untill to the point where I needed to create an A record in the Rout53 hosted zone. I searched through all the documents but coudn't find a way to do that. Following is my code. Any help would be much appriciated.
const cfnUserPool = new CfnUserPool(this, 'MyCognitoUserPool', {
userPoolName: 'MyCognitoUserPool',
adminCreateUserConfig: {
allowAdminCreateUserOnly: false
},
policies: {
passwordPolicy: {
minimumLength: 8,
requireLowercase: true,
requireNumbers: true,
requireSymbols: true,
requireUppercase: true,
temporaryPasswordValidityDays: 30
}
},
usernameAttributes: [
UserPoolAttribute.EMAIL
],
schema: [
{
attributeDataType: 'String',
name: UserPoolAttribute.EMAIL,
mutable: true,
required: true
},
{
attributeDataType: 'String',
name: UserPoolAttribute.FAMILY_NAME,
mutable: false,
required: true
},
{
attributeDataType: 'String',
name: UserPoolAttribute.GIVEN_NAME,
mutable: false,
required: true
}
]
});
const cognitoAppDomain = new CfnUserPoolDomain(this, "PigletAuthDomainName", {
domain: authDomainName,
userPoolId: cfnUserPool.ref,
customDomainConfig: {
certificateArn: 'ACM Certificate arn'
}
});
/*
TODO: Create an A record from the created cnfUserPoolDomain
*/
Everything works up untill to this point. Now the question is how to create an A record using the CfnUserPoolDomain
Any help is much appriciated.
Update May 2020
The UserPoolDomain construct has been extended and a UserPoolDomainTarget was added to provide this functionality.
Now, all you need to do is the following:
const userPoolDomain = new cognito.UserPoolDomain(this, 'UserPoolDomain', {
userPool,
customDomain: {
domainName: authDomainName,
certificate,
},
});
new route53.ARecord(this, 'UserPoolCloudFrontAliasRecord', {
zone: hostedZone,
recordName: authDomainName,
target: route53.RecordTarget.fromAlias(new route53_targets.UserPoolDomainTarget(userPoolDomain)),
});
I had the same Problem, It looks like CloudFormation does not have a return parameter for the CfnUserPoolDomain AliasTarget. Which means the cdk can not provide this parameter either.
I ended up implementing it using the AWS SDK (npm install aws-sdk) and getting the value using the APIs:
Update: The better solution is to use the AwsCustomResource. You can see a detailed example in aws/aws-cdk (#6787):
const userPoolDomainDescription = new customResources.AwsCustomResource(this, 'user-pool-domain-description', {
onCreate: {
physicalResourceId: 'user-pool-domain-description',
service: 'CognitoIdentityServiceProvider',
action: 'describeUserPoolDomain',
parameters: {
Domain: userPoolDomain.domain
}
}
});
const dnsName = userPoolDomainDescription.getData('DomainDescription.CloudFrontDistribution').toString();
// Route53 alias record for the UserPoolDomain CloudFront distribution
new route53.ARecord(this, 'UserPoolDomainAliasRecord', {
recordName: userPoolDomain.domain,
target: route53.RecordTarget.fromAlias({
bind: _record => ({
hostedZoneId: 'Z2FDTNDATAQYW2', // CloudFront Zone ID
dnsName: dnsName,
}),
}),
zone,
})
Here's how to get around it. Assuming you have a stack.yaml that you deploy with a CI tool, say through bash:
THE_STACK_NAME="my-cognito-stack"
THE_DOMAIN_NAME="auth.yourveryowndomain.org"
# get the alias target
# notice that it will be empty upon first launch (chicken and the egg problem)
ALIAS_TARGET=$(aws cognito-idp describe-user-pool-domain --domain ${THE_DOMAIN_NAME} | grep CloudFrontDistribution | cut -d \" -f4)
# create/update the deployment CloudFormation stack
# notice the AliasTarget parameter (which can be empty, it's okay!)
aws cloudformation deploy --stack-name ${THE_STACK_NAME} --template-file stack.yaml --parameter-overrides AliasTarget=${ALIAS_TARGET} DomainName=${THE_DOMAIN_NAME}
The stack.yaml minimal version (remember to fill the UserPool config):
---
AWSTemplateFormatVersion: 2010-09-09
Parameters:
DomainName:
Type: String
Default: auth.yourveryowndomain.org
Description: The domain name to use to serve this project.
ZoneName:
Type: String
Default: yourveryowndomain.org
Description: The hosted zone name coming along with the DomainName used.
AliasTarget: # no default value, can be empty
Type: String
Description: The UserPoolDomain alias target.
Conditions: # here's "the trick"
HasAliasTarget: !Not [!Equals ['', !Ref AliasTarget]]
Resources:
Certificate:
Type: "AWS::CertificateManager::Certificate"
Properties:
DomainName: !Ref ZoneName
DomainValidationOptions:
- DomainName: !Ref ZoneName
ValidationDomain: !Ref ZoneName
SubjectAlternativeNames:
- !Ref DomainName
UserPool:
Type: AWS::Cognito::UserPool
Properties:
[... fill that with your configuration! ...]
UserPoolDomain:
Type: AWS::Cognito::UserPoolDomain
Properties:
UserPoolId: !Ref UserPool
Domain: !Ref DomainName
CustomDomainConfig:
CertificateArn: !Ref Certificate
DnsRecord: # if AliasTarget parameter is empty, well we just can't do that one!
Condition: HasAliasTarget # and here's how we don't do it when we can't
Type: AWS::Route53::RecordSet
Properties:
HostedZoneName: !Sub "${ZoneName}."
AliasTarget:
DNSName: !Ref AliasTarget
EvaluateTargetHealth: false
# HostedZoneId value for CloudFront is always this one
# see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-route53-aliastarget.html
HostedZoneId: Z2FDTNDATAQYW2
Name: !Ref DomainName
Type: A
Be aware CloudFormation conditions are not "a trick" at all: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/conditions-section-structure.html. We simply use it as a trick along with the "first launch won't do it all" to get around our scenario.
Kinda weird, but only for the first run! Launch it again: everything is fine.
PS: can't wait to avoid all that by simply having the CloudFrontDistribution alias target directly in the AWS::Cognito::UserPoolDomain return values!!