diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ef7f91..2ed99d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,6 +3,8 @@ name: tests on: pull_request: push: + branches: + - main jobs: tests: diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index fbf16df..4cdb916 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -6,7 +6,6 @@ on: - main paths: - 'docs/**' - pull_request: jobs: deploy: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f158cc1..4cc58a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,3 +46,11 @@ Build and serve the static site nix run '.#docs' serve which will be available at . + +### 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. diff --git a/README.md b/README.md index 3208707..e241661 100644 --- a/README.md +++ b/README.md @@ -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`. -See [./docs/examples](./docs/examples) for more. +See the [examples](/examples/pod) for more. ## CLI diff --git a/docs/.gitignore b/docs/.gitignore index cbbd1d9..3ce71be 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -3,3 +3,4 @@ resources/ *.lock options.json content/modules/*.md +!_index.md diff --git a/docs/assets/_custom.scss b/docs/assets/_custom.scss index 80f53d5..e5618ea 100644 --- a/docs/assets/_custom.scss +++ b/docs/assets/_custom.scss @@ -87,5 +87,25 @@ details { } aside.book-menu span { + // make menu headers 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; } \ No newline at end of file diff --git a/docs/content/examples/_index.md b/docs/content/examples/_index.md new file mode 100644 index 0000000..27d22f2 --- /dev/null +++ b/docs/content/examples/_index.md @@ -0,0 +1,3 @@ +--- +weight: 20 +--- diff --git a/docs/content/examples/default.nix b/docs/content/examples/default.nix new file mode 100644 index 0000000..6111357 --- /dev/null +++ b/docs/content/examples/default.nix @@ -0,0 +1,4 @@ +{ + deployment = import ./deployment { }; + testing = import ./testing { }; +} diff --git a/docs/content/examples/deployment/_index.md b/docs/content/examples/deployment/_index.md new file mode 100644 index 0000000..67a61f3 --- /dev/null +++ b/docs/content/examples/deployment/_index.md @@ -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" >}} diff --git a/docs/content/examples/deployment/default.nix b/docs/content/examples/deployment/default.nix new file mode 100644 index 0000000..2f1bfe8 --- /dev/null +++ b/docs/content/examples/deployment/default.nix @@ -0,0 +1,9 @@ +{kubenix ? import ../../../..}: +kubenix.evalModules.x86_64-linux { + module = {kubenix, ...}: { + imports = [./module.nix]; + + kubenix.project = "example"; + kubernetes.version = "1.24"; + }; +} diff --git a/docs/examples/nginx-deployment/module.nix b/docs/content/examples/deployment/module.nix similarity index 87% rename from docs/examples/nginx-deployment/module.nix rename to docs/content/examples/deployment/module.nix index ddd711a..4e3cdda 100644 --- a/docs/examples/nginx-deployment/module.nix +++ b/docs/content/examples/deployment/module.nix @@ -4,13 +4,8 @@ pkgs, kubenix, ... -}: -with lib; let - nginx = pkgs.callPackage ./image.nix {}; -in { - imports = with kubenix.modules; [k8s docker]; - - docker.images.nginx.image = nginx; +}: { + imports = with kubenix.modules; [k8s]; kubernetes.resources = { deployments.nginx.spec = { @@ -21,7 +16,7 @@ in { spec = { securityContext.fsGroup = 1000; containers.nginx = { - image = config.docker.images.nginx.path; + image = "nginx"; imagePullPolicy = "IfNotPresent"; volumeMounts = { "/etc/nginx".name = "config"; diff --git a/docs/content/examples/helm/_index.md b/docs/content/examples/helm/_index.md new file mode 100644 index 0000000..1dac2eb --- /dev/null +++ b/docs/content/examples/helm/_index.md @@ -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 +``` diff --git a/docs/examples/helm-chart/default.nix b/docs/content/examples/helm/default.nix similarity index 70% rename from docs/examples/helm-chart/default.nix rename to docs/content/examples/helm/default.nix index 2a4eb0e..a47369f 100644 --- a/docs/examples/helm-chart/default.nix +++ b/docs/content/examples/helm/default.nix @@ -1,5 +1,5 @@ -{evalModules}: -(evalModules { +{kubenix ? import ../../../..}: +kubenix.evalModules.${builtins.currentSystem} { module = {kubenix, ...}: { imports = with kubenix.modules; [helm]; kubernetes.helm.releases.example = { @@ -8,9 +8,9 @@ repo = "https://charts.bitnami.com/bitnami"; sha256 = "sha256-wP3tcBnySx+kvZqfW2W9k665oi8KOI50tCcAl0g9cuw="; }; + values = { + replicaCount = 2; + }; }; }; -}) -.config -.kubernetes -.result +} diff --git a/docs/content/examples/image/_index.md b/docs/content/examples/image/_index.md new file mode 100644 index 0000000..e447113 --- /dev/null +++ b/docs/content/examples/image/_index.md @@ -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') +``` + + diff --git a/docs/content/examples/image/default.nix b/docs/content/examples/image/default.nix new file mode 100644 index 0000000..1207e4b --- /dev/null +++ b/docs/content/examples/image/default.nix @@ -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; + }; + }; +} \ No newline at end of file diff --git a/docs/examples/nginx-deployment/image.nix b/docs/content/examples/image/image.nix similarity index 100% rename from docs/examples/nginx-deployment/image.nix rename to docs/content/examples/image/image.nix diff --git a/docs/content/examples/pod/_index.md b/docs/content/examples/pod/_index.md new file mode 100644 index 0000000..9183d32 --- /dev/null +++ b/docs/content/examples/pod/_index.md @@ -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" + } +} +``` diff --git a/docs/content/examples/pod/default.nix b/docs/content/examples/pod/default.nix new file mode 100644 index 0000000..41136e7 --- /dev/null +++ b/docs/content/examples/pod/default.nix @@ -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"; + }; +} diff --git a/docs/content/examples/secrets/_index.md b/docs/content/examples/secrets/_index.md new file mode 100644 index 0000000..09890f2 --- /dev/null +++ b/docs/content/examples/secrets/_index.md @@ -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). diff --git a/docs/content/examples/testing/_index.md b/docs/content/examples/testing/_index.md new file mode 100644 index 0000000..a1cc357 --- /dev/null +++ b/docs/content/examples/testing/_index.md @@ -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 +``` diff --git a/docs/content/examples/testing/default.nix b/docs/content/examples/testing/default.nix new file mode 100644 index 0000000..7c11612 --- /dev/null +++ b/docs/content/examples/testing/default.nix @@ -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"; + }; + } + ]; + }; + }; +} diff --git a/docs/examples/nginx-deployment/test.nix b/docs/content/examples/testing/test.nix similarity index 57% rename from docs/examples/nginx-deployment/test.nix rename to docs/content/examples/testing/test.nix index 216d5d9..7e4b657 100644 --- a/docs/examples/nginx-deployment/test.nix +++ b/docs/content/examples/testing/test.nix @@ -4,30 +4,22 @@ kubenix, test, ... -}: -with lib; { - imports = [kubenix.modules.test ./module.nix]; +}: { + imports = [kubenix.modules.test]; test = { - name = "nginx-deployment"; - description = "Test testing nginx deployment"; + name = "example"; + description = "can reach deployment"; script = '' @pytest.mark.applymanifest('${test.kubernetes.resultYAML}') def test_nginx_deployment(kube): """Tests whether nginx deployment gets successfully created""" - kube.wait_for_registered(timeout=30) - deployments = kube.get_deployments() nginx_deploy = deployments.get('nginx') assert nginx_deploy is not None - status = nginx_deploy.status() 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 ''; }; } diff --git a/docs/content/modules/.gitkeep b/docs/content/modules/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/docs/examples/default.nix b/docs/examples/default.nix deleted file mode 100644 index 896d90b..0000000 --- a/docs/examples/default.nix +++ /dev/null @@ -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;}; -} diff --git a/docs/examples/nginx-deployment/README.md b/docs/examples/nginx-deployment/README.md deleted file mode 100644 index 77704f2..0000000 --- a/docs/examples/nginx-deployment/README.md +++ /dev/null @@ -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 '.' diff --git a/docs/examples/nginx-deployment/default.nix b/docs/examples/nginx-deployment/default.nix deleted file mode 100644 index ebb197e..0000000 --- a/docs/examples/nginx-deployment/default.nix +++ /dev/null @@ -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; -} diff --git a/docs/layouts/partials/docs/inject/toc-after.html b/docs/layouts/partials/docs/inject/toc-after.html new file mode 100644 index 0000000..0ce7f91 --- /dev/null +++ b/docs/layouts/partials/docs/inject/toc-after.html @@ -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 -}} + +
+{{- index (split $dir "/") 1 }}/
+{{- range $i, $file := $files }}
+{{- if ne (add $i 1) (len $files) }}
+├──
+{{- else  }}
+└──
+{{- end -}}
+ {{ $file.Name }}
+{{- end -}}
+
+ +{{- end -}} \ No newline at end of file diff --git a/docs/layouts/shortcodes/option.html b/docs/layouts/shortcodes/option.html new file mode 100644 index 0000000..cca23f5 --- /dev/null +++ b/docs/layouts/shortcodes/option.html @@ -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 -}} +{{ $option }} +{{- end -}} \ No newline at end of file diff --git a/docs/layouts/shortcodes/source.html b/docs/layouts/shortcodes/source.html new file mode 100644 index 0000000..b42d2b4 --- /dev/null +++ b/docs/layouts/shortcodes/source.html @@ -0,0 +1,6 @@ +{{- $file := .Get 0 -}} +
+
{{ $file }}
+ {{- $file := path.Join .Page.File.Dir $file -}} + {{- highlight ($file | readFile | safeHTML) (path.Ext $file) -}} +
\ No newline at end of file diff --git a/flake.nix b/flake.nix index 62bfd56..0d1eff9 100644 --- a/flake.nix +++ b/flake.nix @@ -166,16 +166,14 @@ if suite.success 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"; - mkExamples = attrs: - (import ./docs/examples {inherit evalModules;}) - ({registry = "docker.io/gatehub";} // attrs); + examples = import ./docs/content/examples; mkK8STests = attrs: (import ./tests {inherit evalModules;}) ({registry = "docker.io/gatehub";} // attrs); in { # 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 (v: {