@friggframework/devtools 2.0.0--canary.474.27d9425.0 → 2.0.0--canary.474.97bfcf0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/infrastructure/domains/health/domain/entities/property-mismatch.js +105 -0
- package/infrastructure/domains/health/domain/entities/property-mismatch.test.js +251 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.js +138 -0
- package/infrastructure/domains/health/domain/value-objects/health-score.test.js +267 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.js +161 -0
- package/infrastructure/domains/health/domain/value-objects/property-mutability.test.js +198 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.js +167 -0
- package/infrastructure/domains/health/domain/value-objects/resource-state.test.js +196 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.js +179 -0
- package/infrastructure/domains/health/domain/value-objects/stack-identifier.test.js +233 -0
- package/package.json +6 -6
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResourceState Value Object
|
|
3
|
+
*
|
|
4
|
+
* Enum-like immutable state for CloudFormation resources
|
|
5
|
+
*
|
|
6
|
+
* States:
|
|
7
|
+
* - IN_STACK: Resource exists in CloudFormation stack and matches expected definition
|
|
8
|
+
* - ORPHANED: Resource exists in AWS but not in CloudFormation stack
|
|
9
|
+
* - MISSING: Resource is in CloudFormation stack but not in AWS
|
|
10
|
+
* - DRIFTED: Resource exists in both but properties differ
|
|
11
|
+
* - EXTERNAL: Resource is intentionally external to stack
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
class ResourceState {
|
|
15
|
+
/**
|
|
16
|
+
* Valid resource states
|
|
17
|
+
* @type {string[]}
|
|
18
|
+
*/
|
|
19
|
+
static VALID_STATES = [
|
|
20
|
+
'IN_STACK',
|
|
21
|
+
'ORPHANED',
|
|
22
|
+
'MISSING',
|
|
23
|
+
'DRIFTED',
|
|
24
|
+
'EXTERNAL',
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Create a new ResourceState
|
|
29
|
+
*
|
|
30
|
+
* @param {string} value - State value
|
|
31
|
+
*/
|
|
32
|
+
constructor(value) {
|
|
33
|
+
if (value === undefined || value === null) {
|
|
34
|
+
throw new Error('Resource state is required');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (!ResourceState.VALID_STATES.includes(value)) {
|
|
38
|
+
throw new Error(`Invalid resource state: ${value}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this._value = value;
|
|
42
|
+
|
|
43
|
+
// Make immutable
|
|
44
|
+
Object.freeze(this);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get state value
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
get value() {
|
|
52
|
+
return this._value;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Prevent modification of value
|
|
57
|
+
* @throws {TypeError}
|
|
58
|
+
*/
|
|
59
|
+
set value(newValue) {
|
|
60
|
+
throw new TypeError('Cannot modify immutable property value');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Check if resource is in stack
|
|
65
|
+
* @returns {boolean}
|
|
66
|
+
*/
|
|
67
|
+
isInStack() {
|
|
68
|
+
return this._value === 'IN_STACK';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if resource is orphaned
|
|
73
|
+
* @returns {boolean}
|
|
74
|
+
*/
|
|
75
|
+
isOrphaned() {
|
|
76
|
+
return this._value === 'ORPHANED';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Check if resource is missing
|
|
81
|
+
* @returns {boolean}
|
|
82
|
+
*/
|
|
83
|
+
isMissing() {
|
|
84
|
+
return this._value === 'MISSING';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Check if resource has drifted
|
|
89
|
+
* @returns {boolean}
|
|
90
|
+
*/
|
|
91
|
+
isDrifted() {
|
|
92
|
+
return this._value === 'DRIFTED';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Check if resource is external
|
|
97
|
+
* @returns {boolean}
|
|
98
|
+
*/
|
|
99
|
+
isExternal() {
|
|
100
|
+
return this._value === 'EXTERNAL';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check equality with another ResourceState
|
|
105
|
+
*
|
|
106
|
+
* @param {ResourceState} other
|
|
107
|
+
* @returns {boolean}
|
|
108
|
+
*/
|
|
109
|
+
equals(other) {
|
|
110
|
+
if (!(other instanceof ResourceState)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return this._value === other._value;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Get string representation
|
|
119
|
+
*
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
toString() {
|
|
123
|
+
return this._value;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Predefined state: IN_STACK
|
|
128
|
+
* @type {ResourceState}
|
|
129
|
+
*/
|
|
130
|
+
static get IN_STACK() {
|
|
131
|
+
return new ResourceState('IN_STACK');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Predefined state: ORPHANED
|
|
136
|
+
* @type {ResourceState}
|
|
137
|
+
*/
|
|
138
|
+
static get ORPHANED() {
|
|
139
|
+
return new ResourceState('ORPHANED');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Predefined state: MISSING
|
|
144
|
+
* @type {ResourceState}
|
|
145
|
+
*/
|
|
146
|
+
static get MISSING() {
|
|
147
|
+
return new ResourceState('MISSING');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Predefined state: DRIFTED
|
|
152
|
+
* @type {ResourceState}
|
|
153
|
+
*/
|
|
154
|
+
static get DRIFTED() {
|
|
155
|
+
return new ResourceState('DRIFTED');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Predefined state: EXTERNAL
|
|
160
|
+
* @type {ResourceState}
|
|
161
|
+
*/
|
|
162
|
+
static get EXTERNAL() {
|
|
163
|
+
return new ResourceState('EXTERNAL');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = ResourceState;
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ResourceState Value Object
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const ResourceState = require('./resource-state');
|
|
6
|
+
|
|
7
|
+
describe('ResourceState', () => {
|
|
8
|
+
describe('valid states', () => {
|
|
9
|
+
it('should accept IN_STACK state', () => {
|
|
10
|
+
const state = new ResourceState('IN_STACK');
|
|
11
|
+
|
|
12
|
+
expect(state.value).toBe('IN_STACK');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should accept ORPHANED state', () => {
|
|
16
|
+
const state = new ResourceState('ORPHANED');
|
|
17
|
+
|
|
18
|
+
expect(state.value).toBe('ORPHANED');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should accept MISSING state', () => {
|
|
22
|
+
const state = new ResourceState('MISSING');
|
|
23
|
+
|
|
24
|
+
expect(state.value).toBe('MISSING');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should accept DRIFTED state', () => {
|
|
28
|
+
const state = new ResourceState('DRIFTED');
|
|
29
|
+
|
|
30
|
+
expect(state.value).toBe('DRIFTED');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should accept EXTERNAL state', () => {
|
|
34
|
+
const state = new ResourceState('EXTERNAL');
|
|
35
|
+
|
|
36
|
+
expect(state.value).toBe('EXTERNAL');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('invalid states', () => {
|
|
41
|
+
it('should reject invalid state', () => {
|
|
42
|
+
expect(() => {
|
|
43
|
+
new ResourceState('INVALID');
|
|
44
|
+
}).toThrow('Invalid resource state: INVALID');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should reject lowercase state', () => {
|
|
48
|
+
expect(() => {
|
|
49
|
+
new ResourceState('in_stack');
|
|
50
|
+
}).toThrow('Invalid resource state: in_stack');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should reject null', () => {
|
|
54
|
+
expect(() => {
|
|
55
|
+
new ResourceState(null);
|
|
56
|
+
}).toThrow('Resource state is required');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should reject undefined', () => {
|
|
60
|
+
expect(() => {
|
|
61
|
+
new ResourceState(undefined);
|
|
62
|
+
}).toThrow('Resource state is required');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('state checks', () => {
|
|
67
|
+
it('should check if resource is in stack', () => {
|
|
68
|
+
const state = new ResourceState('IN_STACK');
|
|
69
|
+
|
|
70
|
+
expect(state.isInStack()).toBe(true);
|
|
71
|
+
expect(state.isOrphaned()).toBe(false);
|
|
72
|
+
expect(state.isMissing()).toBe(false);
|
|
73
|
+
expect(state.isDrifted()).toBe(false);
|
|
74
|
+
expect(state.isExternal()).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should check if resource is orphaned', () => {
|
|
78
|
+
const state = new ResourceState('ORPHANED');
|
|
79
|
+
|
|
80
|
+
expect(state.isInStack()).toBe(false);
|
|
81
|
+
expect(state.isOrphaned()).toBe(true);
|
|
82
|
+
expect(state.isMissing()).toBe(false);
|
|
83
|
+
expect(state.isDrifted()).toBe(false);
|
|
84
|
+
expect(state.isExternal()).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should check if resource is missing', () => {
|
|
88
|
+
const state = new ResourceState('MISSING');
|
|
89
|
+
|
|
90
|
+
expect(state.isInStack()).toBe(false);
|
|
91
|
+
expect(state.isOrphaned()).toBe(false);
|
|
92
|
+
expect(state.isMissing()).toBe(true);
|
|
93
|
+
expect(state.isDrifted()).toBe(false);
|
|
94
|
+
expect(state.isExternal()).toBe(false);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('should check if resource is drifted', () => {
|
|
98
|
+
const state = new ResourceState('DRIFTED');
|
|
99
|
+
|
|
100
|
+
expect(state.isInStack()).toBe(false);
|
|
101
|
+
expect(state.isOrphaned()).toBe(false);
|
|
102
|
+
expect(state.isMissing()).toBe(false);
|
|
103
|
+
expect(state.isDrifted()).toBe(true);
|
|
104
|
+
expect(state.isExternal()).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should check if resource is external', () => {
|
|
108
|
+
const state = new ResourceState('EXTERNAL');
|
|
109
|
+
|
|
110
|
+
expect(state.isInStack()).toBe(false);
|
|
111
|
+
expect(state.isOrphaned()).toBe(false);
|
|
112
|
+
expect(state.isMissing()).toBe(false);
|
|
113
|
+
expect(state.isDrifted()).toBe(false);
|
|
114
|
+
expect(state.isExternal()).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe('equality', () => {
|
|
119
|
+
it('should be equal to same state', () => {
|
|
120
|
+
const state1 = new ResourceState('IN_STACK');
|
|
121
|
+
const state2 = new ResourceState('IN_STACK');
|
|
122
|
+
|
|
123
|
+
expect(state1.equals(state2)).toBe(true);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should not be equal to different state', () => {
|
|
127
|
+
const state1 = new ResourceState('IN_STACK');
|
|
128
|
+
const state2 = new ResourceState('ORPHANED');
|
|
129
|
+
|
|
130
|
+
expect(state1.equals(state2)).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('should not be equal to non-ResourceState', () => {
|
|
134
|
+
const state = new ResourceState('IN_STACK');
|
|
135
|
+
|
|
136
|
+
expect(state.equals('IN_STACK')).toBe(false);
|
|
137
|
+
expect(state.equals(null)).toBe(false);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('toString', () => {
|
|
142
|
+
it('should return string representation', () => {
|
|
143
|
+
const state = new ResourceState('IN_STACK');
|
|
144
|
+
|
|
145
|
+
expect(state.toString()).toBe('IN_STACK');
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
describe('static constants', () => {
|
|
150
|
+
it('should provide IN_STACK constant', () => {
|
|
151
|
+
expect(ResourceState.IN_STACK.value).toBe('IN_STACK');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should provide ORPHANED constant', () => {
|
|
155
|
+
expect(ResourceState.ORPHANED.value).toBe('ORPHANED');
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should provide MISSING constant', () => {
|
|
159
|
+
expect(ResourceState.MISSING.value).toBe('MISSING');
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should provide DRIFTED constant', () => {
|
|
163
|
+
expect(ResourceState.DRIFTED.value).toBe('DRIFTED');
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('should provide EXTERNAL constant', () => {
|
|
167
|
+
expect(ResourceState.EXTERNAL.value).toBe('EXTERNAL');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should provide VALID_STATES array', () => {
|
|
171
|
+
expect(ResourceState.VALID_STATES).toEqual([
|
|
172
|
+
'IN_STACK',
|
|
173
|
+
'ORPHANED',
|
|
174
|
+
'MISSING',
|
|
175
|
+
'DRIFTED',
|
|
176
|
+
'EXTERNAL',
|
|
177
|
+
]);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('immutability', () => {
|
|
182
|
+
it('should not allow modification of value', () => {
|
|
183
|
+
const state = new ResourceState('IN_STACK');
|
|
184
|
+
|
|
185
|
+
expect(() => {
|
|
186
|
+
state.value = 'ORPHANED';
|
|
187
|
+
}).toThrow();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('should be frozen', () => {
|
|
191
|
+
const state = new ResourceState('IN_STACK');
|
|
192
|
+
|
|
193
|
+
expect(Object.isFrozen(state)).toBe(true);
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StackIdentifier Value Object
|
|
3
|
+
*
|
|
4
|
+
* Immutable identifier for a CloudFormation stack
|
|
5
|
+
* Combines stack name, region, and optional account ID
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
class StackIdentifier {
|
|
9
|
+
/**
|
|
10
|
+
* Valid AWS regions
|
|
11
|
+
* @private
|
|
12
|
+
*/
|
|
13
|
+
static VALID_REGIONS = [
|
|
14
|
+
'us-east-1',
|
|
15
|
+
'us-east-2',
|
|
16
|
+
'us-west-1',
|
|
17
|
+
'us-west-2',
|
|
18
|
+
'af-south-1',
|
|
19
|
+
'ap-east-1',
|
|
20
|
+
'ap-south-1',
|
|
21
|
+
'ap-northeast-1',
|
|
22
|
+
'ap-northeast-2',
|
|
23
|
+
'ap-northeast-3',
|
|
24
|
+
'ap-southeast-1',
|
|
25
|
+
'ap-southeast-2',
|
|
26
|
+
'ca-central-1',
|
|
27
|
+
'eu-central-1',
|
|
28
|
+
'eu-west-1',
|
|
29
|
+
'eu-west-2',
|
|
30
|
+
'eu-west-3',
|
|
31
|
+
'eu-north-1',
|
|
32
|
+
'eu-south-1',
|
|
33
|
+
'me-south-1',
|
|
34
|
+
'sa-east-1',
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a new StackIdentifier
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} params
|
|
41
|
+
* @param {string} params.stackName - CloudFormation stack name
|
|
42
|
+
* @param {string} params.region - AWS region
|
|
43
|
+
* @param {string} [params.accountId] - AWS account ID (12 digits)
|
|
44
|
+
*/
|
|
45
|
+
constructor({ stackName, region, accountId = null }) {
|
|
46
|
+
// Validate required fields
|
|
47
|
+
if (stackName === undefined || stackName === null) {
|
|
48
|
+
throw new Error('stackName is required');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (region === undefined || region === null) {
|
|
52
|
+
throw new Error('region is required');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Validate formats
|
|
56
|
+
if (typeof stackName === 'string' && stackName.trim() === '') {
|
|
57
|
+
throw new Error('stackName cannot be empty');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!StackIdentifier.VALID_REGIONS.includes(region)) {
|
|
61
|
+
throw new Error('region must be a valid AWS region');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (accountId !== null && !/^\d{12}$/.test(accountId)) {
|
|
65
|
+
throw new Error('accountId must be a 12-digit number');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Assign properties
|
|
69
|
+
this._stackName = stackName;
|
|
70
|
+
this._region = region;
|
|
71
|
+
this._accountId = accountId;
|
|
72
|
+
|
|
73
|
+
// Make immutable
|
|
74
|
+
Object.freeze(this);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get stack name
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
get stackName() {
|
|
82
|
+
return this._stackName;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Prevent modification of stackName
|
|
87
|
+
* @throws {TypeError}
|
|
88
|
+
*/
|
|
89
|
+
set stackName(value) {
|
|
90
|
+
throw new TypeError('Cannot modify immutable property stackName');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get region
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
get region() {
|
|
98
|
+
return this._region;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Prevent modification of region
|
|
103
|
+
* @throws {TypeError}
|
|
104
|
+
*/
|
|
105
|
+
set region(value) {
|
|
106
|
+
throw new TypeError('Cannot modify immutable property region');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get account ID
|
|
111
|
+
* @returns {string|null}
|
|
112
|
+
*/
|
|
113
|
+
get accountId() {
|
|
114
|
+
return this._accountId;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Prevent modification of accountId
|
|
119
|
+
* @throws {TypeError}
|
|
120
|
+
*/
|
|
121
|
+
set accountId(value) {
|
|
122
|
+
throw new TypeError('Cannot modify immutable property accountId');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check equality with another StackIdentifier
|
|
127
|
+
*
|
|
128
|
+
* @param {StackIdentifier} other
|
|
129
|
+
* @returns {boolean}
|
|
130
|
+
*/
|
|
131
|
+
equals(other) {
|
|
132
|
+
if (!(other instanceof StackIdentifier)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
this.stackName === other.stackName &&
|
|
138
|
+
this.region === other.region &&
|
|
139
|
+
this.accountId === other.accountId
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Get string representation
|
|
145
|
+
*
|
|
146
|
+
* @returns {string}
|
|
147
|
+
*/
|
|
148
|
+
toString() {
|
|
149
|
+
if (this.accountId) {
|
|
150
|
+
return `${this.stackName} (${this.region}, ${this.accountId})`;
|
|
151
|
+
}
|
|
152
|
+
return `${this.stackName} (${this.region})`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Create StackIdentifier from ARN
|
|
157
|
+
*
|
|
158
|
+
* @param {string} arn - CloudFormation stack ARN
|
|
159
|
+
* @returns {StackIdentifier}
|
|
160
|
+
*/
|
|
161
|
+
static fromArn(arn) {
|
|
162
|
+
// arn:aws:cloudformation:region:account-id:stack/stack-name/guid
|
|
163
|
+
const match = arn.match(/^arn:aws:cloudformation:([^:]+):(\d{12}):stack\/([^\/]+)/);
|
|
164
|
+
|
|
165
|
+
if (!match) {
|
|
166
|
+
throw new Error('Invalid CloudFormation stack ARN');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const [, region, accountId, stackName] = match;
|
|
170
|
+
|
|
171
|
+
return new StackIdentifier({
|
|
172
|
+
stackName,
|
|
173
|
+
region,
|
|
174
|
+
accountId,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = StackIdentifier;
|