feat: add GetX/SetX accessors to generated Go models - #160
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (9)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThe generator now optionally emits ChangesGo model accessor feature
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 6✅ Passed checks (6 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
internal/schemas/generator/accessors_test.go (1)
111-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winThanks for the thorough coverage here — would you be open to nudging these toward the repo's table-driven conventions?
These tests read well, and I appreciate the compile gate especially. To align with our test conventions, would it be worth giving the case tables a
reasonfield and running them as subtests viat.Run(name, ...), and usingcmp.Difffor the type comparisons inTestAddAccessors(e.g. on thewant/gotmethod maps)? That tends to give nicer failure output and matches the rest of the suite.No action needed if you'd rather defer, but flagging since it's a documented convention. As per path instructions: "Enforce table-driven test structure ... args/want pattern, use cmp.Diff ... Check for proper test case naming and reason fields."
Also applies to: 226-282
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@internal/schemas/generator/accessors_test.go` around lines 111 - 143, The tests here should be nudged into the repo’s table-driven style: update the cases in TestGenerateFromCRDIncludesAccessors and the related TestAddAccessors coverage to use named subtests with t.Run and include a reason field in each table entry for clearer intent. For the method-map/type assertions in TestAddAccessors, switch from direct comparisons to cmp.Diff on the want/got values to improve failure output and match the suite’s conventions. Use the existing test function names and method-map helpers as the anchor points when refactoring.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/schemas/generator/accessors_test.go`:
- Around line 175-224: The compile gate in
TestGeneratedModelsCompileWithAccessors is not hermetic because the generated
module path dev.crossplane.io/models must be resolved externally during go
build. Update the generator that writes the materialized go.mod so the generated
module can resolve locally, ideally by adding a replace directive that points
dev.crossplane.io/models to the on-disk output directory, or otherwise make the
offline CI requirement explicit if that’s the intended mechanism. Use the
TestGeneratedModelsCompileWithAccessors flow and the generated go.mod output
from goGenerator{}.GenerateFromCRD as the places to verify the fix.
In `@internal/schemas/generator/accessors.go`:
- Around line 34-39: `addAccessors`/`writeStructAccessors` currently emit
`GetX`/`SetX` methods without checking for existing methods, which can collide
with `oapi-codegen`-generated accessors or union helpers. Update
`writeStructAccessors` to inspect the target type’s existing method set before
generating each accessor, and skip any `GetX`/`SetX` whose name is already
present on that struct. Keep the check localized around `writeStructAccessors`
so the generator remains defensive against `additionalProperties` and union-type
method names like `GetAdditionalProperties`, `As`, or `From`.
---
Nitpick comments:
In `@internal/schemas/generator/accessors_test.go`:
- Around line 111-143: The tests here should be nudged into the repo’s
table-driven style: update the cases in TestGenerateFromCRDIncludesAccessors and
the related TestAddAccessors coverage to use named subtests with t.Run and
include a reason field in each table entry for clearer intent. For the
method-map/type assertions in TestAddAccessors, switch from direct comparisons
to cmp.Diff on the want/got values to improve failure output and match the
suite’s conventions. Use the existing test function names and method-map helpers
as the anchor points when refactoring.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 412e1a53-fb0f-44e9-977e-1b7d63eed6e7
📒 Files selected for processing (3)
internal/schemas/generator/accessors.gointernal/schemas/generator/accessors_test.gointernal/schemas/generator/go.go
fb2efaf to
f0c8846
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cmd/crossplane/main.go`:
- Around line 129-130: The `build`, `run`, and `generate` command entrypoints
currently assume `cfg` is always injected by Kong, which could panic if they are
invoked programmatically or in tests; add a nil check at the start of each `Run`
method in `cmd/crossplane/project/build.go`, `cmd/crossplane/project/run.go`,
and `cmd/crossplane/function/generate.go`, and return a clear error before any
`cfg.Features` access when `cfg` is nil.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: e91df577-976e-4c3f-b795-54476785f9e3
📒 Files selected for processing (12)
cmd/crossplane/config/help/config.mdcmd/crossplane/config/set.gocmd/crossplane/function/generate.gocmd/crossplane/function/generate_test.gocmd/crossplane/main.gocmd/crossplane/project/build.gocmd/crossplane/project/run.gointernal/config/config.gointernal/schemas/generator/accessors.gointernal/schemas/generator/accessors_test.gointernal/schemas/generator/go.gointernal/schemas/generator/interface.go
✅ Files skipped from review due to trivial changes (1)
- cmd/crossplane/config/help/config.md
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/schemas/generator/accessors.go
Generate pointer-in/pointer-out accessor methods for every field of every struct in the generated Go models, so consumers can abstract over resources with interfaces and generics. Folded into generateGo as a single AST post-process step so it applies uniformly across all generation paths. Gated behind a new features.generateGoModelAccessors config flag (off by default), threaded from config through the project build/run and function generate commands into the Go generator. Skips any GetX/SetX whose name already exists on a type, so the accessors never collide with methods oapi-codegen already emits (e.g. union or additionalProperties helpers). Verified by a compile gate that builds the generated module with a consumer that uses an accessor through an interface; the gate skips gracefully when module dependencies cannot be resolved offline. Signed-off-by: Erik Miller <erik.miller@gusto.com>
adamwg
left a comment
There was a problem hiding this comment.
Thanks for the contribution @erikmiller-gusto!
The core of this looks good to me. Leaving a few thoughts inline. My biggest concern is unifying the approach with #162 if possible, preferably also setting us up nicely to make the additional code generation non-optional in the future with minimal changes.
Apply accessor generation after Go post-processing at the call sites via a new applyAccessors helper, rather than folding it into generateGo. This mirrors the runtime.Object generation approach in crossplane#162 so both features use the same "post-process the generated code after the fact" pattern, and makes generateGo's signature identical across both, keeping the two changes easy to reconcile. Generating accessors after fixK8sTypeNames/removeSelfImports also means they reference the final type names. Skip unexported struct fields when emitting accessors: generated models don't currently have any, but an accessor for one would be useless to external consumers and could produce oddly-cased method names. Reuse receiverTypeName from accessors.go in the tests instead of redefining an equivalent renderRecv helper. Signed-off-by: Erik Miller <erik.miller@gusto.com>
Applying accessors in each generation loop pushed GenerateFromCRD past the gocognit limit. Extract the shared-K8s-package loop body into a generateSharedK8sPackage helper to bring it back under. Signed-off-by: Erik Miller <erik.miller@gusto.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/schemas/generator/go.go`:
- Around line 1272-1277: Update the error wrapping in both accessor-generation
call sites in internal/schemas/generator/go.go at lines 1272-1277 and 1411-1416,
replacing the generic message with “failed to generate Go model accessors.” Keep
the existing applyAccessors error propagation unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ac59f45c-d434-40ba-b56c-998cb7463bc2
📒 Files selected for processing (12)
cmd/crossplane/config/help/config.mdcmd/crossplane/config/set.gocmd/crossplane/function/generate.gocmd/crossplane/function/generate_test.gocmd/crossplane/main.gocmd/crossplane/project/build.gocmd/crossplane/project/run.gointernal/config/config.gointernal/schemas/generator/accessors.gointernal/schemas/generator/accessors_test.gointernal/schemas/generator/go.gointernal/schemas/generator/interface.go
🚧 Files skipped from review as they are similar to previous changes (10)
- cmd/crossplane/config/set.go
- internal/schemas/generator/interface.go
- internal/config/config.go
- cmd/crossplane/project/run.go
- cmd/crossplane/config/help/config.md
- cmd/crossplane/function/generate_test.go
- cmd/crossplane/main.go
- internal/schemas/generator/accessors.go
- cmd/crossplane/function/generate.go
- internal/schemas/generator/accessors_test.go
Reword the wrapped error at both accessor call sites from the generic "failed to add accessors" to "failed to generate Go model accessors". Signed-off-by: Erik Miller <erik.miller@gusto.com>
adamwg
left a comment
There was a problem hiding this comment.
The code generation here looks good to me, and works in my quick testing. Would be great for @haarchri to take a look as well, since he's worked in the Go generator code a lot.
One issue I did notice in testing: threading through the config option is missing in a few places where we generate schemas. Specifically, the crossplane dependency commands (add and update-cache) and the render commands (both composition and operation). The latter are a little tricky since they currently use the default generator from dependency.NewManager.
Upstream main dropped the `level: error` input from the Vale step, so reviewdog now runs at `-level=info` and Vale warnings fail validate-docs. "so generated resources can be used through interfaces and generics" trips write-good.Passive. Verified with Vale 3.14.2 against the crossplane/docs config at the alert level the action uses: 0 errors, 0 warnings. Signed-off-by: Erik Miller <erik.miller@gusto.com>
project build, project run and function generate already built their generators from the config, but every other command that generates schemas fell back to the flag-off default: - crossplane dependency add - crossplane dependency update-cache - crossplane composition generate - crossplane composition render / crossplane render - crossplane operation render The render commands built the dependency manager without WithSchemaGenerators, so dependency schemas came from the default even once the project's own schemas honored the flag. Build the generators before the manager and hand the same set to both. crossplane composition generate builds its dependency manager in AfterApply, so the config binding goes there rather than on Run. Note on the one remaining default: NewManager still defaults to generator.AllLanguages(), which is correct for a caller that only reads or cleans the cache (crossplane dependency clean-cache). Documented on NewManager so the next flag doesn't repeat this. Verified each command resolves its kong binding and reaches its own logic rather than failing on a missing binding. Signed-off-by: Erik Miller <erik.miller@gusto.com>
|
Overall LGTM one thing to clarify, when we using schemas from the official Providers we missing these new features from this PR and #162 |
True, for |
|
means we starting to have breaking Changes between up and crossplane - something to discuss internally |
adamwg
left a comment
There was a problem hiding this comment.
LGTM, thanks for all your work on this @erikmiller-gusto!
Fixes crossplane#143. Behind a new opt-in feature flag, generated Go models gain controller-gen-style DeepCopyInto/DeepCopy on every struct, and on root types (structs with APIVersion + Kind + Metadata, i.e. the resource and its List) DeepCopyObject, GetObjectKind/GroupVersionKind/SetGroupVersionKind reading and writing the typed APIVersion/Kind fields, and an init registering the type with the package SchemeBuilder. Each package containing root types gets a groupversion_info.go defining GroupVersion, SchemeBuilder and AddToScheme, apimachinery-only with no controller-runtime dependency. Users no longer have to set apiVersion/kind by hand on composed resources. Off by default: crossplane config set features.generateGoRuntimeObjects true The flag is threaded from config through every command that generates schemas: project build, project run, function generate, dependency add, dependency update-cache, composition generate, composition render / render, and operation render. The render commands hand the same generators to the dependency manager, so dependency schemas match the project's own. dependency clean-cache is the exception: it only removes generated schemas, so it keeps the flag-off default in dependency.NewManager. The generated models module requires k8s.io/apimachinery, pinned to the version the Go function template uses so a function consuming the models via a replace statement still resolves everything from the template's go.sum. There is one go.mod and go.sum regardless of the flag: an unused requirement is harmless to go build, and one set of module files is one thing fewer to maintain and test. Built-in Kubernetes types register under their real API group. The group labels the generator uses for those packages are synthetic and only drive the directory layout, so a goPackage now carries the layout group and the API group separately. Without that, core/v1 and meta/v1 registered under "core.k8s.io" and "meta.core.k8s.io", GVKs that disagree with what each type's own GroupVersionKind() reports. Tests cover DeepCopy generation (root vs non-root detection, scalar, struct and named-collection-alias fields), the API group written into groupversion_info.go, the feature-flag plumbing, and a default-off case. Compile gates materialize the generated module and build it, including a behavioral test for deep-copy independence and AddToScheme GVK round-tripping. They shell out to the Go toolchain, so they need network access and a writable module cache that the Nix sandbox running our unit tests does not have; they sit behind a compilegate build tag rather than a runtime skip, which would report as a test that ran: go test -tags compilegate ./internal/schemas/generator/... .golangci.yml lints the tagged file so it cannot rot. Signed-off-by: Erik Miller <erik.miller@gusto.com> Rebased onto main after crossplane#160, which landed the Go model accessors and the generator Option plumbing this change was written against. Four resolutions worth calling out: - generateK8sSharedSchemas, generateK8sPackageCode, generateModelsWithGVK and generateGVKGroupCode took an `accessors bool`. Rather than grow a second positional bool they now take the goGenerator, which already holds both flags. - Both features emit methods onto the same structs, so the order matters. runtime.Object runs first because addAccessors skips method names that already exist and applyRuntimeObjects does not: a field named objectKind yields a GetObjectKind accessor that collides with the one root types get from schema.ObjectKind. Reversed, the struct gets two and the generated module stops compiling. TestRuntimeObjectsThenAccessorsDeduplicates pins it. - Two compile gates build the generated module with both features on, which neither feature's own gate covered. - TestAllLanguagesWithGoRuntimeObjects became TestAllLanguagesGoOptions and now asserts both flags reach the Go generator independently and together. The eight commits this branch carried were squashed into one: they were review iterations that introduced and then removed code, and replaying them over the new base meant resolving conflicts against states that no longer exist. Signed-off-by: Erik Miller <erik.miller@gusto.com>
Add accessor/setter methods to generated Go models
Problem
The Go model generator produces structs that expose only fields:
Because the generated types have no methods, consumers can't abstract over resources with interfaces or write generic code that operates on "any resource with field X". Every field is a pointer (the goRemoveRequired mutator strips required, so oapi-codegen emits everything as optional *T).
What this does
Generates GetX/SetX accessor methods for every field of every generated struct, so consumers can define their own interfaces and satisfy them structurally:
Testing
go test ./... → all passing. go build, gofmt, go vet clean.
I have:
./nix.sh flake checkto ensure this PR is ready for review.Linked a PR or a docs tracking issue to document this change.Addedbackport release-x.ylabels to auto-backport this PR.On the two struck items: the
crossplane confighelp text this PR extends isrendered into the generated command reference, so
crossplane/docspicks the newkey up without a separate change. And this is a new opt-in feature rather than a
fix, so it isn't a backport candidate.
Need help with this checklist? See the cheat sheet.