Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
983b1cd
feat: CommunityToolkit.Aspire.Hosting.K3s
edmondshtogu May 13, 2026
4dda24b
feat: proper support for service endpoints, helm and manifests resources
edmondshtogu May 13, 2026
a2ce723
feat: support files for helm/manifest
edmondshtogu May 13, 2026
2729a69
Merge branch 'main' into main
edmondshtogu May 14, 2026
bc271ec
Merge branch 'main' into main
edmondshtogu May 17, 2026
0e5c9de
Merge branch 'main' into main
edmondshtogu May 18, 2026
d343ee3
compliting left work
edmondshtogu May 18, 2026
0a4d13a
upgrade example aspire version
edmondshtogu May 18, 2026
f9a50d1
fix: copilot reviews
edmondshtogu May 18, 2026
da4bf9d
chore: exluding integration tests for win runners
edmondshtogu May 18, 2026
ff66be4
chore: update example comment
edmondshtogu May 18, 2026
37c11e3
fix: copilot reviews
edmondshtogu May 18, 2026
99297cd
fix: WaitForServiceReadyAsync service health check
edmondshtogu May 18, 2026
7d34ddc
chore: addressing reviews
edmondshtogu May 18, 2026
7473170
fix: copilot reviews
edmondshtogu May 18, 2026
98bf921
fix: copilot reviews
edmondshtogu May 18, 2026
8c11cc5
fix: issues with the integration test build and k3s api port proxying
edmondshtogu May 20, 2026
e33ae5f
chore: excluding the integration test since take too long to run
edmondshtogu May 20, 2026
62e9018
fix: address code review findings — lifecycle, idempotency and mount …
edmondshtogu May 20, 2026
5d913d6
fix: proper lifecycle management for health check and port forwarder
edmondshtogu May 20, 2026
06a7b0f
fix: manifest apply exit code when no kustomize present
edmondshtogu May 20, 2026
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
1 change: 1 addition & 0 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
Hosting.Golang.Tests,
Hosting.JavaScript.Extensions.Tests,
Hosting.Java.Tests,
Hosting.K3s.Tests,
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,6 +23,8 @@ examples/perl/**/local/*
**cpanfile.snapshot
**/.modules/
**/*.AppHost.TypeScript/nuget.config

**/.k3s/
tsconfig.apphost.json
.ngrok
bun.lock
Expand Down
5 changes: 5 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,7 @@
<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.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.3.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,53 @@
// K3s hosting example
// ──────────────────────────────────────────────────────────────────────────────
// Prerequisites (host machine):
// • A container runtime that supports privileged Linux containers:
// - Linux: Docker Engine 20.10+ or rootful Podman 4.0+
// - macOS / Windows: Docker Desktop (WSL2 / Hyper-V)
// No host-side helm or kubectl required — both run as containers.
//
// What this demonstrates:
// 1. A k3s cluster starts inside a Docker container.
// 2. app-config ConfigMap applied via AddK8sManifest (plain YAML file).
// 3. monitoring-config ConfigMap applied via AddK8sManifest (Kustomize overlay —
// auto-detected from kustomization.yaml; adds namespace + common labels).
// 4. podinfo installed via Helm, waiting for both manifests first.
// 5. 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}
// 6. 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");

// Plain YAML file — a ConfigMap in the default namespace.
var appConfig = cluster.AddK8sManifest("app-config", "./k8s/app-config.yaml")
.WaitForCompletion(podinfo);

// Kustomize overlay — auto-detected because the directory contains kustomization.yaml.
// Kustomize injects the 'monitoring' namespace and common labels into every resource
// without modifying the source files.
var monitoringConfig = cluster.AddK8sManifest("monitoring-config", "./k8s/monitoring")
.WaitForCompletion(podinfo)
.WaitForCompletion(appConfig);

// 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)
.WaitForCompletion(monitoringConfig);

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,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
namespace: default
data:
environment: development
log-level: info
# podinfo endpoint — matches the AddServiceEndpoint name in Program.cs
api-endpoint: http://podinfo:9898
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the name of this file common, or is this a play on words leaning on the k from Kubernetes?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

# Kustomize places every resource in this namespace.
namespace: default

# Common labels applied to all resources in this overlay.
labels:
- includeSelectors: true
pairs:
app.kubernetes.io/managed-by: aspire
environment: development


resources:
- monitoring-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: monitoring-config
data:
scrape-interval: 30s
retention: 7d
log-level: info
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { createBuilder, ContainerLifetime } from './.modules/aspire.js';

const builder = await createBuilder();

// ── Runtime path (actually executed) ─────────────────────────────────────────
// Minimal cluster startup — validates that the core add/build/run path works.
const cluster = builder.addK3sCluster('k8s');
const clusterResource = await cluster;
const _apiEndpoint = await clusterResource.apiEndpoint.get();

// ── Compile-time coverage ─────────────────────────────────────────────────────
// Guards with false so these are type-checked but never executed.
// Covers the full exported API surface without requiring Docker/k3s in CI.
const includeCompileOnlyScenarios = false;

if (includeCompileOnlyScenarios) {

// ── Cluster configuration ────────────────────────────────────────────────
const configuredCluster = builder.addK3sCluster('k8s-configured')
.withK3sVersion('v1.32.3-k3s1')
.withPodSubnet('10.42.0.0/16')
.withServiceSubnet('10.43.0.0/16')
.withDisabledComponent('traefik')
.withExtraArg('--write-kubeconfig-mode=644')
.withDataVolume({ name: 'k8s-data' })
.withLifetime(ContainerLifetime.Persistent);

const configuredClusterResource = await configuredCluster;
const _configuredApiEndpoint = await configuredClusterResource.apiEndpoint.get();

// ── Helm release ─────────────────────────────────────────────────────────
const argocd = configuredCluster.addHelmRelease('argocd', 'argo-cd', {
repo: 'https://argoproj.github.io/argo-helm',
version: '7.8.0',
namespace: 'argocd',
})
.withHelmValue('server.insecure', 'true')
.withHelmValuesFile('./deploy/argocd-values.yaml');

const argocdResource = await argocd;
const _argocdParent = await argocdResource.parent.get();
const _argocdReleaseName = await argocdResource.releaseName.get();
const _argocdNamespace = await argocdResource.namespace.get();

// ── K8s manifest / Kustomize overlay ─────────────────────────────────────
const widgetCrd = configuredCluster.addK8sManifest('widget-crd', './k8s/crds/');

const widgetCrdResource = await widgetCrd;
const _crdParent = await widgetCrdResource.parent.get();
const _crdPath = await widgetCrdResource.path.get();

// ── Service endpoint ─────────────────────────────────────────────────────
const ui = configuredCluster.addServiceEndpoint('argocd-ui', 'argocd-server', 443, {
namespace: 'argocd',
});

const uiResource = await ui;
const _uiParent = await uiResource.parent.get();
const _uiServiceName = await uiResource.serviceName.get();
const _uiServicePort = await uiResource.servicePort.get();
const _uiNamespace = await uiResource.namespace.get();
const _uiHostPort = await uiResource.hostPort.get();

// ── WithReference — cluster kubeconfig injection ──────────────────────────
// Project: receives KUBECONFIG=.../.k3s/k8s/local/kubeconfig.yaml
const _projectRef = builder
.addProject('operator', { projectPath: '../WidgetOperator/WidgetOperator.csproj' })
.withReference(configuredCluster);

// Container: receives a bind-mounted kubeconfig and KUBECONFIG=/var/k3s/kubeconfig.yaml
const _containerRef = builder
.addContainer('sidecar', 'myorg/sidecar')
.withReference(configuredCluster);

// ── WithReference — service endpoint URL injection ────────────────────────
// Host: receives services__argocd-ui__url=https://localhost:{port}
const _endpointRef = builder
.addProject('api', { projectPath: '../WidgetApi/WidgetApi.csproj' })
.withReference(ui);
}

await builder.build().run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"appHost": {
"path": "apphost.ts",
"language": "typescript/nodejs"
},
"profiles": {
"https": {
"applicationUrl": "https://localhost:17149;http://localhost:15243",
"environmentVariables": {
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21065",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22170"
}
},
"http": {
"applicationUrl": "http://localhost:15243",
"environmentVariables": {
"ASPIRE_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19027",
"ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20141",
"ASPIRE_ALLOW_UNSECURED_TRANSPORT": "true"
}
}
},
"packages": {
"CommunityToolkit.Aspire.Hosting.K3s": ""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @sebastienros, is this correct? I always forget..."" is valid, right?

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "validationapphost",
"version": "1.0.0",
"type": "module",
"scripts": {
"start": "aspire run",
"build": "tsc",
"dev": "tsc --watch"
},
"dependencies": {
"vscode-jsonrpc": "^8.2.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
"nodemon": "^3.1.11",
"tsx": "^4.19.0",
"typescript": "^5.3.0"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"outDir": "./dist",
"rootDir": "."
},
"include": [
"apphost.ts",
".modules/**/*.ts"
],
"exclude": [
"node_modules"
]
}
Loading
Loading