New Features
Conversion Webhook Decoupled from Leader Election (#678)
Setup function. Providers that set StartWebhooks: true need to update their entry point. Providers that do not use conversion webhooks are unaffected.
Conversion webhook registration was gated behind leader election through the SetupGated/gate mechanism. SetupGated stores a closure that only fires on the elected leader, so follower pods never called ctrl.NewWebhookManagedBy and their webhook servers returned 404 for every conversion request. With two replicas and --leader-election, some conversion webhook calls fail.
The fix separates webhook registration from reconciler setup:
-
controller.go.tmpl: adds SetupWebhookWithManager — a standalone function that registers the conversion webhook for a single resource kind. Removes the if o.StartWebhooks { ... } block from Setup; webhook registration is no longer the reconciler's responsibility.
-
setup.go.tmpl: adds the SetupWebhookWithManager{{ .Group }} aggregator, following the same pattern as Setup{{ .Group }} and SetupGated{{ .Group }}, so providers can register all webhooks for a group in a single call.
Providers call SetupWebhookWithManager_ once before mgr.Start() on every pod, independent of the gate and leader election. Reconciler setup remains behind the gate and runs only on the leader.
// OLD generated code (v2.2.0 and earlier) — inside Setup()
if o.StartWebhooks {
if err := ctrl.NewWebhookManagedBy(mgr, &v1beta1.MyResource{}).
Complete(); err != nil {
return errors.Wrap(err, "cannot register webhook for the kind v1beta1.MyResource")
}
}What changed
The if o.StartWebhooks { ... } block has been removed from the generated Setup() function entirely. In its place, each generated resource controller file now exposes a standalone function:
// NEW generated code (v2.3.0) — standalone function, not inside Setup()
func SetupWebhookWithManager(mgr ctrl.Manager) error {
if err := ctrl.NewWebhookManagedBy(mgr, &v1beta1.MyResource{}).
Complete(); err != nil {
return errors.Wrap(err, "cannot register webhook for the kind v1beta1.MyResource")
}
return nil
}The group-level zz_setup.go file also gains a corresponding aggregator:
func SetupWebhookWithManagerMyGroup(mgr ctrl.Manager) error {
for _, setup := range []func(ctrl.Manager) error{
myresourceSetupWebhookWithManager,
// ... all resources in this group
} {
if err := setup(mgr); err != nil {
return err
}
}
return nil
}The StartWebhooks bool field remains on tjcontroller.Options for now but has no effect on the generated code and will be removed in a future release.
Provider side:
// Webhooks are registered eagerly on all pods before mgr.Start() so that
// every replica (leader and followers alike) can serve conversion requests.
// Reconciler setup is deferred to the gate and only runs on the leader.
startWebhooks := *certsDir != ""
if startWebhooks {
kingpin.FatalIfError(clustercontroller.SetupWebhookWithManager_accesscontextmanager(mgr), "Cannot setup cluster-scoped webhooks")
kingpin.FatalIfError(namespacedcontroller.SetupWebhookWithManager_accesscontextmanager(mgr), "Cannot setup namespaced webhooks")
}MR API Lifecycle Versioning & CRD Version Management (#539, #563, #567, #585)
Upjet now includes a comprehensive framework for managing Managed Resource API versioning across CRD versions. This introduces:
- New configuration APIs (
LifecycleConfiguration, storage/hub version hooks) inpkg/config/resource.goandpkg/pipeline/for generating multi-version CRDs - A
pkg/config/conversionpackage with built-in converters (optional field, field type change) and helpers for registering conversion functions automatically - A new
cmd/schemadiffCLI tool for schema change detection, integrated with thecrddifftool for detecting breaking changes between CRD versions - Documentation guide for managing CRD versions (
docs/breaking-change-detection.md)
API Roundtrip Testing Library (#636)
A new testing library (pkg/test/roundtrip) enables API roundtrip tests for generated provider types:
- Fuzzer-based serialization roundtrip testing with configurable fuzz functions
EquateNilAndZeroValuePtrcomparison helper for comparing nil pointers and zero-value pointers
ReconciliationPolicy Support (#665, #668)
A new ReconciliationPolicy API type (apis/v1alpha1/reconciliation_policy.go) allows per-resource control over reconciliation rate limiting:
- Configurable exponential failure rate limiter with per-resource
baseDelayandmaxDelay - New
reconciliationpolicyreconciler and finalizer implementations inpkg/reconciler/reconciliationpolicy/ handler.WithDefaultRateLimiteroption to make the default rate limiter forhandler.EventHandlerconfigurable (#668)
Resource Identity Support for TF Plugin Framework (#623)
The Terraform Plugin Framework integration now supports resource identity:
- Identity is passed to the framework only when the resource supports it
Missing Resource Identitydiagnostics are now treated as resource-not-found, enabling proper observe behavior
Customizable Controller Setup Template (#655)
Providers can now override the controller setup template used by pipeline.ControllerGenerator via config.Provider. New documentation has been added for main and controller template variables (docs/controller-template-variables.md, docs/main-template-variables.md).
Provider LocalName Support (#566)
Added an optional LocalName field to ProviderRequirement. This allows explicit override of the derived provider local name in Terraform configuration — useful for providers where the resource prefix doesn't match the source path (e.g., port-labs/port-labs).
Bug Fixes
- Race condition in async MR status (#670): Fixed a race condition on MR status between async callbacks and the managed reconciler. Async Plugin SDKv2 and Framework external clients now use deep copies for
Create/Deleteoperations. - Dotted map keys with correct nesting (#635): Fixed incorrect nesting when setting dotted map keys for sensitive values.
- Panic on null prior in
proposedNewAttributes(#641): Fixed a panic in the TF Plugin Framework client when the prior state is null. - Panic on nested attributes in collection types (#606): Fixed a panic while diffing TF framework resources with nested attribute collection types.
- Nested attributes inside blocks (#558): Handle nested attributes inside Terraform blocks correctly during schema traversal.
- Reconstructed state flush after observation failures (#555, #564): Both the Framework and TF SDK external clients now flush reconstructed states for recalculation after observation failures, fixing stale state issues.
- Runtime compatibility (#602): Fixed compatibility issues with
controller-runtimeandapimachinery(required JSON tags), and bumpedcrossplane-runtimeto v2.2.0.
Security
go.opentelemetry.io/otelupdated to v1.41.0github.com/antchfx/xpathupdated to v1.3.6google.golang.org/grpcupdated to v1.79.3
Dependency & CI Updates
- Go version bumped to 1.25.8
- Migrated to
golangci-lintv2 (v2.10.1) - Updated GitHub Actions:
actions/checkout@v6,actions/cache@v5,actions/setup-go@v6,codecov/codecov-action@v6,github/codeql-action@v4,zeebe-io/backport-action@v4,fsfe/reuse-action@v6 - Added Renovate configuration for automated dependency management
- Added
test.racemake target and parallel CI job for race detection
What's Changed
- Move mergenci to emeritus maintainers by @sergenyalcin in #556
- Add doc on managing CRD versions by @jeanduplessis in #539
- ci: Bump golangci-lint version to v2.6.1 by @erhancagirici in #557
- framework-external-client: Flush reconstructed states for recalculation after Observation failures by @erhancagirici in #555
- Handle nested attributes inside blocks by @kangasta in #558
- Adding new conversion functions and handling their runtime aspects by @sergenyalcin in #563
- tfsdk external client: Flush reconstructed states for recalculation after Observation failures by @erhancagirici in #564
- Configure Renovate by @renovate[bot] in #287
- Update zeebe-io/backport-action action to v4 by @renovate[bot] in #575
- Update actions/checkout action to v6 by @renovate[bot] in #572
- Update actions/cache action to v5 by @renovate[bot] in #571
- Update actions/github-script action to v8 by @renovate[bot] in #573
- Update codecov/codecov-action action to v5 by @renovate[bot] in #574
- Update fsfe/reuse-action action to v6 by @renovate[bot] in #577
- Update github/codeql-action action to v4 by @renovate[bot] in #582
- Update actions/setup-go action to v6 by @renovate[bot] in #580
- Update dependency ubuntu to v24 by @renovate[bot] in #581
- Update golangci/golangci-lint-action action to v9.2.0 by @renovate[bot] in #578
- Add CodeRabbit AI codereview agent configuration by @jeanduplessis in #540
- Add one pager to discuss Upjet MR API Lifecycle Versioning Policy by @sergenyalcin in #560
- feat(terraform): add LocalName field to ProviderRequirement by @acarter-wl in #566
- docs: Update README with up-to-date information by @jeanduplessis in #587
- Add new configuration APIs/mechanisms for starting to introduce new MR API version policy by @sergenyalcin in #585
- Update all non-major github action by @renovate[bot] in #586
- Update dependency golangci/golangci-lint to v2.10.1 by @renovate[bot] in #576
- Update all non-major github action by @renovate[bot] in #608
- Fix panic while diffing tf framework resources with nested attributes collection types by @ravilr in #606
- Update all non-major github action by @renovate[bot] in #613
- bump crossplane-runtime to v2.2.0 by @fernandezcuesta in #602
- Update module google.golang.org/grpc to v1.79.3 [SECURITY] by @renovate[bot] in #616
- feat: add resource identity support to TF Plugin Framework integration by @erikmiller-gusto in #623
- Update module github.com/antchfx/xpath to v1.3.6 [SECURITY] by @renovate[bot] in #626
- Update all non-major github action by @renovate[bot] in #620
- Update codecov/codecov-action action to v6 by @renovate[bot] in #622
- Update kubernetes patches by @renovate[bot] in #619
- fix panic when null prior in proposedNewAttributes by @stevendborrelli in #641
- ci: add codecov token by @erhancagirici in #643
- fix: set dotted map keys with correct nesting by @erhancagirici in #635
- feat: API roundtrip testing library by @erhancagirici in #636
- Update module go.opentelemetry.io/otel to v1.41.0 [SECURITY] by @renovate[bot] in #637
- Update all non-major github action by @renovate[bot] in #630
- Allow custom controller template via config.Provider by @ulucinar in #655
- Update all non-major github action by @renovate[bot] in #650
- Integrate schema change detection and add some helpers for registering the conversion functions automatically by @sergenyalcin in #567
- Add Support for ReconciliationPolicy Configurations by @ulucinar in #665
- Make the Default Rate Limiter for the Event Handler Configurable by @ulucinar in #668
- Fix Race on MR Status Between Async Callbacks & Managed Reconciler by @ulucinar in #670
- feat: add Description field to SSA InjectedKey by @fernandezcuesta in #672
- Decouple conversion webhook registration from leader election by @sergenyalcin in #678
New Contributors
- @kangasta made their first contribution in #558
- @renovate[bot] made their first contribution in #287
- @acarter-wl made their first contribution in #566
- @ravilr made their first contribution in #606
- @erikmiller-gusto made their first contribution in #623
- @stevendborrelli made their first contribution in #641
Full Changelog: v2.2.0...v2.3.0