Automated deployments
This page explains how we can use automate the lambda deployment process, using CI. All we need is credentials to an AWS user with the correct permissions.
To read more about the cargo lambda deploy
command see the commands documentation.
Step 1: Create an AWS service account
First we need a set of user credentials, to be able to authenticate to AWS when deploying. Our user needs to be able to create, update, and retrieve lambda functions, and it needs to be able to publish new versions.
Using the AWS CDK
Here's how you might define a service user with the AWS CDK in TypeScript:
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export class CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const user = new iam.User(this, 'lambda-service-user');
const accessKey = new iam.AccessKey(this, 'lambda-service-access-key', { user });
const policy = new iam.Policy(this, 'lambda-service-policy', {
statements: [new iam.PolicyStatement({
sid: "Enable Lambda User Permissions",
effect: iam.Effect.ALLOW,
actions: [
'lambda:GetFunction',
'lambda:GetLayerVersion',
'lambda:CreateFunction',
'lambda:UpdateFunctionCode',
'lambda:UpdateFunctionConfiguration',
'lambda:PublishVersion',
'lambda:TagResource'
],
resources: ['arn:aws:lambda:*:*:function:*'],
})],
});
policy.attachToUser(user);
new cdk.CfnOutput(this, 'aws_access_key_id', {
value: accessKey.accessKeyId,
});
new cdk.CfnOutput(this, 'aws_secret_access_key', {
value: accessKey.secretAccessKey.toString(),
});
}
}
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export class CdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const user = new iam.User(this, 'lambda-service-user');
const accessKey = new iam.AccessKey(this, 'lambda-service-access-key', { user });
const policy = new iam.Policy(this, 'lambda-service-policy', {
statements: [new iam.PolicyStatement({
sid: "Enable Lambda User Permissions",
effect: iam.Effect.ALLOW,
actions: [
'lambda:GetFunction',
'lambda:GetLayerVersion',
'lambda:CreateFunction',
'lambda:UpdateFunctionCode',
'lambda:UpdateFunctionConfiguration',
'lambda:PublishVersion',
'lambda:TagResource'
],
resources: ['arn:aws:lambda:*:*:function:*'],
})],
});
policy.attachToUser(user);
new cdk.CfnOutput(this, 'aws_access_key_id', {
value: accessKey.accessKeyId,
});
new cdk.CfnOutput(this, 'aws_secret_access_key', {
value: accessKey.secretAccessKey.toString(),
});
}
}
When the stack is deployed, the aws_access_key_id
and the aws_secret_access_key
settings for this user will be printed in the terminal.
Using Terraform
Here's how you might define a service user with Terraform:
resource "aws_iam_user" "lambda-service-user" {
name = "lambda-service-user"
}
resource "aws_iam_access_key" "lambda-service-user" {
user = aws_iam_user.lambda-service-user.name
}
resource "aws_iam_policy" "lambda-service-policy" {
name = "lambda-service-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"lambda:GetFunction",
"lambda:GetLayerVersion",
"lambda:CreateFunction",
"lambda:UpdateFunctionCode",
"lambda:UpdateFunctionConfiguration",
"lambda:PublishVersion",
"lambda:TagResource"
]
Resource = [
"arn:aws:lambda:<region>:<account-id>:function:<function-name>",
]
}
]
})
}
resource "aws_iam_user_policy_attachment" "lambda-service-user-policy-attachment" {
user = aws_iam_user.lambda-service-user.name
policy_arn = aws_iam_policy.lambda-service-policy.arn
}
output "aws_access_key_id" {
value = aws_iam_access_key.lambda-service-user.id
}
output "aws_secret_access_key" {
value = aws_iam_access_key.lambda-service-user.secret
sensitive = true
}
resource "aws_iam_user" "lambda-service-user" {
name = "lambda-service-user"
}
resource "aws_iam_access_key" "lambda-service-user" {
user = aws_iam_user.lambda-service-user.name
}
resource "aws_iam_policy" "lambda-service-policy" {
name = "lambda-service-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"lambda:GetFunction",
"lambda:GetLayerVersion",
"lambda:CreateFunction",
"lambda:UpdateFunctionCode",
"lambda:UpdateFunctionConfiguration",
"lambda:PublishVersion",
"lambda:TagResource"
]
Resource = [
"arn:aws:lambda:<region>:<account-id>:function:<function-name>",
]
}
]
})
}
resource "aws_iam_user_policy_attachment" "lambda-service-user-policy-attachment" {
user = aws_iam_user.lambda-service-user.name
policy_arn = aws_iam_policy.lambda-service-policy.arn
}
output "aws_access_key_id" {
value = aws_iam_access_key.lambda-service-user.id
}
output "aws_secret_access_key" {
value = aws_iam_access_key.lambda-service-user.secret
sensitive = true
}
When applied, the secret access key can be read with terraform output -raw aws_secret_access_key
.
If you prefer to do this without the use of terraform, feel free to use another tool like it, or just create the user directly in the AWS console.
Step 2: Add credentials to your repository's secret
If you're using Github, go to github.com/<YOUR-ORG-OR-USERNAME>/<REPO>/settings/secrets/actions
, and add the key and secret we just created:
AWS_ACCESS_KEY_ID
, andAWS_SECRET_ACCESS_KEY
Feel free to name the secrets what you like as long as they're named correctly in the workflow below.
Step 3: Creating the release workflow
Once we have the necessary credentials set up, we can create our workflow. Here we'll define a workflow which releases a new version of our lambda when code is pushed to our main branch.
name: release
on:
push:
branches:
- main # or master
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Cache cargo registry
uses: actions/cache@v3
continue-on-error: false
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-build-cache
- name: Release lambda
run: |
pip install cargo-lambda
cargo lambda build --release
cargo lambda deploy <FUNCTION-NAME>
env:
AWS_DEFAULT_REGION: <YOUR-REGION>
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
name: release
on:
push:
branches:
- main # or master
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Cache cargo registry
uses: actions/cache@v3
continue-on-error: false
with:
path: |
~/.cargo/registry
~/.cargo/git
key: cargo-build-cache
- name: Release lambda
run: |
pip install cargo-lambda
cargo lambda build --release
cargo lambda deploy <FUNCTION-NAME>
env:
AWS_DEFAULT_REGION: <YOUR-REGION>
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
Assuming you've followed all the steps correctly, this should result in a new lambda being created if it's the first time the lambda is deployed this way; otherwise a new version is pushed.
Note that you don't need to use Github actions for this. This is only meant as an example that is comprehensive enough to get you started.
If you have suggestion for how this documentation can be improved, please feel free to submit a PR.