These are notes I wrote to myself, but if they help you please leave a note and let me know. And any tips are certainly appreciated.
To reference your custom category resource from your lambda:
amplify add custom
to make custom category "XXX"- Add Outputs to
XXX-cloudformation-template.json
- Inside
backend-config.json
add adependsOn
to the lambda.
"yourLambdaFunction": {
"build": true,
"providerPlugin": "awscloudformation",
"service": "Lambda",
"dependsOn": [
{
"category": "custom",
"resourceName": "XXX",
"attributes": [
"var name from Outputs of XXX-cloudformation-template.json",
]
}
]
}
- Reference attribures from step 3 in the
Parameters
of the lambda (amplify/backend/function/yourLambda/yourLambda-cloudformation-template.json
) - Name each parameter by mashing up
custom
, the name of your custom category (XXX) and the name of the attribute from step 3. e.g.customXXXMyResourceArn
- Add the environment variable
"Resources": {
"LambdaFunction": {
"Type": "AWS::Lambda::Function",
"Environment": {
"Variables": {
"NAME_OF_ENV_VARIABLE": {
"Ref": "customXXXMyResourceArn"
}
}
}
}
}
}
- Use the environment variable in your code.
console.log('NAME_OF_ENV_VARIABLE', process.env.NAME_OF_ENV_VARIABLE)
- Profit
amplify add custom
If you call your new resource "XXX" you'll end up with a custom
directory containing an XXX
directory that contains the XXX-cloudformation-template.json
file.
Modify the cloudformation-template file to add your custom resources. In this example, the resource is called "YYY". The Type
and Properties
values depend on what type of resource you're adding.
"Resources": {
"YYY": {
"Type": "AWS::SQS::Queue",
"Properties": {
...
}
}
},
When YYY
is defined/created, it will expose multiple values, which you can discover in the CloudFormation documentation Just search for cloudformation followed by the value of the resource type. e.g. cloudformation AWS::SQS::Queue
, scroll past the "Properties" section to the "Return values" section.
You can incorporate those return values in other places within CloudFormation by using "Ref" and "Fn:GetAtt".
For example, if you have an SQS Queue and its Dead Letter Queue, the primary queue needs the Arn of the DLQ. You get this using Fn::GetAtt
where the 1st value is the name of the resource in the Properties
object (SQSDLQ) and the 2nd value is the attribute you need (Arn).
"deadLetterTargetArn": {
"Fn::GetAtt": [
"SQSDLQ",
"Arn"
]
}
Update the Outputs
object in your XXX-cloudformatino-template.json
file with whatever values you want to expose. Typically these will be Name and/or Arn.
In this example, ZZZ
is the name of the output variable you're exposing and YYY
is the resource.
"Outputs": {
"ZZZ": {
"Value": {
"Ref": "YYY"
}
}
}
Remember, the Value
represented by ZZZ
in this example is whatever the Ref
is for the ZZZ
resource. You might need to expose a different attribute or multiple attributes, so your Outputs
may look like this:
"Outputs": {
"ZZZUrl": {
"Value": {
"Ref": "YYY"
}
},
"ZZZArn": {
"Value": {
"Fn::GetAtt": [
"YYY",
"Arn"
]
}
}
}
Because Ref
for resource type ZZZ
returns the url for the resource. And you also need the Arn of the resource, which you get via the Fn::GetAtt
(RRRRemember there's no R in GetAtt) helper.
The name of each output variable can be whatever you like, but it's helpful to incorporate the name of the resource and the attribute you're exposing.
Now your custom category is created. Push it to the cloud using amplify push
, head over to CloudFormation "Stacks", search for "customXXX" where "XXX" is the name of your custom resource, and drill into that stack.
In the "Outputs" tab, you should see a Key for each of the variables you exported, and its value. Eyeball everything to be sure the value is what you expect -- the resources name, Arn, a Url, etc.
If the resource that needs your custom resource doesn't exist yet, create it now. e.g. amplify function add
to make a new Lambda.
Let's call the resource that wants access to your custom resource "WWW".
You'll be asked if you want to access other resources, and you'll see "custom" in the list and your custom resource. Hazah! Unfortunately, that doesn't help you connect the outputs or you wouldn't need this guide.
As a recap, here are "names" we've been using:
- WWW = some amplify resource (e.g. lambda)
- XXX = your custom category
- YYY = your custom resource
- ZZZ = YYY output variable to use
Inside the backend-config.json
file, add an object to the dependsOn
list of the resource (WWW) that needs access to your custom resource (YYY). The resourceName
value is the name of the folder containing your custom catagory (XXX).
Here:
/amplify/backend/custom/XXX/XXX-cloudformatino-template.json
... the resourceName
is XXX
.
EXAMPLE:
File: amplify/backend/backend-config.json
{
"auth": {...},
"storage": {...},
"api": {...},
"function": {
"WWW": {
"build": true,
"providerPlugin": "awscloudformation",
"service": "Lambda",
"dependsOn": [
{
"category": "custom",
"resourceName": "XXX",
"attributes": [
"YYYRef",
"YYYArn"
]
}
]
}
},
"custom": {
"XXX": {
"service": "customCloudformation",
"providerPlugin": "awscloudformation",
"dependsOn": []
}
}
}
Above WWW
is the name of the lambda (resource) that wants access to the custom resource. Within its dependsOn
list, add an entry for your custom category XXX
, and the values (YYYRef, YYYArn
) from the Outputs
section of your custom resource that you defined in /amplify/backend/custom/XXX/XXX-cloudformatino-template.json
.
Head over to the resource (e.g. the WWW lambda) that wants to reference the custom resource (e.g. XXX's YYY).
Add Parameters
inside the /amplify/backend/lambda/WWW/WWW-cloudformation-template.json
file (or whichever resource you want to reference the custom category).
Each parameter has a "magic" name. The name is a mashup of the word "custom" followed by the name of your custom category (XXX) followed by the name of the value from the Outputs
section of your XXX-cloudformation-template.json
file (which is also the same name you used in the dependsOn
attributes
section).
Putting it all together, the name used in Parameters
is customXXXYYYattr
where "YYYattr" is the name of the resource attribute.
The Parameters
section probably already has a few values in it. Adding the new parameters it should look like this:
"Parameters": {
"CloudWatchRule": {
"Type": "String",
"Default": "NONE",
"Description": " Schedule Expression"
},
"deploymentBucketName": {
"Type": "String"
},
"env": {
"Type": "String"
},
"s3Key": {
"Type": "String"
},
"customXXXYYYRef": {
"Type": "String"
},
"customXXXYYYArn": {
"Type": "String"
}
},
Now you can reference your custom resources's output value via {"Ref": "customXXXYYYArn"}
.
Let's say the name of the custom category you created is called "SQS" (we've been using XXX for this) and you're using it to hold all your SQS queues. One of your queues (YYY) is called "OrdersQueue". In order to make the Lambda trigger when messages are added to the OrdersQueue, you add an "EventSourceMapping".
The "Easy" way to do this would be to put this mapping in the resources section of the SQS-cloudformation-template.json
file, like this:
"LambdaSQSEventSource": {
"Type": "AWS::Lambda::EventSourceMapping",
"Properties": {
"Enabled": true,
"EventSourceArn": {
"Fn::GetAtt": [
"OrdersQueue",
"Arn"
]
},
"FunctionName": {
"Ref": "functionNameOfYourLambdaName"
},
"FunctionResponseTypes": [
"ReportBatchItemFailures"
]
}
}
But let's say you like pain. You can put the definition into the NameOfYourLambda-cloudformation-template.json
file and use the Arn of your queue that you exported from your custom resource. Like so:
"LambdaSQSEventSource": {
"Type": "AWS::Lambda::EventSourceMapping",
"Properties": {
"Enabled": true,
"EventSourceArn": {
"Ref": "customSQSOrdersQueueArn"
},
"FunctionName": {
"Fn::GetAtt": [
"LambdaFunction",
"Arn"
]
},
"FunctionResponseTypes": [
"ReportBatchItemFailures"
]
}
}
Where customSQSOrdersQueueArn
is also an entry in the Parameters
section so you can reference it.
Finally, we close the circle by exposing information about your custom category's resources to your Lambda's code.
In your Lambda's cloudformation template (NameOfYourLambda-cloudformation-template.json
) find the LambdaFunction
definition and add your environment variable.
"Resources": {
"LambdaFunction": {
"Type": "AWS::Lambda::Function",
"Metadata": {...},
"Properties": {...},
"Handler": "index.handler",
"FunctionName": {...},
"Environment": {
"Variables": {
"ENV": {
"Ref": "env"
},
"REGION": {
"Ref": "AWS::Region"
},
"NAME_OF_YOUR_ENV_VARIABLE": {
"Ref": "customXXXYYYattr"
},
"SQS_ORDERS_QUEUE_URL": {
"Ref": "customSQSOrdersQueueUrl"
}
}
},
"Role": {...},
"Runtime": "nodejs16.x",
"Layers": [],
"Timeout": 30
}
},
Within your code, you can access it...
if (undefined === process.env.NAME_OF_YOUR_ENV_VARIABLE) {
throw new Error("NAME_OF_YOUR_ENV_VARIABLE is required!")
}
console.log("NAME_OF_YOUR_ENV_VARIABLE", NAME_OF_YOUR_ENV_VARIABLE)
Beware: If you amplify function update
and make changes, edits you made to the build-config.json
file in the updated category will (at time of this writing) be wiped out when the new dependsOn
key is made. Be sure to restore the "category": "custom"
you made or you'll get cloudformation errors when you push.
I like to name the (custom) category with a descriptive (about what the category does or how it's used) term, and the resources within the category simply by their resource type.
I made a custom category to send emails to admins on serious errors. The custom category name is "AlertNotice". The reources in the category are simply "SNSTOPIC" and SNSSUBSCRIPTION_PERSON1, SNSSUBSCRIPTION_PERSON2 The output is "SNSTopicArn". In lambdas, this
dependsOn:
category: custom
resourceName: AlertNotice
attriburtes: SNSTopicArn
... in the cloudformation template becomes: customAlertNoticeSNSTopicArn
... then as an environment var: ALERT_NOTICE_SNS_TOPIC_ARN
An SQS queue with dead letter queue, cloudwatch alarm The custom category name is "Pipeline". The reources in the category are simply "SQS", "DLQ", and "DLQALARM". The outputs are "SQSUrl" and "SQSArn".
In lambdas, this
dependsOn:
category: custom
resourceName: Pipeline
attriburtes: SQSUrl, SQSArn
... in the cloudformation template becomes: customPipelineSQSUrl
... then as an environment var: PIPELINE_SQS_URL
If you need to reference Amplify resources (defined in other -cloudformation-template.json files), you can use the amplify update custom
command to pull references to those resources into your custom cloudformation template. After running that commend, you will have a bunch of Parameters
at the top of your custom cloudformation-template file and simply use Ref
to incorporate them. For example: {"Ref": "name of parameter"}
Unfortunately, when you amplify function update
and indicate you want to reference thigns in your custom category, the outputs you added aren't pulled in, so you need to add them manually.
Pro tip: any time you modify backend-config.json, you should run amplify env checkout dev
(or whatever env you're currently using for development). This seems to rekajigger things. Then you can run amplify push -y
.
Pro tip: sketch out how your resources will interact to avoid creating circular dependencies. It's tedious to shift things around later. If you do need to move something, 1st delete it and push an update, then add it back in the new .cloudformation-template file.
You can reference the output vars in the
custom-policies.json
file of a Lambda function that uses the new custom resource. This causes Amplify to automatically generate the permissions your lambda will need and add them to your cloud watch templates.