Code generator for Jsonnet Kubernetes libraries.
This repository contains the generator code and relevant bits to generate the jsonnet libraries. It can generate libraries directly from OpenAPI spec v2 (Swagger), from CustomResourceDefinitions, or from JSON Schema.
docker pull ghcr.io/thisisibrahimd/k8s-gen:<version>Download pre-built binaries from GitHub Releases:
curl -sL https://github.com/thisisibrahimd/k8s/releases/latest/download/k8s-gen_linux_x86_64.tar.gz | tar xz
sudo mv k8s-gen /usr/local/bin/go install github.com/thisisibrahimd/k8s@latestNote: this installs the binary as k8s (module path name). Rename or symlink it to k8s-gen, or substitute k8s in the commands below.
Create a directory, ideally named after the project:
mkdir libs/cloudnative-pgCreate a config.json with specGenerator to auto-discover CRDs from GitHub:
{
"libName": "cloudnative-pg",
"description": "Generated Jsonnet library for CloudNativePG",
"specGenerator": {
"type": "github",
"repo": "https://github.com/cloudnative-pg/cloudnative-pg",
"crdPath": "config/crd/bases",
"prefix": "^io\\.cnpg\\.postgresql\\..*",
"dedupeCrds": true
},
"specs": []
}Then run:
k8s-gen generate k8s --config libs/cloudnative-pg/config.jsonNote: Output defaults to the config file's directory. Set outputDir in config to change this (relative to config file).
The generator will:
- Fetch tags from the GitHub repo
- Parse the Git tree to find CRD files in
crdPath - Skip consecutive versions whose CRD files are identical to the previous kept version
- Generate specs for each remaining version automatically
By default, auto-discovery skips duplicate CRD versions (dedupeCrds: true). Set dedupeCrds: false in specGenerator to generate a library for every version.
Alternatively, pin the exact versions to generate by adding a versions list to specGenerator. versions and versionLimit are mutually exclusive; when versions is provided, tag discovery is skipped and dedupeCrds is ignored. A target is generated for every version in the list.
The versionPrefix field (default "v") is prepended to each version in versions when resolving the GitHub ref/tag. Use "versionPrefix": "" if your versions already include the prefix (e.g. v1.27.0).
{
"libName": "cloudnative-pg",
"description": "Generated Jsonnet library for CloudNativePG",
"specGenerator": {
"type": "github",
"repo": "https://github.com/cloudnative-pg/cloudnative-pg",
"crdPath": "config/crd/bases",
"prefix": "^io\\.cnpg\\.postgresql\\..*",
"versions": ["1.27.0", "1.30.0"]
},
"specs": []
}GitHub Token: Set GITHUB_TOKEN environment variable to authenticate with GitHub. Without a token, you are limited to 60 requests/hour (unauthenticated rate limit). With a token, you get 5,000 requests/hour.
If you have the GitHub CLI installed, you can use it to get a token:
GITHUB_TOKEN=$(gh auth token) k8s-gen generate k8s --config libs/cloudnative-pg/config.jsonOr export it for the session:
export GITHUB_TOKEN=$(gh auth token)
k8s-gen generate k8s --config libs/cloudnative-pg/config.jsonIf you prefer to manually define versions and CRD URLs, omit specGenerator and populate specs directly:
{
"libName": "cloudnative-pg",
"description": "Generated Jsonnet library for CloudNativePG",
"specs": [
{
"output": "1.27.0",
"prefix": "^io\\.cnpg\\.postgresql\\..*",
"crds": [
"https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/v1.27.0/config/crd/bases/postgresql.cnpg.io_backups.yaml",
"https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/v1.27.0/config/crd/bases/postgresql.cnpg.io_clusters.yaml"
]
},
{
"output": "1.30.0",
"prefix": "^io\\.cnpg\\.postgresql\\..*",
"crds": [
"https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/v1.30.0/config/crd/bases/postgresql.cnpg.io_backups.yaml",
"https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/v1.30.0/config/crd/bases/postgresql.cnpg.io_clusters.yaml"
]
}
]
}Note: When using manual specs, set a per-spec prefix (regex) to filter which API groups are included; without a prefix, all groups found in the specs are included. Each spec must set either crds (a list of CRDs) or openapi (a single OpenAPI v2/Swagger spec) — they are mutually exclusive, and both accept URLs or local file paths.
| Feature | Auto-Discovery | Manual |
|---|---|---|
specGenerator |
Required | Omitted |
specs |
Empty [] |
Populated with versions/CRDs |
prefix |
At specGenerator level |
Not needed |
versionLimit |
Default 10 (mutually exclusive with versions) |
Not applicable |
versions |
Optional exact list of clean versions (without prefix) | Not applicable |
versionPrefix |
Default "v" (prepended to versions for the Git ref) |
Not applicable |
includeVersions |
Default ^v?\d+\.\d+\.\d+$ (regex filter on discovered tag names) |
Not applicable |
dedupeCrds |
Default true (skips duplicate CRD versions; ignored when versions is set) |
Not applicable |
| Maintenance | Automatic on new releases | Manual updates required |
Create a folder in libs/:
mkdir libs/<name>Create a config.json in the new folder. This example renders a lib from CRDs:
{
"libName": "<name>",
"description": "Generated Jsonnet library for <name>",
"specGenerator": {
"type": "github",
"repo": "https://github.com/<owner>/<name>",
"crdPath": "config/crd/bases",
"prefix": "^<prefix>\\.<name>\\..*",
"dedupeCrds": false
},
"specs": []
}Build the binary first (the Makefile expects ./k8s-gen in the repo root):
go build -o k8s-gen .Generate the library:
$ make libs/<name>Pass VERSIONS to regenerate only specific versions:
$ make libs/<name> VERSIONS="1.27.0"Or run the binary directly:
$ k8s-gen generate k8s --config libs/<name>/config.jsonAppend version outputs as positional arguments to regenerate only specific versions, e.g. k8s-gen generate k8s --config libs/<name>/config.json 1.27.0. Use the global --debug flag for verbose logging.
You can also generate a Jsonnet library directly from a JSON Schema file. This is useful for configuration files such as .golangci.yml, package.json, or any other schema-backed document.
Create a small JSON Schema. For example, config.schema.json:
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Config",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"enabled": {
"type": "boolean"
}
}
}Run the generator:
$ k8s-gen generate jsonschema \
--schema ./config.schema.json \
--output ./config.libsonnetThe generated config.libsonnet looks like:
{
withName(name): {
name: name,
},
withEnabled(enabled): {
enabled: enabled,
},
}Nested objects produce nested functions and additive objects (+:), so you can compose configuration pieces:
// example from a schema with nested properties
{
server: {
withPort(port): {
server+: {
port: port,
},
},
},
}| Flag | Required | Description |
|---|---|---|
--schema |
Yes | Path or URL to the JSON Schema file. |
--output |
No | Output file path. If omitted, prints to stdout. |
Array properties generate three function types:
{
// Replaces the entire array (wraps single values in a list automatically)
withTags(tags): {
tags: if std.isArray(v=tags) then tags else [tags],
},
// Appends to the existing array
withTagsMixin(tags): {
tags+: if std.isArray(v=tags) then tags else [tags],
},
// Transforms each element using a function
mapTags(f): {
tags: std.map(f, super.tags),
},
}For arrays of objects, nested helpers are also generated for constructing individual items:
{
containers: {
withName(name): { containers+: { name: name } },
withImage(image): { containers+: { image: image } },
},
}If the array items have a "name" field, an additional mapXyzByName helper is generated for targeted updates:
{
// Transforms only the element where item.name matches
mapContainersByName(name, transformFunc): {
containers: [if c.name == name then transformFunc(c) else c for c in super.containers],
},
}Example usage:
local lib = import './config.libsonnet';
// Add containers and patch one by name
local base = lib
+ lib.withContainers([
{ name: 'app', image: 'myapp:v1' },
{ name: 'sidecar', image: 'busybox' },
])
+ lib.mapContainersByName('app', function(c)
c + { image: 'myapp:v2' }
);
// Transform all tags
base + lib.mapTags(function(t) 'release-' + t)- Schema composition with
anyOf,oneOf, or external$refis limited. - Empty object properties (
{}) may appear when the schema declares an object but provides no properties. - Nested map helpers (
mapXyzinside amapXyztransform) are not supported. The helpers usesuperwhich only works in object merge contexts. For nested arrays, usestd.mapdirectly:lib.mapContainers(function(c) c { ports: std.map(function(p) p { containerPort: 8080 }, super.ports), } )
Because the generator only creates the most minimal yet functional code, more
sophisticated utilities like constructors (deployment.new(name, replicas, containers), etc) are not created.
For that, there are two methods for extending:
The custom/
directory contains a set of .libsonnet files, that are automatically merged
with the generated result in main.libsonnet, so they become part of the
exported API.
For example the patches in libs/k8s:
libs/k8s/
├── config.json # Config to generate the k8s jsonnet libraries
└── custom
└── core
├── apps.libsonnet # Constructors for `apps/v1` (daemonSet, deployment, statefulSet), ported from `ksonnet-gen` and `kausal.libsonnet`
├── autoscaling.libsonnet # Extends `autoscaling/v1` and `autoscaling/v2`
├── batch.libsonnet # Constructors for `batch/v1` (cronJob), ported from `kausal.libsonnet`
├── core.libsonnet # Constructors for `core/v1`, ported from `ksonnet-gen` and `kausal.libsonnet`
├── list.libsonnet # Adds `core.v1.List`
├── rbac.libsonnet # Adds helper functions to rbac objects
└── volumeMounts.libsonnet # Adds helper functions to mount volumes
A reference for these must also be made in the config.json:
{
"libName": "k8s",
"specs": [
{
"output": "<version>",
"patchDir": "custom/core"
}
]
}Extensions serve a similar purpose as custom/ patches, but are not
automatically applied. However, they are still part of the final artifact, but
need to added by the user themselves.
Extensions can be applied as so:
(import "github.com/jsonnet-libs/k8s-libsonnet/1.21/main.libsonnet")
+ (import "github.com/jsonnet-libs/k8s-libsonnet/extensions/<name>.libsonnet")A reference for these must also be made in the config.json:
{
"libName": "k8s",
"specs": [
{
"output": "<version>",
"extensionDir": "extensions/core"
}
]
}