create static docs site with module options

This commit is contained in:
Bryton Hall 2022-08-28 15:22:43 -04:00 committed by GitHub
parent e3127e8c14
commit e75b801a31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 448 additions and 19 deletions

View file

@ -1,7 +1,9 @@
name: CI
name: tests
on:
pull_request:
push:
jobs:
tests:
runs-on: ubuntu-latest

43
.github/workflows/pages.yml vendored Normal file
View file

@ -0,0 +1,43 @@
name: pages
on:
push:
branches:
- main
pull_request:
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- uses: actions/checkout@v3
with:
submodules: true
fetch-depth: 0
- uses: cachix/install-nix-action@v16
with:
install_url: https://github.com/numtide/nix-unstable-installer/releases/download/nix-2.8.0pre20220311_d532269/install
extra_nix_config: |
experimental-features = nix-command flakes
allow-import-from-derivation = true
- name: build
run: nix run '.#docs' -- --minify
- name: upload
uses: actions/upload-pages-artifact@v1
with:
path: './docs/public'
- name: deploy
id: deployment
uses: actions/deploy-pages@v1
permissions:
contents: read
pages: write
id-token: write

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "docs/themes/hugo-book"]
path = docs/themes/hugo-book
url = https://github.com/alex-shpak/hugo-book

View file

@ -1,3 +1,6 @@
{
"nix.formatterPath": "alejandra"
"nix.formatterPath": "alejandra",
"search.exclude": {
"docs/themes": true
}
}

View file

@ -38,3 +38,11 @@ To support a new Kubernetes version:
## Tests
Tests are executed through GitHub actions; see the [workflow definition](../kubenix/.github/workflows/ci.yml) for commands.
## Docs
Build and serve the static site
nix run '.#docs' serve
which will be available at <http://localhost:1313>.

View file

@ -3,7 +3,7 @@
Kubernetes resource management with Nix
<p align="center" style="margin: 2em auto;">
<img src="./docs/logo.svg" alt="nixos logo in kubernetes blue" width="350"/>
<img src="./docs/static/logo.svg" alt="nixos logo in kubernetes blue" width="350"/>
</p>
> **WARN**: this is a work in progress, expect breaking changes
@ -49,7 +49,7 @@ See [./docs/examples](./docs/examples) for more.
## CLI
> **NOTE**: this is a WIP CLI which currently reads the `k8s` attribute on a local flake
> **NOTE**: this is a WIP CLI which currently reads the `k8s` package on a local flake
Render all resources with

5
docs/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
public/
resources/
*.lock
options.json
content/modules/*.md

91
docs/assets/_custom.scss Normal file
View file

@ -0,0 +1,91 @@
$foreground-color: #f8f8f2;
$background-color: #333333;
// create an arrow rotated by $angle
@mixin arrow($angle) {
content: "";
display: inline-block;
position: fixed;
// size
padding: 3px;
// position
margin: 18px 15px;
border: solid $background-color;
border-width: 0 2px 2px 0;
transform: rotate($angle);
-webkit-transform: rotate($angle);
}
details {
padding: 0rem 1rem !important;
margin: 1rem;
padding: 0 !important;
> summary {
padding: 0rem !important;
margin: 0 !important;
// do not show arrow bullet point
list-style: none;
::-webkit-details-marker {
display: none;
}
// replace builtin arrows with custom ones
pre {
display: inline-block;
width: 100%;
margin: 0 !important;
padding-left: 42px !important;
vertical-align: middle;
}
&:before {
@include arrow(-45deg);
}
}
&[open] summary:before {
@include arrow(45deg);
}
table, tbody {
// fill entire width
width: 100% !important;
display: table;
margin: 0 !important;
tr {
// make code blocks a little smaller
pre {
margin: 0 !important;
padding: 0.2rem !important;
&.highlight {
color: $foreground-color;
background-color: $background-color;
};
code {
vertical-align: text-bottom;
padding-left: 0.2rem !important;
}
}
// field name
td:first-child {
font-weight: bold;
font-size: 90%;
width: 8em;
min-width: 8em;
max-width: 8em;
word-break: break-all;
}
}
}
}
aside.book-menu span {
font-weight: bold;
}

10
docs/config.toml Normal file
View file

@ -0,0 +1,10 @@
title = "kubenix"
theme = "hugo-book"
baseURL = "https://kubenix.org/"
[params]
BookLogo = "logo.svg"
BookRepo = "https://github.com/hall/kubenix"
BookSection = '*'
# most pages at this time are auto-generated so editing doesn't make sense
# BookEditPath = 'edit/main/docs'

1
docs/content/_index.md Symbolic link
View file

@ -0,0 +1 @@
../../README.md

View file

0
docs/data/.gitkeep Normal file
View file

122
docs/default.nix Normal file
View file

@ -0,0 +1,122 @@
# adapted from: https://discourse.nixos.org/t/franken-script-to-generate-nixos-options-docs-with-custom-modules/1674/4
{
pkgs,
options,
}: let
extraSources = [];
lib = pkgs.lib;
optionsListVisible =
lib.filter (opt: opt.visible && !opt.internal)
(lib.optionAttrSetToDocList options);
# Replace functions by the string <function>
substFunction = x:
if builtins.isAttrs x
then lib.mapAttrs (name: substFunction) x
else if builtins.isList x
then map substFunction x
else if lib.isFunction x
then "<function>"
else if isPath x
then toString x
else x;
isPath = x: (builtins.typeOf x) == "path";
optionsListDesc = lib.flip map optionsListVisible (
opt:
opt
// {
description = let
attempt = builtins.tryEval opt.description;
in
if attempt.success
then attempt.value
else "N/A";
declarations = map stripAnyPrefixes opt.declarations;
}
// lib.optionalAttrs (opt ? example) {
example = substFunction opt.example;
}
// lib.optionalAttrs (opt ? default) {
default = substFunction opt.default;
}
// lib.optionalAttrs (opt ? type) {
type = substFunction opt.type;
}
// lib.optionalAttrs
(opt ? relatedPackages && opt.relatedPackages != [])
{
relatedPackages = genRelatedPackages opt.relatedPackages;
}
);
genRelatedPackages = packages: let
unpack = p:
if lib.isString p
then {name = p;}
else if lib.isList p
then {path = p;}
else p;
describe = args: let
title = args.title or null;
name = args.name or (lib.concatStringsSep "." args.path);
path = args.path or [args.name];
package =
args.package
or (lib.attrByPath path
(throw
"Invalid package attribute path '${toString path}'")
pkgs);
in
"<listitem>"
+ "<para><literal>${lib.optionalString (title != null)
"${title} aka "}pkgs.${name} (${package.meta.name})</literal>"
+ lib.optionalString (!package.meta.available)
" <emphasis>[UNAVAILABLE]</emphasis>"
+ ": ${package.meta.description or "???"}.</para>"
+ lib.optionalString (args ? comment)
"\n<para>${args.comment}</para>"
+ lib.optionalString (package.meta ? longDescription)
"\n<programlisting>${package.meta.longDescription}"
+ "</programlisting>"
+ "</listitem>";
in "<itemizedlist>${lib.concatStringsSep "\n" (map (p:
describe (unpack p))
packages)}</itemizedlist>";
optionLess = a: b: let
ise = lib.hasPrefix "enable";
isp = lib.hasPrefix "package";
cmp =
lib.splitByAndCompare ise lib.compare
(lib.splitByAndCompare isp lib.compare lib.compare);
in
lib.compareLists cmp a.loc b.loc < 0;
prefixesToStrip = map (p: "${toString p}/") ([../../..] ++ extraSources);
stripAnyPrefixes = lib.flip (lib.fold lib.removePrefix) prefixesToStrip;
###############################################################################
# This is the REAL meat of what we were after.
# Output this however you want.
optionsList = lib.sort optionLess optionsListDesc;
optionsJSON = builtins.unsafeDiscardStringContext (builtins.toJSON
(builtins.listToAttrs (map
(o: {
name = o.name;
value = removeAttrs o [
# Select the fields you want to drop here:
"name"
"visible"
"internal"
"loc"
"readOnly"
];
})
optionsList)));
in
pkgs.writeText "options.json" optionsJSON

View file

@ -0,0 +1,40 @@
{{ $name := index . "name" }}
{{ $option := index . "option" }}
{{ $repo := index . "repo" }}
{{ $path := path.Join (after 2 (split (index $option.declarations 0) "/")) }}
<details id="{{ $name }}">
<summary>
<pre>{{ $name }}</pre>
</summary>
<table>
<tr>
<td>Description</td>
<td>{{ $option.description }}</td>
</tr>
<tr>
<td>Type</td>
<td><pre>{{ $option.type }}</pre></td>
</tr>
<tr>
<td>Default</td>
<td>{{ partial "highlight" $option.default }}</td>
</tr>
{{ with $option.example }}
<tr>
<td>Example</td>
<td>{{ partial "highlight" . }}</td>
</tr>
{{ end }}
<tr>
<td>Declared in</td>
<td><a href="{{ $repo }}/blob/main/{{ $path }}" target="_blank">{{ $path }}</a></td>
</tr>
</table>
</details>

View file

@ -0,0 +1,24 @@
<script>
window.onload = function(){
// open details of option in URL fragment
document.getElementById(decodeURI(window.location.hash.substring(1)))?.setAttribute("open", "");
var options = document.getElementsByTagName('details');
for(var i = 0; i < options.length; i++) {
options[i].getElementsByTagName("summary")[0].onclick = function() {
// set URL fragment to option id
window.location.hash = this.parentElement.id;
// is current panel open, return immediately as it's just being closed
if (this.parentElement.hasAttribute("open")) return;
// collapse all other options
for(var j = 0; j < options.length; j++) {
options[j].removeAttribute("open");
}
};
}
};
</script>

View file

@ -0,0 +1 @@
<link rel="icon" href="/logo.svg">

View file

@ -0,0 +1,10 @@
{{ $text := . }}
{{/* if text is a multiline string add nix's double single-quotes */}}
{{ if in $text "\n" }}
{{ $text = print "''\n " (strings.TrimSuffix " " (replace $text "\n" "\n ") ) "''" }}
{{ else }}
{{ $text = jsonify $text }}
{{ end }}
<pre class="highlight"><code>{{ $text }}</code></pre>

View file

@ -0,0 +1,22 @@
{{ $module := $.Page.File.BaseFileName }}
{{ $repo := $.Site.Params.BookRepo }}
{{ range $name, $option := .Site.Data.options }}
{{/* some module options are nested under others */}}
{{ if and (hasPrefix $name "kubernetes.helm.") }}
{{ if (eq $module "helm") }}
{{ partial "details" (dict "name" $name "option" $option "repo" $repo) }}
{{ end }}
{{ else }}
{{/* only show options for the current module */}}
{{/* but don't list _all_ kubernetes resources */}}
{{ if and
(not (hasPrefix $name "kubernetes.api.resources."))
(hasPrefix $name (print $module "."))
}}
{{ partial "details" (dict "name" $name "option" $option "repo" $repo) }}
{{ end }}
{{ end }}
{{ end }}

View file

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Before After
Before After

1
docs/themes/hugo-book vendored Submodule

@ -0,0 +1 @@
Subproject commit 317ccae23b73f5e49d7341ad57227bd64a89ab38

View file

@ -84,6 +84,7 @@
k9s
kube3d
kubie
hugo
];
packages = [
(pkgs.writeShellScriptBin "evalnix" ''
@ -100,6 +101,28 @@
formatter = pkgs.treefmt;
apps = {
docs = inputs.flake-utils.lib.mkApp {
drv = pkgs.writeShellScriptBin "gen-docs" ''
set -eo pipefail
# generate json object of module options
nix build '.#docs' -o ./docs/data/options.json
# remove all old module pages
rm ./docs/content/modules/*.md || true
# create a page for each module in hugo
for mod in ${builtins.toString (builtins.attrNames self.nixosModules.kubenix)}; do
[[ $mod == "base" ]] && mod=kubenix
[[ $mod == "k8s" ]] && mod=kubernetes
[[ $mod == "submodule"* ]] && continue
echo "&nbsp; {{< options >}}" > ./docs/content/modules/$mod.md
done
# build the site
cd docs && ${pkgs.hugo}/bin/hugo $@
'';
};
generate = inputs.flake-utils.lib.mkApp {
drv = pkgs.writeShellScriptBin "gen-modules" ''
set -eo pipefail
@ -120,8 +143,23 @@
// {
cli = pkgs.callPackage ./pkgs/kubenix.nix {};
default = self.packages.${system}.cli;
docs = import ./docs {
inherit pkgs;
options =
(self.evalModules.${system} {
modules =
builtins.attrValues (builtins.removeAttrs
# the submodules module currently doesn't evaluate:
# error: No module found name/latest
# not sure how important that documentation is a this time
self.nixosModules.kubenix ["submodule" "submodules"]);
})
.options;
};
}
// import ./jobs {inherit pkgs;};
// import ./jobs {
inherit pkgs;
};
checks = let
wasSuccess = suite:
@ -139,7 +177,8 @@
# TODO: access "success" derivation with nice testing utils for nice output
nginx-example = wasSuccess (mkExamples {}).nginx-deployment.config.testing;
}
// builtins.listToAttrs (builtins.map (v: {
// builtins.listToAttrs (builtins.map
(v: {
name = "test-k8s-${builtins.replaceStrings ["."] ["_"] v}";
value = wasSuccess (mkK8STests {k8sVersion = v;});
})

View file

@ -126,11 +126,12 @@ with lib; let
};
indexOf = lst: value:
head (filter (v: v != -1) (imap0 (i: v:
if v == value
then i
else -1)
lst));
head (filter (v: v != -1) (imap0
(i: v:
if v == value
then i
else -1)
lst));
compareVersions = ver1: ver2: let
getVersion = substring 1 10;
@ -302,7 +303,7 @@ in {
type = types.submodule {
imports =
[
(./generated + ''/v'' + cfg.version + ".nix")
./generated/v${cfg.version}.nix
apiOptions
]
++ customResourceOptions;

View file

@ -1,9 +1,9 @@
{ jq
, kubectl
, kubernetes-helm
, nix
, writeShellScriptBin
,
{
jq,
kubectl,
kubernetes-helm,
nix,
writeShellScriptBin,
}:
writeShellScriptBin "kubenix" ''
set -Eeuo pipefail
@ -53,7 +53,7 @@ writeShellScriptBin "kubenix" ''
render)
cat $MANIFESTS;;
*)
${kubectl}/bin/kubectl $@ -f $MANIFESTS;;
${kubectl}/bin/kubectl $@ -f $MANIFESTS || true;;
esac
}

View file

@ -1,3 +1,6 @@
[global]
excludes = ["./docs/themes/*"]
[formatter.nix]
command = "alejandra"
includes = ["*.nix"]