Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d16fb58
trial: branch setup — Project.toml on OpenAPI 1.0 pin (1ff9ba8)
tanmaykm Aug 13, 2026
6a64b93
trial: Phase 1 — regenerate the API layer with the OpenAPI 1.0 generator
tanmaykm Aug 13, 2026
2d9f83a
trial: characterize retry conditions against the pinned runtime
tanmaykm Aug 13, 2026
073bbd0
trial: Phase 2 — rewrite helpers.jl against the 1.0 runtime
tanmaykm Aug 13, 2026
8e46e07
trial: Phase 3 — rewrite simpleapi.jl, plus two new patch rules
tanmaykm Aug 13, 2026
4085cb7
trial: Phase 4 — adapt the tests, docs and CI; add kuber_props
tanmaykm Aug 13, 2026
483f30f
trial: cover the watch acceptance criteria, and stop the re-watch loo…
tanmaykm Aug 13, 2026
a973102
trial: recover from mid-chunk aborts; record results and deviations
tanmaykm Aug 13, 2026
81e8616
trial: point CI's kind cluster at the k8s version the client is gener…
tanmaykm Aug 13, 2026
36b52dc
trial: survey what JuliaRun and JuliaHub need that this branch does n…
tanmaykm Aug 13, 2026
df13ce5
trial: rewrite C1 around the mechanism/content split, and scope it by…
tanmaykm Aug 13, 2026
9774841
trial: let out-of-tree generated layers plug into the registry (C1a)
tanmaykm Aug 14, 2026
90488a1
trial: validate the OP_PARAMS shape at registration too
tanmaykm Aug 14, 2026
5075036
trial: re-list instead of replaying when a resourceVersion expires (G1)
tanmaykm Aug 14, 2026
a8f94f4
trial: export is_retryable, the classification consumers lost with Op…
tanmaykm Aug 14, 2026
3a75a5c
trial: capture group documents from a cluster, and ship metrics.k8s.i…
tanmaykm Aug 14, 2026
6d06c4c
trial: bring back the custom-metrics helpers, and the list form they …
tanmaykm Aug 14, 2026
447beec
trial: cover put!(ctx, O, dict), the form production writes go throug…
tanmaykm Aug 14, 2026
431fb20
trial: record the json-patch vector shape JobLoops uses, under G16
tanmaykm Aug 14, 2026
e593e51
trial: make JSON patches encode at all, and cover the patch shapes (G…
tanmaykm Aug 14, 2026
c8e05c7
test: clear leftovers before the live suite runs
tanmaykm Aug 14, 2026
bc38f95
trial: refresh the suite table after G16
tanmaykm Aug 14, 2026
2fc03fc
test: cover the selector-scoped all-namespaces watch (G4)
tanmaykm Aug 14, 2026
3e9605a
trial: correct the live suite count after the cleanup fix
tanmaykm Aug 14, 2026
1b3b674
test: submit the kinds consumers write that the suite never touched (G6)
tanmaykm Aug 14, 2026
fe3d7e1
test: pin the shapes consumers read off a live result (G9, G10, G11)
tanmaykm Aug 14, 2026
d75b63c
test: cover caller-driven watch re-establishment (G2)
tanmaykm Aug 14, 2026
d02bec7
test: assert event continuity across the re-watch seam (G3)
tanmaykm Aug 14, 2026
b88cbef
test: round-trip Secret data and stringData (G7)
tanmaykm Aug 14, 2026
1783301
test: patch a Node as the cluster-scoped write consumers actually mak…
tanmaykm Aug 14, 2026
3a7c983
test: drive the retry loop against injected failures (G14)
tanmaykm Aug 14, 2026
5b85640
gen: collapse allOf-wrapped refs, so a referenced schema is one type …
tanmaykm Aug 14, 2026
f8c80a2
Own the retry loop: retry 429 with Retry-After, and make max_tries a …
tanmaykm Aug 14, 2026
9b6099f
gen: declare resourceVersion on single-object reads (G17)
tanmaykm Aug 14, 2026
da493bc
Re-measure the performance table, and stop orphaning Job pods
tanmaykm Aug 14, 2026
0334ed2
test: resource limits and requests as open structs, and audit the con…
tanmaykm Aug 14, 2026
cf3b9de
Add unexported getpropertyat/haspropertyat (C2)
tanmaykm Aug 15, 2026
f0e3439
trial: put custom.metrics.k8s.io on Kuber's side of the line
tanmaykm Aug 15, 2026
4973c7e
trial: rename C5's heading, which still called metrics.k8s.io a stub
tanmaykm Aug 15, 2026
23ddfbf
trial: write up how to actually get C5's capture
tanmaykm Aug 15, 2026
acc6356
trial: capture custom.metrics.k8s.io, and decide not to ship it
tanmaykm Aug 15, 2026
26bddc3
gen: stop SPECS_CAPTURED from erasing the groups it is not capturing
tanmaykm Aug 15, 2026
188dcd4
gen: keep unrecognized SPECS_CAPTURED blocks instead of dropping them
tanmaykm Aug 15, 2026
7bf4136
trial: split G5 along what compression can reach
tanmaykm Aug 15, 2026
007c677
test: cover the long-lived watch against a real apiserver (G5a)
tanmaykm Aug 15, 2026
e1f04df
test: close the establish race G5a's first run hit in CI
tanmaykm Aug 15, 2026
629c31a
test: add the long-lived watch probe (G5b)
tanmaykm Aug 15, 2026
d54eba1
docs: add a consumer checklist, arranged by repo rather than by finding
tanmaykm Aug 15, 2026
66de49b
docs: drop JuliaHubK8sApi rather than regenerate it
tanmaykm Aug 15, 2026
8e2c975
docs: reorder the consumer checklist now that nothing has to be built…
tanmaykm Aug 15, 2026
18f8b5b
docs: C10 — OpenAPI itself, not just OpenAPI.Clients
tanmaykm Aug 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
16 changes: 13 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: CI
on:
push:
branches: [master]
branches: [master, openapi-v1-trial]
tags: ["*"]
pull_request:
jobs:
Expand All @@ -12,7 +12,9 @@ jobs:
fail-fast: false
matrix:
version:
- '1.6'
# 1.11 is the floor: OpenAPI.jl 1.0 requires it (and `[sources]` in
# Project.toml needs a Pkg that understands it).
- '1.11'
- '1' # automatically expands to the latest stable 1.x release of Julia
- nightly
os:
Expand All @@ -23,7 +25,15 @@ jobs:
- uses: actions/checkout@v2
- uses: engineerd/setup-kind@v0.5.0
with:
version: "v0.11.1"
# The cluster has to serve the API surface the client was generated
# from: gen/openapi_v1/specs/SPECS_ORIGIN pins the OpenAPI documents
# to kubernetes v1.35.4, and strict response validation checks every
# reply against those schemas. v0.32.0 is the first kind release with
# a v1.35 node image (v1.35.5 differs from the spec tag by a patch
# release, which does not move the API surface). The digest is part of
# the image name on purpose — kind requires the exact published image.
version: "v0.32.0"
image: "kindest/node:v1.35.5@sha256:ce977ae6d65918d0b58a5f8b5e940429c2ce42fa3a5619ec2bbc60b949c0ac95"
- name: Testing
run: |
kubectl cluster-info
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.jl.cov
*.jl.*.cov
*.jl.mem
Manifest.toml
26 changes: 26 additions & 0 deletions Metrics.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,29 @@
> **Status on the `openapi-v1-trial` branch.** Neither API appears in the
> upstream Kubernetes OpenAPI documents this branch generates from — they are
> served by metrics-server and by a metrics adapter, not by the apiserver — so
> both have to be captured from a cluster that serves them
> (`gen/openapi_v1/fetch_specs.sh --from-cluster <group>/<version>`).
>
> - **Node and pod metrics work.** `metrics.k8s.io/v1beta1` was captured from a
> k3s v1.35.4 cluster and is shipped, so `:NodeMetrics` and `:PodMetrics`
> behave as below on any cluster running metrics-server.
> - **Custom metrics are not shipped, by decision.** `custom.metrics.k8s.io` was
> captured from a real adapter on 2026-08-15 and left out: its operations carry
> no group-version-kind and address metrics through a three-variable path, so
> the verb API cannot address them without new work, and nothing in the
> consumer repos calls the API (`OpenAPIv1ConsumerGaps.md` C5 has the
> evidence). The `list_custom_metrics`/`list_namespaced_custom_metrics` helpers
> are implemented and exported, and work as documented below against a group
> generated and registered with
> [`Kuber.register!`](README.md#adding-api-groups-kuber-does-not-ship) — which
> has to solve those two obstacles too.
>
> Two reading differences from the output shown here, which predates the
> rewrite: values come back as typed models rather than JSON, and `usage` is a
> k8s string map, so it is read with `kuber_props` and each entry is a
> `Quantity` whose string is in `.value` —
> `kuber_props(node.usage)["cpu"].value`.

Kubernetes Metrics and Custom Metrics APIs generalize consumption of metrics published by the cluster and applications running within it.

## Node and Pod Metrics
Expand Down
1,926 changes: 1,926 additions & 0 deletions OpenAPIv1ConsumerGaps.md

Large diffs are not rendered by default.

387 changes: 387 additions & 0 deletions OpenAPIv1RewriteNotes.md

Large diffs are not rendered by default.

630 changes: 630 additions & 0 deletions OpenAPIv1TrialBranchPlan.md

Large diffs are not rendered by default.

349 changes: 349 additions & 0 deletions OpenAPIv1TrialResults.md

Large diffs are not rendered by default.

23 changes: 14 additions & 9 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@ authors = ["JuliaHub Inc."]
keywords = ["kubernetes", "client"]
license = "MIT"
desc = "Julia Kubernetes Client"
version = "0.7.11"
version = "0.8.0-dev"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
OpenAPI = "d5e62ea6-ddf3-4d43-8e4c-ad5e6c8bfd7d"
TimeZones = "f269a46b-ccf7-5d73-abea-4c690281aa53"
UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"

[sources]
OpenAPI = {url = "https://github.com/quinnj/OpenAPI.jl", rev = "1ff9ba8dacf5857f0e712d26cc16e6a67bbdc46a"}

[compat]
Downloads = "1"
OpenAPI = "0.1,0.2"
JSON = "0.21, 1"
TimeZones = "1"
julia = "1"
Base64 = "1.11"
Dates = "1.11"
HTTP = "2"
JSON = "1.7"
OpenAPI = "1"
UUIDs = "1.11"
julia = "1.11"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
95 changes: 86 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,28 @@

A Julia Kubernetes Client.

An easy to use API to access Kubernetes clusters from Julia. The `Kuber.ApiImpl.Kubernetes` submodule has the complete set of low level APIs and entities.
An easy to use API to access Kubernetes clusters from Julia. Under the verb API sits a complete generated client: one module per Kubernetes API group version in `Kuber.ApiImpl` (`K8sV1`, `K8sAppsV1`, `K8sBatchV1`, …), with every low level operation and model type.

[Supported API Versions](SupportedAPIVersions.md)

> ### ⚠ This is the `openapi-v1-trial` branch
>
> A trial rebuild of Kuber on [OpenAPI.jl 1.0](https://github.com/JuliaComputing/OpenAPI.jl/pull/103), pinned to an unmerged commit. See [the trial plan](OpenAPIv1TrialBranchPlan.md) and [the evaluation notes](OpenAPIv1RewriteNotes.md). Requires **Julia 1.11+**, HTTP.jl 2.x and JSON.jl 1.7+.
>
> What changes for callers of the verb API:
>
> - **Absent fields are `ABSENT`, not `nothing`.** This is the one semantic change to watch for. A field missing from the payload now reads as `OpenAPI.Runtime.ABSENT`, and `nothing` means an explicit JSON `null`. Code doing `x.field === nothing` to test "not set" must use `Kuber._field(x.field) === nothing` (or compare against `ABSENT`) instead.
> - **Model field names are lowercase**, with a `_` suffix where a name collided: `metadata.resourceversion`, `obj.apiversion`. Type names are unchanged (`IoK8sApiCoreV1Pod`).
> - **String maps are open objects, not `Dict`s.** `metadata.labels`, `metadata.annotations` and friends get a generated struct whose entries live in `additional_properties`. Use `kuber_props(pod.metadata.annotations)["key"]` rather than indexing the field.
> - **`OpenAPI.Clients.getpropertyat`/`haspropertyat` have replacements**: `Kuber.getpropertyat(pod, :spec, :containers, 1, :image)` and `Kuber.haspropertyat`. Unexported, so qualify them. They treat `ABSENT` as absent — which a handwritten `hasproperty` walk cannot, since on 1.0 every field exists — and a path element may name an open-struct entry, so `Kuber.getpropertyat(node, :metadata, :labels, "role")` reads a label directly. Paths use the generated (lowercase) field names, and case is not folded.
> - **Watch events are `KuberEvent`**, with `event.type` and an already-typed `event.object` — `kuber_obj(ctx, event.object)` is no longer needed (it still accepts a dict). The first item on the stream is still the initial typed list result.
> - **Every group module has its own copy of the shared meta types**, so a `Status` from `apps/v1` is not the same Julia type as core's. Compare `kuber_kind(result) == "Status"` rather than the type — this matters for `delete!`, which returns either the deleted object or a `Status`.
> - **Timeouts are HTTP.jl 2.x request options.** `set_timeout(ctx, secs)` now sets `request_timeout`; `set_request_options(ctx; ...)` passes anything else through (including TLS configuration). Watches never carry an overall deadline — bound them with `timeout_seconds` instead.
> - **Errors** are always `KuberException`; there are no `(result, response)` tuples to check.
> - **Aggregated APIs are captured from a cluster, not from release specs.** `metrics.k8s.io/v1beta1` is captured and shipped, so `:NodeMetrics`/`:PodMetrics` work against metrics-server. `custom.metrics.k8s.io` was captured from a real adapter and deliberately not shipped (`OpenAPIv1ConsumerGaps.md` C5), so the `list_custom_metrics`/`list_namespaced_custom_metrics` helpers, which are implemented, need the group registered first. CRD-backed groups stay out: they belong to the deployment that defines them. The generated layer covers the group versions listed in `gen/openapi_v1/fetch_specs.sh` plus the captures in `SPECS_CAPTURED`.
>
> The generated layer is checked in and reproducible; see [`gen/openapi_v1/README.md`](gen/openapi_v1/README.md) to regenerate it. Never hand-edit `src/ApiImpl/generated/`.

Most of the low level APIs fit into a common usage pattern. Kuber.jl makes it possible to use all of them with only a few intuitive verb based APIs. Verbs act on entities. Entities can be identified by names or selector patterns, or otherwise can apply to all entities of that class. Verbs can take additional parameters, e.g. when creating or updating entities.

API and Entity naming convention follows the standard Kubernetes API and Model naming conventions.
Expand Down Expand Up @@ -47,10 +65,10 @@ All verbs have the signature:
verb(ctx::KuberContext, T::Symbol, args...; kwargs...)
```

Kubernetes also provides efficient change notifications on resources via "watches". Certain entities have the special `watch` APIs defined for them and that can be invoked with the `watch` verb. The `watch` API accepts a `Channel` through which it streams events.
Kubernetes also provides efficient change notifications on resources via "watches". These can be invoked with the `watch` verb, which accepts a `Channel` through which it streams events.

```julia
watch(ctx::KuberContext, T::Symbol, outstream::Channel, args...; kwargs...)
watch(ctx::KuberContext, T::Symbol, outstream::Channel; kwargs...)
```

In addition, verbs like `get` and `list` also support watches, and those can be invoked as:
Expand All @@ -65,27 +83,86 @@ end

E.g.:

E.g.:

```julia
watch(ctx, list, :Pod; resource_version=19451) do stream
watch(ctx, list, :Pod) do stream
for event in stream
@info("got event", event)
@info("got event", event) # a PodList first, then KuberEvents
end
end
```

The watch keeps itself alive: if the connection ends or is dropped, it is
re-established from the last `resourceVersion` seen. Closing the stream is how a
consumer stops a watch.

**A list object on the stream means complete current state.** It is the first
frame, and it appears again whenever the watch has to resync. That happens when
the `resourceVersion` expires — the apiserver answers a watch resumed from too
old a version with an in-stream `ERROR`, and Kuber lists again rather than
watching from scratch. Watching from scratch would replay everything that
currently exists as `ADDED` and never mention what was *deleted* while the watch
was gone, so a consumer's cache would keep phantom entries for the life of the
process. So: on a list frame, discard anything you cached that is not in it.

`watch(ctx, :Pod, stream)` — the events-only form — opts out of list frames, and
therefore out of resync state too. It still recovers, but a consumer maintaining
a cache on that form has to track expiry itself.

It also starts from a `resourceVersion` it finds by listing and then discarding
the result, so an object created between the call and that internal list is
never announced. Pass `resource_version=` — read off a `get`/`list` you make
yourself — when there must be no gap between the state you have and the events
you get. That is the list-then-watch shape, and it is what `watch(ctx, list, O)`
does for you.

### Helper methods:

A Kubernetes context can be manipulated with:

- `set_server`: Set the API server location ("http://localhost:8001" if not set)
- `set_ns`: Set the namespace to deal with (`default` namespace is not set)
- `set_retries`: Set the number of times an API call should be retried on a retriable error (5 if not set) and whether all APIs should be retried (only non mutating APIs are retried by default)
- `set_retries`: Set how many **attempts** an API call gets on a retriable error (5 if not set, so up to four retries) and whether all APIs should be retried (only non mutating APIs are retried by default — with the count meaning attempts, that now genuinely means one request). The count is a budget of requests: HTTP.jl's own retry layer is off by default on a `KuberContext`, so nothing retries underneath it. A retriable error is a transport failure or a 429/5xx; a 429's `Retry-After` lengthens the wait
- `set_timeout` / `get_timeout` / `with_timeout`: Set an overall per-request deadline in seconds
- `set_request_options` / `get_request_options`: Pass any other HTTP.jl request option (connection timeouts, TLS configuration, …)

Other convenience methods:

- `kuber_type`: identify the Julia object corresponding to the Kubernetes specification
- `kuber_obj`: instantiate a Julia object from for the supplied Kubernetes specification
- Helper methods for [accessing metrics](Metrics.md)
- `kuber_type`: identify the Julia type corresponding to a Kubernetes payload
- `kuber_obj`: instantiate a Julia object from the supplied Kubernetes specification
- `is_retryable`: whether a failure was transient — the classification Kuber's own retries use, for calls a consumer drives itself. Replaces `OpenAPI.Clients.is_request_interrupted`
- `Kuber.getpropertyat` / `Kuber.haspropertyat`: walk a path of field names, vector indices and open-struct keys, treating `ABSENT` as absent. Unexported replacements for the `OpenAPI.Clients` accessors
- `kuber_kind`: the Kubernetes kind of an object, read off the value rather than its type
- `kind_to_type`: the Julia type for a kind, optionally in a specific API version
- Helper methods for [accessing metrics](Metrics.md) (not available in this trial branch)

### Adding API groups Kuber does not ship:

Kuber ships generated clients for the API groups in a Kubernetes release's own
OpenAPI documents. Aggregated APIs (`metrics.k8s.io`), CRD-backed groups and
anything else specific to a cluster are not in there, and are plugged in at load
time instead:

```julia
module MyK8sGroups
using Kuber

include("K8sMetricsK8sIoV1beta1.jl") # the generated group modules
include("registry.jl") # generated: the six tables, which refer
# to those modules by name

__init__() = Kuber.register!(@__MODULE__)
end
```

`Kuber.register!` merges a generated layer's tables into Kuber's registry, after
which its kinds work with the ordinary verbs. It has to be called from
`__init__` — mutations made during precompilation do not persist — and it
validates the whole registration before merging any of it, so a rejected one
changes nothing. `Kuber.unregister!` undoes it. See the `Kuber.register!`
docstring for the table shapes and for how a kind name served by two groups
resolves.

### References:
- API conventions: https://github.com/kubernetes/community/blob/master/contributors/devel/api-conventions.md
Expand Down
139 changes: 66 additions & 73 deletions SupportedAPIVersions.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,70 @@
## Supported API Versions

The default API client included in this version of Kuber.jl supports the following API versions:
The generated client included in this version of Kuber.jl covers the following
Kubernetes API group versions, one module each:

- `admissionregistration`
- `admissionregistration_v1`
- `admissionregistration_v1beta1`
- `apiextensions`
- `apiextensions_v1`
- `apiextensions_v1beta1`
- `apiregistration`
- `apiregistration_v1`
- `apiregistration_v1beta1`
- `apis`
- `apps`
- `apps_v1`
- `apps_v1beta1`
- `apps_v1beta2`
- `auditregistration`
- `auditregistration_v1alpha1`
- `authentication`
- `authentication_v1`
- `authentication_v1beta1`
- `authorization`
- `authorization_v1`
- `authorization_v1beta1`
- `autoscaling`
- `autoscaling_v1`
- `autoscaling_v2beta1`
- `autoscaling_v2beta2`
- `batch`
- `batch_v1`
- `batch_v1beta1`
- `batch_v2alpha1`
- `certificates`
- `certificates_v1beta1`
- `coordination`
- `coordination_v1`
- `coordination_v1beta1`
- `core`
- `core_v1`
- `custom_metrics_v1beta1`
- `discovery`
- `discovery_v1beta1`
- `events`
- `events_v1beta1`
- `extensions`
- `extensions_v1beta1`
- `flowcontrolApiserver`
- `flowcontrolApiserver_v1alpha1`
- `karpenterSh_v1alpha5`
- `logs`
- `metrics_v1beta1`
- `networking`
- `networking_v1`
- `networking_v1beta1`
- `node`
- `node_v1alpha1`
- `node_v1beta1`
- `policy`
- `policy_v1beta1`
- `rbacAuthorization`
- `rbacAuthorization_v1`
- `rbacAuthorization_v1alpha1`
- `rbacAuthorization_v1beta1`
- `scheduling`
- `scheduling_v1`
- `scheduling_v1alpha1`
- `scheduling_v1beta1`
- `settings`
- `settings_v1alpha1`
- `storage`
- `storage_v1`
- `storage_v1alpha1`
- `storage_v1beta1`
- `version`
| Group version | Module |
| --- | --- |
| `v1` (core) | `K8sV1` |
| `apiextensions.k8s.io/v1` | `K8sApiextensionsK8sIoV1` |
| `apiregistration.k8s.io/v1` | `K8sApiregistrationK8sIoV1` |
| `apps/v1` | `K8sAppsV1` |
| `autoscaling/v1` | `K8sAutoscalingV1` |
| `autoscaling/v2` | `K8sAutoscalingV2` |
| `batch/v1` | `K8sBatchV1` |
| `certificates.k8s.io/v1` | `K8sCertificatesK8sIoV1` |
| `coordination.k8s.io/v1` | `K8sCoordinationK8sIoV1` |
| `discovery.k8s.io/v1` | `K8sDiscoveryK8sIoV1` |
| `events.k8s.io/v1` | `K8sEventsK8sIoV1` |
| `metrics.k8s.io/v1beta1` | `K8sMetricsK8sIoV1beta1` |
| `networking.k8s.io/v1` | `K8sNetworkingK8sIoV1` |
| `node.k8s.io/v1` | `K8sNodeK8sIoV1` |
| `policy/v1` | `K8sPolicyV1` |
| `rbac.authorization.k8s.io/v1` | `K8sRbacAuthorizationK8sIoV1` |
| `scheduling.k8s.io/v1` | `K8sSchedulingK8sIoV1` |
| `storage.k8s.io/v1` | `K8sStorageK8sIoV1` |

Generated from the OpenAPI v3 documents of **Kubernetes v1.35.4** — see
`gen/openapi_v1/specs/SPECS_ORIGIN` for the exact source and checksums.

`metrics.k8s.io/v1beta1` is the exception: it is served by metrics-server rather
than by the apiserver, so it is absent from the release-tag documents and was
captured from a live cluster instead (`gen/openapi_v1/specs/SPECS_CAPTURED`).
Its kinds are only addressable against a cluster that runs metrics-server.

API groups that are not here can be captured the same way and plugged in with
`Kuber.register!` without modifying Kuber; see the README. That includes
`custom.metrics.k8s.io`, which was captured and evaluated on 2026-08-15 and
deliberately left out — see `OpenAPIv1ConsumerGaps.md` C5 for what the document
turned out to look like.

Kubernetes schemas do not close their objects, so a client generated for one
minor tolerates fields *added* by a later server; only contract violations
(nulls where the document promises a value, changed shapes) bite, and GA APIs
rarely do that. Discovery skips any group version the server reports that is not
in the table above, with an informational log line under `verbose=true`.

`Kuber.ApiImpl.GROUP_MODULES` is this table at runtime.

### Not included

- **CRD-backed groups**, which belong to the deployment that defines them and are
registered with `Kuber.register!` rather than shipped here.
- **`custom.metrics.k8s.io`** — captured from a real adapter on 2026-08-15 and
left out on the evidence: its operations carry neither
`x-kubernetes-group-version-kind` nor `x-kubernetes-action`, and address
metrics through a three-variable path, so neither the registry emitter nor the
verb API can carry them without new work, and nothing in the consumer repos
calls the API. See
`OpenAPIv1ConsumerGaps.md` C5, and `gen/openapi_v1/reference-captures/` for the
document itself. The helpers `list_custom_metrics` /
`list_namespaced_custom_metrics` are implemented and exported, and resolve
`:MetricValue` against a group registered with `Kuber.register!`.
- Group versions no longer served by a modern API server (the `*beta*` and
`*alpha*` variants of apps, batch, extensions, settings, auditregistration and
so on, which the 0.2.x client shipped).

### Adding a group

Append it to `K8S_GROUPS` in `gen/openapi_v1/fetch_specs.sh` and rerun the
generation chain (`gen/openapi_v1/README.md`). Keep this file in sync.
Loading
Loading