New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add AWS CDK support #2225
base: main
Are you sure you want to change the base?
Add AWS CDK support #2225
Conversation
I like where this is heading! Have you explored the idea of synthesizing the stack from pieces of the Aspire app model? I believe that would be the ideal user experience, since it unifies the two worlds (Aspire app model & CDK stack) |
@ReubenBond thank you for the feedback! Yes, I started with experimenting of building the stack within Aspire in this branch. My first focus will be to rebase this PR on top of what @normj is merging into the Aspire main branch and add the AddConstruct method to the StackResource builder. It is sort of already supported for the outputs. It basically adds a CfnOutput construct to the stack. Currently I'm working on the asset deploying (S3, Docker Images) as it is needed to complete the functionality of CDK deployments. I like the idea to provide some out-of-the-box resources like a Bucket, DynamoDB Table or SQS Queue it we can easily do this with a couple of extensions methods that add the constructs to the stack underneath. |
44d93ad
to
832d206
Compare
Ok, after some busy weeks, I have picked up this PR again. First step is a rebase onto the work of @normj with the CloudFormation support. I have a made a couple of changes their to improve re-usability of the CloudFormation provisioner. |
b5aaa0e
to
6b49f82
Compare
Building a CDK Stack has been made easier by using Construct resources. I still need to annotate the resource states correctly, but in general it works like this... var stack = builder.AddAWSCDKStack("Stack").WithReference(awsConfig);
var table = stack.AddConstruct("Table", scope => new Table(scope, "Table", new TableProps
{
PartitionKey = new Attribute { Name = "id", Type = AttributeType.STRING },
BillingMode = BillingMode.PAY_PER_REQUEST,
RemovalPolicy = RemovalPolicy.DESTROY
})).WithOutput("TableName", c => c.TableName);
builder.AddProject<Projects.WebApp>("webapp")
.WithReference(table)
.WithReference(awsConfig); |
Next steps could be to compose list of most used resources and make convenient methods for these resources like; var stack = builder.AddAWSCDKStack("Stack").WithReference(awsConfig);
var table = stack.AddDynamoDBTable("Table");
var queue = stack.AddSQSQueue("Queue");
builder.AddProject<Projects.WebApp>("webapp")
.WithReference(table)
.WithReference(queue)
.WithReference(awsConfig); |
I suggest looking at the azure package layering as it should be very similar to what AWS needs to do. |
Seems redundant to pass the same config twice cc @normj |
Are you suggesting that if a project is referencing a CloudFormation / CDK construct that the project should infer the AWS SDK Config from the CloudFormation / CDK construct and pass that configuration to the projects? There could be cases later on for AWS SDK config to have a different configuration when configuration is extended for things like retry settings and timeouts but I can see allowing that fallback mechanism if a project doesn't have an explicit SDK reference. |
Yep. Exactly this. The stack has the config in the fallback case. |
3ce2ff6
to
9293b8e
Compare
Getting closer to building AWS CDK stacks. // Create a Distributed Application builder and enable AWS CDK, needed to host the App construct and attach the stack(s)
var builder = DistributedApplication.CreateBuilder(args).WithAWSCDK();
// Setup a configuration for the AWS .NET SDK.
var awsConfig = builder.AddAWSSDKConfig()
.WithProfile("default")
.WithRegion(RegionEndpoint.EUWest1);
// Create Stack
var stack = builder.AddStack("Stack").WithReference(awsConfig);
// Add DynamoDB Table to stack
var table = stack
.AddDynamoDBTable("Table", new TableProps
{
PartitionKey = new Attribute { Name = "id", Type = AttributeType.STRING },
BillingMode = BillingMode.PAY_PER_REQUEST,
RemovalPolicy = RemovalPolicy.DESTROY
})
.AddGlobalSecondaryIndex(new GlobalSecondaryIndexProps {
IndexName = "OwnerIndex",
PartitionKey = new Attribute { Name = "owner", Type = AttributeType.STRING },
SortKey = new Attribute { Name = "ownerSK", Type = AttributeType.STRING },
ProjectionType = ProjectionType.ALL
});
// Create project
builder.AddProject<Projects.WebApp>("webapp")
.WithReference(table); // Reference DynamoDB Table |
First of all, I'm greatly looking forward to this. I've myself experimenting with Aspire and AWS CDK. Separate ProjectOne thing that comes to mind after your most recent commit. Would it not be better to have the CDK parts remains as a separate project that references ProvisioningI'm agree with @ReubenBond in that having the AppHost project both be a CDK Application and AppHost is the ideal user experience, at least for those of us who are already comfortable using the CLI. I currently don't see any issues with these two approaches living side-by-side. My own implementation (fork of your fork) can be summarized as the following: I create a var builder = DistributedApplication.CreateBuilder(args)
// AwsConfig is used to load outputs from the created stacks
.WithAWSCDKCLI(awsConfig => awsConfig.WithProfile("default").WithRegion(RegionEndpoint.EUWest1));
var stack = new Stack(builder.App, "MyStack");
builder.AddStack(stack).SetDefaultStack(stack);
var bucket = new Bucket(stack, "Bucket");
var topic = new Topic(stack, "Topic");
builder.AddProject<Projects.MyApp>("app")
.WithReference(builder.AddAWSSDKConfig().WithProfile("default").WithRegion(RegionEndpoint.EUWest1))
// All ${Token[TOKEN.*]} are converted to a CfnOutput and uses the "default stack" as scope
.WithEnvironment("BUCKET_NAME", bucket.BucketName)
// Also possible to explicitly provide a scope for the CfnOutput
.WithEnvironment(topic, "TOPIC_ARN", topic.TopicArn)
// References previously created CfnOutput based on same TOKEN to prevent duplicate outputs
.WithEnvironment(bucket, "BUCKET_NAME_AGAIN", bucket.BucketName);
// synthesis occurs when the CLI is executing and then returns before builder.Build() runs
builder.SynthOrBuildAndRun(); |
@djonser thank you for your feedback and mentioning some important points.
This is for me still up for debate. At on one hand we would like to have an easy/out-of-the-box experience when you want to use AWS with .NET Aspire which make it easy to discover the available functionalities and API's. The CloudFormation Stack support is foundational for CDK and distributing them over multiple packages might make it harder to pick what is needed for your application. Especially if we would like to introduce convenient resource extensions like
The current implementation support both the Aspire local development provisioning as provisioning the stack using
Unfortunately with this deployment option you only deploy a part of your stack. The Project resources aren't taken into account yet and needs to be deployed differently when you would to publish your project. We are considering to leverage the AWS .NET Deployment Tools for this as Azure uses I love the
|
My thinking was if other CloudFormation abstractions that is not CDK-based wanted to provide an Aspire package they could use the Maybe I'm in the minority here having created a bunch of extensions and auto-completion seemingly always wants to pick the wrong
It really is the convenience extensions and inference that can be created when AWS CDK and Aspire is used together that is exciting. So much that could be derived from the shared context and reduce the amount of explicitly required configuration. I've been experimenting with a little bit of everything and most recently extensions for Cognito. One thing I'm starting to believe is that if I'm to create constructs via inference (and re-using Aspire resource name as either a construct name or a construct name-prefix) is that it would be good to have a way to manage/track constructs in a way that does not require creation of Aspire Resources. Regardless of Aspire parent resource the resource name must be unique. CloudFormation have less constraints for the resource name than Aspire have for resource names and having to specify both an Aspire resource name and Construct Id is inconvenient. var userPoolId = stack.Node.TryGetContext("@acme/userPoolId");
var userPool = builder.ImportUserPoolById(stack, "UserPool", userPoolId, new UserPoolDefaults(/**/));
var api = builder.AddProject<Projects.Api>("api")
.AsResourceServer("https://localhost", ["read", "write"])
// Or with explicit scope and prefix for created constructs
.AsResourceServer(scope, "idPrefix", "https://localhost", ["read", "write"], name: "api");
builder.AddProject<Projects.Worker>("worker")
.AuthorizeAccess(api, ["read"]); // Component wires up the Api Project.
builder.AddCognitoUserPoolAuthentication();
// ServiceDefaults used by Worker implements HttpClient parts.
builder.AddServiceDefaults(); |
7401923
to
b97fd81
Compare
@normj |
This pull request is intended to gather feedback for supporting AWS CDK with Aspire. It is build upon the work @normj is doing for general AWS with CloudFormation support (#1905 Add AWS CloudFormation Provisioning and SDK Configuration).
AWS CDK helps developers to write Infrastructure as Code using imperative programming languages like (C#, JavaScript/TypeScript, Python, Java, Go). In contrast with using the AWS SDK, CDK synthesized the code to CloudFormation which describes the desired state of the infrastructure and uses CloudFormation to deploy the resources.
This PR contains initial support for CDK stacks written with C#. A developer can still write their infrastructure using a CDK project and use the CDK CLI to deploy this to their AWS account. One of the challenges developers usually have if to leverage AWS services like SQS & DynamoDB in their local development environment. Tools to simulate these services locally exists, but are usually lacking features. Aspire allows the developer to deploy their CDK stacks and reference them to their application projects. Aspire will provision the resources and hookup the projects with the output values from the stack.
AWS CDK Project
Using CDK with .NET requires you to create a console application. This application contains Stacks which contains AWS resources from S3 Buckets, DynamoDB Tables, Lambda Functions to entire Kubernetes Clusters.
A stack can create
CfnOutput
output resources to expose attribute values of resources to the outside or expose them through an interface/property. This allows other stacks to reference resources for their configurations.In the example below you can see the CDK project that allows you to deploy the stacks using the CDK cli (
cdk deploy
)Aspire Stack Resource
In order to make Aspire to work with CDK we introduce a
StackResource
which allows you to create a stack using aStackBuilderDelegate
. The reason to create the stack delayed through a delegate is because Aspire is going to take ownership of the stack and provisioning and needs to additional resources for the outputs. Outputs can be defined using aStackOutputAnnotation
which will allows you to point to the resource attribute in stack. When the stack get referenced by theProjectResource
/IResourceWithEnviroment
it will take the output values and inject them as environment variables, just like the CloudFormationResource does.As an alternative, developers are also able to create stacks from scratch and don't need to reference a CDK project to create AWS resources like S3 buckets as demonstrated in this example. You can create stacks completely with Aspire.
Notes
Aspire.Hosting.AWS.CDK
isn't strong named as CDK and JSII aren't strong names as well. In order to fix this, additional effort have to be taken for JSII and CDK.Microsoft Reviewers: Open in CodeFlow