Skip to content

preserve unknown kubeconfig fields via serde(flatten)#1964

Merged
clux merged 3 commits into
kube-rs:mainfrom
alex-lapuka:feature/non-standard-fields-support
Mar 27, 2026
Merged

preserve unknown kubeconfig fields via serde(flatten)#1964
clux merged 3 commits into
kube-rs:mainfrom
alex-lapuka:feature/non-standard-fields-support

Conversation

@alex-lapuka

@alex-lapuka alex-lapuka commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Preserve unknown kubeconfig fields via serde(flatten)

Motivation

When tools read a kubeconfig, modify it (e.g. add a context), and write it back, any
fields not explicitly modeled in the Kubeconfig structs are silently dropped. This
causes data loss for users with:

  • Exec plugin fields like installHint (present in upstream Go client-go but missing here)
  • Cloud-provider-specific or vendor-specific extensions
  • Future Kubernetes fields not yet modeled in kube-rs

The existing extensions: Option<Vec<NamedExtension>> fields only capture the structured
extensions key — they don't preserve arbitrary unknown fields on each struct.

Changes

#[serde(flatten)] catch-all on all kubeconfig structs

Added pub other: BTreeMap<String, serde_json::Value> with #[serde(flatten)] to:
Kubeconfig, Preferences, NamedCluster, Cluster, NamedAuthInfo, AuthInfo,
AuthProviderConfig, ExecConfig, NamedContext, Context.

This follows the existing pattern in kube-core::schema::SchemaObject which already uses
#[serde(flatten)] extensions: BTreeMap<String, Value> for the same purpose.

Fields like installHint on ExecConfig (present in upstream Go client-go but not modeled
in kube-rs) are now automatically captured in the other map without needing dedicated
typed fields for each one.

Default derive for additional structs

Added Default derive to ExecConfig, Preferences, and AuthProviderConfig. These
structs previously lacked Default, making it harder to construct them with
..Default::default().

Merge logic

Updated Kubeconfig::merge() to merge the other maps using first-wins-per-key
semantics, consistent with the Kubernetes kubeconfig merge specification.

Round-trip test

Added kubeconfig_round_trip_preserves_unknown_fields test that verifies unknown fields
at every level (top-level, cluster, context, exec) survive a deserialize→serialize cycle.

Breaking changes

Adding a public field to these structs is technically a semver-breaking change for code
that constructs them with struct literal syntax without ..Default::default(). However:

  • ExecConfig, Preferences, AuthProviderConfig are never constructed with struct
    literals (only deserialized) — zero downstream impact
  • Kubeconfig, AuthInfo, Cluster, Context all derive Default and downstream code
    overwhelmingly uses ..Default::default()
  • NamedCluster, NamedAuthInfo, NamedContext are simple wrappers — the fix is adding
    ..Default::default() to any explicit construction site

The fix for any affected downstream code is mechanical: add ..Default::default() to
struct literal constructions.

Testing

  • All existing unit tests pass (10 file_config + 45 kube-client lib + 52 doc tests)
  • New round-trip preservation test validates the core behavior
  • Verified against a downstream consumer that reads/modifies/writes kubeconfigs

Add `#[serde(flatten)] pub other: BTreeMap<String, serde_json::Value>`
to all kubeconfig structs (Kubeconfig, Cluster, AuthInfo, ExecConfig,
Context, Preferences, AuthProviderConfig, and Named* wrappers) so that
unmodeled fields survive deserialization and can be serialized back
without data loss.

Also derive Default for ExecConfig, Preferences, and AuthProviderConfig.

Update Kubeconfig::merge() to merge extra fields with first-wins-per-key
semantics. Add a round-trip test verifying unknown fields are preserved
across deserialize/serialize cycles.

Signed-off-by: Alexey Lapuka <alexey@twingate.com>
@alex-lapuka alex-lapuka force-pushed the feature/non-standard-fields-support branch from 534af76 to 5f7da0d Compare March 25, 2026 22:47
@clux clux added this to the 4.0.0 milestone Mar 26, 2026
@clux clux added the changelog-change changelog change category for prs label Mar 26, 2026
@codecov

codecov Bot commented Mar 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.5%. Comparing base (288053e) to head (a850811).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##            main   #1964     +/-   ##
=======================================
+ Coverage   76.4%   76.5%   +0.2%     
=======================================
  Files         89      89             
  Lines       8540    8566     +26     
=======================================
+ Hits        6520    6550     +30     
+ Misses      2020    2016      -4     
Files with missing lines Coverage Δ
kube-client/src/client/mod.rs 77.6% <ø> (ø)
kube-client/src/config/file_config.rs 80.6% <100.0%> (+3.0%) ⬆️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@clux clux left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this. I think this is a good solution that makes us immediately compatible with new fields.

One small downside though, is that this may cause people to not bother contributing new standard fields when they appear in client-go, and ideally this should be a fallback method to access such fields until a release can be made.

I wonder if it makes sense to word something like a "if you are relying on this for standard fields present in upstream client-go, feel free to submit a PR". wdyt?

…consumers relying on standard client-go fields should submit PRs to add them as typed fields rather than using the generic fallback.

Also update the round-trip test to use only non-standard field names to avoid collision when standard fields like installHint are later added as typed fields.

Signed-off-by: Alexey Lapuka <alexey@twingate.com>
@clux clux changed the title feat: preserve unknown kubeconfig fields via serde(flatten) preserve unknown kubeconfig fields via serde(flatten) Mar 27, 2026

@clux clux left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you. Looks great.

@clux clux enabled auto-merge (squash) March 27, 2026 11:49
@clux clux disabled auto-merge March 27, 2026 11:51
@clux clux merged commit cfa38f2 into kube-rs:main Mar 27, 2026
29 of 30 checks passed
eleboucher pushed a commit to eleboucher/towonel that referenced this pull request Jun 18, 2026
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [kube](https://github.com/kube-rs/kube) | dependencies | major | `3.1` → `4.0` |

---

### Release Notes

<details>
<summary>kube-rs/kube (kube)</summary>

### [`v4.0.0`](https://github.com/kube-rs/kube/blob/HEAD/CHANGELOG.md#400--2026-06-16)

[Compare Source](kube-rs/kube@3.1.0...4.0.0)

\===================

<!-- Release notes generated using configuration in .github/release.yml at 4.0.0 -->

#### New Major

As per the release schedule to match up with the [latest Kubernetes ハル release](https://kubernetes.io/blog/2026/04/22/kubernetes-v1-36-release/).
Lots of fixes and improvements. Thanks to everyone who contributed!

#### Kubernetes `v1_36` support via k8s-openapi [0.28](https://github.com/Arnavion/k8s-openapi/releases/tag/v0.28.0)

Please [upgrade k8s-openapi along with kube](https://kube.rs/upgrading/) to avoid conflicts.

#### CEL Validation

A new optional crate [kube-cel](https://docs.rs/kube-cel/latest/kube_cel/) is being re-exported through `kube::core::cel` via [#&#8203;1954](kube-rs/kube#1954)

Kubernetes CRDs support [CEL validation rules](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules) via `x-kubernetes-validations`, and were supported from 3.0 via [`KubeSchema`](https://docs.rs/kube/latest/kube/derive.KubeSchema.html), but these rules could only be evaluated server-side by the API server.

The new crate allows evaluating these rules locally using rules matching the [upstream Kubernetes CEL libraries](https://docs.rs/kube/latest/kube/core/cel/trait.KubeCelExt.html#upstream-sources).

While low-level, a higher-level CEL validator integrates with [`CustomResource`](https://docs.rs/kube/latest/kube/derive.CustomResource.html) via [`#[kube(cel)]`](https://docs.rs/kube/latest/kube/derive.CustomResource.html#cel-validation-client-side) from [#&#8203;2011](kube-rs/kube#2011) and can be used as;

```rust

#[derive(CustomResource, Serialize, Deserialize, Clone, KubeSchema)]
#[kube(group = "example.com", version = "v1", kind = "Foo", namespaced)]

#[kube(cel, validation = "self.spec.replicas >= 0")] // cel trigger + validation rule
struct FooSpec { replicas: i32 }

let foo = Foo::new("test", FooSpec { replicas: -1 });
foo.validate_cel()?;                     // new impl; checks creation rules
new_foo.validate_cel_update(&old_foo)?;  // new impl; checks transition rules
```

See [examples/crd\_derive\_cel.rs](https://github.com/kube-rs/kube/blob/main/examples/crd_derive_cel.rs) for more details.

This is available under the `kube/cel` feature, courtesy of [@&#8203;doxxx93](https://github.com/doxxx93).

#### Config

A lot of improvements to config handling;

- better error handling of malformed client certs in [#&#8203;1966](kube-rs/kube#1966)
- add missing `Kubeconfig` fields in [#&#8203;1965](kube-rs/kube#1965)
- `Kubeconfig` future key compatibility for new fields by adding catch-all `other` key via [#&#8203;1964](kube-rs/kube#1964)
- deserialization changed from `serde-yaml` to [`serde-saphyr`](https://github.com/bourumir-wyngs/serde-saphyr) to get rid of the long-deprecated dependency. [#&#8203;1975](kube-rs/kube#1975)

##### Retry and Timeouts

Better timeout and retry handling to better deal with flaky network conditions, and busy or initializing apiservers.

- default global read timeouts has been unset in favor of `watcher` level timeouts in [#&#8203;1945](kube-rs/kube#1945) (see [#&#8203;1798](kube-rs/kube#1798) for context)
- regular (non-watch) queries now respect the [`RetryPolicy`](https://docs.rs/kube/latest/kube/client/retry/struct.RetryPolicy.html) - now enabled by default in [#&#8203;2007](kube-rs/kube#2007).

##### Client

- properly handling rotating ca certs in cluster via [#&#8203;1962](kube-rs/kube#1962)
- handle `tls-server-name` with `openssl-tls` via [#&#8203;1993](kube-rs/kube#1993)
- auth exec: accept `yaml` output from `exec` plugins via [#&#8203;2003](kube-rs/kube#2003)
- fix `ws` task leak and `drop`, and a deadlock on `join()` via [#&#8203;1978](kube-rs/kube#1978)
- **change**: client tracing now opt-in due to issues. see [#&#8203;1972](kube-rs/kube#1972)

##### Runtime

- [`watcher`](https://docs.rs/kube/latest/kube/runtime/watcher/index.html) automatically uses the `metadata_` api methods when called with [`PartialObjectMeta<K>`](https://docs.rs/kube/latest/kube/core/metadata/struct.PartialObjectMeta.html) via [#&#8203;1952](kube-rs/kube#1952)
  - (this deprecates [`metadata_watcher`](https://docs.rs/kube/latest/kube/runtime/watcher/fn.metadata_watcher.html) in favor of an explicit change from `Api::<K>` to `Api::<PartialObjectMeta<K>>`)
- added [`wait::conditions::is_created`](https://docs.rs/kube/latest/kube/runtime/wait/conditions/fn.is_deleted.html) as a counter to `is_deleted` [#&#8203;2000](kube-rs/kube#2000)
- added [`Store::state_filtered`](https://docs.rs/kube/latest/kube/runtime/reflector/struct.Store.html#method.state_filter) and [`Store::state_filter_selector`](https://docs.rs/kube/latest/kube/runtime/reflector/struct.Store.html#method.state_filter_selector) to allow more efficient slicing of the locked cache via [#&#8203;2002](kube-rs/kube#2002) + [#&#8203;1998](kube-rs/kube#1998)

#### What's Changed

##### Added

- feat: add typed kubeconfig fields for client-go parity by [@&#8203;alex-lapuka](https://github.com/alex-lapuka) in [#&#8203;1965](kube-rs/kube#1965)
- Add CEL validation via kube-cel re-export by [@&#8203;doxxx93](https://github.com/doxxx93) in [#&#8203;1954](kube-rs/kube#1954)
- Add `AdmissionRequest::to_cel_request()` for VAP CEL bridging by [@&#8203;doxxx93](https://github.com/doxxx93) in [#&#8203;1991](kube-rs/kube#1991)
- runtime: implement `Store::state_with` and `Store::state_filtered` by [@&#8203;Alvov1](https://github.com/Alvov1) in [#&#8203;1998](kube-rs/kube#1998)
- runtime: add `wait::conditions::is_created` helper by [@&#8203;orangecms](https://github.com/orangecms) in [#&#8203;2000](kube-rs/kube#2000)
- refactor(runtime): rename Store::state\_with/state\_filtered per review feedback by [@&#8203;Alvov1](https://github.com/Alvov1) in [#&#8203;2002](kube-rs/kube#2002)
- deps: bump kube-cel to 0.6.1 (validation surface flattened) by [@&#8203;doxxx93](https://github.com/doxxx93) in [#&#8203;2005](kube-rs/kube#2005)
- Enable `RetryPolicy::server_retry` by default for `Client` by [@&#8203;Danil-Grigorev](https://github.com/Danil-Grigorev) in [#&#8203;2007](kube-rs/kube#2007)
- feat(derive): client-side CEL validation via #\[kube(cel)] / #\[x\_kube(cel)] by [@&#8203;doxxx93](https://github.com/doxxx93) in [#&#8203;2011](kube-rs/kube#2011)

##### Changed

- preserve unknown kubeconfig fields via serde(flatten) by [@&#8203;alex-lapuka](https://github.com/alex-lapuka) in [#&#8203;1964](kube-rs/kube#1964)
- Remove global read\_timeout default, add watcher-level idle timeout by [@&#8203;doxxx93](https://github.com/doxxx93) in [#&#8203;1945](kube-rs/kube#1945)
- Update tokio-tungstenite requirement from 0.28.0 to 0.29.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1963](kube-rs/kube#1963)
- convert from serde-yaml to serde-saphyr by [@&#8203;clux](https://github.com/clux) in [#&#8203;1975](kube-rs/kube#1975)
- features: making client tracing opt-in by [@&#8203;mattklein123](https://github.com/mattklein123) in [#&#8203;1972](kube-rs/kube#1972)
- client: reload in-cluster CA bundle on rotation (rustls-tls) by [@&#8203;chrnorm](https://github.com/chrnorm) in [#&#8203;1962](kube-rs/kube#1962)
- Api\<PartialObjectMeta<K>> should opportunistically degrade to metadata requests by [@&#8203;doxxx93](https://github.com/doxxx93) in [#&#8203;1952](kube-rs/kube#1952)
- Chore(deps): Update garde requirement from 0.22.0 to 0.23.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;1989](kube-rs/kube#1989)
- bump k8s-openapi to 0.28 by [@&#8203;clux](https://github.com/clux) in [#&#8203;2009](kube-rs/kube#2009)
- Box a large runtime error in ReconcilerErr by [@&#8203;clux](https://github.com/clux) in [#&#8203;1880](kube-rs/kube#1880)

##### Fixed

- fix: feature-flag CREATE\_NO\_WINDOW to not break stderr inheritance by [@&#8203;cristeigabriela](https://github.com/cristeigabriela) in [#&#8203;1971](kube-rs/kube#1971)

- Remove silent error when client-key/client-certificate is malformed by [@&#8203;goenning](https://github.com/goenning) in [#&#8203;1966](kube-rs/kube#1966)

- Fix AttachedProcess task leak on drop and join() deadlock by [@&#8203;SebTardif](https://github.com/SebTardif) in [#&#8203;1978](kube-rs/kube#1978)

- support auth exec yaml output by [@&#8203;aviramha](https://github.com/aviramha) in [#&#8203;2003](kube-rs/kube#2003)

- fix(client): apply tls-server-name on the openssl-tls path by [@&#8203;dgunzy](https://github.com/dgunzy) in [#&#8203;1993](kube-rs/kube#1993)

- Use the resource's own name for the schema title by [@&#8203;cehoffman](https://github.com/cehoffman) in [#&#8203;1985](kube-rs/kube#1985)

- [@&#8203;alex-lapuka](https://github.com/alex-lapuka) made their first contribution in [#&#8203;1965](kube-rs/kube#1965)

- [@&#8203;cristeigabriela](https://github.com/cristeigabriela) made their first contribution in [#&#8203;1971](kube-rs/kube#1971)

- [@&#8203;mattklein123](https://github.com/mattklein123) made their first contribution in [#&#8203;1972](kube-rs/kube#1972)

- [@&#8203;chrnorm](https://github.com/chrnorm) made their first contribution in [#&#8203;1962](kube-rs/kube#1962)

- [@&#8203;SebTardif](https://github.com/SebTardif) made their first contribution in [#&#8203;1978](kube-rs/kube#1978)

- [@&#8203;Alvov1](https://github.com/Alvov1) made their first contribution in [#&#8203;1998](kube-rs/kube#1998)

- [@&#8203;orangecms](https://github.com/orangecms) made their first contribution in [#&#8203;2000](kube-rs/kube#2000)

- [@&#8203;dgunzy](https://github.com/dgunzy) made their first contribution in [#&#8203;1993](kube-rs/kube#1993)

- [@&#8203;cehoffman](https://github.com/cehoffman) made their first contribution in [#&#8203;1985](kube-rs/kube#1985)

**Full Changelog**: <kube-rs/kube@3.1.0...4.0.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xMDEuMSIsInVwZGF0ZWRJblZlciI6IjQzLjEwMS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJ0eXBlL21ham9yIl19-->

Reviewed-on: https://codeberg.org/towonel/towonel/pulls/29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog-change changelog change category for prs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants