forked from aws/aws-cdk
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapplication.ts
293 lines (255 loc) · 10.2 KB
/
application.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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
import { CfnResourceShare } from '@aws-cdk/aws-ram';
import * as cdk from '@aws-cdk/core';
import { Names } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { StageStackAssociator } from './aspects/stack-associator';
import { IAttributeGroup } from './attribute-group';
import { getPrincipalsforSharing, hashValues, ShareOptions, SharePermission } from './common';
import { isAccountUnresolved } from './private/utils';
import { InputValidator } from './private/validation';
import { CfnApplication, CfnAttributeGroupAssociation, CfnResourceAssociation } from './servicecatalogappregistry.generated';
const APPLICATION_READ_ONLY_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryApplicationReadOnly';
const APPLICATION_ALLOW_ACCESS_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryApplicationAllowAssociation';
/**
* A Service Catalog AppRegistry Application.
*/
export interface IApplication extends cdk.IResource {
/**
* The ARN of the application.
* @attribute
*/
readonly applicationArn: string;
/**
* The ID of the application.
* @attribute
*/
readonly applicationId: string;
/**
* The name of the application.
* @attribute
*/
readonly applicationName?: string;
/**
* Associate this application with an attribute group.
*
* @param attributeGroup AppRegistry attribute group
*/
associateAttributeGroup(attributeGroup: IAttributeGroup): void;
/**
* Associate this application with a CloudFormation stack.
*
* @deprecated Use `associateApplicationWithStack` instead.
* @param stack a CFN stack
*/
associateStack(stack: cdk.Stack): void;
/**
* Associate a Cloudformation statck with the application in the given stack.
*
* @param stack a CFN stack
*/
associateApplicationWithStack(stack: cdk.Stack): void;
/**
* Share this application with other IAM entities, accounts, or OUs.
*
* @param shareOptions The options for the share.
*/
shareApplication(shareOptions: ShareOptions): void;
/**
* Associate this application with all stacks under the construct node.
* NOTE: This method won't automatically register stacks under pipeline stages,
* and requires association of each pipeline stage by calling this method with stage Construct.
*
* @param construct cdk Construct
*/
associateAllStacksInScope(construct: Construct): void;
}
/**
* Properties for a Service Catalog AppRegistry Application
*/
export interface ApplicationProps {
/**
* Enforces a particular physical application name.
*/
readonly applicationName: string;
/**
* Description for application.
* @default - No description provided
*/
readonly description?: string;
}
abstract class ApplicationBase extends cdk.Resource implements IApplication {
public abstract readonly applicationArn: string;
public abstract readonly applicationId: string;
public abstract readonly applicationName?: string;
private readonly associatedAttributeGroups: Set<string> = new Set();
private readonly associatedResources: Set<string> = new Set();
/**
* Associate an attribute group with application
* If the attribute group is already associated, it will ignore duplicate request.
*/
public associateAttributeGroup(attributeGroup: IAttributeGroup): void {
if (!this.associatedAttributeGroups.has(attributeGroup.node.addr)) {
const hashId = this.generateUniqueHash(attributeGroup.node.addr);
new CfnAttributeGroupAssociation(this, `AttributeGroupAssociation${hashId}`, {
application: this.applicationId,
attributeGroup: attributeGroup.attributeGroupId,
});
this.associatedAttributeGroups.add(attributeGroup.node.addr);
}
}
/**
* Associate a stack with the application
* If the resource is already associated, it will ignore duplicate request.
* A stack can only be associated with one application.
*
* @deprecated Use `associateApplicationWithStack` instead.
*/
public associateStack(stack: cdk.Stack): void {
if (!this.associatedResources.has(stack.node.addr)) {
const hashId = this.generateUniqueHash(stack.node.addr);
new CfnResourceAssociation(this, `ResourceAssociation${hashId}`, {
application: this.applicationId,
resource: stack.stackId,
resourceType: 'CFN_STACK',
});
this.associatedResources.add(stack.node.addr);
}
}
/**
* Associate stack with the application in the stack passed as parameter.
*
* A stack can only be associated with one application.
*/
public associateApplicationWithStack(stack: cdk.Stack): void {
if (!this.associatedResources.has(stack.node.addr)) {
new CfnResourceAssociation(stack, 'AppRegistryAssociation', {
application: stack === cdk.Stack.of(this) ? this.applicationId : this.applicationName ?? this.applicationId,
resource: stack.stackId,
resourceType: 'CFN_STACK',
});
this.associatedResources.add(stack.node.addr);
if (stack !== cdk.Stack.of(this) && this.isSameAccount(stack) && !this.isStageScope(stack) && !stack.nested) {
stack.addDependency(cdk.Stack.of(this));
}
}
}
/**
* Share an application with accounts, organizations and OUs, and IAM roles and users.
* The application will become available to end users within those principals.
*
* @param shareOptions The options for the share.
*/
public shareApplication(shareOptions: ShareOptions): void {
const principals = getPrincipalsforSharing(shareOptions);
const shareName = `RAMShare${hashValues(Names.nodeUniqueId(this.node), this.node.children.length.toString())}`;
new CfnResourceShare(this, shareName, {
name: shareName,
allowExternalPrincipals: false,
principals: principals,
resourceArns: [this.applicationArn],
permissionArns: [this.getApplicationSharePermissionARN(shareOptions)],
});
}
/**
* Associate all stacks present in construct's aspect with application.
*
* NOTE: This method won't automatically register stacks under pipeline stages,
* and requires association of each pipeline stage by calling this method with stage Construct.
*
*/
public associateAllStacksInScope(scope: Construct): void {
cdk.Aspects.of(scope).add(new StageStackAssociator(this));
}
/**
* Create a unique id
*/
protected abstract generateUniqueHash(resourceAddress: string): string;
/**
* Get the correct permission ARN based on the SharePermission
*/
private getApplicationSharePermissionARN(shareOptions: ShareOptions): string {
switch (shareOptions.sharePermission) {
case SharePermission.ALLOW_ACCESS:
return APPLICATION_ALLOW_ACCESS_RAM_PERMISSION_ARN;
case SharePermission.READ_ONLY:
return APPLICATION_READ_ONLY_RAM_PERMISSION_ARN;
default:
return shareOptions.sharePermission ?? APPLICATION_READ_ONLY_RAM_PERMISSION_ARN;
}
}
/**
* Checks whether a stack is defined in a Stage or not.
*/
private isStageScope(stack : cdk.Stack): boolean {
return !(stack.node.scope instanceof cdk.App) && (stack.node.scope instanceof cdk.Stage);
}
/**
* Verifies if application and the visited node is deployed in different account.
*/
private isSameAccount(stack: cdk.Stack): boolean {
return isAccountUnresolved(this.env.account, stack.account) || this.env.account === stack.account;
}
}
/**
* A Service Catalog AppRegistry Application.
*/
export class Application extends ApplicationBase {
/**
* Imports an Application construct that represents an external application.
*
* @param scope The parent creating construct (usually `this`).
* @param id The construct's name.
* @param applicationArn the Amazon Resource Name of the existing AppRegistry Application
*/
public static fromApplicationArn(scope: Construct, id: string, applicationArn: string): IApplication {
const arn = cdk.Stack.of(scope).splitArn(applicationArn, cdk.ArnFormat.SLASH_RESOURCE_SLASH_RESOURCE_NAME);
const applicationId = arn.resourceName;
if (!applicationId) {
throw new Error('Missing required Application ID from Application ARN: ' + applicationArn);
}
class Import extends ApplicationBase {
public readonly applicationArn = applicationArn;
public readonly applicationId = applicationId!;
public readonly applicationName = undefined;
protected generateUniqueHash(resourceAddress: string): string {
return hashValues(this.applicationArn, resourceAddress);
}
}
return new Import(scope, id, {
environmentFromArn: applicationArn,
});
}
/**
* Application manager URL for the Application.
* @attribute
*/
public readonly applicationManagerUrl?: cdk.CfnOutput;
public readonly applicationArn: string;
public readonly applicationId: string;
public readonly applicationName?: string;
private readonly nodeAddress: string;
constructor(scope: Construct, id: string, props: ApplicationProps) {
super(scope, id);
this.validateApplicationProps(props);
const application = new CfnApplication(this, 'Resource', {
name: props.applicationName,
description: props.description,
});
this.applicationArn = application.attrArn;
this.applicationId = application.attrId;
this.applicationName = props.applicationName;
this.nodeAddress = cdk.Names.nodeUniqueId(application.node);
this.applicationManagerUrl = new cdk.CfnOutput(this, 'ApplicationManagerUrl', {
value: `https://${this.env.region}.console.aws.amazon.com/systems-manager/appmanager/application/AWS_AppRegistry_Application-${this.applicationName}`,
description: 'Application manager url for the application created.',
});
}
protected generateUniqueHash(resourceAddress: string): string {
return hashValues(this.nodeAddress, resourceAddress);
}
private validateApplicationProps(props: ApplicationProps) {
InputValidator.validateLength(this.node.path, 'application name', 1, 256, props.applicationName);
InputValidator.validateRegex(this.node.path, 'application name', /^[a-zA-Z0-9-_]+$/, props.applicationName);
InputValidator.validateLength(this.node.path, 'application description', 0, 1024, props.description);
}
}