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 customto make custom category "XXX"- Add Outputs to
XXX-cloudformation-template.json - Inside
backend-config.jsonadd adependsOnto 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
Parametersof 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.
And if you get cloudformation errors on push, e.g. remember that running
amplify env checkout devany time thebuild-config.jsonfile is touched.