Skip to content

Commit

Permalink
Add developer docs
Browse files Browse the repository at this point in the history
  • Loading branch information
flostadler committed Nov 8, 2024
1 parent dde2841 commit 94cfa06
Showing 1 changed file with 139 additions and 0 deletions.
139 changes: 139 additions & 0 deletions provider/pkg/resources/cfn_custom_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,52 @@ type cfnCustomResource struct {
// Check CfnCustomResource implements CustomResource
var _ CustomResource = (*cfnCustomResource)(nil)

// The CloudFormation Custom Resource Emulator provides a bridge between Pulumi's resource model
// and AWS CloudFormation Custom Resources. It enables Pulumi programs to interact with CloudFormation
// Custom Resources while maintaining Pulumi's strong typing and error handling capabilities.
//
// When are Cloudformation Custom Resources used?
// - Managing resources outside of AWS (e.g., GitHub repositories, external APIs)
// - Implementing complex provisioning logic not possible with standard CloudFormation
// - Performing custom validations or transformations during resource lifecycle events
// - Integrating with legacy systems or third-party services
// - Implementing organization-specific infrastructure patterns
//
// The Custom Resource implementation (i.e. the Lambda function) is responsible for:
// - Processing the request based on the RequestType
// - Sending a response to the pre-signed URL. The response should include:
// - success/failure status and a reason
// - a PhysicalResourceId for the resource
// - optionally return data that can be referenced by other resources
//
// Example CloudFormation Custom Resource:
// Resources:
// MyCustomResource:
// Type: Custom::MyResource
// Properties:
// ServiceToken: arn:aws:lambda:region:account:function:name
// Property1: value1
// Property2: value2
//
//
// This emulator implements this lifecycle in Pulumi by:
// - Translating Pulumi resource operations to CloudFormation custom resource requests
// - Managing the asynchronous response collection via S3 with pre-signed URLs
// - Handling timeout and error scenarios according to CloudFormation specifications
//
// Architecture:
// ┌──────────────┐ ┌─────────────┐ ┌──────────────┐
// │ Pulumi │ CRUD │ Custom │ AWS │ Lambda │
// │ Engine ├────────►│ Resource ├────────►│ Function │
// │ │ │ Emulator │ │ │
// └──────────────┘ └─────────────┘ └──────────────┘
// │ │
// │ │
// │ ┌──────────┐ │
// └─────►│ S3 │◄──────┘
// Poll for │ Bucket │ Response
// Response └──────────┘
//
func NewCfnCustomResource(providerName string, s3Client client.S3Client, lambdaClient client.LambdaClient) *cfnCustomResource {
return &cfnCustomResource{
providerName: providerName,
Expand Down Expand Up @@ -218,6 +264,35 @@ func (c *cfnCustomResource) Check(ctx context.Context, urn urn.URN, randomSeed [

// Create creates the Custom Resource by invoking the Lambda function with the CREATE request type.
// Returns the physical resource ID and outputs if the creation is successful, otherwise an error.
//
// Creation Flow:
//
// ┌─────────┐ ┌─────────────┐ ┌───────────────┐ ┌──────────┐
// │ Create │ │Generate S3 │ │ Invoke Lambda │ │ Wait for │
// │ Request ├──►│Presigned URL├──►│ with CREATE ├──►│ Response │
// └─────────┘ └─────────────┘ │ RequestType │ └────┬─────┘
// └───────────────┘ │
// ▼
// ┌───────────────┐
// │Return Physical│
// │Resource ID & │
// │Outputs │
// └───────────────┘
//
// The Create operation:
// 1. Generates a presigned S3 URL for response collection
// 2. Constructs a CloudFormation CREATE event with:
// - Unique RequestID (UUID)
// - ResponseURL (presigned S3 URL)
// - ResourceType from input
// - LogicalResourceId from Pulumi URN
// - Custom properties from input
// 3. Invokes the Lambda function asynchronously
// 4. Waits for response in S3 bucket
// 5. Processes response:
// - On success: Returns PhysicalResourceId and properties
// - On failure: Returns error with reason
// - Handles `NoEcho` for sensitive data
func (c *cfnCustomResource) Create(ctx context.Context, urn urn.URN, inputs resource.PropertyMap, timeout time.Duration) (*string, resource.PropertyMap, error) {
var typedInputs CfnCustomResourceInputs
_, err := resourcex.Unmarshal(&typedInputs, inputs, resourcex.UnmarshalOptions{})
Expand Down Expand Up @@ -271,6 +346,30 @@ func (c *cfnCustomResource) Create(ctx context.Context, urn urn.URN, inputs reso

// Delete deletes the Custom Resource by invoking the Lambda function with the DELETE request type.
// Returns an error if the delete operation fails, otherwise nil.
//
// Delete Flow:
//
// ┌─────────┐ ┌─────────────┐ ┌───────────────┐ ┌──────────┐
// │ Delete │ │Generate S3 │ │ Invoke Lambda │ │ Wait for │
// │ Request ├──►│Presigned URL├──►│ with DELETE ├──►│Response │
// └─────────┘ └─────────────┘ │ RequestType │ └────┬─────┘
// └───────────────┘ │
// ▼
// ┌───────────────┐
// │Verify Delete │
// │Success │
// └───────────────┘
//
// The Delete operation:
// 1. Generates a presigned S3 URL for response collection
// 2. Constructs CloudFormation DELETE event with:
// - Existing PhysicalResourceId
// - Current ResourceProperties
// - All standard CloudFormation fields
// 3. Invokes Lambda and waits for response
// 4. Handles response:
// - Success: Completes deletion
// - Failure: Returns error with reason from Lambda
func (c *cfnCustomResource) Delete(ctx context.Context, urn urn.URN, id string, inputs, state resource.PropertyMap, timeout time.Duration) error {
var typedInputs CfnCustomResourceInputs
_, err := resourcex.Unmarshal(&typedInputs, inputs, resourcex.UnmarshalOptions{})
Expand Down Expand Up @@ -320,6 +419,46 @@ func (c *cfnCustomResource) Delete(ctx context.Context, urn urn.URN, id string,
// Update updates the custom resource with the given inputs and state by invoking the Lambda function with the UPDATE request type.
// If the update is successful and the physical resource ID has changed,
// it deletes the old resource. The function returns the updated resource properties or an error.
//
// Update Flow:
//
// ┌─────────┐ ┌─────────────┐ ┌───────────────┐ ┌──────────┐
// │ Update │ │Generate S3 │ │ Invoke Lambda │ │ Wait for │
// │ Request ├──►│Presigned URL├──►│ with UPDATE ├──►│ Response │
// └─────────┘ └─────────────┘ │ RequestType │ └────┬─────┘
// └───────────────┘ │
// ▼
// ┌───────────────┐
// │Check Physical │
// │Resource ID │
// └───────┬───────┘
// │
// ▼
// ┌───────────────┐
// │Delete Old │
// │Resource │
// │(if ID changed)│
// └───────┬───────┘
// │
// ▼
// ┌───────────────┐
// │Return updated │
// │Physical │
// │Resource ID & │
// │Outputs │
// └───────────────┘
//
// The Update operation:
// 1. Generates a presigned S3 URL for response collection
// 2. Constructs a CloudFormation UPDATE event with:
// - Existing PhysicalResourceId
// - Old and new ResourceProperties
// - All standard CloudFormation fields
// 3. Invokes Lambda and collects response
// 4. If PhysicalResourceId changes:
// - Initiates cleanup of old resource
// - Sends DELETE event for old PhysicalResourceId
// 5. Returns updated properties and new PhysicalResourceId
func (c *cfnCustomResource) Update(ctx context.Context, urn urn.URN, id string, inputs, oldInputs, state resource.PropertyMap, timeout time.Duration) (resource.PropertyMap, error) {
var oldTypedInputs CfnCustomResourceInputs
_, err := resourcex.Unmarshal(&oldTypedInputs, oldInputs, resourcex.UnmarshalOptions{})
Expand Down

0 comments on commit 94cfa06

Please sign in to comment.