Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(feat) output change set id #44

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
117 changes: 101 additions & 16 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Outputs:

const mockStackId =
'arn:aws:cloudformation:us-east-1:123456789012:stack/myteststack/466df9e0-0dff-08e3-8e2f-5088487c4896'
const mockChangeSetId =
'arn:aws:cloudformation:us-west-2:123456789012:changeSet/my-change-set/4eca1a01-e285-xmpl-8026-9a1967bfb4b0'

const mockCreateStack = jest.fn()
const mockUpdateStack = jest.fn()
Expand Down Expand Up @@ -115,15 +117,19 @@ describe('Deploy CloudFormation Stack', () => {
mockCreateChangeSet.mockImplementation(() => {
return {
promise(): Promise<aws.CloudFormation.Types.CreateChangeSetOutput> {
return Promise.resolve({})
return Promise.resolve({
Id: mockChangeSetId
})
}
}
})

mockDescribeChangeSet.mockImplementation(() => {
return {
promise(): Promise<aws.CloudFormation.Types.DescribeChangeSetOutput> {
return Promise.resolve({})
return Promise.resolve({
ChangeSetId: mockChangeSetId
})
}
}
})
Expand Down Expand Up @@ -204,8 +210,13 @@ describe('Deploy CloudFormation Stack', () => {
DisableRollback: false,
EnableTerminationProtection: false
})
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
'UNKNOWN'
)
})

test('sets the stack outputs as action outputs', async () => {
Expand Down Expand Up @@ -269,10 +280,15 @@ describe('Deploy CloudFormation Stack', () => {
DisableRollback: false,
EnableTerminationProtection: false
})
expect(core.setOutput).toHaveBeenCalledTimes(3)
expect(core.setOutput).toHaveBeenCalledTimes(4)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'hello', 'world')
expect(core.setOutput).toHaveBeenNthCalledWith(3, 'foo', 'bar')
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
'UNKNOWN'
)
expect(core.setOutput).toHaveBeenNthCalledWith(3, 'hello', 'world')
expect(core.setOutput).toHaveBeenNthCalledWith(4, 'foo', 'bar')
})

test('deploys the stack with template url', async () => {
Expand Down Expand Up @@ -311,8 +327,13 @@ describe('Deploy CloudFormation Stack', () => {
DisableRollback: false,
EnableTerminationProtection: false
})
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
'UNKNOWN'
)
})

test('deploys the stack with termination protection', async () => {
Expand Down Expand Up @@ -352,8 +373,13 @@ describe('Deploy CloudFormation Stack', () => {
DisableRollback: false,
EnableTerminationProtection: true
})
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
'UNKNOWN'
)
})

test('deploys the stack with disabling rollback', async () => {
Expand Down Expand Up @@ -393,8 +419,13 @@ describe('Deploy CloudFormation Stack', () => {
DisableRollback: true,
EnableTerminationProtection: false
})
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
'UNKNOWN'
)
})

test('deploys the stack with Notification ARNs', async () => {
Expand Down Expand Up @@ -439,8 +470,13 @@ describe('Deploy CloudFormation Stack', () => {
DisableRollback: false,
EnableTerminationProtection: false
})
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
'UNKNOWN'
)
})

test('deploys the stack with Role ARN', async () => {
Expand Down Expand Up @@ -481,8 +517,13 @@ describe('Deploy CloudFormation Stack', () => {
DisableRollback: false,
EnableTerminationProtection: false
})
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
'UNKNOWN'
)
})

test('deploys the stack with tags', async () => {
Expand Down Expand Up @@ -523,8 +564,13 @@ describe('Deploy CloudFormation Stack', () => {
DisableRollback: false,
EnableTerminationProtection: false
})
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
'UNKNOWN'
)
})

test('deploys the stack with timeout', async () => {
Expand Down Expand Up @@ -565,8 +611,13 @@ describe('Deploy CloudFormation Stack', () => {
DisableRollback: false,
EnableTerminationProtection: false
})
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
'UNKNOWN'
)
})

test('successfully update the stack', async () => {
Expand Down Expand Up @@ -625,6 +676,14 @@ describe('Deploy CloudFormation Stack', () => {
StackName: 'MockStack'
})
expect(mockCfnWaiter).toHaveBeenCalledTimes(2)

expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
mockChangeSetId
)
})

test('no execute change set on update the stack', async () => {
Expand Down Expand Up @@ -692,6 +751,14 @@ describe('Deploy CloudFormation Stack', () => {
})
expect(mockExecuteChangeSet).toHaveBeenCalledTimes(0)
expect(mockCfnWaiter).toHaveBeenCalledTimes(1)

expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
mockChangeSetId
)
})

test('error is caught updating if create change fails', async () => {
Expand Down Expand Up @@ -857,7 +924,13 @@ describe('Deploy CloudFormation Stack', () => {
await run()

expect(core.setFailed).toHaveBeenCalledTimes(0)
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
mockChangeSetId
)
expect(mockDescribeStacks).toHaveBeenCalledTimes(2)
expect(mockDescribeStacks).toHaveBeenNthCalledWith(1, {
StackName: 'MockStack'
Expand Down Expand Up @@ -964,7 +1037,13 @@ describe('Deploy CloudFormation Stack', () => {
await run()

expect(core.setFailed).toHaveBeenCalledTimes(0)
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
mockChangeSetId
)
expect(mockDescribeStacks).toHaveBeenCalledTimes(2)
expect(mockDescribeStacks).toHaveBeenNthCalledWith(1, {
StackName: 'MockStack'
Expand Down Expand Up @@ -1164,7 +1243,13 @@ describe('Deploy CloudFormation Stack', () => {
await run()

expect(core.setFailed).toHaveBeenCalledTimes(0)
expect(core.setOutput).toHaveBeenCalledTimes(1)
expect(core.setOutput).toHaveBeenCalledTimes(2)
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'stack-id', mockStackId)
expect(core.setOutput).toHaveBeenNthCalledWith(
2,
'change-set-id',
mockChangeSetId
)
expect(mockDescribeStacks).toHaveBeenCalledTimes(2)
expect(mockDescribeStacks).toHaveBeenNthCalledWith(1, {
StackName: 'MockStack'
Expand Down
2 changes: 2 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ inputs:
outputs:
stack-id:
description: "The id of the deployed stack. In addition, any outputs declared in the deployed CloudFormation stack will also be set as outputs for the action, e.g. if the stack has a stack output named 'foo', this action will also have an output named 'foo'."
change-set-id:
description: "The id of the created change set. If no change set was created, defaults to 'UNKNOWN'"
runs:
using: "node12"
main: "dist/index.js"
36 changes: 28 additions & 8 deletions src/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import { CreateChangeSetInput, CreateStackInput } from './main'

export type Stack = aws.CloudFormation.Stack

interface IdOutputs {
StackId?: string
ChangeSetId?: string
}

export async function cleanupChangeSet(
cfn: aws.CloudFormation,
stack: Stack,
params: CreateChangeSetInput,
noEmptyChangeSet: boolean,
noDeleteFailedChangeSet: boolean
): Promise<string | undefined> {
): Promise<IdOutputs> {
const knownErrorMessages = [
`No updates are to be performed`,
`The submitted information didn't contain changes`
Expand Down Expand Up @@ -41,13 +46,20 @@ export async function cleanupChangeSet(
changeSetStatus.StatusReason?.includes(err)
)
) {
return stack.StackId
return {
StackId: stack.StackId,
ChangeSetId: changeSetStatus.ChangeSetId
}
}

throw new Error(
`Failed to create Change Set: ${changeSetStatus.StatusReason}`
)
}
return {
StackId: stack.StackId,
ChangeSetId: changeSetStatus.ChangeSetId
}
}

export async function updateStack(
Expand All @@ -57,9 +69,9 @@ export async function updateStack(
noEmptyChangeSet: boolean,
noExecuteChageSet: boolean,
noDeleteFailedChangeSet: boolean
): Promise<string | undefined> {
): Promise<IdOutputs> {
core.debug('Creating CloudFormation Change Set')
await cfn.createChangeSet(params).promise()
const createChangeSet = await cfn.createChangeSet(params).promise()

try {
core.debug('Waiting for CloudFormation Change Set creation')
Expand All @@ -81,7 +93,10 @@ export async function updateStack(

if (noExecuteChageSet) {
core.debug('Not executing the change set')
return stack.StackId
return {
StackId: stack.StackId,
ChangeSetId: createChangeSet.Id
}
}

core.debug('Executing CloudFormation change set')
Expand All @@ -97,7 +112,10 @@ export async function updateStack(
.waitFor('stackUpdateComplete', { StackName: stack.StackId })
.promise()

return stack.StackId
return {
StackId: stack.StackId,
ChangeSetId: createChangeSet.Id
}
}

async function getStack(
Expand Down Expand Up @@ -125,7 +143,7 @@ export async function deployStack(
noEmptyChangeSet: boolean,
noExecuteChageSet: boolean,
noDeleteFailedChangeSet: boolean
): Promise<string | undefined> {
): Promise<IdOutputs> {
const stack = await getStack(cfn, params.StackName)

if (!stack) {
Expand All @@ -136,7 +154,9 @@ export async function deployStack(
.waitFor('stackCreateComplete', { StackName: params.StackName })
.promise()

return stack.StackId
return {
StackId: stack.StackId
}
}

return await updateStack(
Expand Down
9 changes: 5 additions & 4 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,18 @@ export async function run(): Promise<void> {
params.Parameters = parseParameters(parameterOverrides.trim())
}

const stackId = await deployStack(
const deployStackOutputs = await deployStack(
cfn,
params,
noEmptyChangeSet,
noExecuteChageSet,
noDeleteFailedChangeSet
)
core.setOutput('stack-id', stackId || 'UNKNOWN')
core.setOutput('stack-id', deployStackOutputs.StackId || 'UNKNOWN')
core.setOutput('change-set-id', deployStackOutputs.ChangeSetId || 'UNKNOWN')

if (stackId) {
const outputs = await getStackOutputs(cfn, stackId)
if (deployStackOutputs.StackId) {
const outputs = await getStackOutputs(cfn, deployStackOutputs.StackId)
for (const [key, value] of outputs) {
core.setOutput(key, value)
}
Expand Down