No description
  • Go 97.3%
  • Shell 1.5%
  • Makefile 0.9%
  • Dockerfile 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-09 18:16:04 +02:00
.devcontainer Initial commit: minio-operator v0.0.1 2026-04-18 09:02:59 +02:00
api docs: rewrite documentation to ASD-STE100 2026-08-07 19:14:52 +02:00
cmd docs: rewrite documentation to ASD-STE100 2026-08-07 19:14:52 +02:00
config docs: rewrite documentation to ASD-STE100 2026-08-07 19:14:52 +02:00
dist fix(webhook): wire conversion CRD trust bundles 2026-07-24 04:07:20 +02:00
docs docs: rewrite documentation to ASD-STE100 2026-08-07 19:14:52 +02:00
hack fix(build): use canonical envtest builder image 2026-09-02 06:01:04 +02:00
internal docs: rewrite documentation to ASD-STE100 2026-08-07 19:14:52 +02:00
test docs: rewrite documentation to ASD-STE100 2026-08-07 19:14:52 +02:00
.custom-gcl.yml Initial commit: minio-operator v0.0.1 2026-04-18 09:02:59 +02:00
.dockerignore Initial commit: minio-operator v0.0.1 2026-04-18 09:02:59 +02:00
.gitattributes feat(cicd): pipeline onboarding contract + libseanfarm-operator v0.4.1 -> v0.4.10 2026-07-15 20:46:30 +02:00
.gitignore fix+chore: remove dead drift.go (latent nil-panic), dead F156, 76 .bak files 2026-06-11 09:43:20 +02:00
.golangci.yml Initial commit: minio-operator v0.0.1 2026-04-18 09:02:59 +02:00
AGENTS.md docs: rewrite documentation to ASD-STE100 2026-08-07 19:14:52 +02:00
CHANGELOG.md docs: rewrite documentation to ASD-STE100 2026-08-07 19:14:52 +02:00
ci-test.Dockerfile fix(build): use canonical envtest builder image 2026-09-02 06:01:04 +02:00
Dockerfile perf(minio): namespace BuildKit Go caches 2026-09-06 00:18:30 +02:00
go.mod fix(operator): harden reconciliation and storage contracts 2026-07-24 02:51:54 +02:00
go.sum fix(operator): harden reconciliation and storage contracts 2026-07-24 02:51:54 +02:00
Makefile test(build): pin fleet builder contract 2026-07-24 13:44:40 +02:00
operator.mk chore(scaffolding): adopt operator.mk (lib v0.4.12) 2026-07-17 12:33:37 +02:00
PROJECT Rename project to minio-resource-operator 2026-04-20 10:39:01 +02:00
README.md docs: explain native delivery and task telemetry 2026-09-09 18:16:04 +02:00
SECURITY.md docs: rewrite documentation to ASD-STE100 2026-08-07 19:14:52 +02:00
staticcheck.conf Add staticcheck.conf suppressing ST1001 for idiomatic Ginkgo/Gomega dot imports 2026-04-18 12:18:45 +02:00
VERSION chore: devbump 0.3.11 -> 0.3.12-dev 2026-07-24 12:31:29 +00:00

minio-resource-operator

The project name was minio-operator. It is now minio-resource-operator. The new name separates it from the upstream MinIO operator. The upstream operator manages MinIO tenants and clusters. This project manages MinIO resources instead. These resources are IAM objects and buckets. The operator manages them inside an already-running MinIO deployment. The CRD API group (minio.sean.farm) did not change.

This is a Kubernetes operator. It manages MinIO resources through custom resources (CRs). Each CR names a desired piece of MinIO state. Examples are an IAM user, group, policy, or service account. Other examples are an OpenID identity provider, a bucket, or a bucket sub-resource. Bucket sub-resources include versioning, object-lock, retention, quota, encryption, tags, CORS, policy, and lifecycle. The operator reconciles the external MinIO system to match the desired state. It reconciles through MinIO's admin API (madmin) and S3 API (minio-go). This operator is part of the seanfarm operator fleet. The fleet also includes forgejo-operator, kratos-identity-operator, and openbao-operator. All four operators build on the shared libseanfarm-operator reconcile harness.

Built with kubebuilder.

Custom resources

All kinds live in the API group minio.sean.farm. The API serves two versions. v1beta1 is the stored hub version. v1alpha1 is a served spoke version. Generated conversions round-trip v1alpha1 through the hub.

IAM

Kind Purpose
MinioUser IAM user. Writes generated credentials to a managed Secret.
MinioGroup IAM group
MinioGroupMembership Binds a user to a group
MinioPolicy IAM policy document
MinioServiceAccount Service-account credentials derived from a user
MinioIDPOpenID Configuration for an OpenID Connect identity provider

Buckets

Kind Purpose
MinioBucket A bucket. Parent of every sub-resource below.
MinioBucketVersioning Versioning configuration for a bucket
MinioBucketObjectLock Default object-lock retention. Requires versioning.
MinioBucketRetention Bucket-level retention. Cannot be combined with object-lock.
MinioBucketQuota Bucket quota
MinioBucketEncryption Server-side encryption configuration for a bucket
MinioBucketTags Bucket tags
MinioBucketCORS CORS configuration for a bucket
MinioBucketPolicy Bucket access policy, as raw JSON
MinioILMPolicy Bucket lifecycle rules, also called ILM rules

Each bucket sub-resource targets its parent MinioBucket through spec.bucketRef.name in the same namespace. It does not address the bucket directly.

Architecture

Shared reconcile harness

The top-level controllers run on libseanfarm-operator's generic reconcile harness. This harness extracts the lifecycle logic that was once duplicated across four seanfarm operators, each with its own hand-written reconcile loop and its own drift. The harness now owns the entire reconcile lifecycle. It handles finalizer mechanics, external-client resolution, and mapping a handler's convergence result onto the Ready condition. It also handles the reason-to-requeue taxonomy, deletion-policy semantics, conflict-safe status writes, secret-watch mapping, sanitized Events, and uniform concurrency with exponential backoff.

An operator supplies only a thin policy shim:

  • Handler (Converge plus Delete). Converge moves MinIO-side state toward the spec. It reports either Converged or Progressing. It never reports Converged until it has verified the remote state. Delete removes the MinIO-side object.
  • A Taxonomy table. This table classifies each failure reason as Terminal, FixedRequeue, or Backoff.

Ready is structural. Only the harness sets Ready=true. The handler never sets it. The harness sets it only after Converge reports that it actually verified the external state. A handler that is unsure returns Progressing. This keeps Ready=false and triggers a requeue. A handler cannot accidentally assert readiness.

MinIO specifics

  • External client. Reconcilers act through a Client struct. This struct wraps the admin API (madmin) and the S3 API (minio-go). Both connections target the same endpoint with the same root credentials. Both are multiplexed over MinIO's HTTP API. A factory resolves the client from a CR's minioRef. While MinIO is bootstrapping or unreachable, client resolution surfaces DependencyNotReady. It then requeues on a short transient cadence. It does not latch a terminal error. This way, a freshly-created CR does not false-fail Flux health checks during tenant bring-up.

  • Cleanup-forwarding adapter. The top-level controllers reach the harness through an adapter. This adapter forwards an optional CleanupHandler, because a plain adapter would hide it. Cleanup removes Kubernetes-side state that a CR owns, for example a managed credentials Secret. The harness runs Cleanup on every delete path, including under a Retain deletion policy. The deletion policy governs only MinIO-side state. The invariant "CR gone means managed Secret gone" holds regardless.

  • The delete taxonomy holds, it does not fail. On the delete path, some safety refusals hold the finalizer instead of failing. Examples are a MinioBucket that is BucketNotEmpty, or a sub-resource that is still Referenced. These refusals re-check on the resync cadence without returning an error. This way, a state that only a human can resolve is not exponentially backed off. The object also stays visible in kubectl as a clear signal.

  • An auditable requeue knob. This operator deliberately maps TransientError to Backoff (controller-runtime's workqueue). This is the opposite of openbao-operator's FixedRequeue choice for the same reason. Dispositions are data in the Taxonomy table, not logic buried in a code ladder. Because of this, the divergence between operators is an explicit, reviewable decision.

  • Bucket sub-resources are a second lifecycle. The nine per-bucket sub-resource controllers resolve a parent MinioBucket through bucketRef. Each waits for the parent to be Ready before it acts. These controllers are deliberately kept off the generic harness. They re-stamp their own lifecycle, but they still use the shared status, event, and finalizer helpers.

  • Secret and reference watches. Controllers watch the resources they depend on. A MinioUser re-reconciles when a referenced MinioPolicy changes. A bucket sub-resource re-reconciles when its parent MinioBucket changes. MinioIDPOpenID re-reconciles when its client-secret Secret changes. This way, dependents converge promptly instead of waiting for the next resync.

  • Validating webhook. A ValidatingWebhookConfiguration enforces cross-resource invariants that the CRD OpenAPI schema cannot express. One example is the XOR rule between MinioBucketRetention and MinioBucketObjectLock for a given bucket.

  • Metrics. The manager exports per-resource Prometheus series. These cover reconcile duration, reconcile errors, and resource status.

Security

The operator was checked against a real security audit. Condition reasons and messages, and Kubernetes Events, land in world-readable status surfaces. Because of this:

  • Every condition and Event message is bounded. The library caps messages at 512 bytes and cuts on a UTF-8 boundary. A raw MinIO error body cannot land unbounded in a world-readable Event. Events route through the library's truncating emitters. The library also offers a SanitizingRecorder for the same guarantee at the recorder boundary.
  • Credential paths emit generic messages. No plaintext credential material ever reaches status, Events, or logs. This includes access keys, secret keys, and service-account credentials. The rule is: name the Secret, never quote it. Managed-Secret errors identify the Secret by namespace/name and describe the conflict, not its contents.

Build and distribution

  • Images are built in-cluster. Native Binjovi tasks run the tests and build AMD64 and ARM64 candidates with BuildKit. Release publishes the exact candidate digests and verifies their cosign signatures. Local image builds are not the cluster delivery path.
  • Forgejo is canonical, codeberg is the backup. Day-to-day pushes go to Forgejo (code.sean.farm). A ForgejoPushMirror backs the repo up to codeberg.org/someara/minio-resource-operator roughly every ten minutes. make rebuild re-seeds Forgejo from that codeberg backup. Go module fetches read codeberg through the default GOPROXY.
  • CI uses native Binjovi agents. This project does not use Argo Workflows, Tekton, Shipwright, or repository-hosted Actions for delivery.

Release tags are immutable. The project follows pre-1.0 semantic versioning.

Development

  • Red/green TDD. Tests live alongside each reconciler (internal/controller/*_controller_test.go) and run on controller-runtime envtest. The MinIO client has a fake backend (internal/minioclient/fake.go), so tests exercise reconcile behavior without a live server.

  • Standard targets (see the Makefile):

    Target Purpose
    make test Generate, format, vet, and run the unit and envtest suites
    make build Build the manager binary
    make manifests / make generate Regenerate CRDs, RBAC, webhook config, and deepcopy code
    make lint Run golangci-lint
    make build-installer Render a consolidated dist/install.yaml from config/
    make verify-installer Fail if dist/install.yaml is out of sync with config/
    make smoke-install Stand up a kind cluster, install cert-manager and the operator, and check webhook readiness
  • How a change ships. Edit, then run a red/green test, then push to Forgejo. Native Binjovi Build and Release tasks produce and sign the image. The seanfarm GitOps repo and platform compositions pin the deployed tag and digest. Flux then reconciles it onto the cluster.

Binjovi Build and Release proof

Binjovi tests each pull request in an isolated envtest task. It then builds one AMD64 and ARM64 OCI candidate from the exact revision. Binjovi is now the guarded Build and Release authority. Pipelines no longer declares this project. This revision also proves immediate signed webhook delivery and schedulable image execution under shared cluster load. It also proves bounded recovery when Zot rejects an OCI upload session.

License

Apache License 2.0 (per the source file headers).

Native CI

Binjovi builds pull requests with the recipe in sean/binjovi-plans:recipes/minio-resource-operator/. The active plan fixes the recipe revision for each build. Recipe changes use a plans pull request and release; they do not require a Binjovi runtime deployment.

The native Binjovi result is the only admission result for this project.

Task timing and release verification

Open the project dashboard and expand an execution attempt for the native task timeline and available CPU, off-CPU, and syscall profiles. Release is this project's pipeline endpoint; there is no configured Deploy target. Consumer image pins are separate changes.

Use binjovictl release minio-resource-operator --build BUILD_ID --wait for an exact successful Build. Confirm the published image digest and signature in Release evidence. A task profile explains execution; it does not replace artifact verification.