Skip to content

Replace Engine with Worker - #34

Merged
MarceloRGonc merged 13 commits into
mainfrom
mg/OPS-4456
Sep 23, 2026
Merged

MarceloRGonc merged 13 commits into
mainfrom
mg/OPS-4456

Conversation

@MarceloRGonc

Copy link
Copy Markdown
Contributor

Part of OPS-4456

Copilot AI lite review requested due to automatic review settings June 5, 2026 16:46
@linear

linear Bot commented Jun 5, 2026

Copy link
Copy Markdown

OPS-4456

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Helm chart to replace the “engine” component with a “worker” component, including renaming values keys and updating templates to deploy and wire the worker service.

Changes:

  • Rename values/config from engine to worker (including production/CI overlays and schema).
  • Update Kubernetes manifests (Deployment/Service/HPA/PDB/ServiceMonitor/NetworkPolicy/ServiceAccount) to target the worker component and port 3000.
  • Replace OPS_ENGINE_URL with OPS_WORKER_URL and add OPS_CONTAINER_TYPE env var(s).

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated no comments.

Show a summary per file
File Description
chart/values.yaml Renames engine config/env to worker and updates ingress comment reference.
chart/values.schema.json Renames schema section from engine to worker and updates description text in that section.
chart/values.production.yaml Updates production overlay key from engine to worker.
chart/values.ci.yaml Updates CI overlay key from engine to worker.
chart/templates/servicemonitor.yaml Renames the engine ServiceMonitor to worker and points to worker metrics path.
chart/templates/serviceaccount-worker.yaml Switches ServiceAccount creation/labels from engine to worker.
chart/templates/service-worker.yaml Renames service to worker and changes service port/targetPort to 3000.
chart/templates/secret-env.yaml Updates secret auto-generation env sources to include .Values.worker.env instead of engine.
chart/templates/pdb-worker.yaml Switches PDB values/labels from engine to worker.
chart/templates/networkpolicy.yaml Updates network policies to reference worker component and port 3000.
chart/templates/hpa-worker.yaml Switches HPA target/values from engine to worker.
chart/templates/external-secret.yaml Updates external secret env aggregation from engine to worker.
chart/templates/deployment-worker.yaml Renames deployment/image/env wiring from engine to worker; updates container port to 3000 and adds OPS_CONTAINER_TYPE=WORKER.
chart/templates/deployment-app.yaml Adds OPS_CONTAINER_TYPE=APP to the app deployment.
chart/templates/_helpers.tpl Replaces openops.engineServiceUrl with openops.workerServiceUrl and updates port to 3000.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

MarceloRGonc and others added 2 commits June 29, 2026 09:06
Every PDB in the chart set minAvailable, while analytics and tables default
to a single replica. minAvailable: 1 against one replica evaluates to
disruptionsAllowed: 0, so those pods cannot be evicted and every voluntary
disruption fails: cluster upgrades, node image upgrades and autoscaler
scale-down all stall while it is set.

This is not theoretical. It left an Azure AKS estate on three-month-old node
images across four clusters, with the pools reporting Failed and nothing
alerting on it, because the drain could never complete.

maxUnavailable: 1 is equivalent at two replicas and drainable at one, so all
five components now default to it rather than only fixing the two that
deadlock today. Overriding replicas down to 1 is a supported thing to do and
should not reintroduce the deadlock.

minAvailable still works when maxUnavailable is unset, so existing overrides
are unaffected. A PDB may not set both; maxUnavailable takes precedence.

One behaviour change to note: at three or more replicas maxUnavailable: 1
permits one pod down at a time where minAvailable: 1 permitted all but one.
That is safer but makes drains slower.

tables uses ReadWriteOnce storage and cannot be scaled past one replica, so
it still incurs brief downtime while its node drains. This makes the drain
possible, not seamless.

Verified by rendering the chart: all five PDBs emit maxUnavailable: 1 with
default values, and an override of maxUnavailable: null with minAvailable: 2
still emits minAvailable: 2. helm lint and both CI template steps pass. Note
that CI never exercises this path, since values.ci.yaml disables PDBs.

Part of OPS-4725
@MarceloRGonc
MarceloRGonc changed the base branch from main to mg/pdb-maxunavailable August 13, 2026 11:29
The template guarded on truthiness, and Go templates treat 0 as false, so
maxUnavailable: 0 fell through to rendering minAvailable: 1 — silently
producing a PDB that differs from the values that asked for it. 0 is a valid
PodDisruptionBudget value.

kindIs "invalid" tests for nil instead, which keeps all four cases correct:
an explicit 0 renders as 0, an explicit null falls back to minAvailable, an
absent key falls back, and a set value renders. hasKey would not work here,
since it is true for maxUnavailable: null and would render an empty field.

Also correct two documentation errors. The README claimed a PDB "may not set
both" fields and then that maxUnavailable "wins if you set both", conflating
the rendered resource with the values schema. AGENTS.md described PDBs as
covering "all stateless components" while tables, which has one, is stateful.

Part of OPS-4725
Base automatically changed from mg/pdb-maxunavailable to main August 13, 2026 11:53
MarceloRGonc and others added 2 commits August 13, 2026 12:56
e2e is red for a reason that predates this PR: no released openops-worker image exists in any public registry, and the last released openops-app (0.6.25) predates the engine→worker rename this branch carries, so it crash-loops on OPS_ENGINE_URL. It clears with the 0.6.26 release.
Copilot AI review requested due to automatic review settings September 10, 2026 08:07

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical release-secret mismatch and moderate chart, compatibility, registry, and documentation issues remain.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (4)

AGENTS.md:45

  • The documented secret name does not match the workflow: release.yml passes secrets.AZURE_ACR_RELEASE_CLIENT_ID to azure/login, while this line tells operators to configure AZURE_ACR_CLIENT_ID. Following this documentation leaves OIDC login without the client ID and the release fails; please use the workflow's actual secret name here.
- Authenticates with GitHub OIDC. Required secrets: `AZURE_ACR_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_SUBSCRIPTION_ID`; required vars: `ACR_PUBLIC_NAME`, `ACR_PUBLIC_LOGIN_SERVER`.

chart/values.yaml:220

  • Renaming the values key from engine to worker makes existing override files silently lose their engine replicas, resources, service-account settings, and other overrides on upgrade; the schema does not reject the now-unknown engine key. Preserve a compatibility alias or add an explicit migration/validation path before replacing the public values API.
worker:
  name: openops-worker
  image: openops-worker
  repository: ""  # Override global image.repository (empty = use global)

chart/values.yaml:259

  • Because openops.imageRepository prefers a non-empty component repository, this value prevents the custom image.repository mirror documented in README.md from applying to tables. A deployment using only its own private mirror will still pull openops.azurecr.io/openops-tables. Leave this override empty (the default still resolves to the public ACR) or document the required component-specific override.
  repository: "openops.azurecr.io"  # Always the public registry for tables

chart/values.yaml:322

  • The same non-empty component override also makes the documented custom image.repository ineffective for analytics, so a private mirror deployment still pulls openops.azurecr.io/openops-analytics. Leave this override empty so it follows the global repository, or explicitly document the additional override required for analytics.
  repository: "openops.azurecr.io"  # Always the public registry for analytics
  • Files reviewed: 24/24 changed files
  • Comments generated: 8
  • Review effort level: Lite

aws-access-key-id: ${{ secrets.ECR_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.ECR_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.ECR_PUBLIC_REGION }}
client-id: ${{ secrets.AZURE_ACR_RELEASE_CLIENT_ID }}
Comment thread README.md
Comment on lines +331 to +332
image:
repository: my-registry.example.com/openops
Comment thread chart/values.yaml
Comment thread AGENTS.md
Comment thread README.md
Comment thread chart/values.schema.json
}
},
"engine": {
"worker": {
Comment thread docs/DEPLOY_TO_AWS_EKS.md
Comment thread docs/DEPLOY_TO_AWS_EKS_FARGATE.md
Copilot AI review requested due to automatic review settings September 10, 2026 12:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Unresolved critical and moderate security, deployment, networking, secret-rotation, and release-workflow issues remain.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (7)

.github/workflows/release.yml:65

  • The documented release secret is AZURE_ACR_CLIENT_ID (AGENTS.md:47), but this step reads AZURE_ACR_RELEASE_CLIENT_ID. With the documented environment secret configured, Azure login receives an empty client ID and the publish job fails. Use the same secret name in the workflow and documentation.
          client-id: ${{ secrets.AZURE_ACR_RELEASE_CLIENT_ID }}

chart/templates/deployment-mcp.yaml:39

  • When MCP uses a pre-created service account (mcp.serviceAccount.create: false), this helper call resolves to the literal default service account and ignores mcp.serviceAccount.name; the helper only honors the configured name when create is true. That prevents the documented/custom IRSA or Workload Identity account from being used. Make the service-account-name helper honor the configured name in both create modes.
      serviceAccountName: {{ include "openops.serviceAccountName" (dict "root" . "component" "mcp") }}

chart/templates/secret-env.yaml:15

  • Appending mcp.env adds OAuth credentials to the generated Secret, but openops.secretChecksum only hashes explicit secretEnv.data/stringData. Changing OPS_OAUTH_RS_CLIENT_SECRET therefore updates the Secret without changing the MCP pod template, so running pods retain the old value and OAuth token exchange fails until they are manually restarted. Include the generated environment data in the rollout checksum or use a secret-reload mechanism.
{{- $envSources = append $envSources .Values.mcp.env -}}

chart/values.yaml:158

  • This changes the default runner image from a concrete image to an empty string. If an operator enables the existing OPS_SUBAGENTS_ENABLED=true Kubernetes executor without another override, the app will create subagent jobs with no image and execution will fail. Preserve a valid image in the new registry or add render-time validation/documentation that an image override is mandatory when subagents are enabled.
  OPS_SUBAGENT_RUNNER_IMAGE: ""

chart/values.yaml:317

  • When secretEnv.existingSecret is used without the chart's generated ExternalSecret, this derived variable is rendered as a secretKeyRef for OPENOPS_MCP_CLIENT_SECRET, while the configuration only asks operators to provide OPS_OAUTH_RS_CLIENT_SECRET. No alias is created in an existing Secret, so the optional reference is missing and MCP OAuth exchanges fail. Either map the pod to the original key or explicitly require/document the derived key for externally managed Secrets.
    OPENOPS_MCP_CLIENT_SECRET: '{{ .Values.openopsEnvSecrets.OPS_OAUTH_RS_CLIENT_SECRET }}'

docs/DEPLOY_TO_AWS_EKS.md:620

  • This registry edit leaves the rest of the AWS production example using the removed engine values (for example the serviceAccount.engine and engine: blocks later in the file). Those keys are now ignored by the chart, so users following this guide will not get the documented worker replica count or service-account configuration. Rename the remaining engine examples, labels, and commands to worker.
    docs/DEPLOY_TO_AWS_EKS_FARGATE.md:680
  • This registry edit leaves the Fargate guide's component example using the removed engine values later in the file. Those keys are ignored by the chart, so the documented replica, service-account, and operational settings will not apply after the rename. Rename the remaining engine examples, labels, and commands to worker.
  • Files reviewed: 32/32 changed files
  • Comments generated: 4
  • Review effort level: Lite

{{- fail "ERROR: OPS_OAUTH_RS_CLIENT_SECRET must be at least 32 characters when mcp.enabled is true. Generate with: openssl rand -hex 32" -}}
{{- end -}}
{{- $url := include "openops.publicUrl" . -}}
{{- if and (not (hasPrefix "https://" $url)) (not (hasPrefix "http://localhost" $url)) -}}
Comment on lines +32 to +34
{{- with include "openops.secretChecksum" . }}
checksum/secret-env: {{ . }}
{{- end }}
Comment on lines +318 to +324
- to:
- podSelector:
matchLabels:
app.kubernetes.io/component: app
ports:
- protocol: TCP
port: 80
Comment on lines +332 to +336
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
Copilot AI review requested due to automatic review settings September 16, 2026 11:42

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Unresolved moderate issues affect release authentication, MCP security and networking, secret rotation, and worker configuration.

Review details

Suppressed comments (12)

Previously missed (2) — in code that hasn't changed since the last review.

README.md:539

  • This replacement still documents a top-level hpa.worker map, but the chart's HPA template reads .Values.worker.autoscaling and there is no hpa value in values.yaml. Copying this example therefore leaves worker autoscaling disabled; update the example to the actual values shape.
    README.md:765
  • The chart does not read a top-level serviceAccount map: serviceaccount-worker.yaml uses .Values.worker.serviceAccount. Consequently this newly renamed worker block is ignored and the documented IAM role is never attached; nest it under worker.serviceAccount (and update the surrounding component examples consistently).

.github/workflows/release.yml:65

  • AGENTS.md documents AZURE_ACR_CLIENT_ID as the required release secret, but this workflow reads AZURE_ACR_RELEASE_CLIENT_ID. A release configured according to the documented secret name will pass an empty client ID to azure/login and fail; align the workflow and documentation names.
          client-id: ${{ secrets.AZURE_ACR_RELEASE_CLIENT_ID }}

README.md:22

  • The worker rename is not carried through the deployment guides: docs/DEPLOY_TO_AWS_EKS.md still tells users to configure engine/openops-engine, and the Fargate guide still contains engine values plus engine log and health selectors. Following those examples with this chart leaves the worker configuration ignored and diagnostics target nonexistent resources; update the guides as part of this rename.
- **openops-worker**: Workflow execution worker.
- **openops-mcp** (opt-in): MCP server that lets external agents such as Claude Code or Codex operate OpenOps over OAuth.

chart/templates/_helpers.tpl:440

  • This prefix check accepts http://localhost.evil.com and http://localhost@evil.com as the plain-HTTP exception. With MCP enabled, that permits an insecure OAuth issuer on a non-localhost host; require a hostname boundary, such as exact http://localhost or http://localhost:<port>.
{{- if and (not (hasPrefix "https://" $url)) (not (hasPrefix "http://localhost" $url)) -}}

chart/templates/configmap-nginx.yaml:83

  • When HTTPS terminates at the ingress or load balancer, this Nginx server still listens on port 80, so $scheme is http. This overwrites the incoming X-Forwarded-Proto: https and makes MCP see requests as HTTP during OAuth flows; preserve the trusted forwarded header, with an HTTP fallback for local direct access, instead.
            proxy_set_header X-Forwarded-Proto $scheme;

chart/templates/deployment-mcp.yaml:33

  • openops.secretChecksum only hashes secretEnv.data/stringData, not the auto-generated entries from openopsEnvSecrets and mcp.env. Rotating OPS_OAUTH_RS_CLIENT_SECRET therefore updates the Secret without changing this annotation, so existing app and MCP pods keep the old environment value and OAuth fails until they are manually restarted. Include generated secret data in the checksum or explicitly roll these deployments when the shared secret changes.
        {{- include "openops.prometheusAnnotations" (dict "root" . "component" "mcp") | nindent 8 }}
        {{- with include "openops.secretChecksum" . }}
        checksum/secret-env: {{ . }}

chart/templates/networkpolicy.yaml:322

  • With the default networkPolicy.enabled: true, this egress rule is not sufficient for MCP startup: the app policy still permits ingress only from pods labeled nginx, while the MCP pod calls openops-app directly for its API/OpenAPI and OAuth exchange. Add an app-policy ingress rule allowing the mcp component on port 80, otherwise the enabled MCP deployment cannot reach the API.
    - to:
        - podSelector:
            matchLabels:
              app.kubernetes.io/component: app
      ports:

chart/templates/networkpolicy.yaml:336

  • namespaceSelector: {} selects pods in Kubernetes namespaces; it does not allow egress to external IPs such as Logz.io. With NetworkPolicy enabled, the MCP pod's LOGZIO_TOKEN path cannot reach an external HTTPS endpoint despite this rule and the README claiming port 443 is allowed. Add an appropriately scoped ipBlock for the required destinations or document that external shipping is unavailable under this policy.
    - to:
        - namespaceSelector: {}
      ports:
        - protocol: TCP
          port: 443

chart/values.yaml:158

  • Setting the default runner image to an empty string regresses the opt-in Kubernetes subagent path: when OPS_SUBAGENTS_ENABLED=true, the app receives no OPS_SUBAGENT_RUNNER_IMAGE, and this chart provides no validation or documented required override. Keep a valid runner image in the new registry or fail rendering when subagents are enabled; otherwise enabling subagents produces unusable jobs.
  OPS_SUBAGENT_RUNNER_IMAGE: ""

docs/DEPLOY_TO_AWS_EKS.md:620

  • This registry update leaves the rest of the guide on the pre-rename engine API (service account, replicas/resources, PDB/HPA, and log commands). Helm now reads worker, so those copied engine: settings are ignored and the guide's expected openops-engine pod never exists; update all engine references in this guide as part of the rename.
    docs/DEPLOY_TO_AWS_EKS_FARGATE.md:680
  • After this registry change, the Fargate values below still configure engine/openops-engine and later diagnostics target component engine. Those keys no longer configure the chart, so users following the guide silently get default worker settings and invalid log/health commands; migrate the guide to worker.
  • Files reviewed: 32/32 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 22, 2026 15:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MarceloRGonc
MarceloRGonc merged commit 14fe5c1 into main Sep 23, 2026
4 checks passed
@MarceloRGonc
MarceloRGonc deleted the mg/OPS-4456 branch September 23, 2026 12:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants