How to specify ParentId of existing "AWS::ApiGateway::RestApi" for "AWS::ApiGateway::Resource" in CloudFormation template? For example, I have already created API Gateway REST API and I want to specify it in my template:
MyTestResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId:
Ref: 'RestApi'
ParentId: <<placeholder_for_my_value>>
In case when I describe "AWS::ApiGateway::RestApi" in my template too I can do that such way:
ParentId: !GetAtt "RestApi.RootResourceId"
But how can I do that for already existing REST API?
You can lookup the resource id of an existing REST API either through the console
Click "your REST API" -> "Resources"
in the higher left side you'll see something like
APIs>YOUR_API_NAME (YOUT_API_ID)>Resources>/your_already_existing_resource (YOUR_RESOURCE_ID)
That's then the resource Id you can specify as a "parentId".
Alternatively, use the aws cli.
https://docs.aws.amazon.com/cli/latest/reference/apigateway/get-resources.html
Where you'd need to specify the rest-api-id which you again can either get from the console, or via https://docs.aws.amazon.com/cli/latest/reference/apigateway/get-rest-apis.html
In fact, if you need to specify a new resource in an existing API, you can check the stack in the CloudFormation, in the resources tab and see the Logical Did with which the API was generated.
Whit that LogicalId you can specify en that manner, considering logical Id as RestApiLogicalId:
"ParentId": {
"Fn::GetAtt": [
"RestApiLogicalId",
"RootResourceId"
]
}
Enjoy!
Related
I'd like to expose the TopicArn Value (referenced in the outputs section at the bottom of my code snippet) of my SNStopic via Cloudformation template in the outputs tab of my stack in a similar manner to the way it's exposed in the resources when I create an SNStopic through the service catalog. I tried to access it by referencing it in the outputs section of my yaml script using dot notation but have been unsuccessful thus far. How might I be able to do so? I'm looking to do this so others using my script in the future won't have to go searching for the TopicArn in another place in order to subscribe to it.
Another important thing to note is that the provisioned product id below, under the properties section of the resources code block generates an SNSTopic.
Resources:
LabTrainingSnsTopic:
Type: "AWS::ServiceCatalog::CloudFormationProvisionedProduct"
Properties:
ProductId: prod-4iafsjovqrsrm # Sns Topic
ProvisioningArtifactName: "v1.1" # Must be an actual version number.
ProvisionedProductName: !Ref ProvisionedProductName
...
Outputs:
AccountID:
Description: The account in which this was built.
Value: !Ref 'AWS::AccountId'
TopicArn:
Description: Arn of the topic we created
Value: !GetAtt LabTrainingHigSnsTopic.ProvisionedProductName.Resources.SNSTopic
service catalog screenshot
cloudformation screenshot
I have a lambda which has a log group, say LG-1, for which retention is set to Never Expire (default). I need to change this Never Expire to 1 month. I am doing this using CloudFormation. As the log group already exists, when I am trying to deploy my lambda again with the changes in template as :
LambdaFunctionLogGroup:
Type: 'AWS::Logs::LogGroup'
DependsOn: MyLambda
Properties:
RetentionInDays: 30
LogGroupName: !Join
- ''
- - /aws/lambda/
- !Ref MyLambda
the update is failing with error :
[LogGroup Name] already exists.
One possible solution is to delete the log group and then again create it with new changes as shown above which works perfectly well.
But I need to do it without deleting the log group as it will result in the deletion of all the previous logs that I have.
Is there any workaround which is possible ?
#ttulka answered:
".. it is impossible to manipulate resources from CF which already exist out of the stack."
But actually the problem is more general than that and applies to resources created inside of the stack. It has to do with AWS CloudFormation resource "Replacement policy". For some resources the way CloudFormation "updates" the resource is to create a new resource, then delete the old resource (this is called the "Replacement" update policy). This means there is a period of time where you've got two resources of the same type with many of the same properties existing at the same time. But if a certain resource property has to be unique, the two resource can't exist at the same time if they have the same value for this property, so ... CloudFormation blows up.
AWS::Logs::LogGroup.LogGroupName property is one such property. AWS::CloudWatch::Alarm.AlarmName is another example.
A work around is to unset the name so that a random name is used, perform an update, then set the name back to it's predictable fixed value and update again.
Rant: It's an annoying problem that really shouldn't exist. I.e. AWS CF should be smart enough to not have to use this weird clunky resource replacement implementation. But ... that's AWS CF for you ...
I think it is impossible to manipulate resources from CF which already exist out of the stack.
One workaround would be to change the name of the Lambda like my-lambda-v2 to keep the old log group together with the new one.
After one month you can delete the old one.
Use customresource Backed lambda within your cloudformation template. The custom resource would be triggered automatically the first time and update your retention policy of the existing log group. If you need it you custom resource lambda to be triggered every time, then use a templating engine like jinja2.
import boto3
client = boto3.client('logs')
response = client.put_retention_policy(
logGroupName='string',
retentionInDays=123
)
You can basically make your CF template do (almost) anything you want using Custom Resource
More information (Boto3, you can find corresponding SDK for the language you use) - https://boto3.amazonaws.com/v1/documentation/api/1.9.42/reference/services/logs.html#CloudWatchLogs.Client.put_retention_policy
EDIT: Within the CloudFormation Template, it would look something like the following:
LogRetentionSetFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src
Handler: set_retention_period.handler
Role: !GetAtt LambdaRole.Arn
DeploymentPreference:
Type: AllAtOnce
PermissionForLogRetentionSetup:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:invokeFunction
FunctionName:
Fn::GetAtt: [ LogRetentionSetFunction, Arn ]
Principal: lambda.amazonaws.com
InvokeLambdaFunctionToSetLogRetention:
DependsOn: [PermissionForLogRetentionSetup]
Type: Custom::SetLogRetention
Properties:
ServiceToken: !GetAtt LogRetentionSetFunction.Arn
StackName: !Ref AWS::StackName
AnyVariable: "Choose whatever you want to send"
Tags:
'owner': !Ref owner
'task': !Ref task
The lambda function would have the code which sets up the log retention as per the code which I already specified before.
For more information, please google "custom resource backed lambda". Also to get you a head start I have added the ink below:
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html
I am trying to create an AWS cloudformation stack using a yaml template.
The goal is to create a sns topic for some notifications.
I want to output the topic arn, to be able to subscribe multiple functions to that topic by just specifying the topic arn.
However I am getting an error when I try to create the stack from the aws console:
"Template validation error: Template error: resource NotificationsTopic does not support attribute type Arn in Fn::GetAtt"
I have done exactly the same for s3 buckets, dynamodb tables, and all working good, but for some reason, with SNS topic I cannot get the ARN.
I want to avoid hardcoding the topic arn in all functions that are subscribed. Because if one day the the ARN topic changes, I'll need to change all functions, instead I want to import the topic arn in all functions and use it. This way I will have to modify nothing if for any reason I have a new arn topic in the future.
This is the template:
Parameters:
stage:
Type: String
Default: dev
AllowedValues:
- dev
- int
- uat
- prod
Resources:
NotificationsTopic:
Type: AWS::SNS::Topic
Properties:
DisplayName: !Sub 'notifications-${stage}'
Subscription:
- SNS Subscription
TopicName: !Sub 'notifications-${stage}'
Outputs:
NotificationsTopicArn:
Description: The notifications topic Arn.
Value: !GetAtt NotificationsTopic.Arn
Export:
Name: !Sub '${AWS::StackName}-NotificationsTopicArn'
NotificationsTopicName:
Description: Notifications topic name.
Value: !Sub 'notifications-${stage}'
Export:
Name: !Sub '${AWS::StackName}-NotificationsTopicName'
Not all resources are the same. Always check the documentation for the particular resource. It has the "Return Values" section and you can easily verify that SNS topic has ARN as a Ref value, so you don't have to use GetAtt function
https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-sns-topic.html
Edit: Thanks for the comment which points out that not every resource provides its ARN. A notable example is the Autoscaling group. Sure, the key thing in my answer was "check the documentation for each resource", this is an example that not every resource has every attribute.
Having said that, ARN missing for the ASG output is a really strange thing. It cannot be also constructed easily, because the ARN also contains GroupId which is a random hash. There is probably some effort to solve this at least for the use-case of ECS Capacity Providers https://github.com/aws-cloudformation/aws-cloudformation-coverage-roadmap/issues/548 and https://github.com/aws/containers-roadmap/issues/631#issuecomment-648377011 but I think that is is an significant enough issue that it should be mentioned here.
For resources that don't directly return ARN, I found a workaround which consists of building the ARN myself.
For instance, to get the ARN of my codepipeline:
!Join [ ':', [ "arn:aws:codepipeline", !Ref AWS::Region, !Ref AWS::AccountId, !Ref StackDeletePipeline ] ]
I have this for example in my template:
ApiGatewayRestApi:
Type: AWS::ApiGateway::RestApi
Properties:
Name: MyApi
Description: My AWS API Gateway config
Body:
# INSERT swagger.yml content here
Is there some cloudformation function I can use to read swagger.yml in or attach it under "Body:"? So I can keep it in another file and my template doesn't become huge.
There's a Fn::Transform function that allows you to call different Cloudformation macros to process your templates. One of those macros is AWS::Include
Heres an example:
Resources:
APIGateway:
Fn::Transform:
Name: AWS::Include
Parameters:
Location:
Fn::Sub: s3://partials-bucket/${PartialsEnv}/resources/api-gateway.yaml
Here api-gateway.yaml will have the full definition of your resource.
You can use this function in the same way as other intrinsic functions. The only caveat is AWS::Include will only work with files hosted in S3 so you'll need to upload your partials separatedly.
You can try the BodyS3Location .
The Amazon Simple Storage Service (Amazon S3) location that points to
an OpenAPI file, which defines a set of RESTful APIs in JSON or YAML
format.
For Example
"BodyS3Location": {
"Bucket": "you_bucket_name",
"Key": "filename.yaml"
}
For more see BodyS3Location
I'm trying to make AutoScalingGroup names on cloud formation templates dynamic. I was thinking if this is possible via parameters, or any other way?
"DynamicASGName": {
"Type": "AWS::AutoScaling::AutoScalingGroup",
"Properties": {
...properties here...
}
}
CloudFormation uses two sets of names: the logical resource name, to identify a resource within the stack, and the physical name which uniquely identifies it across the whole region.
CloudFormation doesn't support setting the logical name dynamically, but with certain types, you can set the physical name in the template with the Name property. For example:
MyUserPoolClient:
Type: AWS::Cognito::UserPoolClient
Properties:
UserPoolId: !Ref MyUserPool
ClientName: !Sub '${AppName}-userpoolclient'
GenerateSecret: false
Unfortunately, AutoScalingGroup doesn't support this.
A better solution is probably to use Tags on your resources. Most AWS resource types (including AutoScalingGroup) support Tags and they can be set dynamically in a CloudFormation template.