- Go 97.3%
- Shell 1.5%
- Makefile 0.9%
- Dockerfile 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .devcontainer | ||
| api | ||
| cmd | ||
| config | ||
| dist | ||
| docs | ||
| hack | ||
| internal | ||
| test | ||
| .custom-gcl.yml | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| .golangci.yml | ||
| AGENTS.md | ||
| CHANGELOG.md | ||
| ci-test.Dockerfile | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| Makefile | ||
| operator.mk | ||
| PROJECT | ||
| README.md | ||
| SECURITY.md | ||
| staticcheck.conf | ||
| VERSION | ||
minio-resource-operator
The project name was
minio-operator. It is nowminio-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(ConvergeplusDelete).Convergemoves MinIO-side state toward the spec. It reports eitherConvergedorProgressing. It never reportsConvergeduntil it has verified the remote state.Deleteremoves the MinIO-side object.- A
Taxonomytable. This table classifies each failure reason asTerminal,FixedRequeue, orBackoff.
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
Clientstruct. 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'sminioRef. While MinIO is bootstrapping or unreachable, client resolution surfacesDependencyNotReady. 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.Cleanupremoves Kubernetes-side state that a CR owns, for example a managed credentials Secret. The harness runsCleanupon every delete path, including under aRetaindeletion 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
MinioBucketthat isBucketNotEmpty, or a sub-resource that is stillReferenced. 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 inkubectlas a clear signal. -
An auditable requeue knob. This operator deliberately maps
TransientErrortoBackoff(controller-runtime's workqueue). This is the opposite ofopenbao-operator'sFixedRequeuechoice for the same reason. Dispositions are data in theTaxonomytable, 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
MinioBucketthroughbucketRef. Each waits for the parent to beReadybefore 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
MinioUserre-reconciles when a referencedMinioPolicychanges. A bucket sub-resource re-reconciles when its parentMinioBucketchanges.MinioIDPOpenIDre-reconciles when its client-secretSecretchanges. This way, dependents converge promptly instead of waiting for the next resync. -
Validating webhook. A
ValidatingWebhookConfigurationenforces cross-resource invariants that the CRD OpenAPI schema cannot express. One example is the XOR rule betweenMinioBucketRetentionandMinioBucketObjectLockfor 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
SanitizingRecorderfor 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/nameand 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). AForgejoPushMirrorbacks the repo up tocodeberg.org/someara/minio-resource-operatorroughly every ten minutes.make rebuildre-seeds Forgejo from that codeberg backup. Go module fetches read codeberg through the defaultGOPROXY. - 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 testGenerate, format, vet, and run the unit and envtest suites make buildBuild the manager binary make manifests/make generateRegenerate CRDs, RBAC, webhook config, and deepcopy code make lintRun golangci-lint make build-installerRender a consolidated dist/install.yamlfromconfig/make verify-installerFail if dist/install.yamlis out of sync withconfig/make smoke-installStand 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.