AWS Cloud Control API
The AWS Cloud Control API integration in Alchemy provides a unified interface for managing AWS resources using the AWS Cloud Control API. This integration allows you to create, read, update, and delete any AWS resource that is supported by CloudFormation, using a consistent and type-safe interface.
Installation
Section titled “Installation”First, ensure you have the required peer dependency installed:
npm install aws4fetch
Then, import the AWS Cloud Control module:
import AWS from "alchemy/aws/control";
Basic Usage
Section titled “Basic Usage”The AWS Cloud Control integration provides a proxy-based interface that mirrors the AWS service and resource structure. For example, to create an S3 bucket:
import AWS from "alchemy/aws/control";
const bucket = await AWS.S3.Bucket("my-bucket", { BucketName: "my-unique-bucket-name", VersioningConfiguration: { Status: "Enabled", },});
console.log(bucket.BucketName); // "my-unique-bucket-name"
Resource Types
Section titled “Resource Types”All AWS resources that are supported by CloudFormation can be accessed through the AWS namespace. The resource types follow the CloudFormation naming convention:
AWS.S3.Bucket
forAWS::S3::Bucket
AWS.DynamoDB.Table
forAWS::DynamoDB::Table
AWS.Lambda.Function
forAWS::Lambda::Function
- And so on…
Resource Operations
Section titled “Resource Operations”Creating Resources
Section titled “Creating Resources”To create a resource, call the resource type function with an ID and configuration:
const table = await AWS.DynamoDB.Table("users-table", { TableName: "users", AttributeDefinitions: [ { AttributeName: "id", AttributeType: "S", }, ], KeySchema: [ { AttributeName: "id", KeyType: "HASH", }, ], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5, },});
Updating Resources
Section titled “Updating Resources”To update a resource, call the resource type function with the same ID and new configuration:
const updatedBucket = await AWS.S3.Bucket("my-bucket", { BucketName: "my-unique-bucket-name", VersioningConfiguration: { Status: "Suspended", // Change versioning status },});
Deleting Resources
Section titled “Deleting Resources”Resources are automatically deleted when they go out of scope or when the destroy()
function is called:
import { destroy } from "alchemy/destroy";
// Delete all resources in the scopeawait destroy(scope);
Advanced Usage
Section titled “Advanced Usage”Cross-Resource References
Section titled “Cross-Resource References”You can reference attributes from one resource in another:
// Create an IAM role for Lambdaconst role = await AWS.IAM.Role("lambda-role", { RoleName: "my-lambda-role", AssumeRolePolicyDocument: { Version: "2012-10-17", Statement: [ { Effect: "Allow", Principal: { Service: "lambda.amazonaws.com", }, Action: "sts:AssumeRole", }, ], },});
// Create a Lambda function using the roleconst func = await AWS.Lambda.Function("my-function", { FunctionName: "my-function", Role: role.Arn, // Reference the role's ARN Code: { ZipFile: "exports.handler = async () => ({ statusCode: 200 });", }, Handler: "index.handler", Runtime: "nodejs20.x",});
Error Handling
Section titled “Error Handling”The Cloud Control API client includes comprehensive error handling:
try { const bucket = await AWS.S3.Bucket("my-bucket", { BucketName: "invalid/name", // Invalid bucket name });} catch (error) { if (error.code === "ValidationException") { console.error("Invalid resource configuration:", error.message); } else if (error.retryable) { console.error("Transient error, retry the operation:", error.message); } else { console.error("Unexpected error:", error); }}
Configuration Options
Section titled “Configuration Options”You can configure the Cloud Control API client behavior:
import { createCloudControlClient } from "alchemy/aws/control/client";
const client = createCloudControlClient({ region: "us-west-2", // AWS region maxPollingAttempts: 60, // Maximum attempts for polling operations initialPollingDelay: 2000, // Initial delay between polling attempts (ms) maxPollingDelay: 20000, // Maximum delay between polling attempts (ms) operationTimeout: 600000, // Overall operation timeout (ms) maxRetries: 5, // Maximum retries for retryable errors});
Limitations and Known Issues
Section titled “Limitations and Known Issues”-
Resource Updates
- Not all resource properties can be updated after creation
- Some updates may require resource replacement
- Check the CloudFormation resource documentation for updateable properties
-
Operation Timing
- Resource creation and updates are asynchronous
- Operations may take several minutes to complete
- The client includes built-in polling with exponential backoff
-
API Limits
- Cloud Control API has service quotas and throttling limits
- The client includes automatic retries for throttling errors
- Consider implementing your own rate limiting for large-scale operations
Examples
Section titled “Examples”Complete S3 Static Website
Section titled “Complete S3 Static Website”import AWS from "alchemy/aws/control";import { alchemy } from "alchemy";
const app = alchemy.app(import.meta);
// Create a bucket for static website hostingconst bucket = await AWS.S3.Bucket("website", { BucketName: "my-static-website", WebsiteConfiguration: { IndexDocument: "index.html", ErrorDocument: "error.html", }, PublicAccessBlockConfiguration: { BlockPublicAcls: false, BlockPublicPolicy: false, IgnorePublicAcls: false, RestrictPublicBuckets: false, },});
// Add bucket policy for public read accessconst policy = await AWS.S3.BucketPolicy("website-policy", { Bucket: bucket.BucketName, PolicyDocument: { Version: "2012-10-17", Statement: [ { Effect: "Allow", Principal: "*", Action: "s3:GetObject", Resource: `${bucket.Arn}/*`, }, ], },});
console.log( "Website URL:", `http://${bucket.BucketName}.s3-website-${app.region}.amazonaws.com`);
DynamoDB Table with GSI
Section titled “DynamoDB Table with GSI”import AWS from "alchemy/aws/control";
const table = await AWS.DynamoDB.Table("users", { TableName: "users", AttributeDefinitions: [ { AttributeName: "id", AttributeType: "S" }, { AttributeName: "email", AttributeType: "S" }, { AttributeName: "createdAt", AttributeType: "N" }, ], KeySchema: [{ AttributeName: "id", KeyType: "HASH" }], GlobalSecondaryIndexes: [ { IndexName: "email-index", KeySchema: [{ AttributeName: "email", KeyType: "HASH" }], Projection: { ProjectionType: "ALL" }, ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5, }, }, { IndexName: "createdAt-index", KeySchema: [{ AttributeName: "createdAt", KeyType: "HASH" }], Projection: { ProjectionType: "KEYS_ONLY" }, ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5, }, }, ], ProvisionedThroughput: { ReadCapacityUnits: 5, WriteCapacityUnits: 5, }, StreamSpecification: { StreamEnabled: true, StreamViewType: "NEW_AND_OLD_IMAGES", },});
console.log("Table Stream ARN:", table.StreamArn);
Lambda Function with CloudWatch Logs
Section titled “Lambda Function with CloudWatch Logs”import AWS from "alchemy/aws/control";
// Create IAM role for Lambdaconst role = await AWS.IAM.Role("lambda-role", { RoleName: "my-lambda-role", AssumeRolePolicyDocument: { Version: "2012-10-17", Statement: [ { Effect: "Allow", Principal: { Service: "lambda.amazonaws.com", }, Action: "sts:AssumeRole", }, ], }, ManagedPolicyArns: [ "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", ],});
// Create CloudWatch log groupconst logGroup = await AWS.Logs.LogGroup("lambda-logs", { LogGroupName: "/aws/lambda/my-function", RetentionInDays: 14,});
// Create Lambda functionconst func = await AWS.Lambda.Function("my-function", { FunctionName: "my-function", Role: role.Arn, Code: { ZipFile: `exports.handler = async (event) => { console.log("Event:", JSON.stringify(event)); return { statusCode: 200, body: JSON.stringify({ message: "Hello from Lambda!" }), };}; `, }, Handler: "index.handler", Runtime: "nodejs20.x", Environment: { Variables: { LOG_LEVEL: "INFO", }, }, TracingConfig: { Mode: "Active", // Enable X-Ray tracing },});
console.log("Function ARN:", func.FunctionArn);
Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”-
Resource Creation Failures
try {const bucket = await AWS.S3.Bucket("my-bucket", config);} catch (error) {if (error.code === "ResourceConflict") {console.error("Resource already exists");} else if (error.code === "ValidationException") {console.error("Invalid configuration:", error.message);} else if (error.code === "AccessDeniedException") {console.error("Check IAM permissions");}} -
Operation Timeouts
const client = createCloudControlClient({maxPollingAttempts: 60, // Increase for slow operationsoperationTimeout: 900000, // 15 minutes}); -
Rate Limiting
// Implement your own rate limitingconst delay = (ms: number) =>new Promise((resolve) => setTimeout(resolve, ms));for (const config of resourceConfigs) {await delay(1000); // Wait 1 second between operationsawait AWS.S3.Bucket(`bucket-${config.id}`, config);}
Best Practices
Section titled “Best Practices”- Always use proper error handling
- Implement retries with exponential backoff
- Use resource IDs that are unique within your scope
- Clean up resources after tests
- Monitor operation progress and timeouts
- Use TypeScript for better type safety
- Keep resource configurations modular and reusable