From 55cd4e1373d1439009dcb6061befa3c8d54a249c Mon Sep 17 00:00:00 2001 From: "wuychloe@amazon.com chloe1818" Date: Thu, 31 Jul 2025 14:06:15 -0700 Subject: [PATCH 1/3] Fixed README to use aws-region when configuring credentials and clarified usage section description. --- README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 146ba9fe..1a78ea7a 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,11 @@ jobs: # Add any additional inputs your action supports ``` -The required parameters to deploy are function name, code artifacts directory, handler, and runtime. The function name and code artifacts directory need to be provided by the user. However, the handler and runtime do not and will default to index.handler and nodejs20.x if not provided. +The action automatically updates your Lambda function code when the required parameters are provided. If the function doesn't exist, it will be created first and then updated. + +Required parameters include function name, code artifacts directory, handler, and runtime. While function name and code artifacts directory must be specified, handler and runtime will default to `index.handler` and `nodejs20.x` respectively if not provided. + +The following examples demonstrate additional features available: ### Update Function Configuration @@ -178,7 +182,8 @@ Here's an example of using OIDC with the aws-actions/configure-aws-credentials a - name: Configure AWS credentials with OIDC uses: aws-actions/configure-aws-credentials@v2 with: - role-to-assume: arn:aws:iam::123456789012:role/GitHubActionRole + role-to-assume: my-role + aws-region: my-region ``` To use OIDC authentication, you must configure a trust policy in AWS IAM that allows GitHub Actions to assume an IAM role. Here's an example trust policy: From d721b920344da6b8d1bbfb75c6a6b82bae4633db Mon Sep 17 00:00:00 2001 From: "wuychloe@amazon.com chloe1818" Date: Thu, 31 Jul 2025 15:14:36 -0700 Subject: [PATCH 2/3] Updated function create unit test. --- __tests__/function_create.test.js | 162 ++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/__tests__/function_create.test.js b/__tests__/function_create.test.js index aa1e6421..56a56b29 100644 --- a/__tests__/function_create.test.js +++ b/__tests__/function_create.test.js @@ -82,6 +82,19 @@ jest.mock('@aws-sdk/client-s3', () => { })) }; }); +jest.mock('@aws-sdk/client-sts', () => { + const original = jest.requireActual('@aws-sdk/client-sts'); + return { + ...original, + GetCallerIdentityCommand: jest.fn().mockImplementation((params) => ({ + ...params, + type: 'GetCallerIdentityCommand' + })), + STSClient: jest.fn().mockImplementation(() => ({ + send: jest.fn().mockResolvedValue({ Account: '123456789012' }) + })) + }; +}); afterAll(() => { jest.clearAllMocks(); @@ -598,4 +611,153 @@ describe('Function Existence Check', () => { })); }); }); + + test('Config changed with dry run logs message', () => { + const configChanged = true; + const dryRun = true; + + if (configChanged) { + if (dryRun) { + core.info('[DRY RUN] Configuration updates are not simulated in dry run mode'); + return; + } + } + + expect(core.info).toHaveBeenCalledWith('[DRY RUN] Configuration updates are not simulated in dry run mode'); + }); + + test('Config changed without dry run calls updateFunctionConfiguration', async () => { + const mockClient = { send: jest.fn() }; + const configChanged = true; + const dryRun = false; + + if (configChanged) { + if (dryRun) { + core.info('[DRY RUN] Configuration updates are not simulated in dry run mode'); + return; + } + + await index.updateFunctionConfiguration(mockClient, { + functionName: 'test-function', + role: 'test-role', + handler: 'index.handler', + functionDescription: 'test', + parsedMemorySize: 256, + timeout: 30, + runtime: 'nodejs20.x', + kmsKeyArn: 'test-kms', + ephemeralStorage: 512, + vpcConfig: '{}', + parsedEnvironment: {}, + deadLetterConfig: '{}', + tracingConfig: '{}', + layers: '[]', + fileSystemConfigs: '[]', + imageConfig: '{}', + snapStart: '{}', + loggingConfig: '{}', + parsedVpcConfig: {}, + parsedDeadLetterConfig: {}, + parsedTracingConfig: {}, + parsedLayers: [], + parsedFileSystemConfigs: [], + parsedImageConfig: {}, + parsedSnapStart: {}, + parsedLoggingConfig: {} + }); + } + + expect(mockClient.send).toHaveBeenCalled(); + }); + + test('No config changes logs no changes message', () => { + const configChanged = false; + + if (configChanged) { + // Should not execute + } else { + core.info('No configuration changes detected'); + } + + expect(core.info).toHaveBeenCalledWith('No configuration changes detected'); + }); + + test('generateS3Key creates key with timestamp', () => { + const result = index.generateS3Key('test-function'); + expect(result).toMatch(/^lambda-deployments\/test-function\/\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-\d{3}\.zip$/); + }); + + test('generateS3Key includes commit hash when GITHUB_SHA exists', () => { + process.env.GITHUB_SHA = 'abcdef1234567890'; + const result = index.generateS3Key('test-function'); + expect(result).toContain('-abcdef1'); + delete process.env.GITHUB_SHA; + }); + + test('validateBucketName validates bucket names', () => { + expect(index.validateBucketName('valid-bucket-name')).toBe(true); + expect(index.validateBucketName('ab')).toBe(false); + expect(index.validateBucketName('INVALID')).toBe(false); + expect(index.validateBucketName('192.168.1.1')).toBe(false); + expect(index.validateBucketName('bucket..name')).toBe(false); + }); + + test('isEmptyValue checks empty values', () => { + expect(index.isEmptyValue(null)).toBe(true); + expect(index.isEmptyValue('')).toBe(true); + expect(index.isEmptyValue([])).toBe(true); + expect(index.isEmptyValue({})).toBe(true); + expect(index.isEmptyValue('value')).toBe(false); + expect(index.isEmptyValue({ SubnetIds: [] })).toBe(false); + }); + + test('cleanNullKeys removes null values', () => { + expect(index.cleanNullKeys(null)).toBeUndefined(); + expect(index.cleanNullKeys('')).toBeUndefined(); + expect(index.cleanNullKeys({ key: 'value', empty: null })).toEqual({ key: 'value' }); + expect(index.cleanNullKeys([])).toBeUndefined(); + expect(index.cleanNullKeys(['value', null])).toEqual(['value']); + }); + + test('deepEqual compares objects deeply', () => { + expect(index.deepEqual({ a: 1 }, { a: 1 })).toBe(true); + expect(index.deepEqual({ a: 1 }, { a: 2 })).toBe(false); + expect(index.deepEqual([1, 2], [1, 2])).toBe(true); + expect(index.deepEqual([1, 2], [1, 3])).toBe(false); + expect(index.deepEqual(null, null)).toBe(true); + expect(index.deepEqual('test', 'test')).toBe(true); + }); + + test('hasConfigurationChanged detects changes', async () => { + const current = { Role: 'old-role', Handler: 'old.handler' }; + const updated = { Role: 'new-role', Handler: 'old.handler' }; + const result = await index.hasConfigurationChanged(current, updated); + expect(result).toBe(true); + expect(core.info).toHaveBeenCalledWith(expect.stringContaining('Configuration difference detected')); + }); + + test('hasConfigurationChanged returns true for empty current config', async () => { + const result = await index.hasConfigurationChanged({}, { Role: 'test' }); + expect(result).toBe(true); + }); + + test('getAwsAccountId retrieves account ID', async () => { + const mockSend = jest.fn().mockResolvedValue({ Account: '123456789012' }); + const { STSClient } = require('@aws-sdk/client-sts'); + STSClient.mockImplementation(() => ({ send: mockSend })); + + const result = await index.getAwsAccountId('us-east-1'); + expect(result).toBe('123456789012'); + expect(core.info).toHaveBeenCalledWith('Successfully retrieved AWS account ID: 123456789012'); + }); + + test('getAwsAccountId handles errors', async () => { + const mockSend = jest.fn().mockRejectedValue(new Error('STS error')); + const { STSClient } = require('@aws-sdk/client-sts'); + STSClient.mockImplementation(() => ({ send: mockSend })); + + const result = await index.getAwsAccountId('us-east-1'); + expect(result).toBeNull(); + expect(core.warning).toHaveBeenCalledWith('Failed to retrieve AWS account ID: STS error'); + }); }); From b7bee767963736b28bce6aefe6a1fad026be38c6 Mon Sep 17 00:00:00 2001 From: "wuychloe@amazon.com chloe1818" Date: Thu, 31 Jul 2025 22:31:53 -0700 Subject: [PATCH 3/3] Removed unit test that tests S3 key with timestamp. --- __tests__/function_create.test.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/__tests__/function_create.test.js b/__tests__/function_create.test.js index 56a56b29..4327fbe2 100644 --- a/__tests__/function_create.test.js +++ b/__tests__/function_create.test.js @@ -682,11 +682,6 @@ describe('Function Existence Check', () => { expect(core.info).toHaveBeenCalledWith('No configuration changes detected'); }); - test('generateS3Key creates key with timestamp', () => { - const result = index.generateS3Key('test-function'); - expect(result).toMatch(/^lambda-deployments\/test-function\/\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}-\d{3}\.zip$/); - }); - test('generateS3Key includes commit hash when GITHUB_SHA exists', () => { process.env.GITHUB_SHA = 'abcdef1234567890'; const result = index.generateS3Key('test-function');