AWS CloudFormation: How to output a machine's PublicIP? - amazon-web-services

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.

Related

AWS Sample Template: S3_Website_With_CloudFront_Distribution.template Fails Upon Start do to Route 53 Domain Name

I am working with the AWS Samples templates from enter link description here where I reduced the mapping a little, but leaving everything else as is. In review the code, and reading the notes within the template, it appears that I should be able to run the code by simply entering the name of the Host Zone which I have registered with Route 53, within the parameter field when running the stack. I also created the certificate via the ACM.
However, I am getting, however, the following error:
To add an alternate domain name (CNAME) to a CloudFront distribution, you must attach a trusted certificate that validates your authorization to use the domain name. For more details, see: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html#alternate-domain-names-requirements (Service: AmazonCloudFront; Status Code: 400; Error Code: InvalidViewerCertificate; Request ID: ---------)
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "AWS CloudFormation Sample Template S3_Website_With_CloudFront_Distribution: Sample template showing how to create a website with a custom DNS name, hosted on Amazon S3 and served via Amazone CloudFront. It assumes you already have a Hosted Zone registered with Amazon Route 53. **WARNING** This template creates an Amazon Route 53 DNS record, an S3 bucket and a CloudFront distribution. You will be billed for the AWS resources used if you create a stack from this template.",
"Parameters" : {
"HostedZone" : {
"Type" : "String",
"Description" : "The DNS name of an existing Amazon Route 53 hosted zone",
"AllowedPattern" : "(?!-)[a-zA-Z0-9-.]{1,63}(?<!-)",
"ConstraintDescription" : "must be a valid DNS zone name."
}
},
"Mappings" : {
"Region2S3WebsiteSuffix": {
"us-east-1" : { "Suffix": ".s3-website-us-east-1.amazonaws.com" }
}
},
"Resources" : {
"S3BucketForWebsiteContent" : {
"Type" : "AWS::S3::Bucket",
"Properties" : {
"AccessControl" : "PublicRead",
"WebsiteConfiguration" : {
"IndexDocument" : "index.html",
"ErrorDocument" : "error.html"
}
}
},
"WebsiteCDN" : {
"Type" : "AWS::CloudFront::Distribution",
"Properties" : {
"DistributionConfig" : {
"Comment" : "CDN for S3-backed website",
"Aliases" : [{ "Fn::Join" : [ "", [{"Ref" : "AWS::StackName"}, {"Ref" : "AWS::AccountId"}, ".", {"Ref" : "AWS::Region"}, ".", { "Ref" : "HostedZone" }]]}],
"Enabled" : "true",
"DefaultCacheBehavior" : {
"ForwardedValues" : { "QueryString" : "true" },
"TargetOriginId" : "only-origin",
"ViewerProtocolPolicy" : "allow-all"
},
"DefaultRootObject" : "index.html",
"Origins" : [
{ "CustomOriginConfig" :
{
"HTTPPort" : "80",
"HTTPSPort" : "443",
"OriginProtocolPolicy" : "http-only"
},
"DomainName" : { "Fn::Join" : ["", [{"Ref" : "S3BucketForWebsiteContent"},
{"Fn::FindInMap" : [ "Region2S3WebsiteSuffix", {"Ref" : "AWS::Region"}, "Suffix" ]}]]},
"Id" : "only-origin"
}]
}
}
},
"WebsiteDNSName" : {
"Type" : "AWS::Route53::RecordSet",
"Properties" : {
"HostedZoneName" : { "Fn::Join" : [ "", [{ "Ref" : "HostedZone" }, "."]]},
"Comment" : "CNAME redirect custom name to CloudFront distribution",
"Name" : { "Fn::Join" : [ "", [{"Ref" : "AWS::StackName"}, {"Ref" : "AWS::AccountId"}, ".", {"Ref" : "AWS::Region"}, ".", { "Ref" : "HostedZone" }]]},
"Type" : "CNAME",
"TTL" : "900",
"ResourceRecords" : [{ "Fn::Join" : [ "", ["http://", {"Fn::GetAtt" : ["WebsiteCDN", "DomainName"]} ]]}]
}
}
},
"Outputs" : {
"WebsiteURL" : {
"Value" : {"Fn::Join" : [ "", ["http://", {"Ref" : "WebsiteDNSName"} ]] },
"Description" : "The URL of the newly created website"
},
"BucketName" : {
"Value" : { "Ref" : "S3BucketForWebsiteContent" },
"Description" : "Name of S3 bucket to hold website content"
}
}
}
It is because you should have an SSL certificate for the alternate domain that you are trying to add. You can request an ACM public certificate in us-east-1 region.
I suggest you set up the ACM certificate outside the cloudformation, because creating an ACM certificate requires manual DNS/Email verification.
Hope this helps.
Reference:
https://aws.amazon.com/premiumsupport/knowledge-center/cloudfront-invalid-viewer-certificate/

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.

AWS specific parameters and EC2 SecurityGroupIds List String Error

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"] }
]

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

how to construct a string of physical subnet ids to create db subnet group on the fly in a cloudformation script?

I'm trying to build a CLoudFormation script that launches an instance and a db into a vpc at the same time. the issue is the db requires two AZ's so i create a second subnet and now i just need to reference the two subnet physical ids in a 'MyDBSubnetGroup' var. I can get the logical IDs for the subnets i created but dont know how to ref those physical IDs. ANyone know? THanks!!
Heres my code:
"MyDBSubnetGroup" : {
"Type" : "AWS::RDS::DBSubnetGroup",
"Properties" : {
"DBSubnetGroupDescription" : "Subnets available for the RDS DB Instance",
"SubnetIds" : { "Fn::Join" : [ " ", [{"Ref" : "PublicSubnetAZ1"}, ", ", {"Ref" : "PublicSubnetAZ2"}, " " ]]}
}
},
I run into the same issue, after working with AWS support I understood that List of String does not mean what we initially thought. Also, if you want to place the DB inside a VPC you must not use AWS::RDS::DBSecurityGroup objects.
Here is a full sample, it took me a while to get it working:
"dbSubnetGroup" : {
"Type" : "AWS::RDS::DBSubnetGroup",
"Properties" : {
"DBSubnetGroupDescription" : "Availability Zones for RDS DB",
"SubnetIds" : [ { "Ref" : "subnetPrivate1" },
{ "Ref" : "subnetPrivate2" } ]
}
},
"dbInstance" : {
"Type" : "AWS::RDS::DBInstance",
"Properties" : {
"DBInstanceIdentifier" : { "Fn::Join" : [ "",
[ { "Ref" : "AWS::StackName" },
"DB" ] ] },
"DBName" : "dbname",
"DBSubnetGroupName" : { "Ref" : "dbSubnetGroup" },
"MultiAZ" : "true",
"AllocatedStorage" : "8",
"BackupRetentionPeriod" : "0",
"DBInstanceClass" : "db.m1.medium",
"Engine" : "postgres",
"MasterUserPassword" : "masteruserpassword",
"MasterUsername" : "masterusername",
"VPCSecurityGroups" : [ { "Ref" : "sgVpc" }, { "Ref" : "sgDB" } ]
}
},
If you map the subnet ids you can access them with something like this.
"AWSRegionSubnet":{
"us-east-1":{
"RDSSubnets":[
"subnet-aaaaaaaa",
"subnet-bbbbbbbb"
]
},
"us-west-2":{
"RDSSubnets":[
"subnet-cccccccc",
"subnet-dddddddd"
]
}
}
"RDSSubnet":{
"Type":"AWS::RDS::DBSubnetGroup",
"Properties":{
"DBSubnetGroupDescription":"Some cool notes here",
"SubnetIds":{
"Fn::FindInMap":[
"AWSRegionSubnet",
{
"Ref":"AWS::Region"
},
"RDSSubnets"
]
}
}
}