Operator v1.0.0
forail-operator jumps from 0.3.1 to 1.0.0 with three headline changes: five new CRDs finishing the Forail resource model, multi-cluster routing so one operator deployment can drive multiple Forail 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 | Forail resource | v0.3.x | v1.0.0 |
|---|---|---|---|---|
JobTemplate | forail.forail-platform.io/v1alpha1 | /api/v2/job_templates | Yes | Yes |
Inventory | forail.forail-platform.io/v1alpha1 | /api/v2/inventories | Yes | Yes |
Credential | forail.forail-platform.io/v1alpha1 | /api/v2/credentials | Yes | Yes |
Schedule | forail.forail-platform.io/v1alpha1 | /api/v2/schedules | Yes | Yes |
Organization | forail.forail-platform.io/v1alpha1 | /api/v2/organizations | — | new |
Team | forail.forail-platform.io/v1alpha1 | /api/v2/teams + /teams/{id}/users/ | — | new |
Project | forail.forail-platform.io/v1alpha1 | /api/v2/projects | — | new |
Workflow | forail.forail-platform.io/v1alpha1 | /api/v2/workflow_job_templates + /workflow_nodes/ | — | new |
ForailInstance | forail.forail-platform.io/v1alpha1 | control-plane only | — | new |
Multi-cluster routing
Every CR gets an optional spec.forailInstance field. When set, the controller looks up a ForailInstance 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 --forail-url and --forail-token.
Example: route one Project to a European Forail backend and another to a US one, from the same Kubernetes cluster.
---
apiVersion: v1
kind: Secret
metadata: { name: forail-eu-token, namespace: default }
stringData:
token: <PAT from forail-manage create_oauth2_token>
---
apiVersion: forail.forail-platform.io/v1alpha1
kind: ForailInstance
metadata: { name: forail-eu, namespace: default }
spec:
url: https://forail-eu.example.com
tokenSecretRef: { name: forail-eu-token, key: token }
---
apiVersion: forail.forail-platform.io/v1alpha1
kind: Project
metadata: { name: eu-roles, namespace: default }
spec:
forailInstance: forail-eu # routes to forail-eu backend
organization: Default
scmType: git
scmUrl: https://github.com/mycorp/eu-roles.git
scmBranch: main
---
apiVersion: forail.forail-platform.io/v1alpha1
kind: Project
metadata: { name: us-roles, namespace: default }
spec:
# no forailInstance — uses the default backend the operator was bootstrapped with
organization: Default
scmType: git
scmUrl: https://github.com/mycorp/us-roles.git
The ForailInstance 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 forailinstance:
$ kubectl get forailinstance -A
NAMESPACE NAME URL REACHABLE VERSION LAST CHECKED AGE
default forail-eu https://forail-eu.example.com true 2026.04.0 12s 5m
default forail-us https://forail-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: forail.forail-platform.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 Forail 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/forail-operator-bundle:v1.0.0
make catalog-build catalog-push \
BUNDLE_IMG=krlex/forail-operator-bundle:v1.0.0 \
CATALOG_IMG=krlex/forail-operator-catalog:v1.0.0
Subscribe
cat <<EOF | kubectl apply -f -
apiVersion: operators.coreos.com/v1alpha1
kind: CatalogSource
metadata: { name: forail, namespace: olm }
spec:
sourceType: grpc
image: krlex/forail-operator-catalog:v1.0.0
displayName: Forail Operators
publisher: Forail Platform
---
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata: { name: forail-operator, namespace: operators }
spec:
channel: alpha
name: forail-operator
source: forail
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 forail-operator pull
helm upgrade forail-operator ./forail-operator/helm -n forail-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 forail-operator/helm/crds/forail.forail-platform.io_organizations.yaml
kubectl apply -f forail-operator/helm/crds/forail.forail-platform.io_teams.yaml
kubectl apply -f forail-operator/helm/crds/forail.forail-platform.io_projects.yaml
kubectl apply -f forail-operator/helm/crds/forail.forail-platform.io_workflows.yaml
kubectl apply -f forail-operator/helm/crds/forail.forail-platform.io_forailinstances.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 Forail:
kubectl apply -f forail-operator/config/samples/organization-sample.yaml
sleep 5
kubectl get organization platform-team -o jsonpath='{.status.forailId}' # should print a number
kubectl describe organization platform-team | grep 'Reason\|Message' # InSync / Synced
Then verify in Forail itself (you can use the same OAuth2 PAT the operator does):
kubectl -n forail exec deploy/forail-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 Forail 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 Forail 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.
- forail-dev-cluster — 3 master + 4 worker k3s test environment used to validate the release end-to-end.