Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Dockerfile.helm-runner
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM registry.access.redhat.com/ubi9/ubi-minimal@sha256:ae09ecc3d754bc1726cbda3e2599cc7839e09fe1cc547ce173cf669b645be3cc

ARG HELM_VERSION=3.15.4
ARG TARGETARCH=amd64

RUN microdnf install -y ca-certificates curl gzip tar && microdnf clean all
RUN curl -fsSLo /tmp/helm.tar.gz "https://get.helm.sh/helm-v${HELM_VERSION}-linux-${TARGETARCH}.tar.gz" \
&& curl -fsSLo /tmp/helm.tar.gz.sha256sum "https://get.helm.sh/helm-v${HELM_VERSION}-linux-${TARGETARCH}.tar.gz.sha256sum" \
&& cd /tmp \
&& sha256sum -c helm.tar.gz.sha256sum \
&& tar -xzf helm.tar.gz \
&& install -m 0755 "linux-${TARGETARCH}/helm" /usr/local/bin/helm \
&& rm -rf /tmp/helm.tar.gz /tmp/helm.tar.gz.sha256sum "/tmp/linux-${TARGETARCH}"

USER 1001
WORKDIR /tmp/work
ENTRYPOINT ["/usr/local/bin/helm"]
51 changes: 49 additions & 2 deletions deploy/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
set -euo pipefail
DEPLOY_START=$(date +%s)
VALUES_FILE="/tmp/pulse-deploy-values-$$.yaml"
trap 'rm -f /tmp/pulse-ui-build.log /tmp/pulse-ui-push.log "$VALUES_FILE"' EXIT
trap 'rm -f /tmp/pulse-ui-build.log /tmp/pulse-ui-push.log /tmp/pulse-helm-runner.digest "$VALUES_FILE"' EXIT

# ─── Configuration ───────────────────────────────────────────────────────────

Expand All @@ -26,6 +26,8 @@ NAMESPACE="openshiftpulse"
RELEASE="pulse"
UI_IMAGE="${PULSE_UI_IMAGE:-quay.io/amobrem/openshiftpulse}"
AGENT_IMAGE="${PULSE_AGENT_IMAGE:-quay.io/amobrem/pulse-agent}"
HELM_RUNNER_IMAGE="${PULSE_HELM_RUNNER_IMAGE:-quay.io/amobrem/pulse-helm-runner}"
HELM_RUNNER_IMAGE_REF="${PULSE_HELM_RUNNER_IMAGE_REF:-}"
UI_TAG=""
AGENT_TAG=""
_WS_TOKEN_OVERRIDE="${PULSE_AGENT_WS_TOKEN:-}"
Expand Down Expand Up @@ -252,6 +254,10 @@ fi
if [[ -z "$AGENT_TAG" ]]; then
AGENT_TAG=$(git_tag "$AGENT_REPO")
fi
if [[ -n "$HELM_RUNNER_IMAGE_REF" && ! "$HELM_RUNNER_IMAGE_REF" =~ ^[^[:space:]@]+@sha256:[a-fA-F0-9]{64}$ ]]; then
error "PULSE_HELM_RUNNER_IMAGE_REF must be an immutable digest reference (repo@sha256:...)"
exit 1
fi

info "UI tag: $UI_TAG"
info "Agent tag: $AGENT_TAG"
Expand Down Expand Up @@ -305,6 +311,7 @@ if [[ "$DRY_RUN" == "true" ]]; then
echo " Namespace: $NAMESPACE"
echo " UI image: ${UI_IMAGE}:${UI_TAG}"
echo " Agent image: ${AGENT_IMAGE}:${AGENT_TAG}"
echo " Helm runner: ${HELM_RUNNER_IMAGE_REF:-${HELM_RUNNER_IMAGE}:${UI_TAG} (digest resolved after push)}"
if [[ -n "${ANTHROPIC_VERTEX_PROJECT_ID:-}" ]]; then
echo " AI backend: Vertex AI (${ANTHROPIC_VERTEX_PROJECT_ID} / ${CLOUD_ML_REGION:-us-east5})"
elif [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
Expand Down Expand Up @@ -333,10 +340,15 @@ fi
pnpm run build
info "UI built (dist/)"

info "Building UI and Agent images in parallel..."
info "Building UI, Agent, and Helm runner images..."
podman build --platform linux/amd64 -t "${UI_IMAGE}:${UI_TAG}" "$PROJECT_DIR" &>/tmp/pulse-ui-build.log &
UI_BUILD_PID=$!

if [[ -z "$HELM_RUNNER_IMAGE_REF" ]]; then
podman build --platform linux/amd64 -t "${HELM_RUNNER_IMAGE}:${UI_TAG}" -f "$PROJECT_DIR/Dockerfile.helm-runner" "$PROJECT_DIR"
info "Helm runner image built"
fi

cd "$AGENT_REPO"
# Default Dockerfile is the full single-stage build.
# Use Dockerfile.fast only when the pre-built deps image is available in-cluster.
Expand All @@ -355,6 +367,7 @@ fi
info "Pushing images..."
podman tag "${UI_IMAGE}:${UI_TAG}" "${UI_IMAGE}:latest"
podman tag "${AGENT_IMAGE}:${AGENT_TAG}" "${AGENT_IMAGE}:latest"
[[ -z "$HELM_RUNNER_IMAGE_REF" ]] && podman tag "${HELM_RUNNER_IMAGE}:${UI_TAG}" "${HELM_RUNNER_IMAGE}:latest"

podman push "${UI_IMAGE}:${UI_TAG}" &>/tmp/pulse-ui-push.log &
UI_PUSH_PID=$!
Expand All @@ -363,6 +376,15 @@ fi
podman push "${AGENT_IMAGE}:latest"
info "Pushed ${AGENT_IMAGE}:${AGENT_TAG} + latest"

if [[ -z "$HELM_RUNNER_IMAGE_REF" ]]; then
podman push --digestfile /tmp/pulse-helm-runner.digest "${HELM_RUNNER_IMAGE}:${UI_TAG}"
[[ -s /tmp/pulse-helm-runner.digest ]] || { error "Helm runner push did not produce a digest"; exit 1; }
podman push "${HELM_RUNNER_IMAGE}:latest"
HELM_RUNNER_DIGEST=$(cat /tmp/pulse-helm-runner.digest)
HELM_RUNNER_IMAGE_REF="${HELM_RUNNER_IMAGE}@${HELM_RUNNER_DIGEST}"
info "Pushed Helm runner ${HELM_RUNNER_IMAGE_REF}"
fi

if wait $UI_PUSH_PID; then
podman push "${UI_IMAGE}:latest"
info "Pushed ${UI_IMAGE}:${UI_TAG} + latest"
Expand Down Expand Up @@ -433,15 +455,22 @@ fi
# Generate values file (keeps secrets out of process listings)
AI_BACKEND="none"
AI_VALUES=""
AI_VALUES_COMPAT=""
if [[ -n "${ANTHROPIC_VERTEX_PROJECT_ID:-}" ]]; then
AI_VALUES=" vertexAI:
projectId: ${ANTHROPIC_VERTEX_PROJECT_ID}
region: ${CLOUD_ML_REGION:-us-east5}
existingSecret: gcp-sa-key"
AI_VALUES_COMPAT=" vertexAI:
projectId: ${ANTHROPIC_VERTEX_PROJECT_ID}
region: ${CLOUD_ML_REGION:-us-east5}
existingSecret: gcp-sa-key"
AI_BACKEND="vertex"
elif [[ -n "${ANTHROPIC_API_KEY:-}" ]]; then
AI_VALUES=" anthropicApiKey:
existingSecret: anthropic-api-key"
AI_VALUES_COMPAT=" anthropicApiKey:
existingSecret: anthropic-api-key"
AI_BACKEND="anthropic"
fi

Expand Down Expand Up @@ -474,6 +503,9 @@ openshiftpulse:
enabled: $MONITORING_ENABLED
alertmanager:
enabled: $MONITORING_ENABLED
helmInstall:
runnerImage: "$HELM_RUNNER_IMAGE_REF"
serviceAccountName: openshiftpulse-helm-installer
agent:
enabled: true
serviceName: $AGENT_DEPLOY
Expand All @@ -487,9 +519,24 @@ agent:
rbac:
allowWriteOperations: ${AGENT_ALLOW_WRITES:-false}
allowSecretAccess: ${AGENT_ALLOW_SECRETS:-false}
mcp:
enabled: false
wsAuth:
existingSecret: $WS_SECRET
$AI_VALUES
openshift-sre-agent:
enabled: true
image:
repository: $AGENT_IMAGE
tag: "$AGENT_TAG"
rbac:
allowWriteOperations: ${AGENT_ALLOW_WRITES:-false}
allowSecretAccess: ${AGENT_ALLOW_SECRETS:-false}
mcp:
enabled: false
wsAuth:
existingSecret: $WS_SECRET
$AI_VALUES_COMPAT
YAML
chmod 600 "$VALUES_FILE"

Expand Down
5 changes: 3 additions & 2 deletions deploy/helm/openshiftpulse/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,16 @@ spec:
echo "Agent WS token: injected at template time"
fi
{{- end }}
SA_TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token 2>/dev/null || echo "")
sed -i "s|__SA_TOKEN__|${SA_TOKEN}|g" /tmp/nginx.conf
exec nginx -c /tmp/nginx.conf -g 'daemon off;'
ports:
- containerPort: 8080
volumeMounts:
- name: nginx-config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
- name: nginx-config
mountPath: /opt/app-root/src/config.js
subPath: config.js
{{- if .Values.agent.enabled }}
- name: agent-token
mountPath: /etc/nginx/agent-token
Expand Down
28 changes: 16 additions & 12 deletions deploy/helm/openshiftpulse/templates/nginx-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ metadata:
labels:
{{- include "openshiftpulse.labels" . | nindent 4 }}
data:
config.js: |
window.__OPENSHIFTPULSE_CONFIG__ = {
helmInstall: {
runnerImage: {{ .Values.helmInstall.runnerImage | quote }},
serviceAccountName: {{ .Values.helmInstall.serviceAccountName | quote }}
}
};
Comment on lines +9 to +15
nginx.conf: |
worker_processes auto;
error_log /dev/stderr warn;
Expand Down Expand Up @@ -44,11 +51,10 @@ data:
proxy_pass https://kubernetes.default.svc/;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /var/run/secrets/kubernetes.io/serviceaccount/ca.crt;
set $k8s_token $http_x_forwarded_access_token;
if ($k8s_token = '') {
set $k8s_token '__SA_TOKEN__';
if ($http_x_forwarded_access_token = '') {
return 401 '{"error":"X-Forwarded-Access-Token header is required"}';
}
proxy_set_header Authorization "Bearer $k8s_token";
proxy_set_header Authorization "Bearer $http_x_forwarded_access_token";
Comment on lines +54 to +57
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
Expand All @@ -60,11 +66,10 @@ data:
proxy_pass https://{{ .Values.monitoring.prometheus.host }}/;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt;
set $prom_token $http_x_forwarded_access_token;
if ($prom_token = '') {
set $prom_token '__SA_TOKEN__';
if ($http_x_forwarded_access_token = '') {
return 401 '{"error":"X-Forwarded-Access-Token header is required"}';
}
proxy_set_header Authorization "Bearer $prom_token";
proxy_set_header Authorization "Bearer $http_x_forwarded_access_token";
proxy_read_timeout 60s;
}
{{- else }}
Expand All @@ -84,11 +89,10 @@ data:
proxy_pass https://{{ .Values.monitoring.alertmanager.host }}/;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate /var/run/secrets/kubernetes.io/serviceaccount/service-ca.crt;
set $am_token $http_x_forwarded_access_token;
if ($am_token = '') {
set $am_token '__SA_TOKEN__';
if ($http_x_forwarded_access_token = '') {
return 401 '{"error":"X-Forwarded-Access-Token header is required"}';
}
proxy_set_header Authorization "Bearer $am_token";
proxy_set_header Authorization "Bearer $http_x_forwarded_access_token";
proxy_read_timeout 60s;
}
{{- else }}
Expand Down
6 changes: 6 additions & 0 deletions deploy/helm/openshiftpulse/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ monitoring:
selfMonitor:
enabled: true

# In-cluster Helm installs use a project-owned runner image. Leave empty to
# fail closed rather than execute a mutable third-party image.
helmInstall:
runnerImage: ""
serviceAccountName: openshiftpulse-helm-installer

# Resource limits
resources:
app:
Expand Down
6 changes: 3 additions & 3 deletions deploy/helm/pulse/Chart.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ dependencies:
version: 4.7.0
- name: openshift-sre-agent
repository: file://../../../../pulse-agent/chart
version: 2.6.0
digest: sha256:a9ce7715a3e0c6ae6513674f5de98df4be4d79627d42a96091fe7d620fa69d17
generated: "2026-04-28T18:09:47.246482-07:00"
version: 1.13.0
digest: sha256:0408c4a4e57404efbda9a53cf921bb4cde8634b182e14bdb7c87cdc8d760aca5
generated: "2026-06-09T16:58:30.041978735+02:00"
2 changes: 1 addition & 1 deletion deploy/helm/pulse/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ dependencies:
repository: "file://../openshiftpulse"

- name: openshift-sre-agent
version: "2.7.1"
version: "1.13.0"
repository: "file://../../../../pulse-agent/chart"
alias: agent
condition: agent.enabled
Binary file not shown.
Binary file not shown.
Binary file modified deploy/helm/pulse/charts/openshiftpulse-4.7.0.tgz
Binary file not shown.
14 changes: 14 additions & 0 deletions deploy/helm/pulse/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ openshiftpulse:
enabled: true
host: alertmanager-main.openshift-monitoring.svc:9094

helmInstall:
runnerImage: ""
serviceAccountName: openshiftpulse-helm-installer

agent:
enabled: true
# Auto-derived from release name if empty: <release>-openshift-sre-agent
Expand Down Expand Up @@ -65,6 +69,9 @@ agent:
allowWriteOperations: false
allowSecretAccess: false

mcp:
enabled: false

database:
type: postgresql
postgresql:
Expand All @@ -79,3 +86,10 @@ agent:
wsAuth:
existingSecret: "pulse-ws-token"
secretKey: "token"

# Compatibility for stale packaged dependencies that were not aliased as
# `agent`. Keep this until the archived dependency is refreshed everywhere.
openshift-sre-agent:
enabled: true
mcp:
enabled: false
42 changes: 37 additions & 5 deletions deploy/test-helm.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ AGENT_VALUES=(
--set agent.enabled=true
--set agent.image.repository=quay.io/test/agent
--set agent.image.tag=test
--set agent.wsAuth.existingSecret=pulse-ws-token
--set agent.anthropicApiKey.existingSecret=test-secret
# Compatibility for stale packaged dependencies before helm dependency update.
--set openshift-sre-agent.enabled=true
--set openshift-sre-agent.image.repository=quay.io/test/agent
--set openshift-sre-agent.image.tag=test
--set openshift-sre-agent.wsAuth.existingSecret=pulse-ws-token
--set openshift-sre-agent.anthropicApiKey.existingSecret=test-secret
)

# 1. Helm lint
Expand All @@ -53,8 +60,7 @@ fi

# 2. Template renders without errors
echo "--- Template rendering ---"
RENDERED=$(helm template pulse "$CHART_DIR" "${COMMON[@]}" "${AGENT_VALUES[@]}" 2>&1)
if [[ $? -eq 0 ]]; then
if RENDERED=$(helm template pulse "$CHART_DIR" "${COMMON[@]}" "${AGENT_VALUES[@]}" 2>&1); then
pass "Template renders (agent enabled)"
else
fail "Template renders (agent enabled)"
Expand Down Expand Up @@ -118,10 +124,10 @@ else
fi

# 10. Agent disabled renders without errors
RENDERED_NO_AGENT=$(helm template pulse "$CHART_DIR" "${COMMON[@]}" \
if RENDERED_NO_AGENT=$(helm template pulse "$CHART_DIR" "${COMMON[@]}" \
--set openshiftpulse.agent.enabled=false \
--set agent.enabled=false 2>&1)
if [[ $? -eq 0 ]]; then
--set agent.enabled=false \
--set openshift-sre-agent.enabled=false 2>&1); then
pass "Template renders (agent disabled)"
else
fail "Template renders (agent disabled)"
Expand Down Expand Up @@ -149,6 +155,32 @@ else
fail "RollingUpdate strategy missing"
fi

# 14. User token proxy must fail closed instead of falling back to service-account token
if has "$RENDERED" "__SA_TOKEN__"; then
fail "nginx config still contains service-account token fallback"
else
pass "nginx config has no service-account token fallback"
fi

if has "$RENDERED" "X-Forwarded-Access-Token header is required"; then
pass "nginx returns 401 when forwarded access token is missing"
else
fail "nginx missing-token 401 guard not rendered"
fi

if has "$RENDERED" "sed -i \"s|__SA_TOKEN__"; then
fail "deployment still injects service-account token into nginx"
else
pass "deployment does not inject service-account token into nginx"
fi

# 15. MCP server must not render by default
if has "$RENDERED" "app.kubernetes.io/component: mcp-server"; then
fail "MCP server renders by default"
else
pass "MCP server disabled by default"
fi

# Summary
echo ""
echo "==========================="
Expand Down
1 change: 1 addition & 0 deletions public/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
window.__OPENSHIFTPULSE_CONFIG__ = window.__OPENSHIFTPULSE_CONFIG__ || {};
7 changes: 4 additions & 3 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="OpenShift Pulse — Next-generation OpenShift Console" />
<title>OpenShift Pulse</title>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0%25' stop-color='%232563eb'/%3E%3Cstop offset='100%25' stop-color='%237c3aed'/%3E%3C/linearGradient%3E%3ClinearGradient id='ln' x1='0' y1='0' x2='1' y2='0'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='50%25' stop-color='%23ffffff'/%3E%3Cstop offset='100%25' stop-color='%23a78bfa'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='7' fill='url(%23bg)'/%3E%3Cpolyline points='4,16.5 9,16.5 11,16.5 13,12 15,21 17,7 19,25 21,13 23,16.5 25,16.5 28,16.5' fill='none' stroke='url(%23ln)' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='17' cy='7' r='1.2' fill='white' opacity='0.5'/%3E%3C/svg%3E" />
<meta name="description" content="OpenShift Pulse — Next-generation OpenShift Console" />
<title>OpenShift Pulse</title>
<script src="/config.js"></script>
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Cdefs%3E%3ClinearGradient id='bg' x1='0' y1='0' x2='1' y2='1'%3E%3Cstop offset='0%25' stop-color='%232563eb'/%3E%3Cstop offset='100%25' stop-color='%237c3aed'/%3E%3C/linearGradient%3E%3ClinearGradient id='ln' x1='0' y1='0' x2='1' y2='0'%3E%3Cstop offset='0%25' stop-color='%2360a5fa'/%3E%3Cstop offset='50%25' stop-color='%23ffffff'/%3E%3Cstop offset='100%25' stop-color='%23a78bfa'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='32' height='32' rx='7' fill='url(%23bg)'/%3E%3Cpolyline points='4,16.5 9,16.5 11,16.5 13,12 15,21 17,7 19,25 21,13 23,16.5 25,16.5 28,16.5' fill='none' stroke='url(%23ln)' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3Ccircle cx='17' cy='7' r='1.2' fill='white' opacity='0.5'/%3E%3C/svg%3E" />
Comment on lines +6 to +9
<style>
/* Prevent white flash before CSS loads */
html, body { margin: 0; padding: 0; background-color: #020617; color: #e2e8f0; }
Expand Down
Loading