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
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ examples/perl/**/local/*
**cpanfile.snapshot
**/.modules/
**/*.AppHost.TypeScript/nuget.config
<<<<<<< main

**/.k3s/
=======
tsconfig.apphost.json
.ngrok
bun.lock
Expand All @@ -31,3 +35,4 @@ yarn.lock
solr-data
*.lscache
apphost.js
>>>>>>> main
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