Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9d2a4eb
chore: Install demo stuff and couple to public website
sunib Mar 16, 2026
faf356e
Copy raw objects: as we had them
sunib Mar 16, 2026
af38093
chore: the voter demo is now running
sunib Mar 17, 2026
c6e0b92
chore: Tweaking ingress stuff
sunib Mar 17, 2026
33f9a75
chore: Loadtesting and APF tuning (which is really cool stuff)
sunib Mar 17, 2026
46623fb
chore: Prepare demo test (that bootstraps the gitea repo and needed r…
sunib Mar 17, 2026
1584bf1
chore: This needs more testing, and probably get it together in a sin…
sunib Mar 17, 2026
a249c96
chore: Move demo stuff out of 'production' tests so that we could mer…
sunib Mar 18, 2026
951f874
chore: Group reconcile in one commit and less opinions on git signing…
sunib Mar 18, 2026
afd488b
chore: Also allow claude to stay loged in over devcontainer rebuilds
sunib Mar 18, 2026
ab9b01e
chore: have e2e resources in a seperate ns (so that we can keep the g…
sunib Mar 18, 2026
5c53e02
fix: Preventing reconcile code from ignoring ns boundary
sunib Mar 19, 2026
1a6e203
chore: Adding unit tests and only one commit per reconcile
sunib Mar 19, 2026
e313c69
chore: Let's switch to k8s v1.35.2 for our e2e tests (and demo).
sunib Mar 19, 2026
c416062
chore: Improving demo support / performance
sunib Mar 19, 2026
4731bd5
chore: Add KRO and podinfo so that we really can deploy stuff
sunib Mar 20, 2026
267e6a1
chore: Thats simpler
sunib Mar 20, 2026
d2e71d9
chore: Let's get that working as well
sunib Mar 20, 2026
3fe0412
chore: Working with a monster script
sunib Mar 20, 2026
7f5bf22
chore: Replaced monster deployment by a more elaborate initiliaztion …
sunib Mar 20, 2026
aad5d32
chore: trying to delay bootstrap until first meaningfull event
sunib Mar 20, 2026
c759f19
chore: get the architecture back in the git.go, but not yet happy wit…
sunib Mar 20, 2026
7ec3680
chore: appeared to have found the bug already, adding support for can…
sunib Mar 21, 2026
439323b
fix: Have working printcolumns
sunib Mar 21, 2026
1363d0b
chore: Adding more workers and tweaking the printcolumns
sunib Mar 21, 2026
a210541
chore: various improvements
sunib Mar 21, 2026
7708e37
chore: other tweaks
sunib Mar 21, 2026
7f13c65
chore: finalize for demo
sunib Mar 22, 2026
65614ca
chore: the last fix of last night
sunib Mar 23, 2026
88c3903
chore: Let's invite people to react
sunib Mar 23, 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
13 changes: 13 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(go test:*)",
"Bash(make lint:*)",
"Bash(make test-e2e:*)",
"Bash(go build:*)",
"Bash(kubectl apply:*)",
"Bash(kubectl wait:*)",
"Bash(chmod +x /workspaces/gitops-reverser/hack/demo/setup-holland-stuff-deployment.sh)"
]
}
}
13 changes: 7 additions & 6 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ RUN chgrp -R godev /go && \
# Install Go tools used by the project (using @version doesn't need go.mod)
# These run AFTER permissions are set to ensure proper group ownership
RUN go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.19.0 \
&& go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest \
&& go install github.com/checkmake/checkmake/cmd/checkmake@latest

&& go install sigs.k8s.io/controller-runtime/tools/setup-envtest@v0.0.0-20260318145839-6c9615a2a166 \
&& go install github.com/checkmake/checkmake/cmd/checkmake@v0.3.2 \
&& go install golang.org/x/tools/cmd/goimports@v0.43.0

# Initialize golangci-lint cache by running it once on an empty directory
# This downloads linter dependencies without needing source code
RUN mkdir -p /tmp/golangci-init && cd /tmp/golangci-init \
Expand Down Expand Up @@ -147,11 +148,11 @@ RUN arch="$(dpkg --print-architecture)" && \

# Install Delve debugger for Go debugging in VSCode
# ACLs from CI stage should be preserved, so no need to re-apply
RUN go install github.com/go-delve/delve/cmd/dlv@latest
RUN go install github.com/go-delve/delve/cmd/dlv@v1.26.1

# Install VSCode Go extension tools (gopls and staticcheck)
RUN go install golang.org/x/tools/gopls@latest \
&& go install honnef.co/go/tools/cmd/staticcheck@latest
RUN go install golang.org/x/tools/gopls@v0.21.1 \
&& go install honnef.co/go/tools/cmd/staticcheck@v0.7.0

# Create vscode user for non-root development and add to godev group
RUN groupadd --gid 1000 vscode \
Expand Down
15 changes: 9 additions & 6 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,22 +67,25 @@
"golang.go",
"ms-kubernetes-tools.vscode-kubernetes-tools",
"ms-azuretools.vscode-docker",
"openai.chatgpt"
"openai.chatgpt",
"anthropic.claude-code"
]
}
},
"postCreateCommand": "bash .devcontainer/post-create.sh '${containerWorkspaceFolder}'",
"remoteUser": "vscode",
"mounts": [
"source=ghconfig,target=/home/vscode/.config/gh,type=volume",
"source=${localEnv:HOME}${localEnv:USERPROFILE}/.gitconfig,target=/home/vscode/.gitconfig-host,type=bind,readonly,consistency=cached",
"source=devcontainer-ssh,target=/home/vscode/.ssh,type=volume",
"source=gomodcache,target=/go/pkg/mod,type=volume",
"source=gobuildcache,target=/home/vscode/.cache/go-build,type=volume",
"source=codexconfig,target=/home/vscode/.codex,type=volume"
"source=codexconfig,target=/home/vscode/.codex,type=volume",
"source=claudeconfig,target=/home/vscode/.claude,type=volume",
"source=dotkube,target=/home/vscode/.kube,type=volume",
"source=persisted-home,target=/home/vscode/persisted-home,type=volume"
],
"containerEnv": {
"HOST_PROJECT_PATH": "${localWorkspaceFolder}",
"PROJECT_PATH": "/workspaces/${localWorkspaceFolderBasename}"
"PROJECT_PATH": "/workspaces/${localWorkspaceFolderBasename}",
"GIT_USER_NAME": "${localEnv:GIT_USER_NAME}",
"GIT_USER_EMAIL": "${localEnv:GIT_USER_EMAIL}"
}
}
95 changes: 52 additions & 43 deletions .devcontainer/post-create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,75 +11,84 @@ fail() {
exit 1
}

# Resolve workspace path in a way that works both inside and outside
# VS Code-specific shell variable injection.
workspace_dir="${1:-${containerWorkspaceFolder:-${WORKSPACE_FOLDER:-$(pwd)}}}"
log "Using workspace directory: ${workspace_dir}"

# Keep ~/.gitconfig writable inside the container while still importing host settings.
if [ -f /home/vscode/.gitconfig-host ]; then
log "Configuring git to include /home/vscode/.gitconfig-host"
touch /home/vscode/.gitconfig
if git config --global --get-all include.path | grep -Fxq "/home/vscode/.gitconfig-host"; then
log "Host gitconfig include already present"
else
git config --global --add include.path /home/vscode/.gitconfig-host
log "Added host gitconfig include"
fi
# Resolve Git identity from effective config first, then fallback env vars.
git_name="$(git config --get user.name || true)"
git_email="$(git config --get user.email || true)"

if [ -z "${git_name}" ] && [ -n "${GIT_USER_NAME:-}" ]; then
git_name="${GIT_USER_NAME}"
fi

if [ -z "${git_email}" ] && [ -n "${GIT_USER_EMAIL:-}" ]; then
git_email="${GIT_USER_EMAIL}"
fi

# Require basic Git identity information.
git_name="$(git config --global --includes --get user.name || true)"
git_email="$(git config --global --includes --get user.email || true)"
if [ -z "${git_name}" ] || [ -z "${git_email}" ]; then
fail "Missing Git identity. Configure both user.name and user.email in your host ~/.gitconfig."
fail "Missing Git identity. Set user.name and user.email in Git, or provide GIT_USER_NAME and GIT_USER_EMAIL to the devcontainer environment."
fi

# Respect existing signing settings (OpenPGP/SSH). Fallback to SSH signing only when missing.
if git config --global --includes --get commit.gpgsign >/dev/null 2>&1 \
|| git config --global --includes --get gpg.format >/dev/null 2>&1 \
|| git config --global --includes --get user.signingkey >/dev/null 2>&1; then
log "Detected existing Git signing configuration; leaving signing settings unchanged"
else
log "No Git signing configuration detected; configuring SSH signing fallback"
# Persist identity globally in the container if it is not already configured there.
if ! git config --global --get user.name >/dev/null 2>&1; then
git config --global user.name "${git_name}"
fi

mkdir -p /home/vscode/.ssh
if ! git config --global --get user.email >/dev/null 2>&1; then
git config --global user.email "${git_email}"
fi

if ! ssh-add -L >/tmp/ssh-agent-keys.out 2>/dev/null || ! grep -qE '^ssh-' /tmp/ssh-agent-keys.out; then
log "No SSH keys found in agent; creating fallback signing key"
if [ ! -f /home/vscode/.ssh/signing_key ]; then
ssh-keygen -t ed25519 -f /home/vscode/.ssh/signing_key -N "" -C "${git_email}" >/dev/null
fi
cp /home/vscode/.ssh/signing_key.pub /tmp/ssh-agent-keys.out
fi
# Require SSH agent forwarding for signing.
if [ -z "${SSH_AUTH_SOCK:-}" ]; then
fail "SSH agent not available in the devcontainer. Start ssh-agent on your machine, load your key with ssh-add, then reopen the devcontainer."
fi

first_pubkey="$(head -n 1 /tmp/ssh-agent-keys.out)"
printf "%s %s\n" "${git_email}" "${first_pubkey}" > /home/vscode/.ssh/allowed_signers
printf "%s\n" "${first_pubkey}" > /home/vscode/.ssh/signing_key.pub
agent_keys_file="$(mktemp)"
trap 'rm -f "${agent_keys_file}"' EXIT

git config --global gpg.format ssh
git config --global commit.gpgsign true
git config --global gpg.ssh.allowedSignersFile /home/vscode/.ssh/allowed_signers
git config --global user.signingkey /home/vscode/.ssh/signing_key.pub
if ! ssh-add -L >"${agent_keys_file}" 2>/dev/null; then
fail "Could not read SSH keys from agent. Make sure your key is loaded on your machine with ssh-add, then reopen the devcontainer."
fi

rm -f /tmp/ssh-agent-keys.out
if ! grep -qE '^ssh-' "${agent_keys_file}"; then
fail "SSH agent is running but has no keys loaded. Run ssh-add ~/.ssh/id_ed25519 on your machine, then reopen the devcontainer."
fi

first_pubkey="$(head -n 1 "${agent_keys_file}")"

# Enforce SSH commit signing.
git config --global gpg.format ssh
git config --global commit.gpgsign true

mkdir -p /home/vscode/.config/git /home/vscode/.ssh
printf '%s\n' "${first_pubkey}" > /home/vscode/.ssh/devcontainer_signing_key.pub
chmod 600 /home/vscode/.ssh/devcontainer_signing_key.pub
git config --global user.signingkey /home/vscode/.ssh/devcontainer_signing_key.pub

# Useful for local verification output.
printf '%s <%s> %s\n' "${git_name}" "${git_email}" "${first_pubkey}" > /home/vscode/.config/git/allowed_signers
chmod 600 /home/vscode/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile /home/vscode/.config/git/allowed_signers

log "Git identity and SSH signing configured"

# Ensure Go-related caches exist and are writable by vscode
log "Ensuring Go cache directories exist"
sudo mkdir -p \
/home/vscode/.cache/go-build \
/home/vscode/.cache/goimports \
/home/vscode/.cache/golangci-lint

# Fix ownership for workspace and cache roots used by tooling
if [ -d "${workspace_dir}" ]; then
log "Fixing ownership for workspace and cache directories"
log "Fixing ownership for workspace and home directories"
sudo chown -R vscode:vscode "${workspace_dir}" /home/vscode || true
else
log "Workspace directory not found; fixing ownership for cache only"
log "Workspace directory not found; fixing ownership for home only"
sudo chown -R vscode:vscode /home/vscode || true
fi

# Persist the ~/.claude.json file by making it a symlink (this trick can be used for other potenial config file in the home folder as well)
touch /home/vscode/persisted-home/.claude.json
rm -f /home/vscode/.claude.json && ln -s /home/vscode/persisted-home/.claude.json /home/vscode/.claude.json

log "post-create completed"
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,9 @@ charts/gitops-reverser/config/*.yaml
*.o.yaml
*.ignore.*
.stamps/*
test/e2e/setup/manifests/cloudflared-public/tunnel-credentials.yaml

# For now we dont do sops (will add that later)
*tunnel-credentials.yaml
*pull-secret.yaml
loadtest
6 changes: 5 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -368,11 +368,15 @@ linters:
source: 'github\.com/onsi/(ginkgo|gomega)'
linters: [revive]
- text: "should not use dot imports"
path: 'test/utils/.*\.go'
path: 'test/(utils|e2e)/.*\.go'
linters: [staticcheck]
# Allow fmt.Print* in test/e2e (debugging outputs)
- path: 'test/e2e/.*\.go'
linters: [forbidigo]
# Allow the standalone load test CLI to use console output, pseudo-random
# traffic generation, and operator-supplied target URLs.
- path: 'test/loadtest/.*\.go'
linters: [depguard, forbidigo, gochecknoglobals, gosec, mnd]
# Allow fmt.Println in metrics exporter (initialization logs)
- path: 'internal/telemetry/.*\.go'
linters: [forbidigo]
Expand Down
101 changes: 85 additions & 16 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -197,9 +197,13 @@ $(CS)/$(NAMESPACE)/prepare-e2e.ready: $(CS)/$(NAMESPACE)/$(INSTALL_MODE)/install
| $(CS)/$(NAMESPACE)
touch $@

# Keep `make test-e2e` as the classic entrypoint.
.PHONY: test-e2e
test-e2e: $(CS)/e2e.passed
test-e2e: ## Run the full e2e test suite
export CTX=$(CTX)
export INSTALL_MODE=$(INSTALL_MODE)
export NAMESPACE=$(NAMESPACE)
export E2E_AGE_KEY_FILE=$(CS)/age-key.txt
go test ./test/e2e/ -v -ginkgo.v

.PHONY: lint
lint: ## Run golangci-lint linter
Expand Down Expand Up @@ -320,10 +324,14 @@ $(CS)/ready: test/e2e/cluster/start-cluster.sh | $(CS)
touch $@

FLUX_SERVICES_INPUTS = $(shell find $(FLUX_SERVICES_DIR) -type f)
LAST_STEP_MANIFESTS_DIR = test/e2e/setup/manifests
LAST_STEP_MANIFESTS = $(shell find $(LAST_STEP_MANIFESTS_DIR) -type f -name '*.yaml')
SHARED_MANIFESTS_DIR = test/e2e/setup/manifests
SHARED_MANIFESTS = $(shell find $(SHARED_MANIFESTS_DIR) -type f -name '*.yaml')
DEMO_MANIFESTS_DIR = test/e2e/setup/demo-only
DEMO_MANIFESTS = $(shell find $(DEMO_MANIFESTS_DIR) -type f -name '*.yaml')
DEMO_TUNNEL_CREDENTIALS = $(DEMO_MANIFESTS_DIR)/cloudflared-public/tunnel-credentials.yaml
DEMO_PULL_SECRET = $(DEMO_MANIFESTS_DIR)/vote/pull-secret.yaml
FLUX_SETUP_READY_INPUTS = $(CS)/flux.installed $$(FLUX_SERVICES_INPUTS)
SERVICES_READY_INPUTS = $(CS)/flux-setup.ready $$(LAST_STEP_MANIFESTS)
SERVICES_READY_INPUTS = $(CS)/flux-setup.ready $$(SHARED_MANIFESTS)
$(CS)/flux.installed: $(CS)/ready | $(CS)
$(FLUX) install --context $(CTX) --namespace $(FLUX_NAMESPACE)
kubectl --context $(CTX) -n $(FLUX_NAMESPACE) wait \
Expand Down Expand Up @@ -365,7 +373,31 @@ $(CS)/flux-setup.ready: $(FLUX_SETUP_READY_INPUTS) | $(CS)
# Aggregate stamp for external E2E services required by tests.
$(CS)/services.ready: $(SERVICES_READY_INPUTS) | $(CS)
kubectl --context $(CTX) wait --for=condition=Established crd/prometheuses.monitoring.coreos.com crd/servicemonitors.monitoring.coreos.com --timeout=180s
kubectl --context $(CTX) apply -R -f $(LAST_STEP_MANIFESTS_DIR)
kubectl --context $(CTX) apply -k $(SHARED_MANIFESTS_DIR)
touch $@

.PHONY: prepare-e2e-demo
prepare-e2e-demo: $(CS)/demo.ready ## Prepare demo-only prerequisites for talk/demo e2e runs

$(CS)/demo.ready: $(CS)/services.ready $(CS)/$(NAMESPACE)/prepare-e2e.ready $$(DEMO_MANIFESTS) | $(CS)
$(call REQUIRE_FILE,$(DEMO_TUNNEL_CREDENTIALS),demo tunnel credentials,Create it from '$(DEMO_TUNNEL_CREDENTIALS).example' and set stringData.token before running this target.)
$(call REQUIRE_FILE,$(DEMO_PULL_SECRET),demo image pull secret,Create it from '$(DEMO_PULL_SECRET).example' before running this target.)
kubectl --context $(CTX) apply -f $(DEMO_MANIFESTS_DIR)/vote/ns.yaml
kubectl --context $(CTX) apply -f $(DEMO_MANIFESTS_DIR)/cloudflared-public/ns.yaml
kubectl --context $(CTX) wait --for=jsonpath='{.status.phase}'=Active \
namespace/vote \
namespace/cloudflared-public \
--timeout=120s
kubectl --context $(CTX) apply -f $(DEMO_MANIFESTS_DIR)/vote/crds
kubectl --context $(CTX) wait --for=condition=Established \
crd/quizsessions.examples.configbutler.ai \
crd/quizsubmissions.examples.configbutler.ai \
--timeout=180s
kubectl --context $(CTX) apply -f $(DEMO_MANIFESTS_DIR)/podinfo-kro-template.yaml
kubectl --context $(CTX) wait --for=condition=Ready \
resourcegraphdefinition/podinfo-app \
--timeout=120s
kubectl --context $(CTX) apply -k $(DEMO_MANIFESTS_DIR)
touch $@

# Step 1: Generate age key file — no cluster/namespace dependency; safe to run before installation.
Expand Down Expand Up @@ -453,16 +485,6 @@ portforward-ensure: $(CS)/services.ready ## Ensure port-forwards are running (al
export VALKEY_PORT=$(VALKEY_PORT)
bash hack/e2e/setup-port-forwards.sh

E2E_TEST_INPUTS = $(CS)/age-key.txt \
$(shell find test/e2e -type f \( -name '*.go' -o -name '*.sh' -o -name '*.yaml' -o -name '*.tmpl' \)) \
$(wildcard hack/e2e/*.sh)
$(CS)/e2e.passed: $$(E2E_TEST_INPUTS) Makefile | $(CS)
export CTX=$(CTX)
export INSTALL_MODE=$(INSTALL_MODE)
export NAMESPACE=$(NAMESPACE)
export E2E_AGE_KEY_FILE=$(CS)/age-key.txt
go test ./test/e2e/ -v -ginkgo.v
touch $@

.PHONY: install install-helm install-plain-manifests-file install-config-dir
install: $(CS)/$(NAMESPACE)/$(INSTALL_MODE)/install.yaml
Expand Down Expand Up @@ -490,6 +512,14 @@ define UPDATE_IF_CHANGED
fi
endef

define REQUIRE_FILE
@[ -f "$(1)" ] || { \
echo "ERROR: missing required $(2) file '$(1)'" >&2; \
echo "$(3)" >&2; \
exit 1; \
}
endef

##@ Deployments of all manifests needed to run
$(CS)/$(NAMESPACE)/helm/install.yaml: $(CS)/services.ready $(HELM_SYNC_OUTPUTS) $$(CHART_INPUTS) | $(CS)/$(NAMESPACE)/helm
$(DO_CLEANUP_INSTALLS)
Expand Down Expand Up @@ -554,6 +584,18 @@ test-e2e-quickstart-helm: ## Run quickstart smoke test (Helm install)
export E2E_AGE_KEY_FILE=$(CS)/age-key.txt
go test ./test/e2e/ -v -ginkgo.v -ginkgo.label-filter=quickstart-framework

.PHONY: test-e2e-demo
test-e2e-demo: prepare-e2e-demo ## Prepare a reusable demo repo and leave demo resources in place
export CTX=$(CTX)
export REPO_NAME=demo
rm -rf .stamps/repos/$$REPO_NAME
export INSTALL_MODE=$(INSTALL_MODE)
export NAMESPACE=$(NAMESPACE)
export E2E_ENABLE_DEMO=true
export E2E_AGE_KEY_FILE=$(CS)/age-key.txt
export TESTNAMESPACE=vote
go test ./test/e2e/ -v -ginkgo.v -ginkgo.label-filter=demo

.PHONY: test-e2e-audit-redis
test-e2e-audit-redis: ## Run dedicated audit->Redis enqueue e2e scenario
export CTX=$(CTX)
Expand Down Expand Up @@ -584,6 +626,33 @@ test-e2e-quickstart-manifest: ## Run quickstart smoke test (manifest install)



##@ Demo

# Join code shown on the presenter screen. Auto-extracted from the auth-service log
# when not provided, so `make loadtest` just works from inside the devcontainer.
# Override at the command line: make loadtest LOADTEST_CODE=xxxx
LOADTEST_CODE ?= $(shell kubectl -n vote logs deploy/vote-auth-service --tail=5 2>/dev/null | grep 'join-code:' | tail -1 | awk '{print $$NF}' | cut -d= -f2)
LOADTEST_USERS ?= 200
LOADTEST_RAMP ?= 20s
LOADTEST_BASE_URL ?= https://demo.configbutler.ai
LOADTEST_SESSION ?= kubecon-2026
LOADTEST_NS ?= vote

.PHONY: loadtest
loadtest: ## Simulate LOADTEST_USERS participants against the live quiz (override: LOADTEST_CODE=xxxx LOADTEST_USERS=50 LOADTEST_RAMP=30s)
@[ -n "$(LOADTEST_CODE)" ] || { \
echo "ERROR: could not determine join code automatically." >&2; \
echo "Pass it explicitly: make loadtest LOADTEST_CODE=xxxx" >&2; \
exit 1; \
}
go run ./test/loadtest \
--code $(LOADTEST_CODE) \
--users $(LOADTEST_USERS) \
--ramp-duration $(LOADTEST_RAMP) \
--base-url $(LOADTEST_BASE_URL) \
--session $(LOADTEST_SESSION) \
--namespace $(LOADTEST_NS)

##@ Cleanup
.PHONY: clean
clean: ## Remove build artifacts (binaries, coverage, generated dist files, build stamps)
Expand Down
Loading
Loading