Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CommunityToolkit.Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
<Project Path="examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.MassTransit/CommunityToolkit.Aspire.Hosting.ActiveMQ.MassTransit.csproj" />
<Project Path="examples/activemq/CommunityToolkit.Aspire.Hosting.ActiveMQ.ServiceDefaults/CommunityToolkit.Aspire.Hosting.ActiveMQ.ServiceDefaults.csproj" />
</Folder>
<Folder Name="/examples/compose/">
<Project Path="examples/compose/CommunityToolkit.Aspire.Hosting.Compose.AppHost/CommunityToolkit.Aspire.Hosting.Compose.AppHost.csproj" />
</Folder>
<Folder Name="/examples/adminer/">
<Project Path="examples/adminer/CommunityToolkit.Aspire.Hosting.Adminer.AppHost/CommunityToolkit.Aspire.Hosting.Adminer.AppHost.csproj" />
</Folder>
Expand Down Expand Up @@ -254,6 +257,8 @@
<Project Path="src/CommunityToolkit.Aspire.SurrealDb/CommunityToolkit.Aspire.SurrealDb.csproj" />
<Project Path="src\CommunityToolkit.Aspire.Hosting.Keycloak.Extensions\CommunityToolkit.Aspire.Hosting.Keycloak.Extensions.csproj" />
<Project Path="src\CommunityToolkit.Aspire.Hosting.Umami\CommunityToolkit.Aspire.Hosting.Umami.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Compose/CommunityToolkit.Aspire.Hosting.Compose.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Compose.Generator/CommunityToolkit.Aspire.Hosting.Compose.Generator.csproj" />
</Folder>
<Folder Name="/src/Dapr/">
<Project Path="src/CommunityToolkit.Aspire.Hosting.Azure.Dapr.Redis/CommunityToolkit.Aspire.Hosting.Azure.Dapr.Redis.csproj" />
Expand Down Expand Up @@ -316,6 +321,7 @@
<Project Path="tests/CommunityToolkit.Aspire.SurrealDb.Tests/CommunityToolkit.Aspire.SurrealDb.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Testing/CommunityToolkit.Aspire.Testing.csproj" />
<Project Path="tests\CommunityToolkit.Aspire.Hosting.Umami.Tests\CommunityToolkit.Aspire.Hosting.Umami.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Compose.Tests/CommunityToolkit.Aspire.Hosting.Compose.Tests.csproj" />
</Folder>
<Folder Name="/tests/Dapr/">
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Azure.Dapr.Redis.Tests/CommunityToolkit.Aspire.Hosting.Azure.Dapr.Redis.Tests.csproj" />
Expand Down
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
<PackageVersion Include="GitHubActionsTestLogger" Version="3.0.0" />
<PackageVersion Include="Moq" Version="$(MoqVersion)" />
<!-- Build dependencies -->
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.8.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4" />
<!-- Testcontainers packages -->
<PackageVersion Include="Testcontainers" Version="$(TestContainersVersion)" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
services:
grafana:
image: grafana/grafana:10.2.0
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin

prometheus:
image: prom/prometheus:v2.48.0
ports:
- "9090:9090"
depends_on:
- grafana
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
services:
postgres:
image: postgres:16
ports:
- "5432:5432"
environment:
POSTGRES_USER: admin
POSTGRES_PASSWORD: secret
POSTGRES_DB: appdb
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U admin"]
interval: 10s
timeout: 5s
retries: 5

redis:
image: redis:7-alpine
ports:
- "6379:6379"

kafka:
image: confluentinc/cp-kafka:7.5.0
ports:
- "9092:9092"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@localhost:9093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk
depends_on:
- postgres

volumes:
pgdata:
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Aspire.AppHost.Sdk/13.2.0">

<PropertyGroup>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireHost>true</IsAspireHost>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\..\src\CommunityToolkit.Aspire.Hosting.Compose\CommunityToolkit.Aspire.Hosting.Compose.csproj" IsAspireProjectResource="false" />
<ProjectReference Include="..\..\..\src\CommunityToolkit.Aspire.Hosting.Compose.Generator\CommunityToolkit.Aspire.Hosting.Compose.Generator.csproj"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false"
IsAspireProjectResource="false" />
</ItemGroup>

<Import Project="..\..\..\src\CommunityToolkit.Aspire.Hosting.Compose\build\CommunityToolkit.Aspire.Hosting.Compose.props" />

<ItemGroup>
<ComposeReference Include=".infra/compose.yml" Name="Infra" />
<ComposeReference Include=".infra/compose.monitoring.yml" Name="Monitoring" />
</ItemGroup>

<Import Project="..\..\..\src\CommunityToolkit.Aspire.Hosting.Compose\build\CommunityToolkit.Aspire.Hosting.Compose.targets" />

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
var builder = DistributedApplication.CreateBuilder(args);

var infra = builder.AddCompose<Compose.Infra>();
var monitoring = builder.AddCompose<Compose.Monitoring>();

_ = infra.Postgres;
_ = infra.Redis;
_ = infra.Kafka;
_ = monitoring.Grafana;
_ = monitoring.Prometheus;

// var infra = builder.AddCompose(".infra/compose.yml");
// var monitoring = builder.AddCompose(".infra/compose.monitoring.yml");
// _ = infra["postgres"];
// _ = infra["redis"];
// _ = infra["kafka"];
// _ = monitoring["grafana"];
// _ = monitoring["prometheus"];

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:17047;http://localhost:15023",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21036",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22247"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:15023",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19100",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20022"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Aspire.Hosting.Dcp": "Warning"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<IsPackable>false</IsPackable>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
<NoWarn>$(NoWarn);CS1591;RS1035;RS1036;RS2008</NoWarn>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" PrivateAssets="all" />
</ItemGroup>

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

<ItemGroup>
<None Remove="README.md" />
<None Remove="../../docs/images/nuget.png" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System.Globalization;
using System.Text;

namespace CommunityToolkit.Aspire.Hosting.Compose.Generator;

/// <summary>
/// Generates C# source code for compose file wrapper classes.
/// </summary>
internal static class ComposeClassEmitter
{
/// <summary>
/// Emits a wrapper class with typed properties for each compose service.
/// </summary>
public static string EmitClass(string className, string composePath, string[] serviceNames)
{
string sanitizedClassName = SanitizeIdentifier(className);
StringBuilder sb = new();

sb.AppendLine("// <auto-generated/>");
sb.AppendLine("#nullable enable");
sb.AppendLine();
sb.AppendLine("namespace Compose");
sb.AppendLine("{");

sb.AppendLine(" /// <summary>");
sb.AppendLine(string.Format(CultureInfo.InvariantCulture,
" /// Typed wrapper for compose file: {0}",
EscapeXml(composePath)));
sb.AppendLine(" /// </summary>");

sb.AppendLine(string.Format(CultureInfo.InvariantCulture,
" [global::CommunityToolkit.Aspire.Hosting.Compose.ComposeReferencePath(@\"{0}\")]",
composePath.Replace("\"", "\"\"")));
sb.AppendLine(" [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]");

sb.AppendLine(string.Format(CultureInfo.InvariantCulture,
" public sealed class {0}", sanitizedClassName));
sb.AppendLine(" {");

sb.AppendLine(" private readonly global::CommunityToolkit.Aspire.Hosting.Compose.ComposeResourceCollection _inner;");
sb.AppendLine();

sb.AppendLine(string.Format(CultureInfo.InvariantCulture,
" /// <summary>Creates a new <see cref=\"{0}\"/> wrapping the given collection.</summary>",
sanitizedClassName));
sb.AppendLine(string.Format(CultureInfo.InvariantCulture,
" public {0}(global::CommunityToolkit.Aspire.Hosting.Compose.ComposeResourceCollection inner) => _inner = inner;",
sanitizedClassName));
sb.AppendLine();

foreach (string name in serviceNames)
{
string propName = SanitizeIdentifier(name);
sb.AppendLine(" /// <summary>");
sb.AppendLine(string.Format(CultureInfo.InvariantCulture,
" /// Gets the resource builder for the <c>{0}</c> service.",
EscapeXml(name)));
sb.AppendLine(" /// </summary>");
sb.AppendLine(string.Format(CultureInfo.InvariantCulture,
" public global::Aspire.Hosting.ApplicationModel.IResourceBuilder<global::Aspire.Hosting.ApplicationModel.ContainerResource> {0} => _inner[\"{1}\"];",
propName, name.Replace("\"", "\\\"")));
sb.AppendLine();
}

sb.AppendLine(" /// <summary>Gets a resource by service name.</summary>");
sb.AppendLine(" public global::Aspire.Hosting.ApplicationModel.IResourceBuilder<global::Aspire.Hosting.ApplicationModel.ContainerResource> this[string serviceName] => _inner[serviceName];");

sb.AppendLine(" }");
sb.AppendLine("}");

return sb.ToString();
}

/// <summary>
/// Converts a name like "my-postgres" or "my_infra" to a valid C# PascalCase identifier.
/// </summary>
internal static string SanitizeIdentifier(string name)
{
if (string.IsNullOrEmpty(name)) return "Unknown";

StringBuilder sb = new();
bool capitalizeNext = true;

foreach (char ch in name)
{
if (ch is '-' or '_' or '.' or ' ')
{
capitalizeNext = true;
continue;
}

if (sb.Length == 0 && char.IsDigit(ch))
sb.Append('_');

sb.Append(capitalizeNext ? char.ToUpperInvariant(ch) : ch);
capitalizeNext = false;
}

return sb.Length == 0 ? "Unknown" : sb.ToString();
}

private static string EscapeXml(string text)
{
return text
.Replace("&", "&amp;")
.Replace("<", "&lt;")
.Replace(">", "&gt;");
}
}
Loading