@pleri/olam-cli 0.1.159 → 0.1.161
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/dist/agent-stream/agent-sdk-to-chunks.js +3 -0
- package/dist/agent-stream/driver-runner.js +9 -4
- package/dist/agent-stream/host-driver-launch.js +48 -0
- package/dist/commands/bootstrap.d.ts +15 -0
- package/dist/commands/bootstrap.d.ts.map +1 -1
- package/dist/commands/bootstrap.js +30 -1
- package/dist/commands/bootstrap.js.map +1 -1
- package/dist/commands/flywheel/check-persona-skeleton.d.ts +30 -2
- package/dist/commands/flywheel/check-persona-skeleton.d.ts.map +1 -1
- package/dist/commands/flywheel/check-persona-skeleton.js +143 -6
- package/dist/commands/flywheel/check-persona-skeleton.js.map +1 -1
- package/dist/commands/flywheel/diversity-check.d.ts +12 -2
- package/dist/commands/flywheel/diversity-check.d.ts.map +1 -1
- package/dist/commands/flywheel/diversity-check.js +56 -6
- package/dist/commands/flywheel/diversity-check.js.map +1 -1
- package/dist/commands/flywheel/index.d.ts.map +1 -1
- package/dist/commands/flywheel/index.js +2 -0
- package/dist/commands/flywheel/index.js.map +1 -1
- package/dist/commands/flywheel/install-shims.d.ts +36 -3
- package/dist/commands/flywheel/install-shims.d.ts.map +1 -1
- package/dist/commands/flywheel/install-shims.js +118 -7
- package/dist/commands/flywheel/install-shims.js.map +1 -1
- package/dist/commands/flywheel/k10-measure.d.ts +12 -2
- package/dist/commands/flywheel/k10-measure.d.ts.map +1 -1
- package/dist/commands/flywheel/k10-measure.js +55 -6
- package/dist/commands/flywheel/k10-measure.js.map +1 -1
- package/dist/commands/flywheel/migrate-overlays.d.ts +115 -0
- package/dist/commands/flywheel/migrate-overlays.d.ts.map +1 -0
- package/dist/commands/flywheel/migrate-overlays.js +766 -0
- package/dist/commands/flywheel/migrate-overlays.js.map +1 -0
- package/dist/commands/flywheel/sanitize-persona-output.d.ts +33 -2
- package/dist/commands/flywheel/sanitize-persona-output.d.ts.map +1 -1
- package/dist/commands/flywheel/sanitize-persona-output.js +94 -6
- package/dist/commands/flywheel/sanitize-persona-output.js.map +1 -1
- package/dist/commands/memory/index.d.ts.map +1 -1
- package/dist/commands/memory/index.js +2 -0
- package/dist/commands/memory/index.js.map +1 -1
- package/dist/commands/memory/install-hooks.d.ts +22 -0
- package/dist/commands/memory/install-hooks.d.ts.map +1 -0
- package/dist/commands/memory/install-hooks.js +156 -0
- package/dist/commands/memory/install-hooks.js.map +1 -0
- package/dist/commands/skills-doctor.js +2 -2
- package/dist/commands/skills-doctor.js.map +1 -1
- package/dist/commands/skills-source.d.ts.map +1 -1
- package/dist/commands/skills-source.js +10 -0
- package/dist/commands/skills-source.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +169 -1
- package/dist/commands/skills.js.map +1 -1
- package/dist/image-digests.json +7 -7
- package/dist/index.js +4361 -1768
- package/dist/lib/bootstrap-kubernetes.d.ts +42 -0
- package/dist/lib/bootstrap-kubernetes.d.ts.map +1 -0
- package/dist/lib/bootstrap-kubernetes.js +287 -0
- package/dist/lib/bootstrap-kubernetes.js.map +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +6 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/flywheel-probes.d.ts +58 -0
- package/dist/lib/flywheel-probes.d.ts.map +1 -0
- package/dist/lib/flywheel-probes.js +163 -0
- package/dist/lib/flywheel-probes.js.map +1 -0
- package/dist/lib/shim-generator.d.ts +51 -0
- package/dist/lib/shim-generator.d.ts.map +1 -0
- package/dist/lib/shim-generator.js +88 -0
- package/dist/lib/shim-generator.js.map +1 -0
- package/dist/lib/skills-apply-overlays.d.ts +35 -0
- package/dist/lib/skills-apply-overlays.d.ts.map +1 -0
- package/dist/lib/skills-apply-overlays.js +243 -0
- package/dist/lib/skills-apply-overlays.js.map +1 -0
- package/dist/mcp-server.js +1106 -453
- package/hermes-bundle/version.json +1 -1
- package/host-cp/k8s/manifests/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/kg-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/mcp-auth-service/50-deployment.yaml +1 -1
- package/host-cp/k8s/manifests/memory-service/30-configmap.yaml +11 -0
- package/host-cp/k8s/manifests/memory-service/35-configmap-iii-config.yaml +76 -0
- package/host-cp/k8s/manifests/memory-service/50-deployment.yaml +11 -1
- package/host-cp/observability/grafana-port-forward.sh +273 -0
- package/host-cp/observability/kyverno-cardinality-mutate.sh +452 -0
- package/host-cp/observability/loki-ingest.sh +243 -0
- package/host-cp/observability/prom-no-double-grafana.sh +301 -0
- package/host-cp/src/crystallize-planning.mjs +261 -0
- package/host-cp/src/plan-chat-service.mjs +84 -2
- package/host-cp/src/planning-sessions.mjs +270 -0
- package/package.json +1 -1
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# kyverno-cardinality-mutate.sh — Phase C C8 follow-up e2e smoke test.
|
|
3
|
+
#
|
|
4
|
+
# Verifies that the Kyverno ClusterPolicy
|
|
5
|
+
# `enforce-cardinality-labeldrop` mutates incoming ServiceMonitor and
|
|
6
|
+
# PodMonitor objects at admission time, regardless of authorship,
|
|
7
|
+
# closing codex's "policy by convention" gap on PR #783.
|
|
8
|
+
#
|
|
9
|
+
# Test approach:
|
|
10
|
+
# 1. helm-install Kyverno (pinned 3.8.1) into the `kyverno` namespace.
|
|
11
|
+
# 2. Apply the ClusterPolicy.
|
|
12
|
+
# 3. POSITIVE test: apply ServiceMonitor `kyverno-mutate-positive-test`
|
|
13
|
+
# with selector `app: kyverno-mutate-positive-test` (no backing Service)
|
|
14
|
+
# and NO metricRelabelings; assert Kyverno mutated it; delete immediately.
|
|
15
|
+
# 4. IDEMPOTENCY test: apply ServiceMonitor `kyverno-mutate-idempotency-test`
|
|
16
|
+
# with selector `app: kyverno-mutate-idempotency-test` (different non-existent
|
|
17
|
+
# label) and the labeldrop already present; assert count stays at 1; delete.
|
|
18
|
+
# 5. SCRAPE-VERIFICATION test: deploy synthetic `kyverno-emitter` (Service +
|
|
19
|
+
# Deployment + ConfigMap) + dedicated ServiceMonitor `kyverno-emitter-sm`
|
|
20
|
+
# applied WITHOUT metricRelabelings; assert Kyverno mutates the SM at admission;
|
|
21
|
+
# wait for pod Ready; poll Prometheus for http_requests_total; assert
|
|
22
|
+
# world_id label is ABSENT.
|
|
23
|
+
#
|
|
24
|
+
# Key design decision: POSITIVE and IDEMPOTENCY tests use selectors that match
|
|
25
|
+
# no real Service, so they are isolated from each other and from the SCRAPE test.
|
|
26
|
+
# A single dedicated SM (`kyverno-emitter-sm`) owns the emitter endpoint, so
|
|
27
|
+
# prometheus-operator can reliably reconcile exactly one scrape config for it.
|
|
28
|
+
# Root cause of the prior failure (PR #828 CI run 26239574154): two SMs
|
|
29
|
+
# (naive-violator + pre-armoured-violator) competed for the same
|
|
30
|
+
# `app: kyverno-emitter` Endpoints; operator never reconciled either.
|
|
31
|
+
#
|
|
32
|
+
# Pre-conditions:
|
|
33
|
+
# - kube-prometheus-stack installed (cardinality-drop.sh ran).
|
|
34
|
+
# - kubectl context set to a live cluster; helm + jq + curl available.
|
|
35
|
+
#
|
|
36
|
+
# Idempotency: kubectl apply is idempotent; helm upgrade --install is
|
|
37
|
+
# idempotent. Cleanup trap removes synthetic resources on exit. The
|
|
38
|
+
# ClusterPolicy + Kyverno install are LEFT in the cluster (permanent
|
|
39
|
+
# C8 fixtures).
|
|
40
|
+
#
|
|
41
|
+
# Refs: docs/plans/k3s-ingress-observability/phase-c-tasks.md — C8
|
|
42
|
+
# codex review on PR #783 ("policy by convention" finding)
|
|
43
|
+
# PR #828 CI run 26239574154 (competing-SM root cause)
|
|
44
|
+
|
|
45
|
+
set -euo pipefail
|
|
46
|
+
|
|
47
|
+
KYVERNO_VERSION="3.8.1"
|
|
48
|
+
KYVERNO_NAMESPACE="kyverno"
|
|
49
|
+
TEST_NAMESPACE="monitoring"
|
|
50
|
+
PROM_LOCAL_PORT="9092" # 9090, 9091 may be in use by sibling Phase C scripts
|
|
51
|
+
PF_BIND_SECONDS=5
|
|
52
|
+
TARGET_DISCOVERY_TIMEOUT=180
|
|
53
|
+
SCRAPE_POLL_INTERVAL=10
|
|
54
|
+
|
|
55
|
+
log() { printf '[kyverno-mutate] %s\n' "$*" >&2; }
|
|
56
|
+
fail() { printf '[kyverno-mutate] FAIL: %s\n' "$*" >&2; exit 1; }
|
|
57
|
+
|
|
58
|
+
REPO_ROOT="$(git -C "$(dirname "$0")" rev-parse --show-toplevel 2>/dev/null || pwd)"
|
|
59
|
+
|
|
60
|
+
# -------------------------------------------------------------------------
|
|
61
|
+
# Cleanup trap — kill port-forwards; remove synthetic resources on exit.
|
|
62
|
+
# Kyverno chart + ClusterPolicy stay (permanent C8 fixtures).
|
|
63
|
+
# -------------------------------------------------------------------------
|
|
64
|
+
PROM_PF_PID=""
|
|
65
|
+
cleanup() {
|
|
66
|
+
[[ -n "$PROM_PF_PID" ]] && kill "$PROM_PF_PID" 2>/dev/null || true
|
|
67
|
+
log "removing synthetic resources (idempotent)"
|
|
68
|
+
# Mutation-test SMs (already deleted inline, but --ignore-not-found makes this safe)
|
|
69
|
+
kubectl delete servicemonitor kyverno-mutate-positive-test -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
|
|
70
|
+
kubectl delete servicemonitor kyverno-mutate-idempotency-test -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
|
|
71
|
+
# Scrape-verification resources
|
|
72
|
+
kubectl delete servicemonitor kyverno-emitter-sm -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
|
|
73
|
+
kubectl delete deployment kyverno-emitter -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
|
|
74
|
+
kubectl delete service kyverno-emitter-svc -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
|
|
75
|
+
kubectl delete configmap kyverno-emitter-config -n "$TEST_NAMESPACE" --ignore-not-found=true 2>/dev/null || true
|
|
76
|
+
}
|
|
77
|
+
trap cleanup EXIT
|
|
78
|
+
|
|
79
|
+
# -------------------------------------------------------------------------
|
|
80
|
+
# Pre-flight
|
|
81
|
+
# -------------------------------------------------------------------------
|
|
82
|
+
command -v helm >/dev/null 2>&1 || fail "helm not installed"
|
|
83
|
+
command -v kubectl >/dev/null 2>&1 || fail "kubectl not installed"
|
|
84
|
+
command -v curl >/dev/null 2>&1 || fail "curl not installed"
|
|
85
|
+
command -v jq >/dev/null 2>&1 || fail "jq not installed"
|
|
86
|
+
kubectl cluster-info >/dev/null 2>&1 || fail "kubectl: no reachable cluster; set KUBECONFIG"
|
|
87
|
+
|
|
88
|
+
# kube-prom-stack must already be up — we rely on Prometheus + the
|
|
89
|
+
# ServiceMonitor CRD existing.
|
|
90
|
+
kubectl get crd servicemonitors.monitoring.coreos.com >/dev/null 2>&1 \
|
|
91
|
+
|| fail "ServiceMonitor CRD not present — run prom-no-double-grafana.sh first"
|
|
92
|
+
kubectl get deployment -n "$TEST_NAMESPACE" -l "app.kubernetes.io/name=prometheus-operator" \
|
|
93
|
+
>/dev/null 2>&1 \
|
|
94
|
+
|| fail "prometheus-operator not found in $TEST_NAMESPACE — run prom-no-double-grafana.sh first"
|
|
95
|
+
|
|
96
|
+
log "pre-flight checks passed"
|
|
97
|
+
|
|
98
|
+
# -------------------------------------------------------------------------
|
|
99
|
+
# Step 1: helm-install Kyverno
|
|
100
|
+
#
|
|
101
|
+
# Repo add is idempotent; helm upgrade --install handles fresh install + upgrade.
|
|
102
|
+
# `--wait` blocks until pods are Ready; admission webhook needs to be live
|
|
103
|
+
# before we apply the ClusterPolicy or our test ServiceMonitors.
|
|
104
|
+
# -------------------------------------------------------------------------
|
|
105
|
+
log "ensuring kyverno helm repo is configured"
|
|
106
|
+
helm repo add kyverno https://kyverno.github.io/kyverno/ >/dev/null 2>&1 || true
|
|
107
|
+
helm repo update kyverno >/dev/null 2>&1 || true
|
|
108
|
+
|
|
109
|
+
log "installing kyverno chart $KYVERNO_VERSION (waits for admission webhook Ready)"
|
|
110
|
+
helm upgrade --install olam-kyverno kyverno/kyverno \
|
|
111
|
+
--version "$KYVERNO_VERSION" \
|
|
112
|
+
--namespace "$KYVERNO_NAMESPACE" \
|
|
113
|
+
--create-namespace \
|
|
114
|
+
-f "$REPO_ROOT/packages/peripheral-services/helm-values/kyverno-values.yaml" \
|
|
115
|
+
--wait --timeout 300s 2>&1 | tail -8
|
|
116
|
+
|
|
117
|
+
# Sanity: kyverno-admission-controller Deployment Ready.
|
|
118
|
+
kubectl get deployment -n "$KYVERNO_NAMESPACE" -l "app.kubernetes.io/component=admission-controller" \
|
|
119
|
+
>/dev/null 2>&1 \
|
|
120
|
+
|| fail "kyverno admission controller not found in $KYVERNO_NAMESPACE"
|
|
121
|
+
|
|
122
|
+
log "waiting for kyverno admission webhook to be registered with apiserver"
|
|
123
|
+
# The webhook registration is the LAST thing kyverno does after pod-Ready;
|
|
124
|
+
# poll until our ClusterPolicy can be admitted.
|
|
125
|
+
elapsed=0
|
|
126
|
+
while [ "$elapsed" -lt 120 ]; do
|
|
127
|
+
if kubectl get validatingwebhookconfiguration kyverno-policy-validating-webhook-cfg \
|
|
128
|
+
>/dev/null 2>&1; then
|
|
129
|
+
log "kyverno webhooks registered after ${elapsed}s"
|
|
130
|
+
break
|
|
131
|
+
fi
|
|
132
|
+
sleep 5
|
|
133
|
+
elapsed=$((elapsed + 5))
|
|
134
|
+
done
|
|
135
|
+
if [ "$elapsed" -ge 120 ]; then
|
|
136
|
+
fail "kyverno webhook registration timed out after 120s"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# -------------------------------------------------------------------------
|
|
140
|
+
# Step 2: Apply the ClusterPolicy
|
|
141
|
+
# -------------------------------------------------------------------------
|
|
142
|
+
log "applying ClusterPolicy enforce-cardinality-labeldrop"
|
|
143
|
+
kubectl apply -f "$REPO_ROOT/packages/peripheral-services/manifests/96-kyverno-cardinality-mutate.yaml"
|
|
144
|
+
|
|
145
|
+
# Wait for policy to be Ready (Kyverno controller picks it up and reports
|
|
146
|
+
# readiness in status.ready / .conditions).
|
|
147
|
+
log "waiting up to 60s for ClusterPolicy to be Ready"
|
|
148
|
+
elapsed=0
|
|
149
|
+
while [ "$elapsed" -lt 60 ]; do
|
|
150
|
+
READY=$(kubectl get clusterpolicy enforce-cardinality-labeldrop \
|
|
151
|
+
-o jsonpath='{.status.ready}' 2>/dev/null || echo "")
|
|
152
|
+
if [ "$READY" = "true" ]; then
|
|
153
|
+
log "ClusterPolicy Ready after ${elapsed}s"
|
|
154
|
+
break
|
|
155
|
+
fi
|
|
156
|
+
sleep 3
|
|
157
|
+
elapsed=$((elapsed + 3))
|
|
158
|
+
done
|
|
159
|
+
if [ "$elapsed" -ge 60 ]; then
|
|
160
|
+
log "WARN: ClusterPolicy status.ready not observed within 60s; proceeding (status field can lag)"
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# -------------------------------------------------------------------------
|
|
164
|
+
# Step 3: POSITIVE test — mutation only, no backing Service
|
|
165
|
+
#
|
|
166
|
+
# Uses selector `app: kyverno-mutate-positive-test` — a label that no
|
|
167
|
+
# real Service carries, so this SM never competes with anything for
|
|
168
|
+
# Endpoints. Its sole job is to exercise the Kyverno admission webhook.
|
|
169
|
+
#
|
|
170
|
+
# Deleted immediately after assertion so the SM space is clean when
|
|
171
|
+
# the scrape test runs.
|
|
172
|
+
# -------------------------------------------------------------------------
|
|
173
|
+
log "POSITIVE test: applying naive ServiceMonitor (no metricRelabelings, non-Service-backed selector)"
|
|
174
|
+
kubectl apply -f - <<'EOF'
|
|
175
|
+
---
|
|
176
|
+
apiVersion: monitoring.coreos.com/v1
|
|
177
|
+
kind: ServiceMonitor
|
|
178
|
+
metadata:
|
|
179
|
+
name: kyverno-mutate-positive-test
|
|
180
|
+
namespace: monitoring
|
|
181
|
+
labels:
|
|
182
|
+
release: olam-prom
|
|
183
|
+
spec:
|
|
184
|
+
namespaceSelector:
|
|
185
|
+
matchNames:
|
|
186
|
+
- monitoring
|
|
187
|
+
selector:
|
|
188
|
+
matchLabels:
|
|
189
|
+
app: kyverno-mutate-positive-test
|
|
190
|
+
endpoints:
|
|
191
|
+
- port: metrics
|
|
192
|
+
interval: 15s
|
|
193
|
+
# NOTE: deliberately NO metricRelabelings — Kyverno must inject it.
|
|
194
|
+
EOF
|
|
195
|
+
|
|
196
|
+
# Read back and assert.
|
|
197
|
+
ACTUAL=$(kubectl get servicemonitor kyverno-mutate-positive-test -n "$TEST_NAMESPACE" -o json \
|
|
198
|
+
| jq -r '.spec.endpoints[0].metricRelabelings // [] | tojson')
|
|
199
|
+
log "kyverno-mutate-positive-test metricRelabelings after admission: $ACTUAL"
|
|
200
|
+
|
|
201
|
+
INJECTED_COUNT=$(echo "$ACTUAL" | jq '[ .[] | select(.action == "labeldrop" and (.regex | contains("world_id"))) ] | length')
|
|
202
|
+
if [ "$INJECTED_COUNT" -lt 1 ]; then
|
|
203
|
+
log "actual policy state:"
|
|
204
|
+
kubectl get clusterpolicy enforce-cardinality-labeldrop -o yaml >&2 || true
|
|
205
|
+
fail "POSITIVE test FAILED: Kyverno did not inject labeldrop into naive ServiceMonitor — third-party bypass gap NOT closed"
|
|
206
|
+
fi
|
|
207
|
+
log "PASS: naive ServiceMonitor was mutated at admission (labeldrop injected)"
|
|
208
|
+
|
|
209
|
+
log "deleting kyverno-mutate-positive-test (mutation-only test; SM space clean for scrape test)"
|
|
210
|
+
kubectl delete servicemonitor kyverno-mutate-positive-test -n "$TEST_NAMESPACE" --ignore-not-found=true
|
|
211
|
+
|
|
212
|
+
# -------------------------------------------------------------------------
|
|
213
|
+
# Step 4: IDEMPOTENCY test — mutation only, no backing Service
|
|
214
|
+
#
|
|
215
|
+
# Uses selector `app: kyverno-mutate-idempotency-test` — different from
|
|
216
|
+
# the positive test and from the scrape test label. No real Service.
|
|
217
|
+
# Deleted immediately after assertion.
|
|
218
|
+
# -------------------------------------------------------------------------
|
|
219
|
+
log "IDEMPOTENCY test: applying pre-armoured ServiceMonitor (labeldrop already present)"
|
|
220
|
+
kubectl apply -f - <<'EOF'
|
|
221
|
+
---
|
|
222
|
+
apiVersion: monitoring.coreos.com/v1
|
|
223
|
+
kind: ServiceMonitor
|
|
224
|
+
metadata:
|
|
225
|
+
name: kyverno-mutate-idempotency-test
|
|
226
|
+
namespace: monitoring
|
|
227
|
+
labels:
|
|
228
|
+
release: olam-prom
|
|
229
|
+
spec:
|
|
230
|
+
namespaceSelector:
|
|
231
|
+
matchNames:
|
|
232
|
+
- monitoring
|
|
233
|
+
selector:
|
|
234
|
+
matchLabels:
|
|
235
|
+
app: kyverno-mutate-idempotency-test
|
|
236
|
+
endpoints:
|
|
237
|
+
- port: metrics
|
|
238
|
+
interval: 15s
|
|
239
|
+
metricRelabelings:
|
|
240
|
+
- action: labeldrop
|
|
241
|
+
regex: 'world_id|trace_id|user_id|request_id|operator_id'
|
|
242
|
+
EOF
|
|
243
|
+
|
|
244
|
+
DUP_COUNT=$(kubectl get servicemonitor kyverno-mutate-idempotency-test -n "$TEST_NAMESPACE" -o json \
|
|
245
|
+
| jq '[ .spec.endpoints[0].metricRelabelings[] | select(.action == "labeldrop" and (.regex | contains("world_id"))) ] | length')
|
|
246
|
+
log "kyverno-mutate-idempotency-test labeldrop count: $DUP_COUNT"
|
|
247
|
+
if [ "$DUP_COUNT" -ne 1 ]; then
|
|
248
|
+
kubectl get servicemonitor kyverno-mutate-idempotency-test -n "$TEST_NAMESPACE" -o yaml >&2
|
|
249
|
+
fail "IDEMPOTENCY test FAILED: expected 1 labeldrop entry, got $DUP_COUNT — policy double-adds"
|
|
250
|
+
fi
|
|
251
|
+
log "PASS: pre-armoured ServiceMonitor has exactly 1 labeldrop (no double-add)"
|
|
252
|
+
|
|
253
|
+
log "deleting kyverno-mutate-idempotency-test (mutation-only test; SM space clean for scrape test)"
|
|
254
|
+
kubectl delete servicemonitor kyverno-mutate-idempotency-test -n "$TEST_NAMESPACE" --ignore-not-found=true
|
|
255
|
+
|
|
256
|
+
# -------------------------------------------------------------------------
|
|
257
|
+
# Step 5: SCRAPE-VERIFICATION test — dedicated SM + Service + Pod
|
|
258
|
+
#
|
|
259
|
+
# One SM (`kyverno-emitter-sm`) selects exactly one Service (`kyverno-emitter-svc`).
|
|
260
|
+
# No other SM in the cluster selects `app: kyverno-emitter`, so prometheus-operator
|
|
261
|
+
# reconciles a single clean scrape config.
|
|
262
|
+
#
|
|
263
|
+
# The SM is applied WITHOUT metricRelabelings so Kyverno's admission webhook
|
|
264
|
+
# fires — this is the load-bearing check that the policy applies during real
|
|
265
|
+
# scrape setup, not just on test fixtures.
|
|
266
|
+
#
|
|
267
|
+
# After admission we verify the spec has the labeldrop, then wait for the pod
|
|
268
|
+
# to be Ready and poll Prometheus for http_requests_total. We assert
|
|
269
|
+
# world_id is absent from all returned series.
|
|
270
|
+
#
|
|
271
|
+
# Mirrors the working pattern from dashboards-have-data.sh (single dedicated
|
|
272
|
+
# SM + co-located Service in `monitoring` namespace).
|
|
273
|
+
# -------------------------------------------------------------------------
|
|
274
|
+
log "SCRAPE-VERIFICATION test: deploying synthetic kyverno-emitter (emits http_requests_total{world_id})"
|
|
275
|
+
kubectl apply -f - <<'EOF'
|
|
276
|
+
---
|
|
277
|
+
apiVersion: v1
|
|
278
|
+
kind: ConfigMap
|
|
279
|
+
metadata:
|
|
280
|
+
name: kyverno-emitter-config
|
|
281
|
+
namespace: monitoring
|
|
282
|
+
data:
|
|
283
|
+
metrics: |
|
|
284
|
+
# HELP http_requests_total Synthetic counter; world_id is the cardinality bomb
|
|
285
|
+
# TYPE http_requests_total counter
|
|
286
|
+
http_requests_total{world_id="kyverno-world",route="/api",method="GET",status_code="200"} 1
|
|
287
|
+
---
|
|
288
|
+
apiVersion: apps/v1
|
|
289
|
+
kind: Deployment
|
|
290
|
+
metadata:
|
|
291
|
+
name: kyverno-emitter
|
|
292
|
+
namespace: monitoring
|
|
293
|
+
labels:
|
|
294
|
+
app: kyverno-emitter
|
|
295
|
+
spec:
|
|
296
|
+
replicas: 1
|
|
297
|
+
selector:
|
|
298
|
+
matchLabels:
|
|
299
|
+
app: kyverno-emitter
|
|
300
|
+
template:
|
|
301
|
+
metadata:
|
|
302
|
+
labels:
|
|
303
|
+
app: kyverno-emitter
|
|
304
|
+
spec:
|
|
305
|
+
containers:
|
|
306
|
+
- name: emitter
|
|
307
|
+
image: python:3.11-alpine
|
|
308
|
+
ports:
|
|
309
|
+
- containerPort: 8080
|
|
310
|
+
command: ["python3", "-c"]
|
|
311
|
+
args:
|
|
312
|
+
- |
|
|
313
|
+
import http.server
|
|
314
|
+
with open('/config/metrics') as f: METRICS = f.read().encode()
|
|
315
|
+
class H(http.server.BaseHTTPRequestHandler):
|
|
316
|
+
def do_GET(self):
|
|
317
|
+
if self.path != '/metrics':
|
|
318
|
+
self.send_response(404); self.end_headers(); return
|
|
319
|
+
self.send_response(200)
|
|
320
|
+
self.send_header('Content-Type', 'text/plain; version=0.0.4; charset=utf-8')
|
|
321
|
+
self.end_headers()
|
|
322
|
+
self.wfile.write(METRICS)
|
|
323
|
+
def log_message(self, *a): pass
|
|
324
|
+
http.server.HTTPServer(('0.0.0.0', 8080), H).serve_forever()
|
|
325
|
+
volumeMounts:
|
|
326
|
+
- name: config
|
|
327
|
+
mountPath: /config
|
|
328
|
+
volumes:
|
|
329
|
+
- name: config
|
|
330
|
+
configMap:
|
|
331
|
+
name: kyverno-emitter-config
|
|
332
|
+
---
|
|
333
|
+
apiVersion: v1
|
|
334
|
+
kind: Service
|
|
335
|
+
metadata:
|
|
336
|
+
name: kyverno-emitter-svc
|
|
337
|
+
namespace: monitoring
|
|
338
|
+
labels:
|
|
339
|
+
app: kyverno-emitter
|
|
340
|
+
spec:
|
|
341
|
+
selector:
|
|
342
|
+
app: kyverno-emitter
|
|
343
|
+
ports:
|
|
344
|
+
- name: metrics
|
|
345
|
+
port: 8080
|
|
346
|
+
targetPort: 8080
|
|
347
|
+
EOF
|
|
348
|
+
|
|
349
|
+
log "waiting for kyverno-emitter deployment Ready"
|
|
350
|
+
kubectl rollout status deployment/kyverno-emitter -n "$TEST_NAMESPACE" --timeout=120s
|
|
351
|
+
|
|
352
|
+
# Apply the dedicated ServiceMonitor WITHOUT metricRelabelings so Kyverno
|
|
353
|
+
# mutates it at admission — this proves the policy fires on real SM objects,
|
|
354
|
+
# not just on the POSITIVE test fixture.
|
|
355
|
+
log "applying kyverno-emitter-sm (no metricRelabelings — Kyverno must inject)"
|
|
356
|
+
kubectl apply -f - <<'EOF'
|
|
357
|
+
---
|
|
358
|
+
apiVersion: monitoring.coreos.com/v1
|
|
359
|
+
kind: ServiceMonitor
|
|
360
|
+
metadata:
|
|
361
|
+
name: kyverno-emitter-sm
|
|
362
|
+
namespace: monitoring
|
|
363
|
+
labels:
|
|
364
|
+
release: olam-prom
|
|
365
|
+
spec:
|
|
366
|
+
namespaceSelector:
|
|
367
|
+
matchNames:
|
|
368
|
+
- monitoring
|
|
369
|
+
selector:
|
|
370
|
+
matchLabels:
|
|
371
|
+
app: kyverno-emitter
|
|
372
|
+
endpoints:
|
|
373
|
+
- port: metrics
|
|
374
|
+
interval: 15s
|
|
375
|
+
# NOTE: NO metricRelabelings — Kyverno must inject the labeldrop at admission.
|
|
376
|
+
EOF
|
|
377
|
+
|
|
378
|
+
# Verify Kyverno mutated this SM too (belt-and-suspenders: proves the policy
|
|
379
|
+
# applies to the SM that actually drives the scrape, not just the test fixtures).
|
|
380
|
+
SCRAPE_SM_ACTUAL=$(kubectl get servicemonitor kyverno-emitter-sm -n "$TEST_NAMESPACE" -o json \
|
|
381
|
+
| jq -r '.spec.endpoints[0].metricRelabelings // [] | tojson')
|
|
382
|
+
log "kyverno-emitter-sm metricRelabelings after admission: $SCRAPE_SM_ACTUAL"
|
|
383
|
+
|
|
384
|
+
SCRAPE_SM_INJECTED=$(echo "$SCRAPE_SM_ACTUAL" | jq '[ .[] | select(.action == "labeldrop" and (.regex | contains("world_id"))) ] | length')
|
|
385
|
+
if [ "$SCRAPE_SM_INJECTED" -lt 1 ]; then
|
|
386
|
+
log "actual policy state:"
|
|
387
|
+
kubectl get clusterpolicy enforce-cardinality-labeldrop -o yaml >&2 || true
|
|
388
|
+
fail "SCRAPE-VERIFICATION test FAILED: Kyverno did not mutate kyverno-emitter-sm at admission"
|
|
389
|
+
fi
|
|
390
|
+
log "PASS: kyverno-emitter-sm was mutated at admission (labeldrop injected)"
|
|
391
|
+
|
|
392
|
+
# Port-forward Prometheus and poll for metric samples.
|
|
393
|
+
log "port-forwarding svc/prometheus-operated $PROM_LOCAL_PORT:9090"
|
|
394
|
+
kubectl port-forward \
|
|
395
|
+
-n "$TEST_NAMESPACE" \
|
|
396
|
+
"svc/prometheus-operated" \
|
|
397
|
+
"${PROM_LOCAL_PORT}:9090" &
|
|
398
|
+
PROM_PF_PID=$!
|
|
399
|
+
sleep "$PF_BIND_SECONDS"
|
|
400
|
+
kill -0 "$PROM_PF_PID" 2>/dev/null \
|
|
401
|
+
|| fail "Prometheus port-forward exited prematurely"
|
|
402
|
+
|
|
403
|
+
PROM_URL="http://localhost:${PROM_LOCAL_PORT}"
|
|
404
|
+
|
|
405
|
+
# Direct-metric polling rather than target-discovery polling.
|
|
406
|
+
#
|
|
407
|
+
# Rationale: kube-prometheus-stack's default relabel sets the `job` label
|
|
408
|
+
# from the k8s Service name. Polling by job-name is brittle — operator
|
|
409
|
+
# reconciliation races, dropped-target filtering, and rare CRD revision
|
|
410
|
+
# lag have all surfaced as "target not in activeTargets" flakes during
|
|
411
|
+
# earlier ingress-integration runs. What we ACTUALLY care about is
|
|
412
|
+
# whether the mutated relabel was applied to a real scrape sample. So
|
|
413
|
+
# poll for the metric directly. With a single SM selecting on
|
|
414
|
+
# `app=kyverno-emitter`, any http_requests_total series returned
|
|
415
|
+
# necessarily came through kyverno-emitter-sm.
|
|
416
|
+
log "polling Prometheus for http_requests_total samples (up to ${TARGET_DISCOVERY_TIMEOUT}s)"
|
|
417
|
+
elapsed=0
|
|
418
|
+
RESULT=""
|
|
419
|
+
while [ "$elapsed" -lt "$TARGET_DISCOVERY_TIMEOUT" ]; do
|
|
420
|
+
RESULT=$(curl -sf "${PROM_URL}/api/v1/query?query=http_requests_total" 2>/dev/null || echo "")
|
|
421
|
+
if [ -n "$RESULT" ]; then
|
|
422
|
+
SERIES_COUNT=$(echo "$RESULT" | jq '.data.result | length' 2>/dev/null || echo "0")
|
|
423
|
+
if [ "$SERIES_COUNT" -ge 1 ]; then
|
|
424
|
+
log "http_requests_total returned $SERIES_COUNT series after ${elapsed}s"
|
|
425
|
+
break
|
|
426
|
+
fi
|
|
427
|
+
fi
|
|
428
|
+
sleep "$SCRAPE_POLL_INTERVAL"
|
|
429
|
+
elapsed=$((elapsed + SCRAPE_POLL_INTERVAL))
|
|
430
|
+
done
|
|
431
|
+
|
|
432
|
+
if [ "$elapsed" -ge "$TARGET_DISCOVERY_TIMEOUT" ]; then
|
|
433
|
+
log "Active targets snapshot for diagnosis:"
|
|
434
|
+
curl -sf "${PROM_URL}/api/v1/targets" | jq '.data.activeTargets[] | {job: .labels.job, service: .labels.service, namespace: .labels.namespace, health: .health, lastError: .lastError}' >&2 || true
|
|
435
|
+
log "ServiceMonitor kyverno-emitter-sm status:"
|
|
436
|
+
kubectl get servicemonitor kyverno-emitter-sm -n "$TEST_NAMESPACE" -o yaml >&2 || true
|
|
437
|
+
log "prometheus-operator log tail (last 50 lines):"
|
|
438
|
+
kubectl logs -n "$TEST_NAMESPACE" -l "app.kubernetes.io/name=prometheus-operator" --tail=50 >&2 || true
|
|
439
|
+
fail "Prometheus did not scrape kyverno-emitter within ${TARGET_DISCOVERY_TIMEOUT}s"
|
|
440
|
+
fi
|
|
441
|
+
|
|
442
|
+
SERIES_COUNT=$(echo "$RESULT" | jq '.data.result | length')
|
|
443
|
+
|
|
444
|
+
LEAKED=$(echo "$RESULT" | jq '[.data.result[] | .metric | has("world_id")] | any')
|
|
445
|
+
if [ "$LEAKED" = "true" ]; then
|
|
446
|
+
echo "$RESULT" | jq '.data.result[] | .metric' >&2
|
|
447
|
+
fail "world_id label leaked into Prometheus — Kyverno-mutated relabel did NOT take effect at scrape time"
|
|
448
|
+
fi
|
|
449
|
+
|
|
450
|
+
log "PASS: kyverno-emitter scraped via kyverno-emitter-sm; world_id absent at scrape time"
|
|
451
|
+
log "PASS: C8 verified — Kyverno mutates third-party-shaped ServiceMonitors at admission and the mutation takes effect at scrape time"
|
|
452
|
+
exit 0
|