Skip to content

Commit 7baffa2

Browse files
feat(servicecatalogappregistry): add attribute groups to an application (#24672)
To associate a attribute group to an application created in `ApplicationAssociator`, customers have to use `AttributeGroup` construct separately to create and associate the attribute group separately. This makes the `AttributeGroup` and `AttributeGroupAssociation` created in another stack than `ApplicationAssociator` stack. This commits provides an one-stop action, i.e. `Application.addAttributeGroup()`, to create and associate attribute group on `Application` Construct. This solution not only makes attribute group creation and association easier for customer who uses `Application` construct, but also lets customer to have attribute groups and attribute group associations for the `ApplicationAssociator` applications in the same stack. `Application.addAttributeGroup()` has `id` in the parameters, for two reasons: - consistent with the experience where customer can define logical ID when using `new AttributeGroup()` - complexity of deciding logical ID from the attribute group input: - We have to make sure update attributes/description won't trigger create and then delete but update, which will cause name conflict exception. - We also don't want to generate logical ID from attribute group name only, as if two `Application.addAttributeGroup()` method calls with the same name will result in construct ID conflict. This exposes implementation details and makes it hard to customers to debug and resolve. BREAKING CHANGE: This commit contains destructive changes to the RAM Share. Since the application RAM share name is calculated by the application construct, where one method is added. Integration test detects a breaking change where RAM share will be created. Integration test snapshot is updated to cater this destructive change. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 7de5b00 commit 7baffa2

14 files changed

+254
-46
lines changed

packages/@aws-cdk/aws-servicecatalogappregistry/README.md

+5-17
Original file line numberDiff line numberDiff line change
@@ -126,22 +126,6 @@ import * as cdk from "@aws-cdk/core";
126126

127127
const app = new App();
128128

129-
class CustomAppRegistryAttributeGroup extends cdk.Stack {
130-
public readonly attributeGroup: appreg.AttributeGroup
131-
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
132-
super(scope, id, props);
133-
const myAttributeGroup = new appreg.AttributeGroup(app, 'MyFirstAttributeGroup', {
134-
attributeGroupName: 'MyAttributeGroupName',
135-
description: 'Test attribute group',
136-
attributes: {},
137-
});
138-
139-
this.attributeGroup = myAttributeGroup;
140-
}
141-
}
142-
143-
const customAttributeGroup = new CustomAppRegistryAttributeGroup(app, 'AppRegistryAttributeGroup');
144-
145129
const associatedApp = new appreg.ApplicationAssociator(app, 'AssociatedApplication', {
146130
applications: [appreg.TargetApplication.createApplicationStack({
147131
applicationName: 'MyAssociatedApplication',
@@ -154,7 +138,11 @@ const associatedApp = new appreg.ApplicationAssociator(app, 'AssociatedApplicati
154138
});
155139

156140
// Associate application to the attribute group.
157-
customAttributeGroup.attributeGroup.associateWith(associatedApp.appRegistryApplication());
141+
associatedApp.appRegistryApplication.addAttributeGroup('MyAttributeGroup' , {
142+
attributeGroupName: 'MyAttributeGroupName',
143+
description: 'Test attribute group',
144+
attributes: {},
145+
});
158146

159147
```
160148

packages/@aws-cdk/aws-servicecatalogappregistry/lib/application-associator.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export class ApplicationAssociator extends Construct {
7777
* Get the AppRegistry application.
7878
*
7979
*/
80-
public appRegistryApplication(): IApplication {
80+
public get appRegistryApplication(): IApplication {
8181
return this.application;
8282
}
8383
}

packages/@aws-cdk/aws-servicecatalogappregistry/lib/application.ts

+49-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import * as cdk from '@aws-cdk/core';
33
import { Names } from '@aws-cdk/core';
44
import { Construct } from 'constructs';
55
import { StageStackAssociator } from './aspects/stack-associator';
6-
import { IAttributeGroup } from './attribute-group';
6+
import { AttributeGroup, IAttributeGroup } from './attribute-group';
77
import { getPrincipalsforSharing, hashValues, ShareOptions, SharePermission } from './common';
88
import { isAccountUnresolved } from './private/utils';
99
import { InputValidator } from './private/validation';
@@ -12,6 +12,29 @@ import { CfnApplication, CfnAttributeGroupAssociation, CfnResourceAssociation }
1212
const APPLICATION_READ_ONLY_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryApplicationReadOnly';
1313
const APPLICATION_ALLOW_ACCESS_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryApplicationAllowAssociation';
1414

15+
/**
16+
* Properties for a Service Catalog AppRegistry Attribute Group
17+
*/
18+
export interface AttributeGroupAssociationProps {
19+
/**
20+
* Name for attribute group.
21+
*
22+
*/
23+
readonly attributeGroupName: string;
24+
25+
/**
26+
* Description for attribute group.
27+
* @default - No description provided
28+
*/
29+
readonly description?: string;
30+
31+
/**
32+
* A JSON of nested key-value pairs that represent the attributes in the group.
33+
* Attributes maybe an empty JSON '{}', but must be explicitly stated.
34+
*/
35+
readonly attributes: { [key: string]: any };
36+
}
37+
1538
/**
1639
* A Service Catalog AppRegistry Application.
1740
*/
@@ -41,6 +64,14 @@ export interface IApplication extends cdk.IResource {
4164
*/
4265
associateAttributeGroup(attributeGroup: IAttributeGroup): void;
4366

67+
/**
68+
* Create an attribute group and associate this application with the created attribute group.
69+
*
70+
* @param id name of the AttributeGroup construct to be created.
71+
* @param attributeGroupProps AppRegistry attribute group props
72+
*/
73+
addAttributeGroup(id: string, attributeGroupProps: AttributeGroupAssociationProps): IAttributeGroup;
74+
4475
/**
4576
* Associate this application with a CloudFormation stack.
4677
*
@@ -114,6 +145,23 @@ abstract class ApplicationBase extends cdk.Resource implements IApplication {
114145
}
115146
}
116147

148+
/**
149+
* Create an attribute group and associate this application with the created attribute group.
150+
*/
151+
public addAttributeGroup(id: string, props: AttributeGroupAssociationProps): IAttributeGroup {
152+
const attributeGroup = new AttributeGroup(this, id, {
153+
attributeGroupName: props.attributeGroupName,
154+
attributes: props.attributes,
155+
description: props.description,
156+
});
157+
new CfnAttributeGroupAssociation(this, `AttributeGroupAssociation${this.generateUniqueHash(attributeGroup.node.addr)}`, {
158+
application: this.applicationId,
159+
attributeGroup: attributeGroup.attributeGroupId,
160+
});
161+
this.associatedAttributeGroups.add(attributeGroup.node.addr);
162+
return attributeGroup;
163+
}
164+
117165
/**
118166
* Associate a stack with the application
119167
* If the resource is already associated, it will ignore duplicate request.

packages/@aws-cdk/aws-servicecatalogappregistry/lib/aspects/stack-associator.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ export class CheckedStageStackAssociator extends StackAssociatorBase {
132132

133133
constructor(app: ApplicationAssociator, props?: StackAssociatorBaseProps) {
134134
super(props);
135-
this.application = app.appRegistryApplication();
135+
this.application = app.appRegistryApplication;
136136
this.applicationAssociator = app;
137137
}
138138
}

packages/@aws-cdk/aws-servicecatalogappregistry/lib/target-application.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ export interface CreateTargetApplicationOptions extends TargetApplicationCommonO
4545
/**
4646
* Whether create cloudFormation Output for application manager URL.
4747
*
48-
* @default - Application containing stacks deployed via CDK.
48+
* @default - true
4949
*/
5050
readonly emitApplicationManagerUrlAsOutput?: boolean;
5151
}

packages/@aws-cdk/aws-servicecatalogappregistry/test/application-associator.test.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ describe('Scope based Associations with Application within Same Account', () =>
2323
});
2424

2525
const anotherStack = new AppRegistrySampleStack(app, 'SampleStack');
26-
Template.fromStack(appAssociator.appRegistryApplication().stack).resourceCountIs('AWS::ServiceCatalogAppRegistry::Application', 1);
27-
Template.fromStack(appAssociator.appRegistryApplication().stack).hasResourceProperties('AWS::ServiceCatalogAppRegistry::Application', {
26+
Template.fromStack(appAssociator.appRegistryApplication.stack).resourceCountIs('AWS::ServiceCatalogAppRegistry::Application', 1);
27+
Template.fromStack(appAssociator.appRegistryApplication.stack).hasResourceProperties('AWS::ServiceCatalogAppRegistry::Application', {
2828
Name: 'MyAssociatedApplication',
2929
Tags: { managedBy: 'CDK_Application_Associator' },
3030
});
31-
Template.fromStack(appAssociator.appRegistryApplication().stack).hasOutput('DefaultCdkApplicationApplicationManagerUrl27C138EF', {});
31+
Template.fromStack(appAssociator.appRegistryApplication.stack).hasOutput('DefaultCdkApplicationApplicationManagerUrl27C138EF', {});
3232
Template.fromStack(anotherStack).resourceCountIs('AWS::ServiceCatalogAppRegistry::ResourceAssociation', 1);
3333
Template.fromStack(anotherStack).hasResourceProperties('AWS::ServiceCatalogAppRegistry::ResourceAssociation', {
3434
Application: 'MyAssociatedApplication',
@@ -46,14 +46,14 @@ describe('Scope based Associations with Application within Same Account', () =>
4646
});
4747

4848
const anotherStack = new AppRegistrySampleStack(app, 'SampleStack');
49-
Template.fromStack(appAssociator.appRegistryApplication().stack).resourceCountIs('AWS::ServiceCatalogAppRegistry::Application', 1);
50-
Template.fromStack(appAssociator.appRegistryApplication().stack).hasResourceProperties('AWS::ServiceCatalogAppRegistry::Application', {
49+
Template.fromStack(appAssociator.appRegistryApplication.stack).resourceCountIs('AWS::ServiceCatalogAppRegistry::Application', 1);
50+
Template.fromStack(appAssociator.appRegistryApplication.stack).hasResourceProperties('AWS::ServiceCatalogAppRegistry::Application', {
5151
Name: 'MyAssociatedApplication',
5252
Tags: { managedBy: 'CDK_Application_Associator' },
5353
});
5454

5555
expect(
56-
Template.fromStack(appAssociator.appRegistryApplication().stack)
56+
Template.fromStack(appAssociator.appRegistryApplication.stack)
5757
.findOutputs('*', {}),
5858
).toEqual({});
5959
Template.fromStack(anotherStack).resourceCountIs('AWS::ServiceCatalogAppRegistry::ResourceAssociation', 1);
@@ -85,7 +85,7 @@ describe('Associate attribute group with Application', () => {
8585
})],
8686
});
8787

88-
customAttributeGroup.attributeGroup.associateWith(appAssociator.appRegistryApplication());
88+
customAttributeGroup.attributeGroup.associateWith(appAssociator.appRegistryApplication);
8989
Template.fromStack(customAttributeGroup.attributeGroup.stack).resourceCountIs('AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation', 1);
9090
Template.fromStack(customAttributeGroup.attributeGroup.stack).hasResourceProperties('AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation', {
9191
Application: 'TestAssociatedApplication',
@@ -137,7 +137,7 @@ describe('Scope based Associations with Application with Cross Region/Account',
137137
});
138138

139139
expect(
140-
Template.fromStack(appAssociator.appRegistryApplication().stack).findOutputs('*', {}),
140+
Template.fromStack(appAssociator.appRegistryApplication.stack).findOutputs('*', {}),
141141
).toEqual({});
142142
Template.fromStack(firstStack).resourceCountIs('AWS::ServiceCatalogAppRegistry::ResourceAssociation', 1);
143143
Template.fromStack(nestedStack).resourceCountIs('AWS::ServiceCatalogAppRegistry::ResourceAssociation', 1);
@@ -268,7 +268,7 @@ describe('Scope based Associations with Application with Cross Region/Account',
268268
associateStage: true,
269269
});
270270
app.synth();
271-
Template.fromStack(application.appRegistryApplication().stack).hasOutput('DefaultCdkApplicationApplicationManagerUrl27C138EF', {});
271+
Template.fromStack(application.appRegistryApplication.stack).hasOutput('DefaultCdkApplicationApplicationManagerUrl27C138EF', {});
272272
Template.fromStack(pipelineStack).resourceCountIs('AWS::ServiceCatalogAppRegistry::ResourceAssociation', 1);
273273
});
274274
});

packages/@aws-cdk/aws-servicecatalogappregistry/test/application.test.ts

+25
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,31 @@ describe('Application', () => {
160160
});
161161
}),
162162

163+
test('associate new attribute group', () => {
164+
application.addAttributeGroup('AttributeGroup', {
165+
attributeGroupName: 'AttributeGroupName',
166+
attributes: {},
167+
description: 'Description for Attribute Group',
168+
});
169+
170+
Template.fromStack(stack).hasResourceProperties('AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation', {
171+
Application: { 'Fn::GetAtt': ['MyApplication5C63EC1D', 'Id'] },
172+
AttributeGroup: { 'Fn::GetAtt': ['MyApplicationAttributeGroup0BD166B6', 'Id'] },
173+
});
174+
175+
Template.fromStack(stack).templateMatches({
176+
Resources: {
177+
MyApplicationAttributeGroup0BD166B6: {
178+
Type: 'AWS::ServiceCatalogAppRegistry::AttributeGroup',
179+
Properties: {
180+
Name: 'AttributeGroupName',
181+
Attributes: {},
182+
},
183+
},
184+
},
185+
});
186+
}),
187+
163188
test('duplicate attribute group association are idempotent', () => {
164189
const attributeGroup = new appreg.AttributeGroup(stack, 'AttributeGroup', {
165190
attributeGroupName: 'attributeGroupName',
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"version":"30.1.0"}
1+
{"version":"31.0.0"}

packages/@aws-cdk/aws-servicecatalogappregistry/test/integ.application.js.snapshot/integ-servicecatalogappregistry-application.assets.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"version": "30.1.0",
2+
"version": "31.0.0",
33
"files": {
4-
"2332c6df6777cc571585060fa4888d6d3b9ef548aa00dcbfc53fbdde386d7591": {
4+
"5fbf2a286122f4bc412b1730f96351e289444b1122006f36e4ade8fae8442765": {
55
"source": {
66
"path": "integ-servicecatalogappregistry-application.template.json",
77
"packaging": "file"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "2332c6df6777cc571585060fa4888d6d3b9ef548aa00dcbfc53fbdde386d7591.json",
12+
"objectKey": "5fbf2a286122f4bc412b1730f96351e289444b1122006f36e4ade8fae8442765.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}

packages/@aws-cdk/aws-servicecatalogappregistry/test/integ.application.js.snapshot/integ-servicecatalogappregistry-application.template.json

+42-2
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,50 @@
3939
}
4040
}
4141
},
42-
"TestApplicationRAMSharead8ba81b8cdd40199FD1": {
42+
"TestApplicationmyAnotherAttributeGroup375F79DB": {
43+
"Type": "AWS::ServiceCatalogAppRegistry::AttributeGroup",
44+
"Properties": {
45+
"Attributes": {
46+
"stage": "alpha",
47+
"teamMembers": [
48+
"markI",
49+
"markII",
50+
"markIII"
51+
],
52+
"public": false,
53+
"publishYear": 2021,
54+
"plannedRoadMap": {
55+
"alpha": "some time",
56+
"beta": "another time",
57+
"gamma": "penultimate time",
58+
"release": "go time"
59+
}
60+
},
61+
"Name": "myAnotherAttributeGroup",
62+
"Description": "my another attribute group description"
63+
}
64+
},
65+
"TestApplicationAttributeGroupAssociationb6f47e836a8c4FCAC29E": {
66+
"Type": "AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation",
67+
"Properties": {
68+
"Application": {
69+
"Fn::GetAtt": [
70+
"TestApplication2FBC585F",
71+
"Id"
72+
]
73+
},
74+
"AttributeGroup": {
75+
"Fn::GetAtt": [
76+
"TestApplicationmyAnotherAttributeGroup375F79DB",
77+
"Id"
78+
]
79+
}
80+
}
81+
},
82+
"TestApplicationRAMShare004736f08f8e57044D5D": {
4383
"Type": "AWS::RAM::ResourceShare",
4484
"Properties": {
45-
"Name": "RAMSharead8ba81b8cdd",
85+
"Name": "RAMShare004736f08f8e",
4686
"AllowExternalPrincipals": false,
4787
"PermissionArns": [
4888
"arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryApplicationReadOnly"

packages/@aws-cdk/aws-servicecatalogappregistry/test/integ.application.js.snapshot/integ.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "30.1.0",
2+
"version": "31.0.0",
33
"testCases": {
44
"integ.application": {
55
"stacks": [

packages/@aws-cdk/aws-servicecatalogappregistry/test/integ.application.js.snapshot/manifest.json

+25-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "30.1.0",
2+
"version": "31.0.0",
33
"artifacts": {
44
"integ-servicecatalogappregistry-application.assets": {
55
"type": "cdk:asset-manifest",
@@ -17,7 +17,7 @@
1717
"validateOnSynth": false,
1818
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
1919
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
20-
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/2332c6df6777cc571585060fa4888d6d3b9ef548aa00dcbfc53fbdde386d7591.json",
20+
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/5fbf2a286122f4bc412b1730f96351e289444b1122006f36e4ade8fae8442765.json",
2121
"requiresBootstrapStackVersion": 6,
2222
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
2323
"additionalDependencies": [
@@ -51,10 +51,22 @@
5151
"data": "TestApplicationAttributeGroupAssociation4ba7f5842818B8EE1C6F"
5252
}
5353
],
54-
"/integ-servicecatalogappregistry-application/TestApplication/RAMSharead8ba81b8cdd": [
54+
"/integ-servicecatalogappregistry-application/TestApplication/myAnotherAttributeGroup/Resource": [
5555
{
5656
"type": "aws:cdk:logicalId",
57-
"data": "TestApplicationRAMSharead8ba81b8cdd40199FD1"
57+
"data": "TestApplicationmyAnotherAttributeGroup375F79DB"
58+
}
59+
],
60+
"/integ-servicecatalogappregistry-application/TestApplication/AttributeGroupAssociationb6f47e836a8c": [
61+
{
62+
"type": "aws:cdk:logicalId",
63+
"data": "TestApplicationAttributeGroupAssociationb6f47e836a8c4FCAC29E"
64+
}
65+
],
66+
"/integ-servicecatalogappregistry-application/TestApplication/RAMShare004736f08f8e": [
67+
{
68+
"type": "aws:cdk:logicalId",
69+
"data": "TestApplicationRAMShare004736f08f8e57044D5D"
5870
}
5971
],
6072
"/integ-servicecatalogappregistry-application/TestAttributeGroup/Resource": [
@@ -80,6 +92,15 @@
8092
"type": "aws:cdk:logicalId",
8193
"data": "CheckBootstrapVersion"
8294
}
95+
],
96+
"TestApplicationRAMSharead8ba81b8cdd40199FD1": [
97+
{
98+
"type": "aws:cdk:logicalId",
99+
"data": "TestApplicationRAMSharead8ba81b8cdd40199FD1",
100+
"trace": [
101+
"!!DESTRUCTIVE_CHANGES: WILL_DESTROY"
102+
]
103+
}
83104
]
84105
},
85106
"displayName": "integ-servicecatalogappregistry-application"

0 commit comments

Comments
 (0)