Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ jobs:
Hosting.Golang.Tests,
Hosting.JavaScript.Extensions.Tests,
Hosting.Java.Tests,
Hosting.K3s.Tests,
Hosting.K3s.IntegrationTests,
Hosting.k6.Tests,
Hosting.Keycloak.Extensions.Tests,
Hosting.KurrentDB.Tests,
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ examples/perl/**/local/*
**cpanfile.snapshot
**/.modules/
**/*.AppHost.TypeScript/nuget.config

**/.k3s/
6 changes: 6 additions & 0 deletions CommunityToolkit.Aspire.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
<Project Path="examples/java/CommunityToolkit.Aspire.Hosting.Java.ServiceDefaults/CommunityToolkit.Aspire.Hosting.Java.ServiceDefaults.csproj" />
<Project Path="examples/java/CommunityToolkit.Aspire.Hosting.Java.WebApp/CommunityToolkit.Aspire.Hosting.Java.WebApp.csproj" />
</Folder>
<Folder Name="/examples/k3s/">
<Project Path="examples/k3s/CommunityToolkit.Aspire.Hosting.K3s.AppHost/CommunityToolkit.Aspire.Hosting.K3s.AppHost.csproj" />
</Folder>
<Folder Name="/examples/k6/">
<Project Path="examples/k6/CommunityToolkit.Aspire.Hosting.k6.ApiService/CommunityToolkit.Aspire.Hosting.k6.ApiService.csproj" />
<Project Path="examples/k6/CommunityToolkit.Aspire.Hosting.k6.AppHost/CommunityToolkit.Aspire.Hosting.k6.AppHost.csproj" />
Expand Down Expand Up @@ -214,6 +217,7 @@
<Project Path="src/CommunityToolkit.Aspire.Hosting.Golang/CommunityToolkit.Aspire.Hosting.Golang.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.Java/CommunityToolkit.Aspire.Hosting.Java.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.JavaScript.Extensions/CommunityToolkit.Aspire.Hosting.JavaScript.Extensions.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.K3s/CommunityToolkit.Aspire.Hosting.K3s.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.k6/CommunityToolkit.Aspire.Hosting.k6.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.KurrentDB/CommunityToolkit.Aspire.Hosting.KurrentDB.csproj" />
<Project Path="src/CommunityToolkit.Aspire.Hosting.LavinMQ/CommunityToolkit.Aspire.Hosting.LavinMQ.csproj" />
Expand Down Expand Up @@ -276,6 +280,8 @@
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Golang.Tests/CommunityToolkit.Aspire.Hosting.Golang.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Java.Tests/CommunityToolkit.Aspire.Hosting.Java.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.JavaScript.Extensions.Tests/CommunityToolkit.Aspire.Hosting.JavaScript.Extensions.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.K3s.Tests/CommunityToolkit.Aspire.Hosting.K3s.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.K3s.IntegrationTests/CommunityToolkit.Aspire.Hosting.K3s.IntegrationTests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.k6.Tests/CommunityToolkit.Aspire.Hosting.k6.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.Keycloak.Extensions.Tests/CommunityToolkit.Aspire.Hosting.Keycloak.Extensions.Tests.csproj" />
<Project Path="tests/CommunityToolkit.Aspire.Hosting.KurrentDB.Tests/CommunityToolkit.Aspire.Hosting.KurrentDB.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 @@ -93,6 +93,7 @@
<PackageVersion Include="Microsoft.PowerShell.SDK" Version="7.4.10" />
<PackageVersion Include="ModelContextProtocol" Version="0.4.0-preview.3" />
<PackageVersion Include="ModelContextProtocol.AspNetCore" Version="0.4.0-preview.3" />
<PackageVersion Include="KubernetesClient" Version="19.0.2" />
<PackageVersion Include="KurrentDB.Client" Version="1.2.0" />
<PackageVersion Include="SSH.NET" Version="2025.1.0" />
</ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ This repository contains the source code for the Aspire Community Toolkit, a col
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| - **Learn More**: [`Hosting.Golang`][golang-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields]][golang-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.Golang][golang-shields-preview]][golang-nuget-preview] | A hosting integration Golang apps. |
| - **Learn More**: [`Hosting.Java`][java-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields]][java-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.Java][java-shields-preview]][java-nuget-preview] | An integration for running Java code in Aspire either using the local JDK or using a container. |
| - **Learn More**: [`Hosting.K3s`][k3s-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.K3s][k3s-shields]][k3s-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.K3s][k3s-shields-preview]][k3s-nuget-preview] | An Aspire hosting integration for [k3s](https://k3s.io/), a lightweight Kubernetes distribution by Rancher. |
| - **Learn More**: [`Hosting.NodeJS.Extensions`][nodejs-ext-integration-docs] <br /> - Stable 📦: [![CommunityToolkit.Aspire.NodeJS.Extensions][nodejs-ext-shields]][nodejs-ext-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.Hosting.JavaScript.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] <br /> - Stable 📦: [![CommunityToolkit.Aspire.Hosting.Ollama][ollama-shields]][ollama-nuget] <br /> - 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] <br /> - Stable 📦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields]][ollamasharp-nuget] <br /> - Preview 📦: [![CommunityToolkit.Aspire.OllamaSharp][ollamasharp-shields-preview]][ollamasharp-nuget-preview] | An Aspire client integration for the [OllamaSharp](https://github.com/awaescher/OllamaSharp) package. |
Expand Down Expand Up @@ -106,6 +107,11 @@ This project is supported by the [.NET Foundation](https://dotnetfoundation.org)
[java-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Java/
[java-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.Java?label=nuget%20(preview)
[java-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.Java/absoluteLatest
[k3s-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-k3s
[k3s-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.K3s
[k3s-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.K3s/
[k3s-shields-preview]: https://img.shields.io/nuget/vpre/CommunityToolkit.Aspire.Hosting.K3s?label=nuget%20(preview)
[k3s-nuget-preview]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.K3s/absoluteLatest
[nodejs-ext-integration-docs]: https://learn.microsoft.com/dotnet/aspire/community-toolkit/hosting-nodejs-extensions
[nodejs-ext-shields]: https://img.shields.io/nuget/v/CommunityToolkit.Aspire.Hosting.JavaScript.Extensions
[nodejs-ext-nuget]: https://nuget.org/packages/CommunityToolkit.Aspire.Hosting.JavaScript.Extensions/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<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.K3s\CommunityToolkit.Aspire.Hosting.K3s.csproj" IsAspireProjectResource="false" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// K3s hosting example
// ──────────────────────────────────────────────────────────────────────────────
// Prerequisites (host machine):
// • Docker with --privileged support (Linux or Docker Desktop on Mac/Windows)
// • helm → https://helm.sh/docs/intro/install/
// • kubectl → https://kubernetes.io/docs/tasks/tools/
//
// What this demonstrates:
// 1. A k3s cluster starts inside a Docker container.
// 2. podinfo is installed via Helm — a lightweight demo app.
// 3. A K3sServiceEndpointResource exposes the podinfo service:
// • Host processes reach it at http://localhost:{port}
// • DCP-network containers reach it at http://host.docker.internal:{port}
// 4. WithDataVolume keeps the cluster state alive across AppHost restarts.
// ──────────────────────────────────────────────────────────────────────────────

var builder = DistributedApplication.CreateBuilder(args);

var cluster = builder
.AddK3sCluster("k8s")
.WithDataVolume()
.WithLifetime(ContainerLifetime.Persistent);

var podinfo = cluster.AddHelmRelease(
name: "podinfo",
chart: "podinfo",
repo: "https://stefanprodan.github.io/podinfo",
version: "6.7.1",
@namespace: "podinfo");

// Expose the podinfo service as an Aspire endpoint resource.
// WaitForCompletion waits for the helm install container to exit with code 0
// before starting the port-forward — no NodePort required.
cluster.AddServiceEndpoint("podinfo-web", "podinfo", servicePort: 9898, @namespace: "podinfo")
.WaitForCompletion(podinfo);

builder.Build().Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:53201;http://localhost:53202",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:53203",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:53204"
}
},
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:53202",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DOTNET_ENVIRONMENT": "Development",
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true",
"DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:53205",
"DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:53206"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Annotation that describes a Helm chart release to install into the cluster.
/// </summary>
/// <param name="releaseName">The Helm release name.</param>
/// <param name="chart">The chart name or local path.</param>
/// <param name="namespace">The Kubernetes namespace to install into.</param>
/// <param name="repoUrl">Optional Helm repository URL containing the chart.</param>
/// <param name="version">Optional chart version.</param>
public sealed class HelmReleaseAnnotation(
string releaseName,
string chart,
string @namespace,
string? repoUrl,
string? version) : IResourceAnnotation
{
/// <summary>Gets the Helm release name.</summary>
public string ReleaseName { get; } = releaseName;

/// <summary>Gets the chart name or local path.</summary>
public string Chart { get; } = chart;

/// <summary>Gets the target Kubernetes namespace.</summary>
public string Namespace { get; } = @namespace;

/// <summary>Gets the optional Helm repository URL.</summary>
public string? RepoUrl { get; } = repoUrl;

/// <summary>Gets the optional chart version.</summary>
public string? Version { get; } = version;

/// <summary>Gets the extra <c>--set</c> values passed to <c>helm install</c>.</summary>
public IDictionary<string, string> Values { get; } = new Dictionary<string, string>();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Annotation that signals the Kubernetes Dashboard should be installed alongside the cluster.
/// </summary>
/// <param name="version">The Kubernetes Dashboard chart version to install.</param>
public sealed class KubernetesDashboardAnnotation(string? version = null) : IResourceAnnotation
{
/// <summary>Gets the dashboard chart version, or <see langword="null"/> to use the latest.</summary>
public string? Version { get; } = version;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Annotation that describes a Kustomize overlay to apply into the cluster.
/// </summary>
/// <param name="path">Path to the kustomization directory or remote URL.</param>
public sealed class KustomizeAnnotation(string path) : IResourceAnnotation
{
/// <summary>Gets the kustomization path.</summary>
public string Path { get; } = path;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<AdditionalPackageTags>kubernetes k3s hosting cluster</AdditionalPackageTags>
<Description>An Aspire hosting integration for k3s — a lightweight Kubernetes distribution.</Description>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Aspire.Hosting" />
<PackageReference Include="KubernetesClient" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" />
</ItemGroup>

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


</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace CommunityToolkit.Aspire.Hosting;

internal static class HelmContainerImageTags
{
internal const string Registry = "docker.io";
internal const string Image = "alpine/helm";
internal const string Tag = "3.17.3";
}
43 changes: 43 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.K3s/HelmReleaseResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Represents a Helm chart release deployed to a k3s cluster.
/// <para>
/// Runs as an <c>alpine/helm</c> container on the DCP network. The container polls for the
/// cluster kubeconfig (written when the cluster health check first passes), executes
/// <c>helm upgrade --install --wait</c>, and exits with code 0 on success. Use
/// <c>WaitForCompletion(helmRelease)</c> on resources that depend on the release being installed.
/// </para>
/// </summary>
/// <param name="name">The Aspire resource name (also used as the Helm release name).</param>
/// <param name="releaseName">The Helm release name passed to <c>helm upgrade --install</c>.</param>
/// <param name="namespace">The Kubernetes namespace to install into.</param>
/// <param name="cluster">The parent k3s cluster resource.</param>
public sealed class HelmReleaseResource(
string name,
string releaseName,
string @namespace,
K3sClusterResource cluster)
: ContainerResource(name), IResourceWithParent<K3sClusterResource>
{
/// <inheritdoc />
public K3sClusterResource Parent { get; } = cluster ?? throw new ArgumentNullException(nameof(cluster));

/// <summary>Gets the Helm release name.</summary>
public string ReleaseName { get; } = releaseName ?? throw new ArgumentNullException(nameof(releaseName));

/// <summary>Gets the target Kubernetes namespace.</summary>
public string Namespace { get; } = @namespace ?? throw new ArgumentNullException(nameof(@namespace));

internal string? Chart { get; set; }
internal string? RepoUrl { get; set; }
internal string? Version { get; set; }
internal Dictionary<string, string> HelmValues { get; } = new(StringComparer.Ordinal);

/// <summary>
/// Absolute host paths of values files to inject into the helm container via
/// <c>--values /helm-values/{filename}</c>.
/// Populated by <c>WithHelmValuesFile</c>.
/// </summary>
internal List<string> ValuesFiles { get; } = [];
}
22 changes: 22 additions & 0 deletions src/CommunityToolkit.Aspire.Hosting.K3s/K3sAgentResource.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Aspire.Hosting.ApplicationModel;

/// <summary>
/// Represents a k3s agent (worker) node that is a child of a <see cref="K3sClusterResource"/>.
/// <para>
/// Agent nodes run <c>k3s agent</c> and join the cluster by connecting to the server's API
/// server at <c>https://{serverName}:6443</c>, resolved via DCP's built-in Docker DNS.
/// Agents start immediately alongside the server (no <c>WaitFor</c> dependency) and use
/// k3s's built-in retry loop to connect once the server becomes reachable. The cluster's
/// health check waits for all <c>1 + <see cref="K3sClusterResource.AgentCount"/></c> nodes
/// to reach <c>Ready</c> state before transitioning to <c>Running</c>.
/// </para>
/// </summary>
/// <param name="name">The resource name (e.g. <c>k8s-agent-0</c>).</param>
/// <param name="cluster">The parent k3s cluster resource.</param>
public sealed class K3sAgentResource(string name, K3sClusterResource cluster)
: ContainerResource(name), IResourceWithParent<K3sClusterResource>
{
/// <inheritdoc />
public K3sClusterResource Parent { get; } = cluster
?? throw new ArgumentNullException(nameof(cluster));
}
Loading
Loading