From 1238d0b210bf1739b16419072c5d62f6ccff33cd Mon Sep 17 00:00:00 2001 From: Benjamin Borbe Date: Sat, 4 Jul 2026 17:58:40 +0200 Subject: [PATCH 1/2] support multiple controllers + optional agent PVC/resources + recurring affinity/pullSecrets overrides (chart 0.3.0) --- CHANGELOG.md | 5 + helm/Chart.yaml | 2 +- helm/templates/_helpers.tpl | 16 -- helm/templates/agents.yaml | 15 +- helm/templates/controller-secret.yaml | 17 +- helm/templates/controller-service.yaml | 15 +- helm/templates/controller-statefulset.yaml | 69 ++++--- .../recurring-task-creator-statefulset.yaml | 4 +- helm/values.yaml | 191 +++++++++++------- 9 files changed, 195 insertions(+), 139 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b1553c..f1f0dd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ Please choose versions by [Semantic Versioning](http://semver.org/). * MINOR version when you add functionality in a backwards-compatible manner, and * PATCH version when you make backwards-compatible bug fixes. +## Unreleased +- BREAKING(helm): replace the single `controller:` values block with a `controllers:` list so multiple per-vault controllers (e.g. openclaw + personal) install from one release; each entry renders `agent-task-controller-` StatefulSet + Service + Secret. Chart 0.2.0 → 0.3.0. +- helm(agents): make per-agent `volumeMountPath`/`volumeClaim`/PVC and `resources` optional (a stateless agent that declares no `volumeMountPath` gets no PVC), and add an optional `secretName` override (for agents whose existing Secret name differs from the agent name). Lets a cluster reference existing (e.g. teamvault-managed) Secrets by leaving `secretEnv` empty. +- helm(recurring-task-creator): add `recurringTaskCreator.affinity` and `recurringTaskCreator.pullSecrets` overrides (fall back to the globals) — recurring often pins a different node pool / pull secret than the executor + controllers. + ## v0.74.1 - Add `helm/README.md`: third-party install guide — prerequisites, `helm install` from the OCI registry, full values reference, a "generic cluster" divergence section (no keel/mirror/TeamVault/Strimzi), and the two-chart (core + maintainer) story. diff --git a/helm/Chart.yaml b/helm/Chart.yaml index 32d5381..d114baa 100644 --- a/helm/Chart.yaml +++ b/helm/Chart.yaml @@ -3,7 +3,7 @@ name: agent description: bborbe agent framework — event-driven, Kafka-based, K8s-native task platform (controller, executor, and example agents) type: application # Chart version — bumped on chart changes, independent of appVersion. -version: 0.2.0 +version: 0.3.0 # Default image tag for the framework components when values don't override. appVersion: "0.3.1" home: https://github.com/bborbe/agent diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index 4ca3587..1400181 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -17,24 +17,8 @@ helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }} {{- printf "%s/%s:%s" .Values.image.registry .Values.executor.image.repository $tag -}} {{- end -}} -{{/* Controller image ref: registry/repository:tag, tag defaulting to appVersion. */}} -{{- define "agent.controller.image" -}} -{{- $tag := .Values.controller.image.tag | default .Chart.AppVersion -}} -{{- printf "%s/%s:%s" .Values.image.registry .Values.controller.image.repository $tag -}} -{{- end -}} - {{/* Recurring-task-creator image ref: registry/repository:tag, tag defaulting to appVersion. */}} {{- define "agent.recurringTaskCreator.image" -}} {{- $tag := .Values.recurringTaskCreator.image.tag | default .Chart.AppVersion -}} {{- printf "%s/%s:%s" .Values.image.registry .Values.recurringTaskCreator.image.repository $tag -}} {{- end -}} - -{{/* Controller GIT_REST_URL: explicit value wins; otherwise derive from vaultName - as the in-cluster obsidian git-rest service URL. */}} -{{- define "agent.controller.gitRestUrl" -}} -{{- if .Values.controller.gitRestUrl -}} -{{- .Values.controller.gitRestUrl -}} -{{- else -}} -{{- printf "http://vault-obsidian-%s:9090" (required "controller.vaultName is required" .Values.controller.vaultName) -}} -{{- end -}} -{{- end -}} diff --git a/helm/templates/agents.yaml b/helm/templates/agents.yaml index 51e4bf8..1211ae5 100644 --- a/helm/templates/agents.yaml +++ b/helm/templates/agents.yaml @@ -15,9 +15,11 @@ spec: priorityClassName: {{ $agent.name }} image: {{ printf "%s/%s:%s" $.Values.image.registry $agent.image ($agent.tag | default $.Chart.AppVersion) | quote }} heartbeat: {{ $agent.heartbeat | quote }} - secretName: {{ $agent.name }} - volumeClaim: {{ $agent.name }} + secretName: {{ $agent.secretName | default $agent.name }} + {{- if $agent.volumeMountPath }} + volumeClaim: {{ $agent.volumeClaim | default $agent.name }} volumeMountPath: {{ $agent.volumeMountPath | quote }} + {{- end }} {{- with $agent.env }} env: {{- toYaml . | nindent 4 }} @@ -46,19 +48,26 @@ data: {{ $k }}: {{ $v | b64enc }} {{- end }} {{- end }} +{{- if $agent.volumeMountPath }} --- +# PVC only when the agent mounts a volume (some agents — e.g. a stateless +# backtest runner — declare no volumeMountPath and get no claim). apiVersion: v1 kind: PersistentVolumeClaim metadata: - name: {{ $agent.name }} + name: {{ $agent.volumeClaim | default $agent.name }} namespace: {{ include "agent.namespace" $ }} labels: {{- include "agent.labels" $ | nindent 4 }} spec: accessModes: [ReadWriteOnce] + {{- with $agent.storageClassName }} + storageClassName: {{ . }} + {{- end }} resources: requests: storage: {{ $agent.storageSize | default "1Gi" }} +{{- end }} --- # PriorityClass is cluster-scoped (no namespace). Named per-agent so the # ResourceQuota below can cap concurrent pods of exactly this agent. diff --git a/helm/templates/controller-secret.yaml b/helm/templates/controller-secret.yaml index ce490a1..6d4a479 100644 --- a/helm/templates/controller-secret.yaml +++ b/helm/templates/controller-secret.yaml @@ -1,17 +1,20 @@ -{{- if and .Values.controller.enabled (not .Values.controller.existingSecret) (or .Values.controller.sentry.dsn .Values.controller.gatewaySecret) }} +{{- range $c := .Values.controllers }} +{{- if and $c.enabled (not $c.existingSecret) (or ($c.sentry | default dict).dsn $c.gatewaySecret) }} +--- # Created only when no existingSecret is referenced AND at least one secret value # is set. Quant resolves the Sentry DSN + gateway secret from teamvault and passes # them via --set-string; a generic install sets them directly or points -# controller.existingSecret at a Secret managed outside the chart. +# controllers[].existingSecret at a Secret managed outside the chart. apiVersion: v1 kind: Secret type: Opaque metadata: - name: agent-task-controller - namespace: {{ include "agent.namespace" . }} + name: agent-task-controller-{{ $c.name }} + namespace: {{ include "agent.namespace" $ }} labels: - {{- include "agent.labels" . | nindent 4 }} + {{- include "agent.labels" $ | nindent 4 }} data: - sentry-dsn: {{ .Values.controller.sentry.dsn | b64enc | quote }} - gateway-secret: {{ .Values.controller.gatewaySecret | b64enc | quote }} + sentry-dsn: {{ ($c.sentry | default dict).dsn | b64enc | quote }} + gateway-secret: {{ $c.gatewaySecret | b64enc | quote }} +{{- end }} {{- end }} diff --git a/helm/templates/controller-service.yaml b/helm/templates/controller-service.yaml index b26bc3e..97f3968 100644 --- a/helm/templates/controller-service.yaml +++ b/helm/templates/controller-service.yaml @@ -1,12 +1,14 @@ -{{- if .Values.controller.enabled }} +{{- range $c := .Values.controllers }} +{{- if $c.enabled }} +--- apiVersion: v1 kind: Service metadata: - name: agent-task-controller - namespace: {{ include "agent.namespace" . }} + name: agent-task-controller-{{ $c.name }} + namespace: {{ include "agent.namespace" $ }} labels: - app: agent-task-controller - {{- include "agent.labels" . | nindent 4 }} + app: agent-task-controller-{{ $c.name }} + {{- include "agent.labels" $ | nindent 4 }} annotations: admin/port: '9090' admin/path: '' @@ -15,5 +17,6 @@ spec: - name: http port: 9090 selector: - app: agent-task-controller + app: agent-task-controller-{{ $c.name }} +{{- end }} {{- end }} diff --git a/helm/templates/controller-statefulset.yaml b/helm/templates/controller-statefulset.yaml index 820c028..093d346 100644 --- a/helm/templates/controller-statefulset.yaml +++ b/helm/templates/controller-statefulset.yaml @@ -1,30 +1,32 @@ -{{- if .Values.controller.enabled }} +{{- range $c := .Values.controllers }} +{{- if $c.enabled }} +--- apiVersion: apps/v1 kind: StatefulSet metadata: - name: agent-task-controller - namespace: {{ include "agent.namespace" . }} + name: agent-task-controller-{{ $c.name }} + namespace: {{ include "agent.namespace" $ }} labels: - app: agent-task-controller - {{- include "agent.labels" . | nindent 4 }} + app: agent-task-controller-{{ $c.name }} + {{- include "agent.labels" $ | nindent 4 }} annotations: - {{- if .Values.keel.enabled }} + {{- if $.Values.keel.enabled }} keel.sh/policy: force keel.sh/trigger: poll keel.sh/match-tag: "true" - keel.sh/pollSchedule: {{ .Values.keel.pollSchedule | quote }} + keel.sh/pollSchedule: {{ $.Values.keel.pollSchedule | quote }} {{- end }} - {{- with .Values.rolloutNonce }} + {{- with $.Values.rolloutNonce }} random: {{ . | quote }} {{- end }} spec: replicas: 1 - serviceName: agent-task-controller + serviceName: agent-task-controller-{{ $c.name }} updateStrategy: type: RollingUpdate selector: matchLabels: - app: agent-task-controller + app: agent-task-controller-{{ $c.name }} template: metadata: annotations: @@ -32,67 +34,67 @@ spec: prometheus.io/port: "9090" prometheus.io/scheme: http prometheus.io/scrape: "true" - {{- with .Values.rolloutNonce }} + {{- with $.Values.rolloutNonce }} random: {{ . | quote }} {{- end }} labels: - app: agent-task-controller + app: agent-task-controller-{{ $c.name }} spec: - {{- with .Values.controller.podSecurityContext }} + {{- with $c.podSecurityContext }} securityContext: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- with $.Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} containers: - name: service - image: {{ include "agent.controller.image" . | quote }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- with .Values.controller.securityContext }} + image: {{ printf "%s/%s:%s" $.Values.image.registry (($c.image | default dict).repository | default "bborbe/agent-task-controller") (($c.image | default dict).tag | default $.Chart.AppVersion) | quote }} + imagePullPolicy: {{ $.Values.image.pullPolicy }} + {{- with $c.securityContext }} securityContext: {{- toYaml . | nindent 12 }} {{- end }} args: - - -v={{ .Values.controller.logLevel }} + - -v={{ $c.logLevel }} env: - name: LISTEN value: ':9090' - name: SENTRY_DSN valueFrom: secretKeyRef: - name: {{ .Values.controller.existingSecret | default "agent-task-controller" }} + name: {{ $c.existingSecret | default (printf "agent-task-controller-%s" $c.name) }} key: sentry-dsn - name: GATEWAY_SECRET valueFrom: secretKeyRef: - name: {{ .Values.controller.existingSecret | default "agent-task-controller" }} + name: {{ $c.existingSecret | default (printf "agent-task-controller-%s" $c.name) }} key: gateway-secret - {{- with .Values.controller.sentry.proxy }} + {{- with ($c.sentry | default dict).proxy }} - name: SENTRY_PROXY value: {{ . | quote }} {{- end }} - name: BRANCH - value: {{ .Values.controller.branch | quote }} + value: {{ $c.branch | quote }} - name: TOPIC_PREFIX - value: {{ .Values.controller.topicPrefix | quote }} + value: {{ $c.topicPrefix | quote }} - name: KAFKA_BROKERS - value: {{ required "controller.kafkaBrokers is required" .Values.controller.kafkaBrokers | quote }} + value: {{ required "controllers[].kafkaBrokers is required" $c.kafkaBrokers | quote }} - name: GIT_REST_URL - value: {{ include "agent.controller.gitRestUrl" . | quote }} + value: {{ $c.gitRestUrl | default (printf "http://vault-obsidian-%s:9090" (required "controllers[].vaultName is required" $c.vaultName)) | quote }} - name: VAULT_NAME - value: {{ required "controller.vaultName is required" .Values.controller.vaultName | quote }} + value: {{ required "controllers[].vaultName is required" $c.vaultName | quote }} - name: POLL_INTERVAL - value: {{ .Values.controller.pollInterval | default "60s" | quote }} + value: {{ $c.pollInterval | default "60s" | quote }} - name: TASK_DIR - value: {{ .Values.controller.taskDir | quote }} + value: {{ $c.taskDir | quote }} - name: DATA_DIR value: '/data/bolt' - name: NO_SYNC value: "false" - name: AUTO_INJECT_TASK_IDENTIFIER - value: {{ .Values.controller.autoInjectTaskIdentifier | quote }} + value: {{ $c.autoInjectTaskIdentifier | quote }} - name: NAMESPACE valueFrom: fieldRef: @@ -117,11 +119,11 @@ spec: initialDelaySeconds: 5 timeoutSeconds: 5 resources: - {{- toYaml .Values.controller.resources | nindent 12 }} + {{- toYaml $c.resources | nindent 12 }} volumeMounts: - name: datadir mountPath: /data - {{- with .Values.image.pullSecrets }} + {{- with $.Values.image.pullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} @@ -130,12 +132,13 @@ spec: name: datadir spec: accessModes: [ReadWriteOnce] - {{- with .Values.controller.storage.storageClassName }} + {{- with ($c.storage | default dict).storageClassName }} # Omitted when empty so the cluster's DEFAULT StorageClass is used; # an explicit "" would instead disable dynamic provisioning. storageClassName: {{ . }} {{- end }} resources: requests: - storage: {{ .Values.controller.storage.size }} + storage: {{ ($c.storage | default dict).size | default "1Gi" }} +{{- end }} {{- end }} diff --git a/helm/templates/recurring-task-creator-statefulset.yaml b/helm/templates/recurring-task-creator-statefulset.yaml index 82d2d83..4e459be 100644 --- a/helm/templates/recurring-task-creator-statefulset.yaml +++ b/helm/templates/recurring-task-creator-statefulset.yaml @@ -108,7 +108,7 @@ spec: securityContext: {{- toYaml . | nindent 8 }} {{- end }} - {{- with .Values.affinity }} + {{- with (.Values.recurringTaskCreator.affinity | default .Values.affinity) }} affinity: {{- toYaml . | nindent 8 }} {{- end }} @@ -175,7 +175,7 @@ spec: volumes: - name: tmp emptyDir: {} - {{- with .Values.image.pullSecrets }} + {{- with (.Values.recurringTaskCreator.pullSecrets | default .Values.image.pullSecrets) }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} diff --git a/helm/values.yaml b/helm/values.yaml index 78c5616..5793a12 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -77,74 +77,107 @@ executor: cpu: 500m memory: 50Mi -# Controller: the StatefulSet that watches vault tasks, syncs a git-rest vault, -# and publishes work to Kafka. Reuses the shared image.registry / keel / affinity -# / pullSecrets values; only its own coordinates live here. -controller: - enabled: true - image: - repository: bborbe/agent-task-controller - # tag defaults to Chart.appVersion when empty. - tag: "" - logLevel: "2" - # Vault name this controller drives. Required. Also derives the default - # GIT_REST_URL (http://vault-obsidian-:9090) when gitRestUrl is empty. - vaultName: "" - # Explicit git-rest base URL. Empty = derive from vaultName as - # http://vault-obsidian-:9090. - # IMPORTANT: that derivation hard-codes the quant service naming convention - # (a `vault-obsidian-` Service on port 9090). If your git-rest/Obsidian - # API Service is named differently the controller will fail to reach it with no - # install-time warning — set this explicitly on any non-quant cluster. - gitRestUrl: "" - # Sub-path inside the vault holding task files. Empty = vault root. - taskDir: "" - # Stage label forwarded as BRANCH env. Empty = unset. - branch: "" - # Explicit Kafka topic prefix. Empty = unprefixed. Quant sets develop / master. - topicPrefix: "" - # Kafka bootstrap brokers. Required. - kafkaBrokers: "" - # Auto-inject a task identifier into created tasks. String "true"/"false". - autoInjectTaskIdentifier: "false" - # Vault poll interval. Default 60s. - pollInterval: "60s" - # BoltDB persistent volume (volumeClaimTemplates). storageClassName defaults to - # "" = the cluster's default StorageClass (portable). quant sets "local-path" - # in its values overlay; that requires the Local Path Provisioner and is - # single-node oriented — do NOT default it here or multi-node clusters fail to - # bind the PVC. - storage: - size: 1Gi - storageClassName: "" - sentry: - # Sentry proxy cluster URL. Optional. - proxy: "" - # Sentry DSN secret value. When set (and existingSecret empty), the chart - # creates the Secret; quant resolves it from teamvault via --set-string. - dsn: "" - # Gateway secret value (key gateway-secret). Same Secret as sentry-dsn. - gatewaySecret: "" - # Name of a pre-existing Secret providing keys sentry-dsn + gateway-secret. - # When set, the chart does NOT create the Secret. - existingSecret: "" - # Pod-level securityContext. Empty by default — clusters opt in. - podSecurityContext: {} - # Container-level securityContext. Same safe hardening defaults as the executor. - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: false - capabilities: - drop: [ALL] - seccompProfile: - type: RuntimeDefault - resources: - requests: - cpu: 20m - memory: 20Mi - limits: - cpu: 500m - memory: 50Mi +# Controllers: values-driven LIST of StatefulSets, each watching one vault's +# tasks, syncing a git-rest vault, and publishing work to Kafka. Each entry emits +# an `agent-task-controller-` StatefulSet + Service + Secret. The real quant +# cluster runs two (openclaw + personal); a generic install adds the controllers +# it needs. Reuses the shared image.registry / keel / affinity / pullSecrets +# values; only per-controller coordinates live here. Empty by default so an +# executor-only install renders no controllers. +controllers: [] + # - name: openclaw # instance name → workload agent-task-controller- + # enabled: true + # image: + # repository: bborbe/agent-task-controller + # tag: "" # empty => Chart.appVersion + # logLevel: "2" + # # Vault name this controller drives. Required. Also derives the default + # # GIT_REST_URL (http://vault-obsidian-:9090) when gitRestUrl is empty. + # vaultName: openclaw + # # Explicit git-rest base URL. Empty = derive from vaultName as + # # http://vault-obsidian-:9090. + # # IMPORTANT: that derivation hard-codes the quant service naming convention + # # (a `vault-obsidian-` Service on port 9090). If your git-rest/Obsidian + # # API Service is named differently the controller will fail to reach it with no + # # install-time warning — set this explicitly on any non-quant cluster. + # gitRestUrl: "" + # # Sub-path inside the vault holding task files. Empty = vault root. + # taskDir: "" + # # Stage label forwarded as BRANCH env. Empty = unset. + # branch: "" + # # Explicit Kafka topic prefix. Empty = unprefixed. Quant sets develop / master. + # topicPrefix: "" + # # Kafka bootstrap brokers. Required. + # kafkaBrokers: kafka:9092 + # # Auto-inject a task identifier into created tasks. String "true"/"false". + # autoInjectTaskIdentifier: "false" + # # Vault poll interval. Default 60s. + # pollInterval: "60s" + # # BoltDB persistent volume (volumeClaimTemplates). storageClassName defaults to + # # "" = the cluster's default StorageClass (portable). quant sets "local-path" + # # in its values overlay; that requires the Local Path Provisioner and is + # # single-node oriented — do NOT default it here or multi-node clusters fail to + # # bind the PVC. + # storage: + # size: 1Gi + # storageClassName: "" + # sentry: + # # Sentry proxy cluster URL. Optional. + # proxy: "" + # # Sentry DSN secret value. When set (and existingSecret empty), the chart + # # creates the Secret; quant resolves it from teamvault via --set-string. + # dsn: "" + # # Gateway secret value (key gateway-secret). Same Secret as sentry-dsn. + # gatewaySecret: "" + # # Name of a pre-existing Secret providing keys sentry-dsn + gateway-secret. + # # When set, the chart does NOT create the Secret. + # existingSecret: "" + # # Pod-level securityContext. Empty by default — clusters opt in. + # podSecurityContext: {} + # # Container-level securityContext. Same safe hardening defaults as the executor. + # securityContext: + # allowPrivilegeEscalation: false + # readOnlyRootFilesystem: false + # capabilities: + # drop: [ALL] + # seccompProfile: + # type: RuntimeDefault + # resources: + # requests: {cpu: 20m, memory: 20Mi} + # limits: {cpu: 500m, memory: 50Mi} + # - name: personal + # enabled: true + # image: + # repository: bborbe/agent-task-controller + # tag: "" + # logLevel: "2" + # vaultName: personal + # gitRestUrl: "" + # taskDir: "" + # branch: "" + # topicPrefix: "" + # kafkaBrokers: kafka:9092 + # autoInjectTaskIdentifier: "false" + # pollInterval: "60s" + # storage: + # size: 1Gi + # storageClassName: "" + # sentry: + # proxy: "" + # dsn: "" + # gatewaySecret: "" + # existingSecret: "" + # podSecurityContext: {} + # securityContext: + # allowPrivilegeEscalation: false + # readOnlyRootFilesystem: false + # capabilities: + # drop: [ALL] + # seccompProfile: + # type: RuntimeDefault + # resources: + # requests: {cpu: 20m, memory: 20Mi} + # limits: {cpu: 500m, memory: 50Mi} # Leaf agents: values-driven list. Each entry emits a Config CR + Secret + PVC + # PriorityClass (cluster-scoped) + ResourceQuota (caps concurrent pods). Empty by @@ -160,14 +193,23 @@ agents: [] # taskTypes: [llm, healthcheck] # triggerPhases: [planning, execution, ai_review] # triggerStatuses: [in_progress] - # volumeMountPath: /home/claude/.claude # where the PVC mounts in the job pod + # secretName: agent-claude # OPTIONAL: Config.spec.secretName. Defaults to + # # `name`. Set when the existing Secret has a + # # different name (e.g. agent-backtest-agent → agent-backtest). + # volumeMountPath: /home/claude/.claude # OPTIONAL. When set → volumeClaim + + # # PVC are emitted. OMIT entirely for a stateless + # # agent (no PVC, no volumeClaim) — e.g. a backtest runner. + # volumeClaim: agent-claude # OPTIONAL: PVC name. Defaults to `name`. # storageSize: 1Gi # PVC size (default 1Gi) + # storageClassName: "" # OPTIONAL PVC class; empty => cluster default (omitted) # env: # plain env injected into the Config # ALLOWED_TOOLS: WebSearch,WebFetch,Read,Grep - # secretEnv: # rendered into a per-agent Secret (base64) + # secretEnv: # OPTIONAL: rendered into a per-agent Secret (base64). + # # OMIT to reference an existing (e.g. teamvault-managed) + # # Secret via secretName instead of creating one. # SENTRY_DSN: dummy # ANTHROPIC_AUTH_TOKEN: dummy - # resources: + # resources: # OPTIONAL — omit for an agent with no resource block # requests: {cpu: 500m, memory: 1Gi, ephemeral-storage: 2Gi} # limits: {cpu: 500m, memory: 1Gi, ephemeral-storage: 2Gi} # priorityValue: 500 # PriorityClass value (default 500) @@ -200,6 +242,13 @@ recurringTaskCreator: # Name of a pre-existing Secret providing key sentry-dsn. When set, no Secret # is created. existingSecret: "" + # Node affinity override. Empty = fall back to the global .Values.affinity. + # recurring-task-creator often pins to a different node pool than the executor/ + # controllers (e.g. quant: node_type=agent here vs node_type= globally). + affinity: {} + # Image pull secret override. Empty = fall back to the global image.pullSecrets. + # (quant's recurring uses `docker-quant` while the rest use `docker`.) + pullSecrets: [] # Pod-level securityContext. fsGroup 65534 (nobody) so the emptyDir /tmp is # group-writable by the non-root user. podSecurityContext: From 44377487888289a5293b0fcf22bfae71e700ba03 Mon Sep 17 00:00:00 2001 From: Benjamin Borbe Date: Sat, 4 Jul 2026 18:05:39 +0200 Subject: [PATCH 2/2] document controllers[] breaking-change migration path + gitRestUrl derivation caveat --- CHANGELOG.md | 11 +++++++++++ helm/templates/controller-statefulset.yaml | 4 ++++ 2 files changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1f0dd1..c95fda1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,17 @@ Please choose versions by [Semantic Versioning](http://semver.org/). ## Unreleased - BREAKING(helm): replace the single `controller:` values block with a `controllers:` list so multiple per-vault controllers (e.g. openclaw + personal) install from one release; each entry renders `agent-task-controller-` StatefulSet + Service + Secret. Chart 0.2.0 → 0.3.0. + - **Migration (0.2.0 → 0.3.0):** `controllers:` defaults to `[]` (empty), so an existing install that set `controller.enabled: true` renders ZERO controllers until migrated. Move the old block into a one-item list and add a `name`: + ```yaml + # before (0.2.0) # after (0.3.0) + controller: controllers: + enabled: true - name: main # NEW: names the objects + vaultName: myvault enabled: true + kafkaBrokers: ... vaultName: myvault + ... kafkaBrokers: ... + ... + ``` + - **Object rename:** the StatefulSet/Service/Secret/ServiceAccount are now `agent-task-controller-` (were `agent-task-controller`). External RBAC, NetworkPolicies, or tooling that referenced the old fixed name must be updated to the new per-name form. On the quant crossover use `--take-ownership`; the old kubectl-managed objects already carry per-vault names so no rename is needed there. - helm(agents): make per-agent `volumeMountPath`/`volumeClaim`/PVC and `resources` optional (a stateless agent that declares no `volumeMountPath` gets no PVC), and add an optional `secretName` override (for agents whose existing Secret name differs from the agent name). Lets a cluster reference existing (e.g. teamvault-managed) Secrets by leaving `secretEnv` empty. - helm(recurring-task-creator): add `recurringTaskCreator.affinity` and `recurringTaskCreator.pullSecrets` overrides (fall back to the globals) — recurring often pins a different node pool / pull secret than the executor + controllers. diff --git a/helm/templates/controller-statefulset.yaml b/helm/templates/controller-statefulset.yaml index 093d346..f6d715a 100644 --- a/helm/templates/controller-statefulset.yaml +++ b/helm/templates/controller-statefulset.yaml @@ -81,6 +81,10 @@ spec: value: {{ $c.topicPrefix | quote }} - name: KAFKA_BROKERS value: {{ required "controllers[].kafkaBrokers is required" $c.kafkaBrokers | quote }} + # GIT_REST_URL: explicit controllers[].gitRestUrl wins; otherwise + # derived as http://vault-obsidian-:9090 — a quant service + # naming convention. On any cluster whose git-rest Service is named + # differently, set controllers[].gitRestUrl explicitly (see values.yaml). - name: GIT_REST_URL value: {{ $c.gitRestUrl | default (printf "http://vault-obsidian-%s:9090" (required "controllers[].vaultName is required" $c.vaultName)) | quote }} - name: VAULT_NAME