AWS Cloudformation "Include" transform issue - amazon-web-services

AWS Cloudformation defines Transform section on global template level as an array.
See Transform section definition here.
I created two entries in Transform section that include some partial definitions of stack resources.
The goal is to create a few files with definitions/resources grouped by "product domain" per file.
When I create stack based on this template it executes one partial definition only (the last one - other definitions with "AWS::Include" name are ignored or "overridden")
This is the main template definition:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Test of json file inclusion",
"Parameters": {
"Environment": {
"Type": "String",
"Description": "Specify Environment: prod | dev ",
"AllowedValues": ["prod", "dev"],
"Default": "dev"
}
},
"Transform": [
{
"Name": "AWS::Include",
"Parameters": {
"Location": "s3://a1-local/cf-tests/part-1.json"
}
},
{
"Name": "AWS::Include",
"Parameters": {
"Location": "s3://a1-local/cf-tests/part-2.json"
}
}
],
"Outputs": {
}
}
This is part-1 definition
{
"Mappings": {
"MappingForBucket1": {
"eu-west-1": { "AZs": [ "eu-west-1a", "eu-west-1b" ] }
}
},
"Resources": {
"hellobucket1": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": { "Fn::Sub": "as-testbucket1-${Environment}" }
}
}
}
}
This is part-2 definition
{
"Mappings": {
"MappingForBucket2": {
"eu-west-1": { "AZs": [ "eu-west-1a", "eu-west-1b" ] }
}
},
"Resources": {
"hellobucket2": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": { "Fn::Sub": "as-testbucket2-${Environment}" }
}
}
}
}
How to join/chain multiple transformations in such case?

Related

CodePipeline, CodeBuild, CloudFormation, Lambda: build multiple lambdas in a single build and assign their code correctly

I have a CD pipeline built with AWS CDK and CodePipeline. It compiles the code for 5 lambdas, each of which it pushes to a secondary artifact.
The S3 locations of each of the artifacts are assigned to the parameters of a CloudFormation template which are attached to the Code parts of the lambdas.
This is working fine!
My problem is, I cannot add a sixth secondary artifact to CodeBuild (hard limit). I also cannot combine all of my lambda code into a single artifact as (as far as I can see) CodePipeline is not smart enough to look inside an artifact when assigning Code to a lambda in CloudFormation.
What is the recommendation for deploying multiple lambdas from a CodeBuild/CodePipeline? How have other people solved this issue?
EDIT: Code pipeline CF template
note: I have only included 2 lambdas as an example
Lambda application template
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"Lambda1": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "lambda1SourceBucketNameParameter3EE73025"
},
"S3Key": {
"Ref": "lambda1SourceObjectKeyParameter326E8288"
}
}
}
},
"Lambda2": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": {
"Ref": "lambda2SourceBucketNameParameter3EE73025"
},
"S3Key": {
"Ref": "lambda2SourceObjectKeyParameter326E8288"
}
}
}
}
},
"Parameters": {
"lambda1SourceBucketNameParameter3EE73025": {
"Type": "String"
},
"lambda1SourceObjectKeyParameter326E8288": {
"Type": "String"
},
"lambda2SourceBucketNameParameterA0D2319B": {
"Type": "String"
},
"lambda2SourceObjectKeyParameterF3B3F2C2": {
"Type": "String"
}
}
}
Code Pipeline template:
{
"Resources": {
"Pipeline40CE5EDC": {
"Type": "AWS::CodePipeline::Pipeline",
"Properties": {
"Stages": [
{
"Actions": [
{
"ActionTypeId": {
"Provider": "CodeBuild"
},
"Name": "CDK_Build",
"OutputArtifacts": [
{
"Name": "CdkbuildOutput"
}
],
"RunOrder": 1
},
{
"ActionTypeId": {
"Provider": "CodeBuild"
},
"Name": "Lambda_Build",
"OutputArtifacts": [
{ "Name": "CompiledLambdaCode1" },
{ "Name": "CompiledLambdaCode2" }
],
"RunOrder": 1
}
],
"Name": "Build"
},
{
"Actions": [
{
"ActionTypeId": {
"Provider": "CloudFormation"
},
"Configuration": {
"StackName": "DeployLambdas",
"ParameterOverrides": "{\"lambda2SourceBucketNameParameterA0D2319B\":{\"Fn::GetArtifactAtt\":[\"CompiledLambdaCode1\",\"BucketName\"]},\"lambda2SourceObjectKeyParameterF3B3F2C2\":{\"Fn::GetArtifactAtt\":[\"CompiledLambdaCode1\",\"ObjectKey\"]},\"lambda1SourceBucketNameParameter3EE73025\":{\"Fn::GetArtifactAtt\":[\"CompiledLambdaCode2\",\"BucketName\"]},\"lambda1SourceObjectKeyParameter326E8288\":{\"Fn::GetArtifactAtt\":[\"CompiledLambdaCode2\",\"ObjectKey\"]}}",
"ActionMode": "CREATE_UPDATE",
"TemplatePath": "CdkbuildOutput::CFTemplate.template.json"
},
"InputArtifacts": [
{ "Name": "CompiledLambdaCode1" },
{ "Name": "CompiledLambdaCode2" },
{ "Name": "CdkbuildOutput" }
],
"Name": "Deploy",
"RunOrder": 1
}
],
"Name": "Deploy"
}
],
"ArtifactStore": {
"EncryptionKey": "the key",
"Location": "the bucket",
"Type": "S3"
},
"Name": "Pipeline"
}
}
}
}
Reviewed templates.
So, I don't see five inputs to a CodeBuild action, but I do see 2 inputs to a CloudFormation action (Deploy).
I assume your problem was a perceived limit of 5 input to the CloudFormation action. Is that assumption correct?
The limits for a CloudFormation action are actually 10. See "Action Type Constraints for Artifacts
" # https://docs.aws.amazon.com/codepipeline/latest/userguide/reference-pipeline-structure.html#reference-action-artifacts
So if you can use up to 10, will that suffice?
If not, I have other ideas that would take a lot longer to document.

Import s3 bucket from one stack to other stack

I have created S3 Bucket with deletepolicy retain using cloud formation, I Have exported the created bucket using Export in outputs in cloudformation.
Now I want to use the same s3 bucket in another stack using import
Cloud formation for s3:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Creates an S3 bucket to be used for static content/website hosting.",
"Parameters": {
"AssetInsightId": {
"Description": "Asset Insight ID",
"Type": "String",
"Default": "206153"
},
"ResourceOwner": {
"Description": "tr:resource-owner",
"Type": "String",
"Default": "####"
},
"EnvironmentType": {
"Description": "tr:environment-type",
"Default": "preprod",
"Type": "String",
"AllowedValues": ["preprod", "prod"],
"ConstraintDescription": "must specify preprod, prod."
}
},
"Resources": {
"S3Bucket": {
"Type": "AWS::S3::Bucket",
"DeletionPolicy": "Retain",
"Properties": {
"BucketName": {
"Fn::Sub": "a${AssetInsightId}-s3bucket-${EnvironmentType}"
},
"Tags": [{
"Key": "tr:application-asset-insight-id",
"Value": {
"Fn::Sub": "${AssetInsightId}"
}
}, {
"Key": "tr:environment-type",
"Value": {
"Fn::Sub": "${EnvironmentType}"
}
}
]
}
}
},
"Outputs": {
"S3Bucket": {
"Description": "Information about the value",
"Description": "Name of the S3 Resource Bucket",
"Value": "!Ref S3Bucket",
"Export": {
"Name": "ExportS3Bucket"
}
}
}
}
cloud formation to use created s3 bucket from another template with import
Second template :
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Creates an S3 apigateway to be used for static content/website hosting.",
"Parameters": {
"AssetInsightId": {
"Description": "Asset Insight ID",
"Type": "String",
"Default": "206153"
},
"ResourceOwner": {
"Description": "tr:resource-owner",
"Type": "String",
"Default": "swathi.koochi#thomsonreuters.com"
},
"EnvironmentType": {
"Description": "tr:environment-type",
"Default": "preprod",
"Type": "String",
"AllowedValues": ["preprod", "prod"],
"ConstraintDescription": "must specify preprod, prod."
},
"endpointConfiguration": {
"Description": "tr:endpoint-configuration",
"Default": "REGIONAL",
"Type": "String",
"AllowedValues": ["REGIONAL", "EDGE"],
"ConstraintDescription": "must specify REGIONAL, EDGE."
}
},
"Resources": {
"S3BucketImport": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": {"Fn::ImportValue" : "ExportS3Bucket"}
}
},
"APIGateWayRestResourceRestApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": "MyAPI",
"Description": "API Gateway rest api with cloud formation",
"EndpointConfiguration": {
"Types": [{
"Ref": "endpointConfiguration"
}
]
}
}
},
"APIGateWayResource": {
"Type": "AWS::ApiGateway::Resource",
"Properties": {
"RestApiId": {
"Ref": "APIGateWayRestResourceRestApi"
},
"ParentId": {
"Fn::GetAtt": ["APIGateWayRestResourceRestApi", "RootResourceId"]
},
"PathPart": "test"
}
},
"APIGatewayPostMethod": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"AuthorizationType": "NONE",
"HttpMethod": "POST",
"Integration": {
"Type": "AWS_PROXY",
"IntegrationHttpMethod": "POST",
"Uri": {
"Fn::Sub": "arn:aws:apigateway:us-east-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-2:861756181523:function:GreetingLambda/invocations"
}
},
"MethodResponses": [{
"ResponseModels": {
"application/json": {
"Ref": "PostMethodResponse"
}
},
"StatusCode": 200
}
],
"ResourceId": {
"Ref": "APIGateWayResource"
},
"RestApiId": {
"Ref": "APIGateWayRestResourceRestApi"
}
}
},
"PostMethodResponse": {
"Type": "AWS::ApiGateway::Model",
"Properties": {
"ContentType": "application/json",
"Name": "PostMethodResponse",
"RestApiId": {
"Ref": "APIGateWayRestResourceRestApi"
},
"Schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "PostMethodResponse",
"type": "object",
"properties": {
"Email": {
"type": "string"
}
}
}
}
},
"RestApiDeployment": {
"DependsOn": "APIGatewayPostMethod",
"Type": "AWS::ApiGateway::Deployment",
"Properties": {
"RestApiId": {
"Ref": "APIGateWayRestResourceRestApi"
}
}
},
"RestAPIStage": {
"Type": "AWS::ApiGateway::Stage",
"Properties": {
"DeploymentId": {
"Ref": "RestApiDeployment"
},
"MethodSettings": [{
"DataTraceEnabled": true,
"HttpMethod": "*",
"ResourcePath": "/*"
}
],
"RestApiId": {
"Ref": "APIGateWayRestResourceRestApi"
},
"StageName": "Latest"
}
},
"APIGateWayDomainName": {
"Type": "AWS::ApiGateway::DomainName",
"Properties": {
"CertificateArn": {
"Ref": "myCertificate"
},
"DomainName": {
"Fn::Join": [".", [{
"Ref": "AssetInsightId"
}, {
"Ref": "EnvironmentType"
}, "api"]]
},
"EndpointConfiguration": {
"Types": [{
"Ref": "endpointConfiguration"
}
]
}
}
},
"myCertificate": {
"Type": "AWS::CertificateManager::Certificate",
"Properties": {
"DomainName": {
"Fn::Join": [".", [{
"Ref": "AssetInsightId"
}, {
"Ref": "EnvironmentType"
}, "api"]]
}
}
}
}
}
when I/m trying to import using Import Value, I'm getting error saying
S3BucketImport
CREATE_FAILED Bad Request (Service: Amazon S3; Status Code: 400; Error Code: 400 Bad Request; Request ID: 9387EBE0E472E559; S3 Extended Request ID: o8EbE20IOoUgEMwXc7xVjuoyQT03L/nnQ7AsC94Ff1S/PkE100Imeyclf1BxYeM0avuYjDWILxA=)
As #Jarmod correctly pointed out,
In your first template, export the s3 bucket name using { "Ref" : ",S3Bucket" }
In your second template, you don't have to create the bucket again.you can use the exported value from the first template if you want to refer the bucket name from resources. But i don't see any of the resources in the second template refer the S3 bucket name.

Passing values from CloudFormation to Swagger file

I'm creating my infrastructure using CloudFormation.
I'm hoping to create the API using "API Gateway Extensions to OpenAPI"
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-swagger-extensions.html
Relevant code segment looks like this.
"MorningApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Description": {
"Fn::Sub": "${Stage}-MorningApi"
},
"Name": {
"Fn::Sub": "${Stage}-MorningApi"
},
"EndpointConfiguration": {
"Types": [
"REGIONAL"
]
},
"BodyS3Location": {
"Bucket" : "cf-morning",
"Key" : "nested-templates-stack/MorningApiStatusStackSwagger.json"
}
}
},
"MorningApiloadBalancer": {
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"Properties": {
"Name": {
"Fn::Sub": "${Stage}-MorningApiloadBalancer"
},
"Subnets": [
{
"Ref": "PrivateASubnet"
},
{
"Ref": "PrivateBSubnet"
},
{
"Ref": "PrivateCSubnet"
}
],
"Type": {
"Ref": "ELBType"
},
"Scheme": "internal",
"IpAddressType": {
"Ref": "ELBIpAddressType"
}
}
}
I need to pass "DNSName" of the "MorningApiloadBalancer" to the swagger file located in the S3 location. I cannot find a way to do this.
Any help is appreciated.
I haven't tried using swagger with API gateway before.
Using Fn::Transform
Using the Swagger file stored in S3 using the Fn::Tranform macro, it basically takes the content of the swagger file and tranforms into cloudformation.
{
"APIGateway": {
"Type": "AWS::ApiGateway::RestApi",
"Name": "myapi",
"Properties": {
"Body": {
"Fn::Transform": {
"Name": "AWS::Include",
"Parameters": {
"Location": {
"Fn::Sub": "s3://${AWS::AccountId}-swagger-files/${SwaggerFile}"
}
}
}
}
}
}
}
Reference:
https://medium.com/#nabtechblog/integrating-swagger-with-aws-lambda-and-api-gateway-using-cloud-formation-macro-functions-7432dec50dd
Inline swagger definition
I saw an example where the swagger definition is embedded into the cloudformation template. If you do so, you can use intrinsic functions inside the swagger definition. In your case, you can use Ref to get the dns name of the load balancer.
"GreetingApi": {
"Type": "AWS::ApiGateway::RestApi",
"Properties": {
"Name": "Greeting API",
"Description": "API used for Greeting requests",
"FailOnWarnings": true,
"Body": {
"swagger": "2.0",
"paths": {
"/greeting": {
"get": {
"x-amazon-apigateway-integration": {
"uri": {
"Fn::Join": [
"",
[
"arn:aws:apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [
"GreetingLambda",
"Arn"
]
},
"/invocations"
]
]
}
}
}
}
}
}
}
}
Reference:
https://blog.jayway.com/2016/09/18/introduction-swagger-cloudformation-api-gateway/
Getting DNS Name
I think you know this already.
"Fn::GetAtt" : [ "MorningApiloadBalancer" , "DNSName" ]
Hope this helps.

Cloudformation Property validation failure: Encountered unsupported properties

I'm trying to create a nested stack with the root stack looks like this:
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"DynamoDBTable": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"Parameters": {
"TableName": {
"Fn::Sub": "${AWS::StackName}"
}
},
"TemplateURL": "https://s3.amazonaws.com/my-templates-bucket/dynamodb.json"
}
},
"S3WebsiteReact": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"Parameters": {
"BucketName": {
"Fn::Sub": "${AWS::StackName}-website"
}
},
"TemplateURL": "https://s3.amazonaws.com/my-templates-bucket/s3-static-website-react.json"
}
},
"S3UploadBucket": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"Parameters": {
"BucketName": {
"Fn::Sub": "${AWS::StackName}-upload"
}
},
"TemplateURL": "https://s3.amazonaws.com/my-templates-bucket/s3-with-cors.json"
}
},
"Cognito": {
"Type": "AWS::CloudFormation::Stack",
"DependsOn": "DynamoDBTable",
"Properties": {
"Parameters": {
"CognitoUserPoolName": {
"Fn::Join" : ["",
{
"Fn::Split": ["-", {
"Ref": "AWS::StackName"
}]
}
]
}
},
"TemplateURL": "https://s3.amazonaws.com/my-templates-bucket/cognito.json"
}
},
"ApiGateway": {
"Type": "AWS::CloudFormation::Stack",
"DependsOn": ["DynamoDBTable", "Cognito"],
"Properties": {
"Parameters": {
"ApiGatewayName": {
"Fn::Sub": "${AWS::StackName}-api"
},
"CognitoUserPoolArn": {
"Fn::GetAtt": [ "Cognito", "Outputs.UserPoolArn" ]
},
"DynamoDBStack": {
"Fn::GetAtt": [ "DynamoDBTable", "Outputs.DDBStackName" ]
}
},
"TemplateURL": "https://s3.amazonaws.com/my-templates-bucket/api-gateway.json"
}
},
"IdentityPool": {
"Description": "Cognito Identity Pool. Must be created after User Pool and API Gateway.",
"Type": "AWS::Cognito::IdentityPool",
"DependsOn": ["Cognito", "ApiGateway", "S3UploadBucket"],
"Properties": {
"Parameters": {
"AppClientId": {
"Fn::GetAtt": [ "Cognito", "Outputs.AppClientId" ]
},
"UserPoolProviderName": {
"Fn::GetAtt": [ "Cognito", "Outputs.ProviderName" ]
},
"UserPoolName": {
"Fn::GetAtt": [ "Cognito", "Outputs.UserPoolName" ]
},
"UploadBucketName": {
"Fn::GetAtt": [ "S3UploadBucket", "Outputs.UploadBucketName" ]
},
"ApiGatewayId": {
"Fn::GetAtt": [ "ApiGateway", "Outputs.ApiGatewayId" ]
}
},
"TemplateURL": "https://s3.amazonaws.com/my-templates-bucket/identity-pool.json"
}
}
},
"Outputs": {
}
}
And I get this error:
2019-06-19 14:45:14 UTC-0400 IdentityPool CREATE_FAILED Property validation failure: [Encountered unsupported properties in {/}: [TemplateURL, Parameters]]
It looks like my identity pool stack has some issues with the parameters. But the identity pool stack parameters look like this:
"Parameters" : {
"AppClientId": {
"Description": "ID of the App Client of the Cognito User Pool passed into this stack.",
"Type": "String"
},
"UserPoolProviderName": {
"Description": "Cognito User Pool Provider name passed into this stack.",
"Type": "String"
},
"UserPoolName": {
"Description": "Cognito User Pool Name passed into this stack.",
"Type": "String"
},
"UploadBucketName": {
"Description": "Name of the bucket that is used to upload files to.",
"Type": "String"
},
"ApiGatewayId": {
"Description": "ID of the API Gateway created for the stack.",
"Type": "String"
}
},
The funny thing is: I tried creating each stack on its own, then passed the outputs from them as parameters to the stacks that need those parameters and every single stack was created successfully without any problems.
I've tried to look for what is unsupported but was unable to find any answers.
The error:
[Encountered unsupported properties in {/}: [TemplateURL, Parameters]]
Says that those two properties are unsupported. Unlike all the rest of the resources declared in your template which also use those two properties, this resource is a AWS::Cognito::IdentityPool, while the rest are all of type AWS::CloudFormation::Stack.
Those two properties are only valid on the AWS::CloudFormation::Stack type, hence the validation error.

Can't send parameters from a child CloudFormation template to another child template

I am crafting a monstrosity of a CloudFormation template that uses the AWS::CloudFormation::Stack resource to spawn other nested stacks. The purpose of this is the classic separation of duties and management.
The overall goal is to build an application environment from the ground up (starting with VPC and ending with the application instances).
So far, most of it works. But I've hit a road block with referencing the outputs of the subnet CF template and the securitygroup CF template for use in creating the EC2 instances.
It works like this:
Master template --> Builds VPC --> Calls child template to build subnets --> Calls child template to build security groups --> Calls child template to build EC2 instances
I need to pass the outputs from the subnet and security group templates to the EC2 instance template so the instances can be provisioned into the correct part of the architecture. The VPC ID ref and KeyPairs pass in fine, but the subnetID and securitygroupID do not.
Here's the portion of the master template that calls the security group/subnet templates:
"DevNetworkStack": {
"Type": "AWS::CloudFormation::Stack",
"DependsOn": [
"SNVPC",
"SNIGW"
],
"Properties": {
"TemplateURL": {
"Fn::FindInMap": [
"TemplateURLs",
"DevNetworkStack",
"URL"
]
},
"TimeoutInMinutes": "30",
"Parameters": {
"VPCID": {
"Ref": "SNVPC"
},
"SNIGW": {
"Ref": "SNIGW"
}
}
}
},
"DevSecurityGroupsStack": {
"Type": "AWS::CloudFormation::Stack",
"DependsOn": "DevNetworkStack",
"Properties": {
"TemplateURL": {
"Fn::FindInMap": [
"TemplateURLs",
"DevSecurityGroupsStack",
"URL"
]
},
"TimeoutInMinutes": "30",
"Parameters": {
"VPCID": {
"Ref": "SNVPC"
}
}
}
},
These work fine. They create and everything is fine. The templates offer outputs like so:
"Outputs": {
"DevAdminSubnetID": {
"Description": "DevAdminSubnetID",
"Value": {
"Ref": "DevAdminSubnet"
}
},
...
"Outputs": {
"DevAdminSecurityGroupID": {
"Description": "DevAdminSecurityGroupID",
"Value": {
"Ref": "DevAdminSecurityGroup"
}
},
I can see the outputs in the CF console.
Now, the next template is trying to use the Security Group ID and the Subnet ID. But it's not working.
Master template calls the next child as:
"DevAdminStack": {
"Type": "AWS::CloudFormation::Stack",
"DependsOn": [
"DevNetworkStack",
"DevSecurityGroupsStack",
"EC2DevRoleInstanceProfile",
"S3DevUserDataBucket",
"S3DevHomeDirsDataBucket"
],
"Properties": {
"TemplateURL": {
"Fn::FindInMap": [
"TemplateURLs",
"DevAdminStack",
"URL"
]
},
"TimeoutInMinutes": "30",
"Parameters": {
"AdminKeyPair": {
"Ref": "AdminServersKeyPair"
},
"VPCID": {
"Ref": "SNVPC"
},
"DevAdminSecurityGroupID": [
{
"Fn::GetAtt": [
"DevSecurityGroupsStack",
"Outputs.DevAdminSecurityGroupID"
]
}
],
"DevAdminSubnetID": [
{
"Fn::GetAtt": [
"DevNetworkStack",
"Outputs.DevAdminSubnetID"
]
}
]
}
}
}
...and the child template looks like this (removed some portions for brevity because I'm just testing right now)
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Dev-Admin",
"Mappings": {
"RegionMap": {
"DevAdminServer": {
"AMI": "ami-6fc9770e",
"InstanceType": "t2.micro"
}
}
},
"Parameters": {
"AdminKeyPair": {
"Type": "AWS::EC2::KeyPair::KeyName"
},
"VPCID": {
"Type": "AWS::EC2::VPC::Id"
},
"DevAdminSecurityGroupID": {
"Type": "AWS::EC2::SecurityGroup::Id"
},
"DevAdminSubnetID": {
"Type": "AWS::EC2::Subnet::Id"
}
},
"Resources": {
"DevAdminServer": {
"Type": "AWS::EC2::Instance",
"Metadata": {
"Comment": "Sets up administrative tools for the server",
"AWS::CloudFormation::Init": {
"config": {
"packages": {
"yum": {}
},
"files": {},
"services": {}
}
}
},
"Properties": {
"ImageId": {
"Fn::FindInMap": [
"RegionMap",
"DevAdminServer",
"AMI"
]
},
"SecurityGroupIds": [
{
"Ref": "DevAdminSecurityGroupID"
}
],
"SubnetId": {
"Ref": "DevAdminSubnetID"
},
"InstanceType": {
"Fn::FindInMap": [
"RegionMap",
"DevAdminServer",
"InstanceType"
]
},
"KeyName": {
"Ref": "AdminKeyPair"
},
"Tags": [
{
"Key": "Application",
"Value": {
"Ref": "AWS::StackId"
}
}
],
"UserData": {
"Fn::Base64": {
"Fn::Join": [
"",
[]
]
}
}
}
}
}
}
But this resource fails on creation with the error:
Value of property Parameters must be an object with String (or simple type) properties
I know it's the last two variables causing the trouble (subnetID and securitygroupID), because I removed them and the provisioning the child template works just fine.
What am I doing wrong?
The values of DevAdminSecurityGroupID and DevAdminSubnetID are defined as JSON arrays [] in your master template, but they should be Strings (after the Fn::GetAtt intrinsic function expansion):
"DevAdminSecurityGroupID":
{
"Fn::GetAtt": [
"DevSecurityGroupsStack",
"Outputs.DevAdminSecurityGroupID"
]
},
"DevAdminSubnetID":
{
"Fn::GetAtt": [
"DevNetworkStack",
"Outputs.DevAdminSubnetID"
]
}