Skip to content

Commit

Permalink
SWA emulator component
Browse files Browse the repository at this point in the history
  • Loading branch information
aaronpowell committed Jul 16, 2024
1 parent c3b960a commit ea1d998
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 7 deletions.
16 changes: 15 additions & 1 deletion CommunityToolkit.Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{414151D4-700
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Aspire.Hosting.Java", "src\CommunityToolkit.Aspire.Hosting.Java\CommunityToolkit.Aspire.Hosting.Java.csproj", "{DAA67050-44B3-458F-9818-5877D606866A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps", "src\CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps\CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps.csproj", "{125DFA83-328D-4F8B-91EC-3057FFF410BE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{8519CC01-1370-47C8-AD94-B0F326B1563F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "java", "java", "{F120E99A-FB2A-4C3B-B588-9B7ED1CD4E8A}"
Expand All @@ -19,10 +21,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Aspire.Jav
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Aspire.Java.ServiceDefaults", "examples\java\CommunityToolkit.Aspire.Java.ServiceDefaults\CommunityToolkit.Aspire.Java.ServiceDefaults.csproj", "{BBAFA814-1026-4A39-AA28-BCAE3951A224}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{899F0713-7FC6-4750-BAFC-AC650B35B453}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{899F0713-7FC6-4750-BAFC-AC650B35B453}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommunityToolkit.Aspire.Java.Hosting.EndToEndTests", "test\CommunityToolkit.Aspire.Java.Hosting.EndToEndTests\CommunityToolkit.Aspire.Java.Hosting.EndToEndTests.csproj", "{E2905A71-E25D-46FC-A128-D6A366D4D751}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps.Tests", "test\CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps.Tests\CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps.Tests.csproj", "{1B55E682-B518-4E59-8972-07C82ED5A677}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -53,6 +57,14 @@ Global
{E2905A71-E25D-46FC-A128-D6A366D4D751}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2905A71-E25D-46FC-A128-D6A366D4D751}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2905A71-E25D-46FC-A128-D6A366D4D751}.Release|Any CPU.Build.0 = Release|Any CPU
{125DFA83-328D-4F8B-91EC-3057FFF410BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{125DFA83-328D-4F8B-91EC-3057FFF410BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{125DFA83-328D-4F8B-91EC-3057FFF410BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{125DFA83-328D-4F8B-91EC-3057FFF410BE}.Release|Any CPU.Build.0 = Release|Any CPU
{1B55E682-B518-4E59-8972-07C82ED5A677}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1B55E682-B518-4E59-8972-07C82ED5A677}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1B55E682-B518-4E59-8972-07C82ED5A677}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1B55E682-B518-4E59-8972-07C82ED5A677}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -65,6 +77,8 @@ Global
{55AC8E7E-DA0A-489D-8059-4E7190C9EEBF} = {F120E99A-FB2A-4C3B-B588-9B7ED1CD4E8A}
{BBAFA814-1026-4A39-AA28-BCAE3951A224} = {F120E99A-FB2A-4C3B-B588-9B7ED1CD4E8A}
{E2905A71-E25D-46FC-A128-D6A366D4D751} = {899F0713-7FC6-4750-BAFC-AC650B35B453}
{125DFA83-328D-4F8B-91EC-3057FFF410BE} = {414151D4-7009-4E78-A5C6-D99EBD1E67D1}
{1B55E682-B518-4E59-8972-07C82ED5A677} = {899F0713-7FC6-4750-BAFC-AC650B35B453}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {08B1D4B8-D2C5-4A64-BB8B-E1A2B29525F0}
Expand Down
5 changes: 0 additions & 5 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,22 @@
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>

<ItemGroup>
<!-- Aspire packages -->
<PackageVersion Include="Aspire.Hosting" Version="$(AspireVersion)" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireVersion)" />

<!-- AspNetCore packages -->
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="$(AspNetCoreVersion)" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.6.2" />

<!-- .NET packages -->
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="8.7.0" />
<PackageVersion Include="Microsoft.Extensions.ServiceDiscovery" Version="8.0.2" />

<!-- OpenTelemetry packages -->
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="$(OpenTelemetryVersion)" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="$(OpenTelemetryVersion)" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="$(OpenTelemetryVersion)" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="$(OpenTelemetryVersion)" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="$(OpenTelemetryVersion)" />

<!-- Testing packages -->
<PackageVersion Include="Aspire.Hosting.Testing" Version="$(AspireVersion)" />
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Aspire.Hosting" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Aspire.Hosting.ApplicationModel;

namespace CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps;

public class SwaApiEndpointAnnotation(IResourceBuilder<IResourceWithEndpoints> resource) : IResourceAnnotation
{
public string Endpoint => resource.Resource.GetEndpoint("http").Url;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Aspire.Hosting.ApplicationModel;

namespace CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps;

public class SwaAppEndpointAnnotation(IResourceBuilder<IResourceWithEndpoints> resource) : IResourceAnnotation
{
public string Endpoint => resource.Resource.GetEndpoint("http").Url;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;

namespace CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps;

public static class SwaAppHostingExtension
{
/// <summary>
/// Adds a Static Web Apps emulator to the application.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/> to add the resource to.</param>
/// <param name="name">The name of the resource.</param>
/// <param name="options">The <see cref="JavaAppContainerResourceOptions"/> to configure the Java application.</param>"
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
/// <remarks>This resource will not be included in the published manifest.</remarks>
public static IResourceBuilder<SwaResource> AddSwaEmulator(this IDistributedApplicationBuilder builder, string name, int port = 4280)
{
var resource = new SwaResource(name, Environment.CurrentDirectory);
return builder.AddResource(resource)
.WithHttpEndpoint(isProxied: false, port: port)
.WithArgs(ctx =>
{
ctx.Args.Add("start");

if (resource.TryGetAnnotationsOfType<SwaAppEndpointAnnotation>(out var appResource))
{
ctx.Args.Add("--app-devserver-url");
ctx.Args.Add(appResource.First().Endpoint);
}

if (resource.TryGetAnnotationsOfType<SwaApiEndpointAnnotation>(out var apiResource))
{
ctx.Args.Add("--api-devserver-url");
ctx.Args.Add(apiResource.First().Endpoint);
}

ctx.Args.Add("--port");
ctx.Args.Add(port.ToString());
})
.ExcludeFromManifest();
}

/// <summary>
/// Registers the application resource with the Static Web Apps emulator.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/> to add the resource to.</param>
/// <param name="appResource">The existing <see cref="IResourceBuilder{IResourceWithEndpoint}"/> to use as the <c>--app-devserver-url</c> argument.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SwaResource> WithAppResource(this IResourceBuilder<SwaResource> builder, IResourceBuilder<IResourceWithEndpoints> appResource) =>
builder.WithAnnotation<SwaAppEndpointAnnotation>(new(appResource), ResourceAnnotationMutationBehavior.Replace);

/// <summary>
/// Registers the API resource with the Static Web Apps emulator.
/// </summary>
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/> to add the resource to.</param>
/// <param name="apiResource">The existing <see cref="IResourceBuilder{IResourceWithEndpoint}"/> to use as the <c>--api-devserver-url</c> argument.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<SwaResource> WithApiResource(this IResourceBuilder<SwaResource> builder, IResourceBuilder<IResourceWithEndpoints> apiResource) =>
builder.WithAnnotation<SwaApiEndpointAnnotation>(new(apiResource), ResourceAnnotationMutationBehavior.Replace);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
using Aspire.Hosting.ApplicationModel;

namespace CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps;

public class SwaResource(string name, string workingDirectory) : ExecutableResource(name, "swa", workingDirectory)
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
</ItemGroup>

<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps\CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using Aspire.Hosting;
using Aspire.Hosting.ApplicationModel;
using Microsoft.Extensions.DependencyInjection;

namespace CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps.Tests;

public class ResourceCreationTests
{
[Fact]
public void TargetPort_Defaults_to_4280()
{
var builder = DistributedApplication.CreateBuilder();

builder.AddSwaEmulator("swa");

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var resource = appModel.Resources.OfType<SwaResource>().SingleOrDefault();

Assert.NotNull(resource);

Assert.Equal("swa", resource.Name);

var httpEndpoint = resource.GetEndpoint("http");
Assert.Equal(4280, httpEndpoint.TargetPort);
}

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

builder.AddSwaEmulator("swa", port: 1234);

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var resource = appModel.Resources.OfType<SwaResource>().SingleOrDefault();

Assert.NotNull(resource);

Assert.Equal("swa", resource.Name);

var httpEndpoint = resource.GetEndpoint("http");
Assert.Equal(1234, httpEndpoint.TargetPort);
}

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

var appResource = builder
.AddContainer("app", "test/container") // container image doesn't need to be valid as we aren't actually running it
.WithHttpEndpoint();

builder.AddSwaEmulator("swa")
.WithAppResource(appResource);

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var resource = appModel.Resources.OfType<SwaResource>().SingleOrDefault();

Assert.NotNull(resource);

Assert.Equal("swa", resource.Name);

var result = resource.TryGetAnnotationsOfType<SwaAppEndpointAnnotation>(out var appResources);

Assert.True(result);
Assert.NotNull(appResources);
Assert.Single(appResources);
}

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

var apiResource = builder
.AddContainer("api", "test/container") // container image doesn't need to be valid as we aren't actually running it
.WithHttpEndpoint();

builder.AddSwaEmulator("swa")
.WithApiResource(apiResource);

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var resource = appModel.Resources.OfType<SwaResource>().SingleOrDefault();

Assert.NotNull(resource);

Assert.Equal("swa", resource.Name);

var result = resource.TryGetAnnotationsOfType<SwaApiEndpointAnnotation>(out var apiResources);

Assert.True(result);
Assert.NotNull(apiResources);

Assert.Single(apiResources);
}

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

builder.AddSwaEmulator("swa");

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var resource = appModel.Resources.OfType<SwaResource>().SingleOrDefault();

Assert.NotNull(resource);

Assert.Equal("swa", resource.Name);

var result = resource.TryGetAnnotationsOfType<CommandLineArgsCallbackAnnotation>(out var annotations);

Assert.True(result);
Assert.NotNull(annotations);

Assert.Single(annotations);

var annotation = annotations.Single();

List<object> args = [];
var ctx = new CommandLineArgsCallbackContext(args);

annotation.Callback(ctx);

Assert.Contains("start", args);
}

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

builder.AddSwaEmulator("swa");

using var app = builder.Build();

var appModel = app.Services.GetRequiredService<DistributedApplicationModel>();

var resource = appModel.Resources.OfType<SwaResource>().SingleOrDefault();

Assert.NotNull(resource);

Assert.Equal("swa", resource.Name);

var result = resource.TryGetAnnotationsOfType<CommandLineArgsCallbackAnnotation>(out var annotations);

Assert.True(result);
Assert.NotNull(annotations);

Assert.Single(annotations);

var annotation = annotations.Single();

List<object> args = [];
var ctx = new CommandLineArgsCallbackContext(args);

annotation.Callback(ctx);

Assert.Contains("--port", args);
Assert.Contains("4280", args);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
Expand Down
11 changes: 11 additions & 0 deletions test/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project>
<Import Project="..\Directory.Build.props" />

<ItemGroup>
<PackageReference Include="Aspire.Hosting.Testing" />
</ItemGroup>

<ItemGroup>
<Using Include="Aspire.Hosting.Testing" />
</ItemGroup>
</Project>

0 comments on commit ea1d998

Please sign in to comment.