Stage 5: installer chart + release infra, kind e2e pass through the chart
Release / test (push) Successful in 2m48s
CI / chart (push) Successful in 1s
CI / security (push) Successful in 1m3s
CI / test (push) Successful in 2m5s
Release / chart (push) Successful in 4s
Release / image (push) Successful in 7m6s
Release / scan-image (push) Failing after 33s

Chart (charts/terdut-operator) generated via kubebuilder's own helm/v2-alpha
plugin from config/'s kustomize output -- CRDs + manager Deployment/RBAC
come from the same markers every other stage already generates, one source
of truth. Hand-added on top: the optional terdutServer values block
(DESIGN.md §10's "helm install and get a server" path, off by default) and
the release-skill plumbing -- .release.conf, release-vars/helm-lint/push/
helm-package/helm-push/release Makefile targets, .gitea/workflows/release.yaml
(test -> image/chart -> scan-image) -- mirroring terdut-server's own shape
(registry/namespace convention, multi-arch buildx push, trivy/govulncheck/
gitleaks scans). ci.yaml gains security and chart jobs to match.

Two real issues caught while wiring this, fixed before either shipped:
- Dockerfile's builder stage didn't pin --platform=$BUILDPLATFORM, which
  would have made a multi-arch release build fail outright on this org's
  runners (no binfmt registration) -- same fix terdut-server's own
  Dockerfile already needed for the same reason.
- govulncheck found one real, reachable finding: google.golang.org/grpc
  v1.82.1 (transitive via controller-runtime's otel exporter), fixed by
  bumping to v1.83.1.

Full golden-path kind e2e pass, this time through `helm install` rather than
raw kustomize: TerdutServer (real terdut-server v0.33.0 image) -> TerdutTeam
-> one of each child kind, each confirmed Ready and then independently
confirmed against terdut-server's own API from inside the cluster (not just
the operator's own status). Deleted every CR in reverse order and confirmed
server-side cleanup the same independent way for all three child kinds, the
team, and the server. No new bugs found -- Stage 1's own kind pass already
caught what a real cluster catches that envtest can't.

Also dropped the kubebuilder helm plugin's default .github/workflows/
scaffold, same as Stage 0 already did for the main scaffold: this org runs
on Gitea, not GitHub.

Not done here, deliberately: an actual tagged release. release-preflight
found no terdut-operator/ entry under Ryuvia/charts yet to bump -- that
one-time wrapper bootstrap is a decision about deploying this operator for
real, not a side effect of finishing this stage.

make fmt lint test helm-lint build all clean.
This commit is contained in:
Niklas Ye
2026-10-01 14:47:10 +02:00
parent 048f4448c4
commit b4ccdb09d5
50 changed files with 3190 additions and 22 deletions
+51 -3
View File
@@ -78,6 +78,54 @@ jobs:
- name: Format, lint and test - name: Format, lint and test
run: make fmt lint test run: make fmt lint test
# No `chart` job yet -- there's no Helm chart until Stage 6 (ROADMAP.md). No # Runs on every push and pull request, unlike the image scan, which needs something
# `security` job yet either (govulncheck/gitleaks, as terdut-server has); add one # published to scan and so lives in release.yaml -- same split as terdut-server's.
# alongside `test` once there's controller code worth scanning. # govulncheck reads the source and its module graph, gitleaks reads the working
# tree; neither sees what the other does.
security:
runs-on: ubuntu-latest
container:
image: golang:1.26.6-bookworm
volumes:
- go-mod-cache:/go/pkg/mod
- go-build-cache:/root/.cache/go-build
- gobin-cache:/go/bin
steps:
- name: Checkout
env:
REF_NAME: ${{ github.ref_name }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if [ -n "$HEAD_SHA" ]; then
git clone "$REPO_URL" .
git checkout -q "$HEAD_SHA"
else
git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
fi
- name: Go vulnerability scan (govulncheck)
run: make security-go
- name: Secret scan (gitleaks)
run: make security-secrets
# Host mode, no `container:`: helm is baked into the runner image, and a container
# job could not install it -- get.helm.sh is unreachable from the dind bridge, same
# reason terdut-server's own chart job runs on the host.
chart:
runs-on: ubuntu-latest
steps:
- name: Checkout
env:
REF_NAME: ${{ github.ref_name }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if [ -n "$HEAD_SHA" ]; then
git clone "$REPO_URL" .
git checkout -q "$HEAD_SHA"
else
git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
fi
- name: Lint and render the chart
run: make helm-lint
+127
View File
@@ -0,0 +1,127 @@
name: Release
# Checkout, interpolation and caching conventions match ci.yaml -- see the header there
# for why there are no JS actions and why every `${{ }}` goes through `env:`.
on:
push:
tags:
- 'v*'
workflow_dispatch:
# A tag is not normally re-pushed, so this mostly matters when one is force-moved during
# a botched release -- the superseded run stops holding runner slots.
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: true
env:
REPO_URL: https://git.ryuvia.com/niklas/terdut-operator.git
REGISTRY: git.ryuvia.com
IMAGE: git.ryuvia.com/niklas/terdut-operator
jobs:
# Gates every publishing job below. A tag that fails here publishes nothing: the
# image and the chart are both downstream of it. No Postgres service, unlike
# terdut-server's: this suite drives envtest (a fake API server), not a real database.
test:
runs-on: ubuntu-latest
container:
image: golang:1.26.6-bookworm
volumes:
- go-mod-cache:/go/pkg/mod
- go-build-cache:/root/.cache/go-build
- gobin-cache:/go/bin
steps:
- name: Checkout
env:
REF_NAME: ${{ github.ref_name }}
run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
- name: Format, lint and test
run: make fmt lint test
# Host mode on purpose (no `container:`): this is the only context with a Docker CLI
# pointed at the dind daemon. A `container:` job would sit on the dind bridge with no
# docker socket at all -- same reason terdut-server's image job runs on the host.
image:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout
env:
REF_NAME: ${{ github.ref_name }}
run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
- name: Log in to the registry
env:
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: echo "$TOKEN" | docker login "$REGISTRY" -u niklas --password-stdin
# buildx setup, the platform list and why there is no QEMU all live on the `push`
# target now, so the same command publishes from a laptop and from here.
- name: Build and push
env:
REF_NAME: ${{ github.ref_name }}
run: make push VERSION="$REF_NAME"
# Also host mode: helm is baked into the runner image, and a `container:` job could
# not install it -- get.helm.sh is unreachable from the dind bridge.
chart:
needs: test
runs-on: ubuntu-latest
steps:
- name: Checkout
env:
REF_NAME: ${{ github.ref_name }}
run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
# One publisher, triggered by the tag -- same reasoning as terdut-server's own
# chart job: a workflow triggered by the main push cannot know the version it is
# about to be tagged with, so there is no second publisher racing this one.
- name: Refuse a non-version tag
env:
REF_NAME: ${{ github.ref_name }}
run: |
set -eu
if ! echo "$REF_NAME" | grep -qE '^v[0-9]'; then
echo "::error::refusing to publish a chart for non-version tag ${REF_NAME}"
exit 1
fi
# Render before publishing, so a template that does not compile is found here,
# not by Flux after the chart is already in the registry.
- name: Lint and render the chart
run: make helm-lint
- name: Package and push
env:
REF_NAME: ${{ github.ref_name }}
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
set -eu
echo "$TOKEN" | helm registry login "$REGISTRY" -u niklas --password-stdin
make helm-package helm-push VERSION="$REF_NAME"
# Host mode, like image and chart: this needs a docker daemon to run trivy in, and a
# `container:` job would sit on the dind bridge with none.
#
# Scans the pushed image, not a local one -- trivy cannot read a locally built image
# on this runner (no docker socket in a container job, no shared filesystem with the
# dind sidecar), so it pulls from the registry. Runs after `image` rather than gating
# it: a red scan does not unpublish anything -- it means do not bump the wrapper
# chart in Ryuvia/charts to this version. This pipeline does not deploy.
scan-image:
needs: image
runs-on: ubuntu-latest
steps:
- name: Checkout
env:
REF_NAME: ${{ github.ref_name }}
run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" .
- name: Scan the pushed image (trivy)
env:
TRIVY_USERNAME: niklas
TRIVY_PASSWORD: ${{ secrets.REGISTRY_TOKEN }}
REF_NAME: ${{ github.ref_name }}
run: make security-image VERSION="$REF_NAME"
+4
View File
@@ -13,3 +13,7 @@ bin/
# local kubeconfig/secrets some workflows write here # local kubeconfig/secrets some workflows write here
*.kubeconfig *.kubeconfig
cover.out cover.out
# scratch output: build-installer's consolidated manifest and packaged charts
# (release-vars' HELM_CHART is charts/terdut-operator, committed; this is not)
/dist/
+21
View File
@@ -0,0 +1,21 @@
# Read by the `release` skill (~/.claude/skills/release).
#
# Only what the Makefile cannot already say. IMAGE, HELM_CHART and HELM_REPO come from
# `make release-vars`, so they have one definition and cannot drift from what is built.
#
# Defaults, set here only where this repo differs:
# CHARTS_REPO=$HOME/git/charts CHARTS_DIR=<image basename>
# GITEA_LOGIN=Ryuvia APPVERSION_PREFIX=
# PROSE_LANG=en
# Same as the image basename, so this is only stated to be read rather than derived.
CHARTS_DIR=terdut-operator
# This repo writes appVersion: "v0.1.0" (see charts/terdut-operator/Chart.yaml),
# matching terdut-server's own v-prefixed style -- nothing reads the field, but people
# do, and it should say the same thing the release tag does.
APPVERSION_PREFIX=v
# English, for the same reason as terdut-server: this is an on-call tool's operator,
# and nothing about its labels, API or docs is coupled to Swedish.
PROSE_LANG=en
+33 -12
View File
@@ -7,19 +7,40 @@ short pitch.
## Checks ## Checks
`make fmt lint test` is the CI gate (`.gitea/workflows/ci.yaml` calls these targets `make fmt lint test helm-lint` is the CI gate (`.gitea/workflows/ci.yaml`'s `test`,
rather than restating them, same convention as terdut-server). `test` chains through `security` and `chart` jobs call these targets rather than restating them, same
the Kubebuilder-scaffolded `manifests`/`generate` (`controller-gen`) and `setup-envtest` convention as terdut-server). `test` chains through the Kubebuilder-scaffolded
targets automatically — everything needed lands in `bin/` (gitignored) on first run, no `manifests`/`generate` (`controller-gen`) and `setup-envtest` targets automatically —
separate tool install required beyond Go itself and network access to everything needed lands in `bin/` (gitignored) on first run, no separate tool install
`proxy.golang.org`/`storage.googleapis.com`. required beyond Go itself and network access to `proxy.golang.org`/`storage.googleapis.com`.
`security` runs `make security-go`/`security-secrets` (govulncheck/gitleaks), same as
terdut-server's own `security` job.
`make test-e2e` stands up a real `kind` cluster (`kind`/`docker` must be installed) and The kubebuilder-scaffolded `make test-e2e` (a disposable, generic smoke test) is separate
is not part of the CI gate yet — it has no service-image to test against until later from the real golden-path `kind` e2e pass ROADMAP.md's Stage 5 describes (create every CRD
ROADMAP stages produce one. kind, verify against terdut-server's own API, delete, verify gone) — the latter is a
manual pass run and recorded in ROADMAP.md, not a CI job, matching Stage 1-4's own
precedent of validating against a real cluster outside CI.
## Release ## Release
Not wired yet. `.release.conf` and the release-vars Makefile target land in ROADMAP.md's Wired as of Stage 5 (ROADMAP.md): `.release.conf`, `make release-vars`/`helm-lint`/
Stage 6, once there's an actual Helm chart to release — see that file before assuming `push`/`helm-package`/`helm-push`/`release`, and `.gitea/workflows/release.yaml`
the `release` skill's terdut-server/terdut-tui conventions already apply here. (`test` → `image`/`chart` → `scan-image`) all follow terdut-server's established shape —
see that repo's Makefile/`.release.conf` for the shared reasoning, not restated here.
The chart is `charts/terdut-operator` (via kubebuilder's own `helm/v2-alpha` plugin,
regenerate with `kubebuilder edit --plugins helm.kubebuilder.io/v2-alpha --output-dir
charts --force` after `config/` changes, then re-review — `--force` does not touch
`Chart.yaml` but does touch `values.yaml`, which carries hand-written additions, most
importantly the optional `terdutServer` block, DESIGN.md §10). It installs the operator +
CRDs + RBAC, and optionally one `TerdutServer` CR (`terdutServer.enabled`, off by default).
**One manual step the release skill's own automation does not cover**: `release-preflight`
expects an existing `terdut-operator/` entry under `Ryuvia/charts` to bump on release
(steps 8-10 of the skill). There is no such entry yet — this repo's first-ever release
can publish its own image and chart (the `test`/`image`/`chart`/`scan-image` jobs), but
the wrapper-chart bump and PR will fail until someone creates that initial wrapper entry
in `Ryuvia/charts` by hand, the same one-time step every other onboarded repo already had
done for it before its own first release. That's a deliberate decision to deploy this
operator for real, not something to do as a side effect of finishing this stage.
+6 -1
View File
@@ -1,7 +1,12 @@
# Build the manager binary # Build the manager binary
# Override BASE_IMAGE to build from another registry, e.g. docker.io/library/golang:1.26 # Override BASE_IMAGE to build from another registry, e.g. docker.io/library/golang:1.26
ARG BASE_IMAGE=golang:1.26 ARG BASE_IMAGE=golang:1.26
FROM ${BASE_IMAGE} AS builder # --platform=$BUILDPLATFORM pins the builder to the machine doing the building, so a
# multi-arch build compiles both targets natively instead of running an emulated arm64
# toolchain under QEMU -- same reasoning, and the same fix, as terdut-server's own
# Dockerfile: the CI runner has no binfmt registration and no way to get one, so
# this is not just an optimization, it is what makes the arm64 image buildable at all.
FROM --platform=$BUILDPLATFORM ${BASE_IMAGE} AS builder
ARG TARGETOS ARG TARGETOS
ARG TARGETARCH ARG TARGETARCH
+187
View File
@@ -259,3 +259,190 @@ endef
define gomodver define gomodver
$(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null) $(shell go list -m -f '{{if .Replace}}{{.Replace.Version}}{{else}}{{.Version}}{{end}}' $(1) 2>/dev/null)
endef endef
##@ Helm Deployment
## Helm binary to use for deploying the chart
HELM ?= helm
## Namespace to deploy the Helm release
HELM_NAMESPACE ?= terdut-operator-system
## Name of the Helm release
HELM_RELEASE ?= terdut-operator
## Path to the Helm chart directory. Must stay equal to HELM_CHART below
## (the release process's own name for this same path) -- two variables
## because this one is kubebuilder's own scaffold and that one is the
## release skill's contract, not because the path differs.
HELM_CHART_DIR ?= charts/terdut-operator
## Additional arguments to pass to helm commands
HELM_EXTRA_ARGS ?=
.PHONY: install-helm
install-helm: ## Install the latest version of Helm.
@command -v $(HELM) >/dev/null 2>&1 || { \
echo "Installing Helm..." && \
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4 | bash; \
}
.PHONY: helm-deploy
helm-deploy: install-helm ## Deploy manager to the K8s cluster via Helm. Specify an image with IMG.
IMG="$(IMG)"; $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART_DIR) \
--namespace $(HELM_NAMESPACE) \
--create-namespace \
--set manager.image.repository=$${IMG%:*} \
--set manager.image.tag=$${IMG##*:} \
--wait \
--timeout 5m \
$(HELM_EXTRA_ARGS)
.PHONY: helm-uninstall
helm-uninstall: ## Uninstall the Helm release from the K8s cluster.
$(HELM) uninstall $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)
.PHONY: helm-status
helm-status: ## Show Helm release status.
$(HELM) status $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)
.PHONY: helm-history
helm-history: ## Show Helm release history.
$(HELM) history $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)
.PHONY: helm-rollback
helm-rollback: ## Rollback to previous Helm release.
$(HELM) rollback $(HELM_RELEASE) --namespace $(HELM_NAMESPACE)
##@ Release
# Everything below is read by the `release` skill (~/.claude/skills/release), not by
# anything above it in this file -- mirrors terdut-server's Makefile section for
# section, adapted for this repo (no Postgres service for tests, no CLI binaries to
# cross-compile, one image + one chart to publish).
REGISTRY := git.ryuvia.com
# The personal namespace, not ryuvia -- same reasoning as terdut-server: Gitea scopes
# package visibility to the owner with no per-package override, so publishing here
# keeps the image and chart anonymously pullable and Flux needs no registry
# credentials to pull them.
OWNER := niklas
IMAGE := $(REGISTRY)/$(OWNER)/terdut-operator
HELM_CHART := charts/terdut-operator
HELM_REPO := oci://$(REGISTRY)/$(OWNER)
.PHONY: release-vars
release-vars: ## Print the variables the release process reads
@printf 'IMAGE=%s\nHELM_CHART=%s\nHELM_REPO=%s\n' '$(IMAGE)' '$(HELM_CHART)' '$(HELM_REPO)'
# database is required (dsn xor postgresClusterRef, DatabaseSpec's own CEL rule) once
# terdutServer.enabled, so the chart's own `required` calls fail a bare
# `--set terdutServer.enabled=true` the same way a real install without a database
# would be rejected at apply time -- this set gives that path something valid to
# render against, the way terdut-server's own HELM_LINT_SET supplies its one
# required field (database.dsn) for the same reason.
HELM_LINT_SET = --set terdutServer.enabled=true \
--set terdutServer.image.tag=v0.0.0 \
--set terdutServer.networking.hostname=terdut.example.invalid \
--set 'terdutServer.database.dsn=postgres://terdut@terdut-postgres:5432/terdut?sslmode=require'
.PHONY: helm-lint
helm-lint: ## Lint and render the chart
helm lint $(HELM_CHART)
helm template terdut-operator $(HELM_CHART) --namespace terdut-operator-system >/dev/null
# Second pass: the optional TerdutServer CR template (off by default, so the
# bare render above never exercises it at all).
helm lint $(HELM_CHART) $(HELM_LINT_SET)
helm template terdut-operator $(HELM_CHART) --namespace terdut-operator-system \
$(HELM_LINT_SET) >/dev/null
## --- publishing ---
#
# Exist so .gitea/workflows/release.yaml can call `make push` / `make helm-package
# helm-push` instead of restating the build in YAML -- one definition, runnable
# locally, reviewable in a diff. Publishing happens by pushing a tag; nothing else.
#
# VERSION is the git tag, passed in by the workflow. require-version is why a stray
# local `make push` cannot publish: dev is not a version anyone releases.
VERSION ?= dev
# Helm requires strict SemVer -- strip a leading 'v' if present.
CHART_VERSION := $(shell echo "$(VERSION)" | sed 's/^v//')
# Named RELEASE_PLATFORMS, not PLATFORMS: that name is already taken above by
# kubebuilder's own docker-buildx target (a 4-platform list this repo's own release
# process doesn't use), and `?=` on an already-set variable is a silent no-op, not an
# override -- reusing it here would have quietly built s390x/ppc64le instead of the
# two platforms this target actually intends.
RELEASE_PLATFORMS ?= linux/amd64,linux/arm64
BUILDX_BUILDER ?= terdut-operator-release
# An isolated repo list, same reasoning as terdut-server's: the machine-wide one is
# not this build's business, and one unreachable entry in it aborts otherwise-fine
# helm commands. HELM_REPOSITORY_CACHE is deliberately NOT overridden alongside it --
# helm writes a refreshed index to the default cache and then looks for it there.
HELM_ISOLATED = HELM_REPOSITORY_CONFIG=$(CURDIR)/.helm-repos.yaml
.PHONY: require-version
require-version:
@test "$(VERSION)" != "dev" || \
(echo "VERSION=dev names no release -- pass VERSION=vX.Y.Z (the workflow passes the tag)" && exit 1)
# Multi-arch, so this is build-and-push in one step, same reasoning as
# terdut-server's own `push`: buildx cannot load a multi-platform result into the
# local image store, so there is no separate local-only `build` target here either.
#
# No QEMU: the Dockerfile's builder stage runs on $$BUILDPLATFORM and cross-compiles
# via GOOS/GOARCH, so both platforms build natively -- same fix as terdut-server's
# Dockerfile, for the same reason (this CI runner has no binfmt registration).
.PHONY: push
push: require-version ## Build and publish the multi-arch image
docker buildx create --name $(BUILDX_BUILDER) --use 2>/dev/null || docker buildx use $(BUILDX_BUILDER)
docker buildx build \
--platform $(RELEASE_PLATFORMS) \
--tag "$(IMAGE):latest" \
--tag "$(IMAGE):$(VERSION)" \
--push .
# --version/--app-version come from the tag, so Chart.yaml's own fields decide
# nothing about what gets published -- same as terdut-server's chart.
.PHONY: helm-package
helm-package: require-version ## Package the chart, versioned from the tag
$(HELM_ISOLATED) helm package $(HELM_CHART) \
--version $(CHART_VERSION) \
--app-version $(VERSION) \
--destination dist
.PHONY: helm-push
helm-push: require-version ## Push the packaged chart to the OCI registry
$(HELM_ISOLATED) helm push dist/terdut-operator-$(CHART_VERSION).tgz $(HELM_REPO)
.PHONY: release
release: push helm-package helm-push ## Publish image + chart (the workflow's one call)
## --- security ---
GOVULNCHECK_VERSION := v1.1.4
GITLEAKS_VERSION := v8.30.0
TRIVY_VERSION := 0.73.0
# Symbol-level, not dependency-level, same as terdut-server: govulncheck reports a
# vulnerability only when the code can actually reach it.
.PHONY: security-go
security-go: ## Scan Go deps for known CVEs (govulncheck)
go run golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION) ./...
# --no-git scans the working tree rather than the history, so this catches a secret
# on the way in; it says nothing about what is already committed.
.PHONY: security-secrets
security-secrets: ## Scan the working tree for committed secrets (gitleaks)
go run github.com/zricethezav/gitleaks/v8@$(GITLEAKS_VERSION) detect --no-git \
--source . --redact --no-banner --exit-code 1
# Scans the pushed image, not a local one -- trivy cannot read a locally built image
# on this runner, same reasoning as terdut-server. A red scan means: do not bump the
# wrapper chart in Ryuvia/charts to this version; it does not unpublish anything.
.PHONY: security-image
security-image: require-version ## Scan the pushed image for CVEs (needs VERSION)
docker run --rm -e TRIVY_USERNAME -e TRIVY_PASSWORD \
-v trivy-cache:/root/.cache/trivy \
docker.io/aquasec/trivy:$(TRIVY_VERSION) image --severity HIGH,CRITICAL \
--ignore-unfixed --exit-code 1 $(IMAGE):$(VERSION)
+4
View File
@@ -6,6 +6,10 @@ cliVersion: 4.16.0
domain: ryuvia.com domain: ryuvia.com
layout: layout:
- go.kubebuilder.io/v4 - go.kubebuilder.io/v4
plugins:
helm.kubebuilder.io/v2-alpha:
manifests: dist/install.yaml
output: charts
projectName: terdut-operator projectName: terdut-operator
repo: git.ryuvia.com/niklas/terdut-operator repo: git.ryuvia.com/niklas/terdut-operator
resources: resources:
+47
View File
@@ -181,6 +181,53 @@ New commits build forward over the old ones; no git history rewrite.
→ one of each child kind → verify via terdut-server's own API that each → one of each child kind → verify via terdut-server's own API that each
object exists with the right shape → delete the CR → verify the object exists with the right shape → delete the CR → verify the
server-side object is gone. server-side object is gone.
- Chart built via kubebuilder's own `helm/v2-alpha` plugin from `config/`'s
kustomize output (`charts/terdut-operator`), not hand-rolled -- CRDs +
manager Deployment/RBAC come from the same markers/manifests every other
stage already generates, so there's exactly one source of truth for
them. Hand-added on top: the optional `terdutServer` values block (§10's
"helm install and get a server" path), `.release.conf`, and the
`release-vars`/`helm-lint`/`push`/`helm-package`/`helm-push`/`release`
Makefile targets `.gitea/workflows/release.yaml` calls, mirroring
terdut-server's own shape end to end (same registry/namespace
convention, same multi-arch buildx push, same trivy/govulncheck/gitleaks
scans). Also fixed while wiring this: the Dockerfile's builder stage
didn't pin `--platform=$BUILDPLATFORM`, which would have made a
multi-arch release build fail outright on this org's runners (no binfmt
registration) -- caught before it ever shipped, not discovered mid-release;
and govulncheck surfaced one real, reachable finding (`google.golang.org/grpc`
v1.82.1, transitive via controller-runtime's otel exporter), fixed by
bumping to v1.83.1.
- **Done, 2026-10-01**: the full golden-path pass above, run for real
against a `kind` cluster, installed via `helm install` (not raw
kustomize/kubectl apply -- the first time the chart itself, not just
`config/`, was exercised): `TerdutServer` (real terdut-server `v0.33.0`
image, bring-your-own DSN against a throwaway in-cluster Postgres) →
`TerdutTeam` → one `TerdutEscalationRule` + `TerdutDeadmanSwitch` +
`TerdutAlertSource`, each confirmed `Ready` and then confirmed a second
way, independent of the operator's own status: a `curl` pod inside the
cluster, authenticated with the generated team credential, hit
terdut-server's real API directly (`GET /api/teams/{id}/escalation`,
`.../deadman/switches`, `.../integrations`) and got back exactly the
policy/switch/integration each spec declared. Deleting every CR in
reverse order was verified the same way: the escalation policy came back
empty (its only available "undo"), the switch and the integration were
both gone from their list endpoints, the team no longer resolved by
name, and the Deployment/Service/every generated Secret were gone from
the cluster. No new bugs found this pass -- Stage 1's own kind e2e pass
already caught the two issues (`events.k8s.io` RBAC, the podman
`.dockerignore` fix) a real cluster catches and `envtest` can't, and
nothing since has touched that surface.
- Not done in this pass, deliberately: an actual tagged release. `make
release-vars`/`helm-lint`/`push`/`helm-package`/`helm-push` all work
locally and `.gitea/workflows/release.yaml` is wired, but
`release-preflight` found there is no `terdut-operator/` entry under
`Ryuvia/charts` yet to bump -- every other onboarded repo had that
one-time wrapper-chart bootstrap done for it before its own first
release, and this one doesn't, since deploying this operator for real is
a decision for whoever runs the cluster, not a side effect of finishing
this stage. Cutting the first real release (and creating that wrapper
entry) is therefore the next action, not yet taken.
## Deferred (§13, unchanged by this roadmap) ## Deferred (§13, unchanged by this roadmap)
+25
View File
@@ -0,0 +1,25 @@
# Patterns to ignore when building Helm packages.
# Operating system files
.DS_Store
# Version control directories
.git/
.gitignore
.bzr/
.hg/
.hgignore
.svn/
# Backup and temporary files
*.swp
*.tmp
*.bak
*.orig
*~
# IDE and editor-related files
.idea/
.vscode/
# Helm chart artifacts
dist/chart/*.tgz
+20
View File
@@ -0,0 +1,20 @@
apiVersion: v2
name: terdut-operator
description: Installs terdut-operator (CRDs + controller) for terdut-server, and optionally one TerdutServer CR
type: application
# These fields decide nothing: `make helm-package` passes --version and
# --app-version from the release tag (same reasoning as terdut-server's own
# chart). They're for whoever reads the tree before a tag exists.
version: 0.1.0
appVersion: "v0.1.0"
keywords:
- kubernetes
- operator
- terdut
home: https://git.ryuvia.com/niklas/terdut-operator
annotations:
kubebuilder.io/generated-by: kubebuilder
@@ -0,0 +1,15 @@
Thank you for installing {{ .Chart.Name }}.
Your release is named {{ .Release.Name }}.
The controller and CRDs have been installed in namespace {{ .Release.Namespace }}.
To verify the installation:
kubectl get pods -n {{ .Release.Namespace }}
kubectl get customresourcedefinitions
To learn more about the release, try:
$ helm status {{ .Release.Name }} -n {{ .Release.Namespace }}
$ helm get all {{ .Release.Name }} -n {{ .Release.Namespace }}
@@ -0,0 +1,63 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "terdut-operator.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "terdut-operator.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Namespace for generated references.
Always uses the Helm release namespace.
*/}}
{{- define "terdut-operator.namespaceName" -}}
{{- .Release.Namespace }}
{{- end }}
{{/*
Resource name with proper truncation for Kubernetes 63-character limit.
Takes a dict with:
- .suffix: Resource name suffix (e.g., "metrics", "webhook")
- .context: Template context (root context with .Values, .Release, etc.)
Dynamically calculates safe truncation to ensure total name length <= 63 chars.
*/}}
{{- define "terdut-operator.resourceName" -}}
{{- $fullname := include "terdut-operator.fullname" .context }}
{{- $suffix := .suffix }}
{{- $maxLen := sub 62 (len $suffix) | int }}
{{- if gt (len $fullname) $maxLen }}
{{- printf "%s-%s" (trunc $maxLen $fullname | trimSuffix "-") $suffix | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" $fullname $suffix | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
ServiceAccount name to use.
When enabled, use the chart's ServiceAccount name.
When disabled, serviceAccount.name must be set; use "default" to pick the namespace default ServiceAccount.
*/}}
{{- define "terdut-operator.serviceAccountName" -}}
{{- if .Values.serviceAccount.enabled }}
{{- include "terdut-operator.resourceName" (dict "suffix" "controller-manager" "context" .) }}
{{- else }}
{{- required "serviceAccount.name is required when serviceAccount.enabled=false (set name: default explicitly to use the namespace default ServiceAccount)" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
@@ -0,0 +1,198 @@
{{- if .Values.crd.enabled }}
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
{{- if .Values.crd.keep }}
"helm.sh/resource-policy": keep
{{- end }}
controller-gen.kubebuilder.io/version: v0.22.0
name: terdutalertsources.terdut.ryuvia.com
spec:
group: terdut.ryuvia.com
names:
kind: TerdutAlertSource
listKind: TerdutAlertSourceList
plural: terdutalertsources
singular: terdutalertsource
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.teamRef.name
name: Team
type: string
- jsonPath: .status.integrationID
name: IntegrationID
type: integer
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: string
- jsonPath: .status.conditions[?(@.type=="Ready")].reason
name: Reason
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: TerdutAlertSource is the Schema for the terdutalertsources API
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: spec defines the desired state of TerdutAlertSource
properties:
kind:
default: alertmanager
description: |-
kind is the alert source type. Only "alertmanager" is supported
today, mirroring terdut-server's own CHECK constraint on
integrations.kind (internal/db/migrations/003_teams.sql) --
confirmed against source, not assumed. Changing it after the
integration already exists rotates the webhook key (DESIGN.md §5's
reconciliation table): the old one is deleted and a fresh one
created, which breaks whatever sends to the old URL until the new
Secret is picked up.
enum:
- alertmanager
type: string
name:
description: |-
name is this source's own display name server-side -- distinct from
this object's own metadata.name. POST
/api/teams/{teamID}/integrations {"name": ...} at creation, and what
PATCH renames thereafter; renaming never rotates the webhook key.
minLength: 1
type: string
teamRef:
description: |-
TerdutTeamRef names the TerdutTeam this resource belongs to. Always
same-namespace as the CR itself (DESIGN.md §1: only TerdutTeam.spec.serverRef
crosses namespaces in v1) -- no namespace field, unlike TerdutServerRef.
properties:
name:
minLength: 1
type: string
required:
- name
type: object
required:
- name
- teamRef
type: object
status:
description: status defines the observed state of TerdutAlertSource
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
integrationID:
description: integrationID is the server-side id.
format: int64
type: integer
lastAppliedKind:
description: |-
lastAppliedKind is the kind the currently-live integration was
actually created with -- compared against spec.kind on every
reconcile to detect the one spec change that requires
delete-and-recreate (DESIGN.md §5), since terdut-server's own API has
no way to read a live integration's kind back for comparison.
type: string
observedGeneration:
format: int64
type: integer
webhookURLSecretRef:
description: |-
webhookURLSecretRef names the generated Secret holding "url" and
"key" -- the integration's webhook address and credential, shown by
terdut-server's API exactly once, at creation (DESIGN.md §4.5), and
never re-readable afterward, including from this status. Lives in
this CR's own namespace with a plain OwnerReference (§7) -- unlike
TerdutServer/TerdutTeam's credential Secrets, this one never crosses
namespaces, so no finalizer cleanup is needed for it specifically.
properties:
name:
description: name is the Secret's name.
minLength: 1
type: string
required:
- name
type: object
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
{{- end }}
@@ -0,0 +1,184 @@
{{- if .Values.crd.enabled }}
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
{{- if .Values.crd.keep }}
"helm.sh/resource-policy": keep
{{- end }}
controller-gen.kubebuilder.io/version: v0.22.0
name: terdutdeadmanswitches.terdut.ryuvia.com
spec:
group: terdut.ryuvia.com
names:
kind: TerdutDeadmanSwitch
listKind: TerdutDeadmanSwitchList
plural: terdutdeadmanswitches
singular: terdutdeadmanswitch
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.teamRef.name
name: Team
type: string
- jsonPath: .status.switchID
name: SwitchID
type: integer
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: string
- jsonPath: .status.conditions[?(@.type=="Ready")].reason
name: Reason
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: TerdutDeadmanSwitch is the Schema for the terdutdeadmanswitches
API
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: spec defines the desired state of TerdutDeadmanSwitch
properties:
matcher:
description: |-
matcher names the alerts this switch watches, e.g.
"alertname=Watchdog,cluster=prod". One matcher per switch -- add
another TerdutDeadmanSwitch instead of separating with ";"
(terdut-server's own restriction, mirrored here so a bad spec is
rejected at apply time).
minLength: 1
type: string
x-kubernetes-validations:
- message: 'one matcher per switch: add another TerdutDeadmanSwitch
instead of separating with ;'
rule: '!self.contains('';'')'
name:
description: |-
name is optional, same as the API: left empty, terdut-server derives
it from matcher's own canonical form, and that's what the
idempotent-create lookup matches against too.
type: string
severity:
default: critical
enum:
- critical
- error
- warning
- info
type: string
teamRef:
description: |-
TerdutTeamRef names the TerdutTeam this resource belongs to. Always
same-namespace as the CR itself (DESIGN.md §1: only TerdutTeam.spec.serverRef
crosses namespaces in v1) -- no namespace field, unlike TerdutServerRef.
properties:
name:
minLength: 1
type: string
required:
- name
type: object
timeout:
description: timeout is a Go duration string, e.g. "15m".
minLength: 1
type: string
required:
- matcher
- teamRef
- timeout
type: object
status:
description: status defines the observed state of TerdutDeadmanSwitch
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
observedGeneration:
format: int64
type: integer
switchID:
description: switchID is the server-side id.
format: int64
type: integer
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
{{- end }}
@@ -0,0 +1,195 @@
{{- if .Values.crd.enabled }}
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
{{- if .Values.crd.keep }}
"helm.sh/resource-policy": keep
{{- end }}
controller-gen.kubebuilder.io/version: v0.22.0
name: terdutescalationrules.terdut.ryuvia.com
spec:
group: terdut.ryuvia.com
names:
kind: TerdutEscalationRule
listKind: TerdutEscalationRuleList
plural: terdutescalationrules
singular: terdutescalationrule
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.teamRef.name
name: Team
type: string
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: string
- jsonPath: .status.conditions[?(@.type=="Ready")].reason
name: Reason
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: TerdutEscalationRule is the Schema for the terdutescalationrules
API
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: spec defines the desired state of TerdutEscalationRule
properties:
fallbackTopic:
type: string
levels:
items:
description: |-
EscalationLevel is one rung of the ladder: how long to wait, and who to
page if nobody's acknowledged by then.
properties:
targets:
items:
description: |-
EscalationTarget is one page within a level. username is required iff
kind is "user" (terdut-server's own validation, internal/api/escalation.go's
handleSetEscalation -- mirrored here as a CEL rule so a bad spec is
rejected at apply time, not discovered on the next failed PUT).
properties:
kind:
description: EscalationTargetKind is who one rung of the
ladder pages.
enum:
- oncall
- user
type: string
username:
type: string
required:
- kind
type: object
x-kubernetes-validations:
- message: username is required when kind is user
rule: self.kind != 'user' || has(self.username)
- message: username must not be set when kind is oncall
rule: self.kind != 'oncall' || !has(self.username)
minItems: 1
type: array
timeout:
description: timeout is a Go duration string, e.g. "5m".
minLength: 1
type: string
required:
- targets
- timeout
type: object
minItems: 1
type: array
repeatCount:
format: int64
maximum: 10
minimum: 0
type: integer
teamRef:
description: |-
TerdutTeamRef names the TerdutTeam this resource belongs to. Always
same-namespace as the CR itself (DESIGN.md §1: only TerdutTeam.spec.serverRef
crosses namespaces in v1) -- no namespace field, unlike TerdutServerRef.
properties:
name:
minLength: 1
type: string
required:
- name
type: object
required:
- levels
- teamRef
type: object
status:
description: status defines the observed state of TerdutEscalationRule
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
observedGeneration:
format: int64
type: integer
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
{{- end }}
@@ -0,0 +1,457 @@
{{- if .Values.crd.enabled }}
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
{{- if .Values.crd.keep }}
"helm.sh/resource-policy": keep
{{- end }}
controller-gen.kubebuilder.io/version: v0.22.0
name: terdutservers.terdut.ryuvia.com
spec:
group: terdut.ryuvia.com
names:
kind: TerdutServer
listKind: TerdutServerList
plural: terdutservers
singular: terdutserver
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.replicas
name: Replicas
type: integer
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: string
- jsonPath: .status.conditions[?(@.type=="Ready")].reason
name: Reason
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: TerdutServer is the Schema for the terdutservers API
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: spec defines the desired state of TerdutServer
properties:
allowedTeams:
description: |-
allowedTeams gates cross-namespace TerdutTeams (DESIGN.md §4.6).
Unused until TerdutTeam exists (ROADMAP.md Stage 2); present now so
this CRD's schema doesn't need a breaking change to grow it later.
properties:
namespaces:
description: |-
AllowedTeamsNamespaces gates which namespaces a TerdutTeam may resolve a
cross-namespace serverRef into this TerdutServer from (DESIGN.md §4.6).
Same-namespace TerdutTeams are always allowed, regardless of this field.
Modeled on Gateway API's Gateway.spec.allowedListeners.namespaces.
properties:
from:
default: None
description: |-
from selects which namespaces may attach. Same is equivalent to None in
effect (same-namespace is unrestricted either way) but kept for parity
with the upstream enum this mirrors, and to make the policy
self-documenting in a diff.
enum:
- None
- Same
- All
- Selector
type: string
selector:
description: |-
selector is required, and only meaningful, when from is Selector: a
standard label selector over Namespace objects.
properties:
matchExpressions:
description: matchExpressions is a list of label selector
requirements. The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector
applies to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
x-kubernetes-list-type: atomic
required:
- key
- operator
type: object
type: array
x-kubernetes-list-type: atomic
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
type: object
type: object
database:
description: |-
DatabaseSpec is the Postgres connection this TerdutServer uses. Exactly
one of dsn or postgresClusterRef must be set (DESIGN.md §8) — this
operator provisions no database either way, only wires up one that
exists.
properties:
dsn:
description: |-
dsn is a DSN with no password in it, e.g.
"postgres://terdut@terdut-postgres:5432/terdut?sslmode=require" --
mutually exclusive with postgresClusterRef.
type: string
passwordSecretRef:
description: |-
passwordSecretRef is where PGPASSWORD comes from for the dsn path.
pgx falls back to libpq's environment variables for anything the DSN
omits, so the password never appears in the DSN string itself. Unused
on the postgresClusterRef path -- the Zalando-generated Secret is
wired in directly instead.
properties:
key:
description: key is the data key inside the Secret holding
the raw value.
minLength: 1
type: string
name:
description: name is the Secret's name.
minLength: 1
type: string
required:
- key
- name
type: object
postgresClusterRef:
description: |-
postgresClusterRef names a Zalando postgres-operator CR instead of a
plain DSN -- mutually exclusive with dsn.
properties:
name:
minLength: 1
type: string
required:
- name
type: object
type: object
x-kubernetes-validations:
- message: exactly one of dsn or postgresClusterRef must be set
rule: '(has(self.dsn) ? 1 : 0) + (has(self.postgresClusterRef) ?
1 : 0) == 1'
deadman:
description: |-
DeadmanSpec controls dead man's switch alerts. Matchers/Timeout/Severity
map straight to TERDUT_DEADMAN_MATCHERS/TERDUT_DEADMAN_TIMEOUT/
TERDUT_DEADMAN_SEVERITY.
properties:
matchers:
type: string
severity:
type: string
timeout:
type: string
type: object
image:
description: ImageSpec is the terdut-server image to run.
properties:
repository:
minLength: 1
type: string
tag:
minLength: 1
type: string
required:
- repository
- tag
type: object
networking:
description: |-
NetworkingSpec is how this TerdutServer is reached from outside the
cluster.
hostname/gatewayListener describe the intended Gateway API HTTPRoute
(matching charts/terdut-server's own templates/httpproxy.yaml, despite its
name — that chart carries a Gateway API HTTPRoute, not a Contour
HTTPProxy), but creating that HTTPRoute isn't implemented yet: it needs
the Gateway API types as a new dependency, and nothing about proving a
TerdutServer boots and bootstraps a real server depends on external
ingress existing. Tracked as a near-term follow-up, not deferred to a
later ROADMAP.md stage the way Deployment/database/bootstrap once were.
properties:
gatewayListener:
description: |-
gatewayListener is the HTTPRoute's sectionName once it exists. Empty
attaches to every matching listener, including plaintext HTTP.
type: string
hostname:
description: hostname the HTTPRoute will carry once it exists.
type: string
servicePort:
default: 8080
description: |-
servicePort is both the Service's port and the HTTPRoute's backend
port once it exists. Defaults to 8080, matching the chart's own
service.port default.
format: int32
type: integer
type: object
notify:
description: |-
NotifySpec controls push notifications via ntfy. Empty ntfyURL disables
notifications entirely (matches the chart's own default).
properties:
fallbackTopic:
type: string
ntfyURL:
type: string
repeatEvery:
type: string
tokenSecretRef:
description: |-
tokenSecretRef is an optional bearer token for an access-controlled
ntfy. Leave unset for an open ntfy.
properties:
key:
description: key is the data key inside the Secret holding
the raw value.
minLength: 1
type: string
name:
description: name is the Secret's name.
minLength: 1
type: string
required:
- key
- name
type: object
type: object
oidc:
description: |-
OIDCSpec controls single sign-on. Fields the chart also exposes but
DESIGN.md's spec doesn't (usernameClaim, emailClaim, groupsClaim,
trustEmail) use terdut-server's own defaults
(preferred_username/email/groups/false) rather than being added here
speculatively.
properties:
adminGroup:
type: string
allowedGroups:
items:
type: string
type: array
clientID:
type: string
clientSecretRef:
description: |-
SecretKeyRef names one data key inside a Secret. Every use of this type in
TerdutServerSpec resolves in the TerdutServer's own namespace (it's wired
straight into the Deployment's pod spec as a secretKeyRef env source,
which Kubernetes itself only allows same-namespace) -- unlike the
generated credentials Secret (DESIGN.md §6), which always lives in the
operator's own namespace and is never referenced through this type.
properties:
key:
description: key is the data key inside the Secret holding
the raw value.
minLength: 1
type: string
name:
description: name is the Secret's name.
minLength: 1
type: string
required:
- key
- name
type: object
enabled:
type: boolean
issuer:
type: string
name:
default: SSO
type: string
scopes:
default: openid profile email
type: string
sessionMaxAge:
default: 12h
type: string
type: object
passwordLogin:
default: true
description: |-
passwordLogin: whether a user may sign in, or sign up, with a
password.
type: boolean
replicas:
default: 1
description: |-
replicas. terdut-server is not horizontally-scale-tested; keep this
at its default of 1 unless you've verified otherwise -- the sweeper
and the notifier are unsynchronised singletons.
format: int32
type: integer
sweeper:
description: |-
SweeperSpec controls incident auto-resolve/archive timing. Values are
Go duration strings (e.g. "6h"), passed straight through to the
TERDUT_STALE_AFTER/TERDUT_ARCHIVE_AFTER env vars exactly as written --
not a structured metav1.Duration, since terdut-server parses them itself
and a round-trip through a different type would buy nothing.
properties:
archiveAfter:
type: string
staleAfter:
type: string
type: object
required:
- database
- image
- networking
type: object
status:
description: status defines the observed state of TerdutServer
properties:
conditions:
description: conditions represent the current state of the TerdutServer
resource.
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
credentialsSecretRef:
description: |-
credentialsSecretRef is the generated instance-scoped credential
(DESIGN.md §6) -- pure output, always in the operator's own
namespace, under a fixed data key ("token"). Set only once
Bootstrapped is True.
properties:
key:
description: key is the data key inside the Secret holding the
raw value.
minLength: 1
type: string
name:
description: name is the Secret's name.
minLength: 1
type: string
required:
- key
- name
type: object
observedGeneration:
description: |-
observedGeneration is the .metadata.generation this status was last
computed against — the standard way a client (or `kubectl wait`)
tells "applied" from "seen" (DESIGN.md §7).
format: int64
type: integer
serviceName:
description: |-
serviceName is the Service this controller created for the
Deployment, so other objects can reference it without recomputing the
naming convention.
type: string
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
{{- end }}
@@ -0,0 +1,198 @@
{{- if .Values.crd.enabled }}
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
{{- if .Values.crd.keep }}
"helm.sh/resource-policy": keep
{{- end }}
controller-gen.kubebuilder.io/version: v0.22.0
name: terdutteams.terdut.ryuvia.com
spec:
group: terdut.ryuvia.com
names:
kind: TerdutTeam
listKind: TerdutTeamList
plural: terdutteams
singular: terdutteam
scope: Namespaced
versions:
- additionalPrinterColumns:
- jsonPath: .spec.serverRef.name
name: Server
type: string
- jsonPath: .status.teamID
name: TeamID
type: integer
- jsonPath: .status.conditions[?(@.type=="Ready")].status
name: Ready
type: string
- jsonPath: .status.conditions[?(@.type=="Ready")].reason
name: Reason
type: string
name: v1alpha1
schema:
openAPIV3Schema:
description: TerdutTeam is the Schema for the terdutteams API
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: spec defines the desired state of TerdutTeam
properties:
displayName:
description: |-
displayName is this team's name, both in terdut-server's own data
(POST /api/teams {"name": ...}) and as the identity POST /api/teams
and GET /api/teams?name= correlate on (DESIGN.md §5's idempotent-
create rule, via TEAM-LOOKUP.md).
minLength: 1
type: string
oidc:
description: |-
TerdutTeamOIDC binds which identity-provider groups grant membership and
ownership of this team (DESIGN.md §4.2). Both empty means no group grants
either role here — matches terdut-server's own NULLIF-on-empty-string
handling (internal/api/oidc_teams.go).
properties:
memberGroup:
type: string
ownerGroup:
type: string
type: object
serverRef:
description: serverRef names the TerdutServer this team belongs to.
properties:
name:
minLength: 1
type: string
namespace:
type: string
required:
- name
type: object
required:
- displayName
- serverRef
type: object
status:
description: status defines the observed state of TerdutTeam
properties:
conditions:
items:
description: Condition contains details for one aspect of the current
state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
x-kubernetes-list-map-keys:
- type
x-kubernetes-list-type: map
credentialsSecretRef:
description: |-
credentialsSecretRef is this team's own scoped credential
(DESIGN.md §6 point 3) -- pure output, always in the operator's own
namespace, under a fixed data key ("token").
properties:
key:
description: key is the data key inside the Secret holding the
raw value.
minLength: 1
type: string
name:
description: name is the Secret's name.
minLength: 1
type: string
required:
- key
- name
type: object
observedGeneration:
format: int64
type: integer
serverEndpoint:
description: |-
serverEndpoint is the resolved TerdutServer's base URL, resolved once
here so no child controller (TerdutEscalationRule, TerdutDeadmanSwitch,
TerdutAlertSource) ever needs its own RBAC on terdutservers just to
find out where to send a request (DESIGN.md §5).
type: string
teamID:
description: |-
teamID is the server-side id -- needed by every child object's
controller (DESIGN.md §4.2).
format: int64
type: integer
type: object
required:
- spec
type: object
served: true
storage: true
subresources:
status: {}
{{- end }}
@@ -0,0 +1,161 @@
{{- if or (not (hasKey .Values.manager "enabled")) (.Values.manager.enabled) }}
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
control-plane: controller-manager
{{- with .Values.manager.labels }}
{{- with omit . "app.kubernetes.io/managed-by" "app.kubernetes.io/name" "helm.sh/chart" "app.kubernetes.io/instance" "control-plane" }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "controller-manager" "context" $) }}
namespace: {{ .Release.Namespace }}
{{- if .Values.manager.annotations }}
annotations:
{{- toYaml .Values.manager.annotations | nindent 4 }}
{{- end }}
spec:
{{- with .Values.manager.strategy }}
strategy: {{ toYaml . | nindent 6 }}
{{- end }}
replicas: {{ .Values.manager.replicas }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
control-plane: controller-manager
template:
metadata:
annotations:
kubectl.kubernetes.io/default-container: manager
{{- with .Values.manager.pod }}
{{- with .annotations }}
{{- with omit . "kubectl.kubernetes.io/default-container" }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
labels:
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
control-plane: controller-manager
{{- with .Values.manager.pod }}
{{- with .labels }}
{{- with omit . "app.kubernetes.io/name" "helm.sh/chart" "app.kubernetes.io/instance" "app.kubernetes.io/managed-by" "control-plane" }}
{{- toYaml . | nindent 8 }}
{{- end }}
{{- end }}
{{- end }}
spec:
{{- with .Values.manager.topologySpreadConstraints }}
topologySpreadConstraints: {{ toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.manager.priorityClassName }}
priorityClassName: {{ . | quote }}
{{- end }}
{{- with .Values.manager.tolerations }}
tolerations: {{ toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.manager.affinity }}
affinity: {{ toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.manager.nodeSelector }}
nodeSelector: {{ toYaml . | nindent 10 }}
{{- end }}
{{- with .Values.manager.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- args:
{{- if .Values.metrics.enabled }}
- --metrics-bind-address=:{{ .Values.metrics.port }}
{{- if not .Values.metrics.secure }}
- --metrics-secure=false
{{- end }}
{{- else }}
# Bind to :0 to disable the controller-runtime managed metrics server
- --metrics-bind-address=0
{{- end }}
- --health-probe-bind-address=:{{ .Values.manager.healthProbe.port }}
{{- range .Values.manager.args }}
- {{ tpl . $ }}
{{- end }}
command:
- /manager
env:
{{- if or .Values.manager.env (and (kindIs "map" .Values.manager.envOverrides) (not (empty .Values.manager.envOverrides))) }}
{{- if .Values.manager.env }}
{{- toYaml .Values.manager.env | nindent 10 }}
{{- end }}
{{- if kindIs "map" .Values.manager.envOverrides }}
{{- range $k, $v := .Values.manager.envOverrides }}
- name: {{ $k }}
value: {{ $v | quote }}
{{ end }}
{{- end }}
{{- else }}
[]
{{- end }}
image: "{{ .Values.manager.image.repository | default "controller" }}{{- if not (contains "@" (.Values.manager.image.repository | default "controller")) }}:{{ .Values.manager.image.tag | default .Chart.AppVersion }}{{- end }}"
{{- with .Values.manager.image.pullPolicy }}
imagePullPolicy: {{ . }}
{{- end }}
livenessProbe:
httpGet:
path: /healthz
port: {{ .Values.manager.healthProbe.port }}
initialDelaySeconds: 15
periodSeconds: 20
name: manager
ports:
- containerPort: {{ .Values.manager.healthProbe.port }}
name: health
protocol: TCP
readinessProbe:
httpGet:
path: /readyz
port: {{ .Values.manager.healthProbe.port }}
initialDelaySeconds: 5
periodSeconds: 10
resources:
{{- if .Values.manager.resources }}
{{- toYaml .Values.manager.resources | nindent 10 }}
{{- else }}
{}
{{- end }}
securityContext:
{{- if .Values.manager.securityContext }}
{{- toYaml .Values.manager.securityContext | nindent 10 }}
{{- else }}
{}
{{- end }}
volumeMounts:
{{- if .Values.manager.extraVolumeMounts }}
{{- toYaml .Values.manager.extraVolumeMounts | nindent 10 }}
{{- else }}
[]
{{- end }}
securityContext:
{{- if .Values.manager.podSecurityContext }}
{{- toYaml .Values.manager.podSecurityContext | nindent 8 }}
{{- else }}
{}
{{- end }}
serviceAccountName: {{ include "terdut-operator.serviceAccountName" . }}
{{- if and (hasKey .Values.manager "terminationGracePeriodSeconds") (ne .Values.manager.terminationGracePeriodSeconds nil) }}
terminationGracePeriodSeconds: {{ .Values.manager.terminationGracePeriodSeconds }}
{{- end }}
volumes:
{{- if .Values.manager.extraVolumes }}
{{- toYaml .Values.manager.extraVolumes | nindent 8 }}
{{- else }}
[]
{{- end }}
{{- end }}
@@ -0,0 +1,22 @@
{{- if .Values.metrics.enabled }}
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
control-plane: controller-manager
name: {{ include "terdut-operator.resourceName" (dict "suffix" "controller-manager-metrics-service" "context" $) }}
namespace: {{ .Release.Namespace }}
spec:
ports:
- name: {{ if .Values.metrics.secure }}https{{ else }}http{{ end }}
port: {{ .Values.metrics.port }}
protocol: TCP
targetPort: {{ .Values.metrics.port }}
selector:
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
control-plane: controller-manager
{{- end }}
@@ -0,0 +1,25 @@
{{- if and .Values.networkPolicy.enabled .Values.metrics.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "allow-metrics-traffic" "context" $) }}
namespace: {{ .Release.Namespace }}
spec:
podSelector:
matchLabels:
control-plane: controller-manager
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
metrics: enabled
ports:
- port: {{ .Values.metrics.port }}
protocol: TCP
{{- end }}
@@ -0,0 +1,53 @@
{{- if .Values.prometheus.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
control-plane: controller-manager
{{- with .Values.prometheus.labels }}
{{- with omit . "app.kubernetes.io/managed-by" "app.kubernetes.io/name" "helm.sh/chart" "app.kubernetes.io/instance" "control-plane" }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
{{- with .Values.prometheus.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "controller-manager-metrics-monitor" "context" $) }}
namespace: {{ .Release.Namespace }}
spec:
endpoints:
- {{- if .Values.metrics.secure }}
bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
{{- end }}
path: /metrics
port: {{ if .Values.metrics.secure }}https{{ else }}http{{ end }}
scheme: {{ if .Values.metrics.secure }}https{{ else }}http{{ end }}
{{- if .Values.metrics.secure }}
tlsConfig:
serverName: {{ include "terdut-operator.resourceName" (dict "suffix" "controller-manager-metrics-service" "context" $) }}.{{ .Release.Namespace }}.svc
{{- if .Values.certManager.enabled }}
ca:
secret:
name: metrics-server-cert
key: ca.crt
cert:
secret:
name: metrics-server-cert
key: tls.crt
keySecret:
name: metrics-server-cert
key: tls.key
{{- else }}
insecureSkipVerify: true
{{- end }}
{{- end }}
selector:
matchLabels:
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
control-plane: controller-manager
{{- end }}
@@ -0,0 +1,21 @@
{{- if .Values.serviceAccount.enabled }}
apiVersion: v1
kind: ServiceAccount
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- with .Values.serviceAccount.labels }}
{{- with omit . "app.kubernetes.io/managed-by" "app.kubernetes.io/name" "helm.sh/chart" "app.kubernetes.io/instance" }}
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
name: {{ include "terdut-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
@@ -0,0 +1,42 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "leader-election-role" "context" $) }}
namespace: {{ .Release.Namespace }}
rules:
- apiGroups:
- ""
resources:
- configmaps
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- coordination.k8s.io
resources:
- leases
verbs:
- get
- list
- watch
- create
- update
- patch
- delete
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
@@ -0,0 +1,18 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "leader-election-rolebinding" "context" $) }}
namespace: {{ .Release.Namespace }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: {{ include "terdut-operator.resourceName" (dict "suffix" "leader-election-role" "context" $) }}
subjects:
- kind: ServiceAccount
name: {{ include "terdut-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
@@ -0,0 +1,98 @@
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "manager-role" "context" $) }}
rules:
- apiGroups:
- ""
resources:
- namespaces
verbs:
- get
- list
- watch
- apiGroups:
- ""
resources:
- secrets
- services
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- acid.zalan.do
resources:
- postgresqls
verbs:
- get
- list
- watch
- apiGroups:
- apps
resources:
- deployments
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- events.k8s.io
resources:
- events
verbs:
- create
- patch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutalertsources
- terdutdeadmanswitches
- terdutescalationrules
- terdutservers
- terdutteams
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutalertsources/finalizers
- terdutdeadmanswitches/finalizers
- terdutescalationrules/finalizers
- terdutservers/finalizers
- terdutteams/finalizers
verbs:
- update
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutalertsources/status
- terdutdeadmanswitches/status
- terdutescalationrules/status
- terdutservers/status
- terdutteams/status
verbs:
- get
- patch
- update
@@ -0,0 +1,28 @@
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: RoleBinding
{{- else }}
kind: ClusterRoleBinding
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "manager-rolebinding" "context" $) }}
roleRef:
apiGroup: rbac.authorization.k8s.io
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "manager-role" "context" $) }}
subjects:
- kind: ServiceAccount
name: {{ include "terdut-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
@@ -0,0 +1,19 @@
{{- if and .Values.metrics.enabled .Values.metrics.secure }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "terdut-operator.resourceName" (dict "suffix" "metrics-auth-role" "context" $) }}
rules:
- apiGroups:
- authentication.k8s.io
resources:
- tokenreviews
verbs:
- create
- apiGroups:
- authorization.k8s.io
resources:
- subjectaccessreviews
verbs:
- create
{{- end }}
@@ -0,0 +1,14 @@
{{- if and .Values.metrics.enabled .Values.metrics.secure }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "terdut-operator.resourceName" (dict "suffix" "metrics-auth-rolebinding" "context" $) }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "terdut-operator.resourceName" (dict "suffix" "metrics-auth-role" "context" $) }}
subjects:
- kind: ServiceAccount
name: {{ include "terdut-operator.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
{{- end }}
@@ -0,0 +1,11 @@
{{- if and .Values.metrics.enabled .Values.metrics.secure }}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "terdut-operator.resourceName" (dict "suffix" "metrics-reader" "context" $) }}
rules:
- nonResourceURLs:
- /metrics
verbs:
- get
{{- end }}
@@ -0,0 +1,31 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutalertsource-admin-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutalertsources
verbs:
- '*'
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutalertsources/status
verbs:
- get
{{- end }}
@@ -0,0 +1,37 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutalertsource-editor-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutalertsources
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutalertsources/status
verbs:
- get
{{- end }}
@@ -0,0 +1,33 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutalertsource-viewer-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutalertsources
verbs:
- get
- list
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutalertsources/status
verbs:
- get
{{- end }}
@@ -0,0 +1,31 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutdeadmanswitch-admin-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutdeadmanswitches
verbs:
- '*'
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutdeadmanswitches/status
verbs:
- get
{{- end }}
@@ -0,0 +1,37 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutdeadmanswitch-editor-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutdeadmanswitches
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutdeadmanswitches/status
verbs:
- get
{{- end }}
@@ -0,0 +1,33 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutdeadmanswitch-viewer-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutdeadmanswitches
verbs:
- get
- list
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutdeadmanswitches/status
verbs:
- get
{{- end }}
@@ -0,0 +1,31 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutescalationrule-admin-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutescalationrules
verbs:
- '*'
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutescalationrules/status
verbs:
- get
{{- end }}
@@ -0,0 +1,37 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutescalationrule-editor-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutescalationrules
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutescalationrules/status
verbs:
- get
{{- end }}
@@ -0,0 +1,33 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutescalationrule-viewer-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutescalationrules
verbs:
- get
- list
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutescalationrules/status
verbs:
- get
{{- end }}
@@ -0,0 +1,31 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutserver-admin-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutservers
verbs:
- '*'
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutservers/status
verbs:
- get
{{- end }}
@@ -0,0 +1,37 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutserver-editor-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutservers
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutservers/status
verbs:
- get
{{- end }}
@@ -0,0 +1,33 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutserver-viewer-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutservers
verbs:
- get
- list
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutservers/status
verbs:
- get
{{- end }}
@@ -0,0 +1,31 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutteam-admin-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutteams
verbs:
- '*'
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutteams/status
verbs:
- get
{{- end }}
@@ -0,0 +1,37 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutteam-editor-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutteams
verbs:
- create
- delete
- get
- list
- patch
- update
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutteams/status
verbs:
- get
{{- end }}
@@ -0,0 +1,33 @@
{{- if .Values.rbac.helpers.enabled }}
apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.rbac.namespaced }}
kind: Role
{{- else }}
kind: ClusterRole
{{- end }}
metadata:
{{- if .Values.rbac.namespaced }}
namespace: {{ .Release.Namespace }}
{{- end }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
name: {{ include "terdut-operator.resourceName" (dict "suffix" "terdutteam-viewer-role" "context" $) }}
rules:
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutteams
verbs:
- get
- list
- watch
- apiGroups:
- terdut.ryuvia.com
resources:
- terdutteams/status
verbs:
- get
{{- end }}
@@ -0,0 +1,54 @@
{{/*
Optional: one TerdutServer CR from values.yaml (DESIGN.md §10's "helm
install and get a server" path). Off by default -- see values.yaml's own
terdutServer block for the full shape and api/v1alpha1/terdutserver_types.go
for what each field validates.
*/}}
{{- if .Values.terdutServer.enabled }}
apiVersion: terdut.ryuvia.com/v1alpha1
kind: TerdutServer
metadata:
name: {{ .Values.terdutServer.name | default (include "terdut-operator.fullname" .) }}
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/name: {{ include "terdut-operator.name" . }}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/instance: {{ .Release.Name }}
spec:
image:
repository: {{ required "terdutServer.image.repository is required when terdutServer.enabled" .Values.terdutServer.image.repository }}
tag: {{ required "terdutServer.image.tag is required when terdutServer.enabled" .Values.terdutServer.image.tag | quote }}
replicas: {{ .Values.terdutServer.replicas }}
networking:
hostname: {{ required "terdutServer.networking.hostname is required when terdutServer.enabled" .Values.terdutServer.networking.hostname | quote }}
servicePort: {{ .Values.terdutServer.networking.servicePort }}
# Not re-validated here as dsn-xor-postgresClusterRef (api/v1alpha1's own
# CEL rule on DatabaseSpec is the authority, same v1 stance as every
# other cross-field check in this operator -- DESIGN.md §13: no
# admission webhooks, CEL-only validation). This chart just has to pass
# the block through faithfully.
database:
{{- required "terdutServer.database (dsn or postgresClusterRef) is required when terdutServer.enabled" .Values.terdutServer.database | toYaml | nindent 4 }}
{{- with .Values.terdutServer.sweeper }}
sweeper:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.terdutServer.deadman }}
deadman:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.terdutServer.notify }}
notify:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.terdutServer.oidc }}
oidc:
{{- toYaml . | nindent 4 }}
{{- end }}
passwordLogin: {{ .Values.terdutServer.passwordLogin }}
{{- with .Values.terdutServer.allowedTeams }}
allowedTeams:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- end }}
+272
View File
@@ -0,0 +1,272 @@
## String to partially override chart.fullname template (will maintain the release name)
##
# nameOverride: ""
## String to fully override chart.fullname template
##
# fullnameOverride: ""
## Configure the controller manager deployment
##
manager:
## Set to false to skip manager installation
##
enabled: true
replicas: 1
image:
repository: git.ryuvia.com/niklas/terdut-operator
## Image tag (defaults to Chart.appVersion if not set) -- the release
## process (`make helm-package`) always passes --app-version from the
## tag, so leaving this unset here is what tracks a release correctly.
##
# tag: ""
pullPolicy: IfNotPresent
## Arguments
##
args:
- --leader-elect
## Health probes.
## The manager serves the liveness (/healthz) and readiness (/readyz) endpoints on this port.
##
healthProbe:
# Health probe server port
port: 8081
## Environment variables
##
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
## Env overrides (--set manager.envOverrides.VAR=value)
## Same name in env above: this value takes precedence.
##
envOverrides: {}
## Image pull secrets
##
# imagePullSecrets:
# - name: myregistrykey
## Pod-level security settings
##
podSecurityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
## Container-level security settings
##
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
## Resource limits and requests
##
resources:
limits:
cpu: 500m
memory: 128Mi
requests:
cpu: 10m
memory: 64Mi
## Manager pod's affinity
##
affinity: {}
## Manager pod's node selector
##
nodeSelector: {}
## Manager pod's tolerations
##
tolerations: []
## Deployment strategy
##
# strategy:
# type: RollingUpdate
# rollingUpdate:
# maxSurge: 25%
# maxUnavailable: 25%
## Priority class name
##
# priorityClassName: ""
## Topology spread constraints
##
# topologySpreadConstraints: []
## Termination grace period seconds
##
terminationGracePeriodSeconds: 10
## Custom Deployment labels
##
# labels: {}
## Custom Deployment annotations
##
# annotations: {}
## Custom Pod labels and annotations
##
# pod:
# labels: {}
# annotations: {}
## RBAC configuration
##
rbac:
## RBAC resource scope
## - false (default): ClusterRole/ClusterRoleBinding (all namespaces)
## - true: Role/RoleBinding (release namespace only)
##
namespaced: false
## Helper roles for CRD management (admin/editor/viewer)
##
helpers:
## Install convenience admin/editor/viewer roles for CRDs
##
enabled: false
## ServiceAccount configuration
##
serviceAccount:
# Install default ServiceAccount provided
enabled: true
## Existing ServiceAccount name (required when enabled=false)
## Set to "default" to use the namespace default ServiceAccount
## Note: When enabled=true, respects nameOverride/fullnameOverride
##
# name: ""
## Custom ServiceAccount annotations
##
# annotations: {}
## Custom ServiceAccount labels
##
# labels: {}
## Custom Resource Definitions
##
crd:
# Install CRDs with the chart
enabled: true
# Keep CRDs when uninstalling
keep: true
## Controller metrics endpoint.
## Enable to expose /metrics endpoint
##
metrics:
enabled: true
# Metrics server port
port: 8443
# Enable secure metrics: HTTPS with certs/auth (true) or HTTP (false).
# Note: Metrics authn/authz needs ClusterRole access.
secure: true
## Cert-manager integration for TLS certificates.
## Required for webhook certificates and metrics endpoint certificates.
##
certManager:
enabled: false
## Webhook server configuration
##
webhook:
enabled: false
# Webhook server port
port: 9443
## Prometheus ServiceMonitor for metrics scraping.
## Requires prometheus-operator to be installed in the cluster.
##
prometheus:
enabled: false
## Custom ServiceMonitor labels
##
# labels: {}
## Custom ServiceMonitor annotations
##
# annotations: {}
## Network policies for controlling traffic flow.
## Enable to restrict ingress to the controller manager.
##
networkPolicy:
enabled: false
## Optionally render one TerdutServer CR from this chart -- "helm install
## and get a server" without hand-writing a CR (DESIGN.md §10). Off by
## default: most installs only want the operator and CRDs here, then apply
## their own TerdutServer (and TerdutTeam, and so on) separately. The shape
## below mirrors api/v1alpha1/terdutserver_types.go's TerdutServerSpec
## directly -- see that file for what each field means and which are
## required.
##
terdutServer:
enabled: false
## This CR's own metadata.name. Defaults to the chart's own fullname if unset.
# name: ""
image:
repository: git.ryuvia.com/niklas/terdut-server
## Required when terdutServer.enabled.
# tag: ""
replicas: 1
networking:
## Required when terdutServer.enabled -- the hostname a future
## HTTPRoute will carry (see NetworkingSpec's own doc comment: creating
## that HTTPRoute isn't implemented yet).
# hostname: ""
servicePort: 8080
## Exactly one of dsn or postgresClusterRef, matching DatabaseSpec's own
## CEL rule -- required when terdutServer.enabled. Bring-your-own DSN:
## database:
## dsn: "postgres://terdut@terdut-postgres:5432/terdut?sslmode=require"
## passwordSecretRef:
## name: terdut-postgres-password
## key: password
## Zalando postgres-operator instead:
## database:
## postgresClusterRef:
## name: terdut-postgres
database: {}
## All optional -- omit entirely for their terdut-server defaults.
# sweeper:
# staleAfter: 6h
# archiveAfter: 168h
# deadman:
# matchers: "alertname=Watchdog"
# timeout: 15m
# severity: critical
# notify: {}
# oidc: {}
passwordLogin: true
# allowedTeams: {}
+6
View File
@@ -1,2 +1,8 @@
resources: resources:
- manager.yaml - manager.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
images:
- name: controller
newName: controller
newTag: latest
+2 -2
View File
@@ -12,7 +12,7 @@ require (
) )
require ( require (
cel.dev/expr v0.25.1 // indirect cel.dev/expr v0.25.2 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
@@ -88,7 +88,7 @@ require (
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
google.golang.org/grpc v1.82.1 // indirect google.golang.org/grpc v1.83.1 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
+4 -4
View File
@@ -1,5 +1,5 @@
cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= cel.dev/expr v0.25.2 h1:K6j46C81hXtZQfuX60cVWQFBJahKSE2gfRbNuvr5bFs=
cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= cel.dev/expr v0.25.2/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ=
@@ -222,8 +222,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:
google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=