Skip to content
Closed
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
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@
*.fsx text=auto
*.hs text=auto

# Shell scripts must keep LF endings even when checked out on Windows; a CRLF
# turns `set -euxo pipefail` into `pipefail\r` (invalid option) once the file is
# copied into WSL.
*.sh text eol=lf

*.csproj text=auto
*.vbproj text=auto
*.fsproj text=auto
Expand Down
42 changes: 42 additions & 0 deletions .github/e2e/bootstrap-kind.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Bring up a single node Kubernetes control plane inside the kindest/node rootfs
# that oci-to-wsl imported into WSL. kind normally runs this image as a Docker
# container and drives kubeadm from the host through `docker exec`; here WSL
# boots the image's systemd directly, so the equivalent bootstrap is performed
# in place.
set -euxo pipefail

# The kindest/node entrypoint (which WSL does not run) makes the root mount
# shared and enables IPv4 forwarding; reproduce the parts kubeadm and the
# kubelet rely on.
mount --make-rshared / 2>/dev/null || true
sysctl -w net.ipv4.ip_forward=1 || true

# containerd ships as a systemd unit in the node image.
systemctl enable --now containerd

# Wait for the container runtime to accept requests.
for _ in {1..60}; do
if ctr --namespace k8s.io version >/dev/null 2>&1; then
break
fi
sleep 2
done

# Initialize the control plane. Preflight errors are ignored because the WSL
# environment intentionally differs from a vanilla node (swap on, kernel
# modules provided by the Windows-side kernel, and so on).
kubeadm init \
--ignore-preflight-errors=all \
--pod-network-cidr=10.244.0.0/16 \
--apiserver-cert-extra-sans=127.0.0.1,localhost

export KUBECONFIG=/etc/kubernetes/admin.conf

# Single node cluster: allow workloads to schedule on the control-plane node.
kubectl taint nodes --all node-role.kubernetes.io/control-plane- 2>/dev/null || true

# kindest/node bundles the default CNI (kindnet) manifest.
kubectl apply -f /kind/manifests/default-cni.yaml

kubectl wait --for=condition=Ready nodes --all --timeout=180s
21 changes: 21 additions & 0 deletions .github/e2e/kind-wsl.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# oci-to-wsl profile that imports the kindest/node image into WSL as a
# systemd-enabled distribution. Unlike the regular "kind in Docker" flow, the
# Kubernetes node runs natively inside WSL, so no Docker daemon or nested
# containers are required on the Windows runner.
name: kind
image: kindest/node:v1.32.2
install_dir: C:\WSL\kind

# kindest/node expects an init system; WSL boots systemd when configured.
wsl_conf:
mode: merge
content: |
[boot]
systemd=true

# Stage the bootstrap script that turns the imported rootfs into a running
# single node control plane. Path is resolved relative to this profile.
files:
- src: ./bootstrap-kind.sh
dst: /usr/local/bin/bootstrap-kind.sh
mode: "0755"
97 changes: 97 additions & 0 deletions .github/workflows/buildtest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,103 @@ jobs:
exit 1
fi

# Run the same end to end suite as the Linux `e2e` job, but on Windows. There
# is no Linux Kubernetes distribution that runs natively on a Windows runner,
# so the kindest/node image (which already bundles containerd, the kubelet and
# the control-plane components) is imported straight into WSL with oci-to-wsl.
# WSL2 is enabled by default on the runner, so no separate WSL provisioning is
# needed, and the API server is reachable from the Windows host (which runs the
# test process) through WSL2 localhost forwarding.
e2e-windows-wsl:
runs-on: windows-latest
name: E2E (kind in WSL)
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Setup dotnet
uses: actions/setup-dotnet@9a946fdbd5fb07b82b2f5a4466058b876ab72bb2 # v5.3.0
with:
dotnet-version: |
8.0.x
9.0.x
10.0.x
- name: Install oci-to-wsl
shell: pwsh
run: |
$url = "https://github.com/tg123/oci-to-wsl/releases/download/v0.0.1/oci-to-wsl_windows_x86_64.zip"
$expected = "79772846F1CE4A38E5B2841EA2406A070F4DB64F3DBF3EA32F04B519111FD003"
Invoke-WebRequest $url -OutFile oci-to-wsl.zip
$actual = (Get-FileHash oci-to-wsl.zip -Algorithm SHA256).Hash
if ($actual -ne $expected) {
throw "oci-to-wsl checksum mismatch: expected $expected, got $actual"
}
Expand-Archive -Force oci-to-wsl.zip -DestinationPath .
- name: Start kind cluster in WSL
shell: pwsh
run: |
# Import kindest/node into WSL as a systemd-enabled distro named "kind".
.\oci-to-wsl.exe --profile .github/e2e/kind-wsl.yaml

# Reboot the distro so the systemd configuration written by the profile
# takes effect.
wsl --shutdown

# WSL terminates the lightweight VM once its last session exits, which
# tears down the API server between steps and makes localhost:6443
# connections fail with "actively refused". Hold an idle session open in
# the background so the cluster (and WSL2 localhost forwarding) stay up
# for the duration of the host-side test steps.
Start-Process -FilePath wsl -ArgumentList '-d','kind','-u','root','--','sleep','infinity'

# Bring up the single node control plane in place.
wsl -d kind -u root -- bash /usr/local/bin/bootstrap-kind.sh

# Export a kubeconfig for the Windows host. kubeadm points it at the WSL
# eth0 address; rewrite it to localhost, which WSL2 forwards into WSL.
wsl -d kind -u root -- cat /etc/kubernetes/admin.conf | Out-File -Encoding ascii kind.kubeconfig
(Get-Content kind.kubeconfig) -replace 'server: https://[^ ]+', 'server: https://127.0.0.1:6443' | Set-Content kind.kubeconfig

# Wait until the API server is reachable from the Windows host through
# the forwarded port before handing off to the test steps.
$ok = $false
foreach ($i in 1..60) {
if ((Test-NetConnection -ComputerName 127.0.0.1 -Port 6443 -WarningAction SilentlyContinue).TcpTestSucceeded) {
$ok = $true
break
}
Start-Sleep -Seconds 5
}
if (-not $ok) {
throw "API server on 127.0.0.1:6443 was not reachable from the Windows host"
}
- name: Test
shell: pwsh
env:
K8S_E2E_MINIKUBE: "1"
KUBECONFIG: ${{ github.workspace }}\kind.kubeconfig
run: |
"" | Out-File -Encoding ascii skip.log
dotnet test tests/E2E.Tests --logger "SkipTestLogger;file=$PWD/skip.log" -p:BuildInParallel=false
if ((Get-Item skip.log).Length -gt 0) {
Get-Content skip.log
Write-Error "CASES MUST NOT BE SKIPPED"
exit 1
}
- name: AOT Test
shell: pwsh
env:
K8S_E2E_MINIKUBE: "1"
KUBECONFIG: ${{ github.workspace }}\kind.kubeconfig
run: |
"" | Out-File -Encoding ascii skip.log
dotnet test tests/E2E.Aot.Tests --logger "SkipTestLogger;file=$PWD/skip.log" -p:BuildInParallel=false
if ((Get-Item skip.log).Length -gt 0) {
Get-Content skip.log
Write-Error "CASES MUST NOT BE SKIPPED"
exit 1
}

on:
pull_request:
types: [assigned, opened, synchronize, reopened]
Loading