Skip to content
Open
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
25 changes: 25 additions & 0 deletions __tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
isUrl,
parseParameters,
parseBoolean,
parseRetryMode,
withRetry
} from '../src/utils'
import * as path from 'path'
Expand Down Expand Up @@ -473,6 +474,30 @@ describe('withRetry', () => {
})
})

describe('parseRetryMode', () => {
test('returns "standard" for valid input', () => {
expect(parseRetryMode('standard')).toBe('standard')
})

test('returns "adaptive" for valid input', () => {
expect(parseRetryMode('adaptive')).toBe('adaptive')
})

test('throws on invalid input', () => {
expect(() => parseRetryMode('exponential')).toThrow(
"Invalid retry-mode: exponential. Supported values: 'standard', 'adaptive'."
)
})

test('returns undefined for empty string', () => {
expect(parseRetryMode('')).toBeUndefined()
})

test('returns undefined for undefined', () => {
expect(parseRetryMode(undefined)).toBeUndefined()
})
})

describe('Configure Proxy', () => {
beforeEach(() => {
jest.clearAllMocks()
Expand Down
90 changes: 90 additions & 0 deletions __tests__/validation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { validateAndParseInputs } from '../src/validation'

const baseInputs = {
mode: 'create-and-execute',
name: 'TestStack',
template: 'template.yaml',
capabilities: 'CAPABILITY_IAM',
'parameter-overrides': '',
'fail-on-empty-changeset': '1',
'no-execute-changeset': '0',
'no-delete-failed-changeset': '0',
'disable-rollback': '0',
'timeout-in-minutes': '',
'notification-arns': '',
'role-arn': '',
tags: '',
'termination-protection': '',
'http-proxy': '',
'change-set-name': '',
'include-nested-stacks-change-set': '0',
'deployment-mode': '',
's3-bucket': '',
's3-prefix': '',
'execute-change-set-id': '',
'max-attempts': '',
'retry-mode': ''
}

describe('validateAndParseInputs', () => {
describe('max-attempts', () => {
test('parses a valid number string', () => {
const result = validateAndParseInputs({
...baseInputs,
'max-attempts': '10'
})
expect(result['max-attempts']).toBe(10)
})

test('returns undefined for empty string', () => {
const result = validateAndParseInputs({
...baseInputs,
'max-attempts': ''
})
expect(result['max-attempts']).toBeUndefined()
})

test('returns undefined when not provided', () => {
const result = validateAndParseInputs({
...baseInputs,
'max-attempts': undefined
})
expect(result['max-attempts']).toBeUndefined()
})
})

describe('retry-mode', () => {
test('parses "standard" correctly', () => {
const result = validateAndParseInputs({
...baseInputs,
'retry-mode': 'standard'
})
expect(result['retry-mode']).toBe('standard')
})

test('parses "adaptive" correctly', () => {
const result = validateAndParseInputs({
...baseInputs,
'retry-mode': 'adaptive'
})
expect(result['retry-mode']).toBe('adaptive')
})

test('returns undefined for empty string', () => {
const result = validateAndParseInputs({
...baseInputs,
'retry-mode': ''
})
expect(result['retry-mode']).toBeUndefined()
})

test('throws on invalid retry-mode', () => {
expect(() =>
validateAndParseInputs({
...baseInputs,
'retry-mode': 'exponential'
})
).toThrow()
})
})
})
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ inputs:
s3-prefix:
description: "A prefix to use for the S3 object key when uploading the template. The final key will be '<s3-prefix>/<template-filename>'. Defaults to no prefix."
required: false
max-attempts:
description: "The maximum number of retry attempts for AWS API calls. Defaults to the AWS SDK default (3)."
required: false
retry-mode:
description: "The retry mode for AWS API calls. Supported values: 'standard', 'adaptive'. Defaults to the AWS SDK default ('standard')."
required: false
deployment-mode:
description: "The deployment mode for the change set. Use 'REVERT_DRIFT' to create a change set that reverts drift. Defaults to standard deployment."
required: false
Expand Down
23 changes: 22 additions & 1 deletion dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75609,6 +75609,8 @@ function run() {
'deployment-mode': core.getInput('deployment-mode', { required: false }),
's3-bucket': core.getInput('s3-bucket', { required: false }),
's3-prefix': core.getInput('s3-prefix', { required: false }),
'max-attempts': core.getInput('max-attempts', { required: false }),
'retry-mode': core.getInput('retry-mode', { required: false }),
'execute-change-set-id': core.getInput('execute-change-set-id', {
required: false
})
Expand All @@ -75624,6 +75626,12 @@ function run() {
})
});
}
if (inputs['max-attempts']) {
clientConfiguration = Object.assign(Object.assign({}, clientConfiguration), { maxAttempts: inputs['max-attempts'] });
}
if (inputs['retry-mode']) {
clientConfiguration = Object.assign(Object.assign({}, clientConfiguration), { retryMode: inputs['retry-mode'] });
}
const cfn = new client_cloudformation_1.CloudFormationClient(Object.assign({}, clientConfiguration));
// Execute existing change set mode
if (inputs.mode === 'execute-only') {
Expand Down Expand Up @@ -75858,6 +75866,7 @@ exports.parseString = parseString;
exports.parseNumber = parseNumber;
exports.parseBoolean = parseBoolean;
exports.parseParameters = parseParameters;
exports.parseRetryMode = parseRetryMode;
exports.parseDeploymentMode = parseDeploymentMode;
exports.withRetry = withRetry;
exports.configureProxy = configureProxy;
Expand Down Expand Up @@ -75969,6 +75978,16 @@ function parseParameters(parameterOverrides) {
};
});
}
function parseRetryMode(s) {
const parsed = parseString(s);
if (!parsed) {
return undefined;
}
if (parsed === 'standard' || parsed === 'adaptive') {
return parsed;
}
throw new Error(`Invalid retry-mode: ${parsed}. Supported values: 'standard', 'adaptive'.`);
}
function parseDeploymentMode(s) {
const parsed = parseString(s);
if (!parsed) {
Expand Down Expand Up @@ -76048,7 +76067,9 @@ const baseSchema = zod_1.z.object({
.enum(['create-and-execute', 'create-only', 'execute-only'])
.default('create-and-execute'),
name: zod_1.z.string().min(1, 'Stack name is required'),
'http-proxy': zod_1.z.string().optional().transform(emptyToUndefined)
'http-proxy': zod_1.z.string().optional().transform(emptyToUndefined),
'max-attempts': zod_1.z.string().optional().transform(utils_1.parseNumber),
'retry-mode': zod_1.z.string().optional().transform(utils_1.parseRetryMode)
});
const createSchema = baseSchema.extend({
mode: zod_1.z.enum(['create-and-execute', 'create-only']),
Expand Down
16 changes: 16 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ export async function run(): Promise<void> {
'deployment-mode': core.getInput('deployment-mode', { required: false }),
's3-bucket': core.getInput('s3-bucket', { required: false }),
's3-prefix': core.getInput('s3-prefix', { required: false }),
'max-attempts': core.getInput('max-attempts', { required: false }),
'retry-mode': core.getInput('retry-mode', { required: false }),
'execute-change-set-id': core.getInput('execute-change-set-id', {
required: false
})
Expand All @@ -113,6 +115,20 @@ export async function run(): Promise<void> {
}
}

if (inputs['max-attempts']) {
clientConfiguration = {
...clientConfiguration,
...{ maxAttempts: inputs['max-attempts'] }
}
}

if (inputs['retry-mode']) {
clientConfiguration = {
...clientConfiguration,
...{ retryMode: inputs['retry-mode'] }
}
}

const cfn = new CloudFormationClient({ ...clientConfiguration })

// Execute existing change set mode
Expand Down
18 changes: 18 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,24 @@ export function parseParameters(
})
}

export function parseRetryMode(
s?: string
): 'standard' | 'adaptive' | undefined {
const parsed = parseString(s)

if (!parsed) {
return undefined
}

if (parsed === 'standard' || parsed === 'adaptive') {
return parsed
}

throw new Error(
`Invalid retry-mode: ${parsed}. Supported values: 'standard', 'adaptive'.`
)
}

export function parseDeploymentMode(s: string): 'REVERT_DRIFT' | undefined {
const parsed = parseString(s)

Expand Down
7 changes: 5 additions & 2 deletions src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
parseNumber,
parseTags,
parseParameters,
parseBoolean
parseBoolean,
parseRetryMode
} from './utils'

// Helper transformers
Expand All @@ -16,7 +17,9 @@ const baseSchema = z.object({
.enum(['create-and-execute', 'create-only', 'execute-only'])
.default('create-and-execute'),
name: z.string().min(1, 'Stack name is required'),
'http-proxy': z.string().optional().transform(emptyToUndefined)
'http-proxy': z.string().optional().transform(emptyToUndefined),
'max-attempts': z.string().optional().transform(parseNumber),
'retry-mode': z.string().optional().transform(parseRetryMode)
})

const createSchema = baseSchema.extend({
Expand Down