From 9f8ca8447ef7bfdd1bd0132414ec0b744662bc2a Mon Sep 17 00:00:00 2001 From: Jaka Hudoklin Date: Tue, 12 Feb 2019 16:22:18 +0100 Subject: [PATCH] feat: initial testing support --- release.nix | 262 ++++++++++++++++++++------------------- test/default.nix | 28 +++++ test/k8s/deployment.nix | 34 +++++ test/k8s/simple.nix | 23 ++++ test/modules/test.nix | 41 ++++++ test/modules/testing.nix | 101 +++++++++++++++ 6 files changed, 362 insertions(+), 127 deletions(-) create mode 100644 test/default.nix create mode 100644 test/k8s/deployment.nix create mode 100644 test/k8s/simple.nix create mode 100644 test/modules/test.nix create mode 100644 test/modules/testing.nix diff --git a/release.nix b/release.nix index b811b0d..6f64879 100644 --- a/release.nix +++ b/release.nix @@ -1,6 +1,10 @@ {pkgs ? import {}}: let + kubenix = import ./. { inherit pkgs; }; + + lib = kubenix.lib; + generateK8S = path: import ./k8s/generator.nix { inherit pkgs; inherit (pkgs) lib; @@ -12,8 +16,6 @@ let inherit (pkgs) lib; inherit spec; }; - - kubenix = import ./. { inherit pkgs; }; in { generate.k8s = pkgs.linkFarm "k8s-generated.nix" [{ name = "v1.7.nix"; @@ -34,152 +36,158 @@ in { path = generateIstio ./istio/istio-schema.json; }]; - test = kubenix.buildResources ({lib, config, kubenix, ...}: with lib; { - imports = [ - kubenix.k8s - kubenix.submodules - kubenix.istio - ]; + test = import ./test { + inherit pkgs lib kubenix; + }; - config = { - kubernetes.version = "1.10"; - - kubernetes.api.defaults.all.metadata.namespace = mkDefault "my-namespace"; - - submodules.defaults = {config, parentConfig, ...}: { - kubernetes = mkIf (hasAttr "kubernetes" config) { - version = mkDefault parentConfig.kubernetes.version; - api.defaults = mkDefault parentConfig.kubernetes.api.defaults; - }; - }; - - submodules.imports = [ - # import nginx submodule - ./examples/module/nginx.nix - - # import of patched nginx submodule - { - modules = [./examples/module/nginx.nix ({config, ...}: { - config = { - submodule.version = mkForce "1.0-xtruder"; - args.image = "xtruder/nginx"; - - submodules.instances.test2 = { - submodule = "test"; - }; - - kubernetes.objects = config.submodules.instances.test2.config.kubernetes.objects; - }; - })]; - } - - # definition of test submodule - { - module = {submodule, ...}: { - submodule.name = "test"; - - imports = [ - kubenix.k8s - ]; - - kubernetes.api.Pod.my-pod = { - metadata.name = submodule.name; - }; - }; - } + test-old = kubenix.buildResources ({ + module = {lib, config, kubenix, ...}: with lib; { + imports = [ + kubenix.k8s + kubenix.submodules + kubenix.istio ]; - submodules.instances.nginx-default = { - submodule = "nginx"; - }; + config = { + kubernetes.version = "1.10"; - submodules.instances.nginx-xtruder = { - submodule = "nginx"; - version = "1.0-xtruder"; + kubernetes.api.defaults.all.metadata.namespace = mkDefault "my-namespace"; - config = { - args.replicas = 9; - kubernetes.api.Deployment.nginx.metadata.namespace = "other-namespace"; + submodules.defaults = {config, parentConfig, ...}: { + kubernetes = mkIf (hasAttr "kubernetes" config) { + version = mkDefault parentConfig.kubernetes.version; + api.defaults = mkDefault parentConfig.kubernetes.api.defaults; + }; }; - }; - submodules.instances.test = { - submodule = "test"; - }; + submodules.imports = [ + # import nginx submodule + ./examples/module/nginx.nix - kubernetes.api."networking.istio.io"."v1alpha3".Gateway.test.spec = { - selector.istio = "ingressgateway"; - servers = [{ - port = { - number = 80; - name = "http"; - protocol = "HTTP"; + # import of patched nginx submodule + { + modules = [./examples/module/nginx.nix ({config, ...}: { + config = { + submodule.version = mkForce "1.0-xtruder"; + args.image = "xtruder/nginx"; + + submodules.instances.test2 = { + submodule = "test"; + }; + + kubernetes.objects = config.submodules.instances.test2.config.kubernetes.objects; + }; + })]; + } + + # definition of test submodule + { + module = {submodule, ...}: { + submodule.name = "test"; + + imports = [ + kubenix.k8s + ]; + + kubernetes.api.Pod.my-pod = { + metadata.name = submodule.name; + }; + }; + } + ]; + + submodules.instances.nginx-default = { + submodule = "nginx"; + }; + + submodules.instances.nginx-xtruder = { + submodule = "nginx"; + version = "1.0-xtruder"; + + config = { + args.replicas = 9; + kubernetes.api.Deployment.nginx.metadata.namespace = "other-namespace"; }; - hosts = ["host.example.com"]; - tls.httpsRedirect = true; - } { - port = { - number = 443; - name = "https"; - protocol = "HTTPS"; - }; - hosts = ["host.example.com"]; - tls = { - mode = "SIMPLE"; - serverCertificate = "/path/to/server.crt"; - privateKey = "/path/to/private.key"; - caCertificates = "/path/to/ca.crt"; - }; - }]; - }; + }; - #kubernetes.api."cloud.google.com".v1beta1.BackendConfig.my-backend = { - #}; + submodules.instances.test = { + submodule = "test"; + }; - #modules.nginx1 = { - #args = { - #replicas = 2; + kubernetes.api."networking.istio.io"."v1alpha3".Gateway.test.spec = { + selector.istio = "ingressgateway"; + servers = [{ + port = { + number = 80; + name = "http"; + protocol = "HTTP"; + }; + hosts = ["host.example.com"]; + tls.httpsRedirect = true; + } { + port = { + number = 443; + name = "https"; + protocol = "HTTPS"; + }; + hosts = ["host.example.com"]; + tls = { + mode = "SIMPLE"; + serverCertificate = "/path/to/server.crt"; + privateKey = "/path/to/private.key"; + caCertificates = "/path/to/ca.crt"; + }; + }]; + }; + + #kubernetes.api."cloud.google.com".v1beta1.BackendConfig.my-backend = { #}; - #kubernetes.api.defaults.deployments = { - #spec.replicas = mkForce 3; - #}; + #modules.nginx1 = { + #args = { + #replicas = 2; + #}; - #kubernetes.customResources = [{ - #group = "cloud.google.com"; - #version = "v1beta1"; - #kind = "BackendConfig"; - #plural = "backendconfigs"; - #description = "Custom resource"; - #module = { - #options.spec = { - #cdn = mkOption { - #description = "My cdn"; - #type = types.str; - #default = "test"; + #kubernetes.api.defaults.deployments = { + #spec.replicas = mkForce 3; + #}; + + #kubernetes.customResources = [{ + #group = "cloud.google.com"; + #version = "v1beta1"; + #kind = "BackendConfig"; + #plural = "backendconfigs"; + #description = "Custom resource"; + #module = { + #options.spec = { + #cdn = mkOption { + #description = "My cdn"; + #type = types.str; + #default = "test"; + #}; #}; #}; + #}]; + #}; + + #modules.nginx2 = { + #args = { + #replicas = 2; #}; - #}]; - #}; - #modules.nginx2 = { - #args = { - #replicas = 2; + #kubernetes.api.defaults.deployments = { + #spec.replicas = mkForce 3; + #}; #}; - #kubernetes.api.defaults.deployments = { - #spec.replicas = mkForce 3; - #}; - #}; + kubernetes.objects = mkMerge [ + config.submodules.instances.nginx-default.config.kubernetes.objects + config.submodules.instances.nginx-xtruder.config.kubernetes.objects + config.submodules.instances.test.config.kubernetes.objects + ]; - kubernetes.objects = mkMerge [ - config.submodules.instances.nginx-default.config.kubernetes.objects - config.submodules.instances.nginx-xtruder.config.kubernetes.objects - config.submodules.instances.test.config.kubernetes.objects - ]; - - #kubernetes.customResources = config.modules.nginx1.kubernetes.customResources; + #kubernetes.customResources = config.modules.nginx1.kubernetes.customResources; + }; }; }); } diff --git a/test/default.nix b/test/default.nix new file mode 100644 index 0000000..719b95b --- /dev/null +++ b/test/default.nix @@ -0,0 +1,28 @@ +{ pkgs ? import {} +, kubenix ? import ../. {inherit pkgs;} +, lib ? kubenix.lib + +# whether any testing error should throw an error +, throwError ? true }: + +with lib; + +(evalModules { + modules = [ + ./modules/testing.nix + + { + testing.throwError = throwError; + testing.tests = [ + ./k8s/simple.nix + ./k8s/deployment.nix + ]; + } + ]; + args = { + inherit pkgs; + }; + specialArgs = { + inherit kubenix; + }; +}).config.testing.result diff --git a/test/k8s/deployment.nix b/test/k8s/deployment.nix new file mode 100644 index 0000000..f3e6964 --- /dev/null +++ b/test/k8s/deployment.nix @@ -0,0 +1,34 @@ +{ config, test, kubenix, ... }: + +let + cfg = config.kubernetes.api.Deployment.nginx; +in { + imports = [ + kubenix.k8s + ]; + + test = { + name = "k8s/deployment/simple"; + description = "Simple k8s testing a simple deployment"; + assertions = [{ + message = "should have correct apiVersion and kind set"; + assertion = cfg.apiVersion == "apps/v1" && cfg.kind == "Deployment"; + } { + message = "should have replicas set"; + assertion = cfg.spec.replicas == 10; + }]; + }; + + kubernetes.api.Deployment.nginx = { + spec = { + replicas = 10; + selector.matchLabels.app = "nginx"; + template.metadata.labels.app = "nginx"; + template.spec = { + containers.nginx = { + image = "nginx"; + }; + }; + }; + }; +} diff --git a/test/k8s/simple.nix b/test/k8s/simple.nix new file mode 100644 index 0000000..f1c2c88 --- /dev/null +++ b/test/k8s/simple.nix @@ -0,0 +1,23 @@ +{ config, test, kubenix, ... }: + +let + cfg = config.kubernetes.api.Pod.nginx; +in { + imports = [ + kubenix.k8s + ]; + + test = { + name = "k8s/simple"; + description = "Simple k8s testing wheter name, apiVersion and kind are preset"; + assertions = [{ + message = "should have apiVersion and kind set"; + assertion = cfg.apiVersion == "v1" && cfg.kind == "Pod"; + } { + message = "should have name set"; + assertion = cfg.metadata.name == "nginx"; + }]; + }; + + kubernetes.api.Pod.nginx = {}; +} diff --git a/test/modules/test.nix b/test/modules/test.nix new file mode 100644 index 0000000..1b54d97 --- /dev/null +++ b/test/modules/test.nix @@ -0,0 +1,41 @@ +{ lib, ... }: + +with lib; + +{ + options.test = { + name = mkOption { + description = "Test name"; + type = types.str; + }; + + description = mkOption { + description = "Test description"; + type = types.str; + }; + + assertions = mkOption { + type = types.listOf (types.submodule { + options = { + assertion = mkOption { + description = "assertion value"; + type = types.bool; + default = false; + }; + + message = mkOption { + description = "assertion message"; + type = types.str; + }; + }; + }); + default = []; + example = [ { assertion = false; message = "you can't enable this for that reason"; } ]; + description = '' + This option allows modules to express conditions that must + hold for the evaluation of the system configuration to + succeed, along with associated error messages for the user. + ''; + }; + }; +} diff --git a/test/modules/testing.nix b/test/modules/testing.nix new file mode 100644 index 0000000..91e3cfd --- /dev/null +++ b/test/modules/testing.nix @@ -0,0 +1,101 @@ +{ config, pkgs, lib, kubenix, ... }: + +with lib; + +let + cfg = config.testing; +in { + options = { + testing.throwError = mkOption { + description = "Whether to throw error"; + type = types.bool; + default = true; + }; + + testing.tests = mkOption { + description = "Attribute set of test cases"; + default = []; + type = types.listOf (types.coercedTo types.path (module: {inherit module;}) (types.submodule ({config, ...}: let + modules = [config.module ./test.nix { + config._module.args.test = config; + }]; + + test = (kubenix.evalKubernetesModules { + check = false; + inherit modules; + }).config.test; + + evaled = builtins.trace "testing ${test.name}" (kubenix.evalKubernetesModules { + inherit modules; + }); + in { + options = { + module = mkOption { + description = "Module defining submodule"; + type = types.unspecified; + }; + + name = mkOption { + description = "test name"; + type = types.str; + internal = true; + }; + + description = mkOption { + description = "test description"; + type = types.str; + internal = true; + }; + + evaled = mkOption { + description = "Wheter test was evaled"; + type = types.bool; + default = + if cfg.throwError + then if evaled.config.test.assertions != [] then true else true + else (builtins.tryEval evaled.config.test.assertions).success; + internal = true; + }; + + success = mkOption { + description = "Whether test was success"; + type = types.bool; + internal = true; + default = false; + }; + + assertions = mkOption { + description = "Test result"; + type = types.unspecified; + internal = true; + default = []; + }; + }; + + config = { + inherit (test) name description; + assertions = mkIf config.evaled evaled.config.test.assertions; + success = mkIf config.evaled (all (el: el.assertion) config.assertions); + }; + }))); + }; + + testing.success = mkOption { + description = "Whether testing was a success"; + type = types.bool; + default = all (test: test.success) cfg.tests; + }; + + testing.result = mkOption { + description = "Testing result"; + type = types.package; + default = pkgs.writeText "testing-report.json" (builtins.toJSON { + success = cfg.success; + tests = map (test: { + inherit (test) name description evaled success; + assertions = moduleToAttrs test.assertions; + }) cfg.tests; + }); + }; + }; +}