Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/actions/generate-crd-api-reference/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# generate-crd-api-reference

Render a CRD API-reference markdown page from a **local** path of an operator's
Go types using [`crd-ref-docs`](https://github.com/elastic/crd-ref-docs).
Repo-agnostic; bundles its own rendering templates and post-processing fixups.
It does **not** clone — checking out sources is the caller's job.

## Model

The generator runs in the **operator** repo's CI: it reads the operator's own
Go types locally (no cross-repo read of private operator code), renders the
reference, and opens a PR in the **docs** repo. The only cross-repo access
needed is **write** to the docs repo, via `DOCS_TOKEN` (the operator's existing
broad-access token, e.g. `STAKATER_AB_REPOS`).

## Turnkey usage (reusable workflow → PR in the docs repo)

Add `crd-ref-docs.yaml` (your operator's ignore rules) to the **operator** repo
and a caller workflow:

```yaml
name: Generate API Reference
on:
release:
types: [published]
workflow_dispatch:
jobs:
generate:
uses: stakater/.github/.github/workflows/generate-api-reference.yaml@main
with:
docs_repo: stakater/<operator>-docs
source_path: api/<group>/<version>
config_file: crd-ref-docs.yaml
output_file: content/reference/api.md
secrets:
DOCS_TOKEN: ${{ secrets.STAKATER_AB_REPOS }}
```

## Direct action usage (render only, custom delivery)

Check out whatever you need, then point the action at a local `source_path`:

```yaml
- uses: actions/checkout@v5 # the operator repo — has the Go types + config
- uses: stakater/.github/.github/actions/generate-crd-api-reference@main
with:
source_path: api/<group>/<version>
config_file: crd-ref-docs.yaml
output_file: content/reference/api.md
```

The action renders `output_file` from the local `source_path`; committing,
pushing, and PR policy are up to your workflow.

## Run locally

```bash
git clone --depth 1 https://github.com/stakater/.github /tmp/stakater-github
ACTION=/tmp/stakater-github/.github/actions/generate-crd-api-reference

# from your operator repo root
SOURCE_PATH="$PWD/api/<group>/<version>" \
CONFIG_FILE="$PWD/crd-ref-docs.yaml" \
OUTPUT_FILE="$PWD/api.md" \
bash "$ACTION/generate-api-reference.sh"
```

Needs `go` and `perl` on PATH. Templates resolve automatically (sibling of the
script).

## Notes

- `source_path` may point at a single group (`api/group/v1alpha1`) or a parent
(`api/`) — `crd-ref-docs` recurses, so multi-group operators work unchanged.
- Two generic post-processing fixups keep output `mkdocs build --strict`-clean:
broken `map[string]Type` anchors are stripped to plain text, and a
`<!-- markdownlint-disable -->` line is prepended.
- Scratch (`.work/` locally, `RUNNER_TEMP` in CI) is never committed.
64 changes: 64 additions & 0 deletions .github/actions/generate-crd-api-reference/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Generate CRD API Reference
description: >
Render a CRD API-reference markdown page from a LOCAL path of an operator's Go
types using crd-ref-docs. Repo-agnostic; bundles its own rendering templates.
Checkout of source repos is the caller's responsibility — this action does not
clone.

inputs:
source_path:
description: "Path (in the checkout) to the API types (e.g. api/group/v1alpha1, or api/ for all groups)."
required: true
config_file:
description: "Path (in the checkout) to crd-ref-docs.yaml."
required: false
default: "crd-ref-docs.yaml"
output_file:
description: "Path (in the checkout) to write the generated markdown."
required: false
default: "content/reference/api.md"
crd_ref_docs_version:
description: "crd-ref-docs version to install."
required: false
default: "v0.3.0"
go_version:
description: "Go version used to build crd-ref-docs."
required: false
default: "1.26"
working_directory:
description: "Directory that source_path/config_file/output_file are relative to."
required: false
default: "."

outputs:
output_file:
description: "Absolute path to the generated markdown file."
value: ${{ steps.generate.outputs.output_file }}

runs:
using: composite
steps:
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version: ${{ inputs.go_version }}

- name: Generate API reference
id: generate
shell: bash
working-directory: ${{ inputs.working_directory }}
env:
SOURCE_PATH_REL: ${{ inputs.source_path }}
CRD_REF_DOCS_VERSION: ${{ inputs.crd_ref_docs_version }}
CONFIG_FILE_REL: ${{ inputs.config_file }}
OUTPUT_FILE_REL: ${{ inputs.output_file }}
ACTION_PATH: ${{ github.action_path }}
run: |
set -euo pipefail
export SOURCE_PATH="$(pwd)/$SOURCE_PATH_REL"
export CONFIG_FILE="$(pwd)/$CONFIG_FILE_REL"
export OUTPUT_FILE="$(pwd)/$OUTPUT_FILE_REL"
export WORK_DIR="${RUNNER_TEMP:-$(pwd)/.work}/crd-ref"
# Script + templates live beside the action.
"$ACTION_PATH/generate-api-reference.sh"
echo "output_file=$OUTPUT_FILE" >> "$GITHUB_OUTPUT"
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{{- /*
Render types in hierarchical order: each container followed by the types it
contains. Root kinds first in declared order; any leftover types not
reached by traversal at the end, alphabetical.
*/ -}}

{{- define "renderHierarchy" -}}
{{- $args := . -}}
{{- $type := index $args 0 -}}
{{- $visited := index $args 1 -}}
{{- $typeNames := index $args 2 -}}
{{- $allTypes := index $args 3 -}}
{{- if not (hasKey $visited $type.Name) }}
{{- $_ := set $visited $type.Name true }}

{{ template "type" $type }}
{{- range $type.Members }}
{{- if .Type }}
{{- $memberType := .Type }}
{{- if not (hasKey $typeNames $memberType.Name) }}
{{- if $memberType.UnderlyingType }}{{- $memberType = $memberType.UnderlyingType }}{{- end }}
{{- if and $memberType $memberType.UnderlyingType }}
{{- if not (hasKey $typeNames $memberType.Name) }}
{{- $memberType = $memberType.UnderlyingType }}
{{- end }}
{{- end }}
{{- end }}
{{- if and $memberType (hasKey $typeNames $memberType.Name) }}
{{- $resolved := index $allTypes $memberType.Name }}
{{- template "renderHierarchy" (list $resolved $visited $typeNames $allTypes) }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}
{{- end -}}

{{- define "gvDetails" -}}
{{- $gv := . -}}

## {{ $gv.GroupVersionString }}

{{ $gv.Doc }}

{{- if $gv.Kinds }}
### Resource Types
{{- range $gv.SortedKinds }}
- {{ $gv.TypeForKind . | markdownRenderTypeLink }}
{{- end }}
{{ end }}

{{- $typeNames := dict -}}
{{- range $gv.SortedTypes -}}
{{- $_ := set $typeNames .Name true -}}
{{- end -}}

{{- $visited := dict -}}

{{ range $gv.SortedKinds }}
{{- template "renderHierarchy" (list ($gv.TypeForKind .) $visited $typeNames $gv.Types) }}
{{- end }}

{{- range $gv.SortedTypes }}
{{- if not (hasKey $visited .Name) }}
{{- template "renderHierarchy" (list . $visited $typeNames $gv.Types) }}
{{- end }}
{{- end }}

{{- end -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{{- define "gvList" -}}
{{- $groupVersions := . -}}

# API Reference

## Packages
{{- range $groupVersions }}
- {{ markdownRenderGVLink . }}
{{- end }}

{{ range $groupVersions }}
{{ template "gvDetails" . }}
{{ end }}

{{- end -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{{- define "type" -}}
{{- $type := . -}}
{{- if markdownShouldRenderType $type -}}

#### {{ $type.Name }}

{{ if $type.IsAlias }}_Underlying type:_ _{{ markdownRenderTypeLink $type.UnderlyingType }}_{{ end }}

{{ $type.Doc }}

{{ if $type.Validation -}}
_Validation:_
{{- range $type.Validation }}
- {{ . }}
{{- end }}
{{- end }}

{{ if $type.References -}}
_Appears in:_
{{- range $type.SortedReferences }}
- {{ markdownRenderTypeLink . }}
{{- end }}
{{- end }}

{{ if $type.Members -}}
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
{{ if $type.GVK -}}
| `apiVersion` _string_ | `{{ $type.GVK.Group }}/{{ $type.GVK.Version }}` | | |
| `kind` _string_ | `{{ $type.GVK.Kind }}` | | |
{{ end -}}

{{ range $type.Members -}}
| `{{ .Name }}` _{{ markdownRenderType .Type }}_ | {{ template "type_members" . }} | {{ markdownRenderDefault .Default }} | {{ range .Validation -}} {{ markdownRenderFieldDoc . }} <br />{{ end }} |
{{ end -}}

{{ end -}}

{{ if $type.EnumValues -}}
| Field | Description |
| --- | --- |
{{ range $type.EnumValues -}}
| `{{ .Name }}` | {{ markdownRenderFieldDoc .Doc }} |
{{ end -}}
{{ end -}}


{{- end -}}
{{- end -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{- define "type_members" -}}
{{- $field := . -}}
{{- if eq $field.Name "metadata" -}}
Refer to Kubernetes API documentation for fields of `metadata`.
{{- else -}}
{{ markdownRenderFieldDoc $field.Doc }}
{{- end -}}
{{- end -}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
set -euo pipefail

# Generic CRD API-reference renderer. Renders markdown from a LOCAL path of Go
# API types using crd-ref-docs, then applies generic post-processing. Checkout
# of any source repos is the caller's responsibility — this script never clones.
# Driven entirely by env vars so it can run from a composite action, a reusable
# workflow, or locally.

# --- Required ---
: "${SOURCE_PATH:?SOURCE_PATH is required (local path to the API types, e.g. api/group.example.com/v1alpha1, or api/ for all groups)}"
: "${OUTPUT_FILE:?OUTPUT_FILE is required (absolute path to the markdown to write)}"
: "${CONFIG_FILE:?CONFIG_FILE is required (absolute path to crd-ref-docs.yaml)}"

# --- Optional (with defaults) ---
CRD_REF_DOCS_VERSION="${CRD_REF_DOCS_VERSION:-v0.3.0}"

# Templates ALWAYS resolved relative to this script so they travel with it.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TEMPLATES_DIR="${TEMPLATES_DIR:-$SCRIPT_DIR/crd-ref-templates}"

# Scratch area (tool binary only); never committed.
WORK_DIR="${WORK_DIR:-$PWD/.work}"
LOCALBIN="$WORK_DIR/bin"
CRD_REF_DOCS="$LOCALBIN/crd-ref-docs"

mkdir -p "$LOCALBIN" "$(dirname "$OUTPUT_FILE")"

# 1. Install crd-ref-docs (pinned)
if [ ! -x "$CRD_REF_DOCS" ]; then
echo "Installing crd-ref-docs $CRD_REF_DOCS_VERSION..."
GOBIN="$LOCALBIN" go install "github.com/elastic/crd-ref-docs@$CRD_REF_DOCS_VERSION"
fi

# 2. Generate the reference from the local source path
"$CRD_REF_DOCS" \
--source-path="$SOURCE_PATH" \
--config="$CONFIG_FILE" \
--templates-dir="$TEMPLATES_DIR" \
--renderer=markdown \
--output-path="$OUTPUT_FILE"

# 3. Post-process (generic fixups for mkdocs strict builds)
# 3a. crd-ref-docs renders nested `map[string]Type` values as a link whose
# anchor contains brackets (`[map[string]X](#map[string]x)`) — an invalid
# anchor that fails `mkdocs build --strict`. Strip the link, keep the text.
perl -i -pe 's/\[(map\[string\][A-Za-z0-9]+)\]\(#map\[string\][a-z0-9]+\)/$1/g' "$OUTPUT_FILE"
# 3b. Silence markdownlint on the generated file.
sed -i '1i <!-- markdownlint-disable -->' "$OUTPUT_FILE"

echo "Wrote $OUTPUT_FILE (SOURCE_PATH=$SOURCE_PATH)"
Loading