Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(servicecatalogappregistry): Associate an application with attribute group #24378

Merged
merged 11 commits into from
Mar 6, 2023
45 changes: 45 additions & 0 deletions packages/@aws-cdk/aws-servicecatalogappregistry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,41 @@ const associatedApp = new appreg.ApplicationAssociator(app, 'AssociatedApplicati
});
```

If you want to associate an Attribute Group with application created by `ApplicationAssociator`, then use as shown in the example below:

```ts
const app = new App();
const customAttributeGroup = new CustomAppRegistryAttributeGroup(app, 'AppRegistryAttributeGroup');

const associatedApp = new appreg.ApplicationAssociator(app, 'AssociatedApplication', {
applications: [appreg.TargetApplication.createApplicationStack({
applicationName: 'MyAssociatedApplication',
// 'Application containing stacks deployed via CDK.' is the default
applicationDescription: 'Associated Application description',
stackName: 'MyAssociatedApplicationStack',
// AWS Account and Region that are implied by the current CLI configuration is the default
env: { account: '123456789012', region: 'us-east-1' },
})],
});

// Associate application to the attribute group.
customAttributeGroup.attributeGroup.associateApplicationWithAttributeGroup(associatedApp.appRegistryApplication());

class CustomAppRegistryAttributeGroup extends cdk.Stack {
public readonly attributeGroup: appreg.AttributeGroup
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const myAttributeGroup = new appreg.AttributeGroup(this, 'MyFirstAttributeGroup', {
attributeGroupName: 'MyAttributeGroupName',
description: 'Test attribute group',
attributes: {},
});

this.attributeGroup = myAttributeGroup;
}
}
```

If you are using CDK Pipelines to deploy your application, the application stacks will be inside Stages, and
ApplicationAssociator will not be able to find them. Call `associateStage` on each Stage object before adding it to the
Pipeline, as shown in the example below:
Expand Down Expand Up @@ -191,6 +226,16 @@ declare const attributeGroup: appreg.AttributeGroup;
application.associateAttributeGroup(attributeGroup);
```

### Associating an attribute group with application

You can associate an application with an attribute group with `associateApplicationWithAttributeGroup`:

```ts
declare const application: appreg.Application;
declare const attributeGroup: appreg.AttributeGroup;
attributeGroup.associateApplicationWithAttributeGroup(application);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good, although the method name is a little long. Given that the subject is of type AttributeGroup and the object is of type Application, do you really think we need the words "Application" and "AttributeGroup" in the method name?

Why not

attributeGroup.associate(application);
// or
attributeGroup.associateWith(application);
// or
attributeGroup.associateTo(application);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah associateWith makes a lot more sense. Changed the same in the latest version.

```

### Associating application with a Stack

You can associate a stack with an application with the `associateStack()` API:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ 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 { IApplication } from './application';
import { getPrincipalsforSharing, hashValues, ShareOptions, SharePermission } from './common';
import { InputValidator } from './private/validation';
import { CfnAttributeGroup } from './servicecatalogappregistry.generated';
import { CfnAttributeGroup, CfnAttributeGroupAssociation } from './servicecatalogappregistry.generated';

const ATTRIBUTE_GROUP_READ_ONLY_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryAttributeGroupReadOnly';
const ATTRIBUTE_GROUP_ALLOW_ACCESS_RAM_PERMISSION_ARN = 'arn:aws:ram::aws:permission/AWSRAMPermissionServiceCatalogAppRegistryAttributeGroupAllowAssociation';
Expand Down Expand Up @@ -58,6 +59,22 @@ export interface AttributeGroupProps {
abstract class AttributeGroupBase extends cdk.Resource implements IAttributeGroup {
public abstract readonly attributeGroupArn: string;
public abstract readonly attributeGroupId: string;
private readonly associatedApplications: Set<string> = new Set();

/**
* Associate an application with attribute group
* If the attribute group is already associated, it will ignore duplicate request.
*/
public associateApplicationWithAttributeGroup(application: IApplication): void {
if (!this.associatedApplications.has(application.node.addr)) {
const hashId = this.generateUniqueHash(application.node.addr);
new CfnAttributeGroupAssociation(this, `ApplicationAttributeGroupAssociation${hashId}`, {
application: application.applicationId,
attributeGroup: this.attributeGroupId,
});
this.associatedApplications.add(application.node.addr);
}
}

public shareAttributeGroup(shareOptions: ShareOptions): void {
const principals = getPrincipalsforSharing(shareOptions);
Expand Down Expand Up @@ -85,6 +102,11 @@ abstract class AttributeGroupBase extends cdk.Resource implements IAttributeGrou
return shareOptions.sharePermission ?? ATTRIBUTE_GROUP_READ_ONLY_RAM_PERMISSION_ARN;
}
}

/**
* Create a unique hash
*/
protected abstract generateUniqueHash(resourceAddress: string): string;
}

/**
Expand All @@ -109,6 +131,10 @@ export class AttributeGroup extends AttributeGroupBase implements IAttributeGrou
class Import extends AttributeGroupBase {
public readonly attributeGroupArn = attributeGroupArn;
public readonly attributeGroupId = attributeGroupId!;

protected generateUniqueHash(resourceAddress: string): string {
return hashValues(this.attributeGroupArn, resourceAddress);
}
}

return new Import(scope, id, {
Expand All @@ -118,6 +144,7 @@ export class AttributeGroup extends AttributeGroupBase implements IAttributeGrou

public readonly attributeGroupArn: string;
public readonly attributeGroupId: string;
private readonly nodeAddress: string;

constructor(scope: Construct, id: string, props: AttributeGroupProps) {
super(scope, id);
Expand All @@ -132,6 +159,11 @@ export class AttributeGroup extends AttributeGroupBase implements IAttributeGrou

this.attributeGroupArn = attributeGroup.attrArn;
this.attributeGroupId = attributeGroup.attrId;
this.nodeAddress = cdk.Names.nodeUniqueId(attributeGroup.node);
}

protected generateUniqueHash(resourceAddress: string): string {
return hashValues(this.nodeAddress, resourceAddress);
}

private validateAttributeGroupProps(props: AttributeGroupProps) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,38 @@ describe('Scope based Associations with Application within Same Account', () =>
});
});
});

describe('Associate attribute group with Application', () => {
let app: cdk.App;
beforeEach(() => {
app = new cdk.App({
context: {
'@aws-cdk/core:newStyleStackSynthesis': false,
},
});
});

test('Associate Attribute Group with application created by ApplicationAssociator', () => {

const customAttributeGroup = new CustomAppRegistryAttributeGroup(app, 'AppRegistryAttributeGroup');

const appAssociator = new appreg.ApplicationAssociator(app, 'TestApplication', {
applications: [appreg.TargetApplication.createApplicationStack({
applicationName: 'TestAssociatedApplication',
stackName: 'TestAssociatedApplicationStack',
})],
});

customAttributeGroup.attributeGroup.associateApplicationWithAttributeGroup(appAssociator.appRegistryApplication());
Template.fromStack(customAttributeGroup.attributeGroup.stack).resourceCountIs('AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation', 1);
Template.fromStack(customAttributeGroup.attributeGroup.stack).hasResourceProperties('AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation', {
Application: { 'Fn::ImportValue': 'TestAssociatedApplicationStack:ExportsOutputFnGetAttDefaultCdkApplication4573D5A3IdAEBA32E0' },
AttributeGroup: { 'Fn::GetAtt': ['MyFirstAttributeGroupDBC21379', 'Id'] },
});

});
});

describe('Scope based Associations with Application with Cross Region/Account', () => {
let app: cdk.App;
beforeEach(() => {
Expand Down Expand Up @@ -211,3 +243,18 @@ class AppRegistrySampleStack extends cdk.Stack {
super(scope, id, props);
}
}

class CustomAppRegistryAttributeGroup extends cdk.Stack {
public readonly attributeGroup: appreg.AttributeGroup;

constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const myAttributeGroup = new appreg.AttributeGroup(this, 'MyFirstAttributeGroup', {
attributeGroupName: 'MyFirstAttributeGroupName',
description: 'Test attribute group',
attributes: {},
});

this.attributeGroup = myAttributeGroup;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,30 @@ describe('Attribute Group', () => {
});
});

describe('Associate application to an attribute group', () => {
let attributeGroup: appreg.AttributeGroup;

beforeEach(() => {
attributeGroup = new appreg.AttributeGroup(stack, 'MyAttributeGroupForAssociation', {
attributeGroupName: 'MyAttributeGroupForAssociation',
attributes: {},
});
});

test('Associate an application to an attribute group', () => {
const application = new appreg.Application(stack, 'MyApplication', {
applicationName: 'MyTestApplication',
});
attributeGroup.associateApplicationWithAttributeGroup(application);
Template.fromStack(stack).hasResourceProperties('AWS::ServiceCatalogAppRegistry::AttributeGroupAssociation', {
Application: { 'Fn::GetAtt': ['MyApplication5C63EC1D', 'Id'] },
AttributeGroup: { 'Fn::GetAtt': ['MyAttributeGroupForAssociation6B3E1329', 'Id'] },
});

});

});

describe('Resource sharing of an attribute group', () => {
let attributeGroup: appreg.AttributeGroup;

Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"22.0.0"}
{"version":"30.1.0"}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"version": "22.0.0",
"version": "30.1.0",
"files": {
"3dece22dad73361a79cb380f2880362a20ffc5c0cc75ddc6707e26b5a88cf93f": {
"eceea7410a36f9390f3bde280de21a8cf782a5552f62b847978c3106e40e8f7c": {
"source": {
"path": "integ-servicecatalogappregistry-attribute-group.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "3dece22dad73361a79cb380f2880362a20ffc5c0cc75ddc6707e26b5a88cf93f.json",
"objectKey": "eceea7410a36f9390f3bde280de21a8cf782a5552f62b847978c3106e40e8f7c.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
"beta": "time2"
}
},
"Name": "myAttributeGroupTest",
"Description": "my attribute group description"
"Name": "myAttributeGroup",
"Description": "test attribute group description"
}
},
"TestAttributeGroupRAMSharec67f7d80e5baA10EFB4E": {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "22.0.0",
"version": "30.1.0",
"testCases": {
"integ.attribute-group": {
"stacks": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "22.0.0",
"version": "30.1.0",
"artifacts": {
"integ-servicecatalogappregistry-attribute-group.assets": {
"type": "cdk:asset-manifest",
Expand All @@ -17,7 +17,7 @@
"validateOnSynth": false,
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3dece22dad73361a79cb380f2880362a20ffc5c0cc75ddc6707e26b5a88cf93f.json",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/eceea7410a36f9390f3bde280de21a8cf782a5552f62b847978c3106e40e8f7c.json",
"requiresBootstrapStackVersion": 6,
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
"additionalDependencies": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@
"beta": "time2"
}
},
"name": "myAttributeGroupTest",
"description": "my attribute group description"
"name": "myAttributeGroup",
"description": "test attribute group description"
}
},
"constructInfo": {
Expand Down Expand Up @@ -228,7 +228,7 @@
"path": "Tree",
"constructInfo": {
"fqn": "constructs.Construct",
"version": "10.1.189"
"version": "10.1.252"
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ const app = new cdk.App();
const stack = new cdk.Stack(app, 'integ-servicecatalogappregistry-attribute-group');

const attributeGroup = new appreg.AttributeGroup(stack, 'TestAttributeGroup', {
attributeGroupName: 'myAttributeGroupTest',
description: 'my attribute group description',
attributeGroupName: 'myAttributeGroup',
description: 'test attribute group description',
attributes: {
stage: 'alpha',
teamMembers: [
Expand Down