Skip to content

Commit

Permalink
(fix) closes #14, delete change set on no updates
Browse files Browse the repository at this point in the history
  • Loading branch information
katallaxie committed May 28, 2020
1 parent 94102bf commit 37713ba
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 29 deletions.
121 changes: 115 additions & 6 deletions __tests__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,10 +467,6 @@ describe('Deploy CloudFormation Stack', () => {
TemplateURL: undefined,
TimeoutInMinutes: undefined
});
expect(mockDescribeChangeSet).toHaveBeenNthCalledWith(1, {
ChangeSetName: 'MockStack-CS',
StackName: 'MockStack'
});
expect(mockExecuteChangeSet).toHaveBeenNthCalledWith(1, {
ChangeSetName: 'MockStack-CS',
StackName: 'MockStack'
Expand Down Expand Up @@ -527,6 +523,14 @@ describe('Deploy CloudFormation Stack', () => {
};
});

mockCfnWaiter.mockImplementation(() => {
return {
promise(): Promise<{}> {
return Promise.reject({});
}
};
});

await run();

expect(core.setFailed).toHaveBeenCalledTimes(1);
Expand Down Expand Up @@ -577,11 +581,11 @@ describe('Deploy CloudFormation Stack', () => {
'arn:aws:cloudformation:us-east-1:123456789012:stack/myteststack/466df9e0-0dff-08e3-8e2f-5088487c4896',
Tags: [],
Outputs: [],
StackStatusReason: '',
StackStatusReason: `The submitted information didn't contain changes`,
CreationTime: new Date('2013-08-23T01:02:15.422Z'),
Capabilities: [],
StackName: 'MockStack',
StackStatus: 'CREATE_COMPLETE',
StackStatus: 'FAILED',
DisableRollback: false
}
]
Expand All @@ -590,6 +594,14 @@ describe('Deploy CloudFormation Stack', () => {
};
});

mockCfnWaiter.mockImplementation(() => {
return {
promise(): Promise<{}> {
return Promise.reject({});
}
};
});

mockDescribeChangeSet.mockImplementation(() => {
return {
promise(): Promise<aws.CloudFormation.Types.CreateChangeSetOutput> {
Expand Down Expand Up @@ -644,6 +656,103 @@ describe('Deploy CloudFormation Stack', () => {
expect(mockExecuteChangeSet).toHaveBeenCalledTimes(0);
});

test('no error if updating fails with no updates to be performed', async () => {
const inputs: Inputs = {
name: 'MockStack',
template: 'template.yaml',
capabilities: 'CAPABILITY_IAM',
'parameter-overrides': 'AdminEmail=no-reply@amazon.com',
'no-fail-on-empty-changeset': '1'
};

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

mockDescribeStacks.mockImplementation(() => {
return {
promise(): Promise<aws.CloudFormation.Types.DescribeStacksOutput> {
return Promise.resolve({
Stacks: [
{
StackId:
'arn:aws:cloudformation:us-east-1:123456789012:stack/myteststack/466df9e0-0dff-08e3-8e2f-5088487c4896',
Tags: [],
Outputs: [],
StackStatusReason: '',
CreationTime: new Date('2013-08-23T01:02:15.422Z'),
Capabilities: [],
StackName: 'MockStack',
StackStatus: 'UPDATE_COMPLETE',
DisableRollback: false
}
]
});
}
};
});

mockCfnWaiter.mockImplementation(() => {
return {
promise(): Promise<{}> {
return Promise.reject({});
}
};
});

mockDescribeChangeSet.mockImplementation(() => {
return {
promise(): Promise<aws.CloudFormation.Types.CreateChangeSetOutput> {
return Promise.resolve({
Changes: [],
ChangeSetName: 'MockStack-CS',
ChangeSetId:
'arn:aws:cloudformation:us-west-2:123456789012:changeSet/my-change-set/4eca1a01-e285-xmpl-8026-9a1967bfb4b0',
StackId: mockStackId,
StackName: 'MockStack',
Description: null,
Parameters: null,
CreationTime: '2019-10-02T05:20:56.651Z',
ExecutionStatus: 'AVAILABLE',
Status: 'FAILED',
StatusReason: 'No updates are to be performed',
NotificationARNs: [],
RollbackConfiguration: {},
Capabilities: ['CAPABILITY_IAM'],
Tags: null
});
}
};
});

await run();

expect(core.setFailed).toHaveBeenCalledTimes(0);
expect(core.setOutput).toHaveBeenCalledTimes(1);
expect(mockDescribeStacks).toHaveBeenCalledTimes(1);
expect(mockCreateChangeSet).toHaveBeenNthCalledWith(1, {
StackName: 'MockStack',
TemplateBody: mockTemplate,
Capabilities: ['CAPABILITY_IAM'],
Parameters: [
{ ParameterKey: 'AdminEmail', ParameterValue: 'no-reply@amazon.com' }
],
ChangeSetName: 'MockStack-CS',
NotificationARNs: undefined,
ResourceTypes: undefined,
RollbackConfiguration: undefined,
RoleARN: undefined,
Tags: undefined,
TemplateURL: undefined,
TimeoutInMinutes: undefined
});
expect(mockDeleteChangeSet).toHaveBeenNthCalledWith(1, {
ChangeSetName: 'MockStack-CS',
StackName: 'MockStack'
});
expect(mockExecuteChangeSet).toHaveBeenCalledTimes(0);
});

test('error is caught by core.setFailed', async () => {
mockCreateStack.mockImplementation(() => {
throw new Error();
Expand Down
37 changes: 26 additions & 11 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17297,11 +17297,12 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(__webpack_require__(6470));
function updateStack(cfn, stack, params, noEmptyChangeSet) {
var _a;
function cleanupChangeSet(cfn, stack, params, noEmptyChangeSet) {
return __awaiter(this, void 0, void 0, function* () {
core.debug('Creating CloudFormation Change Set');
yield cfn.createChangeSet(params).promise();
const knownErrorMessages = [
`No updates are to be performed`,
`The submitted information didn't contain changes`
];
const changeSetStatus = yield cfn
.describeChangeSet({
ChangeSetName: params.ChangeSetName,
Expand All @@ -17316,18 +17317,32 @@ function updateStack(cfn, stack, params, noEmptyChangeSet) {
StackName: params.StackName
})
.promise();
if (noEmptyChangeSet && ((_a = changeSetStatus.StatusReason) === null || _a === void 0 ? void 0 : _a.includes("The submitted information didn't contain changes"))) {
if (noEmptyChangeSet &&
knownErrorMessages.some(err => { var _a; return (_a = changeSetStatus.StatusReason) === null || _a === void 0 ? void 0 : _a.includes(err); })) {
return stack.StackId;
}
throw new Error(`Failed to create Change Set: ${changeSetStatus.StatusReason}`);
}
});
}
exports.cleanupChangeSet = cleanupChangeSet;
function updateStack(cfn, stack, params, noEmptyChangeSet) {
return __awaiter(this, void 0, void 0, function* () {
core.debug('Creating CloudFormation Change Set');
yield cfn.createChangeSet(params).promise();
try {
core.debug('Waiting for CloudFormation Change Set creation');
yield cfn
.waitFor('changeSetCreateComplete', {
ChangeSetName: params.ChangeSetName,
StackName: params.StackName
})
.promise();
}
catch (_) {
return cleanupChangeSet(cfn, stack, params, noEmptyChangeSet);
}
core.debug('Executing CloudFormation Change Set');
yield cfn
.waitFor('changeSetCreateComplete', {
ChangeSetName: params.ChangeSetName,
StackName: params.StackName
})
.promise();
yield cfn
.executeChangeSet({
ChangeSetName: params.ChangeSetName,
Expand Down
41 changes: 29 additions & 12 deletions src/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,17 @@ import { CreateChangeSetInput, CreateStackInput } from './main';

export type Stack = aws.CloudFormation.Stack;

export async function updateStack(
export async function cleanupChangeSet(
cfn: aws.CloudFormation,
stack: Stack,
params: CreateChangeSetInput,
noEmptyChangeSet: boolean
): Promise<string | undefined> {
core.debug('Creating CloudFormation Change Set');
const knownErrorMessages = [
`No updates are to be performed`,
`The submitted information didn't contain changes`
];

await cfn.createChangeSet(params).promise();
const changeSetStatus = await cfn
.describeChangeSet({
ChangeSetName: params.ChangeSetName,
Expand All @@ -32,8 +34,8 @@ export async function updateStack(

if (
noEmptyChangeSet &&
changeSetStatus.StatusReason?.includes(
"The submitted information didn't contain changes"
knownErrorMessages.some(err =>
changeSetStatus.StatusReason?.includes(err)
)
) {
return stack.StackId;
Expand All @@ -43,15 +45,30 @@ export async function updateStack(
`Failed to create Change Set: ${changeSetStatus.StatusReason}`
);
}
}

core.debug('Executing CloudFormation Change Set');
await cfn
.waitFor('changeSetCreateComplete', {
ChangeSetName: params.ChangeSetName,
StackName: params.StackName
})
.promise();
export async function updateStack(
cfn: aws.CloudFormation,
stack: Stack,
params: CreateChangeSetInput,
noEmptyChangeSet: boolean
): Promise<string | undefined> {
core.debug('Creating CloudFormation Change Set');
await cfn.createChangeSet(params).promise();

try {
core.debug('Waiting for CloudFormation Change Set creation');
await cfn
.waitFor('changeSetCreateComplete', {
ChangeSetName: params.ChangeSetName,
StackName: params.StackName
})
.promise();
} catch (_) {
return cleanupChangeSet(cfn, stack, params, noEmptyChangeSet);
}

core.debug('Executing CloudFormation Change Set');
await cfn
.executeChangeSet({
ChangeSetName: params.ChangeSetName,
Expand Down

0 comments on commit 37713ba

Please sign in to comment.