diff --git a/CommunityToolkit.Aspire.sln b/CommunityToolkit.Aspire.sln index 10ba15dc..670ea438 100644 --- a/CommunityToolkit.Aspire.sln +++ b/CommunityToolkit.Aspire.sln @@ -325,6 +325,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "redis-ext", "redis-ext", "{ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests", "tests\CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests\CommunityToolkit.Aspire.Hosting.Redis.Extensions.Tests.csproj", "{958A251E-9D2A-4D67-9D1C-47D6A9E36B3A}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "mailpit", "mailpit", "{734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit", "src\CommunityToolkit.Aspire.Hosting.MailPit\CommunityToolkit.Aspire.Hosting.MailPit.csproj", "{73E7BC19-A626-4C55-990B-A878BF84D4CA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit.Tests", "tests\CommunityToolkit.Aspire.Hosting.MailPit.Tests\CommunityToolkit.Aspire.Hosting.MailPit.Tests.csproj", "{9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults", "examples\mailpit\CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults\CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults.csproj", "{08EDD2B6-FFC1-42F9-AE62-479178A381FF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi", "examples\mailpit\CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi\CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.csproj", "{547ED5DB-CD15-42B8-99C7-30274C5D843E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityToolkit.Aspire.Hosting.MailPit.AppHost", "examples\mailpit\CommunityToolkit.Aspire.Hosting.MailPit.AppHost\CommunityToolkit.Aspire.Hosting.MailPit.AppHost.csproj", "{69343427-D50D-4600-ADC0-C1B1910C0287}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -855,6 +867,26 @@ Global {958A251E-9D2A-4D67-9D1C-47D6A9E36B3A}.Debug|Any CPU.Build.0 = Debug|Any CPU {958A251E-9D2A-4D67-9D1C-47D6A9E36B3A}.Release|Any CPU.ActiveCfg = Release|Any CPU {958A251E-9D2A-4D67-9D1C-47D6A9E36B3A}.Release|Any CPU.Build.0 = Release|Any CPU + {73E7BC19-A626-4C55-990B-A878BF84D4CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {73E7BC19-A626-4C55-990B-A878BF84D4CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {73E7BC19-A626-4C55-990B-A878BF84D4CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {73E7BC19-A626-4C55-990B-A878BF84D4CA}.Release|Any CPU.Build.0 = Release|Any CPU + {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94}.Release|Any CPU.Build.0 = Release|Any CPU + {08EDD2B6-FFC1-42F9-AE62-479178A381FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08EDD2B6-FFC1-42F9-AE62-479178A381FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08EDD2B6-FFC1-42F9-AE62-479178A381FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08EDD2B6-FFC1-42F9-AE62-479178A381FF}.Release|Any CPU.Build.0 = Release|Any CPU + {547ED5DB-CD15-42B8-99C7-30274C5D843E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {547ED5DB-CD15-42B8-99C7-30274C5D843E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {547ED5DB-CD15-42B8-99C7-30274C5D843E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {547ED5DB-CD15-42B8-99C7-30274C5D843E}.Release|Any CPU.Build.0 = Release|Any CPU + {69343427-D50D-4600-ADC0-C1B1910C0287}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69343427-D50D-4600-ADC0-C1B1910C0287}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69343427-D50D-4600-ADC0-C1B1910C0287}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69343427-D50D-4600-ADC0-C1B1910C0287}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1018,6 +1050,12 @@ Global {CB491C52-A3D5-4F36-97C4-75C482F90A30} = {734EF69D-EE4D-4E9B-96BD-A44E53C5D1EC} {734EF69D-EE4D-4E9B-96BD-A44E53C5D1EC} = {8519CC01-1370-47C8-AD94-B0F326B1563F} {958A251E-9D2A-4D67-9D1C-47D6A9E36B3A} = {899F0713-7FC6-4750-BAFC-AC650B35B453} + {73E7BC19-A626-4C55-990B-A878BF84D4CA} = {414151D4-7009-4E78-A5C6-D99EBD1E67D1} + {9C66ECFF-8077-4D5A-9CEB-6E9AB6CCBE94} = {899F0713-7FC6-4750-BAFC-AC650B35B453} + {734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC} = {8519CC01-1370-47C8-AD94-B0F326B1563F} + {08EDD2B6-FFC1-42F9-AE62-479178A381FF} = {734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC} + {547ED5DB-CD15-42B8-99C7-30274C5D843E} = {734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC} + {69343427-D50D-4600-ADC0-C1B1910C0287} = {734EF69D-EE4D-4E9B-96BD-A44E53C5D2EC} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {08B1D4B8-D2C5-4A64-BB8B-E1A2B29525F0} diff --git a/README.md b/README.md index b5ba88a0..e812d532 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ All features are contributed by you, our amazing .NET community, and maintained This repository contains the source code for the .NET Aspire Community Toolkit, a collection of community created Integrations and extensions for [.NET Aspire](https://aka.ms/dotnet/aspire). | Package | Description | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | - **Learn More**: [`Hosting.Azure.StaticWebApps`][swa-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps][swa-shields]][swa-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Azure.StaticWebApps][swa-shields-preview]][swa-nuget-preview] | A hosting integration for the [Azure Static Web Apps emulator](https://learn.microsoft.com/azure/static-web-apps/static-web-apps-cli-overview) (Note: this does not support deployment of a project to Azure Static Web Apps). | | - **Learn More**: [`Hosting.Golang`][golang-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields]][golang-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields-preview]][golang-nuget-preview] | A hosting integration Golang apps. | -| **Learn More**: [`Hosting.Java`][java-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields]][java-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields-preview]][java-nuget-preview] | An integration for running Java code in .NET Aspire either using the local JDK or using a container. | +| - **Learn More**: [`Hosting.Java`][java-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields]][java-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields-preview]][java-nuget-preview] | An integration for running Java code in .NET Aspire either using the local JDK or using a container. | | - **Learn More**: [`Hosting.NodeJS.Extensions`][nodejs-ext-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.NodeJS.Extensions][nodejs-ext-shields]][nodejs-ext-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.NodeJS.Extensions][nodejs-ext-shields-preview]][nodejs-ext-nuget-preview] | An integration that contains some additional extensions for running Node.js applications | | - **Learn More**: [`Hosting.Ollama`][ollama-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Ollama][ollama-shields]][ollama-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Ollama][ollama-shields-preview]][ollama-nuget-preview] | An Aspire hosting integration leveraging the [Ollama](https://ollama.com) container with support for downloading a model on startup. | | - **Learn More**: [`OllamaSharp`][ollama-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields]][ollamasharp-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields-preview]][ollama-nuget-preview] | An Aspire client integration for the [OllamaSharp](https://github.com/awaescher/OllamaSharp) package. | @@ -36,13 +36,15 @@ This repository contains the source code for the .NET Aspire Community Toolkit, | - **Learn More**: [`Microsoft.EntityFrameworkCore.Sqlite`][sqlite-ef-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields]][sqlite-ef-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Microsoft.EntityFrameworkCore.Sqlite][sqlite-ef-shields-preview]][sqlite-ef-nuget-preview] | An Aspire client integration for the Microsoft.EntityFrameworkCore.Sqlite NuGet package. | | - **Learn More**: [`Hosting.Dapr`][dapr-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields]][dapr-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Dapr][dapr-shields-preview]][dapr-nuget-preview] | An Aspire hosting integration for Dapr. | | - **Learn More**: [`Hosting.Dapr.AzureRedis`][dapr-azureredis-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis][dapr-azureredis-shields]][dapr-azureredis-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Dapr.AzureRedis][dapr-azureredis-shields-preview]][dapr-azureredis-nuget-preview] | An extension for the Dapr hosting integration for using Dapr with Azure Redis cache. | -| - **Learn More**: [`Hosting.RavenDB`][ravendb-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields]][ravendb-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields-preview]][ravendb-nuget-preview] | An Aspire integration leveraging the [RavenDB](https://ravendb.net/) container. | -| - **Learn More**: [`RavenDB.Client`][ravendb-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields]][ravendb-client-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields-preview]][ravendb-client-nuget-preview] | An Aspire client integration for the [RavenDB.Client](https://www.nuget.org/packages/RavenDB.client) package. | +| - **Learn More**: [`Hosting.RavenDB`][ravendb-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields]][ravendb-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.RavenDB][ravendb-shields-preview]][ravendb-nuget-preview] | An Aspire integration leveraging the [RavenDB](https://ravendb.net/) container. | +| - **Learn More**: [`RavenDB.Client`][ravendb-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields]][ravendb-client-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.RavenDB.Client][ravendb-client-shields-preview]][ravendb-client-nuget-preview] | An Aspire client integration for the [RavenDB.Client](https://www.nuget.org/packages/RavenDB.client) package. | | - **Learn More**: [`Hosting.GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields]][go-feature-flag-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.GoFeatureFlag][go-feature-flag-shields-preview]][go-feature-flag-nuget-preview] | An Aspire hosting integration leveraging the [GoFeatureFlag](https://gofeatureflag.org/) container. | | - **Learn More**: [`GoFeatureFlag`][go-feature-flag-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields]][go-feature-flag-client-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.GoFeatureFlag][go-feature-flag-client-shields-preview]][go-feature-flag-client-nuget-preview] | An Aspire client integration for the [GoFeatureFlag](https://github.com/open-feature/dotnet-sdk-contrib/tree/main/src/OpenFeature.Contrib.Providers.GOFeatureFlag) package. | -| - **Learn More**: [`Hosting.MongoDB.Extensions`][mongodb-ext-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.MongoDB.Extensions][mongodb-ext-shields]][mongodb-ext-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.MongoDB.Extensions][mongodb-ext-shields-preview]][mongodb-ext-nuget-preview] | An integration that contains some additional extensions for hosting MongoDB container. | -| - **Learn More**: [`Hosting.PostgreSQL.Extensions`][postgres-ext-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.PostgreSQL.Extensions][postgres-ext-shields]][postgres-ext-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions][postgres-ext-shields-preview]][postgres-ext-nuget-preview] | An integration that contains some additional extensions for hosting PostgreSQL container. | -| - **Learn More**: [`Hosting.Redis.Extensions`][redis-ext-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Redis.Extensions][redis-ext-shields]][redis-ext-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Redis.Extensions][redis-ext-shields-preview]][redis-ext-nuget-preview] | An integration that contains some additional extensions for hosting Redis container. | +| - **Learn More**: [`Hosting.MongoDB.Extensions`][mongodb-ext-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.MongoDB.Extensions][mongodb-ext-shields]][mongodb-ext-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.MongoDB.Extensions][mongodb-ext-shields-preview]][mongodb-ext-nuget-preview] | An integration that contains some additional extensions for hosting MongoDB container. | +| - **Learn More**: [`Hosting.PostgreSQL.Extensions`][postgres-ext-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.PostgreSQL.Extensions][postgres-ext-shields]][postgres-ext-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.PostgreSQL.Extensions][postgres-ext-shields-preview]][postgres-ext-nuget-preview] | An integration that contains some additional extensions for hosting PostgreSQL container. | +| - **Learn More**: [`Hosting.Redis.Extensions`][redis-ext-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Redis.Extensions][redis-ext-shields]][redis-ext-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.Redis.Extensions][redis-ext-shields-preview]][redis-ext-nuget-preview] | An integration that contains some additional extensions for hosting Redis container. | +| - **Learn More**: [`Hosting.MailPit`][mailpit-ext-integration-docs]
- Stable πŸ“¦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields]][mailpit-ext-nuget]
- Preview πŸ“¦: [![CommunityToolkit.Aspire.Hosting.MailPit][mailpit-ext-shields-preview]][mailpit-ext-nuget-preview] | An Aspire integration leveraging the [MailPit](https://mailpit.axllent.org/) container. | + ## πŸ™Œ Getting Started @@ -221,4 +223,9 @@ This project is supported by the [.NET Foundation](https://dotnetfoundation.org) [redis-ext-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.Redis.Extensions [redis-ext-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Redis.Extensions/ [redis-ext-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.Redis.Extensions?label=nuget%20(preview) -[redis-ext-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Redis.Extensions/absoluteLatest \ No newline at end of file +[redis-ext-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Redis.Extensions/absoluteLatest +[mailpit-ext-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-mailpit +[mailpit-ext-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.MailPit +[mailpit-ext-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.MailPit/ +[mailpit-ext-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.MailPit?label=nuget%20(preview) +[mailpit-ext-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.MailPit/absoluteLatest \ No newline at end of file diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/CommunityToolkit.Aspire.Hosting.MailPit.AppHost.csproj b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/CommunityToolkit.Aspire.Hosting.MailPit.AppHost.csproj new file mode 100644 index 00000000..edf88d7e --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/CommunityToolkit.Aspire.Hosting.MailPit.AppHost.csproj @@ -0,0 +1,23 @@ + + + + + + Exe + enable + enable + true + f60c6ce9-5628-467c-a6fc-2fc7938b39ad + + + + + + + + + + + + + diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/Program.cs b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/Program.cs new file mode 100644 index 00000000..5c2945b4 --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/Program.cs @@ -0,0 +1,11 @@ +using Projects; + +var builder = DistributedApplication.CreateBuilder(args); + +var mailPit = builder.AddMailPit("mailpit"); + +var sendmail = builder.AddProject("sendmail") + .WithReference(mailPit) + .WaitFor(mailPit); + +builder.Build().Run(); diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/appsettings.json b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/appsettings.json new file mode 100644 index 00000000..31c092aa --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.csproj b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.csproj new file mode 100644 index 00000000..fa3ea638 --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.csproj @@ -0,0 +1,16 @@ + + + + enable + enable + + + + + + + + + + + diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.http b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.http new file mode 100644 index 00000000..fe6ae012 --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi.http @@ -0,0 +1,13 @@ +@CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi_HostAddress = http://localhost:5075 + +POST http://localhost:5075/send +Accept: application/json +Content-Type: application/json + +{ + "From": "test@test.nl", + "To": "toTest@test.nl", + "Body": "Hello World", + "Subject": "Test" +} +### diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/MailData.cs b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/MailData.cs new file mode 100644 index 00000000..c0f08d8a --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/MailData.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; + +namespace CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi; + +public class MailData +{ + [Required] + public required string From { get; set; } + [Required] + public required string To { get; set; } + [Required] + public required string Body { get; set; } + [Required] + public required string Subject { get; set; } +} \ No newline at end of file diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/Program.cs b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/Program.cs new file mode 100644 index 00000000..8e879689 --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/Program.cs @@ -0,0 +1,44 @@ +using CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi; +using Microsoft.AspNetCore.Mvc; +using System.Data.Common; +using System.Net.Mail; + +WebApplicationBuilder builder = WebApplication.CreateBuilder(args); + +string? mailPitConnectionString = builder.Configuration.GetConnectionString("mailpit"); +DbConnectionStringBuilder connectionBuilder = new() +{ + ConnectionString = mailPitConnectionString +}; + +Uri endpoint = new(connectionBuilder["Endpoint"].ToString()!, UriKind.Absolute); +builder.Services.AddScoped(_ => new SmtpClient(endpoint.Host, endpoint.Port)); +builder.AddServiceDefaults(); +WebApplication app = builder.Build(); + +app.MapPost("/send", ([FromBody]MailData mailData, [FromServices] SmtpClient smtpClient) => + { + MailMessage myMail = CreateMailMessage(mailData); + smtpClient.Send(myMail); + }) +.WithName("SendMail"); + +app.MapGet("/health", () => "OK"); + +app.MapDefaultEndpoints(); +app.Run(); +return; + +MailMessage CreateMailMessage(MailData mailData) +{ + MailAddress from = new(mailData.From, "TestFromName"); + MailAddress to = new(mailData.To, "TestToName"); + MailMessage mailMessage = new(from, to); + MailAddress replyTo = new(mailData.From); + mailMessage.ReplyToList.Add(replyTo); + mailMessage.Subject = mailData.Subject; + mailMessage.SubjectEncoding = System.Text.Encoding.UTF8; + mailMessage.Body = mailData.Body; + mailMessage.BodyEncoding = System.Text.Encoding.UTF8; + return mailMessage; +} \ No newline at end of file diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/appsettings.json b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.SendMailApi/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults.csproj b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults.csproj new file mode 100644 index 00000000..caa6344d --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults.csproj @@ -0,0 +1,21 @@ + + + + enable + enable + true + + + + + + + + + + + + + + + diff --git a/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/Extensions.cs b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/Extensions.cs new file mode 100644 index 00000000..8cf2bbd1 --- /dev/null +++ b/examples/mailpit/CommunityToolkit.Aspire.Hosting.MailPit.ServiceDefaults/Extensions.cs @@ -0,0 +1,118 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Diagnostics.HealthChecks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; +using Microsoft.Extensions.Logging; +using OpenTelemetry; +using OpenTelemetry.Metrics; +using OpenTelemetry.Trace; + +namespace Microsoft.Extensions.Hosting; + +// Adds common .NET Aspire services: service discovery, resilience, health checks, and OpenTelemetry. +// This project should be referenced by each service project in your solution. +// To learn more about using this project, see https://aka.ms/dotnet/aspire/service-defaults +public static class Extensions +{ + public static TBuilder AddServiceDefaults(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.ConfigureOpenTelemetry(); + + builder.AddDefaultHealthChecks(); + + builder.Services.AddServiceDiscovery(); + + builder.Services.ConfigureHttpClientDefaults(http => + { + // Turn on resilience by default + http.AddStandardResilienceHandler(); + + // Turn on service discovery by default + http.AddServiceDiscovery(); + }); + + // Uncomment the following to restrict the allowed schemes for service discovery. + // builder.Services.Configure(options => + // { + // options.AllowedSchemes = ["https"]; + // }); + + return builder; + } + + public static TBuilder ConfigureOpenTelemetry(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Logging.AddOpenTelemetry(logging => + { + logging.IncludeFormattedMessage = true; + logging.IncludeScopes = true; + }); + + builder.Services.AddOpenTelemetry() + .WithMetrics(metrics => + { + metrics.AddAspNetCoreInstrumentation() + .AddHttpClientInstrumentation() + .AddRuntimeInstrumentation(); + }) + .WithTracing(tracing => + { + tracing.AddSource(builder.Environment.ApplicationName) + .AddAspNetCoreInstrumentation() + // Uncomment the following line to enable gRPC instrumentation (requires the OpenTelemetry.Instrumentation.GrpcNetClient package) + //.AddGrpcClientInstrumentation() + .AddHttpClientInstrumentation(); + }); + + builder.AddOpenTelemetryExporters(); + + return builder; + } + + private static TBuilder AddOpenTelemetryExporters(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + var useOtlpExporter = !string.IsNullOrWhiteSpace(builder.Configuration["OTEL_EXPORTER_OTLP_ENDPOINT"]); + + if (useOtlpExporter) + { + builder.Services.AddOpenTelemetry().UseOtlpExporter(); + } + + // Uncomment the following lines to enable the Azure Monitor exporter (requires the Azure.Monitor.OpenTelemetry.AspNetCore package) + //if (!string.IsNullOrEmpty(builder.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"])) + //{ + // builder.Services.AddOpenTelemetry() + // .UseAzureMonitor(); + //} + + return builder; + } + + public static TBuilder AddDefaultHealthChecks(this TBuilder builder) where TBuilder : IHostApplicationBuilder + { + builder.Services.AddHealthChecks() + // Add a default liveness check to ensure app is responsive + .AddCheck("self", () => HealthCheckResult.Healthy(), ["live"]); + + return builder; + } + + public static WebApplication MapDefaultEndpoints(this WebApplication app) + { + // Adding health checks endpoints to applications in non-development environments has security implications. + // See https://aka.ms/dotnet/aspire/healthchecks for details before enabling these endpoints in non-development environments. + if (app.Environment.IsDevelopment()) + { + // All health checks must pass for app to be considered ready to accept traffic after starting + app.MapHealthChecks("/health"); + + // Only health checks tagged with the "live" tag must pass for app to be considered alive + app.MapHealthChecks("/alive", new HealthCheckOptions + { + Predicate = r => r.Tags.Contains("live") + }); + } + + return app; + } +} diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/CommunityToolkit.Aspire.Hosting.MailPit.csproj b/src/CommunityToolkit.Aspire.Hosting.MailPit/CommunityToolkit.Aspire.Hosting.MailPit.csproj new file mode 100644 index 00000000..068e607a --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/CommunityToolkit.Aspire.Hosting.MailPit.csproj @@ -0,0 +1,15 @@ +ο»Ώ + + + An Aspire component leveraging the MailPit container. + mailpit smtp hosting + + + + + + + + + + diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerImageTags.cs b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerImageTags.cs new file mode 100644 index 00000000..bee66efe --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerImageTags.cs @@ -0,0 +1,13 @@ +namespace CommunityToolkit.Aspire.Hosting.MailPit; + +internal static class MailPitContainerImageTags +{ + /// docker.io + public const string Registry = "docker.io"; + + /// axllent/mailpit + public const string Image = "axllent/mailpit"; + + /// v1.22.3 + public const string Tag = "v1.22.3"; +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerResource.cs b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerResource.cs new file mode 100644 index 00000000..63534cf4 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitContainerResource.cs @@ -0,0 +1,23 @@ +ο»Ώnamespace Aspire.Hosting.ApplicationModel; + +/// +/// Resource for the MailPit server. +/// +/// +public class MailPitContainerResource(string name) : ContainerResource(name), IResourceWithConnectionString +{ + internal const int HttpEndpointPort = 8025; + internal const int SmtpEndpointPort = 1025; + internal const string SmtpEndpointName = "smtp"; + internal const string HttpEndpointName = "http"; + internal const string DatabaseEnvVar = "MP_DATABASE"; + + private EndpointReference? _smtpEndpoint; + private EndpointReference SmtpEndpoint => _smtpEndpoint ??= new EndpointReference(this, SmtpEndpointName); + + /// + /// ConnectionString for MailPit smtp endpoint in the form of smtp://host:port. + /// + public ReferenceExpression ConnectionStringExpression => ReferenceExpression.Create( + $"Endpoint={SmtpEndpoint.Scheme}://{SmtpEndpoint.Property(EndpointProperty.Host)}:{SmtpEndpoint.Property(EndpointProperty.Port)}"); +} diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitHostingExtension.cs b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitHostingExtension.cs new file mode 100644 index 00000000..c87eb1bd --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/MailPitHostingExtension.cs @@ -0,0 +1,81 @@ +ο»Ώusing Aspire.Hosting.ApplicationModel; +using CommunityToolkit.Aspire.Hosting.MailPit; + +namespace Aspire.Hosting; + +/// +/// Provides extension methods for adding MailPit to an . +/// +public static class MailPitHostingExtension +{ + /// + /// Adds a MailPit container resource to the . + /// + /// The to which the MailPit resource will be added. + /// The name of the MailPit container resource. + /// Optional. The HTTP port on which MailPit will listen. + /// Optional. The SMTP port on which MailPit will listen. + /// A reference to the for further resource configuration. + public static IResourceBuilder AddMailPit(this IDistributedApplicationBuilder builder, + [ResourceName] string name, + int? httpPort = null, + int? smtpPort = null) + { + ArgumentNullException.ThrowIfNull("Service name must be specified.", nameof(name)); + MailPitContainerResource resource = new(name); + + IResourceBuilder rb = builder.AddResource(resource) + .WithImage(MailPitContainerImageTags.Image) + .WithImageTag(MailPitContainerImageTags.Tag) + .WithImageRegistry(MailPitContainerImageTags.Registry) + .WithEndpoint( + targetPort: MailPitContainerResource.SmtpEndpointPort, + port: smtpPort, + name: MailPitContainerResource.SmtpEndpointName, + scheme: "smtp") + .WithHttpEndpoint( + targetPort: MailPitContainerResource.HttpEndpointPort, + port: httpPort, + name: MailPitContainerResource.HttpEndpointName) + .WithHttpHealthCheck("/", httpPort, MailPitContainerResource.HttpEndpointName); + + return rb; + } + + /// + /// Configures a data volume for the MailPit container resource. + /// + /// The used to configure the resource. + /// The name of the data volume to be mounted. + /// A boolean indicating whether the volume should be mounted as read-only. + /// A reference to the for further configuration. + public static IResourceBuilder WithDataVolume(this IResourceBuilder builder, string name, + bool isReadOnly = false) + { + ArgumentNullException.ThrowIfNull(builder); + + return builder.WithEnvironment(context => + { + context.EnvironmentVariables[MailPitContainerResource.DatabaseEnvVar] = "/data/mailpit.db"; + }).WithVolume(name, "/data", isReadOnly); + } + + /// + /// Configures a bind mount for the data directory of the MailPit container resource. + /// + /// The to configure the bind mount on. + /// The source path on the host system to bind to the container. + /// A value indicating whether the bind mount should be read-only. Default is false. + /// A reference to the with the configured bind mount. + public static IResourceBuilder WithDataBindMount(this IResourceBuilder builder, string source, + bool isReadOnly = false) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(source); + + return builder.WithEnvironment(context => + { + context.EnvironmentVariables[MailPitContainerResource.DatabaseEnvVar] = "/data/mailpit.db"; + }).WithBindMount(source, "/data", isReadOnly); + } +} diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Shipped.txt b/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Shipped.txt new file mode 100644 index 00000000..7dc5c581 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..bae61cc4 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/PublicAPI.Unshipped.txt @@ -0,0 +1,8 @@ +#nullable enable +Aspire.Hosting.ApplicationModel.MailPitContainerResource +Aspire.Hosting.ApplicationModel.MailPitContainerResource.ConnectionStringExpression.get -> Aspire.Hosting.ApplicationModel.ReferenceExpression! +Aspire.Hosting.ApplicationModel.MailPitContainerResource.MailPitContainerResource(string! name) -> void +Aspire.Hosting.MailPitHostingExtension +static Aspire.Hosting.MailPitHostingExtension.AddMailPit(this Aspire.Hosting.IDistributedApplicationBuilder! builder, string! name, int? httpPort = null, int? smtpPort = null) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.MailPitHostingExtension.WithDataBindMount(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! source, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! +static Aspire.Hosting.MailPitHostingExtension.WithDataVolume(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, string! name, bool isReadOnly = false) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.MailPit/README.md b/src/CommunityToolkit.Aspire.Hosting.MailPit/README.md new file mode 100644 index 00000000..a2293851 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.MailPit/README.md @@ -0,0 +1,39 @@ +# CommunityToolkit.Hosting.MailPit + +## Overview + +This .NET Aspire Integration runs [MailPit](https://github.com/axllent/mailpit) in a container. + + +## Usage + +The MailPit integration exposes a connection string with the format `endpoint=smtp://:`. +This connection string can be used to with a DbConnectionStringBuilder to get the smtp endpoint. + +### Example 1: Add MailPit with generated ports + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var mailpit = builder.AddMailPit("mailpit"); + +var xyz = builder.AddProject("application") + .WithReference(mailpit) + .WaitFor(mailpit); + +builder.Build().Run(); +``` + +### Example 2: Add MailPit with user-defined ports + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var mailpit = builder.AddMailPit("mailpit", 80, 25); + +var xyz = builder.AddProject("application") + .WithReference(mailpit) + .WaitFor(mailpit); + +builder.Build().Run(); +``` diff --git a/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/AppHostTests.cs b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/AppHostTests.cs new file mode 100644 index 00000000..19c947cb --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/AppHostTests.cs @@ -0,0 +1,66 @@ +using Aspire.Components.Common.Tests; +using CommunityToolkit.Aspire.Testing; +using System.Data.Common; +using System.Net.Http.Json; +using System.Net.Mail; +using System.Text.Json; + +namespace CommunityToolkit.Aspire.Hosting.MailPit.Tests; + +[RequiresDocker] +public class AppHostTests(AspireIntegrationTestFixture fixture) : IClassFixture> +{ + private const string ResourceName = "mailpit"; + + [Fact] + public async Task ResourceStartsAndMailShouldBeReceived() + { + await fixture.ResourceNotificationService.WaitForResourceHealthyAsync(ResourceName).WaitAsync(TimeSpan.FromMinutes(2)); + SmtpClient smtpClient = await CreateSmtpClient(); + MailMessage myMail = CreateMailMessage(); + HttpClient httpClient = CreateHttpClientForMailPit(); + + Exception? exception = Record.Exception(() => smtpClient.Send(myMail)); + + Assert.Null(exception); + HttpResponseMessage response = await httpClient.GetAsync("/api/v1/messages"); + response.EnsureSuccessStatusCode(); + + JsonDocument? responseDocument = await response.Content.ReadFromJsonAsync(); + Assert.NotNull(responseDocument); + int totalMessageCount = responseDocument.RootElement.GetProperty("messages_count").GetInt32(); + Assert.Equal(1, totalMessageCount); + } + + private HttpClient CreateHttpClientForMailPit() + { + var httpEndpoint = fixture.GetEndpoint(ResourceName, "http"); + return new HttpClient { BaseAddress = httpEndpoint }; + } + + private static MailMessage CreateMailMessage() + { + MailAddress from = new("test@test.com", "TestFromName"); + MailAddress to = new("to@test.com", "TestToName"); + MailMessage myMail = new(from, to); + MailAddress replyTo = new("reply@test.com"); + myMail.ReplyToList.Add(replyTo); + myMail.Subject = "Subject"; + myMail.SubjectEncoding = System.Text.Encoding.UTF8; + myMail.Body = "Hello world!"; + myMail.BodyEncoding = System.Text.Encoding.UTF8; + return myMail; + } + + private async Task CreateSmtpClient() + { + string? connectionString = await fixture.GetConnectionString(ResourceName); + DbConnectionStringBuilder connectionBuilder = new() + { + ConnectionString = connectionString + }; + Uri connectionUri = new(connectionBuilder["Endpoint"].ToString()!, UriKind.Absolute); + SmtpClient smtpClient = new(connectionUri.Host, connectionUri.Port); + return smtpClient; + } +} \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests.csproj new file mode 100644 index 00000000..f34f78da --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests.csproj @@ -0,0 +1,14 @@ +ο»Ώ + + + false + true + + + + + + + + + diff --git a/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/ContainerResourceCreationTests.cs b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/ContainerResourceCreationTests.cs new file mode 100644 index 00000000..0dd80bcd --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.MailPit.Tests/ContainerResourceCreationTests.cs @@ -0,0 +1,96 @@ +using Aspire.Hosting; + +namespace CommunityToolkit.Aspire.Hosting.MailPit.Tests; + +public class ContainerResourceCreationTests +{ + [Fact] + public void AddMailPitBuilderShouldNotBeNull() + { + IDistributedApplicationBuilder builder = null!; + Assert.Throws(() => builder.AddMailPit("mailpit")); + } + + [Fact] + public void AddMailPitBuilderNameShouldNotBeNullOrWhiteSpace() + { + IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(); + + Assert.Throws(() => builder.AddMailPit(null!)); + } + + [Fact] + public void AddMailPitBuilderContainerDetailsSetOnResource() + { + IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(); + + builder.AddMailPit("mailpit"); + + using var app = builder.Build(); + var appModel = app.Services.GetRequiredService(); + + var resource = appModel.Resources.OfType().SingleOrDefault(); + + Assert.NotNull(resource); + Assert.Equal("mailpit", resource.Name); + + Assert.True(resource.TryGetLastAnnotation(out ContainerImageAnnotation? imageAnnotations)); + Assert.Equal(MailPitContainerImageTags.Tag, imageAnnotations.Tag); + Assert.Equal(MailPitContainerImageTags.Image, imageAnnotations.Image); + Assert.Equal(MailPitContainerImageTags.Registry, imageAnnotations.Registry); + } + + [Fact] + public void AddMailPitBuilderContainerWithDataVolumeDetailsSetOnResource() + { + IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(); + + builder.AddMailPit("mailpit") + .WithDataVolume("mailpit-data", isReadOnly: false); + + using var app = builder.Build(); + var appModel = app.Services.GetRequiredService(); + + var resource = appModel.Resources.OfType().SingleOrDefault(); + + Assert.NotNull(resource); + Assert.Equal("mailpit", resource.Name); + + Assert.True(resource.TryGetLastAnnotation(out ContainerImageAnnotation? imageAnnotations)); + Assert.Equal(MailPitContainerImageTags.Tag, imageAnnotations.Tag); + Assert.Equal(MailPitContainerImageTags.Image, imageAnnotations.Image); + Assert.Equal(MailPitContainerImageTags.Registry, imageAnnotations.Registry); + + Assert.True(resource.TryGetLastAnnotation(out ContainerMountAnnotation? mountAnnotations)); + Assert.Equal(ContainerMountType.Volume, mountAnnotations.Type); + Assert.Equal("/data", mountAnnotations.Target); + } + + [Fact] + public void AddMailPitBuilderContainerWithDataMountDetailsSetOnResource() + { + IDistributedApplicationBuilder builder = DistributedApplication.CreateBuilder(); + + builder.AddMailPit("mailpit") + .WithDataBindMount("mailpit-data", isReadOnly: false); + + using var app = builder.Build(); + var appModel = app.Services.GetRequiredService(); + + var resource = appModel.Resources.OfType().SingleOrDefault(); + + Assert.NotNull(resource); + Assert.Equal("mailpit", resource.Name); + + Assert.True(resource.TryGetLastAnnotation(out ContainerImageAnnotation? imageAnnotations)); + Assert.Equal(MailPitContainerImageTags.Tag, imageAnnotations.Tag); + Assert.Equal(MailPitContainerImageTags.Image, imageAnnotations.Image); + Assert.Equal(MailPitContainerImageTags.Registry, imageAnnotations.Registry); + + Assert.True(resource.TryGetLastAnnotation(out ContainerMountAnnotation? mountAnnotations)); + Assert.Equal(ContainerMountType.BindMount, mountAnnotations.Type); + Assert.Equal("/data", mountAnnotations.Target); + Assert.NotNull(mountAnnotations.Source); + } +} +