AWS CDK - update existing lambda environment variables - amazon-web-services

I would love to be able to update an existing lambda function via AWS CDK. I need to update the environment variable configuration. From what I can see this is not possible, is there something workable to make this happen?
I am using code like this to import the lambda:
const importedLambdaFromArn = lambda.Function.fromFunctionAttributes(
this,
'external-lambda-from-arn',
{
functionArn: 'my-arn',
role: importedRole,
}
);
For now, I have to manually alter a cloudformation template. Updating directly in cdk would be much nicer.

Yes, it is possible, although you should read #Allan_Chua's answer before actually doing it. Lambda's UpdateFunctionConfiguration API can modify a deployed function's environment variables. The CDK's AwsCustomResource construct lets us call that API during stack deployment.*
Let's say you want to set TABLE_NAME on a previously deployed lambda to the value of a DynamoDB table's name:
// MyStack.ts
const existingFunc = lambda.Function.fromFunctionArn(this, 'ImportedFunction', arn);
const table = new dynamo.Table(this, 'DemoTable', {
partitionKey: { name: 'id', type: dynamo.AttributeType.STRING },
});
new cr.AwsCustomResource(this, 'UpdateEnvVar', {
onCreate: {
service: 'Lambda',
action: 'updateFunctionConfiguration',
parameters: {
FunctionName: existingFunc.functionArn,
Environment: {
Variables: {
TABLE_NAME: table.tableName,
},
},
},
physicalResourceId: cr.PhysicalResourceId.of('DemoTable'),
},
policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
resources: [existingFunc.functionArn],
}),
});
Under the hood, the custom resource creates a lambda that makes the UpdateFunctionConfiguration call using the JS SDK when the stack is created. There are also onUpdate and onDelete cases to handle.
* Again, whether this is a good idea or not depends on the use case. You could always call UpdateFunctionConfiguration without the CDK.

The main purpose of CDK is to enable AWS customers to have the capability to automatically provision resources. If we're attempting to update settings of pre-existing resources that were managed by other CloudFormation stacks, it is better to update the variable on its parent CloudFormation template instead of CDK. This provides the following advantages on your side:
There's a single source of truth of what the variable should look like
There's no tug o war between the CDK and CloudFormation template whenever an update is being pushed from these sources.
Otherwise, since this is a compute layer, just get rid of the lambda function from CloudFormation and start full CDK usage altogether bro!
Hope this advise helps

If you are using AWS Amplify, the accepted answer will not work and instead you can do this by exporting a CloudFormation Output from your custom resource stack and then referencing that output using an input parameter in the other stack.
With CDK
new CfnOutput(this, 'MyOutput', { value: 'MyValue' });
With CloudFormation Template
"Outputs": {
"MyOutput": {
"Value": "MyValue"
}
}
Add an input parameter to the cloudformation-template.json of the resource you want to reference your output value in:
"Parameters": {
"myInput": {
"Type": "String",
"Description": "A custom input"
},
}
Create a parameters.json file that passes the output to the input parameter:
{
"myInput": {
"Fn::GetAtt": ["customResource", "Outputs.MyOutput"]
}
}
Finally, reference that input in your stack:
"Resources": {
"LambdaFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Environment": {
"Variables": {
"myEnvVar": {
"Ref": "myInput"
},
}
},
}
}
}

Related

How to create an 'AWS::SSM::Document' with DocumentType of Package using CloudFormation

This AWS CloudFormation document suggests that it is possible to administer an 'AWS::SSM::Document' resource with a DocumentType of 'Package'. However the 'Content' required to achieve this remains a mystery.
Is it possible to create a Document of type 'Package' via CloudFormation, and if so, what is the equivalent of this valid CLI command written as a CloudFormation template (preferably with YAML formatting)?
ssm create-document --name my-package --content "file://manifest.json" --attachments Key="SourceUrl",Values="s3://my-s3-bucket" --document-type Package
Failed Attempt. The content used is an inline version of the manifest.json which was provided when using the CLI option. There doesn't seem to be an option to specify an AttachmentSource when using CloudFormation:
AWSTemplateFormatVersion: 2010-09-09
Resources:
Document:
Type: AWS::SSM::Document
Properties:
Name: 'my-package'
Content: !Sub |
{
"schemaVersion": "2.0",
"version": "Auto-Generated-1579701261956",
"packages": {
"windows": {
"_any": {
"x86_64": {
"file": "my-file.zip"
}
}
}
},
"files": {
"my-file.zip": {
"checksums": {
"sha256": "sha...."
}
}
}
}
DocumentType: Package
CloudFormation Error
AttachmentSource not provided in the input request. (Service: AmazonSSM; Status Code: 400; Error Code: InvalidParameterValueException;
Yes, this is possible! I've successfully created a resource with DocumentType: Package and the package shows up in the SSM console under Distributor Packages after the stack succeeds.
Your YAML is almost there, but you need to also include the Attachments property that is now available.
Here is a working example:
AWSTemplateFormatVersion: "2010-09-09"
Description: Sample to create a Package type Document
Parameters:
S3BucketName:
Type: "String"
Default: "my-sample-bucket-for-package-files"
Description: "The name of the S3 bucket."
Resources:
CrowdStrikePackage:
Type: AWS::SSM::Document
Properties:
Attachments:
- Key: "SourceUrl"
Values:
- !Sub "s3://${S3BucketName}"
Content:
!Sub |
{
"schemaVersion": "2.0",
"version": "1.0",
"packages": {
"windows": {
"_any": {
"_any": {
"file": "YourZipFileName.zip"
}
}
}
},
"files": {
"YourZipFileName.zip": {
"checksums": {
"sha256": "7981B430E8E7C45FA1404FE6FDAB8C3A21BBCF60E8860E5668395FC427CE7070"
}
}
}
}
DocumentFormat: "JSON"
DocumentType: "Package"
Name: "YourPackageNameGoesHere"
TargetType: "/AWS::EC2::Instance"
Note: for the Attachments property you must use the SourceUrl key when using DocumentType: Package. The creation process will append a "/" to this S3 bucket URL and concatenate it with each file name you have listed in the manifest that is the Content property when it creates the package.
Seems there is no direct way to create an SSM Document with Attachment via CloudFormation (CFN). You can use a workaround as using a backed Lambda CFN where you will use a Lambda to call the API SDK to create SSM Document then use Custom Resource in CFN to invoke that Lambda.
There are some notes on how to implement this solution as below:
How to invoke Lambda from CFN: Is it possible to trigger a lambda on creation from CloudFormation template
Sample of a Lambda sending response format (when using Custom Resource in CFN): https://github.com/stelligent/cloudformation-custom-resources
In order to deploy Lambda with best practices and easy upload the attachment, Document content from local, you should use sam deploy instead of CFN create stack.
You can get information of the newly created resource from lambda to the CFN by adding the resource detail into the data json in the response lambda send back and the CFN can use it with !GetAtt CustomResrc.Attribute, you can find more detail here.
There are some drawbacks on this solution:
Add more complex to the original solution as you have to create resources for the Lambda execution such as (S3 to deploy Lambda, Role for Lambda execution and assume the SSM execution, SSM content file - or you have to use a 'long' inline content). It won't be a One-call CFN create-stack anymore. However, you can put everything into the SAM template because at the end of the day, it's just a CFN template
When Delete the CFN stack, you have to implement the lambda when RequestType == Delete for cleaning up your resource.
PS: If you don't have to work strictly on CFN, then you can try with Terraform: https://www.terraform.io/docs/providers/aws/r/ssm_document.html

aws cloudformation WAF geo location condition

Trying to create a cloud formation template to configure WAF with geo location condition. Couldnt find the right template yet. Any pointers would be appreciated.
http://docs.aws.amazon.com/waf/latest/developerguide/web-acl-geo-conditions.html
Unfortunately, the actual answer (as of this writing, July 2018) is that you cannot create geo match sets directly in CloudFormation. You can create them via the CLI or SDK, then reference them in the DataId field of a WAFRule's Predicates property.
Creating a GeoMatchSet with one constraint via CLI:
aws waf-regional get-change-token
aws waf-regional create-geo-match-set --name my-geo-set --change-token <token>
aws waf-regional get-change-token
aws waf-regional update-geo-match-set --change-token <new_token> --geo-match-set-id <id> --updates '[ { "Action": "INSERT", "GeoMatchConstraint": { "Type": "Country", "Value": "US" } } ]'
Now reference that GeoMatchSet id in the CloudFormation:
"WebAclGeoRule": {
"Type": "AWS::WAFRegional::Rule",
"Properties": {
...
"Predicates": [
{
"DataId": "00000000-1111-2222-3333-123412341234" // id from create-geo-match-set
"Negated": false,
"Type": "GeoMatch"
}
]
}
}
There is no documentation for it, but it is possible to create the Geo Match in serverless/cloudformation.
Used the following in serverless:
Resources:
Geos:
Type: "AWS::WAFRegional::GeoMatchSet"
Properties:
Name: geo
GeoMatchConstraints:
- Type: "Country"
Value: "IE"
Which translated to the following in cloudformation:
"Geos": {
"Type": "AWS::WAFRegional::GeoMatchSet",
"Properties": {
"Name": "geo",
"GeoMatchConstraints": [
{
"Type": "Country",
"Value": "IE"
}
]
}
}
That can then be referenced when creating a rule:
(serverless) :
Resources:
MyRule:
Type: "AWS::WAFRegional::Rule"
Properties:
Name: waf
Predicates:
- DataId:
Ref: "Geos"
Negated: false
Type: "GeoMatch"
(cloudformation) :
"MyRule": {
"Type": "AWS::WAFRegional::Rule",
"Properties": {
"Name": "waf",
"Predicates": [
{
"DataId": {
"Ref": "Geos"
},
"Negated": false,
"Type": "GeoMatch"
}
]
}
}
I'm afraid that your question is too vague to solicit a helpful response. The CloudFormation User Guide (pdf) defines many different WAF / CloudFront / R53 resources that will perform various forms of geo match / geo blocking capabilities. The link you provide seems a subset of Web Access Control Lists (Web ACL) - see AWS::WAF::WebACL on page 2540.
I suggest you have a look and if you are still stuck, actually describe what it is you are trying to achieve.
Note that the term you used: "geo location condition" doesn't directly relate to an AWS capability that I'm aware of.
Finally, if you are referring to https://aws.amazon.com/about-aws/whats-new/2017/10/aws-waf-now-supports-geographic-match/, then the latest Cloudformation User Guide doesn't seem to have been updated yet to reflect this.

CloudFormation Update support for "Refer to Resources in Another Stack"

I'm using the example from Walkthrough: Refer to Resources in Another Stack to refer resources from another stack (which I think is incredibly useful and should be an out-of-the-box feature). However, the example does not seem to work with updates, i.e. if a new output was added to the referenced stack.
Interestingly, the lambda function isn't even called according to logs and metrics, so it does not seem to be a problem that can be fixed in code. I do think though that the code should use a different PhysicalResourceId on update as per Replacing a Custom Resource During an Update.
Note: this is a cross-post from an unanswered AWS Forum thread
It turns out that CloudFormation does only update a custom resource if one of its properties changes. Once this happens, the custom resource should signal that it changed. So
replace:
response.send(event, context, response.SUCCESS, responseData);
with
var crypto = require('crypto');
var hash = crypto.createHash('md5').update(JSON.stringify(responseData)).digest('hex');
response.send(event, context, response.SUCCESS, responseData, hash);
this will result in following events during an update:
15:08:16 UTC+0200 UPDATE_COMPLETE Custom::NetworkInfo NetworkInfo
15:08:15 UTC+0200 UPDATE_IN_PROGRESS Custom::NetworkInfo NetworkInfo Requested update required the provider to create a new physical resource
15:08:08 UTC+0200 UPDATE_IN_PROGRESS Custom::NetworkInfo NetworkInfo
This still requires a property to change though. The best that I came up with was passing a pseudo-random parameter to the custom resource:
{
"Parameters": {
"Random": {
"Description": "Random value to force stack-outputs update",
"Type": "String"
}
},
"Resources": {
"NetworkInfo": {
"Type": "Custom::NetworkInfo",
"Properties": {
"ServiceToken": { "Fn::GetAtt" : ["LookupStackOutputs", "Arn"] },
"Random": { "Ref": "Random" },
"StackName": { "Ref": "NetworkStackName" }
}
}
}
}
Unknown parameters (i.e. Random) are simply ignored by the lambda function.

Referencing Resources between CloudFormation stacks

If I have two cloudformation stacks, how do I references a resource in one stack from the other stack?
In the example below I have a stack that creates an EBS volume and want to reference that via the Ref: key in the second stack for my EC2 instance but I keep getting a rollback since it can't see that resource from the first stack:
"Template format error: Unresolved resource dependencies"
I already tried the DependsOn clause but it didn't work. Do I need to pass information via Parameters?
{
"AWSTemplateFormatVersion": "2010-09-09",
"Resources": {
"CubesNetworking": {
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "https://s3.amazonaws.com/mybucket/cf_network.json"
}
},
"CubesInstances": {
"DependsOn": ["CubesNetworking"],
"Type": "AWS::CloudFormation::Stack",
"Properties": {
"TemplateURL": "https://s3.amazonaws.com/mybucket/cf_instances.json"
}
}
}
}
In each of your nested stacks, you should have an output section. Then you can get those values in your calling stack (the one you have listed above) with syntax like:
{ "Fn::GetAtt" : [ "CubesNetworking", "Outputs.VolumeID" ] }
You then pass the values into your other nested stacks via Parameters:
"Parameters" : {
"VolumeId" : { "Fn::GetAtt" : [ "CubesNetworking", "Outputs.VolumeID" ] }
You still want the DependsOn since you need the volume created before the instance.
Edit, Mid-2017:
CloudFormation has introduced the ability to export values from one stack, and reference them in other stacks that do not have to be nested.
So your output can specify an export:
Outputs:
Desc:
Value: !Ref CubesNetworking.VolumeID
Export:
Name: some-unique-name
Then in another stack:
Fn::ImportValue: some-unique-name

AWS CloudFormation extract/parse string

If I have a region string such as 'us-east-1', 'us-west-1', 'sa-east-1', ... and I want to create a short versions programmatically such as: 'ue1', 'uw1', 'se1',... how do I do that? Using Mapping is the only way?
Thanks
As stated here in the CloudFormation documentation (http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html) there are only these functions available in a template:
Fn::Base64
Fn::FindInMap
Fn::GetAtt
Fn::GetAZs
Fn::Join
Fn::Select
Ref
So I'd say that the only way to achieve what you want is to use Mapping and the Fn::FindInMap function.
e.g.
{
"Mappings": {
"ShortRegion": {
"us-east-1": {
"short": "ue1"
},
"us-west-1": {
"short": "uw1"
}
}
}
}