add examples to docs site

This commit is contained in:
Bryton Hall 2022-08-29 02:04:47 -04:00 committed by GitHub
parent 53adf2b3b7
commit a76ddefe1c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 328 additions and 119 deletions

View file

@ -3,6 +3,8 @@ name: tests
on: on:
pull_request: pull_request:
push: push:
branches:
- main
jobs: jobs:
tests: tests:

View file

@ -6,7 +6,6 @@ on:
- main - main
paths: paths:
- 'docs/**' - 'docs/**'
pull_request:
jobs: jobs:
deploy: deploy:

View file

@ -46,3 +46,11 @@ Build and serve the static site
nix run '.#docs' serve nix run '.#docs' serve
which will be available at <http://localhost:1313>. which will be available at <http://localhost:1313>.
### Examples
Examples are written at [./docs/content/examples](./docs/content/examples) and are (or will, really) also used as tests which can be executed with
nix flake check
In general, that just means: don't directly put nix snippets into a markdown doc; instead use the shortcode provided to pull them in so they don't have to be parsed out for testing.

View file

@ -45,7 +45,7 @@ Or, if you're not using flakes, a `default.nix` file (build with `nix-build`):
Either way the JSON manifests will be written to `./result`. Either way the JSON manifests will be written to `./result`.
See [./docs/examples](./docs/examples) for more. See the [examples](/examples/pod) for more.
## CLI ## CLI

1
docs/.gitignore vendored
View file

@ -3,3 +3,4 @@ resources/
*.lock *.lock
options.json options.json
content/modules/*.md content/modules/*.md
!_index.md

View file

@ -87,5 +87,25 @@ details {
} }
aside.book-menu span { aside.book-menu span {
// make menu headers bold
font-weight: bold; font-weight: bold;
}
.source {
position: relative;
.filename {
position: absolute;
right: 0px;
margin: 0;
line-height: 0;
font-size: 0.8rem;
background-color: transparent;
color: #d8dee9;
}
}
// directory listing on examples page
.listing {
position: absolute;
bottom: 10px;
} }

View file

@ -0,0 +1,3 @@
---
weight: 20
---

View file

@ -0,0 +1,4 @@
{
deployment = import ./deployment { };
testing = import ./testing { };
}

View file

@ -0,0 +1,11 @@
As a more complete example, let's define some high-level variables and then split our module out into another file as we start to grow.
{{< source "default.nix" >}}
Now we create a module which does a few related things:
- create a `Deployment`
- mount a `ConfigMap` into its pod
- define a `Service`
{{< source "module.nix" >}}

View file

@ -0,0 +1,9 @@
{kubenix ? import ../../../..}:
kubenix.evalModules.x86_64-linux {
module = {kubenix, ...}: {
imports = [./module.nix];
kubenix.project = "example";
kubernetes.version = "1.24";
};
}

View file

@ -4,13 +4,8 @@
pkgs, pkgs,
kubenix, kubenix,
... ...
}: }: {
with lib; let imports = with kubenix.modules; [k8s];
nginx = pkgs.callPackage ./image.nix {};
in {
imports = with kubenix.modules; [k8s docker];
docker.images.nginx.image = nginx;
kubernetes.resources = { kubernetes.resources = {
deployments.nginx.spec = { deployments.nginx.spec = {
@ -21,7 +16,7 @@ in {
spec = { spec = {
securityContext.fsGroup = 1000; securityContext.fsGroup = 1000;
containers.nginx = { containers.nginx = {
image = config.docker.images.nginx.path; image = "nginx";
imagePullPolicy = "IfNotPresent"; imagePullPolicy = "IfNotPresent";
volumeMounts = { volumeMounts = {
"/etc/nginx".name = "config"; "/etc/nginx".name = "config";

View file

@ -0,0 +1,9 @@
To define a helm release, use the {{< option "kubernetes.helm.releases" >}} option.
{{< source "default.nix" >}}
Fetch and render the chart just as we did with plain manifests:
```sh
nix eval -f . --json config.kubernetes.generated
```

View file

@ -1,5 +1,5 @@
{evalModules}: {kubenix ? import ../../../..}:
(evalModules { kubenix.evalModules.${builtins.currentSystem} {
module = {kubenix, ...}: { module = {kubenix, ...}: {
imports = with kubenix.modules; [helm]; imports = with kubenix.modules; [helm];
kubernetes.helm.releases.example = { kubernetes.helm.releases.example = {
@ -8,9 +8,9 @@
repo = "https://charts.bitnami.com/bitnami"; repo = "https://charts.bitnami.com/bitnami";
sha256 = "sha256-wP3tcBnySx+kvZqfW2W9k665oi8KOI50tCcAl0g9cuw="; sha256 = "sha256-wP3tcBnySx+kvZqfW2W9k665oi8KOI50tCcAl0g9cuw=";
}; };
values = {
replicaCount = 2;
};
}; };
}; };
}) }
.config
.kubernetes
.result

View file

@ -0,0 +1,66 @@
---
weight: 30
---
Instead of deploying a 3rd party image, we can build our own.
We rely on the upstream [`dockerTools`](https://github.com/NixOs/nixpkgs/tree/master/pkgs/built-support/docker) package here.
Specifically, we can use the `buildImage` function to define our image:
{{< source "image.nix" >}}
Then we can import this package into our `docker` module:
{{< source "default.nix" >}}
Now build the image with
```sh
nix build -f . --json config.docker.export
```
Render the generated manifests again and see that it now refers to the newly built tag:
```json
{
"apiVersion": "v1",
"items": [
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"annotations": {
"kubenix/k8s-version": "1.24",
"kubenix/project-name": "kubenix"
},
"labels": {
"kubenix/hash": "ac7e4794c3d37f0884e4512a680a30d20e1d6454"
},
"name": "example"
},
"spec": {
"containers": [
{
"image": "docker.somewhere.io/nginx:w7c63alk7kynqh2mqnzxy9n1iqgdc93s",
"name": "custom"
}
]
}
}
],
"kind": "List",
"labels": {
"kubenix/hash": "ac7e4794c3d37f0884e4512a680a30d20e1d6454",
"kubenix/k8s-version": "1.24",
"kubenix/project-name": "kubenix"
}
}
```
Of course, to actually deploy, we need to push the image to our registry. The script defined at {{< option "docker.copyScript" >}} does just that.
```sh
$(nix build -f . --json config.docker.copyScript | jq -r '.[].outputs.out')
```
<!-- TODO: can we make that `nix run -f . config.docker.copyScript` ? -->

View file

@ -0,0 +1,13 @@
{ kubenix ? import ../../../.. }:
kubenix.evalModules.${builtins.currentSystem} {
module = {kubenix, config, pkgs, ...}: {
imports = with kubenix.modules; [k8s docker];
docker = {
registry.url = "docker.somewhere.io";
images.example.image = pkgs.callPackage ./image.nix {};
};
kubernetes.resources.pods.example.spec.containers = {
custom.image = config.docker.images.example.path;
};
};
}

View file

@ -0,0 +1,67 @@
---
weight: 10
---
The simplest, but not incredibly useful, example is likely deploying a bare pod.
Which we can do with the `kubernetes.resources.pods` option:
{{< source "default.nix" >}}
Here, `example` is an arbitrary string which identifies the pod (just as `ex` identifies a container within the pod).
{{< hint info >}}
**NOTE**
The format under {{< option "kubernetes.resources" true >}} largely mirrors that of the [Kubernetes API](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/) which can generally be explored with `kubectl`; e.g.
```sh
kubectl explain poc.spec.containers
```
However, our format uses the plural form and injects resource names where appropriate.
{{< /hint >}}
Create a json manifest with:
```sh
nix eval -f . --json config.kubernetes.generated
```
which should output something like this:
```json
{
"apiVersion": "v1",
"items": [
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"annotations": {
"kubenix/k8s-version": "1.24",
"kubenix/project-name": "kubenix"
},
"labels": {
"kubenix/hash": "6e6ccbb6787f9b600737f8882d2487eeef84af9f"
},
"name": "example"
},
"spec": {
"containers": [
{
"image": "nginx",
"name": "ex"
}
]
}
}
],
"kind": "List",
"labels": {
"kubenix/hash": "6e6ccbb6787f9b600737f8882d2487eeef84af9f",
"kubenix/k8s-version": "1.24",
"kubenix/project-name": "kubenix"
}
}
```

View file

@ -0,0 +1,12 @@
# let's creata a function whose only input is the kubenix package
{kubenix ? import ../../../..}:
# evalModules is our main entrypoint
kubenix.evalModules.${builtins.currentSystem} {
# to it, we pass a module that accepts a (different) kubenix object
module = {kubenix, ...}: {
# in order to define options, we need to import their definitions
imports = with kubenix.modules; [k8s];
# now we have full access to define Kubernetes resources
kubernetes.resources.pods.example.spec.containers.ex.image = "nginx";
};
}

View file

@ -0,0 +1,11 @@
Secrets management requires some extra care as we want to prevent values from
ending up in the, world-readable, nix store.
{{< hint "warning" >}}
**WARNING**
The kubenix secrets story is incomplete. Do not trust it -- it has not been tested.
{{< /hint >}}
The easiest approach is to avoid writing to the store altogether with `nix eval` instead of `nix build`.
This isn't a long-term device and we'll explore integrations with other tools soon(TM).

View file

@ -0,0 +1,13 @@
Testing is still very much in flux but here's a rough example.
{{< source "default.nix" >}}
Where we've defined a might look like:
{{< source "test.nix" >}}
Execute with
```sh
nix eval -f . config.testing.success
```

View file

@ -0,0 +1,17 @@
{kubenix ? import ../../../..}:
kubenix.evalModules.x86_64-linux {
module = {kubenix, ...}: {
imports = with kubenix.modules; [testing];
testing = {
tests = [./test.nix];
common = [
{
features = ["k8s"];
options = {
kubernetes.version = "1.24";
};
}
];
};
};
}

View file

@ -4,30 +4,22 @@
kubenix, kubenix,
test, test,
... ...
}: }: {
with lib; { imports = [kubenix.modules.test];
imports = [kubenix.modules.test ./module.nix];
test = { test = {
name = "nginx-deployment"; name = "example";
description = "Test testing nginx deployment"; description = "can reach deployment";
script = '' script = ''
@pytest.mark.applymanifest('${test.kubernetes.resultYAML}') @pytest.mark.applymanifest('${test.kubernetes.resultYAML}')
def test_nginx_deployment(kube): def test_nginx_deployment(kube):
"""Tests whether nginx deployment gets successfully created""" """Tests whether nginx deployment gets successfully created"""
kube.wait_for_registered(timeout=30) kube.wait_for_registered(timeout=30)
deployments = kube.get_deployments() deployments = kube.get_deployments()
nginx_deploy = deployments.get('nginx') nginx_deploy = deployments.get('nginx')
assert nginx_deploy is not None assert nginx_deploy is not None
status = nginx_deploy.status() status = nginx_deploy.status()
assert status.readyReplicas == 10 assert status.readyReplicas == 10
# TODO: implement those kind of checks from the host machine into the cluster
# via port forwarding, prepare all runtimes accordingly
# ${pkgs.curl}/bin/curl http://nginx.default.svc.cluster.local | grep -i hello
''; '';
}; };
} }

View file

@ -1,7 +0,0 @@
{
system ? builtins.currentSystem,
evalModules ? (import ../. {}).evalModules.${system},
}: {registry ? "docker.io/gatehub"}: {
nginx-deployment = import ./nginx-deployment {inherit evalModules registry;};
helm-chart = import ./helm-chart {inherit evalModules;};
}

View file

@ -1,21 +0,0 @@
# nginx deployment
A simple example creating an nginx docker image and deployment.
## Usage
### Building and applying kubernetes configuration
nix eval -f ./. --json result | kubectl apply -f -
### Building and pushing docker images
nix run -f ./. pushDockerImages -c copy-docker-images
### Running tests
Test will spawn vm with Kubernetes and run test script, which checks if everything
works as expected.
nix build -f ./. test-script
cat result | jq '.'

View file

@ -1,59 +0,0 @@
{
evalModules,
registry,
}: let
# evaluated configuration
inherit
((evalModules {
module = {kubenix, ...}: {
imports = [
kubenix.modules.testing
./module.nix
];
# commonalities
kubenix.project = "nginx-deployment-example";
docker.registry.url = registry;
kubernetes.version = "1.21";
testing = {
tests = [./test.nix];
docker.registryUrl = "";
# testing commonalities for tests that exhibit the respective feature
common = [
{
features = ["k8s"];
options = {
kubernetes.version = "1.20";
};
}
];
};
};
}))
config
;
in {
inherit config;
# config checks
checks = config.testing.success;
# TODO: e2e test
# test = config.testing.result;
# nixos test script for running the test
test-script = config.testing.testsByName.nginx-deployment.script;
# genreated kubernetes List object
inherit (config.kubernetes) generated;
# JSON file you can deploy to kubernetes
inherit (config.kubernetes) result;
# Exported docker images
images = config.docker.export;
# script to push docker images to registry
pushDockerImages = config.docker.copyScript;
}

View file

@ -0,0 +1,29 @@
{{/* only show a directory listing on example pages */}}
{{- if and (eq $.Section "examples") (ne $.Page.File.Path "examples/_index.md") -}}
{{/* get base directory */}}
{{- $dir := $.Page.File.Dir -}}
{{- $loc := path.Join $.Site.Params.BookRepo "blob" "main" "docs" "content" $.Page.File.Dir -}}
{{/* create a list of nix files */}}
{{- $files := slice -}}
{{- range readDir (path.Join "content" $dir) -}}
{{- if strings.HasSuffix .Name ".nix" -}}
{{- $files = $files | append . -}}
{{- end -}}
{{- end -}}
<pre class="listing">
<a href="{{ $loc }}">{{- index (split $dir "/") 1 }}</a>/
{{- range $i, $file := $files }}
{{- if ne (add $i 1) (len $files) }}
├──
{{- else }}
└──
{{- end -}}
&nbsp;<a href="{{ path.Join $loc $file.Name }}" >{{ $file.Name }}</a>
{{- end -}}
</pre>
{{- end -}}

View file

@ -0,0 +1,11 @@
{{- $option := .Get 0 -}}
{{- $module := index (strings.Split $option ".") 0 -}}
{{/* should we use markdown instead of html? some envs strip raw html */}}
{{- $md := .Get 1 | default false -}}
{{- if $md -}}
[{{ $option }}](/modules/{{ $module }}/#{{ $option }})
{{- else -}}
<a href="/modules/{{ $module }}/#{{ $option }}">{{ $option }}</a>
{{- end -}}

View file

@ -0,0 +1,6 @@
{{- $file := .Get 0 -}}
<div class="source">
<pre class="filename">{{ $file }}</pre>
{{- $file := path.Join .Page.File.Dir $file -}}
{{- highlight ($file | readFile | safeHTML) (path.Ext $file) -}}
</div>

View file

@ -166,16 +166,14 @@
if suite.success if suite.success
then pkgs.runCommandNoCC "testing-suite-config-assertions-for-${suite.name}-succeeded" {} "echo success > $out" then pkgs.runCommandNoCC "testing-suite-config-assertions-for-${suite.name}-succeeded" {} "echo success > $out"
else pkgs.runCommandNoCC "testing-suite-config-assertions-for-${suite.name}-failed" {} "exit 1"; else pkgs.runCommandNoCC "testing-suite-config-assertions-for-${suite.name}-failed" {} "exit 1";
mkExamples = attrs: examples = import ./docs/content/examples;
(import ./docs/examples {inherit evalModules;})
({registry = "docker.io/gatehub";} // attrs);
mkK8STests = attrs: mkK8STests = attrs:
(import ./tests {inherit evalModules;}) (import ./tests {inherit evalModules;})
({registry = "docker.io/gatehub";} // attrs); ({registry = "docker.io/gatehub";} // attrs);
in in
{ {
# TODO: access "success" derivation with nice testing utils for nice output # TODO: access "success" derivation with nice testing utils for nice output
nginx-example = wasSuccess (mkExamples {}).nginx-deployment.config.testing; testing = wasSuccess examples.testing.config.testing;
} }
// builtins.listToAttrs (builtins.map // builtins.listToAttrs (builtins.map
(v: { (v: {