Automate Catalyst region upgrades
Diagrid works out which release a self-managed region should install next from the region's release channel and policy. You can read that decision in two places:
- The Control Plane API tells you whether an upgrade is waiting and which version it is. Call it with an API key from anywhere.
- The
catalyst-release-availabilityConfigMap lives in your cluster, and the Catalyst agent keeps it up to date. It also names the exact chart to install, pinned by digest.
Diagrid never applies an upgrade to a self-managed region. It does not run Helm, and it does not edit your Argo CD Application or anything else in your cluster. Your tooling reads the decision, and you choose when and how to apply it.
Read the decision from the API
Every region read includes the decision in status.release:
diagrid region get my-region --output json
The release policy endpoint returns the same decision together with the region's channel and mode. Authenticate with an API key:
curl -s https://api.r1.diagrid.io/apis/cra.diagrid.io/v1beta2/regions/my-region/releasesubscription \
-H "x-diagrid-api-key: ${DIAGRID_API_KEY}"
{
"region": "my-region",
"component": "catalyst-dataplane",
"channel": "stable",
"mode": "follow",
"decisionRequired": false,
"status": {
"upgradeAvailable": true,
"reason": "NEXT_HOP_SELECTED",
"currentVersion": "1.133.0",
"nextVersion": "1.134.0",
"destinationVersion": "1.135.0",
"channelHeadVersion": "1.135.0"
}
}
| Field | Meaning |
|---|---|
upgradeAvailable | true when there is a release to move to now. |
reason | Why the region can or cannot move. See Reason codes. |
currentVersion | The release Diagrid observed the region running. Empty when the region is older than 1.115.0. |
nextVersion | The one release to install now. It can be an intermediate step on the way to destinationVersion. Empty when the region must not move. |
destinationVersion | The release the region is heading for, which can be several upgrades away. Install nextVersion instead. |
channelHeadVersion | The newest release on the region's channel. |
frozenUntil | When a freeze window lifts. Set only with FREEZE_WINDOW_ACTIVE. |
The same endpoint accepts a PUT to change the channel or pause upgrades, which is mode hold. The request sets channel and mode together, so read the policy first and send the channel back unchanged when you only want to pause upgrades. See Get a region's release policy and Set a region's release policy.
Reason codes
NEXT_HOP_SELECTED means a release is waiting. Every other code means "do nothing now":
| Code | Meaning |
|---|---|
ALREADY_AT_DESTINATION | The region runs the newest release it can move to. |
FREEZE_WINDOW_ACTIVE | A freeze window applies. frozenUntil says when it lifts. |
SUBSCRIPTION_HELD, SUBSCRIPTION_PINNED | Paused upgrades or a pin keep the region where it is. |
RELEASE_BLOCKED | Diagrid has stopped rollouts of that release. |
CHANNEL_HEAD_UNKNOWN | Diagrid has not recorded a release for the channel yet. |
CURRENT_RELEASE_UNKNOWN | Diagrid cannot tell what the region runs. Upgrade the region to 1.115.0 or later. |
NO_SUPPORTED_UPGRADE_PATH, AGENT_VERSION_TOO_OLD, NOT_ENTITLED, RELEASE_RETIRED, PINNED_RELEASE_UNRESOLVED, DOWNGRADE_NOT_ALLOWED | These do not clear on their own. Contact Diagrid support. |
Read the decision in your cluster
The API does not say where to install a release from. For that, turn on the catalyst-release-availability ConfigMap. The Catalyst agent asks Diagrid for the decision every five minutes and writes it into the cra-agent namespace, where your tooling reads it with ordinary Kubernetes access.
Turn on the ConfigMap
The ConfigMap requires a Catalyst region version 1.117.0 or later. Add these values to your catalyst-values.yaml:
agent:
config:
release_availability:
enabled: true
endpoint: https://catalyst-releases.r1.diagrid.io
# Optional. Seconds between refreshes. The default is 300.
interval_in_sec: 300
# Optional. Grants read access to the ConfigMap.
readers:
- name: catalyst-upgrade # the ServiceAccount your upgrade job runs as
namespace: ci
Set both enabled and endpoint. With enabled alone the agent logs a warning and writes no ConfigMap. The agent calls the endpoint over mutual TLS, so allow outbound HTTPS to catalyst-releases.r1.diagrid.io without TLS inspection. See Outbound network connections.
Each entry in readers gets a Role that grants get on this one ConfigMap, and a RoleBinding to that ServiceAccount. To manage RBAC yourself, leave readers unset. Grant get with resourceNames: ["catalyst-release-availability"], and do not grant list or watch. Kubernetes cannot limit those to one name, so they would expose every ConfigMap in cra-agent.
Upgrade the region as usual. The ConfigMap appears within a minute of the agent restarting.
What the ConfigMap contains
apiVersion: v1
kind: ConfigMap
metadata:
name: catalyst-release-availability
namespace: cra-agent
data:
state: current
reason: NEXT_HOP_SELECTED
component: catalyst-dataplane
channel: stable
mode: follow
nextVersion: 1.134.0
nextReleaseDigest: sha256:65d4…
nextChartReference: oci://public.ecr.aws/diagrid/catalyst@sha256:b048…
nextChartRepository: oci://public.ecr.aws/diagrid/catalyst
nextChartDigest: sha256:b048…
currentVersion: 1.133.0
currentReleaseDigest: sha256:2c80…
issuedAt: "2026-10-07T09:00:00Z"
expiresAt: "2026-10-07T09:15:00Z"
lastRefreshAt: "2026-10-07T09:01:00Z"
refreshError: ""
answer.json: |
{ … }
Every key except answer.json is a single-line string, so kubectl with jsonpath reads any of them:
kubectl get configmap catalyst-release-availability -n cra-agent \
-o jsonpath='{.data.nextVersion}'
| Key | Meaning |
|---|---|
state | Read this first. See the table below. |
reason | The reason code, as in the API. |
nextVersion | The one release to install now. Empty when the region must not move. |
nextChartReference | The chart to install, pinned by digest. nextChartRepository and nextChartDigest give the same thing in parts. |
currentVersion | The release Diagrid observed the region running. |
issuedAt, expiresAt | When Diagrid made the decision, and when it stops being valid. A decision is valid for about 15 minutes. |
lastRefreshAt, refreshError | When the agent last asked, and why that failed. refreshError is empty after a success. |
answer.json | The full decision as Diagrid sent it, including frozenUntil. |
state | Meaning | What to do |
|---|---|---|
current | The decision had not expired when the agent wrote it. | Act on it. |
expired | The decision is past expiresAt, because the agent could not reach Diagrid for a while. | Nothing. Check refreshError. |
unavailable | The agent has never received a decision. | Nothing. Check refreshError. |
When a refresh fails, the agent keeps the last decision and records the failure in refreshError. It never deletes the ConfigMap or writes half a decision. Setting enabled back to false stops the refreshes but leaves the ConfigMap in place, so delete it if nothing should read it.
Rules for your tooling
- Read it on a schedule.
nextVersionis one step. After you install it, the next refresh can offer the next one. - Check
expiresAtyourself.stateis only as fresh as the agent's last refresh. If the agent stops, nothing marks the decision as expired. - Install by digest. A version tag can be moved to another chart, but a digest cannot.
- Do nothing when there is no
nextVersion. Never fall back to the newest chart in the registry. It skips every block, freeze window and tested upgrade path.
Example: upgrade with Argo CD
This example keeps the Catalyst version in Git. A scheduled job reads the ConfigMap and opens a pull request. When you merge it, Argo CD rolls out the new release.
-
Register the Diagrid chart registry with Argo CD:
apiVersion: v1kind: Secretmetadata:name: diagrid-chartsnamespace: argocdlabels:argocd.argoproj.io/secret-type: repositorystringData:type: helmname: diagridurl: public.ecr.aws/diagridenableOCI: "true" -
Create an
Applicationthat pins the region to an exact version, and commit it to the Git repository Argo CD watches, for example asapps/catalyst.yaml:apiVersion: argoproj.io/v1alpha1kind: Applicationmetadata:name: catalystnamespace: argocdspec:project: defaultsource:repoURL: public.ecr.aws/diagridchart: catalyst# Written by the upgrade job. Always an exact version, never a range.targetRevision: 1.133.0helm:releaseName: catalystvalues: |# the contents of your catalyst-values.yamldestination:server: https://kubernetes.default.svcnamespace: cra-agentsyncPolicy:automated:prune: falseselfHeal: true -
Run an upgrade job on a schedule, such as every 15 minutes. It reads the ConfigMap and opens a pull request when a release is waiting. The job needs
geton the ConfigMap (seereadersin Turn on the ConfigMap), push access to your repository, andcrane,yqandgh:#!/usr/bin/env bashset -euo pipefailread_key() {kubectl get configmap catalyst-release-availability -n cra-agent \-o "jsonpath={.data.$1}"}STATE=$(read_key state)if [ "${STATE}" != "current" ]; thenecho "no usable decision (state: ${STATE}): $(read_key refreshError)" >&2exit 1fi# The timestamps are RFC 3339 in UTC, so a string comparison is enough.if [[ ! "$(date -u +%Y-%m-%dT%H:%M:%SZ)" < "$(read_key expiresAt)" ]]; thenecho "the published decision has expired" >&2exit 1fiVERSION=$(read_key nextVersion)if [ -z "${VERSION}" ]; thenecho "no upgrade waiting: $(read_key reason)"exit 0fi# Argo CD installs by version, so check the tag still points at the chart Diagrid named.if [ "$(crane digest "public.ecr.aws/diagrid/catalyst:${VERSION}")" != "$(read_key nextChartDigest)" ]; thenecho "the ${VERSION} tag no longer matches the published digest" >&2exit 1fiyq -i ".spec.source.targetRevision = \"${VERSION}\"" apps/catalyst.yamlgit checkout -b "catalyst-${VERSION}"git commit -am "Upgrade Catalyst to ${VERSION}"git push origin "catalyst-${VERSION}"gh pr create --fillA run with nothing to do exits without touching the repository.
-
Review and merge the pull request. Argo CD syncs the
Applicationand upgrades the region to the new version.
A region several releases behind gets one pull request at a time. After Argo CD applies one, the agent reports the new version and the job's next run opens a pull request for the next step.
If your job cannot reach the cluster, read status.nextVersion from the API instead. The API does not return the chart digest, so you lose the digest check.
Next steps
- Release channels to choose a channel or pause upgrades.
- Lifecycle management for manual
helm upgradeand rollback. - Helm chart reference for the other chart values.