If you template Kubernetes manifests with ytt, you probably have a data values schema. It starts small: a handful of keys with a #@schema/desc here and a #@schema/validation there. Then it grows. Ours, the deployment.yaml schema for every microservice at Dinero, ended up with a few hundred nested maps. At that size nobody opens the schema file to find out what a key does. They ask a colleague, or copy a value from another service and hope.
The frustrating part is that the answers were all in the schema. ytt's schema annotations carry a title, a description, a default, examples and validation rules for every value. We just had no way to read them that didn't involve scrolling through YAML.
So I built ytt-schema-docs, a small build step that renders a ytt data values schema as a static site. Here is the example site, generated from a deployment-style schema.
What you get
Every map in the schema becomes a table with one row per property: the name and title, the type as a badge, the default and constraints, and the description. Nested maps get their own table, and the property name links to it. Arrays of maps show up as name[].
A few things fall out of the annotations you already write:
-
#@schema/validation min=1, max=65535renders as Minimum and Maximum chips.one_of=renders each allowed value as a chip.min_len=andmax_len=work the same for strings and arrays. - A property is marked Required when its default fails its own validation. That is exactly the pattern the ytt docs recommend for required values: an empty string with
min_len=1, a0withmin=1, or a""against aone_oflist. A#@schema/nullableproperty defaults tonull, which ytt does not validate, so it is never marked required. -
#@schema/examplesrenders as a labeled example under the type. - HTML in descriptions is allowed, so
<strong>and<br/>in a#@schema/descdo what you expect. - Giving a property the title
__REMOVE_ME__hides it and everything nested under it, for internal settings that should not be in the public docs.
Around the tables there is a sidebar listing every map, nested by depth, with a search box that filters on map and property names. The sidebar highlights the map you are reading as you scroll. The site follows your system's light or dark mode and has a toggle, and the tables stack into blocks on a phone.
How it works
The trick is that ytt can export its schema as OpenAPI v3:
ytt --file schema.yaml --data-values-schema-inspect --output openapi-v3
That gives you a standard document with properties, default, minimum, enum and so on, plus the ytt-specific x-example-description. From there it is ordinary web tooling:
- A tiny ytt overlay patches the
infoblock with the site title, description and version. - openapi-generator renders the document with a custom template. The template is little more than a page shell that embeds each schema as JSON.
- A browser script dereferences the
$refs, walks the maps depth first in schema order, and builds the tables and the sidebar with plain DOM APIs. Tailwind provides the styling.
Because it is a build step and not a ytt extension, it works with any ytt version that has the OpenAPI export, and it does not care how your templates are organised. Point it at the schema file and run npm run build.
Using it on your own schema
git clone https://github.com/Baune8D/ytt-schema-docs
cd ytt-schema-docs
npm install
# replace schema.yaml with yours, or change the path in package.json
npm run build
The site lands in dist as a single index.html with a script and a stylesheet next to it. Host it anywhere static files go. We serve ours from a small Node container next to the deploy CLI that owns the schema, and CI regenerates the OpenAPI export before every image build so the docs can never drift from the templates.
You need Node, ytt on your PATH, and a Java runtime for openapi-generator.
What I would like to hear
If you use ytt schemas, I am curious what you would want rendered differently, and whether the required-value rule matches how you write validations. Issues and pull requests are welcome on GitHub.
Top comments (0)