I have a parameter "SecretKey" and I want to provide a default value to it (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html) and the default value would be a generated random string. I already have a lambda function to do the generation of the key and a custom resource (call it RandomSecretKey) to get the value. Ultimately, I want to be able to do this in the parameters section:
"SecretKey": {
...
"Default": { "Fn::GetAtt": ["RandomSecretKey", "Value"] }
}
And this parameter would be referenced somewhere.
But this doesn't work because CloudFormation expects a static String based on the error message. Is there a way to do this?
No. It's not possible to have a dynamic default value for CloudFormation. The reason being that the template has not executed at all at the time that parameters are being collected.
If you want this to be a parameter, your generated value will have to be generated outside of the template and passed into the template as a parameter. You could do this from a bootstrapping creation script.
Alternatively, you should be able to use a Custom Resource in your template to generate your random secret key. It should be able to persist through stack updates.
References:
Custom Resources Docs - http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html
Custom Resources Example - https://blogs.aws.amazon.com/application-management/post/Tx2FNAPE4YGYSRV/Customers-CloudFormation-and-Custom-Resources
Related
I'm creating a LambdaRestApi as follows
this.gateway = new apigw.LambdaRestApi(this, "Endpoint", {
handler: hello,
endpointExportName: "MainURL"
})
and I'd like to get to the CfnOutput it generates, is it possible? I want to pass it to other functions and I want to avoid creating a new one.
Specifically the situation I'm tackling is this: I have have a post stage that verifies things are working at it uses the CfnOutput:
deployStage.addPost(
new CodeBuildStep("VerifyAPIGatewayEndpoint", {
envFromCfnOutputs: {
ENDPOINT_URL: deploy.hcEndpoint
},
commands: [
"curl -Ssf $ENDPOINT_URL",
"curl -Ssf $ENDPOINT_URL/hello",
"curl -Ssf $ENDPOINT_URL/test"
]
})
)
That deploy.hcEndpoint is a CfnOutput that I'm manually creating after the LambdaRestApi is created:
const gateway = new LambdaRestApi(this, "Endpoint", {handler: hello})
this.hcEndpoint = new CfnOutput(this, "GatewayUrl", {value: gateway.url})
and then making sure that every construct makes it available to its parent.
Using CfnOutputs in the post-deployment step makes sense. I am trying to learn the proper way of doing things, and also have clean stacks. With only one Lambda function it's no big deal, but with tens or hundreds it might. And since LambdaRestApi already creates the output, it does feel like I'm repeating myself by creating an identical one.
Assuming you are using the following code for your LambdaRestApi:
this.gateway = new apigw.LambdaRestApi(this, "Endpoint", {
handler: hello,
endpointExportName: "MainURL"
});
Referencing in same stack as LambdaRestApi
const outputValue = this.gateway.urlForPath("/");
Looking at the source code, the output value is just a call to urlForPath. The method is public, so you can use it directly.
Referencing from another stack
You can use cross stack references to get a reference to the output value of the stack.
import { Fn } from 'aws-cdk-lib';
const outputValue = Fn.importValue("MainURL");
If you try to use the first method in another stack, CDK will just generate a cross stack reference dynamically by adding extra outputs, so it is better to import the value directly.
I'd like to get to the CfnOutput it generates, is it possible?
Yes. Use the escape hatch syntax to get a reference to the CfnOutput that RestApi creates for the endpointExportName:
const urlCfnOutput = this.gateway.node.findChild('Endpoint') as cdk.CfnOutput;
console.log(urlCfnOutput.exportName);
// MainURL
console.log(urlCfnOutput.value);
// https://${Token[TOKEN.258]}.execute-api.us-east-1.${Token[AWS.URLSuffix.3]}/${Token[TOKEN.277]}/
Prefer standard CDK
As their name suggests, "escape hatches" are for "emergencies" when the CDK's standard solutions fail. Your use case may be one such instance, I don't know. But as #Kaustubh Khavnekar points out, you don't need the CfnOutput to get the url token value.
console.log(this.gateway.url)
// https://${Token[TOKEN.258]}.execute-api.us-east-1.${Token[AWS.URLSuffix.3]}/${Token[TOKEN.277]}/
I am using context to pass values to CDK. Is there currently a way to define project context file per deployment environment (dev, test) so that when the number of values that I have to pass grow, they will be easier to manage compared to passing the values in the command-line:
cdk synth --context bucketName1=my-dev-bucket1 --context bucketName2=my-dev-bucket2 MyStack
It would be possible to use one cdk.json context file and only pass the environment as the context value in the command-line, and depending on it's value select the correct values:
{
...
"context": {
"devBucketName1": "my-dev-bucket1",
"devBucketName2": "my-dev-bucket2",
"testBucketName1": "my-test-bucket1",
"testBucketName2": "my-test-bucket2",
}
}
But preferably, I would like to split it into separate files, f.e. cdk.dev.json and cdk.test.json which would contain their corresponding values, and use the correct one depending on the environment.
According to the documentation, CDK will look for context in one of several places. However, there's no mention of defining multiple/additional files.
The best solution I've been able to come up with is to make use of JSON to separate context out per environment:
"context": {
"dev": {
"bucketName": "my-dev-bucket"
}
"prod": {
"bucketName": "my-prod-bucket"
}
}
This allows you to access the different values programmatically depending on which environment CDK is deploying to.
let myEnv = dev // This could be passed in as a property of the class instead and accessed via props.myEnv
const myBucket = new s3.Bucket(this, "MyBucket", {
bucketName: app.node.tryGetContext(myEnv).bucketName
})
You can also do so programmatically in your code:
For instance, I have a context variable of deploy_tag cdk deploy Stack\* -c deploy_tag=PROD
then in my code, i have retrieved that deploy_tag variable and I make the decisions there, such as: (using python, but the idea is the same)
bucket_name = BUCKET_NAME_PROD if deploy_tag == 'PROD' else BUCKET_NAME_DEV
this can give you a lot more control, and if you set up a constants file in your code you can keep that up to date with far less in your cdk.json that may become very cluttered with larger stacks and multiple environments. If you go this route then you can have your Prod and Dev constants file, and your context variable can inform your cdk which file to load for a given deployment.
i also tend to create a new class object with all my deployment properties either assigned or derived, and pass that object into each stack, retrieving what i need out of there.
I am trying to define scope in Terraform. I can launch managed rule without scope, it works. But when I am trying to define scope got some error:
Inappropriate value for attribute "compliance_resource_types": set of string
required.
Maybe someone can help to write the scope correctly?
Here is the code for scope :
scope {
compliance_resource_types = "AWS::EC2::SecurityGroup"
}
As the documentation states for compliance_resource_types of resource aws_config_config_rule:
A list of resource types of only those AWS resources that you want to trigger an evaluation for the rule. e.g. AWS::EC2::Instance. You can only specify one type if you also specify a resource ID for compliance_resource_id. See relevant part of AWS Docs for available types.
You are using a single string instead of a list of strings.
The following change should fix your issue:
resource "aws_config_config_rule" "example" {
# ... other configuration ...
scope {
compliance_resource_types = ["AWS::EC2::SecurityGroup"]
}
}
I have a CloudWatch scheduled event invoking a Lambda function.
The event is currently passing a JSON with some parameters. One of which contains the event name, handwritten, which isn't very elegant and can lead to typos.
I cannot choose to pass the Matched event, as I am also passing some other parameters as JSON.
So I would somehow need to pass the event as a parameter in that JSON object, but I couldn't find any docs about that.
How can I get the invoking event name inside the Lambda function?
You can use input transformer which can be used to add your own stuff plus something from the meta data available. In your case
Input path will be
{"ruleName":"$.resources[0]"}
While the Input template will be
{"yourKey": "yourValue", "ruleName": <ruleName>}
The screenshot is here
And this is how it will be available in your lambda function
{
yourKey: 'yourValue',
ruleName: 'arn:aws:events:someregion:someaccountId:rule/testevent'
}
Hope this helps.
I'm trying to create a cloud formation template but I get the error Parameter validation failed: parameter value for parameter name PublicSubnetAz does not exist
when i run the stack creation.
My template includes in the parameter section the following property:
"PublicSubnetAz":{
"Type":"AWS::EC2::AvailabilityZone::Name",
"Default":""
}
and in my Conditions section I have
"xxx":{
"Fn::Not":[
{
"Fn::Equals":[
{
"Ref":"PublicSubnetAz"
},
""
]
}
]
}
If I didn't set the PublicSubnetAz property while creating the stack i get the error reported.
This is an expected behaviour as if the user don't select an AZ i get the first one when I need an AZ reference.
Do somebody has already addressed a similar problem and can help me?
Actually you can't have an optional parameter if this is an AWS-Specific Parameter.
There is a feature request dated 2012 but still not implemented