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

(feat): introduce custom runtime aspect class for nodejs #29998

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as cdk from 'aws-cdk-lib';
import { IntegTest } from '@aws-cdk/integ-tests-alpha';
import { Construct } from 'constructs/lib/construct';
import { AttributeType, Table } from 'aws-cdk-lib/aws-dynamodb';
import * as customAspect from 'aws-cdk-lib/core/lib/nodejs-aspect';
/**
* This test creates a stack and changes termination protection with the setter.
*/

//Dynamo DB
class NodejsAspectTest extends cdk.Stack {

constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
new Table(this, 'Table', {
partitionKey: { name: 'hashKey', type: AttributeType.STRING },
removalPolicy: cdk.RemovalPolicy.DESTROY,
replicationRegions: ['us-east-2'],
});
}
}
const app = new cdk.App();
const stack = new NodejsAspectTest(app, 'NodejsStack', { terminationProtection: false });
customAspect.Runtime.of(stack).add('nodejs18.x');
new IntegTest(app, 'stack', { testCases: [stack] });
4 changes: 4 additions & 0 deletions packages/aws-cdk-lib/core/lib/cfn-resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ export class CfnResource extends CfnRefElement {
return x !== null && typeof(x) === 'object' && x.cfnResourceType !== undefined;
}

public getResourceProperty(key: string): any {
return this._cfnProperties[key];
}

// MAINTAINERS NOTE: this class serves as the base class for the generated L1
// ("CFN") resources (such as `s3.CfnBucket`). These resources will have a
// property for each CloudFormation property of the resource. This means that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,19 @@ export abstract class CustomResourceProviderBase extends Construct {
return this._codeHash;
}

/**
* Base runtime
*/
public get runtime(): string {
if (!this._runtime) {
throw new Error('Runtime not provided');
}
return this._runtime;
}
private _codeHash?: string;
private policyStatements?: any[];
private role?: CfnResource;

private _runtime?: string;
/**
* The ARN of the provider's AWS Lambda function which should be used as the `serviceToken` when defining a custom
* resource.
Expand Down Expand Up @@ -88,6 +97,7 @@ export abstract class CustomResourceProviderBase extends Construct {
// need to initialize this attribute, but there should never be an instance
// where config.enabled=true && config.preventSynthesis=true
this.roleArn = '';
this._runtime = props.runtimeName;
if (config.enabled) {
// gives policyStatements a chance to resolve
this.node.addValidation({
Expand Down Expand Up @@ -250,7 +260,6 @@ export abstract class CustomResourceProviderBase extends Construct {
});

this._codeHash = staging.assetHash;

return {
code: {
S3Bucket: asset.bucketName,
Expand Down Expand Up @@ -279,3 +288,4 @@ export type Code = {
S3Bucket: string;
S3Key: string;
};

138 changes: 138 additions & 0 deletions packages/aws-cdk-lib/core/lib/nodejs-aspect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* eslint-disable brace-style */
import { Construct, IConstruct } from 'constructs';
import { IAspect, Aspects } from './aspect';
import * as cdk from '../../../aws-cdk-lib';
import * as lambda from '../../aws-lambda';
import { ReceiptRuleSet } from '../../aws-ses/lib/receipt-rule-set';
import { Provider } from '../../custom-resources/lib';

/**
* Runtime aspec
*/
abstract class RuntimeAspectsBase implements IAspect {
shikha372 marked this conversation as resolved.
Show resolved Hide resolved

/**
* The string key for the runtime
*/
public readonly targetRuntime: string;

constructor(runtime: string) {
this.targetRuntime = runtime;
}

public visit(construct: IConstruct): void {

//To handle custom providers
if (construct instanceof cdk.CustomResourceProviderBase) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we need to check for instances of CustomResourceProviderBase, Provider, or ReceptRule. At their core these are all the same construct: CfnResource of type AWS::Lambda::Function. This should be all we need to look for.

Copy link
Contributor Author

@shikha372 shikha372 Apr 30, 2024

Choose a reason for hiding this comment

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

This is more to the point that we want to update it only for 'CustomResources' and not for all lambda functions which i think we can cover with CfnResource of type AWS::Lambda::Function , but this is more suitable for the use case of customResources only

this.handleCustomResourceProvider(construct);
}

//To handle providers
else if (construct instanceof Provider) {
this.handleProvider(construct);
}

//To handle single Function case
else if (construct instanceof ReceiptRuleSet) {
this.handleReceiptRuleSet(construct);
}
}

private handleCustomResourceProvider(provider: cdk.CustomResourceProviderBase): void {
const node = provider as cdk.CustomResourceProviderBase;
// only replace for nodejs case
if (this.isValidRuntime(node.runtime)) {
const targetnode = node.node.children.find((child) => child instanceof cdk.CfnResource &&
child.cfnResourceType === 'AWS::Lambda::Function') as cdk.CfnResource;
targetnode.addPropertyOverride('Runtime', 'nodejs20.x');
}
}

private handleProvider(provider: Provider): void {
const functionNodes = provider.node.children.filter((child) => child instanceof lambda.Function);
for ( var node of functionNodes) {
const targetNode = node.node.children.find((child) => child instanceof cdk.CfnResource && child.cfnResourceType === 'AWS::Lambda::Function') as cdk.CfnResource;
shikha372 marked this conversation as resolved.
Show resolved Hide resolved
if (this.isValidRuntime(this.getRuntimeProperty(targetNode))) {
targetNode.addPropertyOverride('Runtime', 'nodejs20.x');
}

// onEvent Handlers
const onEventHandler = provider.onEventHandler as lambda.Function;
const onEventHandlerRuntime = onEventHandler.node.children.find((child) => child instanceof cdk.CfnResource && child.cfnResourceType === 'AWS::Lambda::Function') as cdk.CfnResource;
shikha372 marked this conversation as resolved.
Show resolved Hide resolved
if (this.isValidRuntime(this.getRuntimeProperty(targetNode))) {
onEventHandlerRuntime.addPropertyOverride('Runtime', 'nodejs20.x');
}

//isComplete Handlers
// Handlers
const isCompleteHandler = provider.isCompleteHandler as lambda.Function;
const isCompleteHandlerRuntime = isCompleteHandler.node.children.find((child) => child instanceof cdk.CfnResource && child.cfnResourceType === 'AWS::Lambda::Function') as cdk.CfnResource;
if (this.isValidRuntime(this.getRuntimeProperty(targetNode))) {
isCompleteHandlerRuntime.addPropertyOverride('Runtime', 'nodejs20.x');
}
}
}

private handleReceiptRuleSet(ruleSet: ReceiptRuleSet): void {
const functionnode = ruleSet.node.findChild('DropSpam');
const ses = functionnode.node.findChild('Function') as lambda.CfnFunction;
if (ses.runtime && this.isValidRuntime(ses.runtime)) {
ses.addPropertyOverride('Runtime', 'nodejs20.x');
}
}

/**
*Runtime Validation
*
*/
private isValidRuntime(runtime: string) : boolean {
shikha372 marked this conversation as resolved.
Show resolved Hide resolved
return runtime === this.targetRuntime;
}

private getRuntimeProperty(node: cdk.CfnResource) {
return (node.getResourceProperty('runtime') || node.getResourceProperty('Runtime')) as string;
}
}
//}
/**
* RuntimeAspect
*/
export class RuntimeAspect extends RuntimeAspectsBase {
shikha372 marked this conversation as resolved.
Show resolved Hide resolved

/**
* DEPRECATED: add tags to the node of a construct and all its the taggable children
*
* @deprecated use `Tags.of(scope).add()`
*/
public static add(scope: Construct, key: string) {
Runtime.of(scope).add(key);
}

constructor(key: string) {
super(key);
}
}

/**
* Runtime
*/
export class Runtime {
shikha372 marked this conversation as resolved.
Show resolved Hide resolved
/**
* Returns the runtime API for this scope.
* @param scope The scope
*/
public static of(scope: IConstruct): Runtime {
return new Runtime(scope);
}

private constructor(private readonly scope: IConstruct) { }

/**
* modify runtime
*/
public add(key: string) {
Aspects.of(this.scope).add(new RuntimeAspect(key));
}

}