forked from aws/aws-cdk
/
stack-associator.ts
131 lines (115 loc) · 4.52 KB
/
stack-associator.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import { IAspect, Stack, Stage, Annotations } from '@aws-cdk/core';
import { IConstruct } from 'constructs';
import { IApplication } from '../application';
import { ApplicationAssociator } from '../application-associator';
import { SharePermission } from '../common';
import { isRegionUnresolved, isAccountUnresolved } from '../private/utils';
/**
* Aspect class, this will visit each node from the provided construct once.
*
* For every stack node visited, this class will be responsible to associate
* the stack to the application.
*/
abstract class StackAssociatorBase implements IAspect {
protected abstract readonly application: IApplication;
protected abstract readonly applicationAssociator?: ApplicationAssociator;
protected readonly sharedAccounts: Set<string> = new Set();
public visit(node: IConstruct): void {
// verify if a stage in a particular stack is associated to Application.
node.node.children.forEach((childNode) => {
if (Stage.isStage(childNode)) {
var stageAssociated = this.applicationAssociator?.isStageAssociated(childNode);
if (stageAssociated === false) {
this.error(childNode, 'Associate Stage: ' + childNode.stageName + ' to ensure all stacks in your cdk app are associated with AppRegistry. '
+ 'You can use ApplicationAssociator.associateStage to associate any stage.');
}
}
});
if (Stack.isStack(node)) {
this.handleCrossRegionStack(node);
this.handleCrossAccountStack(node);
this.associate(node);
}
}
/**
* Associate a stage stack to the given application.
*
* @param node A Stage stack.
*/
private associate(node: Stack): void {
this.application.associateApplicationWithStack(node);
}
/**
* Adds an error annotation to a node.
*
* @param node The scope to add the error to.
* @param message The error message.
*/
private error(node: IConstruct, message: string): void {
Annotations.of(node).addError(message);
}
/**
* Adds a warning annotation to a node.
*
* @param node The scope to add the warning to.
* @param message The error message.
*/
private warning(node: IConstruct, message: string): void {
Annotations.of(node).addWarning(message);
}
/**
* Handle cross-region association. AppRegistry do not support
* cross region association at this moment,
* If any stack is evaluated as cross-region than that of application,
* we will throw an error.
*
* @param node Cfn stack.
*/
private handleCrossRegionStack(node: Stack): void {
if (isRegionUnresolved(this.application.env.region, node.region)) {
this.warning(node, 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.');
return;
}
if (node.region != this.application.env.region) {
this.warning(node, 'AppRegistry does not support cross region associations, deployment might fail if there is cross region stacks in the app. Application region '
+ this.application.env.region + ', stack region ' + node.region);
}
}
/**
* Handle cross-account association.
* If any stack is evaluated as cross-account than that of application,
* then we will share the application to the stack owning account.
*
* @param node Cfn stack.
*/
private handleCrossAccountStack(node: Stack): void {
if (isAccountUnresolved(this.application.env.account!, node.account)) {
this.warning(node, 'Environment agnostic stack determined, AppRegistry association might not work as expected in case you deploy cross-region or cross-account stack.');
return;
}
if (node.account != this.application.env.account && !this.sharedAccounts.has(node.account)) {
this.application.shareApplication({
accounts: [node.account],
sharePermission: SharePermission.ALLOW_ACCESS,
});
this.sharedAccounts.add(node.account);
}
}
}
export class CheckedStageStackAssociator extends StackAssociatorBase {
protected readonly application: IApplication;
protected readonly applicationAssociator?: ApplicationAssociator;
constructor(app: ApplicationAssociator) {
super();
this.application = app.appRegistryApplication();
this.applicationAssociator = app;
}
}
export class StageStackAssociator extends StackAssociatorBase {
protected readonly application: IApplication;
protected readonly applicationAssociator?: ApplicationAssociator;
constructor(app: IApplication) {
super();
this.application = app;
}
}