AWS specific parameters and EC2 SecurityGroupIds List String Error - amazon-web-services

I have a rather annoying issue which I am unable to resolve and will do my best to explain.
The following cut down example works in which I am able to reference a parameter and assign the security groups to my instance via the SecurityGroupIds property:
"Parameters" : {
"pDefaultSg" : {
"Description" : "AWS2 VPC default security groups",
"Type" : "List<AWS::EC2::SecurityGroup::Id>",
"Default" : "sg-245xxxxx,sg-275xxxxx,sg-235xxxxx"
}
}
"Resources" : {
"ec2Instance" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
"SecurityGroupIds" : { "Ref" : "pDefaultSg" }
}
}
The issue begins when I also want to add a second value to the SecurityGroupIds property referencing a security group resource instantiated within the same template:
"Resources" : {
"ec2Instance" : { ...
"SecurityGroupIds" : [ { "Ref" : "pDefaultSg" }, { "Fn::GetAtt" : "sgDb", "GroupId" } ],
....
"sgDb" : {
"Type" : "AWS::EC2::SecurityGroup",
"Properties" : { ...
I am then unable to avoid the following error causing the Cloudformation stack to rollback:
Value of property SecurityGroupIds must be of type List of String
I would really appreciate any pointers.
Many Thanks

The issue is that when pDefaultSg is accessed via the Ref intrinsic function it returns a list, therefore your SecurityGroupIds Property looks like
[["sg-245xxxxx","sg-275xxxxx","sg-235xxxxx"],"sg-1234DB"]
The solution is to change your SecurityGroupIds Property to Fn::Join the pDefaultSg List to a comma separated string followed by the sgDb:
"SecurityGroupIds": [
{"Fn::Join":
[",",
{"Ref": "pDefaultSg"}
]
},
{ "Fn::GetAtt" : ["sgDb", "GroupId"] }
]

Related

AWS Cloudformation Cross stack ref for Security group - error

I have below 2 stacks
1) Stack 1 - this is network stack, defines vpc, subnets and security group
2) Stack 2 - this stack defines ec2 instance
Network stack exports following
WebServerSG:
Description : "Web Server Security Group"
Value: !GetAtt InstanceSecurityGroup.GroupId
Export:
Name: !Sub ${AWS::StackName}-WebServerSG
The ec2 instance stack accepts a parameter "NetworkStack" and uses the network stack to refer to the security group as follows
"Resources" : {
"WebServerInstance": {
"Type": "AWS::EC2::Instance",
"Properties": {
"ImageId" : { "Fn::FindInMap" : [ "AWSRegionArch2AMI", { "Ref" : "AWS::Region" },
{ "Fn::FindInMap" : [ "AWSInstanceType2Arch", { "Ref" : "InstanceType" }, "Arch" ] } ] },
"InstanceType" : { "Ref" : "InstanceType" },
"SubnetId" : {"Fn::ImportValue" : {"Fn::Sub" : "${NetworkStack}-SubnetADMZ"}},
"SecurityGroupIds" : {"Fn::ImportValue" : {"Fn::Sub" : "${NetworkStack}-WebServerSG"}},
"KeyName" : { "Ref" : "KeyName" }
}
}
},
The ec2 instance stack fails with the error , "Value of property SecurityGroupIds must be of type List of String"
I tried to use SecurityGroups instead , but received similar error
Even if you want to specify only 1 security group, CloudFormation wants a list. The solution here is to make a list of one element, the element being the imported security group. In your case it will look like this (notice the brackets):
...
"SecurityGroupIds" : [{"Fn::ImportValue" : {"Fn::Sub" : "${NetworkStack}-WebServerSG"}}],
...
Laurent Jalbert Simard answer is correct. Fn:ImportValue should be enclosed in [] to provide imported value as list.

elasticache how to duplicate the configuration group default.redis3.2 in order to change one parameter without writing the 103 unchanged keys

I use AWS elasticache using this parameter group default.redis3.2, as you can see in this cloudformation sample :
"itophubElastiCacheReplicationGroup" : {
"Type" : "AWS::ElastiCache::ReplicationGroup",
"Properties" : {
"ReplicationGroupDescription" : "Hub WebServer redis cache cluster",
"AutomaticFailoverEnabled" : "false",
"AutoMinorVersionUpgrade" : "true",
"CacheNodeType" : "cache.t2.small",
"CacheParameterGroupName" : "default.redis3.2",
"CacheSubnetGroupName" : { "Ref": "cachesubnethubprivatecachesubnetgroup" },
"Engine" : "redis",
"EngineVersion" : "3.2.4",
"NumCacheClusters" : { "Ref" : "ElasticacheRedisNumCacheClusters" },
"PreferredMaintenanceWindow" : "sun:04:00-sun:05:00",
"SecurityGroupIds" : [ { "Fn::GetAtt": ["sgpHubCacheSG", "GroupId"] } ]
}
},
what I want to achieve is to have more than the default 16 databases, to do so, I have to change the key databases in the parameter group. Since default.redis3.2 is read only, I have to create my own, I want to have every parameters but databases identical to default.redis3.2.
It represent 104 parameters, I don't want to copy past each one of them by hand, so :
I'd like to know if there is a way to copy/inherit the parameters of default.redis3.2 created by aws ?
(If possible using cloudformation)
Having no response, I created manually the conf. comparing it to default.redis3.2 every default values was the same, so I ended up changeing only databases.
I created it manually, and then, I've recreated it this way :
"hubElastiCacheParameterGroup" : {
"Type": "AWS::ElastiCache::ParameterGroup",
"Properties": {
"CacheParameterGroupFamily" : "redis3.2",
"Description" : "parameter group to match itop hub needs",
"Properties" : {
"databases": "200"
}
}
},
using it here :
"hubElastiCacheReplicationGroup01" : {
"Type" : "AWS::ElastiCache::ReplicationGroup",
"Properties" : {
[...]
"CacheParameterGroupName" : { "Ref" : "hubElastiCacheParameterGroup" },
[...]
}
},

AWS CloudFormation: How to output a machine's PublicIP?

I wrote a CloudFormation template which creates a linux docker host.
I want to display the PublicIP of the machine under the "Outputs" section.
This is the relevant portion of the template:
"Outputs" : {
"ServerAddress" : {
"Value" : { "Fn::GetAtt" : [ "Server", "PublicDnsName" ] },
"Description" : "Server Domain Name"
},
"SecurityGroup" : {
"Value" : { "Fn::GetAtt" : [ "ServerSecurityGroup", "GroupId" ] },
"Description" : "Server Security Group Id"
},
"PublicIp" : {
"Value" : { "Fn::GetAtt" : [ "ServerPublicIp", "PublicIp" ]},
"Description" : "Server's PublicIp Address"
},
}
I've read in the official AWS documentation about using "Fn::GetAtt" and tried to implement it in my template, but when I try to create the stack I get the following error:
Error
Template validation error: Template error: instance of Fn::GetAtt references undefined resource ServerPublicIp
As far as I understand, the first part in the GetAtt line is a LogicalName (which I can choose?) and the second one is the real attribute as appears on the above link.
So my question is how to display the PublicIP of the server under the Outputs section?
Assuming your have an EC2 instance resource in your template named Server:
"Server" : {
"Type" : "AWS::EC2::Instance",
"Properties" : {
}
}
You output the public IP address referencing it's resource name:
"Outputs" : {
"PublicIp" : {
"Value" : { "Fn::GetAtt" : [ "Server", "PublicIp" ]},
"Description" : "Server's PublicIp Address"
}
}
As mentioned in the docs, the outputs can optionally be exported for cross-stack references. In case that is your use-case:
JSON:
"Outputs" : {
"PublicIp" : {
"Value" : { "Fn::GetAtt" : ["Server", "PublicIp"]},
"Description" : "Server Public IP"
"Export" : {
"Name" : {"Fn::Sub": "${AWS::StackName}-PublicIP"}
}
}
}
YAML:
Outputs:
PublicIp:
Description: Server Public IP
Value: !GetAtt Server.PublicIp
Export:
Name: !Sub "${AWS::StackName}-PublicIp"
See also:
AWS::EC2::Instance properties and return values in the docs.

How do I specify subnet and VPC IDs in AWS CloudFormation?

I want my CloudFormation template to use existing subnets and VPCs. I don't want to create new ones.
How do I parameterize these?
When I look at the docs for AWS::EC2::VPC and AWS::EC2::Subnet, it seems these resources are only for creating new VPCs and subnets. Is that correct?
Should I just point the instance resource directly to the existing VPC and subnets I want it to use?
For example - if I have an instance resource in my template and I point it directly to an existing subnet, like this:
{
"Resources": {
"MyServer": {
"Type": "AWS::EC2::Instance",
"Properties": {
"InstanceType": {
"Ref": "InstanceType"
},
"SubnetId": {
"Ref": "subnet-abc123"
},
...
I get this error when validating the template:
Template contains errors.: Template format error: Unresolved resource dependencies [subnet-abc123] in the Resources block of the template
I tried to do this with mappings but still getting an error:
"Mappings": {
"SubnetID": {
"TopKey": {
"Default": "subnet-abc123"
}
}
And with this in the instance resource:
"SubnetId": {
"Fn::FindInMap": [
"SubnetID",
{
"Ref": "TopKey"
},
"Default"
]
}
I get this error when trying to validate:
Template contains errors.: Template format error: Unresolved resource dependencies [TopKey] in the Resources block of the template
If you wish to use a specific VPC and subnet, just insert their values:
{
"Resources": {
"MyServer": {
"Type": "AWS::EC2::Instance",
"Properties": {
"InstanceType": "t2.micro",
"SubnetId": "subnet-abc123",
"ImageId": "ami-abcd1234"
}
}
}
A subnet always belongs to a VPC, so specifying the subnet will automatically select the matching VPC.
Specify them in the Parameters section, and refer them in Resources section. CF will let you select the VPC first and then the Subnet.
"Parameters" : {
"VpcId" : {
"Type" : "AWS::EC2::VPC::Id",
"Description" : "VPCId of Virtual Private Cloud (VPC).",
"Default" : ""
},
"VpcSubnet": {
"Description" : "SubnetId in VPC",
"Type" : "AWS::EC2::Subnet::Id",
"Default" : ""
},
"Resources" : {
...
"Ec2Instance" : {
"Properties" : {
"SubnetId" : { "Ref" : "VpcSubnet" },

How to name an Auto Scaling Group in a CloudFormation template?

I have a CloudFormation template that creates an auto scaling group (among other things). How can I give the auto scaling group a name in the template?
The AWS docs do not mention anything (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html), and its possible to do if I create it trough the AWS website. (I need to give a group a name because I need to find this group from another script)
EDIT: I've tried to add a tag called "Name", but it still does not work:
"Resources": {
"MyServerGroup" : {
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"Properties" : {
"AvailabilityZones" : { "Fn::GetAZs" : ""},
"LaunchConfigurationName" : { "Ref" : "LaunchConfig" },
"MinSize" : { "Ref" : "ServerCount" },
"MaxSize" : { "Ref" : "ServerCount" },
"DesiredCapacity" : { "Ref" : "ServerCount" },
"LoadBalancerNames" : [ { "Ref" : "ElasticLoadBalancerName" } ],
"Tags" : [ {
"Key" : "Name",
"Value" : { "Ref" : "ServerName" },
"PropagateAtLaunch" : "true"
} ]
},
"CreationPolicy": {
"ResourceSignal": {
"Count": "2",
"Timeout": "PT5M"
}
}
},
The name column in the AWS console still displays something like "MyStackName-MyServerGroup-345MH3NF34N7E", and in the Tags field I can see the key-value pair for the Name tag that I added.
Although naming the AutoScalingGroup (ASG) doesn't seem to be possible, you can export the name assigned when the ASG is created by the CloudFormation (CF) template by using the following:
Outputs:
AutoScalingGroupName:
Description: "AutoScalingGroup Name"
Export:
Name:
Fn::Sub: ${AWS::StackName}-autoscalinggroupname
Value: { Ref: AutoScalingGroup }
The ASG name can then be used in other CF templates, although this is perhaps not what the OP means by "find this group from another script".
CloudFormation now has a property called: AutoScalingGroupName
Description: "The name of the Auto Scaling group. Minimum length of 1. Maximum length of 255. Must follow the following pattern: [\u0020-\uD7FF\uE000-\uFFFD\uD800\uDC00-\uDBFF\uDFFF\r\n\t]*"
Type: String
Required: No
see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html
Create a tag with for the ASG with the key "Name" (the capital N is important). This will be used for the Name column in the console.
Note that you could find the ASG by searching for a well known tag other than Name from your other script. Tags are a great way to search for resources.
Clearly from the AWS Tagging Auto Scaling Groups and Instances doc, you can not set value of tag prefixed with aws:.
I guess, value you have read from console "MyStackName-MyServerGroup-345MH3NF34N7E" is of tag aws:autoscaling:groupName. So its obvious you can not set Auto Scaling Group Name in CloudFormation Template.
Unfortunately, from my experience, you can not pass tag value in CloudFormation template. But best, you can use AWS SDK to read tag value from launched EC2 instances.
The attribute "AutoscalingGroupName" within AWS::AutoScaling::AutoScalingGroup entity works.
Sample template :
"Autoscaling1"
{
"Type" : "AWS::AutoScaling::AutoScalingGroup",
"Properties" : {
"AutoScalingGroupName" : String,
"AvailabilityZones" : [ String, ... ],
"Cooldown" : String,
"DesiredCapacity" : String,
"HealthCheckGracePeriod" : Integer,
"HealthCheckType" : String,
"InstanceId" : String,
"LaunchConfigurationName" : String,
"LifecycleHookSpecificationList" : [ LifecycleHookSpecification, ... ],
"LoadBalancerNames" : [ String, ... ],
"MaxSize" : String,
"MetricsCollection" : [ MetricsCollection, ... ],
"MinSize" : String,
"NotificationConfigurations" : [ NotificationConfiguration, ... ],
"PlacementGroup" : String,
"Tags" : [ TagProperty, ... ],
"TargetGroupARNs" : [ String, ... ],
"TerminationPolicies" : [ String, ... ],
"VPCZoneIdentifier" : [ String, ... ]
}
}
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-as-group.html#cfn-autoscaling-autoscalinggroup-autoscalinggroupname