Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ Below are two commonly used Build examples for Node.js and Python:
| `logging-config` | CloudWatch Logs configuration | No | |
| `code-signing-config-arn` | ARN of code-signing configuration | No | |
| `tags` | Tags to apply to the function as JSON string | No | |
| `durable-config` | Configuration settings for durable functions as JSON string | No | |

## Outputs

Expand Down
184 changes: 184 additions & 0 deletions __tests__/validations.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,190 @@ describe('Validations Tests', () => {
expect(core.setFailed).not.toHaveBeenCalled();
});
});

describe('durable-config validation', () => {
test('should accept valid durable-config with ExecutionTimeout', () => {
const mockGetInput = jest.fn((name) => {
if (name === 'durable-config') {
return '{"ExecutionTimeout":3600}';
}
const inputs = {
'function-name': 'test-function',
'region': 'us-east-1',
'code-artifacts-dir': './test-dir'
};
return inputs[name] || '';
});
core.getInput = mockGetInput;
const result = originalValidations.validateAllInputs();
expect(result.valid).toBe(true);
expect(result.parsedDurableConfig).toEqual({ ExecutionTimeout: 3600 });
expect(core.setFailed).not.toHaveBeenCalled();
});

test('should accept valid durable-config with RetentionPeriodInDays', () => {
const mockGetInput = jest.fn((name) => {
if (name === 'durable-config') {
return '{"RetentionPeriodInDays":7}';
}
const inputs = {
'function-name': 'test-function',
'region': 'us-east-1',
'code-artifacts-dir': './test-dir'
};
return inputs[name] || '';
});
core.getInput = mockGetInput;
const result = originalValidations.validateAllInputs();
expect(result.valid).toBe(true);
expect(result.parsedDurableConfig).toEqual({ RetentionPeriodInDays: 7 });
expect(core.setFailed).not.toHaveBeenCalled();
});

test('should accept valid durable-config with both properties', () => {
const mockGetInput = jest.fn((name) => {
if (name === 'durable-config') {
return '{"ExecutionTimeout":3600,"RetentionPeriodInDays":7}';
}
const inputs = {
'function-name': 'test-function',
'region': 'us-east-1',
'code-artifacts-dir': './test-dir'
};
return inputs[name] || '';
});
core.getInput = mockGetInput;
const result = originalValidations.validateAllInputs();
expect(result.valid).toBe(true);
expect(result.parsedDurableConfig).toEqual({
ExecutionTimeout: 3600,
RetentionPeriodInDays: 7
});
expect(core.setFailed).not.toHaveBeenCalled();
});

test('should reject ExecutionTimeout below minimum', () => {
const mockGetInput = jest.fn((name) => {
if (name === 'durable-config') {
return '{"ExecutionTimeout":0}';
}
const inputs = {
'function-name': 'test-function',
'region': 'us-east-1',
'code-artifacts-dir': './test-dir'
};
return inputs[name] || '';
});
core.getInput = mockGetInput;
const result = originalValidations.validateAllInputs();
expect(result.valid).toBe(false);
expect(core.setFailed).toHaveBeenCalledWith(
expect.stringContaining('ExecutionTimeout must be between 1 and 31622400')
);
});

test('should reject ExecutionTimeout above maximum', () => {
const mockGetInput = jest.fn((name) => {
if (name === 'durable-config') {
return '{"ExecutionTimeout":31622401}';
}
const inputs = {
'function-name': 'test-function',
'region': 'us-east-1',
'code-artifacts-dir': './test-dir'
};
return inputs[name] || '';
});
core.getInput = mockGetInput;
const result = originalValidations.validateAllInputs();
expect(result.valid).toBe(false);
expect(core.setFailed).toHaveBeenCalledWith(
expect.stringContaining('ExecutionTimeout must be between 1 and 31622400')
);
});

test('should reject RetentionPeriodInDays below minimum', () => {
const mockGetInput = jest.fn((name) => {
if (name === 'durable-config') {
return '{"RetentionPeriodInDays":0}';
}
const inputs = {
'function-name': 'test-function',
'region': 'us-east-1',
'code-artifacts-dir': './test-dir'
};
return inputs[name] || '';
});
core.getInput = mockGetInput;
const result = originalValidations.validateAllInputs();
expect(result.valid).toBe(false);
expect(core.setFailed).toHaveBeenCalledWith(
expect.stringContaining('RetentionPeriodInDays must be between 1 and 90')
);
});

test('should reject RetentionPeriodInDays above maximum', () => {
const mockGetInput = jest.fn((name) => {
if (name === 'durable-config') {
return '{"RetentionPeriodInDays":91}';
}
const inputs = {
'function-name': 'test-function',
'region': 'us-east-1',
'code-artifacts-dir': './test-dir'
};
return inputs[name] || '';
});
core.getInput = mockGetInput;
const result = originalValidations.validateAllInputs();
expect(result.valid).toBe(false);
expect(core.setFailed).toHaveBeenCalledWith(
expect.stringContaining('RetentionPeriodInDays must be between 1 and 90')
);
});

test('should accept maximum valid values', () => {
const mockGetInput = jest.fn((name) => {
if (name === 'durable-config') {
return '{"ExecutionTimeout":31622400,"RetentionPeriodInDays":90}';
}
const inputs = {
'function-name': 'test-function',
'region': 'us-east-1',
'code-artifacts-dir': './test-dir'
};
return inputs[name] || '';
});
core.getInput = mockGetInput;
const result = originalValidations.validateAllInputs();
expect(result.valid).toBe(true);
expect(result.parsedDurableConfig).toEqual({
ExecutionTimeout: 31622400,
RetentionPeriodInDays: 90
});
expect(core.setFailed).not.toHaveBeenCalled();
});

test('should reject invalid JSON in durable-config', () => {
const mockGetInput = jest.fn((name) => {
if (name === 'durable-config') {
return '{invalid json}';
}
const inputs = {
'function-name': 'test-function',
'region': 'us-east-1',
'code-artifacts-dir': './test-dir'
};
return inputs[name] || '';
});
core.getInput = mockGetInput;
const result = originalValidations.validateAllInputs();
expect(result.valid).toBe(false);
expect(core.setFailed).toHaveBeenCalledWith(
expect.stringContaining('Invalid JSON in durable-config')
);
});
});
});
describe('getAdditionalInputs function', () => {
test('should handle invalid publish input and default to false', () => {
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ inputs:
tags:
description: 'Tags to apply to the function as a JSON string (e.g. {"Environment":"Production","Team":"DevOps"})'
required: false
durable-config:
description: 'Configuration settings for durable functions as a JSON string (e.g. {"ExecutionTimeout":3600,"RetentionPeriodInDays":7})'
required: false
outputs:
function-arn:
description: 'The ARN of the updated Lambda function.'
Expand Down
51 changes: 40 additions & 11 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ async function run() {
role, codeSigningConfigArn, kmsKeyArn, sourceKmsKeyArn,
vpcConfig, deadLetterConfig, tracingConfig,
layers, fileSystemConfigs, imageConfig, snapStart,
loggingConfig, tags,
loggingConfig, tags, durableConfig,
parsedEnvironment, parsedVpcConfig, parsedDeadLetterConfig,
parsedTracingConfig, parsedLayers, parsedFileSystemConfigs,
parsedImageConfig, parsedSnapStart, parsedLoggingConfig, parsedTags,
parsedDurableConfig,
functionDescription, dryRun, publish, revisionId,
runtime, handler, architectures
} = inputs;
Expand Down Expand Up @@ -81,9 +82,10 @@ async function run() {
revisionId, vpcConfig, parsedEnvironment, deadLetterConfig,
tracingConfig, layers, fileSystemConfigs, imageConfig,
snapStart, loggingConfig, tags, kmsKeyArn, codeSigningConfigArn,
durableConfig,
parsedVpcConfig, parsedDeadLetterConfig, parsedTracingConfig,
parsedLayers, parsedFileSystemConfigs, parsedImageConfig,
parsedSnapStart, parsedLoggingConfig, parsedTags
parsedSnapStart, parsedLoggingConfig, parsedTags, parsedDurableConfig
}, functionExists);

// Update function configuration
Expand All @@ -108,7 +110,8 @@ async function run() {
...(fileSystemConfigs && { FileSystemConfigs: parsedFileSystemConfigs }),
...(imageConfig && { ImageConfig: parsedImageConfig }),
...(snapStart && { SnapStart: parsedSnapStart }),
...(loggingConfig && { LoggingConfig: parsedLoggingConfig })
...(loggingConfig && { LoggingConfig: parsedLoggingConfig }),
...(durableConfig && { DurableConfig: parsedDurableConfig })
});

if (configChanged) {
Expand Down Expand Up @@ -136,14 +139,16 @@ async function run() {
imageConfig,
snapStart,
loggingConfig,
durableConfig,
parsedVpcConfig,
parsedDeadLetterConfig,
parsedTracingConfig,
parsedLayers,
parsedFileSystemConfigs,
parsedImageConfig,
parsedSnapStart,
parsedLoggingConfig
parsedLoggingConfig,
parsedDurableConfig
});
} else {
core.info('No configuration changes detected');
Expand Down Expand Up @@ -302,9 +307,10 @@ async function createFunction(client, inputs, functionExists) {
timeout, publish, architectures, ephemeralStorage, revisionId,
vpcConfig, parsedEnvironment, deadLetterConfig, tracingConfig,
layers, fileSystemConfigs, imageConfig, snapStart, loggingConfig, tags,
kmsKeyArn, codeSigningConfigArn, parsedVpcConfig, parsedDeadLetterConfig,
kmsKeyArn, codeSigningConfigArn, durableConfig,
parsedVpcConfig, parsedDeadLetterConfig,
parsedTracingConfig, parsedLayers, parsedFileSystemConfigs, parsedImageConfig,
parsedSnapStart, parsedLoggingConfig, parsedTags
parsedSnapStart, parsedLoggingConfig, parsedTags, parsedDurableConfig
} = inputs;

if (!functionExists) {
Expand Down Expand Up @@ -390,6 +396,7 @@ async function createFunction(client, inputs, functionExists) {
...(tags && { Tags: parsedTags }),
...(kmsKeyArn && { KMSKeyArn: kmsKeyArn }),
...(codeSigningConfigArn && { CodeSigningConfigArn: codeSigningConfigArn }),
...(durableConfig && { DurableConfig: parsedDurableConfig })
};

core.info(`Creating new Lambda function: ${functionName}`);
Expand Down Expand Up @@ -480,10 +487,10 @@ async function updateFunctionConfiguration(client, params) {
functionName, role, handler, functionDescription, parsedMemorySize,
timeout, runtime, kmsKeyArn, ephemeralStorage, vpcConfig,
parsedEnvironment, deadLetterConfig, tracingConfig, layers,
fileSystemConfigs, imageConfig, snapStart, loggingConfig,
fileSystemConfigs, imageConfig, snapStart, loggingConfig, durableConfig,
parsedVpcConfig, parsedDeadLetterConfig, parsedTracingConfig,
parsedLayers, parsedFileSystemConfigs, parsedImageConfig,
parsedSnapStart, parsedLoggingConfig
parsedSnapStart, parsedLoggingConfig, parsedDurableConfig
} = params;

try {
Expand All @@ -505,7 +512,8 @@ async function updateFunctionConfiguration(client, params) {
...(fileSystemConfigs && { FileSystemConfigs: parsedFileSystemConfigs }),
...(imageConfig && { ImageConfig: parsedImageConfig }),
...(snapStart && { SnapStart: parsedSnapStart }),
...(loggingConfig && { LoggingConfig: parsedLoggingConfig })
...(loggingConfig && { LoggingConfig: parsedLoggingConfig }),
...(durableConfig && { DurableConfig: parsedDurableConfig })
};

core.info(`Updating function configuration for ${functionName}`);
Expand Down Expand Up @@ -98020,10 +98028,11 @@ function validateJsonInputs() {
const snapStart = core.getInput('snap-start', { required: false });
const loggingConfig = core.getInput('logging-config', { required: false });
const tags = core.getInput('tags', { required: false });
const durableConfig = core.getInput('durable-config', { required: false });

let parsedEnvironment, parsedVpcConfig, parsedDeadLetterConfig, parsedTracingConfig,
parsedLayers, parsedFileSystemConfigs, parsedImageConfig, parsedSnapStart,
parsedLoggingConfig, parsedTags;
parsedLoggingConfig, parsedTags, parsedDurableConfig;

try {
if (environment) {
Expand Down Expand Up @@ -98094,6 +98103,24 @@ function validateJsonInputs() {
throw new Error('tags must be an object of key-value pairs');
}
}

if (durableConfig) {
parsedDurableConfig = parseJsonInput(durableConfig, 'durable-config');
if (parsedDurableConfig.ExecutionTimeout !== undefined) {
if (typeof parsedDurableConfig.ExecutionTimeout !== 'number' ||
parsedDurableConfig.ExecutionTimeout < 1 ||
parsedDurableConfig.ExecutionTimeout > 31622400) {
throw new Error('durable-config ExecutionTimeout must be between 1 and 31622400 seconds');
}
}
if (parsedDurableConfig.RetentionPeriodInDays !== undefined) {
if (typeof parsedDurableConfig.RetentionPeriodInDays !== 'number' ||
parsedDurableConfig.RetentionPeriodInDays < 1 ||
parsedDurableConfig.RetentionPeriodInDays > 90) {
Comment thread
bchampp marked this conversation as resolved.
throw new Error('durable-config RetentionPeriodInDays must be between 1 and 90 days');
}
}
}
} catch (error) {
core.setFailed(`Input validation error: ${error.message}`);
return { valid: false };
Expand All @@ -98111,6 +98138,7 @@ function validateJsonInputs() {
snapStart,
loggingConfig,
tags,
durableConfig,
parsedEnvironment,
parsedVpcConfig,
parsedDeadLetterConfig,
Expand All @@ -98120,7 +98148,8 @@ function validateJsonInputs() {
parsedImageConfig,
parsedSnapStart,
parsedLoggingConfig,
parsedTags
parsedTags,
parsedDurableConfig
};
}

Expand Down
Loading