Operator v1.0.0

forge-operator jumps from 0.3.1 to 1.0.0 with three headline changes: five new CRDs finishing the Forge resource model, multi-cluster routing so one operator deployment can drive multiple Forge backends, and an OLM bundle for OperatorHub / OpenShift installation. The previous Helm install path keeps working — this page covers what is new.


CRD Matrix

The operator now manages nine kinds. The first four are the original 0.3.x set; the bold five ship in 1.0.0.

KindAPI groupForge resourcev0.3.xv1.0.0
JobTemplateforge.forgeplatform.io/v1alpha1/api/v2/job_templatesYesYes
Inventoryforge.forgeplatform.io/v1alpha1/api/v2/inventoriesYesYes
Credentialforge.forgeplatform.io/v1alpha1/api/v2/credentialsYesYes
Scheduleforge.forgeplatform.io/v1alpha1/api/v2/schedulesYesYes
Organizationforge.forgeplatform.io/v1alpha1/api/v2/organizationsnew
Teamforge.forgeplatform.io/v1alpha1/api/v2/teams + /teams/{id}/users/new
Projectforge.forgeplatform.io/v1alpha1/api/v2/projectsnew
Workflowforge.forgeplatform.io/v1alpha1/api/v2/workflow_job_templates + /workflow_nodes/new
ForgeInstanceforge.forgeplatform.io/v1alpha1control-plane onlynew

Multi-cluster routing

Every CR gets an optional spec.forgeInstance field. When set, the controller looks up a ForgeInstance CR by that name in the same namespace, dereferences spec.tokenSecretRef to read the bearer token from a Kubernetes Secret, and builds a per-instance HTTP client (cached and invalidated on observed Generation bumps). When the field is empty the CR falls back to the global default supplied via the operator flags --forge-url and --forge-token.

Example: route one Project to a European Forge backend and another to a US one, from the same Kubernetes cluster.

---
apiVersion: v1
kind: Secret
metadata: { name: forge-eu-token, namespace: default }
stringData:
  token: <PAT from forge-manage create_oauth2_token>
---
apiVersion: forge.forgeplatform.io/v1alpha1
kind: ForgeInstance
metadata: { name: forge-eu, namespace: default }
spec:
  url: https://forge-eu.example.com
  tokenSecretRef: { name: forge-eu-token, key: token }
---
apiVersion: forge.forgeplatform.io/v1alpha1
kind: Project
metadata: { name: eu-roles, namespace: default }
spec:
  forgeInstance: forge-eu              # routes to forge-eu backend
  organization: Default
  scmType: git
  scmUrl: https://github.com/mycorp/eu-roles.git
  scmBranch: main
---
apiVersion: forge.forgeplatform.io/v1alpha1
kind: Project
metadata: { name: us-roles, namespace: default }
spec:
  # no forgeInstance — uses the default backend the operator was bootstrapped with
  organization: Default
  scmType: git
  scmUrl: https://github.com/mycorp/us-roles.git

The ForgeInstance reconciler also probes /api/v2/ping/ every 60 seconds and surfaces reachability + server version in status, so you can spot a backend going dark with kubectl get forgeinstance:

$ kubectl get forgeinstance -A
NAMESPACE   NAME       URL                              REACHABLE   VERSION    LAST CHECKED   AGE
default     forge-eu   https://forge-eu.example.com     true        2026.04.0  12s            5m
default     forge-us   https://forge-us.example.com     false       —          22s            5m

Workflow DAG

Workflow is the only CRD with a non-trivial graph model. A workflow is the wrapper resource, and its spec.nodes[] describes a DAG of upstream/downstream edges keyed by per-node identifier:

apiVersion: forge.forgeplatform.io/v1alpha1
kind: Workflow
metadata:
  name: build-and-deploy
spec:
  organization: Default
  nodes:
    - identifier: build
      unifiedJobTemplate: Provision EC2     # references a JobTemplate by name
      successNodes: [deploy]                # graph edge: on success run "deploy"
      failureNodes: [notify-failure]
    - identifier: deploy
      unifiedJobTemplate: Deploy App
      alwaysNodes: [notify-end]
    - identifier: notify-failure
      unifiedJobTemplate: Send Slack Alert
    - identifier: notify-end
      unifiedJobTemplate: Send Slack Alert

Reconcile is three-phase: (1) create or update the workflow shell at /workflow_job_templates/, (2) diff spec.nodes[] against /workflow_job_templates/{id}/workflow_nodes/ and create / update / delete to converge, (3) for each desired node diff successNodes / failureNodes / alwaysNodes against the per-node /success_nodes/, /failure_nodes/, /always_nodes/ sub-relations. Edge association uses the same {"id": targetID} / {"id": targetID, "disassociate": true} M2M pattern Forge uses everywhere else.


OLM bundle

The operator now ships an OLM bundle so it can be installed on OpenShift or any cluster running the upstream Operator Lifecycle Manager. The bundle contains a ClusterServiceVersion with alm-examples for all nine kinds, the CRD manifests, RBAC, and a deployment spec — all in the standard registry+v1 layout.

Build & push

make bundle bundle-build bundle-push \
    BUNDLE_IMG=krlex/forge-operator-bundle:v1.0.0

make catalog-build catalog-push \
    BUNDLE_IMG=krlex/forge-operator-bundle:v1.0.0 \
    CATALOG_IMG=krlex/forge-operator-catalog:v1.0.0

Subscribe

cat <<EOF | kubectl apply -f -
apiVersion: operators.coreos.com/v1alpha1
kind: CatalogSource
metadata: { name: forge, namespace: olm }
spec:
  sourceType: grpc
  image: krlex/forge-operator-catalog:v1.0.0
  displayName: Forge Operators
  publisher: Forge Platform
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata: { name: forge-operator, namespace: operators }
spec:
  channel: alpha
  name: forge-operator
  source: forge
  sourceNamespace: olm
EOF

OLM creates the ClusterServiceVersion, installs all nine CRDs, and runs the operator in AllNamespaces mode. The Helm install path remains supported in parallel and stays the recommended option for non-OLM clusters.


Upgrade from 0.3.x

The release is non-breaking for existing CRs: any JobTemplate, Inventory, Credential, or Schedule that was reconciling against 0.3.x continues to reconcile against 1.0.0 unchanged. The operator deployment, however, needs the wider RBAC for the five added kinds.

If you installed via Helm:

git -C forge-operator pull
helm upgrade forge-operator ./forge-operator/helm -n forge-operator \
    --reuse-values \
    --set image.tag=1.0.0

The chart's helm/crds/ directory now contains all nine CRD manifests. Helm 3 applies CRDs once on install (it does not own them on upgrade), so on an in-place upgrade the new five must be applied manually:

kubectl apply -f forge-operator/helm/crds/forge.forgeplatform.io_organizations.yaml
kubectl apply -f forge-operator/helm/crds/forge.forgeplatform.io_teams.yaml
kubectl apply -f forge-operator/helm/crds/forge.forgeplatform.io_projects.yaml
kubectl apply -f forge-operator/helm/crds/forge.forgeplatform.io_workflows.yaml
kubectl apply -f forge-operator/helm/crds/forge.forgeplatform.io_forgeinstances.yaml

Verifying a fresh install

After install, a healthy operator pod logs Starting workers for each of the nine reconcilers. A round-trip smoke test against a live Forge:

kubectl apply -f forge-operator/config/samples/organization-sample.yaml
sleep 5
kubectl get organization platform-team -o jsonpath='{.status.forgeId}'   # should print a number
kubectl describe organization platform-team | grep 'Reason\|Message'      # InSync / Synced

Then verify in Forge itself (you can use the same OAuth2 PAT the operator does):

kubectl -n forge exec deploy/forge-web -- \
    curl -s http://localhost:8013/api/v2/organizations/ \
    -H "Authorization: Bearer $TOKEN" | jq '.results[] | {id, name}'

Deleting the CR triggers the finalizer — the operator issues a Forge DELETE, waits for a 2xx, and only then drops the finalizer so the CR can leave Kubernetes. kubectl get organization showing nothing while the matching Forge row is gone confirms the cleanup pipeline.


See also