Skip to content

Commit

Permalink
- Added change sets and deploy method to cloudformation
Browse files Browse the repository at this point in the history
  • Loading branch information
etiennenoel committed Nov 26, 2023
1 parent b51d0f3 commit 1939a92
Show file tree
Hide file tree
Showing 4 changed files with 252 additions and 12 deletions.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"jest-extended": "^4.0.2",
"lerna": "^7.4.2",
"ts-jest": "^29.1.1",
"typescript": "^4.9.5"
"typescript": "^5.2.2"
},
"dependencies": {
"@pristine-ts/auth0": "file:packages/auth0",
Expand Down
178 changes: 178 additions & 0 deletions packages/aws/src/clients/cloudformation.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,30 @@ import {
CreateStackCommand,
CreateStackCommandInput,
CreateStackCommandOutput, DeleteStackCommand, DeleteStackCommandInput, DeleteStackCommandOutput,
CreateChangeSetCommand,
CreateChangeSetCommandInput,
CreateChangeSetCommandOutput,
DeleteChangeSetCommand,
DeleteChangeSetCommandInput,
DeleteChangeSetCommandOutput,
DescribeChangeSetCommand,
DescribeChangeSetCommandInput,
DescribeChangeSetCommandOutput,
ExecuteChangeSetCommand,
ExecuteChangeSetCommandInput,
ExecuteChangeSetCommandOutput,
ListChangeSetsCommand,
ListChangeSetsCommandInput,
ListChangeSetsCommandOutput,
DescribeStacksCommand,
DescribeStacksCommandOutput,
StackStatus, ChangeSetStatus,
Parameter,
Capability,
Stack, UpdateStackCommand, UpdateStackCommandInput, UpdateStackCommandOutput
} from "@aws-sdk/client-cloudformation";
import {CloudformationClientInterface} from "../interfaces/cloudformation-client.interface";
import {v4 as uuid} from "uuid";

/**
* The client to use to interact with AWS Cloudformation. It is a wrapper around the CloudformationClient of @aws-sdk/client-cloudformation.
Expand Down Expand Up @@ -151,4 +170,163 @@ export class CloudformationClient implements CloudformationClientInterface {
throw e;
}
}

/**
* Creates a Change Set.
* @param input The input to create a change set.
*/
async createChangeSet(input: CreateChangeSetCommandInput): Promise<CreateChangeSetCommandOutput> {
this.logHandler.debug("CLOUDFORMATION CLIENT - Create Change Set", {input}, AwsModuleKeyname);
const command = new CreateChangeSetCommand(input)
try {
return await this.getClient().send(command);
} catch (e) {
this.logHandler.error("Error creating change set in cloudformation", {error: e}, AwsModuleKeyname);
throw e;
}
}

/**
* Deletes a Change Set.
* @param input The input to delete a change set.
*/
async deleteChangeSet(input: DeleteChangeSetCommandInput): Promise<DeleteChangeSetCommandOutput> {
this.logHandler.debug("CLOUDFORMATION CLIENT - delete Change Set", {input}, AwsModuleKeyname);
const command = new DeleteChangeSetCommand(input)
try {
return await this.getClient().send(command);
} catch (e) {
this.logHandler.error("Error deleting change set in cloudformation", {error: e}, AwsModuleKeyname);
throw e;
}
}

/**
* Describes a Change Set.
* @param input The input to describe a change set.
*/
async describeChangeSet(input: DescribeChangeSetCommandInput): Promise<DescribeChangeSetCommandOutput> {
this.logHandler.debug("CLOUDFORMATION CLIENT - Describe Change Set", {input}, AwsModuleKeyname);
const command = new DescribeChangeSetCommand(input)
try {
return await this.getClient().send(command);
} catch (e) {
this.logHandler.error("Error describing change set in cloudformation", {error: e}, AwsModuleKeyname);
throw e;
}
}

/**
* Executes a Change Set.
* @param input The input to execute a change set.
*/
async executeChangeSet(input: ExecuteChangeSetCommandInput): Promise<ExecuteChangeSetCommandOutput> {
this.logHandler.debug("CLOUDFORMATION CLIENT - Execute Change Set", {input}, AwsModuleKeyname);
const command = new ExecuteChangeSetCommand(input)
try {
return await this.getClient().send(command);
} catch (e) {
this.logHandler.error("Error executing change set in cloudformation", {error: e}, AwsModuleKeyname);
throw e;
}
}

/**
* Lists a Change Set.
* @param input The input to list a change set.
*/
async listChangeSets(input: ListChangeSetsCommandInput): Promise<ListChangeSetsCommandOutput> {
this.logHandler.debug("CLOUDFORMATION CLIENT - List Change Sets", {input}, AwsModuleKeyname);
const command = new ListChangeSetsCommand(input)
try {
return await this.getClient().send(command);
} catch (e) {
this.logHandler.error("Error listing change sets in cloudformation", {error: e}, AwsModuleKeyname);
throw e;
}
}

/**
* This method encapsulates the deployment of a Stack, whether the Stack already exists or not. It uses ChangeSets to do so.
* It monitors the status of the stack and returns the status when the status is a final state.
* @param stackName
* @param cloudformationTemplateS3Url
* @param stackParameters
* @param capabilities
* @param statusCallback
*/
async deployStack(stackName: string, cloudformationTemplateS3Url: string, stackParameters: {[key in string]:string}, capabilities: Capability[], statusCallback?: (status: ChangeSetStatus, changeSetName: string) => void): Promise<ChangeSetStatus | "NO_CHANGES_TO_PERFORM"> {
const parameters: Parameter[] = [];

for(const key in stackParameters) {
if(stackParameters.hasOwnProperty(key) === false) {
continue;
}
parameters.push({
ParameterKey: key,
ParameterValue: stackParameters[key],
});
}

const changeSetName = uuid();

await this.createChangeSet(
{
StackName: stackName,
TemplateURL: cloudformationTemplateS3Url,
Parameters: parameters,
Capabilities: capabilities,
ChangeSetName: changeSetName,
}
);

// Check if there are actual changes in the ChangeSet.
const describeChangeSetCommandOutput = await this.describeChangeSet({
StackName: stackName,
ChangeSetName: changeSetName,
})

if(describeChangeSetCommandOutput?.Changes?.length === 0) {
return "NO_CHANGES_TO_PERFORM";
}

// Execute the changes and start monitoring
await this.executeChangeSet({
StackName: stackName,
ChangeSetName: changeSetName,
})

while(true) {
const response = await this.describeChangeSet({
StackName: stackName,
ChangeSetName: changeSetName,
});

const status = response.Status;

if(status === undefined) {
await new Promise(resolve => setTimeout(resolve, 5000));
continue;
}

switch (response.Status) {
case "CREATE_IN_PROGRESS":
case "CREATE_PENDING":
case "DELETE_IN_PROGRESS":
case "DELETE_PENDING":
if(statusCallback) {
statusCallback(status, changeSetName);
}
continue;

case "CREATE_COMPLETE":
case "DELETE_COMPLETE":
case "DELETE_FAILED":
case "FAILED":
return response.Status;
}

await new Promise(resolve => setTimeout(resolve, 5000));
}
}
}
68 changes: 65 additions & 3 deletions packages/aws/src/interfaces/cloudformation-client.interface.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
import { S3PresignedOperationTypeEnum } from "../enums/s3-presigned-operation-type.enum";
import {S3PresignedOperationTypeEnum} from "../enums/s3-presigned-operation-type.enum";
import {GetObjectCommandOutput, S3Client as AWSS3Client, S3ClientConfig} from "@aws-sdk/client-s3";
import {CloudFormationClient as AWSCloudformationClient} from "@aws-sdk/client-cloudformation/dist-types/CloudFormationClient";
import {
CloudFormationClient as AWSCloudformationClient
} from "@aws-sdk/client-cloudformation/dist-types/CloudFormationClient";
import {
CreateStackCommandInput,
CreateStackCommandOutput,
DeleteStackCommandInput, DeleteStackCommandOutput,
Stack, UpdateStackCommandInput, UpdateStackCommandOutput
Stack, UpdateStackCommandInput, UpdateStackCommandOutput,
CreateChangeSetCommand,
CreateChangeSetCommandInput,
CreateChangeSetCommandOutput,
DeleteChangeSetCommand,
DeleteChangeSetCommandInput,
DeleteChangeSetCommandOutput,
DescribeChangeSetCommand,
DescribeChangeSetCommandInput,
DescribeChangeSetCommandOutput,
ExecuteChangeSetCommand,
ExecuteChangeSetCommandInput,
ExecuteChangeSetCommandOutput,
ListChangeSetsCommand,
ListChangeSetsCommandInput,
ListChangeSetsCommandOutput,
DescribeStacksCommand,
DescribeStacksCommandOutput,
StackStatus, Capability, ChangeSetStatus,
} from "@aws-sdk/client-cloudformation";
import {CloudFormationClientConfig} from "@aws-sdk/client-cloudformation/dist-types/ts3.4";
import {AwsModuleKeyname} from "../aws.module.keyname";

/**
* The CloudformationClient Interface defines the methods that a Cloudformation client must implement.
Expand Down Expand Up @@ -53,4 +74,45 @@ export interface CloudformationClientInterface {
* @param input The input to delete the new stack.
*/
deleteStack(input: DeleteStackCommandInput): Promise<DeleteStackCommandOutput>;

/**
* Creates a Change Set.
* @param input The input to create a change set.
*/
createChangeSet(input: CreateChangeSetCommandInput): Promise<CreateChangeSetCommandOutput>;

/**
* Deletes a Change Set.
* @param input The input to delete a change set.
*/
deleteChangeSet(input: DeleteChangeSetCommandInput): Promise<DeleteChangeSetCommandOutput>;

/**
* Describes a Change Set.
* @param input The input to describe a change set.
*/
describeChangeSet(input: DescribeChangeSetCommandInput): Promise<DescribeChangeSetCommandOutput>;

/**
* Executes a Change Set.
* @param input The input to execute a change set.
*/
executeChangeSet(input: ExecuteChangeSetCommandInput): Promise<ExecuteChangeSetCommandOutput>;

/**
* Lists a Change Set.
* @param input The input to list a change set.
*/
listChangeSets(input: ListChangeSetsCommandInput): Promise<ListChangeSetsCommandOutput>;

/**
* This method encapsulates the deployment of a Stack, whether the Stack already exists or not. It uses ChangeSets to do so.
* It monitors the status of the stack and returns the status when the status is a final state.
* @param stackName
* @param cloudformationTemplateS3Url
* @param stackParameters
* @param capabilities
* @param statusCallback
*/
deployStack(stackName: string, cloudformationTemplateS3Url: string, stackParameters: {[key in string]:string}, capabilities: Capability[], statusCallback?: (status: ChangeSetStatus, changeSetName: string) => void): Promise<ChangeSetStatus | "NO_CHANGES_TO_PERFORM">;
}

0 comments on commit 1939a92

Please sign in to comment.