Refactor:

- Support for module features
- Remove kubenix.module and rather use explicit modules
- Make tests framework independent of k8s module
- Remove metacontroller submodule, which will go to kubenix-modules
- Improved submodule propagation and passthru
- Added additional test for k8s-submodule
This commit is contained in:
Jaka Hudoklin 2019-03-12 20:33:56 +01:00
parent b670139906
commit 6183fcc190
No known key found for this signature in database
GPG key ID: 6A08896BFD32BD95
30 changed files with 513 additions and 508 deletions

View file

@ -3,4 +3,4 @@ script:
- nix-env -iA nixpkgs.jq
- nix build -f ./release.nix test-results --arg e2e false
- cat result | jq '.'
- nix eval -f ./release.nix --arg e2e false tests-check
- nix eval -f ./release.nix --arg e2e false test-check

View file

@ -13,19 +13,20 @@ kubernetes resources very easyly.
### Building tests
```shell
nix-build release.nix -A tests.results --show-trace
nix-build release.nix -A tests-results --show-trace
```
**Building single e2e test**
```
nix-build release.nix -A tests.tests.v1_10.testing.testsByName.<name>.script
cd tests
nix-build release.nix -A tests.k8s-1_10.testsByName.<test-name>.test
```
**Debugging e2e test**
```
nix-build release.nix -A tests.tests.v1_10.testing.testsByName.<name>.script.driver
nix-build release.nix -A tests.k8s-1_10.testsByName.<test-name>.test.driver
resut/bin/nixos-test-driver
testScript;
```

View file

@ -32,6 +32,5 @@ let
inherit evalModules modules;
lib = kubenixLib;
module = modules.module;
};
in kubenix

View file

@ -5,7 +5,7 @@ with lib;
let
nginx = pkgs.callPackage ./image.nix { };
in {
imports = [ kubenix.module ];
imports = with kubenix.modules; [ k8s docker ];
docker.images.nginx.image = nginx;

View file

@ -12,8 +12,6 @@ rec {
else value;
mkOptionDefault = mkOverride 1001;
mkAllDefault = value: priority:
if isAttrs value
then mapAttrs (n: v: mkAllDefault v priority) value

19
modules/base.nix Normal file
View file

@ -0,0 +1,19 @@
{ config, lib, ... }:
with lib;
{
options._module.features = mkOption {
description = "List of features exposed by module";
type = types.listOf types.str;
default = [];
};
options.kubenix = {
project = mkOption {
description = "Name of the project";
type = types.str;
default = "kubenix";
};
};
}

View file

@ -2,9 +2,9 @@
k8s = ./k8s.nix;
istio = ./istio.nix;
submodules = ./submodules.nix;
submodule = ./submodule.nix;
helm = ./helm.nix;
docker = ./docker.nix;
metacontroller = ./metacontroller;
testing = ./testing.nix;
test = ./test.nix;
module = ./module.nix;

View file

@ -3,61 +3,77 @@
with lib;
let
globalConfig = config;
cfg = config.docker;
in {
options.docker.registry.url = mkOption {
description = "Default registry url where images are published";
type = types.str;
default = "";
options.docker = {
registry.url = mkOption {
description = "Default registry url where images are published";
type = types.str;
default = "";
};
images = mkOption {
description = "Attribute set of docker images that should be published";
type = types.attrsOf (types.submodule ({ name, config, ... }: {
options = {
image = mkOption {
description = "Docker image to publish";
type = types.nullOr types.package;
default = null;
};
name = mkOption {
description = "Desired docker image name";
type = types.str;
default = builtins.unsafeDiscardStringContext config.image.imageName;
};
tag = mkOption {
description = "Desired docker image tag";
type = types.str;
default = builtins.unsafeDiscardStringContext config.image.imageTag;
};
registry = mkOption {
description = "Docker registry url where image is published";
type = types.str;
default = cfg.registry.url;
};
path = mkOption {
description = "Full docker image path";
type = types.str;
default =
if config.registry != ""
then "${config.registry}/${config.name}:${config.tag}"
else "${config.name}:${config.tag}";
};
};
}));
default = {};
};
export = mkOption {
description = "List of images to export";
type = types.listOf (types.package);
default = [];
};
};
options.docker.images = mkOption {
description = "Attribute set of docker images that should be published";
type = types.attrsOf (types.submodule ({ name, config, ... }: {
options = {
image = mkOption {
description = "Docker image to publish";
type = types.nullOr types.package;
default = null;
};
config = {
_module.features = ["docker"];
name = mkOption {
description = "Desired docker image name";
type = types.str;
default = builtins.unsafeDiscardStringContext config.image.imageName;
};
docker.export = mkMerge [
(mapAttrsToList (_: i: i.image)
(filterAttrs (_: i: i.registry != null) config.docker.images))
tag = mkOption {
description = "Desired docker image tag";
type = types.str;
default = builtins.unsafeDiscardStringContext config.image.imageTag;
};
registry = mkOption {
description = "Docker registry url where image is published";
type = types.str;
default = globalConfig.docker.registry.url;
};
path = mkOption {
description = "Full docker image path";
type = types.str;
default =
if config.registry != ""
then "${config.registry}/${config.name}:${config.tag}"
else "${config.name}:${config.tag}";
};
};
}));
default = {};
# passthru of docker exported images if passthru is enabled on submodule
# and submodule has docker module loaded
(flatten (mapAttrsToList (_: submodule:
optionals
(submodule.passthru.enable && (elem "docker" submodule.config._module.features))
submodule.config.docker.export
) config.submodules.instances))
];
};
options.docker.export = mkOption {
description = "List of images to export";
type = types.listOf (types.package);
default = [];
};
config.docker.export = mapAttrsToList (_: i: i.image)
(filterAttrs (_: i: i.registry != null)config.docker.images);
}

View file

@ -27,9 +27,6 @@ let
in {
imports = [ ./k8s.nix ];
# expose helm helper methods as module argument
config._module.args.helm = import ../lib/helm { inherit pkgs; };
options.kubernetes.helm = {
instances = mkOption {
description = "Attribute set of helm instances";
@ -84,7 +81,7 @@ in {
};
config.overrides = mkIf (config.overrideNamespace && config.namespace != null) [{
metadata.namespace = mkDefault config.namespace;
metadata.namespace = config.namespace;
}];
config.objects = importJSON (helm.chart2json {
@ -94,14 +91,19 @@ in {
};
};
config.kubernetes.api = mkMerge (flatten (mapAttrsToList (_: instance:
map (object: let
apiVersion = parseApiVersion object.apiVersion;
name = object.metadata.name;
in {
"${apiVersion.group}"."${apiVersion.version}".${object.kind}."${name}" = mkMerge ([
object
] ++ instance.overrides);
}) instance.objects
) cfg.instances));
config = {
# expose helm helper methods as module argument
_module.args.helm = import ../lib/helm { inherit pkgs; };
kubernetes.api = mkMerge (flatten (mapAttrsToList (_: instance:
map (object: let
apiVersion = parseApiVersion object.apiVersion;
name = object.metadata.name;
in {
"${apiVersion.group}"."${apiVersion.version}".${object.kind}."${name}" = mkMerge ([
object
] ++ instance.overrides);
}) instance.objects
) cfg.instances));
};
}

View file

@ -68,6 +68,12 @@ let
default = null;
};
propagate = mkOption {
description = "Whether to propagate default";
type = types.bool;
default = false;
};
default = mkOption {
description = "Default to apply";
type = types.unspecified;
@ -164,107 +170,162 @@ let
};
}) cfg.customResources;
in {
# expose k8s helper methods as module argument
config._module.args.k8s = import ../lib/k8s.nix { inherit lib; };
imports = [ ./base.nix ./submodules.nix ];
options.kubernetes.version = mkOption {
description = "Kubernetes version to use";
type = types.enum ["1.7" "1.8" "1.9" "1.10" "1.11" "1.12" "1.13"];
default = "1.13";
};
options.kubernetes.resourceOrder = mkOption {
description = "Preffered resource order";
type = types.listOf types.str;
default = [
"CustomResourceDefinition"
"Namespace"
];
};
options.kubernetes.api = mkOption {
type = types.submodule {
imports = [
(./generated + ''/v'' + cfg.version + ".nix")
apiOptions
] ++ customResourceOptions;
options.kubernetes = {
version = mkOption {
description = "Kubernetes version to use";
type = types.enum ["1.7" "1.8" "1.9" "1.10" "1.11" "1.12" "1.13"];
default = "1.13";
};
default = {};
};
options.kubernetes.customResources = mkOption {
default = [];
description = "List of custom resource definitions to make API for";
type = types.listOf (types.submodule ({config, ...}: {
options = {
group = mkOption {
description = "Custom resource definition group";
type = types.str;
};
namespace = mkOption {
description = "Default namespace where to deploy kubernetes resources";
type = types.str;
default = "default";
};
version = mkOption {
description = "Custom resource definition version";
type = types.str;
};
resourceOrder = mkOption {
description = "Preffered resource order";
type = types.listOf types.str;
default = [
"CustomResourceDefinition"
"Namespace"
];
};
kind = mkOption {
description = "Custom resource definition kind";
type = types.str;
};
resource = mkOption {
description = "Custom resource definition resource name";
type = types.nullOr types.str;
default = null;
};
description = mkOption {
description = "Custom resource definition description";
type = types.str;
default = "";
};
module = mkOption {
description = "Custom resource definition module";
default = types.unspecified;
};
alias = mkOption {
description = "Alias to create for API";
type = types.nullOr types.str;
default = null;
};
api = mkOption {
type = types.submodule {
imports = [
(./generated + ''/v'' + cfg.version + ".nix")
apiOptions
] ++ customResourceOptions;
};
}));
default = {};
};
customResources = mkOption {
default = [];
description = "List of custom resource definitions to make API for";
type = types.listOf (types.submodule ({config, ...}: {
options = {
group = mkOption {
description = "Custom resource definition group";
type = types.str;
};
version = mkOption {
description = "Custom resource definition version";
type = types.str;
};
kind = mkOption {
description = "Custom resource definition kind";
type = types.str;
};
resource = mkOption {
description = "Custom resource definition resource name";
type = types.nullOr types.str;
default = null;
};
description = mkOption {
description = "Custom resource definition description";
type = types.str;
default = "";
};
module = mkOption {
description = "Custom resource definition module";
default = types.unspecified;
};
alias = mkOption {
description = "Alias to create for API";
type = types.nullOr types.str;
default = null;
};
};
}));
};
objects = mkOption {
description = "List of generated kubernetes objects";
type = types.listOf types.attrs;
apply = items: sort (r1: r2:
if elem r1.kind cfg.resourceOrder && elem r2.kind cfg.resourceOrder
then indexOf cfg.resourceOrder r1.kind < indexOf cfg.resourceOrder r2.kind
else if elem r1.kind cfg.resourceOrder then true else false
) (moduleToAttrs (unique items));
default = [];
};
generated = mkOption {
description = "Generated kubernetes list object";
type = types.attrs;
};
};
config.kubernetes.api.resources = map (cr: {
inherit (cr) group version kind resource;
}) cfg.customResources;
config = {
# expose k8s helper methods as module argument
_module.args.k8s = import ../lib/k8s.nix { inherit lib; };
options.kubernetes.objects = mkOption {
description = "List of generated kubernetes objects";
type = types.listOf types.attrs;
apply = items: sort (r1: r2:
if elem r1.kind cfg.resourceOrder && elem r2.kind cfg.resourceOrder
then indexOf cfg.resourceOrder r1.kind < indexOf cfg.resourceOrder r2.kind
else if elem r1.kind cfg.resourceOrder then true else false
) (moduleToAttrs (unique items));
default = [];
};
_module.features = [ "k8s" ];
config.kubernetes.objects = flatten (map (gvk:
mapAttrsToList (name: resource:
removeKubenixOptions (moduleToAttrs resource)
) cfg.api.${gvk.group}.${gvk.version}.${gvk.kind}
) cfg.api.resources);
kubernetes.api.resources = map (cr: {
inherit (cr) group version kind resource;
}) cfg.customResources;
options.kubernetes.generated = mkOption {
description = "Generated kubernetes list object";
type = types.attrs;
};
kubernetes.objects = mkMerge [
# gvk resources
(flatten (map (gvk:
mapAttrsToList (name: resource:
removeKubenixOptions (moduleToAttrs resource)
) cfg.api.${gvk.group}.${gvk.version}.${gvk.kind}
) cfg.api.resources))
config.kubernetes.generated = k8s.mkHashedList {
items = config.kubernetes.objects;
# passthru of child kubernetes objects if passthru is enabled on submodule
# and submodule has k8s module loaded
(flatten (mapAttrsToList (_: submodule:
optionals
(submodule.passthru.enable && (elem "k8s" submodule.config._module.features))
submodule.config.kubernetes.objects
) config.submodules.instances))
];
kubernetes.generated = k8s.mkHashedList {
items = config.kubernetes.objects;
labels."kubenix/project-name" = config.kubenix.project;
};
kubernetes.api.defaults = [{
default = {
metadata.namespace = mkDefault config.kubernetes.namespace;
metadata.labels = mkMerge [
{
"kubenix/project-name" = config.kubenix.project;
}
# if we are inside submodule, define additional labels
(mkIf (elem "submodule" config._module.features) {
"kubenix/module-name" = config.submodule.name;
"kubenix/module-version" = config.submodule.version;
})
];
};
}];
submodules.defaults = [{
features = [ "k8s" ];
default = { config, name, ... }: {
# propagate kubernetes version and namespace
kubernetes.version = mkDefault cfg.version;
kubernetes.namespace = mkDefault cfg.namespace;
# propagate defaults if default propagation is enabled
kubernetes.api.defaults = filter (default: default.propagate) cfg.api.defaults;
};
}];
};
}

View file

@ -1,139 +0,0 @@
{ config, lib, ... }:
with lib;
{
options = {
parentResource = {
apiVersion = mkOption {
description = "Parent resource apiVersion";
type = types.str;
example = "apps/v1";
};
resource = mkOption {
description = "The canonical, lowercase, plural name of the parent resource";
type = types.str;
example = "deployments";
};
revisionHistory = mkOption {
description = "A list of field path strings specifying which parent fields trigger rolling updates of children";
type = types.listOf types.str;
default = ["spec"];
example = ["spec.template"];
};
};
childResources = mkOption {
description = "A list of resource rules specifying the child resources";
type = types.listOf (types.submodule ({ config, ... }: {
options = {
apiVersion = mkOption {
description = "The API group/version of the child resource, or just version for core APIs";
type = types.str;
example = "apps/v1";
};
resource = mkOption {
description = "The canonical, lowercase, plural name of the child resource";
type = types.str;
example = "deployments";
};
updateStrategy = {
method = mkOption {
description = ''
A string indicating the overall method that should be used for updating this type of child resource.
The default is OnDelete, which means don't try to update children that already exist.
- OnDelete: Don't update existing children unless they get deleted by some other agent.
- Recreate: Immediately delete any children that differ from the desired state, and recreate them in the desired state.
- InPlace: Immediately update any children that differ from the desired state.
- RollingRecreate: Delete each child that differs from the desired state, one at a time,
and recreate each child before moving on to the next one.
Pause the rollout if at any time one of the children that have already been updated fails one or more status checks.
- RollingInPlace: Update each child that differs from the desired state, one at a time. Pause the rollout if at any time
one of the children that have already been updated fails one or more status checks.
'';
type = types.enum [
"OnDelete"
"Recreate"
"InPlace"
"RollingRecreate"
"RollingInPlace"
];
default = "OnDelete";
};
statusChecks.conditions = mkOption {
description = ''
A list of status condition checks that must all pass on already-updated
children for the rollout to continue.
'';
type = types.listOf (types.submodule ({ config, ... }: {
options = {
type = mkOption {
description = "A string specifying the status condition type to check.";
type = types.str;
};
status = mkOption {
description = ''
A string specifying the required status of the given status condition.
If none is specified, the condition's status is not checked.
'';
type = types.str;
default = "";
};
reason = mkOption {
description = ''
A string specifying the required reason of the given status condition.
If none is specified, the condition's reason is not checked.
'';
type = types.str;
default = "";
};
};
}));
default = [];
};
};
};
}));
default = [];
};
resyncPeriodSeconds = mkOption {
description = ''
How often, in seconds, you want every parent object to be resynced,
even if no changes are detected.
'';
type = types.int;
default = 0;
};
generateSelector = mkOption {
description = ''
If true, ignore the selector in each parent object and instead generate
a unique selector that prevents overlap with other objects.
'';
type = types.bool;
default = false;
};
hooks = {
sync.webhook.url = mkOption {
description = "Webhook URL where to send sync request";
type = types.str;
};
finalize.webhook.url = mkOption {
description = "Webhook URL where to send finalize request";
type = types.str;
default = "";
};
};
};
}

View file

@ -1,36 +0,0 @@
{ config, lib, ... }:
with lib;
{
imports = [ ../k8s.nix ];
options.metacontroller = {
compositeControllers = mkOption {
type = types.attrsOf (types.submodule ({ name, config, ... }: {
imports = [ ./compositecontroller.nix ];
options = {
name = mkOption {
description = "Name of the composite controller";
type = types.str;
default = name;
};
};
}));
default = {};
};
};
config = {
kubernetes.customResources = [{
group = "metacontroller.k8s.io";
version = "v1alpha1";
kind = "CompositeController";
resource = "compositecontrollers";
description = "Composite controller";
alias = "compositecontrollers";
module.imports = [ ./compositecontroller.nix ];
}];
};
}

View file

@ -1,75 +0,0 @@
# module.nix defines default kubenix module with additional helper options
# and preincluded kubenix module definitions for kubernetes, docker and
# kubenix submodules
{ config, lib, ... }:
with lib;
let
parentConfig = config;
in {
imports = [ ./k8s.nix ./docker.nix ./submodules.nix ];
options = {
kubenix.release = mkOption {
description = "Name of the release";
type = types.str;
default = "default";
};
kubernetes.propagateDefaults = mkOption {
description = "Whether to propagate child defaults to submodules";
type = types.bool;
default = true;
};
submodules.instances = mkOption {
type = types.attrsOf (types.submodule ({config, ...}: {
options = {
namespace = mkOption {
description = "Default kubernetes namespace";
type = types.str;
default = "default";
};
};
config.config = {
kubernetes.api.defaults = [{
default.metadata.namespace = mkDefault config.namespace;
}];
};
}));
};
};
config = {
submodules.defaults = [{
default = {
imports = [ ./module.nix ];
kubernetes.version = mkDefault config.kubernetes.version;
kubernetes.api.defaults =
mkIf config.kubernetes.propagateDefaults config.kubernetes.api.defaults;
};
} {
default = ({config, ...}: {
kubenix.release = parentConfig.kubenix.release;
kubernetes.api.defaults = [{
default.metadata.labels = {
"kubenix/release-name" = config.kubenix.release;
"kubenix/module-name" = config.submodule.name;
"kubenix/module-version" = config.submodule.version;
};
}];
});
}];
kubernetes.objects = mkMerge (mapAttrsToList (_: submodule:
submodule.config.kubernetes.objects
) config.submodules.instances);
docker.export = mkMerge (mapAttrsToList (_: submodule:
submodule.config.docker.export
) config.submodules.instances);
};
}

40
modules/submodule.nix Normal file
View file

@ -0,0 +1,40 @@
{ config, lib, ... }:
with lib;
{
imports = [ ./base.nix ];
options.submodule = {
name = mkOption {
description = "Module name";
type = types.str;
};
description = mkOption {
description = "Module description";
type = types.str;
default = "";
};
version = mkOption {
description = "Module version";
type = types.str;
default = "1.0.0";
};
tags = mkOption {
description = "List of submodule tags";
type = types.listOf types.str;
default = [];
};
passthru = mkOption {
description = "Attribute set to passthru to parent";
default = {};
type = types.attrs;
};
};
config._module.features = ["submodule"];
}

View file

@ -4,13 +4,18 @@ with lib;
let
cfg = config.submodules;
parentConfig = config;
getDefaults = name: tags:
getDefaults = {name, tags, features}:
catAttrs "default" (filter (submodule:
(submodule.name == null || submodule.name == name) &&
(
(length submodule.tags == 0) ||
(length (intersectLists submodule.tags tags)) > 0
) &&
(
(length submodule.features == 0) ||
(length (intersectLists submodule.features features)) > 0
)
) config.submodules.defaults);
@ -59,35 +64,6 @@ let
};
};
submoduleDefinitionOptions = {
name = mkOption {
description = "Module name";
type = types.str;
};
description = mkOption {
description = "Module description";
type = types.str;
default = "";
};
version = mkOption {
description = "Module version";
type = types.str;
default = "1.0.0";
};
tags = mkOption {
description = "List of submodule tags";
type = types.listOf types.str;
default = [];
};
};
submoduleOptions = {
options.submodule = submoduleDefinitionOptions;
};
specialArgs = cfg.specialArgs // {
parentConfig = config;
};
@ -113,6 +89,8 @@ let
else head versionSortedSubmodules;
in matchingModule;
in {
imports = [ ./base.nix ];
options = {
submodules.specialArgs = mkOption {
description = "Special args to pass to submodules. These arguments can be used for imports";
@ -136,6 +114,12 @@ in {
default = [];
};
features = mkOption {
description = "List of features that submodule has to have to apply defaults";
type = types.listOf types.str;
default = [];
};
default = mkOption {
description = "Default to apply to submodule instance";
type = types.unspecified;
@ -146,10 +130,10 @@ in {
default = [];
};
submodules.propagate = mkOption {
description = "Whether to propagate defaults and imports to submodule's submodules";
submodules.propagate.enable = mkOption {
description = "Whether to propagate defaults and imports from parent to child";
type = types.bool;
default = false;
default = true;
};
submodules.imports = mkOption {
@ -159,11 +143,16 @@ in {
types.path
(module: {inherit module;})
(types.submodule ({name, config, ...}: let
submoduleDefinition = (evalModules {
evaledSubmodule' = (evalModules {
inherit specialArgs;
modules = config.modules ++ [submoduleOptions];
modules = config.modules ++ [ ./base.nix ];
check = false;
}).config.submodule;
}).config;
evaledSubmodule =
if (!(elem "submodule" evaledSubmodule'._module.features))
then throw "no submodule defined"
else evaledSubmodule';
in {
options = {
module = mkOption {
@ -177,11 +166,23 @@ in {
default = [config.module];
};
definition = submoduleDefinitionOptions;
features = mkOption {
description = "List of features exposed by submodule";
type = types.listOf types.str;
};
definition = mkOption {
description = "Submodule definition";
type = types.attrs;
};
};
config.definition = {
inherit (submoduleDefinition) name description version tags;
config = {
definition = {
inherit (evaledSubmodule.submodule) name description version tags;
};
features = evaledSubmodule._module.features;
};
})
)
@ -203,7 +204,11 @@ in {
submoduleDefinition = submodule.definition;
# submodule defaults
defaults = getDefaults submoduleDefinition.name submoduleDefinition.tags;
defaults = getDefaults {
name = submoduleDefinition.name;
tags = submoduleDefinition.tags;
features = submodule.features;
};
in {
options = {
name = mkOption {
@ -227,10 +232,16 @@ in {
default = null;
};
passthru.enable = mkOption {
description = "Whether to passthru submodule resources";
type = types.bool;
default = true;
};
config = mkOption {
description = "Submodule instance ${config.name} for ${submoduleDefinition.name}:${submoduleDefinition.version} config";
type = submoduleWithSpecialArgs ({...}: {
imports = submodule.modules ++ defaults ++ [submoduleOptions];
imports = submodule.modules ++ defaults ++ [ ./base.nix ];
_module.args.pkgs = pkgs;
_module.args.name = config.name;
_module.args.submodule = config;
@ -243,16 +254,31 @@ in {
default = {};
};
config = {
submodules.specialArgs.kubenix = kubenix;
submodules.defaults = [(mkIf cfg.propagate {
default = {
imports = [./submodules.nix];
submodules = {
defaults = cfg.defaults;
imports = cfg.imports;
config = mkMerge [
{
_module.features = ["submodules"];
submodules.specialArgs.kubenix = kubenix;
# passthru kubenix.project to submodules
submodules.defaults = [{
default = {
kubenix.project = parentConfig.kubenix.project;
};
};
})];
};
}];
}
(mkIf cfg.propagate.enable {
# if propagate is enabled and submodule has submodules included propagage defaults and imports
submodules.defaults = [{
features = ["submodules"];
default = {
submodules = {
defaults = cfg.defaults;
imports = cfg.imports;
};
};
}];
})
];
}

View file

@ -4,7 +4,6 @@ with lib;
let
cfg = config.testing;
parentConfig = config;
nixosTesting = import <nixpkgs/nixos/lib/testing.nix> {
inherit pkgs;
@ -110,7 +109,9 @@ let
testOptions = {config, ...}: let
modules = [config.module ./test.nix {
config._module.args.test = config;
config._module.args = {
test = config;
} // cfg.args;
}] ++ cfg.defaults;
test = (kubenix.evalModules {
@ -229,6 +230,12 @@ in {
apply = tests: filter (test: test.enable) tests;
};
testing.args = mkOption {
description = "Attribute set of extra args passed to tests";
type = types.attrs;
default = {};
};
testing.testsByName = mkOption {
description = "Tests by name";
type = types.attrsOf types.attrs;
@ -249,6 +256,7 @@ in {
tests = map (test: {
inherit (test) name description success test;
assertions = moduleToAttrs test.assertions;
generated = test.generated;
}) (filter (test: test.enable) cfg.tests);
};
};

View file

@ -100,7 +100,7 @@ in rec {
results = mapAttrs (_: test: test.result) tests;
});
tests-check =
test-check =
if !(all (test: test.success) (attrValues tests))
then throw "tests failed"
else true;

View file

@ -26,19 +26,16 @@ let
./k8s/1.13/crd.nix
./k8s/defaults.nix
./k8s/order.nix
./k8s/submodule.nix
./helm/simple.nix
./istio/bookinfo.nix
./submodules/simple.nix
./submodules/defaults.nix
./submodules/versioning.nix
./module.nix
./metacontroller/compositecontroller.nix
];
testing.defaults = ({kubenix, ...}: {
imports = [kubenix.modules.k8s];
kubernetes.version = k8sVersion;
_module.args.images = images;
});
testing.args = {
inherit images k8sVersion;
};
}
];
args = {

View file

@ -1,4 +1,4 @@
{ config, lib, pkgs, kubenix, helm, ... }:
{ config, lib, pkgs, kubenix, helm, k8sVersion, ... }:
with lib;
with kubenix.lib;
@ -60,6 +60,8 @@ in {
'';
};
kubernetes.version = k8sVersion;
kubernetes.api.namespaces.test = {};
kubernetes.helm.instances.app-psql = {

View file

@ -1,4 +1,4 @@
{ config, kubenix, ... }:
{ config, kubenix, k8sVersion, ... }:
{
imports = with kubenix.modules; [ test k8s istio ];
@ -8,6 +8,8 @@
description = "Simple istio bookinfo application (WIP)";
};
kubernetes.version = k8sVersion;
kubernetes.api."networking.istio.io"."v1alpha3" = {
Gateway."bookinfo-gateway" = {
spec = {

View file

@ -1,4 +1,4 @@
{ config, lib, kubenix, ... }:
{ config, lib, kubenix, k8sVersion, ... }:
with lib;
@ -17,6 +17,8 @@ in {
}];
};
kubernetes.version = k8sVersion;
kubernetes.api.customresourcedefinitions.crontabs = {
metadata.name = "crontabs.stable.example.com";
spec = {

View file

@ -1,4 +1,4 @@
{ config, lib, kubenix, pkgs, ... }:
{ config, lib, kubenix, pkgs, k8sVersion, ... }:
with lib;
@ -22,6 +22,8 @@ in {
'';
};
kubernetes.version = k8sVersion;
kubernetes.api.customresourcedefinitions.crontabs = {
metadata.name = "crontabs.stable.example.com";
spec = {

View file

@ -1,4 +1,4 @@
{ config, lib, kubenix, ... }:
{ config, lib, kubenix, k8sVersion, ... }:
with lib;
@ -23,6 +23,8 @@ in {
}];
};
kubernetes.version = k8sVersion;
kubernetes.api.pods.pod1 = {};
kubernetes.api.pods.pod2 = {

View file

@ -1,4 +1,4 @@
{ config, lib, pkgs, kubenix, images, ... }:
{ config, lib, pkgs, kubenix, images, k8sVersion, ... }:
with lib;
@ -41,6 +41,8 @@ in {
docker.images.nginx.image = image;
kubernetes.version = k8sVersion;
kubernetes.api.deployments.nginx = {
spec = {
replicas = 10;

View file

@ -1,4 +1,4 @@
{ config, lib, kubenix, pkgs, ... }:
{ config, lib, kubenix, pkgs, k8sVersion, ... }:
with lib;
@ -20,6 +20,8 @@ in {
}];
};
kubernetes.version = k8sVersion;
kubernetes.api.customresourcedefinitions.crontabs = {
metadata.name = "crontabs.stable.example.com";
spec = {

View file

@ -1,4 +1,4 @@
{ config, kubenix, ... }:
{ config, kubenix, k8sVersion, ... }:
let
cfg = config.kubernetes.api.pods.nginx;
@ -17,5 +17,7 @@ in {
}];
};
kubernetes.version = k8sVersion;
kubernetes.api.pods.nginx = {};
}

56
tests/k8s/submodule.nix Normal file
View file

@ -0,0 +1,56 @@
{ name, config, lib, kubenix, images, ... }:
with lib;
let
cfg = config.submodules.instances.passthru;
in {
imports = with kubenix.modules; [ test submodules k8s docker ];
test = {
name = "k8s-submodule";
description = "Simple k8s submodule test";
assertions = [{
message = "Submodule has correct name set";
assertion = (head config.kubernetes.objects).metadata.name == "passthru";
} {
message = "Should expose docker image";
assertion = (head config.docker.export).imageName == "xtruder/nginx";
}];
};
kubenix.project = "test-release";
kubernetes.namespace = "test-namespace";
submodules.imports = [{
module = {name, config, ...}: {
imports = with kubenix.modules; [ submodule k8s docker ];
config = {
submodule.name = "test-submodule";
kubernetes.api.pods.nginx = {
metadata.name = name;
spec.containers.nginx.image = config.docker.images.nginx.path;
};
docker.images.nginx.image = images.nginx;
};
};
}];
kubernetes.api.defaults = [{
propagate = true;
default.metadata.labels.my-label = "my-value";
}];
submodules.instances.passthru = {
submodule = "test-submodule";
};
submodules.instances.no-passthru = {
submodule = "test-submodule";
passthru.enable = false;
};
}

View file

@ -9,23 +9,27 @@ let
instance4 = config.submodules.instances.instance4;
instance5 = config.submodules.instances.instance5;
module = {name, ...}: {
options.args.value = mkOption {
description = "Submodule value";
type = types.str;
};
submodule = {name, ...}: {
imports = [ kubenix.modules.submodule ];
options.args.defaultValue = mkOption {
description = "Submodule default value";
type = types.str;
options = {
args.value = mkOption {
description = "Submodule value";
type = types.str;
};
args.defaultValue = mkOption {
description = "Submodule default value";
type = types.str;
};
};
};
in {
imports = with kubenix.modules; [ test submodules ];
test = {
name = "submodules-defatuls";
description = "Simple k8s submodule test";
name = "submodules-defaults";
description = "Simple submodule test";
assertions = [{
message = "should apply defaults by tag1";
assertion = instance1.config.args.value == "value1";
@ -53,34 +57,34 @@ in {
};
submodules.imports = [{
modules = [module {
modules = [submodule {
submodule = {
name = "submodule1";
tags = ["tag1"];
};
}];
} {
modules = [module {
modules = [submodule {
submodule = {
name = "submodule2";
tags = ["tag2"];
};
}];
} {
modules = [module {
modules = [submodule {
submodule = {
name = "submodule3";
tags = ["tag2"];
};
}];
} {
modules = [module {
modules = [submodule {
submodule = {
name = "submodule4";
};
}];
} {
modules = [module {
modules = [submodule {
submodule = {
name = "submodule5";
};

View file

@ -25,20 +25,30 @@ in {
} {
message = "Should have submodule name set";
assertion = cfg.config.args.name == "instance";
} {
message = "should have tag set";
assertion = elem "tag" (cfg.config.submodule.tags);
}];
};
submodules.propagate.enable = true;
submodules.imports = [{
module = {name, ...}: {
config.submodule.name = "submodule";
options.args.value = mkOption {
description = "Submodule argument";
type = types.str;
imports = [ kubenix.modules.submodule ];
config = {
submodule.name = "submodule";
submodule.tags = ["tag"];
};
options.args.name = mkOption {
description = "Submodule name";
type = types.str;
default = name;
options = {
args.value = mkOption {
description = "Submodule argument";
type = types.str;
};
args.name = mkOption {
description = "Submodule name";
type = types.str;
default = name;
};
};
};
}];

View file

@ -8,12 +8,14 @@ let
inst-latest = config.submodules.instances.inst-latest.config;
submodule = {
config.submodule.name = "subm";
imports = [ kubenix.modules.submodule ];
options.version = mkOption {
type = types.str;
default = "undefined";
};
config.submodule.name = "subm";
};
in {
imports = with kubenix.modules; [ test submodules ];