ref: move source code to ./src (clean top level)

This commit is contained in:
David Arnold 2021-05-28 14:31:36 -05:00
parent 6ae1e2eb15
commit 09e268920b
No known key found for this signature in database
GPG key ID: 6D6A936E69C59D08
35 changed files with 2 additions and 2 deletions

View file

@ -1,39 +0,0 @@
{ 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 = [ ];
};
};
}

View file

@ -1,11 +0,0 @@
{
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;
}

View file

@ -1,94 +0,0 @@
{ 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);
};
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,118 +0,0 @@
# 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));
};
}

View file

@ -1,16 +0,0 @@
{ 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";
}

File diff suppressed because it is too large Load diff

View file

@ -1,478 +0,0 @@
# 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);
};
}

View file

@ -1,48 +0,0 @@
{ 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" ];
}

View file

@ -1,361 +0,0 @@
{ 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
# &gt; and &lt; 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);
}

View file

@ -1,104 +0,0 @@
{ nixosPath, 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";
};
};
}

View file

@ -1,46 +0,0 @@
{ 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;
};
}];
}

View file

@ -1,58 +0,0 @@
{ 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;
}

View file

@ -1,15 +0,0 @@
{ 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;
}

View file

@ -1,128 +0,0 @@
{ 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;
})
];
}

View file

@ -1,44 +0,0 @@
{ 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;
}

View file

@ -1,96 +0,0 @@
# nixos-k8s implements nixos kubernetes testing runtime
{ nixosPath
, 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 "${nixosPath}/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;
}

View file

@ -1,57 +0,0 @@
{ 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;
};
};
}