Created
February 16, 2021 15:56
-
-
Save stoyan-scava/46cfc265cea52ba2b82553731f0f4bb9 to your computer and use it in GitHub Desktop.
Stack Manager - Wrapper of AWS CloudFormation calls. To be used in CI scripts for resource look-up.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {CloudFormation} from "aws-sdk"; | |
import { | |
ListStackResourcesOutput, | |
ListStacksOutput, | |
Stack, | |
StackResourceSummary, | |
StackStatus, | |
StackSummary | |
} from "aws-sdk/clients/cloudformation"; | |
/** | |
* The minimal configuration required to deploy a stack | |
*/ | |
export interface StackEnvironment { | |
account: string, | |
region: string, | |
stage: string | |
} | |
/** | |
* Manage Cloudformation Stacks and their resources. | |
* Wrapper of CloudFormation calls | |
*/ | |
export class StackManager { | |
/** | |
* Stacks with these statuses actually exist | |
* @private | |
*/ | |
private readonly existingStackStatuses = [ | |
"CREATE_IN_PROGRESS", | |
"CREATE_COMPLETE", | |
"ROLLBACK_IN_PROGRESS", | |
"ROLLBACK_FAILED", | |
"ROLLBACK_COMPLETE", | |
"DELETE_IN_PROGRESS", | |
"DELETE_FAILED", | |
"UPDATE_IN_PROGRESS", | |
"UPDATE_COMPLETE_CLEANUP_IN_PROGRESS", | |
"UPDATE_COMPLETE", | |
"UPDATE_ROLLBACK_IN_PROGRESS", | |
"UPDATE_ROLLBACK_FAILED", | |
"UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS", | |
"UPDATE_ROLLBACK_COMPLETE", | |
"REVIEW_IN_PROGRESS", | |
"IMPORT_IN_PROGRESS", | |
"IMPORT_COMPLETE", | |
"IMPORT_ROLLBACK_IN_PROGRESS", | |
"IMPORT_ROLLBACK_FAILED", | |
"IMPORT_ROLLBACK_COMPLETE" | |
] | |
// Stacks with these statuses does not exist | |
// private readonly nonExistingStackStatuses = [ | |
// "CREATE_FAILED", | |
// "DELETE_COMPLETE" | |
// ] | |
/** | |
* Resources with these statuses actually exist | |
* @private | |
*/ | |
private readonly existingResourceStatuses = [ | |
"CREATE_IN_PROGRESS", | |
"CREATE_COMPLETE", | |
"DELETE_IN_PROGRESS", | |
"DELETE_FAILED", | |
"DELETE_SKIPPED", | |
"UPDATE_IN_PROGRESS", | |
"UPDATE_FAILED", | |
"UPDATE_COMPLETE", | |
"IMPORT_FAILED", | |
"IMPORT_COMPLETE", | |
"IMPORT_IN_PROGRESS", | |
"IMPORT_ROLLBACK_IN_PROGRESS", | |
"IMPORT_ROLLBACK_FAILED", | |
"IMPORT_ROLLBACK_COMPLETE" | |
] | |
/** | |
* Resources with these statuses does not exist | |
* @private | |
*/ | |
private readonly nonExistingResourceStatuses = [ | |
"CREATE_FAILED", | |
"DELETE_COMPLETE" | |
] | |
constructor() { | |
} | |
async listStacksInEnvironment(env: StackEnvironment, statusFilter?: StackStatus[]): Promise<StackSummary[]> { | |
const cf = new CloudFormation({ | |
region: env.region | |
}) | |
const StackStatusFilter = statusFilter ?? this.existingStackStatuses; | |
let stackSummaries: StackSummary[] = []; | |
let listStacksOutput: ListStacksOutput; | |
let NextToken: string | undefined = undefined; | |
do { | |
listStacksOutput = await cf.listStacks({NextToken, StackStatusFilter}).promise(); | |
if (listStacksOutput.StackSummaries !== undefined) stackSummaries = [ | |
...stackSummaries, | |
...listStacksOutput.StackSummaries | |
]; | |
NextToken = listStacksOutput.NextToken | |
} while (NextToken !== undefined) | |
// return stackSummaries | |
return stackSummaries.filter((summary: StackSummary) => { | |
return (summary.StackId) ? this.stackIsInEnv(summary.StackId, env) : false | |
}) | |
} | |
stackIsInEnv(stackId: string, env: StackEnvironment): boolean { | |
return ( | |
(this.getRegionFromArn(stackId) === env.region) && | |
(this.getAccountIdFromArn(stackId) === env.account) && | |
(this.getStackNameFromArn(stackId).startsWith(env.stage)) | |
) | |
} | |
// WIP | |
// async getStackByNameAndEnvironment(stackName: string, env: StackEnvironment): Promise<Stack> { | |
// const cf = new CloudFormation({region: env.region}); | |
// const describeStacksOutput: DescribeStacksOutput = await cf.describeStacks({ | |
// StackName: stackName, | |
// NextToken: undefined | |
// }).promise() | |
// describeStacksOutput.Stacks?. | |
// } | |
getPartitionFromArn(arn: string): string { | |
return arn.split(":")[1] | |
} | |
getServiceFromArn(arn: string): string { | |
return arn.split(":")[2] | |
} | |
getRegionFromArn(arn: string): string { | |
return arn.split(":")[3] | |
} | |
getAccountIdFromArn(arn: string): string { | |
return arn.split(":")[4] | |
} | |
getResourceIdFromArn(arn: string): string { | |
return arn.split(":")[5] | |
} | |
getStackNameFromArn(arn: string): string { | |
const resourceId = this.getResourceIdFromArn(arn); | |
return resourceId.split("/")[1] | |
} | |
async listResourcesInStack(stackId: string): Promise<StackResourceSummary[]> { | |
const region = this.getRegionFromArn(stackId); | |
const cf = new CloudFormation({region}) | |
const StackName = this.getStackNameFromArn(stackId) | |
let stackResourceSummaries: StackResourceSummary[] = []; | |
let listStackResourcesOutput: ListStackResourcesOutput; | |
let NextToken: string | undefined = undefined; | |
do { | |
listStackResourcesOutput = await cf.listStackResources({NextToken, StackName}).promise(); | |
if (listStackResourcesOutput.StackResourceSummaries !== undefined) stackResourceSummaries = [ | |
...stackResourceSummaries, | |
...listStackResourcesOutput.StackResourceSummaries | |
]; | |
console.log(`${listStackResourcesOutput.StackResourceSummaries?.length} new resources`); | |
console.log(`${stackResourceSummaries.length} total resources`) | |
console.log(`NextToken: ${!!listStackResourcesOutput.NextToken}`); | |
NextToken = listStackResourcesOutput.NextToken | |
} while (NextToken !== undefined) | |
// Filter out resources that failed to be created or are deleted already | |
return stackResourceSummaries.filter((resource: StackResourceSummary) => { | |
// Note: another possible check is if the resource has PhysicalResourceId (if not then the resource does not exist) | |
return (!this.nonExistingResourceStatuses.includes(resource.ResourceStatus)) | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment