Skip to content

forail-operator

CI

A Kubernetes operator that reconciles native Forail Platform resources (Organizations, Teams, Projects, Inventories, Credentials, JobTemplates, Schedules, Workflows) declared as Kubernetes Custom Resources. Each CR is translated to a Forail REST API call so the cluster becomes the source of truth — kubectl apply -f workflow.yaml builds the corresponding WorkflowJobTemplate + DAG inside Forail; kubectl delete removes it.

At a glance

CRD Forail resource Notes
Organization /api/v2/organizations/ Top-level tenant, max-host quota
Team /api/v2/teams/ Membership reconciled via /teams/{id}/users/
Project /api/v2/projects/ Git/Hg/SVN/manual, optional credential + EE
Inventory /api/v2/inventories/ Hosts + groups + nested children
Credential /api/v2/credentials/ Sensitive fields sourced from k8s Secrets
JobTemplate /api/v2/job_templates/ Multi-credential attach
Schedule /api/v2/schedules/ RFC 5545 RRULE
Workflow /api/v2/workflow_job_templates/ Declarative DAG (nodes + edges)
ForailInstance n/a (control plane) Pointer to a Forail backend for multi-cluster

All CRDs live in API group forail.forail-platform.io/v1alpha1.

Multi-cluster

A single operator deployment can sync against any number of Forail backends. Declare each backend as a ForailInstance:

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 }

Then point any CR at it via spec.forailInstance: forail-eu. CRs that omit the field fall back to the global default supplied via --forail-url / --forail-token. The reconcile loop on ForailInstance also probes /api/v2/ping/ every 60 seconds and surfaces reachability

  • server version in status.

Install

Via Helm (recommended for dev)

TOKEN=$(kubectl -n forail exec deploy/forail-web -- \
    forail-manage create_oauth2_token --user admin | tail -1)
helm install forail-operator ./helm -n forail-operator --create-namespace \
    --set forail.url=http://forail-web.forail.svc.cluster.local:8013 \
    --set forail.token=$TOKEN

Two namespace notes:

  • Secrets referenced by Credential CRs resolve in the CR's own namespace, and the operator only reads the namespaces it was given. It holds a namespaced Secret Role (never a ClusterRole) and caches Secrets only where that Role exists. By default that is just the release namespace, so Credentials live beside the operator. To keep them elsewhere, list those namespaces at install time:

    helm install forail-operator ./helm ... --set 'secretNamespaces={team-a,team-b}'

    which creates the Secret Role/RoleBinding in each and adds it to the operator's Secret cache. Both halves come from that one key — a RoleBinding added by hand is not enough, the cache would still reject the namespace with unknown namespace for the cache.

  • forail.url must be a host Forail accepts. The chart's forail.allowedHosts covers the forail-web Service DNS names, so the URL above works as-is. Reaching Forail under any other name — an ingress host, an external load balancer — means passing --set forail.hostHeader=<that host>, otherwise Django rejects every call with 400.

Via OLM (recommended for OpenShift / OperatorHub)

# 1. Build + push bundle and catalog images.
make bundle bundle-build bundle-push \
    BUNDLE_IMG=ghcr.io/forail-platform/forail-operator-bundle:2026.06.0
make catalog-build catalog-push \
    BUNDLE_IMG=ghcr.io/forail-platform/forail-operator-bundle:2026.06.0 \
    CATALOG_IMG=ghcr.io/forail-platform/forail-operator-catalog:2026.06.0

# 2. Apply a CatalogSource pointing at the catalog image.
cat <<EOF | kubectl apply -f -
apiVersion: operators.coreos.com/v1alpha1
kind: CatalogSource
metadata: { name: forail, namespace: olm }
spec:
  sourceType: grpc
  image: ghcr.io/forail-platform/forail-operator-catalog:2026.06.0
  displayName: Forail Operators
  publisher: Forail Platform
EOF

# 3. Subscribe.
cat <<EOF | kubectl apply -f -
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 the CRDs, and runs the operator in AllNamespaces mode.

Layout

forail-operator/
├── api/v1alpha1/                          # 9 CRD type definitions
├── internal/
│   ├── controller/                        # one reconciler per CRD
│   ├── forailapi/                          # thin client over Forail REST
│   │   └── clientpool.go                  # per-ForailInstance routing
│   └── ...
├── cmd/main.go                            # manager bootstrap
├── config/
│   ├── crd/bases/                         # generated CRD YAMLs
│   ├── samples/                           # one example per CRD
│   ├── rbac/                              # generated ClusterRole
│   └── manifests/bases/                   # CSV base for OLM
├── helm/                                  # Helm chart for in-cluster install
├── bundle/                                # OLM bundle (manifests + metadata)
└── bundle.Dockerfile                      # bundle image

Development

make tidy          # go mod tidy
make generate      # regen zz_generated.deepcopy.go
make manifests     # regen config/crd/bases/*.yaml + config/rbac/role.yaml
make build         # binary at bin/manager
make vet
make test          # envtest-driven integration tests
make docker-build  # build IMAGE=ghcr.io/forail-platform/forail-operator:2026.06.0
make bundle        # populate bundle/manifests/ from CRDs + CSV base
make run           # run the operator out-of-cluster against $KUBECONFIG

make test downloads envtest assets via setup-envtest; the first run takes a minute as it fetches the matching apiserver + etcd binaries.

See also

Releases

Packages

Contributors

Languages