in packages/aws-cdk-lib/custom-resources/lib/aws-custom-resource/aws-custom-resource.ts [463:581]
constructor(scope: Construct, id: string, props: AwsCustomResourceProps) {
super(scope, id);
if (!props.onCreate && !props.onUpdate && !props.onDelete) {
throw new Error('At least `onCreate`, `onUpdate` or `onDelete` must be specified.');
}
if (!props.role && !props.policy) {
throw new Error('At least one of `policy` or `role` (or both) must be specified.');
}
if (props.onCreate && !props.onCreate.physicalResourceId) {
throw new Error("'physicalResourceId' must be specified for 'onCreate' call.");
}
if (!props.onCreate && props.onUpdate && !props.onUpdate.physicalResourceId) {
throw new Error("'physicalResourceId' must be specified for 'onUpdate' call when 'onCreate' is omitted.");
}
for (const call of [props.onCreate, props.onUpdate, props.onDelete]) {
if (call?.physicalResourceId?.responsePath) {
AwsCustomResource.breakIgnoreErrorsCircuit([call], 'PhysicalResourceId.fromResponse');
}
}
if (includesPhysicalResourceIdRef(props.onCreate?.parameters)) {
throw new Error('`PhysicalResourceIdReference` must not be specified in `onCreate` parameters.');
}
this.props = props;
let memorySize = props.memorySize;
if (props.installLatestAwsSdk) {
memorySize ??= 512;
}
const provider = new AwsCustomResourceSingletonFunction(this, 'Provider', {
uuid: AwsCustomResource.PROVIDER_FUNCTION_UUID,
lambdaPurpose: 'AWS',
memorySize: memorySize,
timeout: props.timeout || cdk.Duration.minutes(2),
role: props.role,
// props.logRetention is deprecated, make sure we only set it if it is actually provided
// otherwise jsii will print warnings even for users that don't use this directly
...(props.logRetention ? { logRetention: props.logRetention } : {}),
logGroup: props.logGroup,
functionName: props.functionName,
vpc: props.vpc,
vpcSubnets: props.vpcSubnets,
});
this.grantPrincipal = provider.grantPrincipal;
const installLatestAwsSdk = (props.installLatestAwsSdk
?? this.node.tryGetContext(cxapi.AWS_CUSTOM_RESOURCE_LATEST_SDK_DEFAULT)
?? true);
if (installLatestAwsSdk && props.installLatestAwsSdk === undefined) {
// This is dangerous. Add a warning.
Annotations.of(this).addWarningV2('@aws-cdk/custom-resources:installLatestAwsSdkNotSpecified', [
'installLatestAwsSdk was not specified, and defaults to true. You probably do not want this.',
`Set the global context flag \'${cxapi.AWS_CUSTOM_RESOURCE_LATEST_SDK_DEFAULT}\' to false to switch this behavior off project-wide,`,
'or set the property explicitly to true if you know you need to call APIs that are not in Lambda\'s built-in SDK version.',
].join(' '));
}
const create = props.onCreate || props.onUpdate;
this.customResource = new cdk.CustomResource(this, 'Resource', {
resourceType: props.resourceType || 'Custom::AWS',
serviceToken: provider.functionArn,
serviceTimeout: props.serviceTimeout,
pascalCaseProperties: true,
removalPolicy: props.removalPolicy,
properties: {
create: create && this.formatSdkCall(create),
update: props.onUpdate && this.formatSdkCall(props.onUpdate),
delete: props.onDelete && this.formatSdkCall(props.onDelete),
installLatestAwsSdk,
},
});
// Create the policy statements for the custom resource function role, or use the user-provided ones
if (props.policy) {
const statements = [];
if (props.policy.statements.length !== 0) {
// Use custom statements provided by the user
for (const statement of props.policy.statements) {
statements.push(statement);
}
} else {
// Derive statements from AWS SDK calls
for (const call of [props.onCreate, props.onUpdate, props.onDelete]) {
if (call && call.assumedRoleArn == null) {
const statement = new iam.PolicyStatement({
actions: [awsSdkToIamAction(call.service, call.action)],
resources: props.policy.resources,
});
statements.push(statement);
} else if (call && call.assumedRoleArn != null) {
const statement = new iam.PolicyStatement({
actions: ['sts:AssumeRole'],
resources: [call.assumedRoleArn],
});
statements.push(statement);
}
}
}
const policy = new iam.Policy(this, 'CustomResourcePolicy', {
statements: statements,
});
if (provider.role !== undefined) {
policy.attachToRole(provider.role);
}
// If the policy was deleted first, then the function might lose permissions to delete the custom resource
// This is here so that the policy doesn't get removed before onDelete is called
this.customResource.node.addDependency(policy);
}
}