1
+ import type { Stack , Tag } from '@aws-sdk/client-cloudformation' ;
2
+ import { formatErrorMessage , deserializeStructure } from '../../util' ;
3
+ import type { ICloudFormationClient } from '../aws-auth' ;
4
+ import { StackStatus } from '../stack-events' ;
5
+ import { ToolkitError } from '../toolkit-error' ;
6
+
1
7
export interface Template {
2
8
Parameters ?: Record < string , TemplateParameter > ;
3
9
[ section : string ] : any ;
@@ -9,3 +15,175 @@ export interface TemplateParameter {
9
15
Description ?: string ;
10
16
[ key : string ] : any ;
11
17
}
18
+
19
+ /**
20
+ * Represents an (existing) Stack in CloudFormation
21
+ *
22
+ * Bundle and cache some information that we need during deployment (so we don't have to make
23
+ * repeated calls to CloudFormation).
24
+ */
25
+ export class CloudFormationStack {
26
+ public static async lookup (
27
+ cfn : ICloudFormationClient ,
28
+ stackName : string ,
29
+ retrieveProcessedTemplate : boolean = false ,
30
+ ) : Promise < CloudFormationStack > {
31
+ try {
32
+ const response = await cfn . describeStacks ( { StackName : stackName } ) ;
33
+ return new CloudFormationStack ( cfn , stackName , response . Stacks && response . Stacks [ 0 ] , retrieveProcessedTemplate ) ;
34
+ } catch ( e : any ) {
35
+ if ( e . name === 'ValidationError' && formatErrorMessage ( e ) === `Stack with id ${ stackName } does not exist` ) {
36
+ return new CloudFormationStack ( cfn , stackName , undefined ) ;
37
+ }
38
+ throw e ;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Return a copy of the given stack that does not exist
44
+ *
45
+ * It's a little silly that it needs arguments to do that, but there we go.
46
+ */
47
+ public static doesNotExist ( cfn : ICloudFormationClient , stackName : string ) {
48
+ return new CloudFormationStack ( cfn , stackName ) ;
49
+ }
50
+
51
+ /**
52
+ * From static information (for testing)
53
+ */
54
+ public static fromStaticInformation ( cfn : ICloudFormationClient , stackName : string , stack : Stack ) {
55
+ return new CloudFormationStack ( cfn , stackName , stack ) ;
56
+ }
57
+
58
+ private _template : any ;
59
+
60
+ protected constructor (
61
+ private readonly cfn : ICloudFormationClient ,
62
+ public readonly stackName : string ,
63
+ private readonly stack ?: Stack ,
64
+ private readonly retrieveProcessedTemplate : boolean = false ,
65
+ ) {
66
+ }
67
+
68
+ /**
69
+ * Retrieve the stack's deployed template
70
+ *
71
+ * Cached, so will only be retrieved once. Will return an empty
72
+ * structure if the stack does not exist.
73
+ */
74
+ public async template ( ) : Promise < Template > {
75
+ if ( ! this . exists ) {
76
+ return { } ;
77
+ }
78
+
79
+ if ( this . _template === undefined ) {
80
+ const response = await this . cfn . getTemplate ( {
81
+ StackName : this . stackName ,
82
+ TemplateStage : this . retrieveProcessedTemplate ? 'Processed' : 'Original' ,
83
+ } ) ;
84
+ this . _template = ( response . TemplateBody && deserializeStructure ( response . TemplateBody ) ) || { } ;
85
+ }
86
+ return this . _template ;
87
+ }
88
+
89
+ /**
90
+ * Whether the stack exists
91
+ */
92
+ public get exists ( ) {
93
+ return this . stack !== undefined ;
94
+ }
95
+
96
+ /**
97
+ * The stack's ID
98
+ *
99
+ * Throws if the stack doesn't exist.
100
+ */
101
+ public get stackId ( ) {
102
+ this . assertExists ( ) ;
103
+ return this . stack ! . StackId ! ;
104
+ }
105
+
106
+ /**
107
+ * The stack's current outputs
108
+ *
109
+ * Empty object if the stack doesn't exist
110
+ */
111
+ public get outputs ( ) : Record < string , string > {
112
+ if ( ! this . exists ) {
113
+ return { } ;
114
+ }
115
+ const result : { [ name : string ] : string } = { } ;
116
+ ( this . stack ! . Outputs || [ ] ) . forEach ( ( output ) => {
117
+ result [ output . OutputKey ! ] = output . OutputValue ! ;
118
+ } ) ;
119
+ return result ;
120
+ }
121
+
122
+ /**
123
+ * The stack's status
124
+ *
125
+ * Special status NOT_FOUND if the stack does not exist.
126
+ */
127
+ public get stackStatus ( ) : StackStatus {
128
+ if ( ! this . exists ) {
129
+ return new StackStatus ( 'NOT_FOUND' , 'Stack not found during lookup' ) ;
130
+ }
131
+ return StackStatus . fromStackDescription ( this . stack ! ) ;
132
+ }
133
+
134
+ /**
135
+ * The stack's current tags
136
+ *
137
+ * Empty list if the stack does not exist
138
+ */
139
+ public get tags ( ) : Tag [ ] {
140
+ return this . stack ?. Tags || [ ] ;
141
+ }
142
+
143
+ /**
144
+ * SNS Topic ARNs that will receive stack events.
145
+ *
146
+ * Empty list if the stack does not exist
147
+ */
148
+ public get notificationArns ( ) : string [ ] {
149
+ return this . stack ?. NotificationARNs ?? [ ] ;
150
+ }
151
+
152
+ /**
153
+ * Return the names of all current parameters to the stack
154
+ *
155
+ * Empty list if the stack does not exist.
156
+ */
157
+ public get parameterNames ( ) : string [ ] {
158
+ return Object . keys ( this . parameters ) ;
159
+ }
160
+
161
+ /**
162
+ * Return the names and values of all current parameters to the stack
163
+ *
164
+ * Empty object if the stack does not exist.
165
+ */
166
+ public get parameters ( ) : Record < string , string > {
167
+ if ( ! this . exists ) {
168
+ return { } ;
169
+ }
170
+ const ret : Record < string , string > = { } ;
171
+ for ( const param of this . stack ! . Parameters ?? [ ] ) {
172
+ ret [ param . ParameterKey ! ] = param . ResolvedValue ?? param . ParameterValue ! ;
173
+ }
174
+ return ret ;
175
+ }
176
+
177
+ /**
178
+ * Return the termination protection of the stack
179
+ */
180
+ public get terminationProtection ( ) : boolean | undefined {
181
+ return this . stack ?. EnableTerminationProtection ;
182
+ }
183
+
184
+ private assertExists ( ) {
185
+ if ( ! this . exists ) {
186
+ throw new ToolkitError ( `No stack named '${ this . stackName } '` ) ;
187
+ }
188
+ }
189
+ }
0 commit comments