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(appconfig-alpha): extensions always create cdk diff #28264

Merged
merged 6 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-appconfig-alpha/lib/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,7 @@ export class Application extends ApplicationBase {
resourceName: this.applicationId,
});

this.extensible = new ExtensibleBase(scope, this.applicationArn, this.name);
this.extensible = new ExtensibleBase(this, this.applicationArn, this.name);
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/@aws-cdk/aws-appconfig-alpha/lib/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ export class HostedConfiguration extends ConfigurationBase {
resource: 'application',
resourceName: `${this.applicationId}/configurationprofile/${this.configurationProfileId}`,
});
this.extensible = new ExtensibleBase(scope, this.configurationProfileArn, this.name);
this.extensible = new ExtensibleBase(this, this.configurationProfileArn, this.name);

this.content = props.content.content;
this.contentType = props.content.contentType;
Expand Down Expand Up @@ -617,7 +617,7 @@ export class SourcedConfiguration extends ConfigurationBase {
resource: 'application',
resourceName: `${this.applicationId}/configurationprofile/${this.configurationProfileId}`,
});
this.extensible = new ExtensibleBase(scope, this.configurationProfileArn, this.name);
this.extensible = new ExtensibleBase(this, this.configurationProfileArn, this.name);

this.addExistingEnvironmentsToApplication();
this.deployConfigToEnvironments();
Expand Down
2 changes: 1 addition & 1 deletion packages/@aws-cdk/aws-appconfig-alpha/lib/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export class Environment extends EnvironmentBase {
resource: 'application',
resourceName: `${this.applicationId}/environment/${this.environmentId}`,
});
this.extensible = new ExtensibleBase(scope, this.environmentArn, this.name);
this.extensible = new ExtensibleBase(this, this.environmentArn, this.name);

this.application.addExistingEnvironment(this);
}
Expand Down
59 changes: 37 additions & 22 deletions packages/@aws-cdk/aws-appconfig-alpha/lib/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ export class LambdaDestination implements IEventDestination {
this.policyDocument = new iam.PolicyDocument({
statements: [policy],
});

if (!func.permissionsNode.tryFindChild('AppConfigPermission')) {
func.addPermission('AppConfigPermission', {
principal: new iam.ServicePrincipal('appconfig.amazonaws.com'),
});
}
}
}

Expand Down Expand Up @@ -500,21 +506,19 @@ export class Extension extends Resource implements IExtension {
this.parameters = props.parameters;

const resource = new CfnExtension(this, 'Resource', {
actions: this.actions.reduce((acc: {[key: string]: {[key: string]: string}[]}, cur: Action) => {
actions: this.actions.reduce((acc: {[key: string]: {[key: string]: string}[]}, cur: Action, index: number) => {
const extensionUri = cur.eventDestination.extensionUri;
const sourceType = cur.eventDestination.type;
this.executionRole = cur.executionRole;
const name = cur.name ?? `${this.name}-${index}`;
cur.actionPoints.forEach((actionPoint) => {
acc[actionPoint] = [
{
Name: cur.name || Names.uniqueResourceName(this, {
maxLength: 64,
separator: '-',
}),
Name: name,
Uri: extensionUri,
...(sourceType === SourceType.EVENTS || cur.invokeWithoutExecutionRole
? {}
: { RoleArn: this.executionRole?.roleArn || this.getExecutionRole(cur.eventDestination).roleArn }),
: { RoleArn: this.executionRole?.roleArn || this.getExecutionRole(cur.eventDestination, name).roleArn }),
...(cur.description ? { Description: cur.description } : {}),
},
];
Expand Down Expand Up @@ -543,8 +547,10 @@ export class Extension extends Resource implements IExtension {
});
}

private getExecutionRole(eventDestination: IEventDestination): iam.IRole {
this.executionRole = new iam.Role(this, `Role${getHash(eventDestination.extensionUri)}`, {
private getExecutionRole(eventDestination: IEventDestination, actionName: string): iam.IRole {
const versionNumber = this.latestVersionNumber ? this.latestVersionNumber + 1 : 1;
const combinedObjects = stringifyObjects(this.name, versionNumber, actionName);
this.executionRole = new iam.Role(this, `Role${getHash(combinedObjects)}`, {
roleName: PhysicalName.GENERATE_IF_NEEDED,
assumedBy: new iam.ServicePrincipal('appconfig.amazonaws.com'),
inlinePolicies: {
Expand Down Expand Up @@ -652,13 +658,13 @@ export class ExtensibleBase implements IExtensible {
}

public addExtension(extension: IExtension) {
this.addExtensionAssociation(extension, {
parameters: extension.parameters,
});
this.addExtensionAssociation(extension);
}

private getExtensionForActionPoint(eventDestination: IEventDestination, actionPoint: ActionPoint, options?: ExtensionOptions) {
const extension = new Extension(this.scope, `Extension${this.getExtensionHash(eventDestination, actionPoint, options)}`, {
const name = options?.name || this.getExtensionDefaultName();
const versionNumber = options?.latestVersionNumber ? options?.latestVersionNumber + 1 : 1;
const extension = new Extension(this.scope, `Extension${this.getExtensionHash(name, versionNumber)}`, {
actions: [
new Action({
eventDestination,
Expand All @@ -667,20 +673,22 @@ export class ExtensibleBase implements IExtensible {
],
}),
],
name,
...(options?.description ? { description: options.description } : {}),
...(options?.latestVersionNumber ? { latestVersionNumber: options.latestVersionNumber } : {}),
...(options?.name ? { name: options.name } : {}),
...(options?.parameters ? { parameters: options.parameters } : {}),
});
this.addExtensionAssociation(extension, options);
this.addExtensionAssociation(extension);
}

private addExtensionAssociation(extension: IExtension, options?: ExtensionOptions) {
new CfnExtensionAssociation(this.scope, `AssociationResource${this.getExtensionAssociationHash(extension)}`, {
private addExtensionAssociation(extension: IExtension) {
const versionNumber = extension?.latestVersionNumber ? extension?.latestVersionNumber + 1 : 1;
const name = extension.name ?? this.getExtensionDefaultName();
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const name = extension.name ?? this.getExtensionDefaultName();
const name = extension.name ?? options?.name ?? this.getExtensionDefaultName();

name can be passed via options as well.
I think that getExtensionDefaultName should be used here as well for consistency.

To avoid this default to chaining after initialization we could think about making this and this required and use getExtensionDefaultName for imported extensions.
This may be a better approach, not sure how much refactoring it would require.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We actually don't need the options parameter here, I just got rid of it.

For the second point, the scope in these two places are different. In the link you attached, the user would pass in the logical id which might be something like MyExtension so here adding the -Extension there would be redundant. Here the scope is the parent so adding the -Extension prevents it from being named the same as the parent. For requiring the name as a parameter, this goes against the CDK guidelines as we want to try to take as much of the work off the user. This was additionally a customer pain point when working with the L1 constructs.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok clear, thanks for the follow-up and the parameter cleanup 👍

new CfnExtensionAssociation(this.scope, `AssociationResource${this.getExtensionAssociationHash(name, versionNumber)}`, {
extensionIdentifier: extension.extensionId,
resourceIdentifier: this.resourceArn,
extensionVersionNumber: extension.extensionVersionNumber,
parameters: options?.parameters?.reduce((acc: {[key: string]: string}, cur: Parameter) => {
parameters: extension.parameters?.reduce((acc: {[key: string]: string}, cur: Parameter) => {
if (cur.value) {
acc[cur.name] = cur.value;
}
Expand All @@ -689,16 +697,23 @@ export class ExtensibleBase implements IExtensible {
});
}

private getExtensionHash(eventDestination: IEventDestination, actionPoint: ActionPoint, options?: ExtensionOptions) {
const combinedString = stringifyObjects(eventDestination, actionPoint, options);
private getExtensionHash(name: string, versionNumber: number) {
const combinedString = stringifyObjects(name, versionNumber);
return getHash(combinedString);
}

private getExtensionAssociationHash(extension: IExtension) {
const resourceIdentifier = this.resourceName ? this.resourceName : this.resourceArn;
const combinedString = stringifyObjects(resourceIdentifier, extension.name, extension.extensionVersionNumber);
private getExtensionAssociationHash(name: string, versionNumber: number) {
const resourceIdentifier = this.resourceName ?? this.resourceArn;
const combinedString = stringifyObjects(resourceIdentifier, name, versionNumber);
return getHash(combinedString);
}

private getExtensionDefaultName() {
return Names.uniqueResourceName(this.scope, {
maxLength: 54,
separator: '-',
}) + '-Extension';
}
}

/**
Expand Down
Loading