Package: aws-cdk.custom-resources
AWS CDK Custom Resources---AWS CDK v1 has reached End-of-Support on 2023-06-01.
This package is no longer being updated, and users should migrate to AWS CDK v2.For more information on how to migrate, see theMigrating to AWS CDK v2guide.Provider FrameworkAWS CloudFormationcustom resourcesare extension points to the provisioning
engine. When CloudFormation needs to create, update or delete a custom resource,
it sends a lifecycle event notification to acustom resource provider. The provider
handles the event (e.g. creates a resource) and sends back a response to CloudFormation.The@aws-cdk/custom-resources.Providerconstruct is a "mini-framework" for
implementing providers for AWS CloudFormation custom resources. The framework offers a high-level API which makes it easier to implement robust
and powerful custom resources and includes the following capabilities:Handles responses to AWS CloudFormation and protects against blocked
deploymentsValidates handler return values to help with correct handler implementationSupports asynchronous handlers to enable operations that require a long waiting period for a resource, which can exceed the AWS Lambda timeoutImplements default behavior for physical resource IDs.The following code shows how theProviderconstruct is used in conjunction
with aCustomResourceand a user-provided AWS Lambda function which implements
the actual handler.# on_event: lambda.Function# is_complete: lambda.Function# my_role: iam.Rolemy_provider=cr.Provider(self,"MyProvider",on_event_handler=on_event,is_complete_handler=is_complete,# optional async "waiter"log_retention=logs.RetentionDays.ONE_DAY,# default is INFINITErole=my_role)CustomResource(self,"Resource1",service_token=my_provider.service_token)CustomResource(self,"Resource2",service_token=my_provider.service_token)Providers are implemented through AWS Lambda functions that are triggered by the
provider framework in response to lifecycle events.At the minimum, users must define theonEventhandler, which is invoked by the
framework for all resource lifecycle events (Create,UpdateandDelete)
and returns a result which is then submitted to CloudFormation.The following example is a skeleton for a Python implementation ofonEvent:defon_event(event,context):print(event)request_type=event['RequestType']ifrequest_type=='Create':returnon_create(event)ifrequest_type=='Update':returnon_update(event)ifrequest_type=='Delete':returnon_delete(event)raiseException("Invalid request type:%s"%request_type)defon_create(event):props=event["ResourceProperties"]print("create new resource with props%s"%props)# add your create code here...physical_id=...return{'PhysicalResourceId':physical_id}defon_update(event):physical_id=event["PhysicalResourceId"]props=event["ResourceProperties"]print("update resource%swith props%s"%(physical_id,props))# ...defon_delete(event):physical_id=event["PhysicalResourceId"]print("delete resource%s"%physical_id)# ...Users may also provide an additional handler calledisComplete, for cases
where the lifecycle operation cannot be completed immediately. TheisCompletehandler will be retried asynchronously afteronEventuntil it
returnsIsComplete: true, or until the total provider timeout has expired.The following example is a skeleton for a Python implementation ofisComplete:defis_complete(event,context):physical_id=event["PhysicalResourceId"]request_type=event["RequestType"]# check if resource is stable based on request_typeis_ready=...return{'IsComplete':is_ready}Security Note: the Custom Resource Provider Framework will write the value ofResponseURL,
which is a pre-signed S3 URL used to report the success or failure of the Custom Resource execution
back to CloudFormation, in a readable form to the AWS Step Functions execution history.Anybody who can list and read AWS StepFunction executions in your account will be able to write
a fake response to this URL and make your CloudFormation deployments fail.Do not use this library if your threat model requires that you cannot trust actors who are able
to list StepFunction executions in your account.Handling Lifecycle Events: onEventThe user-definedonEventAWS Lambda function is invoked whenever a resource
lifecycle event occurs. The function is expected to handle the event and return
a response to the framework that, at least, includes the physical resource ID.IfonEventreturns successfully, the framework will submit a "SUCCESS" response
to AWS CloudFormation for this resource operation.  If the provider isasynchronous(isCompleteHandleris
defined), the framework will only submit a response based on the result ofisComplete.IfonEventthrows an error, the framework will submit a "FAILED" response to
AWS CloudFormation.The input event includes the following fields derived from the [Custom Resource
Provider Request]:FieldTypeDescriptionRequestTypeStringThe type of lifecycle event:Create,UpdateorDelete.LogicalResourceIdStringThe template developer-chosen name (logical ID) of the custom resource in the AWS CloudFormation template.PhysicalResourceIdStringThis field will only be present forUpdateandDeleteevents and includes the value returned inPhysicalResourceIdof the previous operation.ResourcePropertiesJSONThis field contains the properties defined in the template for this custom resource.OldResourcePropertiesJSONThis field will only be present forUpdateevents and contains the resource properties that were declared previous to the update request.ResourceTypeStringThe resource type defined for this custom resource in the template. A provider may handle any number of custom resource types.RequestIdStringA unique ID for the request.StackIdStringThe ARN that identifies the stack that contains the custom resource.The return value fromonEventmust be a JSON object with the following fields:FieldTypeRequiredDescriptionPhysicalResourceIdStringNoThe allocated/assigned physical ID of the resource. If omitted forCreateevents, the event'sRequestIdwill be used. ForUpdate, the current physical ID will be used. If a different value is returned, CloudFormation will follow with a subsequentDeletefor the previous ID (resource replacement). ForDelete, it will always return the current physical resource ID, and if the user returns a different one, an error will occur.DataJSONNoResource attributes, which can later be retrieved throughFn::GetAtton the custom resource object.NoEchoBooleanNoWhether to mask the output of the custom resource when retrieved by using theFn::GetAttfunction.anyanyNoAny other field included in the response will be passed through toisComplete. This can sometimes be useful to pass state between the handlers.Asynchronous Providers: isCompleteIt is not uncommon for the provisioning of resources to be an asynchronous
operation, which means that the operation does not immediately finish, and we
need to "wait" until the resource stabilizes.The provider framework makes it easy to implement "waiters" by allowing users to
specify an additional AWS Lambda function inisCompleteHandler.The framework will repeatedly invoke the handler everyqueryInterval. WhenisCompletereturns withIsComplete: true, the framework will submit a
"SUCCESS" response to AWS CloudFormation. IftotalTimeoutexpires and the
operation has not yet completed, the framework will submit a "FAILED" response
with the message "Operation timed out".If an error is thrown, the framework will submit a "FAILED" response to AWS
CloudFormation.The input event toisCompleteincludes all request fields, combined with all
fields returned fromonEvent. IfPhysicalResourceIdhas not been explicitly
returned fromonEvent, it's value will be calculated based on the heuristics
described above.The return value must be a JSON object with the following fields:FieldTypeRequiredDescriptionIsCompleteBooleanYesIndicates if the operation has finished or not.DataJSONNoMay only be sent ifIsCompleteistrueand includes additional resource attributes. These attributes will bemergedwith the ones returned fromonEventPhysical Resource IDsEvery resource in CloudFormation has a physical resource ID. When a resource is
created, thePhysicalResourceIdreturned from theCreateoperation is stored
by AWS CloudFormation and assigned to the logical ID defined for this resource
in the template. If aCreateoperation returns without aPhysicalResourceId,
the framework will useRequestIdas the default. This is sufficient for
various cases such as "pseudo-resources" which only query data.ForUpdateandDeleteoperations, the resource event will always include the
currentPhysicalResourceIdof the resource.When anUpdateoperation occurs, the default behavior is to return the current
physical resource ID. if theonEventreturns aPhysicalResourceIdwhich is
different from the current one, AWS CloudFormation will treat this as aresource replacement, and it will issue a subsequentDeleteoperation for
the old resource.As a rule of thumb, if your custom resource supports configuring a physical name
(e.g. you can specify aBucketNamewhen you define anAWS::S3::Bucket), you
must return this name inPhysicalResourceIdand make sure to handle
replacement properly. TheS3Fileexample demonstrates this
through theobjectKeyproperty.When there are errorsAs mentioned above, if any of the user handlers fail (i.e. throws an exception)
or times out (due to their AWS Lambda timing out), the framework will trap these
errors and submit a "FAILED" response to AWS CloudFormation, along with the error
message.Since errors can occur in multiple places in the provider (framework,onEvent,isComplete), it is important to know that there could situations where a
resource operation fails even though the operation technically succeeded (i.e.
isComplete throws an error).When AWS CloudFormation receives a "FAILED" response, it will attempt to roll
back the stack to it's last state. This has different meanings for different
lifecycle events:If aCreateevent fails, the resource provider framework will automatically
ignore the subsequentDeleteoperation issued by AWS CloudFormation. The
framework currently does not support customizing this behavior (seehttps://github.com/aws/aws-cdk/issues/5524).If anUpdateevent fails, CloudFormation will issue an additionalUpdatewith the previous properties.If aDeleteevent fails, CloudFormation will abandon this resource.Important cases to handleYou should keep the following list in mind when writing custom resources to
make sure your custom resource behaves correctly in all cases:DuringCreate:If the create fails, theprovider frameworkwill make sure you
don't get a subsequentDeleteevent. If your create involves multiple distinct
operations, it is your responsibility to catch and rethrow and clean up
any partial updates that have already been performed. Make sure your
API call timeouts and Lambda timeouts allow for this.DuringUpdate:If the update fails, you will get a subsequentUpdateevent
to roll back to the previous state (withResourcePropertiesandOldResourcePropertiesreversed).If you return a differentPhysicalResourceId, you will subsequently
receive aDeleteevent to clean up the previous state of the resource.DuringDelete:If the behavior of your custom resource is tied to another AWS resource
(for example, it exists to clean the contents of a stateful resource), keep
in mind that your custom resource may be deleted independently of the other
resource and you must confirm that it is appropriate to perform the action.(only if you arenotusing the provider framework) aDeleteevent
may be caused by a failedCreate. You must be able to handle the case
where the resource you are trying to delete hasn't even been created yet.If you update the code of your custom resource and change the format of the
resource properties, be aware that there may still be already-deployed
instances of your custom resource out there, and you may still receive
theoldproperty format inResourceProperties(duringDeleteand
rollbackUpdates) or inOldResourceProperties(during rollforwardUpdate). You must continue to handle all possible sets of properties
your custom resource could have ever been created with in the past.Provider Framework Execution PolicySimilarly to any AWS Lambda function, if the user-defined handlers require
access to AWS resources, you will have to define these permissions
by calling "grant" methods such asmyBucket.grantRead(myHandler)), usingmyHandler.addToRolePolicyor specifying aninitialPolicywhen defining the function.Bear in mind that in most cases, a single provider will be used for multiple
resource instances. This means that the execution policy of the provider must
have the appropriate privileges.The following example grants theonEventhandlers3:GetObject*permissions
to all buckets:lambda_.Function(self,"OnEventHandler",runtime=lambda_.Runtime.NODEJS_14_X,handler="index.handler",code=lambda_.Code.from_inline("my code"),initial_policy=[iam.PolicyStatement(actions=["s3:GetObject*"],resources=["*"])])TimeoutsUsers are responsible to define the timeouts for the AWS Lambda functions for
user-defined handlers. It is recommended not to exceed a14 minutestimeout,
since all framework functions are configured to time out after 15 minutes, which
is the maximal AWS Lambda timeout.If your operation takes over14 minutes, the recommended approach is to
implement anasynchronous provider, and
then configure the timeouts for the asynchronous retries through thequeryIntervaland thetotalTimeoutoptions.Provider Framework ExamplesThis module includes a few examples for custom resource implementations:S3FileProvisions an object in an S3 bucket with textual contents. See the source code
for theconstructandhandler.The following example will create the filefolder/file1.txtinsidemyBucketwith the contentshello!.// This example exists only for TypeScript

declare const myBucket: s3.Bucket;
new cr.S3File(this, 'MyFile', {
  bucket: myBucket,
  objectKey: 'folder/file1.txt', // optional
  content: 'hello!',
  public: true, // optional
});This sample demonstrates the following concepts:Synchronous implementation (isCompleteis not defined)Automatically generates the physical name ifobjectKeyis not definedHandles physical name changesReturns resource attributesHandles deletionsImplemented in TypeScriptS3AssertChecks that the textual contents of an S3 object matches a certain value. The check will be retried for 5 minutes as long as the object is not found or the value is different. See the source code for theconstructandhandler.The following example defines anS3Assertresource which waits untilmyfile.txtinmyBucketexists and includes the contentsfoo bar:// This example exists only for TypeScript

declare const myBucket: s3.Bucket;
new cr.S3Assert(this, 'AssertMyFile', {
  bucket: myBucket,
  objectKey: 'myfile.txt',
  expectedContent: 'foo bar',
});This sample demonstrates the following concepts:Asynchronous implementationNon-intrinsic physical IDsImplemented in PythonCustomizing Provider Function nameIn multi-account environments or when the custom resource may be re-utilized across several
stacks it may be useful to manually set a name for the Provider Function Lambda and therefore
have a predefined service token ARN.# on_event: lambda.Function# is_complete: lambda.Function# my_role: iam.Rolemy_provider=cr.Provider(self,"MyProvider",on_event_handler=on_event,is_complete_handler=is_complete,log_retention=logs.RetentionDays.ONE_DAY,role=my_role,provider_function_name="the-lambda-name")Custom Resources for AWS APIsSometimes a single API call can fill the gap in the CloudFormation coverage. In
this case you can use theAwsCustomResourceconstruct. This construct creates
a custom resource that can be customized to make specific API calls for theCREATE,UPDATEandDELETEevents. Additionally, data returned by the API
call can be extracted and used in other constructs/resources (creating a real
CloudFormation dependency usingFn::GetAttunder the hood).The physical id of the custom resource can be specified or derived from the data
returned by the API call.TheAwsCustomResourceuses the AWS SDK for JavaScript. Services, actions and
parameters can be found in theAPI documentation.Path to data must be specified using a dot notation, e.g. to get the string value
of theTitleattribute for the first item returned bydynamodb.queryit should
beItems.0.Title.S.To make sure that the newest API calls are available the latest AWS SDK v2 is installed
in the Lambda function implementing the custom resource. The installation takes around 60
seconds. If you prefer to optimize for speed, you can disable the installation by setting
theinstallLatestAwsSdkprop tofalse.Custom Resource Execution PolicyYou must provide thepolicyproperty defining the IAM Policy that will be applied to the API calls.
The library provides two factory methods to quickly configure this:AwsCustomResourcePolicy.fromSdkCalls- Use this to auto-generate IAM
Policy statements based on the configured SDK calls. Keep two things in mind
when using this policy:This policy variant assumes the IAM policy name has the same name as the API
call. This is true in 99% of cases, but there are exceptions (for example,
S3'sPutBucketLifecycleConfigurationrequiress3:PutLifecycleConfigurationpermissions, Lambda'sInvokerequireslambda:InvokeFunctionpermissions). UsefromStatementsif you want to
do a call that requires different IAM action names.You will have to either provide specific ARNs, or explicitly useAwsCustomResourcePolicy.ANY_RESOURCEto allow access to any resource.AwsCustomResourcePolicy.fromStatements- Use this to specify your own
custom statements.The custom resource also implementsiam.IGrantable, making it possible to use thegrantXxx()methods.As this custom resource uses a singleton Lambda function, it's important to note
that the function's role will eventually accumulate the permissions/grants from all
resources.Chained API calls can be achieved by creating dependencies:aws_custom1=cr.AwsCustomResource(self,"API1",on_create=cr.AwsSdkCall(service="...",action="...",physical_resource_id=cr.PhysicalResourceId.of("...")),policy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE))aws_custom2=cr.AwsCustomResource(self,"API2",on_create=cr.AwsSdkCall(service="...",action="...",parameters={"text":aws_custom1.get_response_field("Items.0.text")},physical_resource_id=cr.PhysicalResourceId.of("...")),policy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE))Physical Resource Id ParameterSome AWS APIs may require passing the physical resource id in as a parameter for doing updates and deletes. You can pass it by usingPhysicalResourceIdReference.aws_custom=cr.AwsCustomResource(self,"aws-custom",on_create=cr.AwsSdkCall(service="...",action="...",parameters={"text":"..."},physical_resource_id=cr.PhysicalResourceId.of("...")),on_update=cr.AwsSdkCall(service="...",action="...",parameters={"text":"...","resource_id":cr.PhysicalResourceIdReference()}),policy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE))Handling Custom Resource ErrorsEvery error produced by the API call is treated as is and will cause a "FAILED" response to be submitted to CloudFormation.
You can ignore some errors by specifying theignoreErrorCodesMatchingproperty, which accepts a regular expression that is
tested against thecodeproperty of the response. If matched, a "SUCCESS" response is submitted.
Note that in such a case, the call response data and theDatakey submitted to CloudFormation would both be an empty JSON object.
Since a successful resource provisioning might or might not produce outputs, this presents us with some limitations:PhysicalResourceId.fromResponse- Since the call response data might be empty, we cannot use it to extract the physical id.getResponseFieldandgetResponseFieldReference- Since theDatakey is empty, the resource will not have any attributes, and therefore, invoking these functions will result in an error.In both the cases, you will get a synth time error if you attempt to use it in conjunction withignoreErrorCodesMatching.Customizing the Lambda function implementing the custom resourceUse therole,timeout,logRetentionandfunctionNameproperties to customize
the Lambda function implementing the custom resource:# my_role: iam.Rolecr.AwsCustomResource(self,"Customized",role=my_role,# must be assumable by the `lambda.amazonaws.com` service principaltimeout=Duration.minutes(10),# defaults to 2 minuteslog_retention=logs.RetentionDays.ONE_WEEK,# defaults to never delete logsfunction_name="my-custom-name",# defaults to a CloudFormation generated namepolicy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE))Restricting the output of the Custom ResourceCloudFormation imposes a hard limit of 4096 bytes for custom resources response
objects. If your API call returns an object that exceeds this limit, you can restrict
the data returned by the custom resource to specific paths in the API response:cr.AwsCustomResource(self,"ListObjects",on_create=cr.AwsSdkCall(service="s3",action="listObjectsV2",parameters={"Bucket":"my-bucket"},physical_resource_id=cr.PhysicalResourceId.of("id"),output_paths=["Contents.0.Key","Contents.1.Key"]),policy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE))Note that even if you restrict the output of your custom resource you can still use any
path inPhysicalResourceId.fromResponse().Custom Resource ExamplesVerify a domain with SESimportaws_cdk.aws_route53asroute53# zone: route53.HostedZoneverify_domain_identity=cr.AwsCustomResource(self,"VerifyDomainIdentity",on_create=cr.AwsSdkCall(service="SES",action="verifyDomainIdentity",parameters={"Domain":"example.com"},physical_resource_id=cr.PhysicalResourceId.from_response("VerificationToken")),policy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE))route53.TxtRecord(self,"SESVerificationRecord",zone=zone,record_name="_amazonses.example.com",values=[verify_domain_identity.get_response_field("VerificationToken")])Get the latest version of a secure SSM parameterget_parameter=cr.AwsCustomResource(self,"GetParameter",on_update=cr.AwsSdkCall(# will also be called for a CREATE eventservice="SSM",action="getParameter",parameters={"Name":"my-parameter","WithDecryption":True},physical_resource_id=cr.PhysicalResourceId.of(Date.now().to_string())),policy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE))# Use the value in another construct withget_parameter.get_response_field("Parameter.Value")Associate a PrivateHostedZone with VPC shared from another accountget_parameter=cr.AwsCustomResource(self,"AssociateVPCWithHostedZone",on_create=cr.AwsSdkCall(assumed_role_arn="arn:aws:iam::OTHERACCOUNT:role/CrossAccount/ManageHostedZoneConnections",service="Route53",action="associateVPCWithHostedZone",parameters={"HostedZoneId":"hz-123","VPC":{"VPCId":"vpc-123","VPCRegion":"region-for-vpc"}},physical_resource_id=cr.PhysicalResourceId.of("${vpcStack.SharedVpc.VpcId}-${vpcStack.Region}-${PrivateHostedZone.HostedZoneId}")),# Will ignore any resource and use the assumedRoleArn as resource and 'sts:AssumeRole' for service:actionpolicy=cr.AwsCustomResourcePolicy.from_sdk_calls(resources=cr.AwsCustomResourcePolicy.ANY_RESOURCE))This module is part of theAWS Cloud Development Kitproject.