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.
| Kind | API group | Forge resource | v0.3.x | v1.0.0 |
|---|---|---|---|---|
JobTemplate | forge.forgeplatform.io/v1alpha1 | /api/v2/job_templates | Yes | Yes |
Inventory | forge.forgeplatform.io/v1alpha1 | /api/v2/inventories | Yes | Yes |
Credential | forge.forgeplatform.io/v1alpha1 | /api/v2/credentials | Yes | Yes |
Schedule | forge.forgeplatform.io/v1alpha1 | /api/v2/schedules | Yes | Yes |
Organization | forge.forgeplatform.io/v1alpha1 | /api/v2/organizations | — | new |
Team | forge.forgeplatform.io/v1alpha1 | /api/v2/teams + /teams/{id}/users/ | — | new |
Project | forge.forgeplatform.io/v1alpha1 | /api/v2/projects | — | new |
Workflow | forge.forgeplatform.io/v1alpha1 | /api/v2/workflow_job_templates + /workflow_nodes/ | — | new |
ForgeInstance | forge.forgeplatform.io/v1alpha1 | control-plane only | — | new |
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
- Kubernetes Deployment — full Helm + Pages context this page assumes.
- Operator CHANGELOG — the per-day breakdown of the 1.0.0 work.
- forge-dev-cluster — 3 master + 4 worker k3s test environment used to validate the release end-to-end.