6 Commits

Author SHA1 Message Date
Niklas Ye 0d104755f1 Set the chart's placeholder version to 0.7.0
CI / chart (push) Successful in 1s
CI / security (push) Successful in 51s
CI / test (push) Successful in 3m3s
Release / test (push) Successful in 8m29s
Release / chart (push) Successful in 2s
Release / image (push) Successful in 7m22s
Release / scan-image (push) Successful in 35s
Cosmetic: make helm-package passes --version and --app-version from the tag,
so these fields decide nothing about what is published. They are kept in
step so the tree does not state an older number than the release it heads
for, as in 295dc3a.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-22 18:20:43 +02:00
Niklas Ye 03df2bd5ce Pin the demo to terdut-server v0.49.1
Same reason as 93d2a00: the kind demo shows the current web UI, which now
includes the metrics graph. The demo has no Prometheus, so it sets no
allowedHosts and the graph stays off.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-22 18:20:43 +02:00
Niklas Ye bde5611b16 Let a TerdutServer turn on metrics from the alerts' Prometheus
terdut-server v0.49.0 graphs the metric behind each alert on the incident
page, but only queries hosts listed in TERDUT_PROMETHEUS_ALLOWED_HOSTS,
because the URL comes out of a webhook payload. An install this operator
creates had no way to set it short of spec.pod.extraEnv.

spec.prometheus takes allowedHosts, preroll and timeout and maps them to
TERDUT_PROMETHEUS_ALLOWED_HOSTS, _PREROLL and _TIMEOUT. Nothing is emitted
unless allowedHosts is non-empty, so existing TerdutServers get the same
Deployment as before, and unset durations are left to the server's defaults.
There is no CRD default for the host: which Prometheus to trust is the
deployer's call, not something this operator should assume.

The CRD in config/ is regenerated and the chart's copy carries the same
schema; the chart's optional TerdutServer passes the block through.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-22 18:20:43 +02:00
Niklas Ye 295dc3a9ec Set the chart's placeholder version to 0.6.1
Release / image (push) Successful in 6m45s
CI / chart (push) Successful in 1s
CI / security (push) Successful in 1m32s
Release / chart (push) Successful in 1s
Release / scan-image (push) Successful in 36s
CI / test (push) Successful in 7m24s
Release / test (push) Successful in 1m36s
Cosmetic: make helm-package passes --version and --app-version from the tag,
so these fields decide nothing about what is published. They are kept in
step so the tree does not state an older number than the release it heads
for, as in 3036abf.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-22 09:35:04 +02:00
Niklas Ye 69e610a5c2 Scan and build with Go 1.26.10
govulncheck fails on GO-2026-6809, a crypto/x509 flaw in the standard
library that is fixed in 1.26.10. Move the workflows' golang image off 1.26.9
the way terdut-server did, so the scan and the shipped binary agree.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-21 22:07:54 +02:00
Niklas Ye 93d2a00a00 Pin the demo to terdut-server v0.48.0
v0.43.0 predates v0.47.2, which lets the instance service account list a
team's dead man's switches. Without it GET /api/teams/{id}/deadman/switches
404s for the operator's key, so a TerdutTeam never becomes ready against the
demo's server. The demo is meant to track the current release.

Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
2026-10-21 22:07:50 +02:00
13 changed files with 170 additions and 7 deletions
+2 -2
View File
@@ -55,7 +55,7 @@ jobs:
test:
runs-on: ubuntu-latest
container:
image: golang:1.26.9-bookworm
image: golang:1.26.10-bookworm
volumes:
- go-mod-cache:/go/pkg/mod
- go-build-cache:/root/.cache/go-build
@@ -85,7 +85,7 @@ jobs:
security:
runs-on: ubuntu-latest
container:
image: golang:1.26.9-bookworm
image: golang:1.26.10-bookworm
volumes:
- go-mod-cache:/go/pkg/mod
- go-build-cache:/root/.cache/go-build
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
test:
runs-on: ubuntu-latest
container:
image: golang:1.26.9-bookworm
image: golang:1.26.10-bookworm
volumes:
- go-mod-cache:/go/pkg/mod
- go-build-cache:/root/.cache/go-build
+4
View File
@@ -107,6 +107,10 @@ spec:
fallbackTopic: ""
repeatEvery: 15m
tokenSecretRef: {name: "", key: token}
prometheus: # terdut-server v0.49.0+: metrics on incidents; off unless allowedHosts is set
allowedHosts: [] # e.g. ["kube-prometheus-stack-prometheus.kube-prometheus-stack:9090"]; wildcards: "*.monitoring.svc"
preroll: "" # unset = server default (1h)
timeout: "" # unset = server default (10s)
oidc:
enabled: false
issuer: ""
+31
View File
@@ -118,6 +118,32 @@ type NotifySpec struct {
TokenSecretRef *SecretKeyRef `json:"tokenSecretRef,omitempty"`
}
// PrometheusSpec controls the metrics terdut-server shows on an incident,
// fetched from the Prometheus named in each alert's generatorURL
// (terdut-server v0.49.0+). Off unless allowedHosts is set: the URL comes
// out of a webhook payload, so the server queries only hosts listed here.
type PrometheusSpec struct {
// allowedHosts are the Prometheus servers terdut-server may query,
// passed as TERDUT_PROMETHEUS_ALLOWED_HOSTS. Entries are host names
// (with "*" as one DNS label and a leading "**" as one or more), IPs or
// CIDR ranges, each with an optional ":port", e.g.
// "kube-prometheus-stack-prometheus.kube-prometheus-stack:9090" or
// "*.monitoring.svc". Empty turns the feature off. The server refuses to
// start on a malformed entry or a bare "*".
// +optional
AllowedHosts []string `json:"allowedHosts,omitempty"`
// preroll is how far before an alert started its graph begins (a Go
// duration, e.g. "1h"). Unset uses terdut-server's default.
// +optional
Preroll string `json:"preroll,omitempty"`
// timeout bounds one request to a Prometheus (a Go duration, e.g.
// "10s"). Unset uses terdut-server's default.
// +optional
Timeout string `json:"timeout,omitempty"`
}
// OIDCSpec controls single sign-on.
type OIDCSpec struct {
// +optional
@@ -325,6 +351,11 @@ type TerdutServerSpec struct {
// +optional
OIDC OIDCSpec `json:"oidc,omitempty"`
// prometheus enables the metrics on an incident (terdut-server
// v0.49.0+; an older image ignores the env vars it produces).
// +optional
Prometheus PrometheusSpec `json:"prometheus,omitempty"`
// passwordLogin: whether a user may sign in, or sign up, with a
// password.
// +kubebuilder:default=true
+21
View File
@@ -391,6 +391,26 @@ func (in *PostgresClusterRef) DeepCopy() *PostgresClusterRef {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *PrometheusSpec) DeepCopyInto(out *PrometheusSpec) {
*out = *in
if in.AllowedHosts != nil {
in, out := &in.AllowedHosts, &out.AllowedHosts
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PrometheusSpec.
func (in *PrometheusSpec) DeepCopy() *PrometheusSpec {
if in == nil {
return nil
}
out := new(PrometheusSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *SecretKeyRef) DeepCopyInto(out *SecretKeyRef) {
*out = *in
@@ -606,6 +626,7 @@ func (in *TerdutServerSpec) DeepCopyInto(out *TerdutServerSpec) {
out.Sweeper = in.Sweeper
in.Notify.DeepCopyInto(&out.Notify)
in.OIDC.DeepCopyInto(&out.OIDC)
in.Prometheus.DeepCopyInto(&out.Prometheus)
in.AllowedTeams.DeepCopyInto(&out.AllowedTeams)
in.Pod.DeepCopyInto(&out.Pod)
}
+2 -2
View File
@@ -6,8 +6,8 @@ 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.6.0
appVersion: "v0.6.0"
version: 0.7.0
appVersion: "v0.7.0"
keywords:
- kubernetes
@@ -4301,6 +4301,34 @@ spec:
type: object
type: array
type: object
prometheus:
description: |-
prometheus enables the metrics on an incident (terdut-server
v0.49.0+; an older image ignores the env vars it produces).
properties:
allowedHosts:
description: |-
allowedHosts are the Prometheus servers terdut-server may query,
passed as TERDUT_PROMETHEUS_ALLOWED_HOSTS. Entries are host names
(with "*" as one DNS label and a leading "**" as one or more), IPs or
CIDR ranges, each with an optional ":port", e.g.
"kube-prometheus-stack-prometheus.kube-prometheus-stack:9090" or
"*.monitoring.svc". Empty turns the feature off. The server refuses to
start on a malformed entry or a bare "*".
items:
type: string
type: array
preroll:
description: |-
preroll is how far before an alert started its graph begins (a Go
duration, e.g. "1h"). Unset uses terdut-server's default.
type: string
timeout:
description: |-
timeout bounds one request to a Prometheus (a Go duration, e.g.
"10s"). Unset uses terdut-server's default.
type: string
type: object
replicas:
default: 2
description: |-
@@ -42,6 +42,10 @@ spec:
oidc:
{{- toYaml . | nindent 4 }}
{{- end }}
{{- with .Values.terdutServer.prometheus }}
prometheus:
{{- toYaml . | nindent 4 }}
{{- end }}
passwordLogin: {{ .Values.terdutServer.passwordLogin }}
{{- with .Values.terdutServer.allowedTeams }}
allowedTeams:
+6
View File
@@ -269,6 +269,12 @@ terdutServer:
# notify: {}
# oidc: {}
## Metrics on incidents (terdut-server v0.49.0+): hosts it may query for the
## Prometheus named in an alert's generatorURL. Empty/unset = off.
# prometheus:
# allowedHosts:
# - kube-prometheus-stack-prometheus.kube-prometheus-stack:9090
passwordLogin: true
# allowedTeams: {}
@@ -4298,6 +4298,34 @@ spec:
type: object
type: array
type: object
prometheus:
description: |-
prometheus enables the metrics on an incident (terdut-server
v0.49.0+; an older image ignores the env vars it produces).
properties:
allowedHosts:
description: |-
allowedHosts are the Prometheus servers terdut-server may query,
passed as TERDUT_PROMETHEUS_ALLOWED_HOSTS. Entries are host names
(with "*" as one DNS label and a leading "**" as one or more), IPs or
CIDR ranges, each with an optional ":port", e.g.
"kube-prometheus-stack-prometheus.kube-prometheus-stack:9090" or
"*.monitoring.svc". Empty turns the feature off. The server refuses to
start on a malformed entry or a bare "*".
items:
type: string
type: array
preroll:
description: |-
preroll is how far before an alert started its graph begins (a Go
duration, e.g. "1h"). Unset uses terdut-server's default.
type: string
timeout:
description: |-
timeout bounds one request to a Prometheus (a Go duration, e.g.
"10s"). Unset uses terdut-server's default.
type: string
type: object
replicas:
default: 2
description: |-
+2 -2
View File
@@ -15,7 +15,7 @@ spec:
image:
repository: git.ryuvia.com/niklas/terdut-server
# v0.36.0 is the floor now that replicas below is 2 (this demo pins
# the current release, v0.43.0, so it shows the current web UI too): that
# the current release, v0.49.1, so it shows the current web UI too): that
# release put the sweeper, the notifier and the migration runner each
# behind a Postgres advisory lock, and gave incident creation its own
# conflict resolution, which is what makes a second replica safe
@@ -24,7 +24,7 @@ spec:
# can adopt/rotate a key on a team-scoped account it didn't just create
# in the same call -- without which terdutteam-* can wedge permanently
# on the crash-window race this demo hit live, niklas/terdut-operator#3.)
tag: v0.43.0
tag: v0.49.1
# Matches this CRD's own spec.replicas default (v0.4.0) -- stated
# explicitly, like every other field in this file, rather than left to
# the default. RollingUpdate follows automatically; this operator does
@@ -112,6 +112,7 @@ var _ = Describe("TerdutServer Controller", func() {
Expect(env["TERDUT_DB_DSN"].Value).To(Equal(testDSN))
Expect(env["TERDUT_OPERATOR_MODE"].Value).To(Equal("true"))
Expect(env).NotTo(HaveKey("TERDUT_DEADMAN_MATCHERS"), "dead man's switches are per team now")
Expect(env).NotTo(HaveKey("TERDUT_PROMETHEUS_ALLOWED_HOSTS"), "metrics stay off unless spec.prometheus asks")
var svc corev1.Service
Expect(k8sClient.Get(ctx, objKey, &svc)).To(Succeed())
@@ -252,6 +253,36 @@ var _ = Describe("TerdutServer Controller", func() {
})
})
Describe("spec.prometheus", func() {
It("passes the allowlist and tuning to the server as env vars", func(ctx SpecContext) {
spec := dsnSpec()
spec.Prometheus = terdutv1alpha1.PrometheusSpec{
AllowedHosts: []string{"kube-prometheus-stack-prometheus.kube-prometheus-stack:9090", "*.monitoring.svc"},
Preroll: "2h",
Timeout: "5s",
}
createServer(ctx, spec)
reconcileOnce(ctx)
env := envOf(ctx)
Expect(env["TERDUT_PROMETHEUS_ALLOWED_HOSTS"].Value).To(Equal("kube-prometheus-stack-prometheus.kube-prometheus-stack:9090,*.monitoring.svc"))
Expect(env["TERDUT_PROMETHEUS_PREROLL"].Value).To(Equal("2h"))
Expect(env["TERDUT_PROMETHEUS_TIMEOUT"].Value).To(Equal("5s"))
})
It("leaves the tuning to the server's defaults when only hosts are given", func(ctx SpecContext) {
spec := dsnSpec()
spec.Prometheus = terdutv1alpha1.PrometheusSpec{AllowedHosts: []string{"prometheus.monitoring.svc"}}
createServer(ctx, spec)
reconcileOnce(ctx)
env := envOf(ctx)
Expect(env).To(HaveKey("TERDUT_PROMETHEUS_ALLOWED_HOSTS"))
Expect(env).NotTo(HaveKey("TERDUT_PROMETHEUS_PREROLL"))
Expect(env).NotTo(HaveKey("TERDUT_PROMETHEUS_TIMEOUT"))
})
})
Describe("spec.pod", func() {
It("wires pod-level customization onto the right spot on the Deployment", func(ctx SpecContext) {
spec := dsnSpec()
@@ -216,6 +216,16 @@ func buildEnv(srv *terdutv1alpha1.TerdutServer, dbEnv []corev1.EnvVar) []corev1.
}
}
if prom := srv.Spec.Prometheus; len(prom.AllowedHosts) > 0 {
env = append(env, corev1.EnvVar{Name: "TERDUT_PROMETHEUS_ALLOWED_HOSTS", Value: strings.Join(prom.AllowedHosts, ",")})
if prom.Preroll != "" {
env = append(env, corev1.EnvVar{Name: "TERDUT_PROMETHEUS_PREROLL", Value: prom.Preroll})
}
if prom.Timeout != "" {
env = append(env, corev1.EnvVar{Name: "TERDUT_PROMETHEUS_TIMEOUT", Value: prom.Timeout})
}
}
publicURL := fmt.Sprintf("https://%s", srv.Spec.Networking.Hostname)
env = append(env,
corev1.EnvVar{Name: "TERDUT_PUBLIC_URL", Value: publicURL},