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
79 changes: 75 additions & 4 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ExecuteChangeSetCommand,
DescribeStacksCommand
} from '@aws-sdk/client-cloudformation'
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3'
import { mockClient } from 'aws-sdk-client-mock'
import { FileHandle } from 'fs/promises'
import 'aws-sdk-client-mock-jest'
Expand Down Expand Up @@ -60,6 +61,7 @@ const mockStackId =
'arn:aws:cloudformation:us-east-1:123456789012:stack/myteststack/466df9e0-0dff-08e3-8e2f-5088487c4896'

const mockCfnClient = mockClient(CloudFormationClient)
const mockS3Client = mockClient(S3Client)

// Helper function to create complete inputs
const createInputs = (overrides: Partial<Inputs> = {}): Inputs => ({
Expand All @@ -81,6 +83,8 @@ const createInputs = (overrides: Partial<Inputs> = {}): Inputs => ({
'change-set-name': '',
'include-nested-stacks-change-set': '0',
'deployment-mode': '',
's3-bucket': '',
's3-prefix': '',
'execute-change-set-id': '',
...overrides
})
Expand Down Expand Up @@ -135,17 +139,15 @@ describe('Deploy CloudFormation Stack', () => {
}
)

mockS3Client.reset().on(PutObjectCommand).resolves({})

mockCfnClient
.reset()
.on(CreateChangeSetCommand)
.resolves({
StackId: mockStackId
})
.on(CreateChangeSetCommand)
.resolves({
StackId: mockStackId
})
.on(CreateChangeSetCommand)
.resolves({})
.on(DescribeChangeSetCommand)
.resolves({
Expand Down Expand Up @@ -1554,4 +1556,73 @@ describe('Deploy CloudFormation Stack', () => {
expect(core.setOutput).toHaveBeenCalledWith('has-changes', 'false')
expect(core.setOutput).toHaveBeenCalledWith('changes-count', '0')
})

test('uploads template to S3 when s3-bucket is provided', async () => {
const inputs = createInputs({
's3-bucket': 'my-deploy-bucket'
})

jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
return inputs[name]
})

await run()

expect(core.setFailed).toHaveBeenCalledTimes(0)
expect(mockS3Client).toHaveReceivedCommandTimes(PutObjectCommand, 1)
expect(mockS3Client).toHaveReceivedCommandWith(PutObjectCommand, {
Bucket: 'my-deploy-bucket',
Key: 'template.yaml',
Body: mockTemplate
})
expect(mockCfnClient).toHaveReceivedCommandWith(CreateChangeSetCommand, {
StackName: 'MockStack',
TemplateURL: 'https://my-deploy-bucket.s3.amazonaws.com/template.yaml',
TemplateBody: undefined
})
})

test('uploads template to S3 with prefix when s3-prefix is provided', async () => {
const inputs = createInputs({
's3-bucket': 'my-deploy-bucket',
's3-prefix': 'cfn-templates'
})

jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
return inputs[name]
})

await run()

expect(core.setFailed).toHaveBeenCalledTimes(0)
expect(mockS3Client).toHaveReceivedCommandTimes(PutObjectCommand, 1)
expect(mockS3Client).toHaveReceivedCommandWith(PutObjectCommand, {
Bucket: 'my-deploy-bucket',
Key: 'cfn-templates/template.yaml',
Body: mockTemplate
})
expect(mockCfnClient).toHaveReceivedCommandWith(CreateChangeSetCommand, {
StackName: 'MockStack',
TemplateURL:
'https://my-deploy-bucket.s3.amazonaws.com/cfn-templates/template.yaml',
TemplateBody: undefined
})
})

test('does not upload to S3 when template is a URL', async () => {
const inputs = createInputs({
template:
'https://s3.amazonaws.com/templates/myTemplate.template?versionId=123ab1cdeKdOW5IH4GAcYbEngcpTJTDW',
's3-bucket': 'my-deploy-bucket'
})

jest.spyOn(core, 'getInput').mockImplementation((name: string) => {
return inputs[name]
})

await run()

expect(core.setFailed).toHaveBeenCalledTimes(0)
expect(mockS3Client).toHaveReceivedCommandTimes(PutObjectCommand, 0)
})
})
6 changes: 6 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ inputs:
execute-change-set-id:
description: "Execute an existing change set by ID or name instead of creating a new one. When provided, only 'name' (stack name) is required."
required: false
s3-bucket:
description: "The name of the S3 bucket where the template will be uploaded before creating the change set. When provided, the template file is uploaded to S3 and TemplateURL is used instead of TemplateBody, avoiding the 51,200 byte inline limit."
required: false
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
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
24 changes: 16 additions & 8 deletions dist/579.index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,13 @@ class EventStreamSerde {
body: event.body,
};
}
const unionMember = Object.keys(event).find((key) => {
return key !== "__type";
}) ?? "";
let unionMember = "";
for (const key in event) {
if (key !== "__type") {
unionMember = key;
break;
}
}
const { additionalHeaders, body, eventType, explicitPayloadContentType } = this.writeEventBody(unionMember, unionSchema, event);
const headers = {
":event-type": { type: "string", value: eventType },
Expand All @@ -81,9 +85,13 @@ class EventStreamSerde {
const memberSchemas = unionSchema.getMemberSchemas();
const initialResponseMarker = Symbol("initialResponseMarker");
const asyncIterable = marshaller.deserialize(response.body, async (event) => {
const unionMember = Object.keys(event).find((key) => {
return key !== "__type";
}) ?? "";
let unionMember = "";
for (const key in event) {
if (key !== "__type") {
unionMember = key;
break;
}
}
const body = event[unionMember].body;
if (unionMember === "initial-response") {
const dataObject = await this.deserializer.read(responseSchema, body);
Expand Down Expand Up @@ -159,8 +167,8 @@ class EventStreamSerde {
if (!responseSchema) {
throw new Error("@smithy::core/protocols - initial-response event encountered in event stream but no response schema given.");
}
for (const [key, value] of Object.entries(firstEvent.value)) {
initialResponseContainer[key] = value;
for (const key in firstEvent.value) {
initialResponseContainer[key] = firstEvent.value[key];
}
}
return {
Expand Down
Loading
Loading