Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
result
.direnv/
.direnv/
.nixos-test-history
3 changes: 3 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@
formatting = (self.treefmtEval.${pkgs.system}).config.build.check self;
# TODO: access "success" derivation with nice testing utils for nice output
testing = wasSuccess examples.testing.config.testing;
label-filtering = pkgs.callPackage ./tests/label-filtering.nix {
kubenix = self.packages.${pkgs.system}.default;
};
} // builtins.listToAttrs (builtins.map
(v: {
name = "test-k8s-${builtins.replaceStrings ["."] ["_"] v}";
Expand Down
2 changes: 1 addition & 1 deletion modules/k8s.nix
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ in
kubeconfig = mkOption {
description = "path to kubeconfig file (default: use $KUBECONFIG)";
type = types.nullOr types.str;
default = "$HOME/.kube/config";
default = null;
example = "/run/secrets/kubeconfig";
};

Expand Down
6 changes: 3 additions & 3 deletions pkgs/kubenix.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let
}).config or { };
kubernetes = config.kubernetes or { };

kubeconfig = kubernetes.kubeconfig or "";
kubeconfig = kubernetes.kubeconfig or null;
result = kubernetes.result or "";

# kubectl does some parsing which removes the -I flag so
Expand All @@ -29,11 +29,11 @@ writeShellApplication {
name = "kubenix";
runtimeInputs = [ vals kubectl ];
text = builtins.readFile ./kubenix.sh;
bashOptions = [ "u" "o pipefail" ];
runtimeEnv = {
KUBECONFIG = toString kubeconfig;
KUBECTL_EXTERNAL_DIFF = toString diff;
MANIFEST = toString result;
} // lib.optionalAttrs (kubeconfig != null) {
KUBECONFIG = toString kubeconfig;
};
derivationArgs = {
passthru = {
Expand Down
63 changes: 25 additions & 38 deletions pkgs/kubenix.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail

function _help() {
echo "
Expand All @@ -19,41 +20,27 @@ function _kubectl() {
vals eval -fail-on-missing-key-in-map <"$MANIFEST" | kubectl "$@"
}

# if no args given, add empty string
[ $# -eq 0 ] && set -- ""

# parse arguments
while test $# -gt 0; do
case "$1" in

-h | --help)
_help
exit 0
;;

"")
_kubectl diff -f - --prune
if [[ $? -eq 1 ]]; then
read -r -p 'apply? [y/N]: ' response
[[ $response == "y" ]] && _kubectl apply -f - --prune --all
fi
shift
;;

render)
vals eval <"$MANIFEST"
shift
;;

apply | diff)
_kubectl "$@" -f - --prune
shift
;;

*)
_kubectl "$@"
shift
;;

esac
done
case "${1:-}" in
-h | --help)
_help
;;

"")
_kubectl diff -f - --prune || (
read -r -p 'apply? [y/N]: ' response
[[ $response == "y" ]] && _kubectl apply -f - --prune --all
)
;;

render)
vals eval <"$MANIFEST"
;;

apply | diff)
_kubectl "$@" -f - --prune
;;

*)
_kubectl "$@"
;;
esac
83 changes: 83 additions & 0 deletions tests/label-filtering.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{ pkgs
, kubenix
, ...
}:
let
testKubenix = kubenix.override {
module = { kubenix, ... }: {
imports = [ kubenix.modules.k8s ];

kubernetes.resources.configMaps = {
test-instance-a = {
metadata.labels."kubenix/module-instance" = "instance-a";
data.testkey = "value-a";
};
test-instance-b = {
metadata.labels."kubenix/module-instance" = "instance-b";
data.testkey = "value-b";
};
};
};
};
in
pkgs.testers.runNixOSTest {
name = "kubenix-label-filter";

nodes.k3s = { pkgs, ... }: {
environment.systemPackages = [
testKubenix
pkgs.kubectl
];

networking.firewall.enable = false;

virtualisation = {
memorySize = 2048;
diskSize = 4096;
cores = 2;
};

services.k3s = {
enable = true;
role = "server";
extraFlags = [
"--disable=traefik"
"--disable=servicelb"
"--disable=coredns"
"--disable=local-storage"
"--disable=metrics-server"
];
};

environment.sessionVariables.KUBECONFIG = "/etc/rancher/k3s/k3s.yaml";
};

testScript = ''
k3s.wait_for_unit("k3s")
k3s.wait_until_succeeds("kubectl get nodes | grep ' Ready'", timeout=60)
k3s.wait_until_succeeds("kubectl get serviceaccount default", timeout=60)

with subtest("kubenix render shows both instances"):
result = k3s.succeed("kubenix render")
assert "instance-a" in result, "instance-a not in render output"
assert "instance-b" in result, "instance-b not in render output"

with subtest("kubenix apply -l deploys only instance-a"):
k3s.succeed("kubenix apply -l kubenix/module-instance=instance-a")
result = k3s.succeed("kubectl get configmap -o name | sort")
assert "test-instance-a" in result, "instance-a should be deployed"
assert "test-instance-b" not in result, "instance-b should NOT be deployed with -l filter"

with subtest("kubenix apply -l deploys instance-b separately"):
k3s.succeed("kubenix apply -l kubenix/module-instance=instance-b")
result = k3s.succeed("kubectl get configmap -o name | sort")
assert "test-instance-a" in result, "instance-a should still exist"
assert "test-instance-b" in result, "instance-b should now be deployed"

with subtest("kubectl delete -l removes only instance-a"):
k3s.succeed("kubenix render | kubectl delete -l kubenix/module-instance=instance-a -f -")
result = k3s.succeed("kubectl get configmap -o name | sort")
assert "test-instance-a" not in result, "instance-a should be deleted"
assert "test-instance-b" in result, "instance-b should still exist"
'';
}
Loading