Skip to content

Commit

Permalink
Add implementation for CloudFormation Custom Resource Emulator (#1806)
Browse files Browse the repository at this point in the history
This PR adds support for [CloudFormation Custom
Resource](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html)
to the aws-native provider. It implements an emulator that enables
Pulumi programs to interact with Lambda-backed CloudFormation Custom
Resources.

A CloudFormation custom resource is essentially an extension point to
run arbitrary code as part of the CloudFormation lifecycle. It is
similar in concept to the [Pulumi Command
Provider](https://www.pulumi.com/registry/packages/command/), the
difference being that CloudFormation CustomResources are executed in the
Cloud; either through Lambda or SNS.

For the first implementation we decided to limit the scope to Lambda
backed Custom Resources, because the SNS variants are not widely used.

## Custom Resource Protocol
The implementation follows the CloudFormation Custom Resource protocol.
I derived the necessary parts by combining information from the
[docs](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref.html),
[CDKs CustomResource
Framework](https://github.com/aws/aws-cdk/tree/main/packages/%40aws-cdk/custom-resource-handlers/lib/custom-resources-framework)
and trial&error.

Notable aspects of that protocol are:
- primitive properties need to be string encoded when sending them to
Custom Resource handlers. This includes deeply nested properties:
aws-cloudformation/cloudformation-coverage-roadmap#1037
- The Lambda Function is invoked asynchronously. Lambda will retry the
execution if the function fails unexpectedly (e.g. unhandled exception).
- Due to the async invocation, the response is not returned from the
Lambda Function, instead it's sent to a `ResponseURL` that needs to be
included in the request payload.
- Similarly to CloudFormation, we decided to implement this using S3
Buckets and presigned URLs.

### Custom Resource Lifecycle
```mermaid
sequenceDiagram
    participant A as aws-native
    participant S3 as S3 Bucket
    participant L as Lambda
    
    %% Create Flow
    Note over A,L: Create Operation
    A->>S3: Generate presigned URL
    A->>L: Invoke with CREATE event
    activate L
    loop Until response found or timeout
        A->>S3: Poll for response
        L-->>S3: Upload response
    end
    deactivate L
    A->>S3: Fetch response
    alt Success
        A->>A: Store PhysicalId & outputs
    else Failure
        A->>A: Return error
    end

    %% Update Flow
    Note over A,L: Update Operation
    A->>S3: Generate presigned URL
    A->>L: Invoke with UPDATE event
    activate L
    loop Until response found or timeout
        A->>S3: Poll for response
        L-->>S3: Upload response
    end
    deactivate L
    A->>S3: Fetch response
    alt Success
        A->>A: Check PhysicalId
        alt ID Changed
            A->>S3: Generate presigned URL for cleanup
            A->>L: Invoke with DELETE event for old resource
            activate L
            loop Until cleanup response found or timeout
                A->>S3: Poll for cleanup response
                L-->>S3: Upload cleanup response
            end
            deactivate L
            A->>S3: Fetch cleanup response
        end
    else Failure
        A->>A: Return error
    end

    %% Delete Flow
    Note over A,L: Delete Operation
    A->>S3: Generate presigned URL
    A->>L: Invoke with DELETE event
    activate L
    loop Until response found or timeout
        A->>S3: Poll for response
        L-->>S3: Upload response
    end
    deactivate L
    A->>S3: Fetch response
    alt Success
        A->>A: Return success
    else Failure
        A->>A: Return error
    end
```

## Reviewer Notes

Key areas to review:
1. Error handling in the response collection mechanism
2. Timeout management, especially for the `Update` lifecycle
3. Documentation completeness and accuracy

Exposing this resource and schematizing it is part of this PR
#1807.
Automatically cleaning up the response objects is not included in this
PR in order to keep its size manageable. Implementing this is tracked
here: #1813.

Please pay special attention to:
- S3 response collection mechanism security
- State management during updates
- Cleanup handling when physical resource IDs change

## Testing
- Unit tests including error handling tests for various failure
scenarios
- Integration tests with actual Lambda functions are added in this
stacked PR: #1807

## Related Issues
- pulumi/pulumi-cdk#109
- #1812
- #1813
  • Loading branch information
flostadler authored Nov 8, 2024
1 parent 04539b4 commit 14a21ae
Show file tree
Hide file tree
Showing 5 changed files with 2,228 additions and 0 deletions.
1 change: 1 addition & 0 deletions provider/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.21

require (
github.com/apparentlymart/go-cidr v1.1.0
github.com/aws/aws-lambda-go v1.47.0
github.com/aws/aws-sdk-go v1.50.36
github.com/aws/aws-sdk-go-v2 v1.32.3
github.com/aws/aws-sdk-go-v2/config v1.27.11
Expand Down
2 changes: 2 additions & 0 deletions provider/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aws/aws-lambda-go v1.47.0 h1:0H8s0vumYx/YKs4sE7YM0ktwL2eWse+kfopsRI1sXVI=
github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7RfgJv23DymV8A=
github.com/aws/aws-sdk-go v1.50.36 h1:PjWXHwZPuTLMR1NIb8nEjLucZBMzmf84TLoLbD8BZqk=
github.com/aws/aws-sdk-go v1.50.36/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk=
github.com/aws/aws-sdk-go-v2 v1.32.3 h1:T0dRlFBKcdaUPGNtkBSwHZxrtis8CQU17UpNBZYd0wk=
Expand Down
4 changes: 4 additions & 0 deletions provider/pkg/metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,7 @@ type CloudAPIFunction struct {
// ExtensionResourceToken is a Pulumi token for the resource to deploy
// custom third-party CloudFormation types.
const ExtensionResourceToken = "aws-native:index:ExtensionResource"

// CfnCustomResourceToken is a Pulumi token for the resource to deploy
// CloudFormation custom resources.
const CfnCustomResourceToken = "aws-native:cloudformation:CustomResourceEmulator"
Loading

0 comments on commit 14a21ae

Please sign in to comment.