Skip to content

Commit 29ea35c

Browse files
authored
Merge pull request #37 from greyshi/main
feat: support creating and updating container image functions
2 parents adbae7a + e970af4 commit 29ea35c

File tree

9 files changed

+881
-279
lines changed

9 files changed

+881
-279
lines changed

README.md

Lines changed: 122 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AWS Lambda Deploy GitHub Action
22

3-
Updates the code and configuration of AWS Lambda functions as part of GitHub Actions workflow steps.
3+
Updates the code and configuration of AWS Lambda functions as part of GitHub Actions workflow steps. Supports both .zip file archives and container images stored in Amazon ECR.
44

55
**Table of Contents**
66

@@ -10,6 +10,7 @@ Updates the code and configuration of AWS Lambda functions as part of GitHub Act
1010
* [Update Function Configuration](#update-function-configuration)
1111
* [Using S3 Deployment Method](#using-s3-deployment-method)
1212
* [Dry Run Mode](#dry-run-mode)
13+
* [Container Image Deployment](#container-image-deployment)
1314
- [Build from Source](#build-from-source)
1415
- [Inputs](#inputs)
1516
- [Outputs](#outputs)
@@ -51,7 +52,7 @@ jobs:
5152
# The role-to-assume should be the ARN of the IAM role you created for GitHub Actions OIDC
5253

5354
- name: Deploy Lambda Function
54-
uses: aws-actions/aws-lambda-deploy@v1
55+
uses: aws-actions/aws-lambda-deploy@v1.1.0
5556
with:
5657
function-name: my-function-name
5758
code-artifacts-dir: my-code-artifacts-dir
@@ -60,9 +61,22 @@ jobs:
6061
# Add any additional inputs this action supports
6162
```
6263

63-
The required parameters to deploy are `function-name`, `code-artifacts-dir`, `handler`, and `runtime`. If the function does not exist yet, the `role` parameter is also required to specify the function's IAM execution role.
64+
The required parameters depend on the deployment type:
6465

65-
If a function with the name specified by `function-name` does not exist, it will be created with the provided code within `code-artifacts-dir` and configuration parameters using the [CreateFunction](https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html) API.
66+
**For zip file deployments (default):**
67+
- `function-name` - Name of the Lambda function
68+
- `code-artifacts-dir` - Path to code artifacts directory
69+
- `handler` - Function handler method
70+
- `runtime` - Function runtime identifier
71+
72+
**For container image deployments:**
73+
- `function-name` - Name of the Lambda function
74+
- `package-type` - Must be set to `Image`
75+
- `image-uri` - URI of the container image in Amazon ECR
76+
77+
**Note:** If the function does not exist yet, the `role` parameter is also required for both deployment types to specify the function's IAM execution role.
78+
79+
If a function with the name specified by `function-name` does not exist, it will be created with the provided code or image and configuration parameters using the [CreateFunction](https://docs.aws.amazon.com/lambda/latest/api/API_CreateFunction.html) API.
6680

6781
For the full list of inputs this GitHub Action supports, see [Inputs](#inputs).
6882

@@ -72,7 +86,7 @@ Function configuration will be updated using the [UpdateFunctionConfiguration](h
7286
As a first step, [GetFunctionConfiguration](https://docs.aws.amazon.com/lambda/latest/api/API_GetFunctionConfiguration.html) is called to perform a diff between the provided configuration parameters and the configuration of the currently deployed function. If there is no change, UpdateFunctionConfiguration will not be called.
7387
```yaml
7488
- name: Update Lambda configuration
75-
uses: aws-actions/aws-lambda-deploy@v1
89+
uses: aws-actions/aws-lambda-deploy@v1.1.0
7690
with:
7791
function-name: my-function-name
7892
code-artifacts-dir: my-code-artifacts-dir
@@ -82,10 +96,10 @@ As a first step, [GetFunctionConfiguration](https://docs.aws.amazon.com/lambda/l
8296
```
8397
8498
### Using S3 Deployment Method
85-
Optionally store code artifacts in S3 instead of direct `.zip` file upload.
99+
For zip file deployments, you can optionally store code artifacts in S3 instead of direct `.zip` file upload. Note: This method is only available for zip deployments, not container images.
86100
```yaml
87101
- name: Deploy Lambda function via S3
88-
uses: aws-actions/aws-lambda-deploy@v1
102+
uses: aws-actions/aws-lambda-deploy@v1.1.0
89103
with:
90104
function-name: my-function-name
91105
code-artifacts-dir: my-code-artifacts-dir
@@ -97,16 +111,48 @@ Optionally store code artifacts in S3 instead of direct `.zip` file upload.
97111
Validate parameters and permissions without any function code or configuration modifications.
98112
```yaml
99113
- name: Deploy on dry run mode
100-
uses: aws-actions/aws-lambda-deploy@v1
114+
uses: aws-actions/aws-lambda-deploy@v1.1.0
101115
with:
102116
function-name: my-function-name
103117
code-artifacts-dir: my-code-artifacts-dir
104118
dry-run: true
105119
```
106-
**Note**: Dry run will still call `GetFunctionConfiguration` to check if the function exists and perform configuration diffs against what's currently deployed.
120+
**Note**: Dry run will still call `GetFunctionConfiguration` to check if the function exists and perform configuration diffs against what's currently deployed.
121+
122+
### Container Image Deployment
123+
Deploy Lambda functions using container images from Amazon ECR. See [aws-actions/amazon-ecr-login](https://github.com/aws-actions/amazon-ecr-login) for details on logging into ECR.
124+
```yaml
125+
- name: Login to Amazon ECR
126+
id: login-ecr
127+
uses: aws-actions/amazon-ecr-login@v1
128+
# Authenticates with ECR and returns the registry URL for building images
129+
130+
- name: Build, tag, and push image to Amazon ECR
131+
id: build-image
132+
env:
133+
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
134+
ECR_REPOSITORY: my-lambda-repo
135+
IMAGE_TAG: ${{ github.sha }}
136+
run: |
137+
# Build Docker image from Dockerfile in repository root
138+
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
139+
# Push the built image to ECR repository
140+
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
141+
# Output the full image URI for the next step
142+
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
143+
144+
- name: Deploy Lambda function with container image
145+
uses: aws-actions/[email protected]
146+
with:
147+
function-name: my-container-function
148+
package-type: Image # Required: Indicates container deployment (not zip)
149+
image-uri: ${{ steps.build-image.outputs.image }} # ECR image URI from previous step
150+
role: arn:aws:iam::123456789012:role/lambda-role # IAM execution role for Lambda
151+
# Note: handler, runtime, and layers should not be provided for container images
152+
```
107153
## Build from Source
108154

109-
To automate building your source code, add a build step based on your runtime and build process. This build step should be performed before the AWS Lambda Deploy step, and AWS Lambda Deploy's `code-artifacts-dir` parameter will typically be set to the build step's code artifact output directory.
155+
For zip file deployments, to automate building your source code, add a build step based on your runtime and build process. This build step should be performed before the AWS Lambda Deploy step, and AWS Lambda Deploy's `code-artifacts-dir` parameter will typically be set to the build step's code artifact output directory.
110156

111157
Below are two commonly used Build examples for Node.js and Python:
112158

@@ -138,9 +184,11 @@ Below are two commonly used Build examples for Node.js and Python:
138184
| Name | Description | Required | Default |
139185
|------|-------------|----------|---------|
140186
| `function-name` | Name of the Lambda function | Yes | |
141-
| `code-artifacts-dir` | Path to a directory of code artifacts to zip and deploy | Yes | |
142-
| `handler` | Name of the function handler method | Yes | `index.handler` |
143-
| `runtime` | Function runtime identifier | Yes | `nodejs20.x` |
187+
| `package-type` | Package type of the Lambda function (`Zip` or `Image`) | No | `Zip` |
188+
| `image-uri` | URI of the container image in Amazon ECR (required when package-type is `Image`) | No | |
189+
| `code-artifacts-dir` | Path to a directory of code artifacts to zip and deploy (required when package-type is `Zip`) | No | |
190+
| `handler` | Name of the function handler method (required when package-type is `Zip`) | No | `index.handler` |
191+
| `runtime` | Function runtime identifier (required when package-type is `Zip`) | No | `nodejs20.x` |
144192
| `s3-bucket` | S3 bucket name for Lambda deployment package. Uses S3 deployment method if provided | No | |
145193
| `s3-key` | S3 key (path) for the Lambda deployment package | No | Auto-generated |
146194
| `publish` | Publish a new version of the function after updating | No | `true` |
@@ -252,7 +300,66 @@ This action requires the following minimum set of permissions:
252300
}
253301
```
254302

255-
If you're using the S3 deployment method, ensure your IAM role also has the following permissions:
303+
If you're using container image deployments, two sets of permissions are required:
304+
305+
**1. IAM permissions for the GitHub Actions role** to create/update the Lambda function with the container image:
306+
307+
```json
308+
{
309+
"Version": "2012-10-17",
310+
"Statement": [
311+
{
312+
"Sid": "ECRAuthToken",
313+
"Effect": "Allow",
314+
"Action": "ecr:GetAuthorizationToken",
315+
"Resource": "*"
316+
},
317+
{
318+
"Sid": "AllowPushPull",
319+
"Effect": "Allow",
320+
"Action": [
321+
"ecr:BatchGetImage",
322+
"ecr:BatchCheckLayerAvailability",
323+
"ecr:CompleteLayerUpload",
324+
"ecr:GetDownloadUrlForLayer",
325+
"ecr:InitiateLayerUpload",
326+
"ecr:PutImage",
327+
"ecr:UploadLayerPart"
328+
],
329+
"Resource": "arn:aws:ecr:<region>:<aws_account_id>:repository/<repository_name>"
330+
}
331+
]
332+
}
333+
```
334+
335+
**Note:** The above permissions include both push and pull operations for ECR. If you're only pulling pre-built images (not pushing), you can remove the write permissions and keep only:
336+
- `ecr:BatchGetImage`
337+
- `ecr:GetDownloadUrlForLayer`
338+
339+
**2. ECR repository policy** to allow the Lambda service to pull images:
340+
341+
```json
342+
{
343+
"Version": "2012-10-17",
344+
"Statement": [
345+
{
346+
"Sid": "LambdaECRImageRetrievalPolicy",
347+
"Effect": "Allow",
348+
"Principal": {
349+
"Service": "lambda.amazonaws.com"
350+
},
351+
"Action": [
352+
"ecr:BatchGetImage",
353+
"ecr:GetDownloadUrlForLayer"
354+
]
355+
}
356+
]
357+
}
358+
```
359+
360+
For cross-account deployments or more details, see [AWS Lambda container image deployment documentation](https://docs.aws.amazon.com/lambda/latest/dg/images-create.html#images-create-permissions).
361+
362+
If you're using the S3 deployment method (for zip file deployments), ensure your IAM role also has the following permissions:
256363

257364
```json
258365
{
@@ -264,6 +371,7 @@ If you're using the S3 deployment method, ensure your IAM role also has the foll
264371
"Action": [
265372
"s3:ListBucket",
266373
"s3:CreateBucket",
374+
"s3:GetObject",
267375
"s3:PutObject",
268376
"s3:PutBucketPublicAccessBlock",
269377
"s3:PutEncryptionConfiguration",
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
const core = require('@actions/core');
2+
const originalValidations = require('../validations');
3+
4+
jest.mock('@actions/core');
5+
6+
describe('Container Image Support Tests', () => {
7+
let originalEnv;
8+
9+
beforeEach(() => {
10+
jest.clearAllMocks();
11+
originalEnv = process.env;
12+
process.env = { ...originalEnv };
13+
process.env.GITHUB_SHA = 'abc123';
14+
15+
// Default mock implementations
16+
core.getInput.mockImplementation((name) => {
17+
const inputs = {
18+
'function-name': 'test-function',
19+
'package-type': 'Image',
20+
'image-uri': '123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:latest',
21+
'region': 'us-east-1'
22+
};
23+
return inputs[name] || '';
24+
});
25+
26+
core.getBooleanInput.mockReturnValue(false);
27+
});
28+
29+
afterEach(() => {
30+
process.env = originalEnv;
31+
});
32+
33+
describe('Package Type Validation', () => {
34+
test('should accept Image package type with image-uri', () => {
35+
const result = originalValidations.validateAllInputs();
36+
expect(result.valid).toBe(true);
37+
expect(result.packageType).toBe('Image');
38+
expect(result.imageUri).toBe('123456789012.dkr.ecr.us-east-1.amazonaws.com/my-repo:latest');
39+
expect(core.setFailed).not.toHaveBeenCalled();
40+
});
41+
42+
test('should fail when Image package type is used without image-uri', () => {
43+
core.getInput.mockImplementation((name) => {
44+
const inputs = {
45+
'function-name': 'test-function',
46+
'package-type': 'Image',
47+
'region': 'us-east-1'
48+
};
49+
return inputs[name] || '';
50+
});
51+
52+
const result = originalValidations.validateAllInputs();
53+
expect(result.valid).toBe(false);
54+
expect(core.setFailed).toHaveBeenCalledWith('image-uri must be provided when package-type is "Image"');
55+
});
56+
57+
58+
test('should fail when Zip package type is used without code-artifacts-dir', () => {
59+
core.getInput.mockImplementation((name) => {
60+
const inputs = {
61+
'function-name': 'test-function',
62+
'package-type': 'Zip',
63+
'region': 'us-east-1'
64+
};
65+
return inputs[name] || '';
66+
});
67+
68+
const result = originalValidations.validateAllInputs();
69+
expect(result.valid).toBe(false);
70+
expect(core.setFailed).toHaveBeenCalledWith('code-artifacts-dir must be provided when package-type is "Zip"');
71+
});
72+
73+
test('should reject invalid package type', () => {
74+
core.getInput.mockImplementation((name) => {
75+
const inputs = {
76+
'function-name': 'test-function',
77+
'package-type': 'InvalidType',
78+
'region': 'us-east-1'
79+
};
80+
return inputs[name] || '';
81+
});
82+
83+
const result = originalValidations.validateAllInputs();
84+
expect(result.valid).toBe(false);
85+
expect(core.setFailed).toHaveBeenCalledWith('Package type must be either \'Zip\' or \'Image\', got: InvalidType');
86+
});
87+
88+
test('should default to Zip package type when not specified', () => {
89+
core.getInput.mockImplementation((name) => {
90+
const inputs = {
91+
'function-name': 'test-function',
92+
'code-artifacts-dir': './artifacts',
93+
'region': 'us-east-1'
94+
};
95+
return inputs[name] || '';
96+
});
97+
98+
const result = originalValidations.validateAllInputs();
99+
expect(result.valid).toBe(true);
100+
expect(result.packageType).toBe('Zip');
101+
});
102+
});
103+
104+
105+
});

0 commit comments

Comments
 (0)