Skip to content
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 CloudFormation Provisioning and SDK Configuration #1905

Merged
merged 53 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
b79ba3f
Add CloudFormation provisioner
normj Nov 29, 2023
61ea393
Combine work to AWS.Hosting.AWS project
normj Dec 14, 2023
c4724b9
Add ability to configure the CloudFormation service client used for p…
normj Dec 16, 2023
bc5cbac
Add methods to configure service clients
normj Dec 16, 2023
1b3af50
Fix AWS Blazor sample app to enable InteractiveServer
normj Dec 17, 2023
839a68a
Rework SDK configuration as an IAWSSDKConfigResource
normj Dec 18, 2023
e1fc1cb
Merge branch 'main' into normj/aws-provisioning
normj Jan 26, 2024
708c32e
Rework AWS sample application to display the configuration applied fr…
normj Jan 26, 2024
3a9159b
Add the AWS .NET SDK OTel provider
normj Jan 27, 2024
56c12f7
Add more comments about what is going on in AppHost
normj Jan 27, 2024
a21c441
Add missing AddAWSService call for SNS
normj Jan 27, 2024
ff67ca4
Fail CloudFormation provisioning of stack is already in an "in progre…
normj Jan 27, 2024
88bbe5b
Minor cleanup
normj Jan 27, 2024
162b3bf
Fix whitespace
normj Jan 27, 2024
7a1c8f9
Move AWS sample to playground
normj Jan 27, 2024
73b6761
Address some of the PR comments.
normj Jan 29, 2024
53494e5
Address PR comments
normj Jan 31, 2024
12468ae
Add support for importing existing AWS resources using the AddAWSClou…
normj Feb 2, 2024
b8a5e7e
Merge branch 'main' into normj/aws-provisioning
normj Feb 2, 2024
ee17fa2
Fix after latest merge from main
normj Feb 2, 2024
411d32a
Add CloudFormation to manifest
normj Feb 2, 2024
46e4917
Add ability to set input parameters to CloudFormation template
normj Feb 3, 2024
b28f102
Lifecycle skips work if publishing manifest
normj Feb 3, 2024
dd93d1f
Add "aws." prefix to the AWS resources in the manifest
normj Feb 3, 2024
f9093ab
Rename WithParameter to AddParameter
normj Feb 3, 2024
f67fb5f
Merge branch 'main' into normj/aws-provisioning
normj Feb 24, 2024
c9f4744
Fixes after merging preview 4
normj Feb 24, 2024
b72ea36
Merge branch 'main' into normj/aws-provisioning
normj Mar 3, 2024
21b0f89
Publish logs and state to dashboard while doing CLoudFormation provis…
normj Mar 5, 2024
0b39eea
Block projects from starting till CloudFormation is done.
normj Mar 5, 2024
5198bcc
Fix issue with resource state being set for the initial state.
normj Mar 5, 2024
40a590d
Switch to use update state instead of replace state pattern.
normj Mar 5, 2024
46ec9d1
Ran VS code cleanup to clean up the formatting
normj Mar 6, 2024
106f5a1
Add logging message when project is waiting on CloudFormation stack.
normj Mar 6, 2024
a316cf7
Set source property for resource dashboard
normj Mar 6, 2024
8f242c4
Add ability to bind a single output variable to a project.
normj Mar 6, 2024
9c1756b
Fix source not being set when using AddAWSCloudFormationStack for bin…
normj Mar 6, 2024
4cc515a
Delete orphaned files
normj Mar 6, 2024
2ecd5ba
Merge branch 'main' into normj/aws-provisioning
normj Mar 6, 2024
b8dc8d3
Clean up adding manifest callbacks and rename AddParameter to WithPar…
normj Mar 6, 2024
e4cd5bb
Address some CR comments
normj Mar 7, 2024
8e33322
Replace the CloudFormation reference resource with an Annotation
normj Mar 7, 2024
dae3f4a
Add SDK configuration
normj Mar 7, 2024
b3470f1
Merge branch 'main' into normj/aws-provisioning
normj Mar 8, 2024
2ef50f1
Refactor the logic for making the provisioning calls to CloudFormation.
normj Mar 9, 2024
79f6f6b
Update project description and package tags
normj Mar 9, 2024
dc4cf58
Add unit tests
normj Mar 9, 2024
e2947f8
Test cleanup
normj Mar 9, 2024
ba86f69
Update the AWS dependencies in the Directory.Packages.props
normj Mar 13, 2024
39907b5
Remove unnecessary Prometheus dependency
eerhardt Mar 13, 2024
3492fcd
Merge remote-tracking branch 'upstream/main' into normj/aws-provisioning
eerhardt Mar 13, 2024
9ac29a0
Fix build for latest code.
eerhardt Mar 13, 2024
eaea240
Fix AWS tests
normj Mar 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/Aspire.Hosting.AWS/Aspire.Hosting.AWS.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,9 @@
<PackageReference Include="AWSSDK.Core" />
<PackageReference Include="AWSSDK.CloudFormation" />
</ItemGroup>


<ItemGroup>
<InternalsVisibleTo Include="Aspire.Hosting.Tests" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ internal sealed class CloudFormationStackResource(string name) : CloudFormationR
internal void WriteToManifest(ManifestPublishingContext context)
{
context.Writer.WriteString("type", "aws.cloudformation.stack.v0");
context.Writer.TryWriteString("stack-name", context.GetManifestRelativePath(Name));
context.Writer.TryWriteString("stack-name", Name);

context.Writer.WritePropertyName("references");
context.Writer.WriteStartArray();
foreach (var cloudFormationResource in Annotations.OfType<CloudFormationReferenceAnnotation>())
{
context.Writer.WriteStartObject();
context.Writer.WriteString("TargetResource", cloudFormationResource.TargetResource);
context.Writer.WriteString("target-resource", cloudFormationResource.TargetResource);
context.Writer.WriteEndObject();
}
context.Writer.WriteEndArray();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ public ICloudFormationTemplateResource AddParameter(string parameterName, string
internal void WriteToManifest(ManifestPublishingContext context)
{
context.Writer.WriteString("type", "aws.cloudformation.template.v0");
context.Writer.TryWriteString("stack-name", context.GetManifestRelativePath(Name));
context.Writer.TryWriteString("stack-name", Name);
context.Writer.TryWriteString("template-path", context.GetManifestRelativePath(TemplatePath));

context.Writer.WritePropertyName("references");
context.Writer.WriteStartArray();
foreach (var cloudFormationResource in Annotations.OfType<CloudFormationReferenceAnnotation>())
{
context.Writer.WriteStartObject();
context.Writer.WriteString("TargetResource", cloudFormationResource.TargetResource);
context.Writer.WriteString("target-resource", cloudFormationResource.TargetResource);
context.Writer.WriteEndObject();
}
context.Writer.WriteEndArray();
Expand Down
113 changes: 113 additions & 0 deletions tests/Aspire.Hosting.Tests/AWS/AWSCloudFormationResourceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json.Nodes;
using Amazon;
using Aspire.Hosting.AWS.CloudFormation;
using Aspire.Hosting.Utils;
using Xunit;

namespace Aspire.Hosting.Tests.AWS;
public class AWSCloudFormationResourceTests
{
[Fact]
public void AddAWSCloudFormationStackResourceTest()
{
var builder = DistributedApplication.CreateBuilder();

var awsSdkConfig = builder.AddAWSSDKConfig()
.WithRegion(RegionEndpoint.USWest2)
.WithProfile("test-profile");

var resource = builder.AddAWSCloudFormationStack("ExistingStack")
.WithReference(awsSdkConfig)
.Resource;

Assert.Equal("ExistingStack", resource.Name);
Assert.NotNull(resource.AWSSDKConfig);
Assert.Equal(RegionEndpoint.USWest2, resource.AWSSDKConfig.Region);
Assert.Equal("test-profile", resource.AWSSDKConfig.Profile);
}

[Fact]
public void AddAWSCloudFormationTemplateResourceTest()
{
var builder = DistributedApplication.CreateBuilder();

var awsSdkConfig = builder.AddAWSSDKConfig()
.WithRegion(RegionEndpoint.USWest2)
.WithProfile("test-profile");

var resource = builder.AddAWSCloudFormationTemplate("NewStack", "cf.template")
.WithParameter("key1", "value1")
.WithParameter("key2", "value2")
.WithReference(awsSdkConfig)
.Resource as CloudFormationTemplateResource;

Assert.NotNull(resource);
Assert.Equal("NewStack", resource.Name);
Assert.Equal("cf.template", resource.TemplatePath);
Assert.NotNull(resource.AWSSDKConfig);
Assert.Equal(RegionEndpoint.USWest2, resource.AWSSDKConfig.Region);
Assert.Equal("test-profile", resource.AWSSDKConfig.Profile);

Assert.Equal(2, resource.CloudFormationParameters.Count);
Assert.Equal("value1", resource.CloudFormationParameters["key1"]);
Assert.Equal("value2", resource.CloudFormationParameters["key2"]);
}

[Fact]
public void ManifestAWSCloudFormationStackResourceTest()
{
var builder = DistributedApplication.CreateBuilder();

var resourceBuilder = builder.AddAWSCloudFormationStack("ExistingStack");

var projectBuilder = builder.AddProject<Projects.ServiceA>("serviceA")
.WithReference(resourceBuilder);

var resource = resourceBuilder.Resource as CloudFormationStackResource;
Assert.NotNull(resource);
var obj = ManifestUtils.GetManifest(resource.WriteToManifest);

Assert.NotNull(obj);
Assert.Equal("aws.cloudformation.stack.v0", obj["type"]?.ToString());
normj marked this conversation as resolved.
Show resolved Hide resolved
Assert.Equal("ExistingStack", obj["stack-name"]?.ToString());

var references = obj["references"] as JsonArray;
Assert.NotNull(references);
Assert.Single(references);

var ref1 = references[0];
Assert.NotNull(ref1);
Assert.Equal("serviceA", ref1["target-resource"]?.ToString());
}

[Fact]
public void ManifestAWSCloudFormationTemplateResourceTest()
{
var builder = DistributedApplication.CreateBuilder();

var resourceBuilder = builder.AddAWSCloudFormationTemplate("NewStack", "cf.template");

var projectBuilder = builder.AddProject<Projects.ServiceA>("serviceA")
.WithReference(resourceBuilder);

var resource = resourceBuilder.Resource as CloudFormationTemplateResource;
Assert.NotNull(resource);
var obj = ManifestUtils.GetManifest(resource.WriteToManifest);

Assert.NotNull(obj);
Assert.Equal("aws.cloudformation.template.v0", obj["type"]?.ToString());
Assert.Equal("NewStack", obj["stack-name"]?.ToString());
Assert.Equal("net8.0/cf.template", obj["template-path"]?.ToString());

var references = obj["references"] as JsonArray;
Assert.NotNull(references);
Assert.Single(references);

var ref1 = references[0];
Assert.NotNull(ref1);
Assert.Equal("serviceA", ref1["target-resource"]?.ToString());
}
}
76 changes: 76 additions & 0 deletions tests/Aspire.Hosting.Tests/AWS/StackOutputReferenceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Amazon.CloudFormation.Model;
using Aspire.Hosting.AWS.CloudFormation;
using Xunit;

namespace Aspire.Hosting.Tests.AWS;
public class StackOutputReferenceTests
{
[Fact]
public async Task GetValueAsyncTest()
{
var builder = DistributedApplication.CreateBuilder();

var resourceBuilder = builder.AddAWSCloudFormationTemplate("NewStack", "cf.template");

var resource = resourceBuilder.Resource as CloudFormationTemplateResource;
Assert.NotNull(resource);

resource.Outputs = new List<Output>
{
new Output{OutputKey = "key1", OutputValue = "value1"}
};

resource.ProvisioningTaskCompletionSource = new TaskCompletionSource();

var reference = resourceBuilder.GetOutput("key1");
Assert.Equal("key1", reference.Name);

var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(2));
await Assert.ThrowsAsync<TaskCanceledException>(() => reference.GetValueAsync(cancellationSource.Token).AsTask());

resource.ProvisioningTaskCompletionSource.TrySetResult();
var value = await reference.GetValueAsync(cancellationSource.Token);
Assert.Equal("value1", value);
}

[Fact]
public void ValueExpressionTest()
{
var builder = DistributedApplication.CreateBuilder();

var resourceBuilder = builder.AddAWSCloudFormationTemplate("NewStack", "cf.template");

var resource = resourceBuilder.Resource as CloudFormationTemplateResource;
Assert.NotNull(resource);

resource.Outputs = new List<Output>
{
new Output{OutputKey = "key1", OutputValue = "value1"}
};

var reference = resourceBuilder.GetOutput("key1");
Assert.Equal("{NewStack.output.key1}", reference.ValueExpression);
}

[Fact]
public void InvalidOutputKey()
{
var builder = DistributedApplication.CreateBuilder();

var resourceBuilder = builder.AddAWSCloudFormationTemplate("NewStack", "cf.template");

var resource = resourceBuilder.Resource as CloudFormationTemplateResource;
Assert.NotNull(resource);

resource.Outputs = new List<Output>
{
new Output{OutputKey = "key1", OutputValue = "value1"}
normj marked this conversation as resolved.
Show resolved Hide resolved
};

var reference = resourceBuilder.GetOutput("not-found");
Assert.Throws<System.InvalidOperationException>(() => reference.Value);
}
}
1 change: 1 addition & 0 deletions tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
Expand All @@ -9,6 +9,7 @@

<ItemGroup>
<ProjectReference Include="..\..\src\Aspire.Hosting\Aspire.Hosting.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.AWS\Aspire.Hosting.AWS.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\src\Aspire.Hosting.Azure\Aspire.Hosting.Azure.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\testproject\TestProject.AppHost\TestProject.AppHost.csproj" IsAspireProjectResource="false" />

Expand Down
Loading