From a2aac7baeb7df701441be9cf0cc3e93179127b40 Mon Sep 17 00:00:00 2001 From: "Yolean k8s-qa (buckety maintainer)" Date: Wed, 22 Jul 2026 08:41:47 +0000 Subject: [PATCH 1/4] Standalone whole-document CR schemas, suffixed by family and driver Editor validation for Buckety maintainers, the kubernetes-json-schema pattern: schema/ carries generated draft-07 schemas for the full CR document, referenced via yaml-language-server annotations. The suffix is a specialize/generalize ladder walked by switching the URL: buckety.schema.json any driver, parameters open buckety-objectstore.schema.json family-common only (portable CR) buckety-gcs / -s3 / -kadm full per-driver parameters bucketyaccess.schema.json The family rung is the interesting one: a yaml annotated with it provably stays provisionable on any bucket backend, because the schema rejects driver-specific parameters at edit time. Its parameter definitions ship as pkg/drivers/objectstore/schema/v0.1/parameters.schema.json, consistent with the per-driver layout. scripts/gen-cr-schemas composes the outputs from the CRD yamls (shape, descriptions, CEL stripped) and the parameters schemas - nothing is hand-written twice, and CI asserts schema/ is committed. Verified against all 66 example CR documents (the only rejections are webhook-fallback-validation's deliberately invalid fixtures) plus negative cases for unknown, gcs-only and malformed values. Editor schemas are advisory; admission remains the authority and validates against the backend the CR names. Co-Authored-By: Claude Fable 5 --- .github/workflows/e2e.yaml | 6 + SPEC.md | 25 +- .../schema/v0.1/parameters.schema.json | 18 ++ schema/README.md | 38 +++ schema/buckety-gcs.schema.json | 228 ++++++++++++++++++ schema/buckety-kadm.schema.json | 211 ++++++++++++++++ schema/buckety-objectstore.schema.json | 207 ++++++++++++++++ schema/buckety-s3.schema.json | 214 ++++++++++++++++ schema/buckety.schema.json | 194 +++++++++++++++ schema/bucketyaccess.schema.json | 149 ++++++++++++ scripts/gen-cr-schemas/main.go | 168 +++++++++++++ 11 files changed, 1453 insertions(+), 5 deletions(-) create mode 100644 pkg/drivers/objectstore/schema/v0.1/parameters.schema.json create mode 100644 schema/README.md create mode 100644 schema/buckety-gcs.schema.json create mode 100644 schema/buckety-kadm.schema.json create mode 100644 schema/buckety-objectstore.schema.json create mode 100644 schema/buckety-s3.schema.json create mode 100644 schema/buckety.schema.json create mode 100644 schema/bucketyaccess.schema.json create mode 100644 scripts/gen-cr-schemas/main.go diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index e7fd918..bae5190 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -37,6 +37,12 @@ jobs: go vet ./... go test ./... + - name: Assert generated CR schemas are committed + run: | + set -euxo pipefail + go run ./scripts/gen-cr-schemas + git diff --exit-code -- schema/ + - name: Install k3d, kubectl, crane, yq run: | set -euxo pipefail diff --git a/SPEC.md b/SPEC.md index 0e45997..f2bdedf 100644 --- a/SPEC.md +++ b/SPEC.md @@ -561,11 +561,26 @@ Each schema lives in the operator repo under `pkg/drivers//schema//*.schema.json` and is published via the GitHub raw URL (this is the publication mechanism — there's no separate registry). Versioning is the -driver SemVer (see *Driver versioning*). Example YAMLs (in this -repo and in consumer ystack) carry -`# yaml-language-server: $schema=...` annotations pointing at -the raw URL so editors validate before kubectl ever sees the -file. +driver SemVer (see *Driver versioning*). The object-store family +publishes its family-common parameters the same way, under +`pkg/drivers/objectstore/schema/`. + +On top of these, `schema/` at the repo root carries GENERATED +standalone whole-document schemas for editors (the +kubernetes-json-schema pattern): `buckety.schema.json` plus one +per family and per driver (`buckety-objectstore`, `buckety-gcs`, +`buckety-s3`, `buckety-kadm`) and `bucketyaccess.schema.json`. +The suffix forms a specialize/generalize ladder walked by +switching the URL: the family rung accepts only family-common +parameters, so a resource annotated with it provably stays +portable across bucket backends. `go run ./scripts/gen-cr-schemas` +composes them from the CRD yamls and the parameters schemas; CI +asserts the output is committed. Example YAMLs (in this repo and +in consumer ystack) carry `# yaml-language-server: $schema=...` +annotations pointing at the raw URL so editors validate before +kubectl ever sees the file. Editor schemas are advisory: the +admission webhook stays the authority, validating against the +backend the CR names rather than the schema the file claims. ## Mutability diff --git a/pkg/drivers/objectstore/schema/v0.1/parameters.schema.json b/pkg/drivers/objectstore/schema/v0.1/parameters.schema.json new file mode 100644 index 0000000..1cb659d --- /dev/null +++ b/pkg/drivers/objectstore/schema/v0.1/parameters.schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "object-store family Buckety parameters", + "description": "Schema for Buckety.spec.parameters restricted to the object-store family's common parameters (SPEC.md section \"Driver families\"). A resource that validates here is portable across every bucket backend (gcs, s3): driver-specific knobs (gcs location, uniformBucketLevelAccess, softDeleteRetentionSeconds; r2 jurisdiction) belong in the backend config entry's parameters defaults, not in the CR.", + "type": "object", + "additionalProperties": false, + "properties": { + "versioning": { + "type": "string", + "enum": ["true", "false"], + "description": "Object versioning. Mutable; reconciled in place by every driver in the family. A backend that does not implement versioning is skipped fail-safe: the bucket still provisions." + }, + "lifecycle": { + "type": "string", + "description": "Object lifecycle rules as a JSON document in the same shape `gsutil lifecycle set` takes, restricted to the family's PORTABLE SUBSET: action Delete or AbortIncompleteMultipartUpload, condition age (required) plus at most one matchesPrefix. Example: {\"rule\": [{\"action\": {\"type\": \"Delete\"}, \"condition\": {\"age\": 7, \"matchesPrefix\": [\"board-prints/\"]}}]}. {\"rule\": []} clears all rules; omitting the parameter leaves rules unmanaged. A backend that cannot express lifecycle configuration is skipped fail-safe." + } + } +} diff --git a/schema/README.md b/schema/README.md new file mode 100644 index 0000000..d04141a --- /dev/null +++ b/schema/README.md @@ -0,0 +1,38 @@ +# Standalone CR schemas + +Whole-document JSON schemas for editor validation of Buckety and +BucketyAccess yamls, annotated the same way as +kubernetes-json-schema: + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-objectstore.schema.json +``` + +The suffix picks a rung on a specialize/generalize ladder; a +maintainer moves a resource between rungs by switching the URL, +nothing else: + +| Schema | spec.parameters accepts | +| --- | --- | +| `buckety.schema.json` | anything (string values) - any driver | +| `buckety-objectstore.schema.json` | object-store family-common only (`versioning`, `lifecycle` portable subset) - the resource stays provisionable on any bucket backend, gcs or s3 | +| `buckety-gcs.schema.json` | the full gcs driver set | +| `buckety-s3.schema.json` | the full s3 driver set | +| `buckety-kadm.schema.json` | the kadm driver set (`partitions`, `replicationFactor`, `config.*`) - kadm is not in a family | +| `bucketyaccess.schema.json` | (BucketyAccess; parameters unconstrained) | + +Editor validation is documentation and early feedback, not +enforcement: the admission webhook remains the authority, and it +validates against the backend the CR actually names, not the +schema the file claims. The two agree by construction - both +compose from the same per-driver definitions. + +Pin a release by ref, e.g. +`.../buckety-controller/v0.1.1/schema/...` (schemas ship from +v0.1.1; the v0.1.0 tag predates this directory), or track `main`. + +Generated - do not edit by hand. Source of truth is the CRD +yamls (CR shape) plus +`pkg/drivers//schema/v0.1/parameters.schema.json` +(parameters). Regenerate with `go run ./scripts/gen-cr-schemas`; +CI fails if the output is not committed. diff --git a/schema/buckety-gcs.schema.json b/schema/buckety-gcs.schema.json new file mode 100644 index 0000000..a554ddd --- /dev/null +++ b/schema/buckety-gcs.schema.json @@ -0,0 +1,228 @@ +{ + "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Standalone editor schema for a Buckety whose backend resolves to the gcs driver. GENERALIZE to buckety-objectstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints.", + "properties": { + "apiVersion": { + "enum": [ + "buckety.yolean.se/v1alpha1" + ], + "type": "string" + }, + "kind": { + "enum": [ + "Buckety" + ], + "type": "string" + }, + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "spec": { + "properties": { + "adoption": { + "default": "AdoptEmpty", + "description": "What happens when the resolved backend resource\nalready exists at first reconcile. AdoptEmpty (the\ndefault) adopts only content-free resources; Adopt\nclaims a resource with pre-existing content.\nAdopted resources are never deleted from the\nbackend, whatever retentionPolicy says. See SPEC.md\nsection \"Adoption\".\n", + "enum": [ + "AdoptEmpty", + "Adopt" + ], + "type": "string" + }, + "backend": { + "description": "Name of a backend defined in the controller's config\nfile. Immutable after creation.\n", + "minLength": 1, + "type": "string" + }, + "defaultAccess": { + "description": "Single-consumer shortcut. Materialises an implicit\nBucketyAccess of the same name; reclaimed via\nowner-ref when an explicit BucketyAccess arrives or\nthis field is removed. See SPEC.md.\n", + "properties": { + "credentialsSecretName": { + "minLength": 1, + "type": "string" + }, + "role": { + "default": "ReadWrite", + "enum": [ + "Reader", + "Writer", + "ReadWrite" + ], + "type": "string" + } + }, + "required": [ + "credentialsSecretName" + ], + "type": "object" + }, + "name": { + "description": "Optional name template. Resolved once at first\nreconcile and frozen in status.backendResourceName.\nDefaults to metadata.name if omitted. See SPEC.md\nsection \"Naming templates\" for the grammar.\n", + "type": "string" + }, + "parameters": { + "additionalProperties": false, + "description": "Schema for Buckety.spec.parameters when the resolved driver is gcs. All values are strings per the CRD contract. Omitted parameters are unmanaged: the driver never touches that knob on the backend and GCS's own defaults apply at creation (location US, uniform bucket-level access off, versioning off, no lifecycle rules).", + "properties": { + "labels": { + "description": "Bucket labels as a JSON object of string values, e.g. {\"site\": \"tenant1\", \"managed-by\": \"buckety\"}, for cost attribution and ownership. Listed labels are converged to their declared values; labels absent from the parameter are unmanaged and never deleted (same posture as unlisted parameters). Key/value charset rules are enforced by the backend.", + "type": "string" + }, + "lifecycle": { + "description": "Object lifecycle rules as a JSON document in the same shape `gsutil lifecycle set` takes: {\"rule\": [{\"action\": {\"type\": \"Delete\"}, \"condition\": {\"age\": 30}}]}. Multiple concurrent rules are supported (e.g. distinct matchesPrefix retention classes). Action types: Delete, SetStorageClass (with storageClass), AbortIncompleteMultipartUpload. Condition fields: age, createdBefore, customTimeBefore, daysSinceCustomTime, daysSinceNoncurrentTime, isLive, matchesPrefix, matchesStorageClass, matchesSuffix, noncurrentTimeBefore, numNewerVersions. Mutable; reconciled in place. {\"rule\": []} clears all rules; omitting the parameter leaves rules unmanaged.", + "type": "string" + }, + "location": { + "description": "Bucket location (e.g. EUROPE-WEST4, EU, US). Set at bucket creation, immutable post-create: buckets cannot move in place, so a location that disagrees with the backend surfaces ParameterDrift.", + "minLength": 1, + "type": "string" + }, + "softDeleteRetentionSeconds": { + "description": "Soft delete window (softDeletePolicy.retentionDurationSeconds): how long deleted or overwritten objects remain restorable. \"0\" disables; GCS accepts enabled windows of 604800 to 7776000 seconds (7 to 90 days) only. Note GCS defaults NEW buckets to 7 days on - declare \"0\" to opt out, or a value to pin it against console drift. Mutable; reconciled in place. Cheap insurance against mass-delete with a compromised data-plane key, at the cost of also retaining intentionally lifecycle-deleted objects for the window.", + "type": "string" + }, + "uniformBucketLevelAccess": { + "description": "Uniform bucket-level access (IAM-only, no object ACLs). Recommended \"true\" for private gateway-fronted buckets. Mutable; note GCS only allows disabling within 90 days of enabling - a later disable surfaces as a reconcile error.", + "enum": [ + "true", + "false" + ], + "type": "string" + }, + "versioning": { + "description": "Object versioning. Mutable; reconciled in place.", + "enum": [ + "true", + "false" + ], + "type": "string" + } + }, + "title": "gcs driver Buckety parameters", + "type": "object" + }, + "retentionPolicy": { + "default": "Retain", + "description": "What happens to the backend resource when this\nBuckety is deleted.\n", + "enum": [ + "Retain", + "Delete" + ], + "type": "string" + } + }, + "required": [ + "backend" + ], + "type": "object" + }, + "status": { + "properties": { + "backend": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "backendResourceName": { + "description": "Resolved name template. Sticky from first reconcile.\n", + "type": "string" + }, + "conditions": { + "items": { + "properties": { + "lastTransitionTime": { + "format": "date-time", + "type": "string" + }, + "message": { + "type": "string" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "reason": { + "type": "string" + }, + "status": { + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "status", + "lastTransitionTime", + "reason", + "message" + ], + "type": "object" + }, + "type": "array" + }, + "driver": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "driverBuildVersion": { + "description": "Full SemVer of the binary that most recently\nreconciled this resource. Informational.\n", + "type": "string" + }, + "driverMajor": { + "description": "Sticky. Gates driver-version compatibility. See\nSPEC.md \"Driver versioning\".\n", + "type": "integer" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "provenance": { + "description": "Created when the controller made the backend\nresource, Adopted when it claimed a pre-existing\none. Sticky. Absent on resources stamped before\nadoption tracking existed. DeleteBuckety only runs\nfor Created; see SPEC.md section \"Adoption\".\n", + "enum": [ + "Created", + "Adopted" + ], + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "title": "Buckety (gcs)", + "type": "object" +} diff --git a/schema/buckety-kadm.schema.json b/schema/buckety-kadm.schema.json new file mode 100644 index 0000000..3651065 --- /dev/null +++ b/schema/buckety-kadm.schema.json @@ -0,0 +1,211 @@ +{ + "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Standalone editor schema for a Buckety whose backend resolves to the kadm driver. kadm is deliberately not in a driver family (families are per service kind); GENERALIZE to buckety for no parameter constraints.", + "properties": { + "apiVersion": { + "enum": [ + "buckety.yolean.se/v1alpha1" + ], + "type": "string" + }, + "kind": { + "enum": [ + "Buckety" + ], + "type": "string" + }, + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "spec": { + "properties": { + "adoption": { + "default": "AdoptEmpty", + "description": "What happens when the resolved backend resource\nalready exists at first reconcile. AdoptEmpty (the\ndefault) adopts only content-free resources; Adopt\nclaims a resource with pre-existing content.\nAdopted resources are never deleted from the\nbackend, whatever retentionPolicy says. See SPEC.md\nsection \"Adoption\".\n", + "enum": [ + "AdoptEmpty", + "Adopt" + ], + "type": "string" + }, + "backend": { + "description": "Name of a backend defined in the controller's config\nfile. Immutable after creation.\n", + "minLength": 1, + "type": "string" + }, + "defaultAccess": { + "description": "Single-consumer shortcut. Materialises an implicit\nBucketyAccess of the same name; reclaimed via\nowner-ref when an explicit BucketyAccess arrives or\nthis field is removed. See SPEC.md.\n", + "properties": { + "credentialsSecretName": { + "minLength": 1, + "type": "string" + }, + "role": { + "default": "ReadWrite", + "enum": [ + "Reader", + "Writer", + "ReadWrite" + ], + "type": "string" + } + }, + "required": [ + "credentialsSecretName" + ], + "type": "object" + }, + "name": { + "description": "Optional name template. Resolved once at first\nreconcile and frozen in status.backendResourceName.\nDefaults to metadata.name if omitted. See SPEC.md\nsection \"Naming templates\" for the grammar.\n", + "type": "string" + }, + "parameters": { + "additionalProperties": false, + "description": "Schema for Buckety.spec.parameters when the resolved driver is kadm. Driver-known keys are listed under properties; pass-through Kafka topic configs match patternProperties.", + "patternProperties": { + "^config\\.[a-z][a-z0-9._]*$": { + "description": "Pass-through Kafka topic config (e.g. config.retention.ms, config.cleanup.policy, config.compression.type). Backend's own validation catches typos. Drivers document which keys are immutable broker-side.", + "type": "string" + } + }, + "properties": { + "partitions": { + "description": "Partition count. Omit to use broker default (CreateTopics replication=-1 + broker default). Can grow but not shrink; an attempted shrink surfaces ParameterDrift.", + "pattern": "^[1-9][0-9]*$", + "type": "string" + }, + "replicationFactor": { + "description": "Replication factor. Omit (or set -1) to use broker default.replication.factor.", + "pattern": "^-?[1-9][0-9]*$", + "type": "string" + } + }, + "title": "kadm driver Buckety parameters", + "type": "object" + }, + "retentionPolicy": { + "default": "Retain", + "description": "What happens to the backend resource when this\nBuckety is deleted.\n", + "enum": [ + "Retain", + "Delete" + ], + "type": "string" + } + }, + "required": [ + "backend" + ], + "type": "object" + }, + "status": { + "properties": { + "backend": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "backendResourceName": { + "description": "Resolved name template. Sticky from first reconcile.\n", + "type": "string" + }, + "conditions": { + "items": { + "properties": { + "lastTransitionTime": { + "format": "date-time", + "type": "string" + }, + "message": { + "type": "string" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "reason": { + "type": "string" + }, + "status": { + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "status", + "lastTransitionTime", + "reason", + "message" + ], + "type": "object" + }, + "type": "array" + }, + "driver": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "driverBuildVersion": { + "description": "Full SemVer of the binary that most recently\nreconciled this resource. Informational.\n", + "type": "string" + }, + "driverMajor": { + "description": "Sticky. Gates driver-version compatibility. See\nSPEC.md \"Driver versioning\".\n", + "type": "integer" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "provenance": { + "description": "Created when the controller made the backend\nresource, Adopted when it claimed a pre-existing\none. Sticky. Absent on resources stamped before\nadoption tracking existed. DeleteBuckety only runs\nfor Created; see SPEC.md section \"Adoption\".\n", + "enum": [ + "Created", + "Adopted" + ], + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "title": "Buckety (kadm)", + "type": "object" +} diff --git a/schema/buckety-objectstore.schema.json b/schema/buckety-objectstore.schema.json new file mode 100644 index 0000000..a5a738f --- /dev/null +++ b/schema/buckety-objectstore.schema.json @@ -0,0 +1,207 @@ +{ + "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-objectstore.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Standalone editor schema for a Buckety carrying only object-store family-common parameters, provisionable on any bucket backend (gcs, s3) - see SPEC.md \"Driver families\". SPECIALIZE to buckety-gcs or buckety-s3 for driver-specific parameters; GENERALIZE to buckety for no parameter constraints.", + "properties": { + "apiVersion": { + "enum": [ + "buckety.yolean.se/v1alpha1" + ], + "type": "string" + }, + "kind": { + "enum": [ + "Buckety" + ], + "type": "string" + }, + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "spec": { + "properties": { + "adoption": { + "default": "AdoptEmpty", + "description": "What happens when the resolved backend resource\nalready exists at first reconcile. AdoptEmpty (the\ndefault) adopts only content-free resources; Adopt\nclaims a resource with pre-existing content.\nAdopted resources are never deleted from the\nbackend, whatever retentionPolicy says. See SPEC.md\nsection \"Adoption\".\n", + "enum": [ + "AdoptEmpty", + "Adopt" + ], + "type": "string" + }, + "backend": { + "description": "Name of a backend defined in the controller's config\nfile. Immutable after creation.\n", + "minLength": 1, + "type": "string" + }, + "defaultAccess": { + "description": "Single-consumer shortcut. Materialises an implicit\nBucketyAccess of the same name; reclaimed via\nowner-ref when an explicit BucketyAccess arrives or\nthis field is removed. See SPEC.md.\n", + "properties": { + "credentialsSecretName": { + "minLength": 1, + "type": "string" + }, + "role": { + "default": "ReadWrite", + "enum": [ + "Reader", + "Writer", + "ReadWrite" + ], + "type": "string" + } + }, + "required": [ + "credentialsSecretName" + ], + "type": "object" + }, + "name": { + "description": "Optional name template. Resolved once at first\nreconcile and frozen in status.backendResourceName.\nDefaults to metadata.name if omitted. See SPEC.md\nsection \"Naming templates\" for the grammar.\n", + "type": "string" + }, + "parameters": { + "additionalProperties": false, + "description": "Schema for Buckety.spec.parameters restricted to the object-store family's common parameters (SPEC.md section \"Driver families\"). A resource that validates here is portable across every bucket backend (gcs, s3): driver-specific knobs (gcs location, uniformBucketLevelAccess, softDeleteRetentionSeconds; r2 jurisdiction) belong in the backend config entry's parameters defaults, not in the CR.", + "properties": { + "lifecycle": { + "description": "Object lifecycle rules as a JSON document in the same shape `gsutil lifecycle set` takes, restricted to the family's PORTABLE SUBSET: action Delete or AbortIncompleteMultipartUpload, condition age (required) plus at most one matchesPrefix. Example: {\"rule\": [{\"action\": {\"type\": \"Delete\"}, \"condition\": {\"age\": 7, \"matchesPrefix\": [\"board-prints/\"]}}]}. {\"rule\": []} clears all rules; omitting the parameter leaves rules unmanaged. A backend that cannot express lifecycle configuration is skipped fail-safe.", + "type": "string" + }, + "versioning": { + "description": "Object versioning. Mutable; reconciled in place by every driver in the family. A backend that does not implement versioning is skipped fail-safe: the bucket still provisions.", + "enum": [ + "true", + "false" + ], + "type": "string" + } + }, + "title": "object-store family Buckety parameters", + "type": "object" + }, + "retentionPolicy": { + "default": "Retain", + "description": "What happens to the backend resource when this\nBuckety is deleted.\n", + "enum": [ + "Retain", + "Delete" + ], + "type": "string" + } + }, + "required": [ + "backend" + ], + "type": "object" + }, + "status": { + "properties": { + "backend": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "backendResourceName": { + "description": "Resolved name template. Sticky from first reconcile.\n", + "type": "string" + }, + "conditions": { + "items": { + "properties": { + "lastTransitionTime": { + "format": "date-time", + "type": "string" + }, + "message": { + "type": "string" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "reason": { + "type": "string" + }, + "status": { + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "status", + "lastTransitionTime", + "reason", + "message" + ], + "type": "object" + }, + "type": "array" + }, + "driver": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "driverBuildVersion": { + "description": "Full SemVer of the binary that most recently\nreconciled this resource. Informational.\n", + "type": "string" + }, + "driverMajor": { + "description": "Sticky. Gates driver-version compatibility. See\nSPEC.md \"Driver versioning\".\n", + "type": "integer" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "provenance": { + "description": "Created when the controller made the backend\nresource, Adopted when it claimed a pre-existing\none. Sticky. Absent on resources stamped before\nadoption tracking existed. DeleteBuckety only runs\nfor Created; see SPEC.md section \"Adoption\".\n", + "enum": [ + "Created", + "Adopted" + ], + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "title": "Buckety (objectstore)", + "type": "object" +} diff --git a/schema/buckety-s3.schema.json b/schema/buckety-s3.schema.json new file mode 100644 index 0000000..8f87786 --- /dev/null +++ b/schema/buckety-s3.schema.json @@ -0,0 +1,214 @@ +{ + "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Standalone editor schema for a Buckety whose backend resolves to the s3 driver. GENERALIZE to buckety-objectstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints.", + "properties": { + "apiVersion": { + "enum": [ + "buckety.yolean.se/v1alpha1" + ], + "type": "string" + }, + "kind": { + "enum": [ + "Buckety" + ], + "type": "string" + }, + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "spec": { + "properties": { + "adoption": { + "default": "AdoptEmpty", + "description": "What happens when the resolved backend resource\nalready exists at first reconcile. AdoptEmpty (the\ndefault) adopts only content-free resources; Adopt\nclaims a resource with pre-existing content.\nAdopted resources are never deleted from the\nbackend, whatever retentionPolicy says. See SPEC.md\nsection \"Adoption\".\n", + "enum": [ + "AdoptEmpty", + "Adopt" + ], + "type": "string" + }, + "backend": { + "description": "Name of a backend defined in the controller's config\nfile. Immutable after creation.\n", + "minLength": 1, + "type": "string" + }, + "defaultAccess": { + "description": "Single-consumer shortcut. Materialises an implicit\nBucketyAccess of the same name; reclaimed via\nowner-ref when an explicit BucketyAccess arrives or\nthis field is removed. See SPEC.md.\n", + "properties": { + "credentialsSecretName": { + "minLength": 1, + "type": "string" + }, + "role": { + "default": "ReadWrite", + "enum": [ + "Reader", + "Writer", + "ReadWrite" + ], + "type": "string" + } + }, + "required": [ + "credentialsSecretName" + ], + "type": "object" + }, + "name": { + "description": "Optional name template. Resolved once at first\nreconcile and frozen in status.backendResourceName.\nDefaults to metadata.name if omitted. See SPEC.md\nsection \"Naming templates\" for the grammar.\n", + "type": "string" + }, + "parameters": { + "additionalProperties": false, + "description": "Schema for Buckety.spec.parameters when the resolved driver is s3. Omitted parameters are unmanaged: the driver never touches that knob on the backend. versioning and lifecycle are object-store family parameters shared with the gcs driver (SPEC: Driver families); a backend that answers NotImplemented for one of them is skipped fail-safe - the bucket still provisions. Capability-gated parameters are honoured only when the backend declares the matching implementation discriminator.", + "properties": { + "jurisdiction": { + "description": "Cloudflare R2 data jurisdiction. Capability-gated: accepted only when the backend's config declares implementation: r2; admission rejects on any other implementation. Set at bucket creation, immutable post-create.", + "enum": [ + "eu" + ], + "type": "string" + }, + "lifecycle": { + "description": "Object lifecycle rules as a JSON document in the same shape `gsutil lifecycle set` takes - the object-store family's portable subset only: action Delete or AbortIncompleteMultipartUpload, condition age (required) plus at most one matchesPrefix. Anything outside the subset is an admission error; silently dropping conditions would delete more than asked. {\"rule\": []} clears all rules; omitting the parameter leaves rules unmanaged. Skipped fail-safe when the backend does not implement lifecycle configuration.", + "type": "string" + }, + "versioning": { + "description": "Object versioning. Mutable; reconciled in place (\"false\" on an ever-versioned bucket suspends it). Skipped fail-safe when the backend does not implement versioning.", + "enum": [ + "true", + "false" + ], + "type": "string" + } + }, + "title": "s3 driver Buckety parameters", + "type": "object" + }, + "retentionPolicy": { + "default": "Retain", + "description": "What happens to the backend resource when this\nBuckety is deleted.\n", + "enum": [ + "Retain", + "Delete" + ], + "type": "string" + } + }, + "required": [ + "backend" + ], + "type": "object" + }, + "status": { + "properties": { + "backend": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "backendResourceName": { + "description": "Resolved name template. Sticky from first reconcile.\n", + "type": "string" + }, + "conditions": { + "items": { + "properties": { + "lastTransitionTime": { + "format": "date-time", + "type": "string" + }, + "message": { + "type": "string" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "reason": { + "type": "string" + }, + "status": { + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "status", + "lastTransitionTime", + "reason", + "message" + ], + "type": "object" + }, + "type": "array" + }, + "driver": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "driverBuildVersion": { + "description": "Full SemVer of the binary that most recently\nreconciled this resource. Informational.\n", + "type": "string" + }, + "driverMajor": { + "description": "Sticky. Gates driver-version compatibility. See\nSPEC.md \"Driver versioning\".\n", + "type": "integer" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "provenance": { + "description": "Created when the controller made the backend\nresource, Adopted when it claimed a pre-existing\none. Sticky. Absent on resources stamped before\nadoption tracking existed. DeleteBuckety only runs\nfor Created; see SPEC.md section \"Adoption\".\n", + "enum": [ + "Created", + "Adopted" + ], + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "title": "Buckety (s3)", + "type": "object" +} diff --git a/schema/buckety.schema.json b/schema/buckety.schema.json new file mode 100644 index 0000000..83843bb --- /dev/null +++ b/schema/buckety.schema.json @@ -0,0 +1,194 @@ +{ + "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Standalone editor schema for a Buckety against any backend. spec.parameters is unconstrained at this level; SPECIALIZE by switching the $schema URL suffix: buckety-objectstore (portable across bucket backends), buckety-gcs, buckety-s3, buckety-kadm.", + "properties": { + "apiVersion": { + "enum": [ + "buckety.yolean.se/v1alpha1" + ], + "type": "string" + }, + "kind": { + "enum": [ + "Buckety" + ], + "type": "string" + }, + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "spec": { + "properties": { + "adoption": { + "default": "AdoptEmpty", + "description": "What happens when the resolved backend resource\nalready exists at first reconcile. AdoptEmpty (the\ndefault) adopts only content-free resources; Adopt\nclaims a resource with pre-existing content.\nAdopted resources are never deleted from the\nbackend, whatever retentionPolicy says. See SPEC.md\nsection \"Adoption\".\n", + "enum": [ + "AdoptEmpty", + "Adopt" + ], + "type": "string" + }, + "backend": { + "description": "Name of a backend defined in the controller's config\nfile. Immutable after creation.\n", + "minLength": 1, + "type": "string" + }, + "defaultAccess": { + "description": "Single-consumer shortcut. Materialises an implicit\nBucketyAccess of the same name; reclaimed via\nowner-ref when an explicit BucketyAccess arrives or\nthis field is removed. See SPEC.md.\n", + "properties": { + "credentialsSecretName": { + "minLength": 1, + "type": "string" + }, + "role": { + "default": "ReadWrite", + "enum": [ + "Reader", + "Writer", + "ReadWrite" + ], + "type": "string" + } + }, + "required": [ + "credentialsSecretName" + ], + "type": "object" + }, + "name": { + "description": "Optional name template. Resolved once at first\nreconcile and frozen in status.backendResourceName.\nDefaults to metadata.name if omitted. See SPEC.md\nsection \"Naming templates\" for the grammar.\n", + "type": "string" + }, + "parameters": { + "additionalProperties": { + "type": "string" + }, + "description": "Driver-validated parameters. Per-driver schema is\nenforced by the controller's admission webhook.\nMutable per-key per driver.\n", + "type": "object" + }, + "retentionPolicy": { + "default": "Retain", + "description": "What happens to the backend resource when this\nBuckety is deleted.\n", + "enum": [ + "Retain", + "Delete" + ], + "type": "string" + } + }, + "required": [ + "backend" + ], + "type": "object" + }, + "status": { + "properties": { + "backend": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "backendResourceName": { + "description": "Resolved name template. Sticky from first reconcile.\n", + "type": "string" + }, + "conditions": { + "items": { + "properties": { + "lastTransitionTime": { + "format": "date-time", + "type": "string" + }, + "message": { + "type": "string" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "reason": { + "type": "string" + }, + "status": { + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "status", + "lastTransitionTime", + "reason", + "message" + ], + "type": "object" + }, + "type": "array" + }, + "driver": { + "description": "Sticky. Stamped at first reconcile.", + "type": "string" + }, + "driverBuildVersion": { + "description": "Full SemVer of the binary that most recently\nreconciled this resource. Informational.\n", + "type": "string" + }, + "driverMajor": { + "description": "Sticky. Gates driver-version compatibility. See\nSPEC.md \"Driver versioning\".\n", + "type": "integer" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "provenance": { + "description": "Created when the controller made the backend\nresource, Adopted when it claimed a pre-existing\none. Sticky. Absent on resources stamped before\nadoption tracking existed. DeleteBuckety only runs\nfor Created; see SPEC.md section \"Adoption\".\n", + "enum": [ + "Created", + "Adopted" + ], + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "title": "Buckety (any driver)", + "type": "object" +} diff --git a/schema/bucketyaccess.schema.json b/schema/bucketyaccess.schema.json new file mode 100644 index 0000000..97d5f78 --- /dev/null +++ b/schema/bucketyaccess.schema.json @@ -0,0 +1,149 @@ +{ + "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/bucketyaccess.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "Standalone editor schema for a BucketyAccess. Driver-specific spec.parameters validation happens at admission; this schema leaves it unconstrained.", + "properties": { + "apiVersion": { + "enum": [ + "buckety.yolean.se/v1alpha1" + ], + "type": "string" + }, + "kind": { + "enum": [ + "BucketyAccess" + ], + "type": "string" + }, + "metadata": { + "properties": { + "annotations": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "labels": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "namespace": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "spec": { + "properties": { + "bucketyRef": { + "description": "Reference to a Buckety in the same namespace.\nCross-namespace references are not supported in\nv1alpha1.\n", + "properties": { + "name": { + "minLength": 1, + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "credentialsSecretName": { + "description": "Name of the Secret to mint in this namespace.\nImmutable.\n", + "minLength": 1, + "type": "string" + }, + "parameters": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "role": { + "default": "ReadWrite", + "description": "In v1alpha1 all roles receive identical credentials\n(no per-consumer scoping). Drivers surface a\nScopingNotImplemented condition rather than silently\ntreating Reader as ReadWrite.\n", + "enum": [ + "Reader", + "Writer", + "ReadWrite" + ], + "type": "string" + } + }, + "required": [ + "bucketyRef", + "credentialsSecretName" + ], + "type": "object" + }, + "status": { + "properties": { + "conditions": { + "items": { + "properties": { + "lastTransitionTime": { + "format": "date-time", + "type": "string" + }, + "message": { + "type": "string" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "reason": { + "type": "string" + }, + "status": { + "enum": [ + "True", + "False", + "Unknown" + ], + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type", + "status", + "lastTransitionTime", + "reason", + "message" + ], + "type": "object" + }, + "type": "array" + }, + "observedGeneration": { + "format": "int64", + "type": "integer" + }, + "principal": { + "description": "Backend-side identity granted access. In v1alpha1\nwith no per-consumer scoping this is typically\nthe backend's root principal.\n", + "type": "string" + } + }, + "type": "object" + } + }, + "required": [ + "apiVersion", + "kind", + "metadata", + "spec" + ], + "title": "BucketyAccess", + "type": "object" +} diff --git a/scripts/gen-cr-schemas/main.go b/scripts/gen-cr-schemas/main.go new file mode 100644 index 0000000..5d4ee1a --- /dev/null +++ b/scripts/gen-cr-schemas/main.go @@ -0,0 +1,168 @@ +// gen-cr-schemas emits standalone whole-document JSON schemas for +// Buckety and BucketyAccess into schema/, for yaml-language-server +// annotations on CR files (the kubernetes-json-schema pattern). +// +// The CRD yamls stay the source of truth for the CR shape; the +// per-driver and per-family parameters schemas stay the source of +// truth for spec.parameters. This generator only composes them, so +// the suffixed outputs form a specialize/generalize hierarchy a +// maintainer walks by switching the URL: +// +// buckety.schema.json any driver, parameters unconstrained +// buckety-objectstore.schema.json family-common parameters only (portable CR) +// buckety-gcs.schema.json full gcs driver parameters +// buckety-s3.schema.json full s3 driver parameters +// buckety-kadm.schema.json kadm driver parameters (no family) +// +// Run via `go run ./scripts/gen-cr-schemas` from the repo root; CI +// asserts the output is committed (git diff --exit-code -- schema/). +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + yaml "sigs.k8s.io/yaml" +) + +const rawBase = "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/" + +type doc = map[string]interface{} + +func main() { + bucketySpec, bucketyStatus := crdSchema("deploy/kustomize/crd/buckety.yaml") + accessSpec, accessStatus := crdSchema("deploy/kustomize/crd/bucketyaccess.yaml") + + variants := []struct { + suffix string + paramsFile string + desc string + }{ + {"", "", + "Standalone editor schema for a Buckety against any backend. spec.parameters is unconstrained at this level; SPECIALIZE by switching the $schema URL suffix: buckety-objectstore (portable across bucket backends), buckety-gcs, buckety-s3, buckety-kadm."}, + {"objectstore", "pkg/drivers/objectstore/schema/v0.1/parameters.schema.json", + "Standalone editor schema for a Buckety carrying only object-store family-common parameters, provisionable on any bucket backend (gcs, s3) - see SPEC.md \"Driver families\". SPECIALIZE to buckety-gcs or buckety-s3 for driver-specific parameters; GENERALIZE to buckety for no parameter constraints."}, + {"gcs", "pkg/drivers/gcs/schema/v0.1/parameters.schema.json", + "Standalone editor schema for a Buckety whose backend resolves to the gcs driver. GENERALIZE to buckety-objectstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints."}, + {"s3", "pkg/drivers/s3/schema/v0.1/parameters.schema.json", + "Standalone editor schema for a Buckety whose backend resolves to the s3 driver. GENERALIZE to buckety-objectstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints."}, + {"kadm", "pkg/drivers/kadm/schema/v0.1/parameters.schema.json", + "Standalone editor schema for a Buckety whose backend resolves to the kadm driver. kadm is deliberately not in a driver family (families are per service kind); GENERALIZE to buckety for no parameter constraints."}, + } + + must(os.MkdirAll("schema", 0o755)) + for _, v := range variants { + spec := deepCopy(bucketySpec) + if v.paramsFile != "" { + params := loadJSON(v.paramsFile) + delete(params, "$schema") + spec["properties"].(doc)["parameters"] = params + } + name := "buckety" + title := "Buckety (any driver)" + if v.suffix != "" { + name += "-" + v.suffix + title = "Buckety (" + v.suffix + ")" + } + write("schema/"+name+".schema.json", + wrap(name, title, v.desc, "Buckety", spec, deepCopy(bucketyStatus))) + } + write("schema/bucketyaccess.schema.json", + wrap("bucketyaccess", "BucketyAccess", + "Standalone editor schema for a BucketyAccess. Driver-specific spec.parameters validation happens at admission; this schema leaves it unconstrained.", + "BucketyAccess", deepCopy(accessSpec), deepCopy(accessStatus))) +} + +// crdSchema extracts the served version's spec and status schemas +// from a CRD manifest, with Kubernetes-only extensions stripped. +func crdSchema(path string) (spec, status doc) { + raw, err := os.ReadFile(path) + must(err) + var crd doc + must(yaml.Unmarshal(raw, &crd)) + versions := crd["spec"].(doc)["versions"].([]interface{}) + root := versions[0].(doc)["schema"].(doc)["openAPIV3Schema"].(doc) + stripKubernetesExtensions(root) + props := root["properties"].(doc) + return props["spec"].(doc), props["status"].(doc) +} + +// stripKubernetesExtensions removes x-kubernetes-* keys (CEL +// validations, list-type hints, preserve-unknown-fields) that are +// not JSON Schema and would be dead weight in an editor schema. +func stripKubernetesExtensions(m doc) { + for k, v := range m { + if strings.HasPrefix(k, "x-kubernetes-") { + delete(m, k) + continue + } + switch t := v.(type) { + case doc: + stripKubernetesExtensions(t) + case []interface{}: + for _, e := range t { + if em, ok := e.(doc); ok { + stripKubernetesExtensions(em) + } + } + } + } +} + +func wrap(name, title, description, kind string, spec, status doc) doc { + return doc{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": rawBase + name + ".schema.json", + "title": title, + "description": description, + "type": "object", + "required": []string{"apiVersion", "kind", "metadata", "spec"}, + "properties": doc{ + "apiVersion": doc{"type": "string", "enum": []string{"buckety.yolean.se/v1alpha1"}}, + "kind": doc{"type": "string", "enum": []string{kind}}, + "metadata": doc{ + "type": "object", + "required": []string{"name"}, + "properties": doc{ + "name": doc{"type": "string"}, + "namespace": doc{"type": "string"}, + "labels": doc{"type": "object", "additionalProperties": doc{"type": "string"}}, + "annotations": doc{"type": "object", "additionalProperties": doc{"type": "string"}}, + }, + }, + "spec": spec, + "status": status, + }, + } +} + +func loadJSON(path string) doc { + raw, err := os.ReadFile(path) + must(err) + var m doc + must(json.Unmarshal(raw, &m)) + return m +} + +func deepCopy(m doc) doc { + raw, err := json.Marshal(m) + must(err) + var out doc + must(json.Unmarshal(raw, &out)) + return out +} + +func write(path string, m doc) { + raw, err := json.MarshalIndent(m, "", " ") + must(err) + must(os.WriteFile(path, append(raw, '\n'), 0o644)) + fmt.Println("wrote", path) +} + +func must(err error) { + if err != nil { + panic(err) + } +} From d3d5a09669121909ce0ef921f67a50cb6868d209 Mon Sep 17 00:00:00 2001 From: "Yolean k8s-qa (buckety maintainer)" Date: Wed, 22 Jul 2026 08:41:47 +0000 Subject: [PATCH 2/4] examples: yaml-language-server schema annotations on every CR file Driver-suffixed for scenario CRs, buckety-objectstore for the portable-blobs-cr pair (both sides get the identical line, so the byte-identity assert still holds), bucketyaccess for access files, and the generic buckety schema for webhook-fallback-validation's deliberately invalid fixtures so editors do not flag them red. Fulfils the SPEC Schemas section's long-standing promise that example YAMLs carry the annotations. Co-Authored-By: Claude Fable 5 --- examples/gcs/adoption/fresh.yaml | 1 + examples/gcs/adoption/pre.yaml | 1 + examples/gcs/adoption/void.yaml | 1 + examples/gcs/backend-stickiness/original.yaml | 1 + examples/gcs/backend-stickiness/renamed.yaml | 1 + examples/gcs/happy-path/buckety.yaml | 1 + examples/gcs/multi-consumer/access.yaml | 1 + examples/gcs/multi-consumer/buckety.yaml | 1 + examples/gcs/naming-templates/bad-name.yaml | 1 + examples/gcs/naming-templates/bad-template.yaml | 1 + examples/gcs/naming-templates/buckety.yaml | 1 + examples/gcs/oob-drift/buckety.yaml | 1 + examples/gcs/parameter-mutation/buckety.yaml | 1 + examples/gcs/portable-blobs-cr/buckety.yaml | 1 + examples/gcs/retention-policy/buckety.yaml | 1 + examples/gcs/scaled-to-zero/buckety.yaml | 1 + examples/kadm/access-migration/buckety-initial.yaml | 1 + examples/kadm/access-migration/migrated/access-explicit.yaml | 1 + examples/kadm/access-migration/migrated/buckety.yaml | 1 + examples/kadm/adoption/fresh.yaml | 1 + examples/kadm/adoption/pre.yaml | 1 + examples/kadm/adoption/void.yaml | 1 + examples/kadm/backend-stickiness/original.yaml | 1 + examples/kadm/backend-stickiness/renamed.yaml | 1 + examples/kadm/driver-version/buckety.yaml | 1 + examples/kadm/happy-path/buckety.yaml | 1 + examples/kadm/multi-consumer/access.yaml | 1 + examples/kadm/multi-consumer/buckety.yaml | 1 + examples/kadm/naming-templates/bad-template.yaml | 1 + examples/kadm/naming-templates/buckety.yaml | 1 + examples/kadm/oob-drift/buckety.yaml | 1 + examples/kadm/parameter-mutation/buckety.yaml | 1 + examples/kadm/retention-policy/buckety.yaml | 1 + examples/kadm/scaled-to-zero/buckety.yaml | 1 + examples/kadm/secret-conflict/access.yaml | 1 + examples/kadm/secret-conflict/buckety.yaml | 1 + examples/kadm/webhook-fallback-validation/base.yaml | 1 + examples/kadm/webhook-fallback-validation/invalid-access.yaml | 1 + examples/kadm/webhook-fallback-validation/invalid-params.yaml | 1 + examples/kadm/webhook-fallback-validation/resume-probe.yaml | 1 + examples/s3/adoption/fresh.yaml | 1 + examples/s3/adoption/pre.yaml | 1 + examples/s3/adoption/void.yaml | 1 + examples/s3/backend-stickiness/original.yaml | 1 + examples/s3/backend-stickiness/renamed.yaml | 1 + examples/s3/driver-version/buckety.yaml | 1 + examples/s3/happy-path/buckety.yaml | 1 + examples/s3/jurisdiction-gating/accepted.yaml | 1 + examples/s3/jurisdiction-gating/rejected-on-non-r2.yaml | 1 + examples/s3/multi-consumer/access.yaml | 1 + examples/s3/multi-consumer/buckety.yaml | 1 + examples/s3/naming-templates/bad-name.yaml | 1 + examples/s3/naming-templates/bad-template.yaml | 1 + examples/s3/naming-templates/buckety.yaml | 1 + examples/s3/oob-drift/buckety.yaml | 1 + examples/s3/parameter-mutation/buckety.yaml | 1 + examples/s3/portable-blobs-cr/buckety.yaml | 1 + examples/s3/retention-policy/buckety.yaml | 1 + examples/s3/scaled-to-zero/buckety.yaml | 1 + 59 files changed, 59 insertions(+) diff --git a/examples/gcs/adoption/fresh.yaml b/examples/gcs/adoption/fresh.yaml index 46cd160..41d9f43 100644 --- a/examples/gcs/adoption/fresh.yaml +++ b/examples/gcs/adoption/fresh.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/adoption/pre.yaml b/examples/gcs/adoption/pre.yaml index 0a187f2..cc184d8 100644 --- a/examples/gcs/adoption/pre.yaml +++ b/examples/gcs/adoption/pre.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json # Collides with the pre-created, content-holding resource; the # default adoption policy (AdoptEmpty) must refuse it. apiVersion: buckety.yolean.se/v1alpha1 diff --git a/examples/gcs/adoption/void.yaml b/examples/gcs/adoption/void.yaml index 30aa1b0..774c5da 100644 --- a/examples/gcs/adoption/void.yaml +++ b/examples/gcs/adoption/void.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json # Collides with the pre-created EMPTY resource; AdoptEmpty (the # default) adopts it silently. apiVersion: buckety.yolean.se/v1alpha1 diff --git a/examples/gcs/backend-stickiness/original.yaml b/examples/gcs/backend-stickiness/original.yaml index fb4ecfc..5f4544c 100644 --- a/examples/gcs/backend-stickiness/original.yaml +++ b/examples/gcs/backend-stickiness/original.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/backend-stickiness/renamed.yaml b/examples/gcs/backend-stickiness/renamed.yaml index f145bcc..db3e36c 100644 --- a/examples/gcs/backend-stickiness/renamed.yaml +++ b/examples/gcs/backend-stickiness/renamed.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/happy-path/buckety.yaml b/examples/gcs/happy-path/buckety.yaml index 79f65dc..2cb431e 100644 --- a/examples/gcs/happy-path/buckety.yaml +++ b/examples/gcs/happy-path/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/multi-consumer/access.yaml b/examples/gcs/multi-consumer/access.yaml index 4c7d943..5aebd96 100644 --- a/examples/gcs/multi-consumer/access.yaml +++ b/examples/gcs/multi-consumer/access.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/bucketyaccess.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: BucketyAccess metadata: diff --git a/examples/gcs/multi-consumer/buckety.yaml b/examples/gcs/multi-consumer/buckety.yaml index ff94d62..8d3cbc2 100644 --- a/examples/gcs/multi-consumer/buckety.yaml +++ b/examples/gcs/multi-consumer/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/naming-templates/bad-name.yaml b/examples/gcs/naming-templates/bad-name.yaml index c19f826..8df0556 100644 --- a/examples/gcs/naming-templates/bad-name.yaml +++ b/examples/gcs/naming-templates/bad-name.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/naming-templates/bad-template.yaml b/examples/gcs/naming-templates/bad-template.yaml index 9013499..7f1316e 100644 --- a/examples/gcs/naming-templates/bad-template.yaml +++ b/examples/gcs/naming-templates/bad-template.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/naming-templates/buckety.yaml b/examples/gcs/naming-templates/buckety.yaml index 6234d7b..ecaff56 100644 --- a/examples/gcs/naming-templates/buckety.yaml +++ b/examples/gcs/naming-templates/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/oob-drift/buckety.yaml b/examples/gcs/oob-drift/buckety.yaml index d2e2116..fc1c334 100644 --- a/examples/gcs/oob-drift/buckety.yaml +++ b/examples/gcs/oob-drift/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/parameter-mutation/buckety.yaml b/examples/gcs/parameter-mutation/buckety.yaml index 8867732..48466d2 100644 --- a/examples/gcs/parameter-mutation/buckety.yaml +++ b/examples/gcs/parameter-mutation/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/portable-blobs-cr/buckety.yaml b/examples/gcs/portable-blobs-cr/buckety.yaml index 4a82044..68a12ca 100644 --- a/examples/gcs/portable-blobs-cr/buckety.yaml +++ b/examples/gcs/portable-blobs-cr/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-objectstore.schema.json # One CR for any bucket-family backend (SPEC "Driver families"). # Everything in spec is family-common and the backend name # "objects" is use-case-named, not driver-named; this file is diff --git a/examples/gcs/retention-policy/buckety.yaml b/examples/gcs/retention-policy/buckety.yaml index 0ab50b3..1f3ad53 100644 --- a/examples/gcs/retention-policy/buckety.yaml +++ b/examples/gcs/retention-policy/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/gcs/scaled-to-zero/buckety.yaml b/examples/gcs/scaled-to-zero/buckety.yaml index 3b88942..05fa945 100644 --- a/examples/gcs/scaled-to-zero/buckety.yaml +++ b/examples/gcs/scaled-to-zero/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/access-migration/buckety-initial.yaml b/examples/kadm/access-migration/buckety-initial.yaml index 5584955..e516b9e 100644 --- a/examples/kadm/access-migration/buckety-initial.yaml +++ b/examples/kadm/access-migration/buckety-initial.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/access-migration/migrated/access-explicit.yaml b/examples/kadm/access-migration/migrated/access-explicit.yaml index 62e456b..8f1186a 100644 --- a/examples/kadm/access-migration/migrated/access-explicit.yaml +++ b/examples/kadm/access-migration/migrated/access-explicit.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/bucketyaccess.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: BucketyAccess metadata: diff --git a/examples/kadm/access-migration/migrated/buckety.yaml b/examples/kadm/access-migration/migrated/buckety.yaml index d792bd8..af79081 100644 --- a/examples/kadm/access-migration/migrated/buckety.yaml +++ b/examples/kadm/access-migration/migrated/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/adoption/fresh.yaml b/examples/kadm/adoption/fresh.yaml index ddfcf18..d81ca23 100644 --- a/examples/kadm/adoption/fresh.yaml +++ b/examples/kadm/adoption/fresh.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/adoption/pre.yaml b/examples/kadm/adoption/pre.yaml index 189f9c9..997159f 100644 --- a/examples/kadm/adoption/pre.yaml +++ b/examples/kadm/adoption/pre.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json # Collides with the pre-created, content-holding resource; the # default adoption policy (AdoptEmpty) must refuse it. apiVersion: buckety.yolean.se/v1alpha1 diff --git a/examples/kadm/adoption/void.yaml b/examples/kadm/adoption/void.yaml index aa20456..079f819 100644 --- a/examples/kadm/adoption/void.yaml +++ b/examples/kadm/adoption/void.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json # Collides with the pre-created EMPTY resource; AdoptEmpty (the # default) adopts it silently. apiVersion: buckety.yolean.se/v1alpha1 diff --git a/examples/kadm/backend-stickiness/original.yaml b/examples/kadm/backend-stickiness/original.yaml index 8fe89a2..548734e 100644 --- a/examples/kadm/backend-stickiness/original.yaml +++ b/examples/kadm/backend-stickiness/original.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/backend-stickiness/renamed.yaml b/examples/kadm/backend-stickiness/renamed.yaml index d1adfc0..6b77464 100644 --- a/examples/kadm/backend-stickiness/renamed.yaml +++ b/examples/kadm/backend-stickiness/renamed.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/driver-version/buckety.yaml b/examples/kadm/driver-version/buckety.yaml index 8450465..f0c4f6b 100644 --- a/examples/kadm/driver-version/buckety.yaml +++ b/examples/kadm/driver-version/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/happy-path/buckety.yaml b/examples/kadm/happy-path/buckety.yaml index 2151e39..b31363c 100644 --- a/examples/kadm/happy-path/buckety.yaml +++ b/examples/kadm/happy-path/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/multi-consumer/access.yaml b/examples/kadm/multi-consumer/access.yaml index fa2f636..20c40ab 100644 --- a/examples/kadm/multi-consumer/access.yaml +++ b/examples/kadm/multi-consumer/access.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/bucketyaccess.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: BucketyAccess metadata: diff --git a/examples/kadm/multi-consumer/buckety.yaml b/examples/kadm/multi-consumer/buckety.yaml index 141e127..0904639 100644 --- a/examples/kadm/multi-consumer/buckety.yaml +++ b/examples/kadm/multi-consumer/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/naming-templates/bad-template.yaml b/examples/kadm/naming-templates/bad-template.yaml index 450f5aa..334039b 100644 --- a/examples/kadm/naming-templates/bad-template.yaml +++ b/examples/kadm/naming-templates/bad-template.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/naming-templates/buckety.yaml b/examples/kadm/naming-templates/buckety.yaml index 99d7329..7426535 100644 --- a/examples/kadm/naming-templates/buckety.yaml +++ b/examples/kadm/naming-templates/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/oob-drift/buckety.yaml b/examples/kadm/oob-drift/buckety.yaml index 5e82e96..0891f02 100644 --- a/examples/kadm/oob-drift/buckety.yaml +++ b/examples/kadm/oob-drift/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/parameter-mutation/buckety.yaml b/examples/kadm/parameter-mutation/buckety.yaml index 8523f08..8d6e935 100644 --- a/examples/kadm/parameter-mutation/buckety.yaml +++ b/examples/kadm/parameter-mutation/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/retention-policy/buckety.yaml b/examples/kadm/retention-policy/buckety.yaml index f96d885..b4a44dc 100644 --- a/examples/kadm/retention-policy/buckety.yaml +++ b/examples/kadm/retention-policy/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/scaled-to-zero/buckety.yaml b/examples/kadm/scaled-to-zero/buckety.yaml index fb42807..6c8ec72 100644 --- a/examples/kadm/scaled-to-zero/buckety.yaml +++ b/examples/kadm/scaled-to-zero/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/secret-conflict/access.yaml b/examples/kadm/secret-conflict/access.yaml index 9cd49c9..c0aaa5a 100644 --- a/examples/kadm/secret-conflict/access.yaml +++ b/examples/kadm/secret-conflict/access.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/bucketyaccess.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: BucketyAccess metadata: diff --git a/examples/kadm/secret-conflict/buckety.yaml b/examples/kadm/secret-conflict/buckety.yaml index 5d3568d..d44752c 100644 --- a/examples/kadm/secret-conflict/buckety.yaml +++ b/examples/kadm/secret-conflict/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-kadm.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/webhook-fallback-validation/base.yaml b/examples/kadm/webhook-fallback-validation/base.yaml index 9a72f68..51f3c73 100644 --- a/examples/kadm/webhook-fallback-validation/base.yaml +++ b/examples/kadm/webhook-fallback-validation/base.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/webhook-fallback-validation/invalid-access.yaml b/examples/kadm/webhook-fallback-validation/invalid-access.yaml index 4671664..31af161 100644 --- a/examples/kadm/webhook-fallback-validation/invalid-access.yaml +++ b/examples/kadm/webhook-fallback-validation/invalid-access.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/bucketyaccess.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: BucketyAccess metadata: diff --git a/examples/kadm/webhook-fallback-validation/invalid-params.yaml b/examples/kadm/webhook-fallback-validation/invalid-params.yaml index dbbc199..ec2f69c 100644 --- a/examples/kadm/webhook-fallback-validation/invalid-params.yaml +++ b/examples/kadm/webhook-fallback-validation/invalid-params.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/kadm/webhook-fallback-validation/resume-probe.yaml b/examples/kadm/webhook-fallback-validation/resume-probe.yaml index 8d98135..f9a57ad 100644 --- a/examples/kadm/webhook-fallback-validation/resume-probe.yaml +++ b/examples/kadm/webhook-fallback-validation/resume-probe.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/adoption/fresh.yaml b/examples/s3/adoption/fresh.yaml index 7e25800..88fb49a 100644 --- a/examples/s3/adoption/fresh.yaml +++ b/examples/s3/adoption/fresh.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/adoption/pre.yaml b/examples/s3/adoption/pre.yaml index f25401b..a34f574 100644 --- a/examples/s3/adoption/pre.yaml +++ b/examples/s3/adoption/pre.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json # Collides with the pre-created, content-holding resource; the # default adoption policy (AdoptEmpty) must refuse it. apiVersion: buckety.yolean.se/v1alpha1 diff --git a/examples/s3/adoption/void.yaml b/examples/s3/adoption/void.yaml index 961b834..8580e1a 100644 --- a/examples/s3/adoption/void.yaml +++ b/examples/s3/adoption/void.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json # Collides with the pre-created EMPTY resource; AdoptEmpty (the # default) adopts it silently. apiVersion: buckety.yolean.se/v1alpha1 diff --git a/examples/s3/backend-stickiness/original.yaml b/examples/s3/backend-stickiness/original.yaml index 2044ca9..f26b733 100644 --- a/examples/s3/backend-stickiness/original.yaml +++ b/examples/s3/backend-stickiness/original.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/backend-stickiness/renamed.yaml b/examples/s3/backend-stickiness/renamed.yaml index fb8f3eb..98715db 100644 --- a/examples/s3/backend-stickiness/renamed.yaml +++ b/examples/s3/backend-stickiness/renamed.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/driver-version/buckety.yaml b/examples/s3/driver-version/buckety.yaml index edec550..0c5b74e 100644 --- a/examples/s3/driver-version/buckety.yaml +++ b/examples/s3/driver-version/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/happy-path/buckety.yaml b/examples/s3/happy-path/buckety.yaml index 838d424..aebc57a 100644 --- a/examples/s3/happy-path/buckety.yaml +++ b/examples/s3/happy-path/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/jurisdiction-gating/accepted.yaml b/examples/s3/jurisdiction-gating/accepted.yaml index 0d4f3b7..8298943 100644 --- a/examples/s3/jurisdiction-gating/accepted.yaml +++ b/examples/s3/jurisdiction-gating/accepted.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/jurisdiction-gating/rejected-on-non-r2.yaml b/examples/s3/jurisdiction-gating/rejected-on-non-r2.yaml index a9106b8..f30de6a 100644 --- a/examples/s3/jurisdiction-gating/rejected-on-non-r2.yaml +++ b/examples/s3/jurisdiction-gating/rejected-on-non-r2.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/multi-consumer/access.yaml b/examples/s3/multi-consumer/access.yaml index 4c7d943..5aebd96 100644 --- a/examples/s3/multi-consumer/access.yaml +++ b/examples/s3/multi-consumer/access.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/bucketyaccess.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: BucketyAccess metadata: diff --git a/examples/s3/multi-consumer/buckety.yaml b/examples/s3/multi-consumer/buckety.yaml index 4b94389..3e13eaf 100644 --- a/examples/s3/multi-consumer/buckety.yaml +++ b/examples/s3/multi-consumer/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/naming-templates/bad-name.yaml b/examples/s3/naming-templates/bad-name.yaml index 39d0f33..bef416d 100644 --- a/examples/s3/naming-templates/bad-name.yaml +++ b/examples/s3/naming-templates/bad-name.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/naming-templates/bad-template.yaml b/examples/s3/naming-templates/bad-template.yaml index f5ea005..77b3109 100644 --- a/examples/s3/naming-templates/bad-template.yaml +++ b/examples/s3/naming-templates/bad-template.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/naming-templates/buckety.yaml b/examples/s3/naming-templates/buckety.yaml index 730b949..3e2fc05 100644 --- a/examples/s3/naming-templates/buckety.yaml +++ b/examples/s3/naming-templates/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/oob-drift/buckety.yaml b/examples/s3/oob-drift/buckety.yaml index b5c49e3..bb9d68b 100644 --- a/examples/s3/oob-drift/buckety.yaml +++ b/examples/s3/oob-drift/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/parameter-mutation/buckety.yaml b/examples/s3/parameter-mutation/buckety.yaml index 386fa47..679afd4 100644 --- a/examples/s3/parameter-mutation/buckety.yaml +++ b/examples/s3/parameter-mutation/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/portable-blobs-cr/buckety.yaml b/examples/s3/portable-blobs-cr/buckety.yaml index 4a82044..68a12ca 100644 --- a/examples/s3/portable-blobs-cr/buckety.yaml +++ b/examples/s3/portable-blobs-cr/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-objectstore.schema.json # One CR for any bucket-family backend (SPEC "Driver families"). # Everything in spec is family-common and the backend name # "objects" is use-case-named, not driver-named; this file is diff --git a/examples/s3/retention-policy/buckety.yaml b/examples/s3/retention-policy/buckety.yaml index 1b6886b..a3dd8f2 100644 --- a/examples/s3/retention-policy/buckety.yaml +++ b/examples/s3/retention-policy/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: diff --git a/examples/s3/scaled-to-zero/buckety.yaml b/examples/s3/scaled-to-zero/buckety.yaml index 2c8bfa2..5444503 100644 --- a/examples/s3/scaled-to-zero/buckety.yaml +++ b/examples/s3/scaled-to-zero/buckety.yaml @@ -1,3 +1,4 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json apiVersion: buckety.yolean.se/v1alpha1 kind: Buckety metadata: From aeebb6d8e82cb30f5abd556d610588b8da4b69e0 Mon Sep 17 00:00:00 2001 From: "Yolean k8s-qa (buckety maintainer)" Date: Wed, 22 Jul 2026 08:55:57 +0000 Subject: [PATCH 3/4] Pin parameters schemas to ValidateParameters in both directions The CI schema/ regen guard covers generated output vs sources, but the sources themselves - each driver's parameters.schema.json - were hand-synced with ValidateParameters, so a parameter added in code but not published would regenerate cleanly and ship wrong. TestParametersSchemaInSync closes that link per driver: every schema property must be code-known (probe never returns 'unknown parameter'), and every key the unknown-parameter error advertises must be in the schema. The gcs and s3 twins also enforce SPEC Driver families rule 1 at the schema level: every object-store family-common parameter must exist in the driver's own schema. kadm additionally pins the config.* pass-through to patternProperties. Mutation-checked: dropping versioning from the gcs schema fails the test. Co-Authored-By: Claude Fable 5 --- pkg/drivers/gcs/driver_test.go | 81 +++++++++++++++++++++++++++++++++ pkg/drivers/kadm/driver_test.go | 52 +++++++++++++++++++++ pkg/drivers/s3/driver_test.go | 70 ++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+) diff --git a/pkg/drivers/gcs/driver_test.go b/pkg/drivers/gcs/driver_test.go index 977d6be..f3849e6 100644 --- a/pkg/drivers/gcs/driver_test.go +++ b/pkg/drivers/gcs/driver_test.go @@ -2,6 +2,7 @@ package gcs import ( "encoding/json" + "os" "strings" "testing" "time" @@ -425,3 +426,83 @@ func keysOf(m map[string][]byte) []string { } return out } + +// The published parameters schema is hand-maintained next to +// ValidateParameters; this pins the two together in both +// directions so neither can outdate silently. The generated +// whole-CR schemas (schema/) compose from this file, so this is +// also their sync guard. +func TestParametersSchemaInSync(t *testing.T) { + props := schemaProperties(t, "schema/v0.1/parameters.schema.json") + d := &Driver{cfg: &Config{Project: "p"}} + + // Every schema property must be a code-known key: probing with + // an empty value may fail value validation, but never as + // "unknown parameter". + for key := range props { + if err := d.ValidateParameters(map[string]string{key: ""}); err != nil && strings.Contains(err.Error(), "unknown parameter") { + t.Errorf("schema property %q is unknown to ValidateParameters: %v", key, err) + } + } + + // Every key the unknown-parameter message advertises must be + // in the schema; a parameter added in code but not published + // fails here. + for _, key := range acceptedKeysFromError(t, d.ValidateParameters(map[string]string{"definitely-not-a-parameter": "x"})) { + if _, ok := props[key]; !ok { + t.Errorf("ValidateParameters advertises %q but schema/v0.1/parameters.schema.json does not list it", key) + } + } + + // Family portability (SPEC "Driver families" rule 1): every + // object-store family-common parameter must exist in this + // driver's schema too. + for key := range schemaProperties(t, "../objectstore/schema/v0.1/parameters.schema.json") { + if _, ok := props[key]; !ok { + t.Errorf("family parameter %q missing from the gcs parameters schema", key) + } + } +} + +func schemaProperties(t *testing.T, path string) map[string]json.RawMessage { + t.Helper() + raw, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + var s struct { + Properties map[string]json.RawMessage `json:"properties"` + } + if err := json.Unmarshal(raw, &s); err != nil { + t.Fatalf("parse %s: %v", path, err) + } + return s.Properties +} + +// acceptedKeysFromError extracts parameter names from the +// "( ... accepts: a, b, c)" enumeration, skipping filler +// words and capability qualifiers. +func acceptedKeysFromError(t *testing.T, err error) []string { + t.Helper() + if err == nil { + t.Fatal("expected an unknown-parameter error to parse") + } + msg := err.Error() + i := strings.Index(msg, "accepts: ") + if i < 0 { + t.Fatalf("no 'accepts:' enumeration in %q", msg) + } + seg := strings.TrimSuffix(msg[i+len("accepts: "):], ")") + var keys []string + for _, tok := range strings.Split(seg, " ") { + tok = strings.Trim(tok, ",") + switch { + case tok == "" || tok == "and" || tok == "when": + case strings.Contains(tok, "="): // capability qualifier, not a key + case strings.Contains(tok, "*"): // pattern shorthand, checked separately + default: + keys = append(keys, tok) + } + } + return keys +} diff --git a/pkg/drivers/kadm/driver_test.go b/pkg/drivers/kadm/driver_test.go index 198684f..c34ec2e 100644 --- a/pkg/drivers/kadm/driver_test.go +++ b/pkg/drivers/kadm/driver_test.go @@ -1,6 +1,8 @@ package kadm import ( + "encoding/json" + "os" "strings" "testing" ) @@ -111,3 +113,53 @@ func checkErr(t *testing.T, err error, want string) { t.Fatalf("error %q does not contain %q", err, want) } } + +// Pins the published parameters schema to ValidateParameters in +// both directions; see the gcs twin for rationale. The generated +// whole-CR schemas (schema/) compose from this file. +func TestParametersSchemaInSync(t *testing.T) { + raw, err := os.ReadFile("schema/v0.1/parameters.schema.json") + if err != nil { + t.Fatalf("read schema: %v", err) + } + var s struct { + Properties map[string]json.RawMessage `json:"properties"` + PatternProperties map[string]json.RawMessage `json:"patternProperties"` + } + if err := json.Unmarshal(raw, &s); err != nil { + t.Fatalf("parse schema: %v", err) + } + + for key := range s.Properties { + if verr := d.ValidateParameters(map[string]string{key: ""}); verr != nil && strings.Contains(verr.Error(), "unknown parameter") { + t.Errorf("schema property %q is unknown to ValidateParameters: %v", key, verr) + } + } + // The config.* pass-through advertised in the error message is + // published as patternProperties. + if len(s.PatternProperties) != 1 { + t.Errorf("expected exactly one patternProperties (config.*), got %d", len(s.PatternProperties)) + } + if verr := d.ValidateParameters(map[string]string{"config.retention.ms": "1000"}); verr != nil { + t.Errorf("config.* pass-through rejected: %v", verr) + } + + verr := d.ValidateParameters(map[string]string{"definitely-not-a-parameter": "x"}) + if verr == nil { + t.Fatal("expected an unknown-parameter error") + } + msg := verr.Error() + i := strings.Index(msg, "accepts: ") + if i < 0 { + t.Fatalf("no 'accepts:' enumeration in %q", msg) + } + for _, tok := range strings.Split(strings.TrimSuffix(msg[i+len("accepts: "):], ")"), " ") { + tok = strings.Trim(tok, ",") + if tok == "" || tok == "and" || strings.Contains(tok, "*") { + continue + } + if _, ok := s.Properties[tok]; !ok { + t.Errorf("ValidateParameters advertises %q but the schema does not list it", tok) + } + } +} diff --git a/pkg/drivers/s3/driver_test.go b/pkg/drivers/s3/driver_test.go index dcaac4a..7276d9c 100644 --- a/pkg/drivers/s3/driver_test.go +++ b/pkg/drivers/s3/driver_test.go @@ -1,8 +1,10 @@ package s3 import ( + "encoding/json" "fmt" "net/http" + "os" "strings" "testing" @@ -226,3 +228,71 @@ func TestIsNotImplemented(t *testing.T) { t.Error("403 AccessDenied wrongly treated as not-implemented") } } + +// Pins the published parameters schema to ValidateParameters in +// both directions; see the gcs twin for rationale. The generated +// whole-CR schemas (schema/) compose from this file. +func TestParametersSchemaInSync(t *testing.T) { + props := schemaProperties(t, "schema/v0.1/parameters.schema.json") + + // r2 driver so the capability-gated jurisdiction probes as + // value-invalid, not implementation-rejected. + for key := range props { + if err := r2Driver().ValidateParameters(map[string]string{key: ""}); err != nil && strings.Contains(err.Error(), "unknown parameter") { + t.Errorf("schema property %q is unknown to ValidateParameters: %v", key, err) + } + } + + for _, key := range acceptedKeysFromError(t, bareDriver().ValidateParameters(map[string]string{"definitely-not-a-parameter": "x"})) { + if _, ok := props[key]; !ok { + t.Errorf("ValidateParameters advertises %q but schema/v0.1/parameters.schema.json does not list it", key) + } + } + + // Family portability (SPEC "Driver families" rule 1). + for key := range schemaProperties(t, "../objectstore/schema/v0.1/parameters.schema.json") { + if _, ok := props[key]; !ok { + t.Errorf("family parameter %q missing from the s3 parameters schema", key) + } + } +} + +func schemaProperties(t *testing.T, path string) map[string]json.RawMessage { + t.Helper() + raw, err := os.ReadFile(path) + if err != nil { + t.Fatalf("read %s: %v", path, err) + } + var s struct { + Properties map[string]json.RawMessage `json:"properties"` + } + if err := json.Unmarshal(raw, &s); err != nil { + t.Fatalf("parse %s: %v", path, err) + } + return s.Properties +} + +func acceptedKeysFromError(t *testing.T, err error) []string { + t.Helper() + if err == nil { + t.Fatal("expected an unknown-parameter error to parse") + } + msg := err.Error() + i := strings.Index(msg, "accepts: ") + if i < 0 { + t.Fatalf("no 'accepts:' enumeration in %q", msg) + } + seg := strings.TrimSuffix(msg[i+len("accepts: "):], ")") + var keys []string + for _, tok := range strings.Split(seg, " ") { + tok = strings.Trim(tok, ",") + switch { + case tok == "" || tok == "and" || tok == "when": + case strings.Contains(tok, "="): + case strings.Contains(tok, "*"): + default: + keys = append(keys, tok) + } + } + return keys +} From eb0ce42703004d328f6e03c9bbe3ad5e3bef1eb4 Mon Sep 17 00:00:00 2001 From: "Yolean k8s-qa (buckety maintainer)" Date: Wed, 22 Jul 2026 08:57:13 +0000 Subject: [PATCH 4/4] Rename family schema suffix: buckety-blobstore The user-facing schema name follows the vocabulary consumers already use (blobs namespaces, site-userdata-blobs), not the internal family package name. pkg/drivers/objectstore keeps the SPEC term; schema/README.md documents the bridge. Co-Authored-By: Claude Fable 5 --- SPEC.md | 5 +++-- examples/gcs/portable-blobs-cr/buckety.yaml | 2 +- examples/s3/portable-blobs-cr/buckety.yaml | 2 +- schema/README.md | 8 ++++++-- ...e.schema.json => buckety-blobstore.schema.json} | 4 ++-- schema/buckety-gcs.schema.json | 2 +- schema/buckety-s3.schema.json | 2 +- schema/buckety.schema.json | 2 +- scripts/gen-cr-schemas/main.go | 14 +++++++++----- 9 files changed, 25 insertions(+), 16 deletions(-) rename schema/{buckety-objectstore.schema.json => buckety-blobstore.schema.json} (98%) diff --git a/SPEC.md b/SPEC.md index f2bdedf..fb1d078 100644 --- a/SPEC.md +++ b/SPEC.md @@ -568,8 +568,9 @@ publishes its family-common parameters the same way, under On top of these, `schema/` at the repo root carries GENERATED standalone whole-document schemas for editors (the kubernetes-json-schema pattern): `buckety.schema.json` plus one -per family and per driver (`buckety-objectstore`, `buckety-gcs`, -`buckety-s3`, `buckety-kadm`) and `bucketyaccess.schema.json`. +per family and per driver (`buckety-blobstore` for the +object-store family, `buckety-gcs`, `buckety-s3`, +`buckety-kadm`) and `bucketyaccess.schema.json`. The suffix forms a specialize/generalize ladder walked by switching the URL: the family rung accepts only family-common parameters, so a resource annotated with it provably stays diff --git a/examples/gcs/portable-blobs-cr/buckety.yaml b/examples/gcs/portable-blobs-cr/buckety.yaml index 68a12ca..c8a8ba9 100644 --- a/examples/gcs/portable-blobs-cr/buckety.yaml +++ b/examples/gcs/portable-blobs-cr/buckety.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-objectstore.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-blobstore.schema.json # One CR for any bucket-family backend (SPEC "Driver families"). # Everything in spec is family-common and the backend name # "objects" is use-case-named, not driver-named; this file is diff --git a/examples/s3/portable-blobs-cr/buckety.yaml b/examples/s3/portable-blobs-cr/buckety.yaml index 68a12ca..c8a8ba9 100644 --- a/examples/s3/portable-blobs-cr/buckety.yaml +++ b/examples/s3/portable-blobs-cr/buckety.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-objectstore.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-blobstore.schema.json # One CR for any bucket-family backend (SPEC "Driver families"). # Everything in spec is family-common and the backend name # "objects" is use-case-named, not driver-named; this file is diff --git a/schema/README.md b/schema/README.md index d04141a..804aae2 100644 --- a/schema/README.md +++ b/schema/README.md @@ -5,9 +5,13 @@ BucketyAccess yamls, annotated the same way as kubernetes-json-schema: ```yaml -# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-objectstore.schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-blobstore.schema.json ``` +"blobstore" is the user-facing name of the object-store driver +family (`pkg/drivers/objectstore`); the schema suffix uses the +name consumers reach for, the family package keeps the SPEC term. + The suffix picks a rung on a specialize/generalize ladder; a maintainer moves a resource between rungs by switching the URL, nothing else: @@ -15,7 +19,7 @@ nothing else: | Schema | spec.parameters accepts | | --- | --- | | `buckety.schema.json` | anything (string values) - any driver | -| `buckety-objectstore.schema.json` | object-store family-common only (`versioning`, `lifecycle` portable subset) - the resource stays provisionable on any bucket backend, gcs or s3 | +| `buckety-blobstore.schema.json` | object-store family-common only (`versioning`, `lifecycle` portable subset) - the resource stays provisionable on any bucket backend, gcs or s3 | | `buckety-gcs.schema.json` | the full gcs driver set | | `buckety-s3.schema.json` | the full s3 driver set | | `buckety-kadm.schema.json` | the kadm driver set (`partitions`, `replicationFactor`, `config.*`) - kadm is not in a family | diff --git a/schema/buckety-objectstore.schema.json b/schema/buckety-blobstore.schema.json similarity index 98% rename from schema/buckety-objectstore.schema.json rename to schema/buckety-blobstore.schema.json index a5a738f..20a0f34 100644 --- a/schema/buckety-objectstore.schema.json +++ b/schema/buckety-blobstore.schema.json @@ -1,5 +1,5 @@ { - "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-objectstore.schema.json", + "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-blobstore.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", "description": "Standalone editor schema for a Buckety carrying only object-store family-common parameters, provisionable on any bucket backend (gcs, s3) - see SPEC.md \"Driver families\". SPECIALIZE to buckety-gcs or buckety-s3 for driver-specific parameters; GENERALIZE to buckety for no parameter constraints.", "properties": { @@ -202,6 +202,6 @@ "metadata", "spec" ], - "title": "Buckety (objectstore)", + "title": "Buckety (blobstore)", "type": "object" } diff --git a/schema/buckety-gcs.schema.json b/schema/buckety-gcs.schema.json index a554ddd..0158f3e 100644 --- a/schema/buckety-gcs.schema.json +++ b/schema/buckety-gcs.schema.json @@ -1,7 +1,7 @@ { "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-gcs.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "Standalone editor schema for a Buckety whose backend resolves to the gcs driver. GENERALIZE to buckety-objectstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints.", + "description": "Standalone editor schema for a Buckety whose backend resolves to the gcs driver. GENERALIZE to buckety-blobstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints.", "properties": { "apiVersion": { "enum": [ diff --git a/schema/buckety-s3.schema.json b/schema/buckety-s3.schema.json index 8f87786..39cea3d 100644 --- a/schema/buckety-s3.schema.json +++ b/schema/buckety-s3.schema.json @@ -1,7 +1,7 @@ { "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety-s3.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "Standalone editor schema for a Buckety whose backend resolves to the s3 driver. GENERALIZE to buckety-objectstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints.", + "description": "Standalone editor schema for a Buckety whose backend resolves to the s3 driver. GENERALIZE to buckety-blobstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints.", "properties": { "apiVersion": { "enum": [ diff --git a/schema/buckety.schema.json b/schema/buckety.schema.json index 83843bb..205cfa9 100644 --- a/schema/buckety.schema.json +++ b/schema/buckety.schema.json @@ -1,7 +1,7 @@ { "$id": "https://raw.githubusercontent.com/Yolean/buckety-controller/main/schema/buckety.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "Standalone editor schema for a Buckety against any backend. spec.parameters is unconstrained at this level; SPECIALIZE by switching the $schema URL suffix: buckety-objectstore (portable across bucket backends), buckety-gcs, buckety-s3, buckety-kadm.", + "description": "Standalone editor schema for a Buckety against any backend. spec.parameters is unconstrained at this level; SPECIALIZE by switching the $schema URL suffix: buckety-blobstore (portable across bucket backends), buckety-gcs, buckety-s3, buckety-kadm.", "properties": { "apiVersion": { "enum": [ diff --git a/scripts/gen-cr-schemas/main.go b/scripts/gen-cr-schemas/main.go index 5d4ee1a..cfc5457 100644 --- a/scripts/gen-cr-schemas/main.go +++ b/scripts/gen-cr-schemas/main.go @@ -9,7 +9,7 @@ // maintainer walks by switching the URL: // // buckety.schema.json any driver, parameters unconstrained -// buckety-objectstore.schema.json family-common parameters only (portable CR) +// buckety-blobstore.schema.json family-common parameters only (portable CR) // buckety-gcs.schema.json full gcs driver parameters // buckety-s3.schema.json full s3 driver parameters // buckety-kadm.schema.json kadm driver parameters (no family) @@ -41,13 +41,17 @@ func main() { desc string }{ {"", "", - "Standalone editor schema for a Buckety against any backend. spec.parameters is unconstrained at this level; SPECIALIZE by switching the $schema URL suffix: buckety-objectstore (portable across bucket backends), buckety-gcs, buckety-s3, buckety-kadm."}, - {"objectstore", "pkg/drivers/objectstore/schema/v0.1/parameters.schema.json", + "Standalone editor schema for a Buckety against any backend. spec.parameters is unconstrained at this level; SPECIALIZE by switching the $schema URL suffix: buckety-blobstore (portable across bucket backends), buckety-gcs, buckety-s3, buckety-kadm."}, + // "blobstore" is the user-facing name of the object-store + // driver family (pkg/drivers/objectstore) - it matches how + // consumers talk about these resources (blobs namespaces + // etc), while the family package keeps the SPEC term. + {"blobstore", "pkg/drivers/objectstore/schema/v0.1/parameters.schema.json", "Standalone editor schema for a Buckety carrying only object-store family-common parameters, provisionable on any bucket backend (gcs, s3) - see SPEC.md \"Driver families\". SPECIALIZE to buckety-gcs or buckety-s3 for driver-specific parameters; GENERALIZE to buckety for no parameter constraints."}, {"gcs", "pkg/drivers/gcs/schema/v0.1/parameters.schema.json", - "Standalone editor schema for a Buckety whose backend resolves to the gcs driver. GENERALIZE to buckety-objectstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints."}, + "Standalone editor schema for a Buckety whose backend resolves to the gcs driver. GENERALIZE to buckety-blobstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints."}, {"s3", "pkg/drivers/s3/schema/v0.1/parameters.schema.json", - "Standalone editor schema for a Buckety whose backend resolves to the s3 driver. GENERALIZE to buckety-objectstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints."}, + "Standalone editor schema for a Buckety whose backend resolves to the s3 driver. GENERALIZE to buckety-blobstore to keep the resource portable across bucket backends, or to buckety for no parameter constraints."}, {"kadm", "pkg/drivers/kadm/schema/v0.1/parameters.schema.json", "Standalone editor schema for a Buckety whose backend resolves to the kadm driver. kadm is deliberately not in a driver family (families are per service kind); GENERALIZE to buckety for no parameter constraints."}, }