Skip to content

Feature/kubebuilder style versioning#27

Merged
alexlovelltroy merged 44 commits intomainfrom
feature/kubebuilder-style-versioning
Feb 3, 2026
Merged

Feature/kubebuilder style versioning#27
alexlovelltroy merged 44 commits intomainfrom
feature/kubebuilder-style-versioning

Conversation

@alexlovelltroy
Copy link
Member

This pull request introduces a comprehensive hub/spoke API versioning system, major changes to the resource code generation flow, and updates to project configuration and documentation. The main focus is on enabling Kubebuilder-style API versioning, supporting automatic conversion between versions, and enforcing a new, explicit resource envelope structure. It also deprecates legacy, non-versioned resource management and updates legal metadata files for SPDX compliance.

API Versioning and Resource Generation:

  • Implements hub/spoke (Kubebuilder-style) API versioning:

    • Adds a version registry, conversion middleware, and apis.yaml configuration for managing groups, versions, and imports.
    • Enforces a single hub (storage) version per API group with automatic conversion between hub and spoke (external) versions.
    • Updates code generation to create explicit resource envelopes with apiVersion, kind, metadata, spec, and status fields, replacing the previous embedded resource.Resource approach. [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12]
  • Updates the CLI:

    • Adds subcommands for adding resources and API versions, with flags for specifying versions and force-adding to non-alpha versions.
    • Enforces versioned resource addition and deprecates legacy (non-versioned) resource mode. [1] [2] [3] [4]

Configuration and Validation:

  • Updates .fabrica.yaml schema and validation:
    • Requires explicit configuration of API group, storage version, and version list for versioning.
    • Adds validation to ensure correct versioning setup and that the storage version is part of the versions list. [1] [2] [3] [4]

Documentation:

  • Documents the new versioning system:
    • Adds a comprehensive Hub/Spoke Versioning Guide.
    • Updates the README with new features and migration instructions. [1] Fd5f02adL61R61, [2]

Legal and Metadata Updates:

  • Migrates copyright/license metadata:
    • Replaces .reuse/dep5 with a new SPDX-compliant REUSE.toml file for better license tracking. [1] [2]

Breaking Changes:

  • BREAKING: Generated resource types no longer embed resource.Resource; any custom code referencing the embedded field must be updated to use explicit fields.

References:
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] Fd5f02adL61R61, [14] [15] [16]

Checklist

  • My code follows the style guidelines of this project
  • I have added/updated comments where needed
  • I have added tests that prove my fix is effective or my feature works
  • I have run make test (or equivalent) locally and all tests pass
  • DCO Sign-off: All commits are signed off (git commit -s) with my real name and email
  • REUSE Compliance:

Description

Please include a summary of the change and which issue is fixed.
Also include relevant motivation and context.

Fixes #(issue)

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

For more info, see Contributing Guidelines.

@davidallendj
Copy link
Contributor

It looks like some of the main README documentation needs to be updated like the quickstart before merging.
https://github.com/OpenCHAMI/fabrica/blob/feature/kubebuilder-style-versioning/README.md#-quick-start-5-minutes

@davidallendj
Copy link
Contributor

davidallendj commented Jan 6, 2026

A couple of notes testing this PR at this commit:

I went through the basic CRUD example and the basics seemed to work like expected and I'm able to add multiple versions of the API with fabrica add version v2 --force. However, when I try to add a v2 Device, it returns with apiVersion: v1 in the output.

Here's the create command by default with no version specified (works as expected):

./client device create --spec '{
  "description": "Core network switch",
  "ipAddress": "192.168.1.10",
  "location": "DataCenter A",
  "rack": "R42"
}'
{
  "apiVersion": "v1",
  "kind": "Device",
  "metadata": {
    "name": "",
    "uid": "device-f260b82a",
    "createdAt": "2026-01-06T14:03:30.424022558-07:00",
    "updatedAt": "2026-01-06T14:03:30.424022558-07:00"
  },
  "spec": {
    "description": "Core network switch",
    "ipAddress": "192.168.1.10",
    "location": "DataCenter A",
    "rack": "R42"
  },
  "status": {
    "ready": false
  }
}

And the corresponding v1 device specification defined in apis/example.fabrica.dev/v1/device_types.go:

type DeviceSpec struct {
  Description string `json:"description,omitempty" validate:"max=200"`
  IPAddress   string `json:"ipAddress,omitempty" validate:"omitempty,ip"`
  Location    string `json:"location,omitempty"`
  Rack        string `json:"rack,omitempty"`
}

Here's the command again, but specifying the --version flag with a value of v2. I would expect this to either give me an error or only return the fields defined by the v2 spec in apis/example.fabrica.dev/v2/device_types.go.

./client device create --spec '{
  "description": "Core network switch",
  "ipAddress": "192.168.1.10",
  "location": "DataCenter A",
  "rack": "R42"
}' --version v2
{
  "apiVersion": "v1",
  "kind": "Device",
  "metadata": {
    "name": "",
    "uid": "device-1ab74b62",
    "createdAt": "2026-01-06T14:03:44.894932171-07:00",
    "updatedAt": "2026-01-06T14:03:44.894932171-07:00"
  },
  "spec": {
    "description": "Core network switch",
    "ipAddress": "192.168.1.10",
    "location": "DataCenter A",
    "rack": "R42"
  },
  "status": {
    "ready": false
  }
}

Here's the v2 device specification in the apis/example.fabrica.dev/v2/device_types.go file:

type DeviceSpec struct {
  Description string `json:"description,omitempty" validate:"max=200"`
}

I made sure to run fabrica generate, go mod tidy, and then restart the server.

I'm guessing that some of this field validation should probably be handled in Validate() in the generated apis/example.fabrica.dev/v{1,2}/device_types.go files by default. I also noticed that it's possible to specify any arbitrary version using this command like --version whatever and it creates a new device even though the version does not exist. I suspect that there needs to be some --version flag validation behavior to check if the version of the API actually exists.

I built the binary using make build on the feature/kubebuilder-style-versioning branch so I was using a locally built binary. I noticed that the file paths for the generated files are outdated in the example README that I was following.

I also had to add replace github.com/openchami/fabrica => .. to the generated go.mod for the development version to work on this branch. I'm not sure if this would be necessary after this is merged to main, but I suspect it would be any time we're cloning and testing a local version fabrica.

Edit 1:

I noticed running fabrica add resource Device --version v1 produces the following output after fabrica init Device even though v1 is in the apis.yaml and the directory already exists at apis/example.fabrica.dev/v1.

fabrica add resource Device --version v1       
Error: adding resource to non-alpha version v1 requires --force flag

Stable versions should not have new resources added after release.
Use --force if you understand the implications, or consider adding to an alpha version first.
Usage:
  fabrica add resource [name] [flags]

Flags:
      --force             Force adding to non-alpha version
  -h, --help              help for resource
      --package string    Package name (defaults to lowercase resource name)
      --version string    API version (required for versioned projects, e.g., v1alpha1)
      --with-status       Include Status struct (default true)
      --with-validation   Include validation tags (default true)
      --with-versioning   Enable per-resource spec versioning (snapshots). Status is never versioned.

adding resource to non-alpha version v1 requires --force flag

Stable versions should not have new resources added after release.
Use --force if you understand the implications, or consider adding to an alpha version first.

The apis.yaml after fabrica init Device:

cat apis.yaml
groups:
    - name: example.fabrica.dev
      storageVersion: v1
      versions:
        - v1

Removing the --version v1 creates the resource like expected.

@davidallendj
Copy link
Contributor

davidallendj commented Jan 8, 2026

From a usability standpoint, I find it a bit annoying and tedious having to list and then delete all of my devices individually with ./client device delete <device-id>. Could there be a way to delete all of them at once (maybe a clear command?) or at least allow multiple UIDs with the delete subcommand?

The current way looks something like this to delete multiple devices.

./client device list
# ...show list of devices...
./client device delete device-0fc0a34f               
Device device-0fc0a34f deleted successfully
# ...scroll back up or run './client device list' again
./client device delete device-71ea3e92               
Device device-71ea3e92 deleted successfully
# ...repeat...
./client device delete device-a305f539               
Device device-a305f539 deleted successfully
# ...repeat...
./client device delete device-a9fc4c25               
Device device-a9fc4c25 deleted successfully
# ...repeat...
./client device delete device-fa6b7ef7               
Device device-fa6b7ef7 deleted successfully

storage_version: v1
versions:
- v1
resources: []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick, but groups.resources was not generated with the above command.

@davidallendj
Copy link
Contributor

I'm going to go ahead and resolve the documentation format issues for now and fix them in a future PR.

@davidallendj
Copy link
Contributor

davidallendj commented Jan 16, 2026

Getting a device requires the UID in the URL params instead of the name like in the documentation. This command needs to be updated in API versioning example.
https://github.com/OpenCHAMI/fabrica/tree/feature/kubebuilder-style-versioning/examples/08-api-versioning#get-a-device

Likewise, the delete example needs to be update to use the UID as well.
https://github.com/OpenCHAMI/fabrica/tree/feature/kubebuilder-style-versioning/examples/08-api-versioning#delete-a-device

For example, create a device.

curl -X POST http://localhost:8080/devices \
  -H "Content-Type: application/json" \
  -d @data.json

This will give this output. Notice that the uid is device-9e44d386 and the name is device-1.

{"apiVersion":"v1","kind":"Device","metadata":{"name":"device-1","uid":"device-9e44d386","createdAt":"2026-01-16T14:57:15.816284969-07:00","updatedAt":"2026-01-16T14:57:15.816284969-07:00"},"spec":{"ipAddress":"192.168.1.100","location":"DataCenter A","deviceType":"server","tags":{"env":"prod"}},"status":{"ready":false}}

With name in the URL:

curl http://localhost:8080/devices/device-1
# ...
{"error":"Device not found: failed to load Device device-1: resource not found","code":404}

With uid in the URL:

curl http://localhost:8080/devices/device-9e44d386 
# ...
{"apiVersion":"v1","kind":"Device","metadata":{"name":"device-1","uid":"device-9e44d386","createdAt":"2026-01-16T14:57:15.816284969-07:00","updatedAt":"2026-01-16T14:57:15.816284969-07:00"},"spec":{"ipAddress":"192.168.1.100","location":"DataCenter A","deviceType":"server","tags":{"env":"prod"}},"status":{"ready":false}}

@davidallendj
Copy link
Contributor

davidallendj commented Jan 20, 2026

I'm able to create a device with an arbitrary apiVersion and the response returns v1 still.

curl -X POST http://localhost:8080/devices \
  -H "Content-Type: application/json" \
  -d '{
    "apiVersion": "infra.example.io/v2asdasda",
    "kind": "Device",
    "metadata": {"name": "device-100"},
    "spec": {
      "ipAddress": "192.168.1.100",
      "location": "DataCenter A",
      "deviceType": "server",
      "tags": {"env": "prod"}
    }
  }'

And the output:

{"apiVersion":"v1","kind":"Device","metadata":{"name":"device-100","uid":"device-2e57421b","createdAt":"2026-01-20T13:20:23.289968826-07:00","updatedAt":"2026-01-20T13:20:23.289968826-07:00"},"spec":{"ipAddress":"192.168.1.100","location":"DataCenter A","deviceType":"server","tags":{"env":"prod"}},"status":{"ready":false}}

I have a spec for both v1 and v2 devices and I would expect to be able to create a Device for both.

@alexlovelltroy alexlovelltroy force-pushed the feature/kubebuilder-style-versioning branch from e4904c1 to 11117fb Compare January 23, 2026 15:48
@davidallendj davidallendj force-pushed the feature/kubebuilder-style-versioning branch from d97a199 to 377500f Compare February 3, 2026 22:20
Copy link
Contributor

@davidallendj davidallendj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Tried, tested, and worked like expected.

alexlovelltroy and others added 9 commits February 3, 2026 16:19
Add core infrastructure for hub/spoke API versioning:

- pkg/apiversion: Registry for API groups and versions, Hub/Convertible interfaces
- pkg/imports/catalog: Type metadata resolution for external imports

These packages provide the runtime infrastructure needed for version
negotiation and automatic conversion between hub (storage) and spoke
(external) API versions.

Part of: Hub/Spoke API Versioning feature

Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Add code generation templates for hub/spoke versioning:
- Hub (storage) version types with flattened envelopes
- Spoke (external) version types with conversion functions
- Version registry initialization

Integrate into generator:
- Load apiversion templates in LoadTemplates()
- Implement GenerateAPIVersions() method with placeholder
- Wire into GenerateAll() flow after models generation

Templates generate explicit APIVersion, Kind, Metadata, Spec, Status
fields instead of embedding resource.Resource, improving Go autodoc
and navigation.

Part of: Hub/Spoke API Versioning feature

Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Add configuration support for hub/spoke API versioning:
- APIsConfig struct with APIGroup, APIResource definitions
- Field mapping structs for version-specific transformations
- Import support for external type references
- LoadAPIsConfig() function to read apis.yaml
- ValidateAPIsConfig() with comprehensive validation rules

Validation ensures:
- At least one API group defined
- Group name and storageVersion required
- storageVersion must be in versions list
- Versions list not empty

This enables projects to declare multiple API versions via apis.yaml
configuration file, supporting hub/spoke versioning pattern.

Part of: Hub/Spoke API Versioning feature

Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Add comprehensive documentation for hub/spoke API versioning:

Documentation:
- docs/versioning.md: Complete guide (400+ lines) covering model, usage,
  migration path, and best practices
- README.md: Updated with versioning feature in Key Features
- CHANGELOG.md: Documented breaking change (flattened envelope)
- examples/README.md: Added "What's New in v0.4" section
- examples/01-basic-crud/README.md: Added versioning migration note

New Example:
- examples/08-api-versioning: Complete walkthrough demonstrating
  hub/spoke versioning with v1alpha1, v1beta1, v1 versions
- apis.yaml.example: Configuration template for multi-version APIs

Integration Tests:
- test/integration/versioning_test.go: 5 comprehensive test cases
  - Flattened envelope structure validation
  - apis.yaml placeholder functionality
  - Backward compatibility verification
  - Config validation
  - JSON format compatibility

BREAKING CHANGE: Generated resource types now use flattened envelope
structure with explicit APIVersion, Kind, Metadata, Spec, Status fields
instead of embedding resource.Resource. JSON wire format unchanged.

Migration guide: docs/versioning.md#migration-from-pre-flattening

Part of: Hub/Spoke API Versioning feature

Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
…adata

- Eliminate pkg/resources/ and apis/ redundancy in versioned mode
- Merge apis.yaml into .fabrica.yaml (single config)
- Add fabrica.Metadata type alias for cleaner imports
- New commands: `fabrica add version`, versioned `fabrica init`
- Generator discovers resources from apis/<group>/<storage-version>/
- Complete example 8 rewrite with working project structure
- Flattened envelope: explicit APIVersion, Kind, Metadata fields

Breaking: Versioned projects now define types directly, no generation
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
- Deleted go.sum.license and storage.go files as they are no longer needed.
- Enhanced code generation to support versioned projects by introducing a new `IsVersioned` flag.
- Updated handler templates to differentiate between versioned and legacy modes for resource creation, updates, and deletions.
- Added resource prefix registration to ensure proper UID generation for versioned resources.
- Improved event publishing logic to accommodate versioned resources.

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
alexlovelltroy and others added 27 commits February 3, 2026 16:25
…validation

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…initialization

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…rce management

- Updated README.md to include a Quickstart guide and improved navigation links.
- Refined usage instructions in USAGE.md for initializing projects with custom API groups.
- Improved error messages in add.go for better clarity on versioning and resource addition.
- Removed unnecessary versioning configuration from config.go.
- Enhanced generate.go to check for existing generated handler files before performing version checks.
- Updated init.go to provide clearer next steps for users after project initialization.
- Expanded quickstart.md with optional API versioning customization and detailed steps for resource addition.
- Adjusted storage-ent.md to clarify migration steps for existing projects.
- Revised codegen.md to reflect changes in resource discovery and registration.
- Updated versioning.md with detailed steps for adding new API versions and managing resource evolution.
- Modified examples to reflect new directory structure and resource definition locations.
- Improved main.go.tmpl to clarify reconciliation controller initialization and registration of reconcilers.

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…adability

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…ioned projects

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…move deprecated device_types.go

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
- Introduced quick-start and test scripts for the Ent Advanced example.
- Implemented export and import commands in the server for resource management.
- Added templates for generating query builders and transaction support.
- Enhanced README documentation for examples and usage instructions.
- Updated code generation to include new export/import commands and helpers.

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…s with validation functions

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…versioning commands; add export/import integration tests

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…structure

- Updated code generation documentation to reflect the transition from embedding `resource.Resource` to using a flattened envelope structure with explicit `APIVersion`, `Kind`, `Metadata`, `Spec`, and `Status` fields.
- Revised status subresource documentation to import `fabrica.Condition` instead of `resource.Condition`.
- Adjusted versioning documentation to redirect to the consolidated guide and removed deprecated content.
- Modified examples to reference the new API structure and updated file paths accordingly.
- Ensured all instances of `resource.Resource` are replaced with the new structure in example code.

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…mentation

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
- Updated `export_import_test.go` to use Ent storage for project creation and adjusted assertions for file backend.
- Enhanced `helpers.go` with server runtime management, including starting and stopping the server, and added HTTP call utilities.
- Introduced `runtime_test.go` to validate generated API servers and client library functionality through runtime tests.
- Created `test-lib.sh` for shared testing utilities, including HTTP request functions and validation checks.
- Added `test_debug.sh` for manual testing and debugging of server functionality.

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…d update related documentation

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…metadata handling

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
…metadata + spec)

Signed-off-by: Alex Lovell-Troy <alovelltroy@lanl.gov>
Signed-off-by: David Allen <davidallendj@gmail.com>
- Added new API definitions for Blade, BMC, Chassis, Node, Rack, and RackTemplate in the apis directory.
- Updated README.md to reflect the new directory structure and added apis.yaml for API group/version configuration.
- Refactored rack_reconciler to utilize new API types and updated resource creation logic.
- Modified example commands in various README files to use the new server import/export commands.
- Enhanced documentation to clarify the usage of apis.yaml and the versioning strategy.

Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
…ning registration

Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
- Create README.md for Example 09: Advanced Ent Storage Features, detailing query builders, transactions, and export/import capabilities.
- Implement quick-start.sh script for automated setup of the Ent advanced example.
- Add test-advanced.sh script to validate query builders, transactions, and export/import functionalities.
- Create README.md for Example 10: Export/Import, outlining backup and restore workflows using generated server commands.
- Update main examples README.md to include new examples and their descriptions.
- Add copyright headers to middleware test file.

Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
…stency

Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: Alex Lovell-Troy <alex@lovelltroy.org>
Signed-off-by: David Allen <davidallendj@gmail.com>
Signed-off-by: David Allen <davidallendj@gmail.com>
@synackd synackd force-pushed the feature/kubebuilder-style-versioning branch from 377500f to cf4dddc Compare February 3, 2026 23:26
@alexlovelltroy alexlovelltroy merged commit 27b3561 into main Feb 3, 2026
6 checks passed
@synackd synackd deleted the feature/kubebuilder-style-versioning branch February 3, 2026 23:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants