From 4cdf554d1dde36b5d5771bc7cadfb4c8d36a3480 Mon Sep 17 00:00:00 2001 From: Oleksii Nikiforov Date: Tue, 11 Jun 2024 11:14:06 +0000 Subject: [PATCH 1/5] feat: Add Aspire Dashboard module #1190 --- .../.editorconfig | 1 + .../AspireDashboardBuilder.cs | 91 +++++++++++++++++++ .../AspireDashboardConfiguration.cs | 65 +++++++++++++ .../AspireDashboardContainer.cs | 43 +++++++++ .../Testcontainers.CosmosDb.csproj | 12 +++ src/Testcontainers.AspireDashboard/Usings.cs | 11 +++ 6 files changed, 223 insertions(+) create mode 100644 src/Testcontainers.AspireDashboard/.editorconfig create mode 100644 src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs create mode 100644 src/Testcontainers.AspireDashboard/AspireDashboardConfiguration.cs create mode 100644 src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs create mode 100644 src/Testcontainers.AspireDashboard/Testcontainers.CosmosDb.csproj create mode 100644 src/Testcontainers.AspireDashboard/Usings.cs diff --git a/src/Testcontainers.AspireDashboard/.editorconfig b/src/Testcontainers.AspireDashboard/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/src/Testcontainers.AspireDashboard/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs b/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs new file mode 100644 index 000000000..95163cf68 --- /dev/null +++ b/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs @@ -0,0 +1,91 @@ +namespace Testcontainers.AspireDashboard; + +/// +[PublicAPI] +public sealed class AspireDashboardBuilder : ContainerBuilder +{ + public const string AspireDashboardImage = "mcr.microsoft.com/dotnet/aspire-dashboard:latest"; + + public const ushort AspireDashboardPort = 18888; + + public const ushort AspireDashboardOtlpPort = 18889; + + /// + /// Initializes a new instance of the class. + /// + public AspireDashboardBuilder() + : this(new AspireDashboardConfiguration(false, false)) + { + DockerResourceConfiguration = Init().DockerResourceConfiguration; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + private AspireDashboardBuilder(AspireDashboardConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + DockerResourceConfiguration = resourceConfiguration; + } + + /// + /// Sets the AllowAnonymous mode. + /// + /// + /// A configured instance of . + public AspireDashboardBuilder AllowAnonymous(bool allowed) + { + return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(allowAnonymous: allowed)) + .WithEnvironment("DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS", allowed.ToString().ToLower()); + } + + /// + /// Sets the AllowAnonymous mode. + /// + /// + /// A configured instance of . + public AspireDashboardBuilder AllowUnsecuredTransport(bool allowed) + { + return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(allowUnsecuredTransport: allowed)) + .WithEnvironment("ASPIRE_ALLOW_UNSECURED_TRANSPORT", allowed.ToString().ToLower()); + } + + /// + protected override AspireDashboardConfiguration DockerResourceConfiguration { get; } + + /// + public override AspireDashboardContainer Build() + { + Validate(); + return new AspireDashboardContainer(DockerResourceConfiguration); + } + + /// + protected override AspireDashboardBuilder Init() + { + return base.Init() + .WithImage(AspireDashboardImage) + .WithPortBinding(AspireDashboardPort, AspireDashboardPort) + .WithPortBinding(AspireDashboardOtlpPort, true) + .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(r => r.ForPort(AspireDashboardPort))); + } + + /// + protected override AspireDashboardBuilder Clone(IResourceConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(resourceConfiguration)); + } + + /// + protected override AspireDashboardBuilder Clone(IContainerConfiguration resourceConfiguration) + { + return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(resourceConfiguration)); + } + + /// + protected override AspireDashboardBuilder Merge(AspireDashboardConfiguration oldValue, AspireDashboardConfiguration newValue) + { + return new AspireDashboardBuilder(new AspireDashboardConfiguration(oldValue, newValue)); + } +} \ No newline at end of file diff --git a/src/Testcontainers.AspireDashboard/AspireDashboardConfiguration.cs b/src/Testcontainers.AspireDashboard/AspireDashboardConfiguration.cs new file mode 100644 index 000000000..c7446b370 --- /dev/null +++ b/src/Testcontainers.AspireDashboard/AspireDashboardConfiguration.cs @@ -0,0 +1,65 @@ +namespace Testcontainers.AspireDashboard; + +/// +[PublicAPI] +public sealed class AspireDashboardConfiguration : ContainerConfiguration +{ + /// + /// Initializes a new instance of the class. + /// + public AspireDashboardConfiguration(bool allowAnonymous = false, bool allowUnsecuredTransport = false) + { + AllowAnonymous = allowAnonymous; + AllowUnsecuredTransport = allowUnsecuredTransport; + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public AspireDashboardConfiguration(IResourceConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public AspireDashboardConfiguration(IContainerConfiguration resourceConfiguration) + : base(resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The Docker resource configuration. + public AspireDashboardConfiguration(AspireDashboardConfiguration resourceConfiguration) + : this(new AspireDashboardConfiguration(), resourceConfiguration) + { + // Passes the configuration upwards to the base implementations to create an updated immutable copy. + } + + /// + /// Initializes a new instance of the class. + /// + /// The old Docker resource configuration. + /// The new Docker resource configuration. + public AspireDashboardConfiguration(AspireDashboardConfiguration oldValue, AspireDashboardConfiguration newValue) + : base(oldValue, newValue) + { + } + + /// + /// Gets AllowAnonymous mode. + /// + public bool AllowAnonymous { get; } + + /// + /// Gets AllowUnsecuredTransport mode. + /// + public bool AllowUnsecuredTransport { get; } +} \ No newline at end of file diff --git a/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs b/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs new file mode 100644 index 000000000..1daced392 --- /dev/null +++ b/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs @@ -0,0 +1,43 @@ +namespace Testcontainers.AspireDashboard; + +/// +[PublicAPI] +public sealed class AspireDashboardContainer : DockerContainer +{ + /// + /// Initializes a new instance of the class. + /// + /// The container configuration. + public AspireDashboardContainer(AspireDashboardConfiguration configuration) + : base(configuration) + { + } + + /// + /// Gets the AspireDashboard URL. + /// + /// The AspireDashboard URL. + public string GetDashboardUrl() + { + var endpoint = new UriBuilder( + Uri.UriSchemeHttp, + Hostname, + GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardPort)); + + return endpoint.ToString(); + } + + /// + /// Gets the AspireDashboard OTLP endpoint URL. + /// + /// The AspireDashboard OTLP endpoint URL. + public string GetOtlpEndpointUrl() + { + var endpoint = new UriBuilder( + Uri.UriSchemeHttp, + Hostname, + GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardOtlpPort)); + + return endpoint.ToString(); + } +} \ No newline at end of file diff --git a/src/Testcontainers.AspireDashboard/Testcontainers.CosmosDb.csproj b/src/Testcontainers.AspireDashboard/Testcontainers.CosmosDb.csproj new file mode 100644 index 000000000..8b2ed72c6 --- /dev/null +++ b/src/Testcontainers.AspireDashboard/Testcontainers.CosmosDb.csproj @@ -0,0 +1,12 @@ + + + net6.0;net8.0;netstandard2.0;netstandard2.1 + latest + + + + + + + + \ No newline at end of file diff --git a/src/Testcontainers.AspireDashboard/Usings.cs b/src/Testcontainers.AspireDashboard/Usings.cs new file mode 100644 index 000000000..8642a70f5 --- /dev/null +++ b/src/Testcontainers.AspireDashboard/Usings.cs @@ -0,0 +1,11 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Net.Http; +global using System.Threading; +global using System.Threading.Tasks; +global using Docker.DotNet.Models; +global using DotNet.Testcontainers.Builders; +global using DotNet.Testcontainers.Configurations; +global using DotNet.Testcontainers.Containers; +global using JetBrains.Annotations; \ No newline at end of file From b818522782b762663fee3f47295f86907f827970 Mon Sep 17 00:00:00 2001 From: Oleksii Nikiforov Date: Wed, 12 Jun 2024 06:22:50 +0000 Subject: [PATCH 2/5] Add tests --- Testcontainers.sln | 16 ++++++++- ... => Testcontainers.AspireDashboard.csproj} | 0 .../.editorconfig | 1 + .../AspireDashboardContainerTest.cs | 35 +++++++++++++++++++ ...estcontainers.AspireDashboard.Tests.csproj | 18 ++++++++++ .../Usings.cs | 6 ++++ 6 files changed, 75 insertions(+), 1 deletion(-) rename src/Testcontainers.AspireDashboard/{Testcontainers.CosmosDb.csproj => Testcontainers.AspireDashboard.csproj} (100%) create mode 100644 tests/Testcontainers.AspireDashboard.Tests/.editorconfig create mode 100644 tests/Testcontainers.AspireDashboard.Tests/AspireDashboardContainerTest.cs create mode 100644 tests/Testcontainers.AspireDashboard.Tests/Testcontainers.AspireDashboard.Tests.csproj create mode 100644 tests/Testcontainers.AspireDashboard.Tests/Usings.cs diff --git a/Testcontainers.sln b/Testcontainers.sln index e8c10a811..b80bc1012 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 @@ -199,6 +199,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AspireDashboard", "src\Testcontainers.AspireDashboard\Testcontainers.AspireDashboard.csproj", "{9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AspireDashboard.Tests", "tests\Testcontainers.AspireDashboard.Tests\Testcontainers.AspireDashboard.Tests.csproj", "{82F79BC0-5E4A-4380-82C5-8B6E3CF05958}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -580,6 +584,14 @@ Global {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Release|Any CPU.Build.0 = Release|Any CPU + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -675,5 +687,7 @@ Global {1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} EndGlobalSection EndGlobal diff --git a/src/Testcontainers.AspireDashboard/Testcontainers.CosmosDb.csproj b/src/Testcontainers.AspireDashboard/Testcontainers.AspireDashboard.csproj similarity index 100% rename from src/Testcontainers.AspireDashboard/Testcontainers.CosmosDb.csproj rename to src/Testcontainers.AspireDashboard/Testcontainers.AspireDashboard.csproj diff --git a/tests/Testcontainers.AspireDashboard.Tests/.editorconfig b/tests/Testcontainers.AspireDashboard.Tests/.editorconfig new file mode 100644 index 000000000..6f066619d --- /dev/null +++ b/tests/Testcontainers.AspireDashboard.Tests/.editorconfig @@ -0,0 +1 @@ +root = true \ No newline at end of file diff --git a/tests/Testcontainers.AspireDashboard.Tests/AspireDashboardContainerTest.cs b/tests/Testcontainers.AspireDashboard.Tests/AspireDashboardContainerTest.cs new file mode 100644 index 000000000..381ab9213 --- /dev/null +++ b/tests/Testcontainers.AspireDashboard.Tests/AspireDashboardContainerTest.cs @@ -0,0 +1,35 @@ +namespace Testcontainers.AspireDashboard; + +public sealed class AspireDashboardContainerTest : IAsyncLifetime +{ + private readonly AspireDashboardContainer _container = new AspireDashboardBuilder() + .AllowAnonymous(true) + .AllowUnsecuredTransport(false) + .Build(); + + public Task InitializeAsync() + { + return _container.StartAsync(); + } + + public Task DisposeAsync() + { + return _container.DisposeAsync().AsTask(); + } + + [Fact] + public async Task GetDashboardReturnsHttpStatusCodeOk() + { + // Given + using var httpClient = new HttpClient(); + + var address = new Uri(_container.GetDashboardUrl()); + httpClient.BaseAddress = address; + + // When + using var response = await httpClient.GetAsync("/"); + + // Then + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } +} \ No newline at end of file diff --git a/tests/Testcontainers.AspireDashboard.Tests/Testcontainers.AspireDashboard.Tests.csproj b/tests/Testcontainers.AspireDashboard.Tests/Testcontainers.AspireDashboard.Tests.csproj new file mode 100644 index 000000000..dd7b9ac30 --- /dev/null +++ b/tests/Testcontainers.AspireDashboard.Tests/Testcontainers.AspireDashboard.Tests.csproj @@ -0,0 +1,18 @@ + + + net8.0 + false + false + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Testcontainers.AspireDashboard.Tests/Usings.cs b/tests/Testcontainers.AspireDashboard.Tests/Usings.cs new file mode 100644 index 000000000..dc15b5753 --- /dev/null +++ b/tests/Testcontainers.AspireDashboard.Tests/Usings.cs @@ -0,0 +1,6 @@ +global using System; +global using System.Net; +global using System.Net.Http; +global using System.Threading.Tasks; +global using DotNet.Testcontainers.Commons; +global using Xunit; \ No newline at end of file From 2db71e12a050e2b09940662f8a4f1c46c2d6e230 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 22 Jun 2024 09:07:13 +0200 Subject: [PATCH 3/5] chore: Remove BOM, sort projects --- Testcontainers.sln | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Testcontainers.sln b/Testcontainers.sln index b80bc1012..5dc5dde7b 100644 --- a/Testcontainers.sln +++ b/Testcontainers.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 @@ -17,6 +17,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ActiveMq", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ArangoDb", "src\Testcontainers.ArangoDb\Testcontainers.ArangoDb.csproj", "{AB9C1563-07C7-4685-BACD-BB1FF64B3611}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AspireDashboard", "src\Testcontainers.AspireDashboard\Testcontainers.AspireDashboard.csproj", "{9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Azurite", "src\Testcontainers.Azurite\Testcontainers.Azurite.csproj", "{3F2E254F-C203-43FD-A078-DC3E2CBC0F9F}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.BigQuery", "src\Testcontainers.BigQuery\Testcontainers.BigQuery.csproj", "{A9FF9C7F-BBA0-4B44-90B7-48A60F9E00F3}" @@ -105,6 +107,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ActiveMq.Tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.ArangoDb.Tests", "tests\Testcontainers.ArangoDb.Tests\Testcontainers.ArangoDb.Tests.csproj", "{8E1E0A6D-EEBB-4455-B8E8-A55AF9B2062C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AspireDashboard.Tests", "tests\Testcontainers.AspireDashboard.Tests\Testcontainers.AspireDashboard.Tests.csproj", "{82F79BC0-5E4A-4380-82C5-8B6E3CF05958}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Azurite.Tests", "tests\Testcontainers.Azurite.Tests\Testcontainers.Azurite.Tests.csproj", "{B272FDDE-5E01-425D-B9E1-10FF883DDAAA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.BigQuery.Tests", "tests\Testcontainers.BigQuery.Tests\Testcontainers.BigQuery.Tests.csproj", "{03E60673-078A-4508-99AD-8537CE6F78F1}" @@ -199,10 +203,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.Tests", "tes EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.WebDriver.Tests", "tests\Testcontainers.WebDriver.Tests\Testcontainers.WebDriver.Tests.csproj", "{EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AspireDashboard", "src\Testcontainers.AspireDashboard\Testcontainers.AspireDashboard.csproj", "{9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Testcontainers.AspireDashboard.Tests", "tests\Testcontainers.AspireDashboard.Tests\Testcontainers.AspireDashboard.Tests.csproj", "{82F79BC0-5E4A-4380-82C5-8B6E3CF05958}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -220,6 +220,10 @@ Global {AB9C1563-07C7-4685-BACD-BB1FF64B3611}.Debug|Any CPU.Build.0 = Debug|Any CPU {AB9C1563-07C7-4685-BACD-BB1FF64B3611}.Release|Any CPU.ActiveCfg = Release|Any CPU {AB9C1563-07C7-4685-BACD-BB1FF64B3611}.Release|Any CPU.Build.0 = Release|Any CPU + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Release|Any CPU.Build.0 = Release|Any CPU {3F2E254F-C203-43FD-A078-DC3E2CBC0F9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3F2E254F-C203-43FD-A078-DC3E2CBC0F9F}.Debug|Any CPU.Build.0 = Debug|Any CPU {3F2E254F-C203-43FD-A078-DC3E2CBC0F9F}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -396,6 +400,10 @@ Global {8E1E0A6D-EEBB-4455-B8E8-A55AF9B2062C}.Debug|Any CPU.Build.0 = Debug|Any CPU {8E1E0A6D-EEBB-4455-B8E8-A55AF9B2062C}.Release|Any CPU.ActiveCfg = Release|Any CPU {8E1E0A6D-EEBB-4455-B8E8-A55AF9B2062C}.Release|Any CPU.Build.0 = Release|Any CPU + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Debug|Any CPU.Build.0 = Debug|Any CPU + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Release|Any CPU.ActiveCfg = Release|Any CPU + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Release|Any CPU.Build.0 = Release|Any CPU {B272FDDE-5E01-425D-B9E1-10FF883DDAAA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B272FDDE-5E01-425D-B9E1-10FF883DDAAA}.Debug|Any CPU.Build.0 = Debug|Any CPU {B272FDDE-5E01-425D-B9E1-10FF883DDAAA}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -584,18 +592,11 @@ Global {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2}.Release|Any CPU.Build.0 = Release|Any CPU - {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A}.Release|Any CPU.Build.0 = Release|Any CPU - {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82F79BC0-5E4A-4380-82C5-8B6E3CF05958}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {5365F780-0E6C-41F0-B1B9-7DC34368F80C} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {AB9C1563-07C7-4685-BACD-BB1FF64B3611} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} + {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {3F2E254F-C203-43FD-A078-DC3E2CBC0F9F} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {A9FF9C7F-BBA0-4B44-90B7-48A60F9E00F3} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {302EC1E0-AE75-4E99-A6BF-524F35338BC8} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} @@ -640,6 +641,7 @@ Global {EC76857B-A3B8-4B7A-A1B0-8D867A4D1733} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} {AB93C67F-0A53-4525-AE6C-29B065820ABE} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {8E1E0A6D-EEBB-4455-B8E8-A55AF9B2062C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} + {82F79BC0-5E4A-4380-82C5-8B6E3CF05958} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {B272FDDE-5E01-425D-B9E1-10FF883DDAAA} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {03E60673-078A-4508-99AD-8537CE6F78F1} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {2E7B92E3-8526-4706-90F3-00F0F5C47C37} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} @@ -687,7 +689,5 @@ Global {1A1983E6-5297-435F-B467-E8E1F11277D6} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {27CDB869-A150-4593-958F-6F26E5391E7C} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} {EBA72C3B-57D5-43FF-A5B4-3D55B3B6D4C2} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} - {9B8A4BDE-1D9C-48A0-A64A-C33FBC701E0A} = {673F23AE-7694-4BB9-ABD4-136D6C13634E} - {82F79BC0-5E4A-4380-82C5-8B6E3CF05958} = {7164F1FB-7F24-444A-ACD2-2C329C2B3CCF} EndGlobalSection EndGlobal From 6f10ad485143e4128a39f0b18a3fc463b5c3d897 Mon Sep 17 00:00:00 2001 From: Andre Hofmeister <9199345+HofmeisterAn@users.noreply.github.com> Date: Sat, 22 Jun 2024 09:24:44 +0200 Subject: [PATCH 4/5] chore: Align XML doc --- .../AspireDashboardBuilder.cs | 28 +++++++++---------- .../AspireDashboardContainer.cs | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs b/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs index 95163cf68..d4f1ad20b 100644 --- a/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs +++ b/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs @@ -6,7 +6,7 @@ public sealed class AspireDashboardBuilder : ContainerBuilder + protected override AspireDashboardConfiguration DockerResourceConfiguration { get; } + /// - /// Sets the AllowAnonymous mode. + /// Configures the dashboard to accept anonymous access. /// - /// - /// A configured instance of . + /// A value indicating whether anonymous access is allowed. + /// A configured instance of . public AspireDashboardBuilder AllowAnonymous(bool allowed) { return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(allowAnonymous: allowed)) - .WithEnvironment("DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS", allowed.ToString().ToLower()); + .WithEnvironment("DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS", allowed.ToString().ToLowerInvariant()); } /// - /// Sets the AllowAnonymous mode. + /// Configures the dashboard to allow unsecured transport. /// - /// - /// A configured instance of . + /// A value indicating whether unsecured transport is allowed. + /// A configured instance of . public AspireDashboardBuilder AllowUnsecuredTransport(bool allowed) { return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(allowUnsecuredTransport: allowed)) - .WithEnvironment("ASPIRE_ALLOW_UNSECURED_TRANSPORT", allowed.ToString().ToLower()); + .WithEnvironment("ASPIRE_ALLOW_UNSECURED_TRANSPORT", allowed.ToString().ToLowerInvariant()); } - /// - protected override AspireDashboardConfiguration DockerResourceConfiguration { get; } - /// public override AspireDashboardContainer Build() { @@ -66,9 +66,9 @@ protected override AspireDashboardBuilder Init() { return base.Init() .WithImage(AspireDashboardImage) - .WithPortBinding(AspireDashboardPort, AspireDashboardPort) + .WithPortBinding(AspireDashboardFrontendPort, AspireDashboardFrontendPort) .WithPortBinding(AspireDashboardOtlpPort, true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(r => r.ForPort(AspireDashboardPort))); + .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(r => r.ForPort(AspireDashboardFrontendPort))); } /// diff --git a/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs b/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs index 1daced392..871f3d2bb 100644 --- a/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs +++ b/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs @@ -22,7 +22,7 @@ public string GetDashboardUrl() var endpoint = new UriBuilder( Uri.UriSchemeHttp, Hostname, - GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardPort)); + GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardFrontendPort)); return endpoint.ToString(); } From 4bd053ba645900431b7462eec51500db503d0d17 Mon Sep 17 00:00:00 2001 From: Oleksii Nikiforov Date: Sat, 21 Sep 2024 14:31:34 +0300 Subject: [PATCH 5/5] fix: fixes after code review --- docs/modules/aspire-dashboard.md | 46 ++++++++++++++++ mkdocs.yml | 1 + .../AspireDashboardBuilder.cs | 55 ++++++++++++++----- .../AspireDashboardConfiguration.cs | 31 ++++------- .../AspireDashboardContainer.cs | 27 ++++++++- .../AspireDashboardContainerTest.cs | 6 +- 6 files changed, 127 insertions(+), 39 deletions(-) create mode 100644 docs/modules/aspire-dashboard.md diff --git a/docs/modules/aspire-dashboard.md b/docs/modules/aspire-dashboard.md new file mode 100644 index 000000000..087352cb3 --- /dev/null +++ b/docs/modules/aspire-dashboard.md @@ -0,0 +1,46 @@ +# Aspire Dashboard + +## Configuration + +The Aspire Dashboard can be configured in the following ways: + +- Use `AllowAnonymous(true)` to allow anonymous access to the dashboard. +- Use `AllowUnsecuredTransport` to allow unsecured transport, such as HTTP. +- Aspire Dashboard usually runs on port 18888. You can change the port by using the `WithPortBinding` + +```csharp +public sealed class AspireDashboardContainerTest : IAsyncLifetime +{ + private readonly AspireDashboardContainer _container = new AspireDashboardBuilder() + .AllowAnonymous(true) + .AllowUnsecuredTransport(false) + .WithPortBinding( + AspireDashboardBuilder.AspireDashboardFrontendPort, + AspireDashboardBuilder.AspireDashboardFrontendPort + ) + .Build(); + + public Task InitializeAsync() + { + return _container.StartAsync(); + } + + public Task DisposeAsync() + { + return _container.DisposeAsync().AsTask(); + } + + [Fact] + public async Task GetDashboardReturnsHttpStatusCodeOk() + { + using var httpClient = new HttpClient(); + var address = new Uri(_container.GetDashboardUrl()); + httpClient.BaseAddress = address; + + using var response = await httpClient.GetAsync("/"); + + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } +} +``` +[Aspire Dashboard on Microsoft Artifact Registry](https://mcr.microsoft.com/en-us/product/dotnet/aspire-dashboard/tags) diff --git a/mkdocs.yml b/mkdocs.yml index 8c43c04de..f6a5069af 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -47,3 +47,4 @@ nav: - modules/postgres.md - modules/pulsar.md - modules/rabbitmq.md + - modules/aspire-dashboard.md diff --git a/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs b/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs index d4f1ad20b..bac21cfc3 100644 --- a/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs +++ b/src/Testcontainers.AspireDashboard/AspireDashboardBuilder.cs @@ -2,9 +2,15 @@ namespace Testcontainers.AspireDashboard; /// [PublicAPI] -public sealed class AspireDashboardBuilder : ContainerBuilder +public sealed class AspireDashboardBuilder + : ContainerBuilder< + AspireDashboardBuilder, + AspireDashboardContainer, + AspireDashboardConfiguration + > { - public const string AspireDashboardImage = "mcr.microsoft.com/dotnet/aspire-dashboard:latest"; + // https://mcr.microsoft.com/en-us/product/dotnet/aspire-dashboard/tags + public const string AspireDashboardImage = "mcr.microsoft.com/dotnet/aspire-dashboard:8.1.0"; public const ushort AspireDashboardFrontendPort = 18888; @@ -14,7 +20,7 @@ public sealed class AspireDashboardBuilder : ContainerBuilder class. /// public AspireDashboardBuilder() - : this(new AspireDashboardConfiguration(false, false)) + : this(new AspireDashboardConfiguration()) { DockerResourceConfiguration = Init().DockerResourceConfiguration; } @@ -39,8 +45,11 @@ private AspireDashboardBuilder(AspireDashboardConfiguration resourceConfiguratio /// A configured instance of . public AspireDashboardBuilder AllowAnonymous(bool allowed) { - return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(allowAnonymous: allowed)) - .WithEnvironment("DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS", allowed.ToString().ToLowerInvariant()); + return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration()) + .WithEnvironment( + "DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS", + allowed.ToString().ToLowerInvariant() + ); } /// @@ -50,8 +59,11 @@ public AspireDashboardBuilder AllowAnonymous(bool allowed) /// A configured instance of . public AspireDashboardBuilder AllowUnsecuredTransport(bool allowed) { - return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(allowUnsecuredTransport: allowed)) - .WithEnvironment("ASPIRE_ALLOW_UNSECURED_TRANSPORT", allowed.ToString().ToLowerInvariant()); + return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration()) + .WithEnvironment( + "ASPIRE_ALLOW_UNSECURED_TRANSPORT", + allowed.ToString().ToLowerInvariant() + ); } /// @@ -66,26 +78,41 @@ protected override AspireDashboardBuilder Init() { return base.Init() .WithImage(AspireDashboardImage) - .WithPortBinding(AspireDashboardFrontendPort, AspireDashboardFrontendPort) + .WithPortBinding(AspireDashboardFrontendPort, true) .WithPortBinding(AspireDashboardOtlpPort, true) - .WithWaitStrategy(Wait.ForUnixContainer().UntilHttpRequestIsSucceeded(r => r.ForPort(AspireDashboardFrontendPort))); + .AllowAnonymous(true) + .WithWaitStrategy( + Wait.ForUnixContainer() + .UntilHttpRequestIsSucceeded(r => r.ForPort(AspireDashboardFrontendPort)) + ); } /// - protected override AspireDashboardBuilder Clone(IResourceConfiguration resourceConfiguration) + protected override AspireDashboardBuilder Clone( + IResourceConfiguration resourceConfiguration + ) { - return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(resourceConfiguration)); + return Merge( + DockerResourceConfiguration, + new AspireDashboardConfiguration(resourceConfiguration) + ); } /// protected override AspireDashboardBuilder Clone(IContainerConfiguration resourceConfiguration) { - return Merge(DockerResourceConfiguration, new AspireDashboardConfiguration(resourceConfiguration)); + return Merge( + DockerResourceConfiguration, + new AspireDashboardConfiguration(resourceConfiguration) + ); } /// - protected override AspireDashboardBuilder Merge(AspireDashboardConfiguration oldValue, AspireDashboardConfiguration newValue) + protected override AspireDashboardBuilder Merge( + AspireDashboardConfiguration oldValue, + AspireDashboardConfiguration newValue + ) { return new AspireDashboardBuilder(new AspireDashboardConfiguration(oldValue, newValue)); } -} \ No newline at end of file +} diff --git a/src/Testcontainers.AspireDashboard/AspireDashboardConfiguration.cs b/src/Testcontainers.AspireDashboard/AspireDashboardConfiguration.cs index c7446b370..91e162357 100644 --- a/src/Testcontainers.AspireDashboard/AspireDashboardConfiguration.cs +++ b/src/Testcontainers.AspireDashboard/AspireDashboardConfiguration.cs @@ -7,17 +7,15 @@ public sealed class AspireDashboardConfiguration : ContainerConfiguration /// /// Initializes a new instance of the class. /// - public AspireDashboardConfiguration(bool allowAnonymous = false, bool allowUnsecuredTransport = false) - { - AllowAnonymous = allowAnonymous; - AllowUnsecuredTransport = allowUnsecuredTransport; - } + public AspireDashboardConfiguration() { } /// /// Initializes a new instance of the class. /// /// The Docker resource configuration. - public AspireDashboardConfiguration(IResourceConfiguration resourceConfiguration) + public AspireDashboardConfiguration( + IResourceConfiguration resourceConfiguration + ) : base(resourceConfiguration) { // Passes the configuration upwards to the base implementations to create an updated immutable copy. @@ -48,18 +46,9 @@ public AspireDashboardConfiguration(AspireDashboardConfiguration resourceConfigu /// /// The old Docker resource configuration. /// The new Docker resource configuration. - public AspireDashboardConfiguration(AspireDashboardConfiguration oldValue, AspireDashboardConfiguration newValue) - : base(oldValue, newValue) - { - } - - /// - /// Gets AllowAnonymous mode. - /// - public bool AllowAnonymous { get; } - - /// - /// Gets AllowUnsecuredTransport mode. - /// - public bool AllowUnsecuredTransport { get; } -} \ No newline at end of file + public AspireDashboardConfiguration( + AspireDashboardConfiguration oldValue, + AspireDashboardConfiguration newValue + ) + : base(oldValue, newValue) { } +} diff --git a/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs b/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs index 871f3d2bb..cc9cd6e36 100644 --- a/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs +++ b/src/Testcontainers.AspireDashboard/AspireDashboardContainer.cs @@ -1,3 +1,5 @@ +using Microsoft.Extensions.Logging; + namespace Testcontainers.AspireDashboard; /// @@ -11,6 +13,23 @@ public sealed class AspireDashboardContainer : DockerContainer public AspireDashboardContainer(AspireDashboardConfiguration configuration) : base(configuration) { + Started += (_, _) => Logger.LogInformation("AspireDashboard container is ready!"); + Logger.LogInformation( + "Dashboard available at {Url}.", + new UriBuilder( + Uri.UriSchemeHttp, + Hostname, + GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardFrontendPort) + ) + ); + Logger.LogInformation( + "OTLP endpoint available at {Url}.", + new UriBuilder( + Uri.UriSchemeHttp, + Hostname, + GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardOtlpPort) + ) + ); } /// @@ -22,7 +41,8 @@ public string GetDashboardUrl() var endpoint = new UriBuilder( Uri.UriSchemeHttp, Hostname, - GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardFrontendPort)); + GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardFrontendPort) + ); return endpoint.ToString(); } @@ -36,8 +56,9 @@ public string GetOtlpEndpointUrl() var endpoint = new UriBuilder( Uri.UriSchemeHttp, Hostname, - GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardOtlpPort)); + GetMappedPublicPort(AspireDashboardBuilder.AspireDashboardOtlpPort) + ); return endpoint.ToString(); } -} \ No newline at end of file +} diff --git a/tests/Testcontainers.AspireDashboard.Tests/AspireDashboardContainerTest.cs b/tests/Testcontainers.AspireDashboard.Tests/AspireDashboardContainerTest.cs index 381ab9213..13f0c53fa 100644 --- a/tests/Testcontainers.AspireDashboard.Tests/AspireDashboardContainerTest.cs +++ b/tests/Testcontainers.AspireDashboard.Tests/AspireDashboardContainerTest.cs @@ -5,6 +5,10 @@ public sealed class AspireDashboardContainerTest : IAsyncLifetime private readonly AspireDashboardContainer _container = new AspireDashboardBuilder() .AllowAnonymous(true) .AllowUnsecuredTransport(false) + .WithPortBinding( + AspireDashboardBuilder.AspireDashboardFrontendPort, + AspireDashboardBuilder.AspireDashboardFrontendPort + ) .Build(); public Task InitializeAsync() @@ -32,4 +36,4 @@ public async Task GetDashboardReturnsHttpStatusCodeOk() // Then Assert.Equal(HttpStatusCode.OK, response.StatusCode); } -} \ No newline at end of file +}