Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
2908fa1
docs: add ADRs for OpenFGA operator proposal
emilic Apr 6, 2026
c6a645a
feat: add operator for migration orchestration (Stage 1)
emilic Apr 10, 2026
4108b2a
fix: address PR #309 review feedback from Copilot and CodeRabbit
emilic Apr 11, 2026
40ca6ca
fix: address additional Copilot review feedback on PR #309
emilic Apr 11, 2026
7e33bf7
fix: address remaining PR #309 review feedback
emilic Apr 11, 2026
ff2b39a
fix: remove EnvFrom from migration Job to preserve least-privilege
emilic Apr 11, 2026
722f3e7
fix: address Copilot review round 3 on PR #309
emilic Apr 11, 2026
b2c0061
fix: desired version to replace problematic ":" with "_" for label va…
emilic Apr 11, 2026
c84d751
fix: address Copilot review comments
emilic Apr 11, 2026
0f0c736
fix: validate flags to prevent negative or out-of-range values (i.e. …
emilic Apr 11, 2026
c415e56
fix: gate legacy migration initContainers on operator.enabled
emilic Apr 11, 2026
d642358
fix: use single quotes for Helm annotations with nested template expr…
emilic Apr 11, 2026
3740978
fix: address Copilot review round 6 on PR #309
emilic Apr 11, 2026
a71fbe6
fix: remove env var filtering, fix tests, updated README.md to clarif…
emilic Apr 11, 2026
767d6eb
fix: update includes OwnerReferences
emilic Apr 11, 2026
65be2d7
feat: add PodDisruptionBudget to operator subchart
emilic Apr 11, 2026
aaddab3
fix: use Job conditions instead of status counters for failure detection
emilic Apr 11, 2026
6d25647
test: add helm-unittest tests for operator mode
emilic Apr 11, 2026
3d88092
fix: inherit resource limits in operator migration Job
emilic Apr 11, 2026
f864451
test: add missing controller unit tests for edge cases
emilic Apr 11, 2026
f70c58f
fix: wire migration Job flags (backoff/deadline/TTL) through Helm values
emilic Apr 11, 2026
1be6309
fix: document namespaceOverride in operator subchart values.yaml
emilic Apr 11, 2026
96edaf9
fix: rename misleading ScaleToZero test to match actual behavior
emilic Apr 11, 2026
329f05e
fix: require explicit serviceAccount.name when create=false
emilic Apr 11, 2026
ec643f9
docs: update chart structure
emilic Apr 11, 2026
bd534ca
fix: handle AlreadyExists on migration Job creation gracefully
emilic Apr 11, 2026
467b1ea
fix: harden openfga-operator chart security and quality defaults
emilic Apr 11, 2026
dfe3180
fix: replace scale-to-zero with lookup-based zero-downtime upgrades
emilic Apr 11, 2026
4d46e90
refactor(operator): resolve container via annotation and tidy deploym…
emilic Apr 18, 2026
022a8f4
chore: remove ADRs not relevant to this PR
emilic Apr 19, 2026
0bea6c4
fix: clear retry-after annotation after Job creation
emilic Apr 19, 2026
06d8130
fix: a migration Job without a version annotation or matching label i…
emilic Apr 19, 2026
4700037
fix(chart): restore full label set on pod template metadata
emilic Apr 19, 2026
9763e5e
ci: add operator-mode coverage and v1.9.5 → v1.14.1 upgrade E2E
emilic Apr 20, 2026
160f1e9
docs: update docs to reflect operator deployment changes
emilic Apr 20, 2026
a7bffbe
fix(operator): react to JobFailureTarget for fast failure detection
emilic Apr 20, 2026
2cb102d
chore(schema): reject unknown keys in operator and migration values
emilic Apr 20, 2026
98e2b8b
ci(operator): build multi-arch on PRs, add immutable tag on main
emilic Apr 20, 2026
729b0ec
docs(chart): explain 0-replica install in NOTES when operator is enabled
emilic Apr 20, 2026
d106292
fix: add missing global block to fix helm unit tests
emilic Apr 20, 2026
ff6de22
fix(operator): use multi-arch base image digests + Go cross-compile
emilic Apr 20, 2026
4bdead4
docs(operator-chart): clarify watchNamespace default
emilic Apr 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .github/ci/operator-postgres-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# E2E values consumed by the "operator + postgres E2E" step in test.yml.
# Not under charts/openfga/ci/ on purpose — chart-testing's helm-test runs
# a gRPC probe immediately after install, which would race the operator's
# scale-up. The dedicated workflow step waits for the migration ConfigMap
# and the scale-up explicitly, then verifies readiness.
replicaCount: 1

operator:
enabled: true

migration:
enabled: true

datastore:
engine: postgres
uriSecret: openfga-e2e-postgres-credentials

openfga-operator:
image:
pullPolicy: Never

extraObjects:
- apiVersion: v1
kind: Secret
metadata:
name: openfga-e2e-postgres-credentials
stringData:
uri: "postgres://openfga:changeme@openfga-e2e-postgres:5432/openfga?sslmode=disable"
- apiVersion: apps/v1
kind: Deployment
metadata:
name: openfga-e2e-postgres
spec:
replicas: 1
selector:
matchLabels:
app: openfga-e2e-postgres
template:
metadata:
labels:
app: openfga-e2e-postgres
spec:
containers:
- name: postgres
image: postgres:17
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER
value: openfga
- name: POSTGRES_PASSWORD
value: changeme
- name: POSTGRES_DB
value: openfga
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command: ["pg_isready", "-U", "openfga", "-d", "openfga"]
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: data
emptyDir: {}
- apiVersion: v1
kind: Service
metadata:
name: openfga-e2e-postgres
spec:
selector:
app: openfga-e2e-postgres
ports:
- port: 5432
targetPort: 5432
116 changes: 116 additions & 0 deletions .github/workflows/operator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
name: Operator

on:
push:
branches:
- main
paths:
- "operator/**"
- "charts/openfga-operator/**"
- ".github/workflows/operator.yml"
pull_request:
paths:
- "operator/**"
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- "charts/openfga-operator/**"
- ".github/workflows/operator.yml"
workflow_dispatch:
inputs:
push_image:
description: "Push the operator image to GHCR"
type: boolean
default: true

env:
IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/openfga-operator

jobs:
test:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: operator/go.mod
cache-dependency-path: operator/go.sum

- name: Run tests
working-directory: operator
run: go test ./... -v

- name: Run vet
working-directory: operator
run: go vet ./...

build-and-push:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout
uses: actions/checkout@v6

- name: Extract version from Chart.yaml
id: version
run: |
version=$(grep '^appVersion:' charts/openfga-operator/Chart.yaml | awk '{print $2}' | tr -d '"')
echo "version=${version}" >> "$GITHUB_OUTPUT"
short_sha="${GITHUB_SHA::7}"
echo "short_sha=${short_sha}" >> "$GITHUB_OUTPUT"
echo "Operator version: ${version} (sha: ${short_sha})"

- name: Determine image tags and push policy
id: tags
run: |
if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then
# Main push: publish floating :<version> and :latest plus an
# immutable :<version>-<sha> so consumers pinning a specific
# commit have a stable reference.
echo "tags=${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }},${{ env.IMAGE_NAME }}:latest,${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}-${{ steps.version.outputs.short_sha }}" >> "$GITHUB_OUTPUT"
echo "push=true" >> "$GITHUB_OUTPUT"
elif [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.push_image }}" == "true" ]]; then
echo "tags=${{ env.IMAGE_NAME }}:${{ steps.version.outputs.version }}-${{ steps.version.outputs.short_sha }}" >> "$GITHUB_OUTPUT"
echo "push=true" >> "$GITHUB_OUTPUT"
else
# Pull request (or workflow_dispatch with push_image=false):
# build both platforms but don't publish — catches arm64-incompatible
# changes (build tags, syscalls, CGO) before they merge.
echo "tags=${{ env.IMAGE_NAME }}:pr-${{ steps.version.outputs.short_sha }}" >> "$GITHUB_OUTPUT"
echo "push=false" >> "$GITHUB_OUTPUT"
fi

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to GHCR
if: steps.tags.outputs.push == 'true'
uses: docker/login-action@v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and (conditionally) push
uses: docker/build-push-action@v6
with:
context: operator
push: ${{ steps.tags.outputs.push }}
platforms: linux/amd64,linux/arm64
tags: ${{ steps.tags.outputs.tags }}
cache-from: type=gha
cache-to: type=gha,mode=max
labels: |
org.opencontainers.image.source=https://github.com/${{ github.repository }}
org.opencontainers.image.version=${{ steps.version.outputs.version }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.title=openfga-operator
org.opencontainers.image.description=OpenFGA Kubernetes operator for migration orchestration
91 changes: 91 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,97 @@ jobs:
if: steps.list-changed.outputs.changed == 'true'
uses: helm/kind-action@v1.14.0

- name: Build and load operator image into kind
if: steps.list-changed.outputs.changed == 'true'
run: |
version=$(grep '^appVersion:' charts/openfga-operator/Chart.yaml | awk '{print $2}' | tr -d '"')
docker build -t "ghcr.io/openfga/openfga-operator:${version}" operator/
kind load docker-image "ghcr.io/openfga/openfga-operator:${version}" --name chart-testing

- name: Run chart-testing (install)
if: steps.list-changed.outputs.changed == 'true'
run: ct install --target-branch ${{ github.event.repository.default_branch }}

- name: E2E test — operator-managed migration across schema boundary
id: e2e-operator
if: steps.list-changed.outputs.changed == 'true'
env:
NS: openfga-e2e
REL: openfga
# v1.9.5 → v1.14.1 crosses the v1.10.0 "!!REQUIRES MIGRATION!!"
# boundary (collation spec change in openfga/openfga#2661).
OLD_VER: v1.9.5
NEW_VER: v1.14.1
run: |
set -euo pipefail
kubectl create namespace "$NS"
helm dependency build charts/openfga

echo "=== Phase 1: fresh install at ${OLD_VER} ==="
helm install "$REL" charts/openfga \
--namespace "$NS" \
--values .github/ci/operator-postgres-values.yaml \
--set image.tag="${OLD_VER}" \
--wait --timeout=3m

# Operator pod must reach Ready (validates /readyz, RBAC, env vars).
kubectl wait deployment -n "$NS" \
-l app.kubernetes.io/name=openfga-operator \
--for=condition=Available=True --timeout=2m

# Operator must run the migration Job and write ConfigMap at OLD_VER.
# Poll because kubectl wait --for=create requires kubectl >=1.31.
for i in $(seq 1 60); do
ver=$(kubectl get configmap "${REL}-migration-status" -n "$NS" \
-o jsonpath='{.data.version}' 2>/dev/null || true)
if [ "$ver" = "${OLD_VER}" ]; then
echo "Phase 1: migration ConfigMap version=${ver}"
break
fi
sleep 3
done
test "$ver" = "${OLD_VER}"

# Operator must scale the openfga Deployment from 0 to 1 ready replica.
# condition=Available alone returns true at 0/0 before scale-up;
# readyReplicas=1 is the load-bearing signal.
kubectl wait deployment/"$REL" -n "$NS" \
--for=jsonpath='{.status.readyReplicas}'=1 --timeout=3m

echo "=== Phase 2: helm upgrade ${OLD_VER} → ${NEW_VER} ==="
helm upgrade "$REL" charts/openfga \
--namespace "$NS" \
--values .github/ci/operator-postgres-values.yaml \
--set image.tag="${NEW_VER}" \
--wait --timeout=3m

# Operator must detect the version change, delete the stale Job,
# run a new migration, and update the ConfigMap to NEW_VER.
for i in $(seq 1 60); do
ver=$(kubectl get configmap "${REL}-migration-status" -n "$NS" \
-o jsonpath='{.data.version}' 2>/dev/null || true)
if [ "$ver" = "${NEW_VER}" ]; then
echo "Phase 2: migration ConfigMap version=${ver}"
break
fi
sleep 3
done
test "$ver" = "${NEW_VER}"

# New pods must roll out at NEW_VER and become Ready.
kubectl wait deployment/"$REL" -n "$NS" \
--for=jsonpath='{.status.readyReplicas}'=1 --timeout=3m
image=$(kubectl get deployment/"$REL" -n "$NS" \
-o jsonpath='{.spec.template.spec.containers[0].image}')
echo "Phase 2 running image: $image"
echo "$image" | grep -q ":${NEW_VER}"

- name: Dump operator E2E diagnostics on failure
if: failure() && steps.e2e-operator.conclusion == 'failure'
env:
NS: openfga-e2e
run: |
kubectl get all,configmap,job -n "$NS" -o wide || true
kubectl describe deployment -n "$NS" || true
kubectl logs -n "$NS" -l app.kubernetes.io/name=openfga-operator --tail=200 || true
kubectl logs -n "$NS" -l job-name --tail=200 || true
18 changes: 18 additions & 0 deletions charts/openfga-operator/.helmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Patterns to ignore when building packages.
.DS_Store
.git
.gitignore
.bzr
.bzrignore
.hg
.hgignore
.svn
*.swp
*.bak
*.tmp
*.orig
*~
.project
.idea
*.tmproj
.vscode
19 changes: 19 additions & 0 deletions charts/openfga-operator/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: v2
name: openfga-operator
description: Helm chart for the OpenFGA Kubernetes operator.

type: application
version: 0.1.0
appVersion: "0.1.0"

home: "https://openfga.github.io/helm-charts"
icon: https://github.com/openfga/community/raw/main/brand-assets/icon/color/openfga-icon-color.svg

maintainers:
- name: OpenFGA Authors
url: https://github.com/openfga
sources:
- https://github.com/openfga/helm-charts

annotations:
artifacthub.io/license: Apache-2.0
4 changes: 4 additions & 0 deletions charts/openfga-operator/ci/default-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Standalone install exercise for chart-testing.
# kind has the operator image preloaded, so skip the registry pull.
image:
pullPolicy: Never
4 changes: 4 additions & 0 deletions charts/openfga-operator/crds/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# CRDs

This directory is reserved for Custom Resource Definitions added in later stages.
No CRDs are installed in Stage 1 (migration orchestration).
16 changes: 16 additions & 0 deletions charts/openfga-operator/templates/NOTES.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
The openfga-operator has been deployed.

NOTE: Ensure the operator image ({{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}) is available in your registry.
If unavailable, the operator pod may remain in ImagePullBackOff until the image is pushed.

To check operator status:
kubectl get deployment --namespace {{ include "openfga-operator.namespace" . }} {{ include "openfga-operator.fullname" . }}

To view operator logs:
kubectl logs --namespace {{ include "openfga-operator.namespace" . }} -l "app.kubernetes.io/name={{ include "openfga-operator.name" . }}"

To check migration status:
kubectl get configmap -n {{ include "openfga-operator.namespace" . }} -l app.kubernetes.io/managed-by=openfga-operator

To inspect migration jobs:
kubectl get jobs -n {{ include "openfga-operator.namespace" . }} -l app.kubernetes.io/part-of=openfga,app.kubernetes.io/component=migration
Loading
Loading