mirror of
https://github.com/TECHNOFAB11/kubenix.git
synced 2025-12-12 08:00:06 +01:00
fixup tests
This commit is contained in:
parent
bf231d19fa
commit
39badb2084
34 changed files with 88 additions and 110 deletions
39
modules/base.nix
Normal file
39
modules/base.nix
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
options = {
|
||||
kubenix.project = mkOption {
|
||||
description = "Name of the project";
|
||||
type = types.str;
|
||||
default = "kubenix";
|
||||
};
|
||||
|
||||
_m.features = mkOption {
|
||||
description = "List of features exposed by module";
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
_m.propagate = mkOption {
|
||||
description = "Module propagation options";
|
||||
type = types.listOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
features = mkOption {
|
||||
description = "List of features that submodule has to have to propagate module";
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
module = mkOption {
|
||||
description = "Module to propagate";
|
||||
type = types.unspecified;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
}
|
||||
11
modules/default.nix
Normal file
11
modules/default.nix
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
k8s = ./k8s.nix;
|
||||
istio = ./istio.nix;
|
||||
submodules = ./submodules.nix;
|
||||
submodule = ./submodule.nix;
|
||||
helm = ./helm.nix;
|
||||
docker = ./docker.nix;
|
||||
testing = ./testing;
|
||||
test = ./testing/test-options.nix;
|
||||
base = ./base.nix;
|
||||
}
|
||||
94
modules/docker.nix
Normal file
94
modules/docker.nix
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
{ config, lib, pkgs, docker, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.docker;
|
||||
in
|
||||
{
|
||||
imports = [ ./base.nix ];
|
||||
|
||||
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 = [ ];
|
||||
};
|
||||
|
||||
copyScript = mkOption {
|
||||
description = "Image copy script";
|
||||
type = types.package;
|
||||
default = docker.copyDockerImages {
|
||||
dest = "docker://${cfg.registry.url}";
|
||||
images = cfg.export;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
# define docker feature
|
||||
_m.features = [ "docker" ];
|
||||
|
||||
# propagate docker options if docker feature is enabled
|
||||
_m.propagate = [{
|
||||
features = [ "docker" ];
|
||||
module = { config, name, ... }: {
|
||||
# propagate registry options
|
||||
docker.registry = cfg.registry;
|
||||
};
|
||||
}];
|
||||
|
||||
# pass docker library as param
|
||||
_module.args.docker = import ../lib/docker { inherit lib pkgs; };
|
||||
|
||||
# list of exported docker images
|
||||
docker.export = mapAttrsToList (_: i: i.image)
|
||||
(filterAttrs (_: i: i.registry != null) config.docker.images);
|
||||
};
|
||||
}
|
||||
22379
modules/generated/v1.19.nix
Normal file
22379
modules/generated/v1.19.nix
Normal file
File diff suppressed because it is too large
Load diff
23483
modules/generated/v1.20.nix
Normal file
23483
modules/generated/v1.20.nix
Normal file
File diff suppressed because it is too large
Load diff
23767
modules/generated/v1.21.nix
Normal file
23767
modules/generated/v1.21.nix
Normal file
File diff suppressed because it is too large
Load diff
118
modules/helm.nix
Normal file
118
modules/helm.nix
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# helm defines kubenix module with options for using helm charts
|
||||
# with kubenix
|
||||
|
||||
{ config, lib, pkgs, helm, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.kubernetes.helm;
|
||||
|
||||
globalConfig = config;
|
||||
|
||||
recursiveAttrs = mkOptionType {
|
||||
name = "recursive-attrs";
|
||||
description = "recursive attribute set";
|
||||
check = isAttrs;
|
||||
merge = loc: foldl' (res: def: recursiveUpdate res def.value) { };
|
||||
};
|
||||
|
||||
parseApiVersion = apiVersion:
|
||||
let
|
||||
splitted = splitString "/" apiVersion;
|
||||
in
|
||||
{
|
||||
group = if length splitted == 1 then "core" else head splitted;
|
||||
version = last splitted;
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
imports = [ ./k8s.nix ];
|
||||
|
||||
options.kubernetes.helm = {
|
||||
instances = mkOption {
|
||||
description = "Attribute set of helm instances";
|
||||
type = types.attrsOf (types.submodule ({ config, name, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Helm release name";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
chart = mkOption {
|
||||
description = "Helm chart to use";
|
||||
type = types.package;
|
||||
};
|
||||
|
||||
namespace = mkOption {
|
||||
description = "Namespace to install helm chart to";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
values = mkOption {
|
||||
description = "Values to pass to chart";
|
||||
type = recursiveAttrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
kubeVersion = mkOption {
|
||||
description = "Kubernetes version to build chart for";
|
||||
type = types.str;
|
||||
default = globalConfig.kubernetes.version;
|
||||
};
|
||||
|
||||
overrides = mkOption {
|
||||
description = "Overrides to apply to all chart resources";
|
||||
type = types.listOf types.unspecified;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
overrideNamespace = mkOption {
|
||||
description = "Whether to apply namespace override";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
objects = mkOption {
|
||||
description = "Generated kubernetes objects";
|
||||
type = types.listOf types.attrs;
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
config.overrides = mkIf (config.overrideNamespace && config.namespace != null) [{
|
||||
metadata.namespace = config.namespace;
|
||||
}];
|
||||
|
||||
config.objects = importJSON (helm.chart2json {
|
||||
inherit (config) chart name namespace values kubeVersion;
|
||||
});
|
||||
}));
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
# expose helm helper methods as module argument
|
||||
_module.args.helm = import ../lib/helm { inherit pkgs; };
|
||||
|
||||
kubernetes.api.resources = 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));
|
||||
};
|
||||
}
|
||||
16
modules/istio-overrides.nix
Normal file
16
modules/istio-overrides.nix
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{ lib, definitions }:
|
||||
|
||||
with lib;
|
||||
|
||||
{
|
||||
"istio_networking_v1alpha3_StringMatch" = recursiveUpdate
|
||||
(recursiveUpdate
|
||||
definitions."istio_networking_v1alpha3_StringMatch_Exact"
|
||||
definitions."istio_networking_v1alpha3_StringMatch_Prefix"
|
||||
)
|
||||
definitions."istio_networking_v1alpha3_StringMatch_Regex";
|
||||
|
||||
"istio_networking_v1alpha3_PortSelector" = recursiveUpdate
|
||||
definitions."istio_networking_v1alpha3_PortSelector_Name"
|
||||
definitions."istio_networking_v1alpha3_PortSelector_Number";
|
||||
}
|
||||
4963
modules/istio.nix
Normal file
4963
modules/istio.nix
Normal file
File diff suppressed because it is too large
Load diff
478
modules/k8s.nix
Normal file
478
modules/k8s.nix
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
# K8S module defines kubernetes definitions for kubenix
|
||||
|
||||
{ options, config, lib, pkgs, k8s, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.kubernetes;
|
||||
|
||||
gvkKeyFn = type: "${type.group}/${type.version}/${type.kind}";
|
||||
|
||||
getDefaults = resource: group: version: kind:
|
||||
catAttrs "default" (filter
|
||||
(default:
|
||||
(resource == null || default.resource == null || default.resource == resource) &&
|
||||
(default.group == null || default.group == group) &&
|
||||
(default.version == null || default.version == version) &&
|
||||
(default.kind == null || default.kind == kind)
|
||||
)
|
||||
cfg.api.defaults);
|
||||
|
||||
moduleToAttrs = value:
|
||||
if isAttrs value
|
||||
then mapAttrs (n: v: moduleToAttrs v) (filterAttrs (n: v: v != null && !(hasPrefix "_" n)) value)
|
||||
|
||||
else if isList value
|
||||
then map (v: moduleToAttrs v) value
|
||||
|
||||
else value;
|
||||
|
||||
apiOptions = { config, ... }: {
|
||||
options = {
|
||||
definitions = mkOption {
|
||||
description = "Attribute set of kubernetes definitions";
|
||||
};
|
||||
|
||||
defaults = mkOption {
|
||||
description = "Kubernetes defaults to apply to resources";
|
||||
type = types.listOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
group = mkOption {
|
||||
description = "Group to apply default to (all by default)";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
description = "Version to apply default to (all by default)";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
kind = mkOption {
|
||||
description = "Kind to apply default to (all by default)";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
resource = mkOption {
|
||||
description = "Resource to apply default to (all by default)";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
propagate = mkOption {
|
||||
description = "Whether to propagate defaults";
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
default = mkOption {
|
||||
description = "Default to apply";
|
||||
type = types.unspecified;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = [ ];
|
||||
apply = unique;
|
||||
};
|
||||
|
||||
types = mkOption {
|
||||
description = "List of registered kubernetes types";
|
||||
type = coerceListOfSubmodulesToAttrs
|
||||
{
|
||||
options = {
|
||||
group = mkOption {
|
||||
description = "Resource type group";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
description = "Resoruce type version";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
kind = mkOption {
|
||||
description = "Resource type kind";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
description = "Resource type name";
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
attrName = mkOption {
|
||||
description = "Name of the nixified attribute";
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
}
|
||||
gvkKeyFn;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
# apply aliased option
|
||||
resources = mkAliasDefinitions options.kubernetes.resources;
|
||||
};
|
||||
};
|
||||
|
||||
indexOf = lst: value:
|
||||
head (filter (v: v != -1) (imap0 (i: v: if v == value then i else -1) lst));
|
||||
|
||||
compareVersions = ver1: ver2:
|
||||
let
|
||||
getVersion = v: substring 1 10 v;
|
||||
splittedVer1 = builtins.splitVersion (getVersion ver1);
|
||||
splittedVer2 = builtins.splitVersion (getVersion ver2);
|
||||
|
||||
v1 = if length splittedVer1 == 1 then "${getVersion ver1}prod" else getVersion ver1;
|
||||
v2 = if length splittedVer2 == 1 then "${getVersion ver2}prod" else getVersion ver2;
|
||||
in
|
||||
builtins.compareVersions v1 v2;
|
||||
|
||||
customResourceTypesByAttrName = zipAttrs (mapAttrsToList
|
||||
(_: resourceType: {
|
||||
${resourceType.attrName} = resourceType;
|
||||
})
|
||||
cfg.customTypes);
|
||||
|
||||
customResourceTypesByAttrNameSortByVersion = mapAttrs
|
||||
(_: resourceTypes:
|
||||
reverseList (sort
|
||||
(r1: r2:
|
||||
compareVersions r1.version r2.version > 0
|
||||
)
|
||||
resourceTypes)
|
||||
)
|
||||
customResourceTypesByAttrName;
|
||||
|
||||
latestCustomResourceTypes =
|
||||
mapAttrsToList (_: resources: last resources) customResourceTypesByAttrNameSortByVersion;
|
||||
|
||||
customResourceModuleForType = config: ct: { name, ... }: {
|
||||
imports = getDefaults ct.name ct.group ct.version ct.kind;
|
||||
options = {
|
||||
apiVersion = mkOption {
|
||||
description = "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources";
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
kind = mkOption {
|
||||
description = "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds";
|
||||
type = types.nullOr types.str;
|
||||
};
|
||||
|
||||
metadata = mkOption {
|
||||
description = "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.";
|
||||
type = types.nullOr (types.submodule config.definitions."io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta");
|
||||
};
|
||||
|
||||
spec = mkOption {
|
||||
description = "Module spec";
|
||||
type = types.either types.attrs (types.submodule ct.module);
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
apiVersion = mkOptionDefault "${ct.group}/${ct.version}";
|
||||
kind = mkOptionDefault ct.kind;
|
||||
metadata.name = mkDefault name;
|
||||
};
|
||||
};
|
||||
|
||||
customResourceOptions = (mapAttrsToList
|
||||
(_: ct: { config, ... }:
|
||||
let
|
||||
module = customResourceModuleForType config ct;
|
||||
in
|
||||
{
|
||||
options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption {
|
||||
description = ct.description;
|
||||
type = types.attrsOf (types.submodule module);
|
||||
default = { };
|
||||
};
|
||||
})
|
||||
cfg.customTypes) ++ (map
|
||||
(ct: { options, config, ... }:
|
||||
let
|
||||
module = customResourceModuleForType config ct;
|
||||
in
|
||||
{
|
||||
options.resources.${ct.attrName} = mkOption {
|
||||
description = ct.description;
|
||||
type = types.attrsOf (types.submodule module);
|
||||
default = { };
|
||||
};
|
||||
|
||||
config.resources.${ct.group}.${ct.version}.${ct.kind} =
|
||||
mkAliasDefinitions options.resources.${ct.attrName};
|
||||
})
|
||||
latestCustomResourceTypes);
|
||||
|
||||
coerceListOfSubmodulesToAttrs = submodule: keyFn:
|
||||
let
|
||||
mergeValuesByFn = keyFn: values:
|
||||
listToAttrs (map
|
||||
(value:
|
||||
nameValuePair (toString (keyFn value)) value
|
||||
)
|
||||
values);
|
||||
|
||||
# Either value of type `finalType` or `coercedType`, the latter is
|
||||
# converted to `finalType` using `coerceFunc`.
|
||||
coercedTo = coercedType: coerceFunc: finalType:
|
||||
mkOptionType rec {
|
||||
name = "coercedTo";
|
||||
description = "${finalType.description} or ${coercedType.description}";
|
||||
check = x: finalType.check x || coercedType.check x;
|
||||
merge = loc: defs:
|
||||
let
|
||||
coerceVal = val:
|
||||
if finalType.check val then
|
||||
val
|
||||
else
|
||||
let coerced = coerceFunc val; in assert finalType.check coerced; coerced;
|
||||
|
||||
in
|
||||
finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
|
||||
getSubOptions = finalType.getSubOptions;
|
||||
getSubModules = finalType.getSubModules;
|
||||
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
|
||||
typeMerge = t1: t2: null;
|
||||
functor = (defaultFunctor name) // { wrapped = finalType; };
|
||||
};
|
||||
in
|
||||
coercedTo
|
||||
(types.listOf (types.submodule submodule))
|
||||
(mergeValuesByFn keyFn)
|
||||
(types.attrsOf (types.submodule submodule));
|
||||
|
||||
in
|
||||
{
|
||||
imports = [ ./base.nix ];
|
||||
|
||||
options.kubernetes = {
|
||||
version = mkOption {
|
||||
description = "Kubernetes version to use";
|
||||
type = types.enum [ "1.19" "1.20" "1.21" ];
|
||||
default = "1.21";
|
||||
};
|
||||
|
||||
namespace = mkOption {
|
||||
description = "Default namespace where to deploy kubernetes resources";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
resourceOrder = mkOption {
|
||||
description = "Preffered resource order";
|
||||
type = types.listOf types.str;
|
||||
default = [
|
||||
"CustomResourceDefinition"
|
||||
"Namespace"
|
||||
];
|
||||
};
|
||||
|
||||
api = mkOption {
|
||||
type = types.submodule {
|
||||
imports = [
|
||||
(./generated + ''/v'' + cfg.version + ".nix")
|
||||
apiOptions
|
||||
] ++ customResourceOptions;
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
|
||||
imports = mkOption {
|
||||
type = types.listOf (types.either types.package types.path);
|
||||
description = "List of resources to import";
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
resources = mkOption {
|
||||
description = "Alias for `config.kubernetes.api.resources` options";
|
||||
default = { };
|
||||
type = types.attrsOf types.attrs;
|
||||
};
|
||||
|
||||
customTypes = mkOption {
|
||||
description = "List of custom resource types to make API for";
|
||||
type = coerceListOfSubmodulesToAttrs
|
||||
{
|
||||
options = {
|
||||
group = mkOption {
|
||||
description = "Custom type group";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
description = "Custom type version";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
kind = mkOption {
|
||||
description = "Custom type kind";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
name = mkOption {
|
||||
description = "Custom type resource name";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
attrName = mkOption {
|
||||
description = "Name of the nixified attribute";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
description = "Custom type description";
|
||||
type = types.str;
|
||||
default = "";
|
||||
};
|
||||
|
||||
module = mkOption {
|
||||
description = "Custom type module";
|
||||
type = types.unspecified;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
}
|
||||
gvkKeyFn;
|
||||
default = { };
|
||||
};
|
||||
|
||||
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
|
||||
)
|
||||
(unique items);
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
generated = mkOption {
|
||||
description = "Generated kubernetes list object";
|
||||
type = types.attrs;
|
||||
};
|
||||
|
||||
result = mkOption {
|
||||
description = "Generated kubernetes JSON file";
|
||||
type = types.package;
|
||||
};
|
||||
|
||||
resultYAML = mkOption {
|
||||
description = "Genrated kubernetes YAML file";
|
||||
type = types.package;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
# features that module is defining
|
||||
_m.features = [ "k8s" ];
|
||||
|
||||
# module propagation options
|
||||
_m.propagate = [{
|
||||
features = [ "k8s" ];
|
||||
module = { config, ... }: {
|
||||
# propagate kubernetes version and namespace
|
||||
kubernetes.version = mkDefault cfg.version;
|
||||
kubernetes.namespace = mkDefault cfg.namespace;
|
||||
};
|
||||
}
|
||||
{
|
||||
features = [ "k8s" "submodule" ];
|
||||
module = { config, ... }: {
|
||||
# set module defaults
|
||||
kubernetes.api.defaults = (
|
||||
# propagate defaults if default propagation is enabled
|
||||
(filter (default: default.propagate) cfg.api.defaults) ++
|
||||
|
||||
[
|
||||
# set module name and version for all kuberentes resources
|
||||
{
|
||||
default.metadata.labels = {
|
||||
"kubenix/module-name" = config.submodule.name;
|
||||
"kubenix/module-version" = config.submodule.version;
|
||||
};
|
||||
}
|
||||
]
|
||||
);
|
||||
};
|
||||
}];
|
||||
|
||||
# expose k8s helper methods as module argument
|
||||
_module.args.k8s = import ../lib/k8s { inherit lib; };
|
||||
|
||||
kubernetes.api = mkMerge ([{
|
||||
# register custom types
|
||||
types = mapAttrsToList
|
||||
(_: cr: {
|
||||
inherit (cr) name group version kind attrName;
|
||||
})
|
||||
cfg.customTypes;
|
||||
|
||||
defaults = [{
|
||||
default = {
|
||||
# set default kubernetes namespace to all resources
|
||||
metadata.namespace = mkIf (config.kubernetes.namespace != null)
|
||||
(mkDefault config.kubernetes.namespace);
|
||||
|
||||
# set project name to all resources
|
||||
metadata.annotations = {
|
||||
"kubenix/project-name" = config.kubenix.project;
|
||||
"kubenix/k8s-version" = cfg.version;
|
||||
};
|
||||
};
|
||||
}];
|
||||
}] ++
|
||||
|
||||
# import of yaml files
|
||||
(map
|
||||
(i:
|
||||
let
|
||||
# load yaml file
|
||||
object = importYAML i;
|
||||
groupVersion = splitString "/" object.apiVersion;
|
||||
name = object.metadata.name;
|
||||
version = last groupVersion;
|
||||
group =
|
||||
if version == (head groupVersion)
|
||||
then "core" else head groupVersion;
|
||||
kind = object.kind;
|
||||
in
|
||||
{
|
||||
resources.${group}.${version}.${kind}.${name} = object;
|
||||
})
|
||||
cfg.imports));
|
||||
|
||||
kubernetes.objects = flatten (mapAttrsToList
|
||||
(_: type:
|
||||
mapAttrsToList (name: resource: moduleToAttrs resource)
|
||||
cfg.api.resources.${type.group}.${type.version}.${type.kind}
|
||||
)
|
||||
cfg.api.types);
|
||||
|
||||
kubernetes.generated = k8s.mkHashedList {
|
||||
items = config.kubernetes.objects;
|
||||
labels."kubenix/project-name" = config.kubenix.project;
|
||||
labels."kubenix/k8s-version" = config.kubernetes.version;
|
||||
};
|
||||
|
||||
kubernetes.result =
|
||||
pkgs.writeText "${config.kubenix.project}-generated.json" (builtins.toJSON cfg.generated);
|
||||
|
||||
kubernetes.resultYAML =
|
||||
toMultiDocumentYaml "${config.kubenix.project}-generated.yaml" (config.kubernetes.objects);
|
||||
};
|
||||
}
|
||||
48
modules/submodule.nix
Normal file
48
modules/submodule.nix
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
{ 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 = [ ];
|
||||
};
|
||||
|
||||
exports = mkOption {
|
||||
description = "Attribute set of functions to export";
|
||||
type = types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
passthru = mkOption {
|
||||
description = "Attribute set to passthru";
|
||||
type = types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
args._empty = mkOption { };
|
||||
};
|
||||
|
||||
config._module.args.args = config.submodule.args;
|
||||
config._m.features = [ "submodule" ];
|
||||
}
|
||||
361
modules/submodules.nix
Normal file
361
modules/submodules.nix
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
{ config, options, kubenix, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.submodules;
|
||||
parentConfig = config;
|
||||
|
||||
matchesVersion = requiredVersion: version:
|
||||
if requiredVersion != null then
|
||||
if hasPrefix "~" requiredVersion
|
||||
then (builtins.match (removePrefix "~" requiredVersion) version) != null
|
||||
else requiredVersion == version
|
||||
else true;
|
||||
|
||||
getDefaults = { name, version, tags, features }:
|
||||
catAttrs "default" (filter
|
||||
(submoduleDefault:
|
||||
(submoduleDefault.name == null || submoduleDefault.name == name) &&
|
||||
(matchesVersion submoduleDefault.version version) &&
|
||||
(
|
||||
(length submoduleDefault.tags == 0) ||
|
||||
(length (intersectLists submoduleDefault.tags tags)) > 0
|
||||
) &&
|
||||
(
|
||||
(length submoduleDefault.features == 0) ||
|
||||
(length (intersectLists submoduleDefault.features features)) > 0
|
||||
)
|
||||
)
|
||||
config.submodules.defaults);
|
||||
|
||||
specialArgs = cfg.specialArgs // {
|
||||
parentConfig = config;
|
||||
};
|
||||
|
||||
findSubmodule = { name, version ? null, latest ? true }:
|
||||
let
|
||||
matchingSubmodules = filter
|
||||
(el:
|
||||
el.definition.name == name &&
|
||||
(matchesVersion version el.definition.version)
|
||||
)
|
||||
cfg.imports;
|
||||
|
||||
versionSortedSubmodules = sort
|
||||
(s1: s2:
|
||||
if builtins.compareVersions s1.definition.version s2.definition.version > 0
|
||||
then true else false
|
||||
)
|
||||
matchingSubmodules;
|
||||
|
||||
matchingModule =
|
||||
if length versionSortedSubmodules == 0
|
||||
then throw "No module found ${name}/${if version == null then "latest" else version}"
|
||||
else head versionSortedSubmodules;
|
||||
in
|
||||
matchingModule;
|
||||
|
||||
passthruConfig = mapAttrsToList
|
||||
(name: opt: {
|
||||
${name} = mkMerge (mapAttrsToList
|
||||
(_: inst:
|
||||
if inst.passthru.enable
|
||||
then inst.config.submodule.passthru.${name} or { }
|
||||
else { }
|
||||
)
|
||||
config.submodules.instances);
|
||||
|
||||
_module.args = mkMerge (mapAttrsToList
|
||||
(_: inst:
|
||||
if inst.passthru.enable
|
||||
then inst.config.submodule.passthru._module.args or { }
|
||||
else { }
|
||||
)
|
||||
config.submodules.instances);
|
||||
})
|
||||
(removeAttrs options [ "_definedNames" "_module" "_m" "submodules" ]);
|
||||
|
||||
submoduleWithSpecialArgs = opts: specialArgs:
|
||||
let
|
||||
opts' = toList opts;
|
||||
inherit (lib.modules) evalModules;
|
||||
in
|
||||
mkOptionType rec {
|
||||
name = "submodule";
|
||||
check = x: isAttrs x || isFunction x;
|
||||
merge = loc: defs:
|
||||
let
|
||||
coerce = def: if isFunction def then def else { config = def; };
|
||||
modules = opts' ++ map (def: { _file = def.file; imports = [ (coerce def.value) ]; }) defs;
|
||||
in
|
||||
(evalModules {
|
||||
inherit modules specialArgs;
|
||||
prefix = loc;
|
||||
}).config;
|
||||
getSubOptions = prefix: (evalModules
|
||||
{
|
||||
modules = opts'; inherit prefix specialArgs;
|
||||
# This is a work-around due to the fact that some sub-modules,
|
||||
# such as the one included in an attribute set, expects a "args"
|
||||
# attribute to be given to the sub-module. As the option
|
||||
# evaluation does not have any specific attribute name, we
|
||||
# provide a default one for the documentation.
|
||||
#
|
||||
# This is mandatory as some option declaration might use the
|
||||
# "name" attribute given as argument of the submodule and use it
|
||||
# as the default of option declarations.
|
||||
#
|
||||
# Using lookalike unicode single angle quotation marks because
|
||||
# of the docbook transformation the options receive. In all uses
|
||||
# > and < wouldn't be encoded correctly so the encoded values
|
||||
# would be used, and use of `<` and `>` would break the XML document.
|
||||
# It shouldn't cause an issue since this is cosmetic for the manual.
|
||||
args.name = "‹name›";
|
||||
}).options;
|
||||
getSubModules = opts';
|
||||
substSubModules = m: submoduleWithSpecialArgs m specialArgs;
|
||||
functor = (defaultFunctor name) // {
|
||||
# Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
|
||||
# each submodule with its location.
|
||||
payload = [ ];
|
||||
binOp = lhs: rhs: [ ];
|
||||
};
|
||||
};
|
||||
in
|
||||
{
|
||||
imports = [ ./base.nix ];
|
||||
|
||||
options = {
|
||||
submodules.specialArgs = mkOption {
|
||||
description = "Special args to pass to submodules. These arguments can be used for imports";
|
||||
type = types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
submodules.defaults = mkOption {
|
||||
description = "List of defaults to apply to submodule instances";
|
||||
type = types.listOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Name of the submodule to apply defaults for";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
description = ''
|
||||
Version of submodule to apply defaults for. If version starts with
|
||||
"~" it is threated as regex pattern for example "~1.0.*
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
tags = mkOption {
|
||||
description = "List of tags to apply defaults for";
|
||||
type = types.listOf types.str;
|
||||
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;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
submodules.propagate.enable = mkOption {
|
||||
description = "Whether to propagate defaults and imports from parent to child";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
submodules.imports = mkOption {
|
||||
description = "List of submodule imports";
|
||||
type = types.listOf (
|
||||
types.coercedTo
|
||||
types.path
|
||||
(module: { inherit module; })
|
||||
(types.submodule ({ name, config, ... }:
|
||||
let
|
||||
evaledSubmodule' = evalModules {
|
||||
inherit specialArgs;
|
||||
modules = config.modules ++ [ ./base.nix ];
|
||||
check = false;
|
||||
};
|
||||
|
||||
evaledSubmodule =
|
||||
if (!(elem "submodule" evaledSubmodule'.config._m.features))
|
||||
then throw "no submodule defined"
|
||||
else evaledSubmodule';
|
||||
in
|
||||
{
|
||||
options = {
|
||||
module = mkOption {
|
||||
description = "Module defining submodule";
|
||||
type = types.unspecified;
|
||||
};
|
||||
|
||||
modules = mkOption {
|
||||
description = "List of modules defining submodule";
|
||||
type = types.listOf types.unspecified;
|
||||
default = [ config.module ];
|
||||
};
|
||||
|
||||
features = mkOption {
|
||||
description = "List of features exposed by submodule";
|
||||
type = types.listOf types.str;
|
||||
};
|
||||
|
||||
definition = mkOption {
|
||||
description = "Submodule definition";
|
||||
type = types.attrs;
|
||||
};
|
||||
|
||||
exportAs = mkOption {
|
||||
description = "Name under which to register exports";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
definition = {
|
||||
inherit (evaledSubmodule.config.submodule) name description version tags exports;
|
||||
};
|
||||
|
||||
features = evaledSubmodule.config._m.features;
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
submodules.instances = mkOption {
|
||||
description = "Attribute set of submodule instances";
|
||||
default = { };
|
||||
type = types.attrsOf (types.submodule ({ name, config, options, ... }:
|
||||
let
|
||||
# submodule associated with
|
||||
submodule = findSubmodule {
|
||||
name = config.submodule;
|
||||
version = config.version;
|
||||
};
|
||||
|
||||
# definition of a submodule
|
||||
submoduleDefinition = submodule.definition;
|
||||
|
||||
# submodule defaults
|
||||
defaults = getDefaults {
|
||||
name = submoduleDefinition.name;
|
||||
version = submoduleDefinition.version;
|
||||
tags = submoduleDefinition.tags;
|
||||
features = submodule.features;
|
||||
};
|
||||
in
|
||||
{
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Submodule instance name";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
submodule = mkOption {
|
||||
description = "Name of the submodule to use";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
description = ''
|
||||
Version of submodule to use, if version starts with "~" it is
|
||||
threated as regex pattern for example "~1.0.*"
|
||||
'';
|
||||
type = types.nullOr types.str;
|
||||
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 ++ [ ./base.nix ];
|
||||
_module.args.pkgs = pkgs;
|
||||
_module.args.name = config.name;
|
||||
_module.args.submodule = config;
|
||||
submodule.args = mkAliasDefinitions options.args;
|
||||
})
|
||||
specialArgs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
args = mkOption {
|
||||
description = "Submodule arguments (alias of config.submodule.args)";
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
default = { };
|
||||
};
|
||||
|
||||
config = mkMerge ([
|
||||
{
|
||||
# register exported functions as args
|
||||
_module.args = mkMerge (map
|
||||
(submodule: {
|
||||
${submodule.exportAs} = submodule.definition.exports;
|
||||
})
|
||||
(filter (submodule: submodule.exportAs != null) cfg.imports));
|
||||
|
||||
_m.features = [ "submodules" ];
|
||||
|
||||
submodules.specialArgs.kubenix = kubenix;
|
||||
|
||||
# passthru kubenix.project to submodules
|
||||
submodules.defaults = mkMerge [
|
||||
[{
|
||||
default = {
|
||||
kubenix.project = parentConfig.kubenix.project;
|
||||
};
|
||||
}]
|
||||
|
||||
(map
|
||||
(propagate: {
|
||||
features = propagate.features;
|
||||
default = propagate.module;
|
||||
})
|
||||
config._m.propagate)
|
||||
];
|
||||
}
|
||||
|
||||
(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;
|
||||
};
|
||||
};
|
||||
}];
|
||||
})
|
||||
] ++ passthruConfig);
|
||||
}
|
||||
104
modules/testing/default.nix
Normal file
104
modules/testing/default.nix
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
{ config, pkgs, lib, kubenix, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.testing;
|
||||
|
||||
testModule = {
|
||||
imports = [ ./evalTest.nix ];
|
||||
|
||||
# passthru testing configuration
|
||||
config._module.args = {
|
||||
inherit pkgs kubenix;
|
||||
testing = cfg;
|
||||
};
|
||||
};
|
||||
|
||||
isTestEnabled = test:
|
||||
(cfg.enabledTests == null || elem test.name cfg.enabledTests) && test.enable;
|
||||
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
./docker.nix
|
||||
./driver/kubetest.nix
|
||||
./runtime/local.nix
|
||||
./runtime/nixos-k8s.nix
|
||||
];
|
||||
|
||||
options.testing = {
|
||||
name = mkOption {
|
||||
description = "Testing suite name";
|
||||
type = types.str;
|
||||
default = "default";
|
||||
};
|
||||
|
||||
throwError = mkOption {
|
||||
description = "Whether to throw error";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
defaults = mkOption {
|
||||
description = "List of defaults to apply to tests";
|
||||
type = types.listOf (types.submodule ({ config, ... }: {
|
||||
options = {
|
||||
features = mkOption {
|
||||
description = "List of features that test has to have to apply defaults";
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
default = mkOption {
|
||||
description = "Default to apply to test";
|
||||
type = types.unspecified;
|
||||
default = { };
|
||||
};
|
||||
};
|
||||
}));
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
tests = mkOption {
|
||||
description = "List of test cases";
|
||||
default = [ ];
|
||||
type = types.listOf (types.coercedTo types.path
|
||||
(module: {
|
||||
inherit module;
|
||||
})
|
||||
(types.submodule testModule));
|
||||
apply = tests: filter isTestEnabled tests;
|
||||
};
|
||||
|
||||
testsByName = mkOption {
|
||||
description = "Tests by name";
|
||||
type = types.attrsOf types.attrs;
|
||||
default = listToAttrs (map (test: nameValuePair test.name test) cfg.tests);
|
||||
};
|
||||
|
||||
enabledTests = mkOption {
|
||||
description = "List of enabled tests (by default all tests are enabled)";
|
||||
type = types.nullOr (types.listOf types.str);
|
||||
default = null;
|
||||
};
|
||||
|
||||
args = mkOption {
|
||||
description = "Attribute set of extra args passed to tests";
|
||||
type = types.attrs;
|
||||
default = { };
|
||||
};
|
||||
|
||||
success = mkOption {
|
||||
internal = true; # read only property
|
||||
description = "Whether testing was a success";
|
||||
type = types.bool;
|
||||
default = all (test: test.success) cfg.tests;
|
||||
};
|
||||
|
||||
testScript = mkOption {
|
||||
internal = true; # set by test driver
|
||||
type = types.package;
|
||||
description = "Script to run e2e tests";
|
||||
};
|
||||
};
|
||||
}
|
||||
46
modules/testing/docker.nix
Normal file
46
modules/testing/docker.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
with import ../../lib/docker { inherit lib pkgs; };
|
||||
let
|
||||
testing = config.testing;
|
||||
|
||||
allImages = unique (flatten (map (t: t.evaled.config.docker.export or [ ]) testing.tests));
|
||||
|
||||
cfg = config.testing.docker;
|
||||
|
||||
in
|
||||
{
|
||||
options.testing.docker = {
|
||||
registryUrl = mkOption {
|
||||
description = "Docker registry url";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
images = mkOption {
|
||||
description = "List of images to export";
|
||||
type = types.listOf types.package;
|
||||
};
|
||||
|
||||
copyScript = mkOption {
|
||||
description = "Script to copy images to registry";
|
||||
type = types.package;
|
||||
};
|
||||
};
|
||||
|
||||
config.testing.docker = {
|
||||
images = allImages;
|
||||
|
||||
copyScript = copyDockerImages {
|
||||
images = cfg.images;
|
||||
dest = "docker://" + cfg.registryUrl;
|
||||
};
|
||||
};
|
||||
|
||||
config.testing.defaults = [{
|
||||
features = [ "docker" ];
|
||||
default = {
|
||||
docker.registry.url = cfg.registryUrl;
|
||||
};
|
||||
}];
|
||||
}
|
||||
58
modules/testing/driver/kubetest.nix
Normal file
58
modules/testing/driver/kubetest.nix
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
testing = config.testing;
|
||||
cfg = testing.driver.kubetest;
|
||||
|
||||
kubetest = import ./kubetestdrv.nix { inherit pkgs; };
|
||||
|
||||
pythonEnv = pkgs.python38.withPackages (ps: with ps; [
|
||||
pytest
|
||||
kubetest
|
||||
kubernetes
|
||||
] ++ cfg.extraPackages);
|
||||
|
||||
toTestScript = t:
|
||||
if isString t.script
|
||||
then
|
||||
pkgs.writeText "${t.name}.py" ''
|
||||
${cfg.defaultHeader}
|
||||
${t.script}
|
||||
''
|
||||
else t.script;
|
||||
|
||||
tests = pkgs.linkFarm "${testing.name}-tests" (
|
||||
map
|
||||
(t: {
|
||||
path = toTestScript t;
|
||||
name = "${t.name}_test.py";
|
||||
})
|
||||
(filter (t: t.script != null) testing.tests)
|
||||
);
|
||||
|
||||
testScript = pkgs.writeScript "test-${testing.name}.sh" ''
|
||||
#!/usr/bin/env bash
|
||||
${pythonEnv}/bin/pytest -p no:cacheprovider ${tests} $@
|
||||
'';
|
||||
|
||||
in
|
||||
{
|
||||
options.testing.driver.kubetest = {
|
||||
defaultHeader = mkOption {
|
||||
type = types.lines;
|
||||
description = "Default test header";
|
||||
default = ''
|
||||
import pytest
|
||||
'';
|
||||
};
|
||||
|
||||
extraPackages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
description = "Extra packages to pass to tests";
|
||||
default = [ ];
|
||||
};
|
||||
};
|
||||
|
||||
config.testing.testScript = testScript;
|
||||
}
|
||||
15
modules/testing/driver/kubetestdrv.nix
Normal file
15
modules/testing/driver/kubetestdrv.nix
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{ pkgs ? import <nixpkgs> { } }:
|
||||
with pkgs;
|
||||
with pkgs.python38Packages;
|
||||
|
||||
with pkgs.python38;
|
||||
pkgs.python38Packages.buildPythonPackage rec {
|
||||
pname = "kubetest";
|
||||
version = "0.9.5";
|
||||
src = fetchPypi {
|
||||
inherit pname version;
|
||||
sha256 = "sha256-TqDHMciAEXv4vMWLJY1YdtXsP4ho+INgdFB3xQQNoZU=";
|
||||
};
|
||||
propagatedBuildInputs = [ pytest kubernetes ];
|
||||
doCheck = false;
|
||||
}
|
||||
128
modules/testing/evalTest.nix
Normal file
128
modules/testing/evalTest.nix
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
{ lib, config, testing, kubenix, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
modules = [
|
||||
# testing module
|
||||
config.module
|
||||
|
||||
./test-options.nix
|
||||
../base.nix
|
||||
|
||||
# passthru some options to test
|
||||
{
|
||||
config = {
|
||||
kubenix.project = mkDefault config.name;
|
||||
_module.args = {
|
||||
inherit kubenix;
|
||||
test = config;
|
||||
} // testing.args;
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
# eval without checking
|
||||
evaled' = kubenix.evalModules {
|
||||
check = false;
|
||||
inherit modules;
|
||||
};
|
||||
|
||||
# test configuration
|
||||
testConfig = evaled'.config.test;
|
||||
|
||||
# test features
|
||||
testFeatures = evaled'.config._m.features;
|
||||
|
||||
# defaults that can be applied on tests
|
||||
defaults =
|
||||
filter
|
||||
(d:
|
||||
(intersectLists d.features testFeatures) == d.features ||
|
||||
(length d.features) == 0
|
||||
)
|
||||
testing.defaults;
|
||||
|
||||
# add default modules to all modules
|
||||
modulesWithDefaults = modules ++ (map (d: d.default) defaults);
|
||||
|
||||
# evaled test
|
||||
evaled =
|
||||
let
|
||||
evaled' = kubenix.evalModules {
|
||||
modules = modulesWithDefaults;
|
||||
};
|
||||
in
|
||||
if testing.throwError then evaled'
|
||||
else if (builtins.tryEval evaled'.config.test.assertions).success
|
||||
then evaled' else null;
|
||||
|
||||
in
|
||||
{
|
||||
options = {
|
||||
module = mkOption {
|
||||
description = "Module defining kubenix test";
|
||||
type = types.unspecified;
|
||||
};
|
||||
|
||||
evaled = mkOption {
|
||||
description = "Test evaulation result";
|
||||
type = types.nullOr types.attrs;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
success = mkOption {
|
||||
description = "Whether test assertions were successfull";
|
||||
type = types.bool;
|
||||
internal = true;
|
||||
default = false;
|
||||
};
|
||||
|
||||
# transparently forwarded from the test's `test` attribute for ease of access
|
||||
name = mkOption {
|
||||
description = "test name";
|
||||
type = types.str;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
description = "test description";
|
||||
type = types.str;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
enable = mkOption {
|
||||
description = "Whether to enable test";
|
||||
type = types.bool;
|
||||
internal = true;
|
||||
};
|
||||
|
||||
assertions = mkOption {
|
||||
description = "Test result";
|
||||
type = types.unspecified;
|
||||
internal = true;
|
||||
default = [ ];
|
||||
};
|
||||
|
||||
script = mkOption {
|
||||
description = "Test script to use for e2e test";
|
||||
type = types.nullOr (types.either types.lines types.path);
|
||||
internal = true;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
config = mkMerge [
|
||||
{
|
||||
inherit evaled;
|
||||
inherit (testConfig) name description enable;
|
||||
}
|
||||
|
||||
# if test is evaled check assertions
|
||||
(mkIf (config.evaled != null) {
|
||||
inherit (evaled.config.test) assertions script;
|
||||
|
||||
# if all assertions are true, test is successfull
|
||||
success = all (el: el.assertion) config.assertions;
|
||||
})
|
||||
];
|
||||
}
|
||||
44
modules/testing/runtime/local.nix
Normal file
44
modules/testing/runtime/local.nix
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
testing = config.testing;
|
||||
|
||||
script = pkgs.writeScript "run-local-k8s-tests-${testing.name}.sh" ''
|
||||
#!${pkgs.runtimeShell}
|
||||
|
||||
set -e
|
||||
|
||||
KUBECONFIG=''${KUBECONFIG:-~/.kube/config}
|
||||
SKOPEOARGS=""
|
||||
|
||||
while (( "$#" )); do
|
||||
case "$1" in
|
||||
--kubeconfig)
|
||||
KUBECONFIG=$2
|
||||
shift 2
|
||||
;;
|
||||
--skopeo-args)
|
||||
SKOPEOARGS=$2
|
||||
shift 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "--> copying docker images to registry"
|
||||
${testing.docker.copyScript} $SKOPEOARGS
|
||||
|
||||
echo "--> running tests"
|
||||
${testing.testScript} --kube-config=$KUBECONFIG
|
||||
'';
|
||||
in
|
||||
{
|
||||
options.testing.runtime.local = {
|
||||
script = mkOption {
|
||||
type = types.package;
|
||||
description = "Runtime script";
|
||||
};
|
||||
};
|
||||
|
||||
config.testing.runtime.local.script = script;
|
||||
}
|
||||
95
modules/testing/runtime/nixos-k8s.nix
Normal file
95
modules/testing/runtime/nixos-k8s.nix
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# nixos-k8s implements nixos kubernetes testing runtime
|
||||
|
||||
{ config
|
||||
, pkgs
|
||||
, lib
|
||||
, ...
|
||||
}:
|
||||
|
||||
with lib;
|
||||
let
|
||||
testing = config.testing;
|
||||
# kubeconfig = "/etc/${config.services.kubernetes.pki.etcClusterAdminKubeconfig}";
|
||||
kubeconfig = "/etc/kubernetes/cluster-admin.kubeconfig";
|
||||
kubecerts = "/var/lib/kubernetes/secrets";
|
||||
|
||||
# how we differ from the standard configuration of mkKubernetesBaseTest
|
||||
extraConfiguration = { config, pkgs, lib, nodes, ... }: {
|
||||
|
||||
virtualisation = {
|
||||
memorySize = 2048;
|
||||
};
|
||||
|
||||
networking = {
|
||||
nameservers = [ "10.0.0.254" ];
|
||||
firewall = {
|
||||
trustedInterfaces = [ "docker0" "cni0" ];
|
||||
};
|
||||
};
|
||||
|
||||
services.kubernetes = {
|
||||
flannel.enable = false;
|
||||
kubelet = {
|
||||
seedDockerImages = testing.docker.images;
|
||||
networkPlugin = "cni";
|
||||
cni.config = [{
|
||||
name = "mynet";
|
||||
type = "bridge";
|
||||
bridge = "cni0";
|
||||
addIf = true;
|
||||
ipMasq = true;
|
||||
isGateway = true;
|
||||
ipam = {
|
||||
type = "host-local";
|
||||
subnet = "10.1.0.0/16";
|
||||
gateway = "10.1.0.1";
|
||||
routes = [{
|
||||
dst = "0.0.0.0/0";
|
||||
}];
|
||||
};
|
||||
}];
|
||||
};
|
||||
};
|
||||
|
||||
systemd = {
|
||||
extraConfig = "DefaultLimitNOFILE=1048576";
|
||||
# Host tools should have a chance to access guest's kube api
|
||||
services.copy-certs = {
|
||||
description = "Share k8s certificates with host";
|
||||
script = "cp -rf ${kubecerts} /tmp/xchg/; cp -f ${kubeconfig} /tmp/xchg/;";
|
||||
after = [ "kubernetes.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
script = ''
|
||||
machine1.succeed("${testing.testScript} --kube-config=${kubeconfig}")
|
||||
'';
|
||||
|
||||
test =
|
||||
with import "${pkgs.path}/nixos/tests/kubernetes/base.nix" { inherit pkgs; inherit (pkgs) system; };
|
||||
mkKubernetesSingleNodeTest {
|
||||
inherit extraConfiguration;
|
||||
inherit (config.testing) name;
|
||||
test = script;
|
||||
};
|
||||
|
||||
|
||||
in
|
||||
{
|
||||
options.testing.runtime.nixos-k8s = {
|
||||
driver = mkOption {
|
||||
description = "Test driver";
|
||||
type = types.package;
|
||||
internal = true;
|
||||
};
|
||||
};
|
||||
|
||||
config.testing.runtime.nixos-k8s.driver = test.driver;
|
||||
}
|
||||
57
modules/testing/test-options.nix
Normal file
57
modules/testing/test-options.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
{ lib, config, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.test;
|
||||
|
||||
in
|
||||
{
|
||||
options.test = {
|
||||
name = mkOption {
|
||||
description = "Test name";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
description = "Test description";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
enable = mkOption {
|
||||
description = "Whether to enable test";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
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 some 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.
|
||||
'';
|
||||
};
|
||||
|
||||
script = mkOption {
|
||||
description = "Test script to use for e2e test";
|
||||
type = types.nullOr (types.either types.lines types.path);
|
||||
default = null;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue