This commit is contained in:
GTrunSec 2022-04-02 12:40:35 -07:00
parent a0ce293db8
commit 60592d3096
No known key found for this signature in database
GPG key ID: 2368FAFA4ABDD2A0
55 changed files with 23668 additions and 30925 deletions

View file

@ -1,9 +1,9 @@
{ system ? builtins.currentSystem }: {system ? builtins.currentSystem}: let
let
in in
( (
(import ./compat.nix).flake-compat { (import ./compat.nix).flake-compat {
src = ./.; src = ./.;
inherit system; inherit system;
} }
).defaultNix )
.defaultNix

View file

@ -1,9 +1,6 @@
{ system ? builtins.currentSystem
, evalModules ? (import ../. { }).evalModules.${system}
}:
{ registry ? "docker.io/gatehub" }:
{ {
nginx-deployment = import ./nginx-deployment { inherit evalModules registry; }; system ? builtins.currentSystem,
evalModules ? (import ../. {}).evalModules.${system},
}: {registry ? "docker.io/gatehub"}: {
nginx-deployment = import ./nginx-deployment {inherit evalModules registry;};
} }

View file

@ -1,10 +1,11 @@
{ evalModules, registry }: {
evalModules,
let registry,
}: let
# evaluated configuration # evaluated configuration
config = (evalModules { config =
module = (evalModules {
{ kubenix, ... }: { module = {kubenix, ...}: {
imports = [ imports = [
kubenix.modules.testing kubenix.modules.testing
./module.nix ./module.nix
@ -16,12 +17,12 @@ let
kubernetes.version = "1.21"; kubernetes.version = "1.21";
testing = { testing = {
tests = [ ./test.nix ]; tests = [./test.nix];
docker.registryUrl = ""; docker.registryUrl = "";
# testing commonalities for tests that exhibit the respective feature # testing commonalities for tests that exhibit the respective feature
common = [ common = [
{ {
features = [ "k8s" ]; features = ["k8s"];
options = { options = {
kubernetes.version = "1.20"; kubernetes.version = "1.20";
}; };
@ -29,10 +30,9 @@ let
]; ];
}; };
}; };
}).config; })
.config;
in in {
{
inherit config; inherit config;
# config checks # config checks

View file

@ -1,8 +1,10 @@
{ dockerTools, nginx }: {
dockerTools,
nginx,
}:
dockerTools.buildLayeredImage { dockerTools.buildLayeredImage {
name = "nginx"; name = "nginx";
contents = [ nginx ]; contents = [nginx];
extraCommands = '' extraCommands = ''
mkdir -p etc mkdir -p etc
chmod u+w etc chmod u+w etc
@ -10,9 +12,9 @@ dockerTools.buildLayeredImage {
echo "nginx:x:1000:nginx" > etc/group echo "nginx:x:1000:nginx" > etc/group
''; '';
config = { config = {
Cmd = [ "nginx" "-c" "/etc/nginx/nginx.conf" ]; Cmd = ["nginx" "-c" "/etc/nginx/nginx.conf"];
ExposedPorts = { ExposedPorts = {
"80/tcp" = { }; "80/tcp" = {};
}; };
}; };
} }

View file

@ -1,11 +1,14 @@
{ config, lib, pkgs, kubenix, ... }:
with lib;
let
nginx = pkgs.callPackage ./image.nix { };
in
{ {
imports = with kubenix.modules; [ k8s docker ]; config,
lib,
pkgs,
kubenix,
...
}:
with lib; let
nginx = pkgs.callPackage ./image.nix {};
in {
imports = with kubenix.modules; [k8s docker];
docker.images.nginx.image = nginx; docker.images.nginx.image = nginx;
@ -54,10 +57,12 @@ in
kubernetes.resources.services.nginx = { kubernetes.resources.services.nginx = {
spec = { spec = {
ports = [{ ports = [
name = "http"; {
port = 80; name = "http";
}]; port = 80;
}
];
selector.app = "nginx"; selector.app = "nginx";
}; };
}; };

View file

@ -1,9 +1,13 @@
{ config, lib, pkgs, kubenix, test, ... }:
with lib;
{ {
imports = [ kubenix.modules.test ./module.nix ]; config,
lib,
pkgs,
kubenix,
test,
...
}:
with lib; {
imports = [kubenix.modules.test ./module.nix];
test = { test = {
name = "nginx-deployment"; name = "nginx-deployment";

View file

@ -7,55 +7,59 @@
devshell-flake.url = "github:numtide/devshell"; devshell-flake.url = "github:numtide/devshell";
}; };
outputs = { self, nixpkgs, flake-utils, devshell-flake }: outputs = {
self,
(flake-utils.lib.eachDefaultSystem (system: nixpkgs,
let flake-utils,
devshell-flake,
}:
(flake-utils.lib.eachDefaultSystem (
system: let
pkgs = import nixpkgs { pkgs = import nixpkgs {
inherit system; inherit system;
overlays = [ overlays = [
self.overlay self.overlay
devshell-flake.overlay devshell-flake.overlay
]; ];
config = { allowUnsupportedSystem = true; }; config = {allowUnsupportedSystem = true;};
}; };
lib = pkgs.lib; lib = pkgs.lib;
kubenix = { kubenix = {
lib = import ./lib { inherit lib pkgs; }; lib = import ./lib {inherit lib pkgs;};
evalModules = self.evalModules.${system}; evalModules = self.evalModules.${system};
modules = self.modules; modules = self.modules;
}; };
# evalModules with same interface as lib.evalModules and kubenix as # evalModules with same interface as lib.evalModules and kubenix as
# special argument # special argument
evalModules = attrs@{ module ? null, modules ? [ module ], ... }: evalModules = attrs @ {
let module ? null,
lib' = lib.extend (lib: self: import ./lib/upstreamables.nix { inherit lib pkgs; }); modules ? [module],
attrs' = builtins.removeAttrs attrs [ "module" ]; ...
in }: let
lib' = lib.extend (lib: self: import ./lib/upstreamables.nix {inherit lib pkgs;});
attrs' = builtins.removeAttrs attrs ["module"];
in
lib'.evalModules (lib.recursiveUpdate lib'.evalModules (lib.recursiveUpdate
{ {
inherit modules; inherit modules;
specialArgs = { inherit kubenix; }; specialArgs = {inherit kubenix;};
args = { args = {
inherit pkgs; inherit pkgs;
name = "default"; name = "default";
}; };
} }
attrs'); attrs');
in {
in
{
inherit evalModules; inherit evalModules;
jobs = import ./jobs { inherit pkgs; }; jobs = import ./jobs {inherit pkgs;};
devShell = with pkgs; devshell.mkShell devShell = with pkgs;
{ imports = [ (devshell.importTOML ./devshell.toml) ]; }; devshell.mkShell
{imports = [(devshell.importTOML ./devshell.toml)];};
packages = flake-utils.lib.flattenTree { packages = flake-utils.lib.flattenTree {
inherit (pkgs) kubernetes kubectl; inherit (pkgs) kubernetes kubectl;
@ -66,31 +70,30 @@
if suite.success == true if suite.success == true
then pkgs.runCommandNoCC "testing-suite-config-assertions-for-${suite.name}-succeeded" {} "echo success > $out" then pkgs.runCommandNoCC "testing-suite-config-assertions-for-${suite.name}-succeeded" {} "echo success > $out"
else pkgs.runCommandNoCC "testing-suite-config-assertions-for-${suite.name}-failed" {} "exit 1"; else pkgs.runCommandNoCC "testing-suite-config-assertions-for-${suite.name}-failed" {} "exit 1";
mkExamples = attrs: (import ./examples { inherit evalModules; }) mkExamples = attrs:
({ registry = "docker.io/gatehub"; } // attrs); (import ./examples {inherit evalModules;})
mkK8STests = attrs: (import ./tests { inherit evalModules; }) ({registry = "docker.io/gatehub";} // attrs);
({ registry = "docker.io/gatehub"; } // attrs); mkK8STests = attrs:
(import ./tests {inherit evalModules;})
({registry = "docker.io/gatehub";} // attrs);
in { in {
# TODO: access "success" derivation with nice testing utils for nice output # TODO: access "success" derivation with nice testing utils for nice output
nginx-example = wasSuccess (mkExamples { }).nginx-deployment.config.testing; nginx-example = wasSuccess (mkExamples {}).nginx-deployment.config.testing;
tests-k8s-1_19 = wasSuccess (mkK8STests { k8sVersion = "1.19"; }); tests-k8s-1_19 = wasSuccess (mkK8STests {k8sVersion = "1.19";});
tests-k8s-1_20 = wasSuccess (mkK8STests { k8sVersion = "1.20"; }); tests-k8s-1_20 = wasSuccess (mkK8STests {k8sVersion = "1.20";});
tests-k8s-1_21 = wasSuccess (mkK8STests { k8sVersion = "1.21"; }); tests-k8s-1_21 = wasSuccess (mkK8STests {k8sVersion = "1.21";});
}; };
} }
)) ))
// {
//
{
modules = import ./modules; modules = import ./modules;
overlay = final: prev: { overlay = final: prev: {
kubenix.evalModules = self.evalModules.${prev.system}; kubenix.evalModules = self.evalModules.${prev.system};
# up to date versions of their nixpkgs equivalents # up to date versions of their nixpkgs equivalents
kubernetes = prev.callPackage ./pkgs/applications/networking/cluster/kubernetes kubernetes =
{ }; prev.callPackage ./pkgs/applications/networking/cluster/kubernetes
kubectl = prev.callPackage ./pkgs/applications/networking/cluster/kubectl { }; {};
kubectl = prev.callPackage ./pkgs/applications/networking/cluster/kubectl {};
}; };
}; };
} }

View file

@ -1,4 +1,3 @@
{ pkgs }: {pkgs}: {
{ generators = pkgs.callPackage ./generators {};
generators = pkgs.callPackage ./generators { };
} }

View file

@ -1,6 +1,7 @@
{ pkgs, lib }: {
let pkgs,
lib,
}: let
generateIstio = import ./istio { generateIstio = import ./istio {
inherit inherit
pkgs pkgs
@ -8,22 +9,22 @@ let
; ;
}; };
generateK8S = name: spec: import ./k8s { generateK8S = name: spec:
inherit import ./k8s {
name inherit
pkgs name
lib pkgs
spec lib
; spec
}; ;
};
in in {
{ istio = pkgs.linkFarm "istio-generated" [
{
istio = pkgs.linkFarm "istio-generated" [{ name = "latest.nix";
name = "latest.nix"; path = generateIstio;
path = generateIstio; }
}]; ];
k8s = pkgs.linkFarm "k8s-generated" [ k8s = pkgs.linkFarm "k8s-generated" [
{ {
@ -50,5 +51,4 @@ in
}); });
} }
]; ];
} }

View file

@ -1,13 +1,15 @@
{ pkgs ? import <nixpkgs> { }, lib ? pkgs.lib, spec ? ./istio-schema.json }: {
pkgs ? import <nixpkgs> {},
with lib; lib ? pkgs.lib,
let spec ? ./istio-schema.json,
}:
with lib; let
gen = rec { gen = rec {
mkMerge = values: ''mkMerge [${concatMapStrings mkMerge = values: ''mkMerge [${concatMapStrings
(value: " (value: "
${value} ${value}
") ")
values}]''; values}]'';
toNixString = value: toNixString = value:
if isAttrs value || isList value if isAttrs value || isList value
@ -20,17 +22,18 @@ values}]'';
removeEmptyLines = str: concatStringsSep "\n" (filter (l: (builtins.match "( |)+" l) == null) (splitString "\n" str)); removeEmptyLines = str: concatStringsSep "\n" (filter (l: (builtins.match "( |)+" l) == null) (splitString "\n" str));
mkOption = mkOption = {
{ description ? null description ? null,
, type ? null type ? null,
, default ? null default ? null,
, apply ? null apply ? null,
}: removeEmptyLines ''mkOption { }:
${optionalString (description != null) "description = ${builtins.toJSON description};"} removeEmptyLines '' mkOption {
${optionalString (type != null) ''type = ${type};''} ${optionalString (description != null) "description = ${builtins.toJSON description};"}
${optionalString (default != null) ''default = ${toNixString default};''} ${optionalString (type != null) ''type = ${type};''}
${optionalString (apply != null) ''apply = ${apply};''} ${optionalString (default != null) ''default = ${toNixString default};''}
}''; ${optionalString (apply != null) ''apply = ${apply};''}
}'';
mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}"; mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}";
@ -43,327 +46,335 @@ values}]'';
nullOr = val: "(types.nullOr ${val})"; nullOr = val: "(types.nullOr ${val})";
attrsOf = val: "(types.attrsOf ${val})"; attrsOf = val: "(types.attrsOf ${val})";
listOf = val: "(types.listOf ${val})"; listOf = val: "(types.listOf ${val})";
coercedTo = coercedType: coerceFunc: finalType: coercedTo = coercedType: coerceFunc: finalType: "(types.coercedTo ${coercedType} ${coerceFunc} ${finalType})";
"(types.coercedTo ${coercedType} ${coerceFunc} ${finalType})";
either = val1: val2: "(types.either ${val1} ${val2})"; either = val1: val2: "(types.either ${val1} ${val2})";
loaOf = type: "(types.loaOf ${type})"; loaOf = type: "(types.loaOf ${type})";
}; };
hasTypeMapping = def: hasTypeMapping = def:
hasAttr "type" def && hasAttr "type" def
elem def.type [ "string" "integer" "boolean" ]; && elem def.type ["string" "integer" "boolean"];
mergeValuesByKey = mergeKey: ''(mergeValuesByKey "${mergeKey}")''; mergeValuesByKey = mergeKey: ''(mergeValuesByKey "${mergeKey}")'';
mapType = def: mapType = def:
if def.type == "string" then if def.type == "string"
then
if hasAttr "format" def && def.format == "int-or-string" if hasAttr "format" def && def.format == "int-or-string"
then types.either types.int types.str then types.either types.int types.str
else types.str else types.str
else if def.type == "integer" then types.int else if def.type == "integer"
else if def.type == "number" then types.int then types.int
else if def.type == "boolean" then types.bool else if def.type == "number"
else if def.type == "object" then types.attrs then types.int
else if def.type == "boolean"
then types.bool
else if def.type == "object"
then types.attrs
else throw "type ${def.type} not supported"; else throw "type ${def.type} not supported";
submoduleOf = definitions: ref: ''(submoduleOf "${ref}")''; submoduleOf = definitions: ref: ''(submoduleOf "${ref}")'';
submoduleForDefinition = ref: name: kind: group: version: submoduleForDefinition = ref: name: kind: group: version: ''(submoduleForDefinition "${ref}" "${name}" "${kind}" "${group}" "${version}")'';
''(submoduleForDefinition "${ref}" "${name}" "${kind}" "${group}" "${version}")'';
coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: ''(coerceAttrsOfSubmodulesToListByKey "${ref}" "${mergeKey}")'';
''(coerceAttrsOfSubmodulesToListByKey "${ref}" "${mergeKey}")'';
attrsToList = "values: if values != null then mapAttrsToList (n: v: v) values else values"; attrsToList = "values: if values != null then mapAttrsToList (n: v: v) values else values";
refDefinition = attr: head (tail (tail (splitString "/" attr."$ref"))); refDefinition = attr: head (tail (tail (splitString "/" attr."$ref")));
}; };
fixJSON = content: replaceStrings [ "\\u" ] [ "u" ] content; fixJSON = content: replaceStrings ["\\u"] ["u"] content;
fetchSpecs = path: builtins.fromJSON (fixJSON (builtins.readFile path)); fetchSpecs = path: builtins.fromJSON (fixJSON (builtins.readFile path));
genDefinitions = swagger: with gen; (mapAttrs genDefinitions = swagger:
(name: definition: with gen; (mapAttrs
# if $ref is in definition it means it's an alias of other definition (
if hasAttr "$ref" definition name: definition:
then definitions."${refDefinition definition}" # if $ref is in definition it means it's an alias of other definition
if hasAttr "$ref" definition
else if !(hasAttr "properties" definition) then definitions."${refDefinition definition}"
then { else if !(hasAttr "properties" definition)
type = mapType definition; then {
} type = mapType definition;
}
else { else {
options = mapAttrs options =
(propName: property: mapAttrs
let (
isRequired = elem propName (definition.required or [ ]); propName: property: let
requiredOrNot = type: if isRequired then type else types.nullOr type; isRequired = elem propName (definition.required or []);
optionProperties = requiredOrNot = type:
# if $ref is in property it references other definition, if isRequired
# but if other definition does not have properties, then just take it's type then type
if hasAttr "$ref" property then else types.nullOr type;
if hasTypeMapping swagger.definitions.${refDefinition property} then { optionProperties =
type = requiredOrNot (mapType swagger.definitions.${refDefinition property}); # if $ref is in property it references other definition,
} # but if other definition does not have properties, then just take it's type
else { if hasAttr "$ref" property
type = requiredOrNot (submoduleOf definitions (refDefinition property)); then
} if hasTypeMapping swagger.definitions.${refDefinition property}
then {
else if !(hasAttr "type" property) then { type = requiredOrNot (mapType swagger.definitions.${refDefinition property});
type = types.unspecified;
}
# if property has an array type
else if property.type == "array" then
# if reference is in items it can reference other type of another
# definition
if hasAttr "$ref" property.items then
# if it is a reference to simple type
if hasTypeMapping swagger.definitions.${refDefinition property.items}
then {
type = requiredOrNot (types.listOf (mapType swagger.definitions.${refDefinition property.items}.type));
}
# if a reference is to complex type
else
# if x-kubernetes-patch-merge-key is set then make it an
# attribute set of submodules
if hasAttr "x-kubernetes-patch-merge-key" property
then
let
mergeKey = property."x-kubernetes-patch-merge-key";
in
{
type = requiredOrNot (coerceAttrsOfSubmodulesToListByKey (refDefinition property.items) mergeKey);
apply = attrsToList;
}
# in other case it's a simple list
else {
type = requiredOrNot (types.listOf (submoduleOf definitions (refDefinition property.items)));
} }
else {
# in other case it only references a simple type type = requiredOrNot (submoduleOf definitions (refDefinition property));
else { }
type = requiredOrNot (types.listOf (mapType property.items)); else if !(hasAttr "type" property)
} then {
type = types.unspecified;
else if property.type == "object" && hasAttr "additionalProperties" property }
then # if property has an array type
# if it is a reference to simple type else if property.type == "array"
if ( then
hasAttr "$ref" property.additionalProperties && # if reference is in items it can reference other type of another
hasTypeMapping swagger.definitions.${refDefinition property.additionalProperties} # definition
) then { if hasAttr "$ref" property.items
type = requiredOrNot (types.attrsOf (mapType swagger.definitions.${refDefinition property.additionalProperties})); then
} # if it is a reference to simple type
if hasTypeMapping swagger.definitions.${refDefinition property.items}
else if hasAttr "$ref" property.additionalProperties then {
then { type = requiredOrNot (types.listOf (mapType swagger.definitions.${refDefinition property.items}.type));
type = requiredOrNot types.attrs; }
} # if a reference is to complex type
else
# if is an array # if x-kubernetes-patch-merge-key is set then make it an
else if property.additionalProperties.type == "array" # attribute set of submodules
then { if hasAttr "x-kubernetes-patch-merge-key" property
type = requiredOrNot (types.loaOf (mapType property.additionalProperties.items)); then let
} mergeKey = property."x-kubernetes-patch-merge-key";
in {
else { type = requiredOrNot (coerceAttrsOfSubmodulesToListByKey (refDefinition property.items) mergeKey);
type = requiredOrNot (types.attrsOf (mapType property.additionalProperties)); apply = attrsToList;
} }
# in other case it's a simple list
# just a simple property else {
else { type = requiredOrNot (types.listOf (submoduleOf definitions (refDefinition property.items)));
type = requiredOrNot (mapType property); }
}; # in other case it only references a simple type
in else {
mkOption ({ type = requiredOrNot (types.listOf (mapType property.items));
description = property.description or ""; }
} // optionProperties) else if property.type == "object" && hasAttr "additionalProperties" property
) then
definition.properties; # if it is a reference to simple type
config = if
let (
optionalProps = filterAttrs hasAttr "$ref" property.additionalProperties
(propName: property: && hasTypeMapping swagger.definitions.${refDefinition property.additionalProperties}
!(elem propName (definition.required or [ ])) )
then {
type = requiredOrNot (types.attrsOf (mapType swagger.definitions.${refDefinition property.additionalProperties}));
}
else if hasAttr "$ref" property.additionalProperties
then {
type = requiredOrNot types.attrs;
}
# if is an array
else if property.additionalProperties.type == "array"
then {
type = requiredOrNot (types.loaOf (mapType property.additionalProperties.items));
}
else {
type = requiredOrNot (types.attrsOf (mapType property.additionalProperties));
}
# just a simple property
else {
type = requiredOrNot (mapType property);
};
in
mkOption ({
description = property.description or "";
}
// optionProperties)
) )
definition.properties; definition.properties;
in config = let
mapAttrs (name: property: mkOverride 1002 null) optionalProps; optionalProps =
} filterAttrs
) (
swagger.definitions); propName: property:
!(elem propName (definition.required or []))
)
definition.properties;
in
mapAttrs (name: property: mkOverride 1002 null) optionalProps;
}
)
swagger.definitions);
genResources = swagger: (mapAttrsToList genResources = swagger:
(name: property: rec { (mapAttrsToList
splittedType = splitString "." (removePrefix "me.snowdrop.istio.api." property.javaType); (name: property: rec {
group = (concatStringsSep "." (take ((length splittedType) - 2) splittedType)) + ".istio.io"; splittedType = splitString "." (removePrefix "me.snowdrop.istio.api." property.javaType);
kind = removeSuffix "Spec" (last splittedType); group = (concatStringsSep "." (take ((length splittedType) - 2) splittedType)) + ".istio.io";
version = last (take ((length splittedType) - 1) splittedType); kind = removeSuffix "Spec" (last splittedType);
ref = removePrefix "#/definitions/" property."$ref"; version = last (take ((length splittedType) - 1) splittedType);
}) ref = removePrefix "#/definitions/" property."$ref";
(filterAttrs })
(name: property: (filterAttrs
(hasPrefix "me.snowdrop.istio.api" property.javaType) && (
hasSuffix "Spec" property.javaType name: property:
) (hasPrefix "me.snowdrop.istio.api" property.javaType)
swagger.properties)) ++ (mapAttrsToList && hasSuffix "Spec" property.javaType
(name: property: rec { )
splittedType = splitString "." (removePrefix "me.snowdrop.istio.mixer." property.javaType); swagger.properties))
group = "config.istio.io"; ++ (mapAttrsToList
version = "v1alpha2"; (name: property: rec {
kind = head (tail splittedType); splittedType = splitString "." (removePrefix "me.snowdrop.istio.mixer." property.javaType);
ref = removePrefix "#/definitions/" property."$ref"; group = "config.istio.io";
}) version = "v1alpha2";
(filterAttrs kind = head (tail splittedType);
(name: property: ref = removePrefix "#/definitions/" property."$ref";
(hasPrefix "me.snowdrop.istio.mixer" property.javaType) && })
hasSuffix "Spec" property.javaType (filterAttrs
) (
swagger.properties)); name: property:
(hasPrefix "me.snowdrop.istio.mixer" property.javaType)
&& hasSuffix "Spec" property.javaType
)
swagger.properties));
swagger = fetchSpecs spec; swagger = fetchSpecs spec;
definitions = genDefinitions swagger; definitions = genDefinitions swagger;
generated = '' generated = ''
# This file was generated with kubenix k8s generator, do not edit # This file was generated with kubenix k8s generator, do not edit
{lib, config, ... }: {lib, config, ... }:
with lib; with lib;
let let
types = lib.types // rec { types = lib.types // rec {
str = mkOptionType { str = mkOptionType {
name = "str"; name = "str";
description = "string"; description = "string";
check = isString; check = isString;
merge = mergeEqualOption; merge = mergeEqualOption;
};
# 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; };
};
}; };
# Either value of type `finalType` or `coercedType`, the latter is mkOptionDefault = mkOverride 1001;
# 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); extraOptions = {
getSubOptions = finalType.getSubOptions; kubenix = {};
getSubModules = finalType.getSubModules;
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
typeMerge = t1: t2: null;
functor = (defaultFunctor name) // { wrapped = finalType; };
}; };
};
mkOptionDefault = mkOverride 1001; mergeValuesByKey = mergeKey: values:
listToAttrs (map
(value: nameValuePair (
if isAttrs value.''${mergeKey}
then toString value.''${mergeKey}.content
else (toString value.''${mergeKey})
) value)
values);
extraOptions = { submoduleOf = ref: types.submodule ({name, ...}: {
kubenix = {}; options = definitions."''${ref}".options;
}; config = definitions."''${ref}".config;
mergeValuesByKey = mergeKey: values:
listToAttrs (map
(value: nameValuePair (
if isAttrs value.''${mergeKey}
then toString value.''${mergeKey}.content
else (toString value.''${mergeKey})
) value)
values);
submoduleOf = ref: types.submodule ({name, ...}: {
options = definitions."''${ref}".options;
config = definitions."''${ref}".config;
});
submoduleWithMergeOf = ref: mergeKey: types.submodule ({name, ...}: let
convertName = name:
if definitions."''${ref}".options.''${mergeKey}.type == types.int
then toInt name
else name;
in {
options = definitions."''${ref}".options;
config = definitions."''${ref}".config // {
''${mergeKey} = mkOverride 1002 (convertName name);
};
});
submoduleForDefinition = ref: resource: kind: group: version:
types.submodule ({name, ...}: {
options = definitions."''${ref}".options // extraOptions;
config = mkMerge ([
definitions."''${ref}".config
{
kind = mkOptionDefault kind;
apiVersion = mkOptionDefault version;
# metdata.name cannot use option default, due deep config
metadata.name = mkOptionDefault name;
}
] ++ (config.defaults.''${resource} or [])
++ (config.defaults.all or []));
}); });
coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: (types.coercedTo submoduleWithMergeOf = ref: mergeKey: types.submodule ({name, ...}: let
(types.listOf (submoduleOf ref)) convertName = name:
(mergeValuesByKey mergeKey) if definitions."''${ref}".options.''${mergeKey}.type == types.int
(types.attrsOf (submoduleWithMergeOf ref mergeKey)) then toInt name
); else name;
in {
options = definitions."''${ref}".options;
config = definitions."''${ref}".config // {
''${mergeKey} = mkOverride 1002 (convertName name);
};
});
definitions = { submoduleForDefinition = ref: resource: kind: group: version:
${concatStrings (mapAttrsToList (name: value: '' types.submodule ({name, ...}: {
options = definitions."''${ref}".options // extraOptions;
config = mkMerge ([
definitions."''${ref}".config
{
kind = mkOptionDefault kind;
apiVersion = mkOptionDefault version;
# metdata.name cannot use option default, due deep config
metadata.name = mkOptionDefault name;
}
] ++ (config.defaults.''${resource} or [])
++ (config.defaults.all or []));
});
coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: (types.coercedTo
(types.listOf (submoduleOf ref))
(mergeValuesByKey mergeKey)
(types.attrsOf (submoduleWithMergeOf ref mergeKey))
);
definitions = {
${concatStrings (mapAttrsToList (name: value: ''
"${name}" = {${optionalString (hasAttr "options" value) " "${name}" = {${optionalString (hasAttr "options" value) "
options = {${concatStrings (mapAttrsToList (name: value: '' options = {${concatStrings (mapAttrsToList (name: value: ''
"${name}" = ${value}; "${name}" = ${value};
'') value.options)}}; '')
value.options)}};
"} "}
${optionalString (hasAttr "config" value) '' ${optionalString (hasAttr "config" value) ''
config = {${concatStrings (mapAttrsToList (name: value: '' config = {${concatStrings (mapAttrsToList (name: value: ''
"${name}" = ${value}; "${name}" = ${value};
'') value.config)}}; '')
''} value.config)}};
''}
}; };
'') definitions)} '')
} // (import ./overrides.nix {inheirt definitions lib;})); definitions)}
in { } // (import ./overrides.nix {inheirt definitions lib;}));
kubernetes.customResources = [ in {
${concatMapStrings kubernetes.customResources = [
(resource: ''{ ${concatMapStrings
group = "${resource.group}"; (resource: '' {
version = "${resource.version}"; group = "${resource.group}";
kind = "${resource.kind}"; version = "${resource.version}";
description = ""; kind = "${resource.kind}";
module = definitions."${resource.ref}"; description = "";
}'') module = definitions."${resource.ref}";
(genResources swagger)} }'')
]; (genResources swagger)}
} ];
''; }
'';
in in
pkgs.runCommand "istio-gen.nix" pkgs.runCommand "istio-gen.nix"
{ {
buildInputs = [ pkgs.nixpkgs-fmt ]; buildInputs = [pkgs.nixpkgs-fmt];
} '' } ''
cat << 'GENERATED' > ./raw cat << 'GENERATED' > ./raw
"${generated}" "${generated}"
GENERATED GENERATED
nixpkgs-fmt ./raw nixpkgs-fmt ./raw
cp ./raw $out cp ./raw $out
'' ''

View file

@ -1,17 +1,16 @@
{ name {
, pkgs name,
, lib pkgs,
, spec lib,
spec,
}: }:
with lib; let
with lib;
let
gen = rec { gen = rec {
mkMerge = values: ''mkMerge [${concatMapStrings mkMerge = values: ''mkMerge [${concatMapStrings
(value: " (value: "
${value} ${value}
") ")
values}]''; values}]'';
toNixString = value: toNixString = value:
if isAttrs value || isList value if isAttrs value || isList value
@ -24,17 +23,18 @@ values}]'';
removeEmptyLines = str: concatStringsSep "\n" (filter (l: (builtins.match "( |)+" l) == null) (splitString "\n" str)); removeEmptyLines = str: concatStringsSep "\n" (filter (l: (builtins.match "( |)+" l) == null) (splitString "\n" str));
mkOption = mkOption = {
{ description ? null description ? null,
, type ? null type ? null,
, default ? null default ? null,
, apply ? null apply ? null,
}: removeEmptyLines ''mkOption { }:
${optionalString (description != null) "description = ${builtins.toJSON description};"} removeEmptyLines '' mkOption {
${optionalString (type != null) ''type = ${type};''} ${optionalString (description != null) "description = ${builtins.toJSON description};"}
${optionalString (default != null) ''default = ${toNixString default};''} ${optionalString (type != null) ''type = ${type};''}
${optionalString (apply != null) ''apply = ${apply};''} ${optionalString (default != null) ''default = ${toNixString default};''}
}''; ${optionalString (apply != null) ''apply = ${apply};''}
}'';
mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}"; mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}";
@ -47,36 +47,38 @@ values}]'';
nullOr = val: "(types.nullOr ${val})"; nullOr = val: "(types.nullOr ${val})";
attrsOf = val: "(types.attrsOf ${val})"; attrsOf = val: "(types.attrsOf ${val})";
listOf = val: "(types.listOf ${val})"; listOf = val: "(types.listOf ${val})";
coercedTo = coercedType: coerceFunc: finalType: coercedTo = coercedType: coerceFunc: finalType: "(types.coercedTo ${coercedType} ${coerceFunc} ${finalType})";
"(types.coercedTo ${coercedType} ${coerceFunc} ${finalType})";
either = val1: val2: "(types.either ${val1} ${val2})"; either = val1: val2: "(types.either ${val1} ${val2})";
loaOf = type: "(types.loaOf ${type})"; loaOf = type: "(types.loaOf ${type})";
}; };
hasTypeMapping = def: hasTypeMapping = def:
hasAttr "type" def && hasAttr "type" def
elem def.type [ "string" "integer" "boolean" ]; && elem def.type ["string" "integer" "boolean"];
mergeValuesByKey = mergeKey: ''(mergeValuesByKey "${mergeKey}")''; mergeValuesByKey = mergeKey: ''(mergeValuesByKey "${mergeKey}")'';
mapType = def: mapType = def:
if def.type == "string" then if def.type == "string"
then
if hasAttr "format" def && def.format == "int-or-string" if hasAttr "format" def && def.format == "int-or-string"
then types.either types.int types.str then types.either types.int types.str
else types.str else types.str
else if def.type == "integer" then types.int else if def.type == "integer"
else if def.type == "number" then types.int then types.int
else if def.type == "boolean" then types.bool else if def.type == "number"
else if def.type == "object" then types.attrs then types.int
else if def.type == "boolean"
then types.bool
else if def.type == "object"
then types.attrs
else throw "type ${def.type} not supported"; else throw "type ${def.type} not supported";
submoduleOf = definitions: ref: ''(submoduleOf "${ref}")''; submoduleOf = definitions: ref: ''(submoduleOf "${ref}")'';
submoduleForDefinition = ref: name: kind: group: version: submoduleForDefinition = ref: name: kind: group: version: ''(submoduleForDefinition "${ref}" "${name}" "${kind}" "${group}" "${version}")'';
''(submoduleForDefinition "${ref}" "${name}" "${kind}" "${group}" "${version}")'';
coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: ''(coerceAttrsOfSubmodulesToListByKey "${ref}" "${mergeKey}")'';
''(coerceAttrsOfSubmodulesToListByKey "${ref}" "${mergeKey}")'';
attrsToList = "values: if values != null then mapAttrsToList (n: v: v) values else values"; attrsToList = "values: if values != null then mapAttrsToList (n: v: v) values else values";
@ -85,179 +87,193 @@ values}]'';
refType = attr: head (tail (tail (splitString "/" attr."$ref"))); refType = attr: head (tail (tail (splitString "/" attr."$ref")));
compareVersions = ver1: ver2: compareVersions = ver1: ver2: let
let getVersion = v: substring 1 10 v;
getVersion = v: substring 1 10 v; splitVersion = v: builtins.splitVersion (getVersion v);
splitVersion = v: builtins.splitVersion (getVersion v); isAlpha = v: elem "alpha" (splitVersion v);
isAlpha = v: elem "alpha" (splitVersion v); patchVersion = v:
patchVersion = v: if isAlpha v
if isAlpha v then "" then ""
else if length (splitVersion v) == 1 then "${getVersion v}prod" else if length (splitVersion v) == 1
else getVersion v; then "${getVersion v}prod"
else getVersion v;
v1 = patchVersion ver1; v1 = patchVersion ver1;
v2 = patchVersion ver2; v2 = patchVersion ver2;
in in
builtins.compareVersions v1 v2; builtins.compareVersions v1 v2;
fixJSON = content: replaceStrings [ "\\u" ] [ "u" ] content; fixJSON = content: replaceStrings ["\\u"] ["u"] content;
fetchSpecs = path: builtins.fromJSON (fixJSON (builtins.readFile path)); fetchSpecs = path: builtins.fromJSON (fixJSON (builtins.readFile path));
genDefinitions = swagger: with gen; mapAttrs genDefinitions = swagger:
(name: definition: with gen;
# if $ref is in definition it means it's an alias of other definition mapAttrs
if hasAttr "$ref" definition (
then definitions."${refDefinition definition}" name: definition:
# if $ref is in definition it means it's an alias of other definition
else if !(hasAttr "properties" definition) if hasAttr "$ref" definition
then { } then definitions."${refDefinition definition}"
else if !(hasAttr "properties" definition)
# in other case it's an actual definition then {}
else { # in other case it's an actual definition
options = mapAttrs else {
(propName: property: options =
let mapAttrs
isRequired = elem propName (definition.required or [ ]); (
requiredOrNot = type: if isRequired then type else types.nullOr type; propName: property: let
optionProperties = isRequired = elem propName (definition.required or []);
requiredOrNot = type:
# if $ref is in property it references other definition, if isRequired
# but if other definition does not have properties, then just take it's type then type
if hasAttr "$ref" property then else types.nullOr type;
if hasTypeMapping swagger.definitions.${refDefinition property} then { optionProperties =
type = requiredOrNot (mapType swagger.definitions.${refDefinition property}); # if $ref is in property it references other definition,
} # but if other definition does not have properties, then just take it's type
else { if hasAttr "$ref" property
type = requiredOrNot (submoduleOf definitions (refDefinition property)); then
} if hasTypeMapping swagger.definitions.${refDefinition property}
then {
# if property has an array type type = requiredOrNot (mapType swagger.definitions.${refDefinition property});
else if property.type == "array" then
# if reference is in items it can reference other type of another
# definition
if hasAttr "$ref" property.items then
# if it is a reference to simple type
if hasTypeMapping swagger.definitions.${refDefinition property.items}
then {
type = requiredOrNot (types.listOf (mapType swagger.definitions.${refDefinition property.items}.type));
}
# if a reference is to complex type
else
# if x-kubernetes-patch-merge-key is set then make it an
# attribute set of submodules
if hasAttr "x-kubernetes-patch-merge-key" property
then
let
mergeKey = property."x-kubernetes-patch-merge-key";
in
{
type = requiredOrNot (coerceAttrsOfSubmodulesToListByKey (refDefinition property.items) mergeKey);
apply = attrsToList;
}
# in other case it's a simple list
else {
type = requiredOrNot (types.listOf (submoduleOf definitions (refDefinition property.items)));
} }
else {
# in other case it only references a simple type type = requiredOrNot (submoduleOf definitions (refDefinition property));
else { }
type = requiredOrNot (types.listOf (mapType property.items)); # if property has an array type
} else if property.type == "array"
then
else if property.type == "object" && hasAttr "additionalProperties" property # if reference is in items it can reference other type of another
then # definition
# if it is a reference to simple type if hasAttr "$ref" property.items
if ( then
hasAttr "$ref" property.additionalProperties && # if it is a reference to simple type
hasTypeMapping swagger.definitions.${refDefinition property.additionalProperties} if hasTypeMapping swagger.definitions.${refDefinition property.items}
) then { then {
type = requiredOrNot (types.attrsOf (mapType swagger.definitions.${refDefinition property.additionalProperties})); type = requiredOrNot (types.listOf (mapType swagger.definitions.${refDefinition property.items}.type));
} }
# if a reference is to complex type
else if hasAttr "$ref" property.additionalProperties else
then { # if x-kubernetes-patch-merge-key is set then make it an
type = requiredOrNot types.attrs; # attribute set of submodules
} if hasAttr "x-kubernetes-patch-merge-key" property
then let
# if is an array mergeKey = property."x-kubernetes-patch-merge-key";
else if property.additionalProperties.type == "array" in {
then { type = requiredOrNot (coerceAttrsOfSubmodulesToListByKey (refDefinition property.items) mergeKey);
type = requiredOrNot (types.loaOf (mapType property.additionalProperties.items)); apply = attrsToList;
} }
# in other case it's a simple list
else { else {
type = requiredOrNot (types.attrsOf (mapType property.additionalProperties)); type = requiredOrNot (types.listOf (submoduleOf definitions (refDefinition property.items)));
} }
# in other case it only references a simple type
# just a simple property else {
else { type = requiredOrNot (types.listOf (mapType property.items));
type = requiredOrNot (mapType property); }
}; else if property.type == "object" && hasAttr "additionalProperties" property
in then
mkOption ({ # if it is a reference to simple type
description = property.description or ""; if
} // optionProperties) (
) hasAttr "$ref" property.additionalProperties
definition.properties; && hasTypeMapping swagger.definitions.${refDefinition property.additionalProperties}
config = )
let then {
optionalProps = filterAttrs type = requiredOrNot (types.attrsOf (mapType swagger.definitions.${refDefinition property.additionalProperties}));
(propName: property: }
!(elem propName (definition.required or [ ])) else if hasAttr "$ref" property.additionalProperties
then {
type = requiredOrNot types.attrs;
}
# if is an array
else if property.additionalProperties.type == "array"
then {
type = requiredOrNot (types.loaOf (mapType property.additionalProperties.items));
}
else {
type = requiredOrNot (types.attrsOf (mapType property.additionalProperties));
}
# just a simple property
else {
type = requiredOrNot (mapType property);
};
in
mkOption ({
description = property.description or "";
}
// optionProperties)
) )
definition.properties; definition.properties;
in config = let
mapAttrs (name: property: mkOverride 1002 null) optionalProps; optionalProps =
} filterAttrs
) (
swagger.definitions; propName: property:
!(elem propName (definition.required or []))
)
definition.properties;
in
mapAttrs (name: property: mkOverride 1002 null) optionalProps;
}
)
swagger.definitions;
mapCharPairs = f: s1: s2: concatStrings (imap0 mapCharPairs = f: s1: s2:
(i: c1: concatStrings (imap0
f i c1 (if i >= stringLength s2 then "" else elemAt (stringToCharacters s2) i) (
) i: c1:
(stringToCharacters s1)); f i c1 (
if i >= stringLength s2
then ""
else elemAt (stringToCharacters s2) i
)
)
(stringToCharacters s1));
getAttrName = resource: kind: getAttrName = resource: kind:
mapCharPairs mapCharPairs
(i: c1: c2: (
if hasPrefix "API" kind && i == 0 then "A" i: c1: c2:
else if i == 0 then c1 if hasPrefix "API" kind && i == 0
else if c2 == "" || (toLower c2) != c1 then c1 then "A"
else if i == 0
then c1
else if c2 == "" || (toLower c2) != c1
then c1
else c2 else c2
) )
resource resource
kind; kind;
genResourceTypes = swagger: mapAttrs' genResourceTypes = swagger:
(name: path: mapAttrs'
let (name: path: let
ref = refType (head path.post.parameters).schema; ref = refType (head path.post.parameters).schema;
group' = path.post."x-kubernetes-group-version-kind".group; group' = path.post."x-kubernetes-group-version-kind".group;
version' = path.post."x-kubernetes-group-version-kind".version; version' = path.post."x-kubernetes-group-version-kind".version;
kind' = path.post."x-kubernetes-group-version-kind".kind; kind' = path.post."x-kubernetes-group-version-kind".kind;
name' = last (splitString "/" name); name' = last (splitString "/" name);
attrName = getAttrName name' kind'; attrName = getAttrName name' kind';
in in
nameValuePair ref { nameValuePair ref {
inherit ref attrName; inherit ref attrName;
name = name'; name = name';
group = if group' == "" then "core" else group'; group =
if group' == ""
then "core"
else group';
version = version'; version = version';
kind = kind'; kind = kind';
description = swagger.definitions.${ref}.description; description = swagger.definitions.${ref}.description;
defintion = refDefinition (head path.post.parameters).schema; defintion = refDefinition (head path.post.parameters).schema;
}) })
(filterAttrs (filterAttrs
(name: path: (
hasAttr "post" path && name: path:
path.post."x-kubernetes-action" == "post" hasAttr "post" path
&& path.post."x-kubernetes-action" == "post"
) )
swagger.paths); swagger.paths);
@ -271,35 +287,38 @@ values}]'';
}) })
resourceTypes); resourceTypes);
resourcesTypesByKindSortByVersion = mapAttrs resourcesTypesByKindSortByVersion =
(kind: resourceTypes: mapAttrs
reverseList (sort (
(r1: r2: kind: resourceTypes:
compareVersions r1.version r2.version > 0 reverseList (sort
) (
resourceTypes) r1: r2:
compareVersions r1.version r2.version > 0
)
resourceTypes)
) )
resourceTypesByKind; resourceTypesByKind;
latestResourceTypesByKind = latestResourceTypesByKind =
mapAttrs (kind: resources: last resources) resourcesTypesByKindSortByVersion; mapAttrs (kind: resources: last resources) resourcesTypesByKindSortByVersion;
genResourceOptions = resource: with gen; let genResourceOptions = resource:
submoduleForDefinition' = definition: with gen; let
let submoduleForDefinition' = definition: let
in in
submoduleForDefinition submoduleForDefinition
definition.ref definition.ref
definition.name definition.name
definition.kind definition.kind
definition.group definition.group
definition.version; definition.version;
in in
mkOption { mkOption {
description = resource.description; description = resource.description;
type = types.attrsOf (submoduleForDefinition' resource); type = types.attrsOf (submoduleForDefinition' resource);
default = { }; default = {};
}; };
generated = '' generated = ''
# This file was generated with kubenix k8s generator, do not edit # This file was generated with kubenix k8s generator, do not edit
@ -401,32 +420,37 @@ values}]'';
definitions = { definitions = {
${concatStrings (mapAttrsToList (name: value: '' ${concatStrings (mapAttrsToList (name: value: ''
"${name}" = { "${name}" = {
${optionalString (hasAttr "options" value) " ${optionalString (hasAttr "options" value) "
options = {${concatStrings (mapAttrsToList (name: value: '' options = {${concatStrings (mapAttrsToList (name: value: ''
"${name}" = ${value}; "${name}" = ${value};
'') value.options)}}; '')
value.options)}};
"} "}
${optionalString (hasAttr "config" value) '' ${optionalString (hasAttr "config" value) ''
config = {${concatStrings (mapAttrsToList (name: value: '' config = {${concatStrings (mapAttrsToList (name: value: ''
"${name}" = ${value}; "${name}" = ${value};
'') value.config)}}; '')
''} value.config)}};
}; ''}
'') definitions)} };
'')
definitions)}
}; };
in { in {
# all resource versions # all resource versions
options = { options = {
resources = { resources = {
${concatStrings (mapAttrsToList (_: rt: '' ${concatStrings (mapAttrsToList (_: rt: ''
"${rt.group}"."${rt.version}"."${rt.kind}" = ${genResourceOptions rt}; "${rt.group}"."${rt.version}"."${rt.kind}" = ${genResourceOptions rt};
'') resourceTypes)} '')
resourceTypes)}
} // { } // {
${concatStrings (mapAttrsToList (_: rt: '' ${concatStrings (mapAttrsToList (_: rt: ''
"${rt.attrName}" = ${genResourceOptions rt}; "${rt.attrName}" = ${genResourceOptions rt};
'') latestResourceTypesByKind)} '')
latestResourceTypesByKind)}
}; };
}; };
@ -435,32 +459,34 @@ values}]'';
inherit definitions; inherit definitions;
# register resource types # register resource types
types = [${concatStrings (mapAttrsToList (_: rt: ''{ types = [${concatStrings (mapAttrsToList (_: rt: '' {
name = "${rt.name}"; name = "${rt.name}";
group = "${rt.group}"; group = "${rt.group}";
version = "${rt.version}"; version = "${rt.version}";
kind = "${rt.kind}"; kind = "${rt.kind}";
attrName = "${rt.attrName}"; attrName = "${rt.attrName}";
}'') resourceTypes)}]; }'')
resourceTypes)}];
resources = { resources = {
${concatStrings (mapAttrsToList (_: rt: '' ${concatStrings (mapAttrsToList (_: rt: ''
"${rt.group}"."${rt.version}"."${rt.kind}" = "${rt.group}"."${rt.version}"."${rt.kind}" =
mkAliasDefinitions options.resources."${rt.attrName}"; mkAliasDefinitions options.resources."${rt.attrName}";
'') latestResourceTypesByKind)} '')
latestResourceTypesByKind)}
}; };
}; };
} }
''; '';
in in
pkgs.runCommand "k8s-${name}-gen.nix" pkgs.runCommand "k8s-${name}-gen.nix"
{ {
buildInputs = [ pkgs.nixpkgs-fmt ]; buildInputs = [pkgs.nixpkgs-fmt];
} '' } ''
cat << 'GENERATED' > ./raw cat << 'GENERATED' > ./raw
"${generated}" "${generated}"
GENERATED GENERATED
nixpkgs-fmt ./raw nixpkgs-fmt ./raw
cp ./raw $out cp ./raw $out
'' ''

View file

@ -1,7 +1,8 @@
{ lib, pkgs }:
{ {
k8s = import ./k8s { inherit lib; }; lib,
docker = import ./docker { inherit lib pkgs; }; pkgs,
helm = import ./helm { inherit pkgs; }; }: {
k8s = import ./k8s {inherit lib;};
docker = import ./docker {inherit lib pkgs;};
helm = import ./helm {inherit pkgs;};
} }

View file

@ -1,9 +1,13 @@
{ lib, pkgs }:
with lib;
{ {
copyDockerImages = { images, dest, args ? "" }: lib,
pkgs,
}:
with lib; {
copyDockerImages = {
images,
dest,
args ? "",
}:
pkgs.writeScript "copy-docker-images.sh" (concatMapStrings pkgs.writeScript "copy-docker-images.sh" (concatMapStrings
(image: '' (image: ''
#!${pkgs.runtimeShell} #!${pkgs.runtimeShell}

View file

@ -1,54 +1,54 @@
{ stdenvNoCC, lib, kubernetes-helm, gawk, remarshal, jq }:
with lib;
{ {
# chart to template stdenvNoCC,
chart lib,
kubernetes-helm,
# release name gawk,
, name remarshal,
jq,
# namespace to install release into
, namespace ? null
# values to pass to chart
, values ? { }
# kubernetes version to template chart for
, kubeVersion ? null
}: }:
let with lib;
valuesJsonFile = builtins.toFile "${name}-values.json" (builtins.toJSON values); {
in # chart to template
stdenvNoCC.mkDerivation { chart,
name = "${name}.json"; # release name
buildCommand = '' name,
# template helm file and write resources to yaml # namespace to install release into
helm template "${name}" \ namespace ? null,
${optionalString (kubeVersion != null) "--api-versions ${kubeVersion}"} \ # values to pass to chart
${optionalString (namespace != null) "--namespace ${namespace}"} \ values ? {},
${optionalString (values != { }) "-f ${valuesJsonFile}"} \ # kubernetes version to template chart for
${chart} >resources.yaml kubeVersion ? null,
}: let
valuesJsonFile = builtins.toFile "${name}-values.json" (builtins.toJSON values);
in
stdenvNoCC.mkDerivation {
name = "${name}.json";
buildCommand = ''
# template helm file and write resources to yaml
helm template "${name}" \
${optionalString (kubeVersion != null) "--api-versions ${kubeVersion}"} \
${optionalString (namespace != null) "--namespace ${namespace}"} \
${optionalString (values != {}) "-f ${valuesJsonFile}"} \
${chart} >resources.yaml
# split multy yaml file into multiple files # split multy yaml file into multiple files
awk 'BEGIN{i=1}{line[i++]=$0}END{j=1;n=0; while (j<i) {if (line[j] ~ /^---/) n++; else print line[j] >>"resource-"n".yaml"; j++}}' resources.yaml awk 'BEGIN{i=1}{line[i++]=$0}END{j=1;n=0; while (j<i) {if (line[j] ~ /^---/) n++; else print line[j] >>"resource-"n".yaml"; j++}}' resources.yaml
# join multiple yaml files in jsonl file # join multiple yaml files in jsonl file
for file in ./resource-*.yaml for file in ./resource-*.yaml
do do
remarshal -i $file -if yaml -of json >>resources.jsonl remarshal -i $file -if yaml -of json >>resources.jsonl
done done
# convert jsonl file to json array, remove null values and write to $out # convert jsonl file to json array, remove null values and write to $out
cat resources.jsonl | jq -Scs 'walk( cat resources.jsonl | jq -Scs 'walk(
if type == "object" then if type == "object" then
with_entries(select(.value != null)) with_entries(select(.value != null))
elif type == "array" then elif type == "array" then
map(select(. != null)) map(select(. != null))
else else
. .
end)' > $out end)' > $out
''; '';
nativeBuildInputs = [ kubernetes-helm gawk remarshal jq ]; nativeBuildInputs = [kubernetes-helm gawk remarshal jq];
} }

View file

@ -1,6 +1,4 @@
{ pkgs }: {pkgs}: {
chart2json = pkgs.callPackage ./chart2json.nix {};
{ fetch = pkgs.callPackage ./fetchhelm.nix {};
chart2json = pkgs.callPackage ./chart2json.nix { };
fetch = pkgs.callPackage ./fetchhelm.nix { };
} }

View file

@ -1,50 +1,80 @@
{ stdenvNoCC, lib, kubernetes-helm, cacert }:
let
cleanName = name: lib.replaceStrings [ "/" ] [ "-" ] name;
in
{ {
# name of the chart stdenvNoCC,
chart lib,
kubernetes-helm,
cacert,
}: let
cleanName = name: lib.replaceStrings ["/"] ["-"] name;
in
{
# name of the chart
chart,
# chart url to fetch from custom location
chartUrl ? null,
# version of the chart
version ? null,
# chart hash
sha256,
# whether to extract chart
untar ? true,
# use custom charts repo
repo ? null,
# pass --verify to helm chart
verify ? false,
# pass --devel to helm chart
devel ? false,
}:
stdenvNoCC.mkDerivation {
name = "${cleanName chart}-${
if version == null
then "dev"
else version
}";
# chart url to fetch from custom location buildCommand = ''
, chartUrl ? null export HOME="$PWD"
echo "adding helm repo"
# version of the chart ${
, version ? null if repo == null
then ""
# chart hash else "helm repo add repository ${repo}"
, sha256 }
echo "fetching helm chart"
# whether to extract chart helm fetch -d ./chart \
, untar ? true ${
if untar
# use custom charts repo then "--untar"
, repo ? null else ""
} \
# pass --verify to helm chart ${
, verify ? false if version == null
then ""
# pass --devel to helm chart else "--version ${version}"
, devel ? false } \
}: stdenvNoCC.mkDerivation { ${
name = "${cleanName chart}-${if version == null then "dev" else version}"; if devel
then "--devel"
buildCommand = '' else ""
export HOME="$PWD" } \
echo "adding helm repo" ${
${if repo == null then "" else "helm repo add repository ${repo}"} if verify
echo "fetching helm chart" then "--verify"
helm fetch -d ./chart \ else ""
${if untar then "--untar" else ""} \ } \
${if version == null then "" else "--version ${version}"} \ ${
${if devel then "--devel" else ""} \ if chartUrl == null
${if verify then "--verify" else ""} \ then
${if chartUrl == null then (if repo == null then chart else "repository/${chart}") else chartUrl} (
cp -r chart/*/ $out if repo == null
''; then chart
outputHashMode = "recursive"; else "repository/${chart}"
outputHashAlgo = "sha256"; )
outputHash = sha256; else chartUrl
nativeBuildInputs = [ kubernetes-helm cacert ]; }
} cp -r chart/*/ $out
'';
outputHashMode = "recursive";
outputHashAlgo = "sha256";
outputHash = sha256;
nativeBuildInputs = [kubernetes-helm cacert];
}

View file

@ -1,9 +1,7 @@
{ pkgs ? import <nixpkgs> { } }: {pkgs ? import <nixpkgs> {}}: let
let fetchhelm = pkgs.callPackage ./fetchhelm.nix {};
fetchhelm = pkgs.callPackage ./fetchhelm.nix { }; chart2json = pkgs.callPackage ./chart2json.nix {};
chart2json = pkgs.callPackage ./chart2json.nix { }; in rec {
in
rec {
postgresql-chart = fetchhelm { postgresql-chart = fetchhelm {
chart = "stable/postgresql"; chart = "stable/postgresql";
version = "0.18.1"; version = "0.18.1";

View file

@ -1,31 +1,43 @@
{ lib }: {lib}:
with lib; rec {
with lib;
rec {
# TODO: refactor with mkOptionType # TODO: refactor with mkOptionType
mkSecretOption = { description ? "", default ? { }, allowNull ? true }: mkOption { mkSecretOption = {
inherit description; description ? "",
type = (if allowNull then types.nullOr else id) (types.submodule { default ? {},
options = { allowNull ? true,
name = mkOption ({ }:
description = "Name of the secret where secret is stored"; mkOption {
type = types.str; inherit description;
default = default.name; type =
} // (optionalAttrs (default ? "name") { (
default = default.name; if allowNull
})); then types.nullOr
else id
) (types.submodule {
options = {
name = mkOption ({
description = "Name of the secret where secret is stored";
type = types.str;
default = default.name;
}
// (optionalAttrs (default ? "name") {
default = default.name;
}));
key = mkOption ({ key = mkOption ({
description = "Name of the key where secret is stored"; description = "Name of the key where secret is stored";
type = types.str; type = types.str;
} // (optionalAttrs (default ? "key") { }
default = default.key; // (optionalAttrs (default ? "key") {
})); default = default.key;
}; }));
}); };
default = if default == null then null else { }; });
}; default =
if default == null
then null
else {};
};
secretToEnv = value: { secretToEnv = value: {
valueFrom.secretKeyRef = { valueFrom.secretKeyRef = {
@ -34,7 +46,10 @@ rec {
}; };
# Creates kubernetes list from a list of kubernetes objects # Creates kubernetes list from a list of kubernetes objects
mkList = { items, labels ? { } }: { mkList = {
items,
labels ? {},
}: {
kind = "List"; kind = "List";
apiVersion = "v1"; apiVersion = "v1";
@ -42,22 +57,27 @@ rec {
}; };
# Creates hashed kubernetes list from a list of kubernetes objects # Creates hashed kubernetes list from a list of kubernetes objects
mkHashedList = { items, labels ? { } }: mkHashedList = {
let items,
hash = builtins.hashString "sha1" (builtins.toJSON items); labels ? {},
}: let
hash = builtins.hashString "sha1" (builtins.toJSON items);
labeledItems = map labeledItems =
(item: recursiveUpdate item { map
(item:
recursiveUpdate item {
metadata.labels."kubenix/hash" = hash; metadata.labels."kubenix/hash" = hash;
}) })
items; items;
in
in
mkList { mkList {
items = labeledItems; items = labeledItems;
labels = { labels =
"kubenix/hash" = hash; {
} // labels; "kubenix/hash" = hash;
}
// labels;
}; };
toBase64 = lib.toBase64; toBase64 = lib.toBase64;

View file

@ -1,36 +1,45 @@
{ lib, pkgs }: {
lib,
pkgs,
}:
with lib; let
self = {
importYAML = path:
importJSON (pkgs.runCommand "yaml-to-json" {} ''
${pkgs.remarshal}/bin/remarshal -i ${path} -if yaml -of json > $out
'');
with lib; toYAML = config:
builtins.readFile (pkgs.runCommand "to-yaml" {} ''
${pkgs.remarshal}/bin/remarshal -i ${pkgs.writeText "to-json" (builtins.toJSON config)} -if json -of yaml > $out
'');
let self = { toMultiDocumentYaml = name: documents:
pkgs.runCommand name {}
(concatMapStringsSep "\necho --- >> $out\n"
(
d: "${pkgs.remarshal}/bin/remarshal -i ${builtins.toFile "doc" (builtins.toJSON d)} -if json -of yaml >> $out"
)
documents);
importYAML = path: importJSON (pkgs.runCommand "yaml-to-json" { } '' toBase64 = value:
${pkgs.remarshal}/bin/remarshal -i ${path} -if yaml -of json > $out builtins.readFile
''); (pkgs.runCommand "value-to-b64" {} "echo -n '${value}' | ${pkgs.coreutils}/bin/base64 -w0 > $out");
toYAML = config: builtins.readFile (pkgs.runCommand "to-yaml" { } '' exp = base: exp: foldr (value: acc: acc * base) 1 (range 1 exp);
${pkgs.remarshal}/bin/remarshal -i ${pkgs.writeText "to-json" (builtins.toJSON config)} -if json -of yaml > $out
'');
toMultiDocumentYaml = name: documents: pkgs.runCommand name { } octalToDecimal = value:
(concatMapStringsSep "\necho --- >> $out\n" (foldr
(d: (char: acc: {
"${pkgs.remarshal}/bin/remarshal -i ${builtins.toFile "doc" (builtins.toJSON d)} -if json -of yaml >> $out" i = acc.i + 1;
) value = acc.value + (toInt char) * (self.exp 8 acc.i);
documents); })
{
toBase64 = value: i = 0;
builtins.readFile value = 0;
(pkgs.runCommand "value-to-b64" { } "echo -n '${value}' | ${pkgs.coreutils}/bin/base64 -w0 > $out"); }
(stringToCharacters value))
exp = base: exp: foldr (value: acc: acc * base) 1 (range 1 exp); .value;
};
octalToDecimal = value: (foldr in
(char: acc: { self
i = acc.i + 1;
value = acc.value + (toInt char) * (self.exp 8 acc.i);
})
{ i = 0; value = 0; }
(stringToCharacters value)).value;
};
in self

View file

@ -1,8 +1,9 @@
{ config, lib, ... }:
with lib;
{ {
config,
lib,
...
}:
with lib; {
options = { options = {
kubenix.project = mkOption { kubenix.project = mkOption {
description = "Name of the project"; description = "Name of the project";
@ -13,27 +14,27 @@ with lib;
_m.features = mkOption { _m.features = mkOption {
description = "List of features exposed by module"; description = "List of features exposed by module";
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [];
}; };
_m.propagate = mkOption { _m.propagate = mkOption {
description = "Module propagation options"; description = "Module propagation options";
type = types.listOf (types.submodule ({ config, ... }: { type = types.listOf (types.submodule ({config, ...}: {
options = { options = {
features = mkOption { features = mkOption {
description = "List of features that submodule has to have to propagate module"; description = "List of features that submodule has to have to propagate module";
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [];
}; };
module = mkOption { module = mkOption {
description = "Module to propagate"; description = "Module to propagate";
type = types.unspecified; type = types.unspecified;
default = { }; default = {};
}; };
}; };
})); }));
default = [ ]; default = [];
}; };
}; };
} }

View file

@ -1,11 +1,14 @@
{ config, lib, pkgs, docker, ... }:
with lib;
let
cfg = config.docker;
in
{ {
imports = [ ./base.nix ]; config,
lib,
pkgs,
docker,
...
}:
with lib; let
cfg = config.docker;
in {
imports = [./base.nix];
options.docker = { options.docker = {
registry.url = mkOption { registry.url = mkOption {
@ -16,7 +19,11 @@ in
images = mkOption { images = mkOption {
description = "Attribute set of docker images that should be published"; description = "Attribute set of docker images that should be published";
type = types.attrsOf (types.submodule ({ name, config, ... }: { type = types.attrsOf (types.submodule ({
name,
config,
...
}: {
options = { options = {
image = mkOption { image = mkOption {
description = "Docker image to publish"; description = "Docker image to publish";
@ -52,13 +59,13 @@ in
}; };
}; };
})); }));
default = { }; default = {};
}; };
export = mkOption { export = mkOption {
description = "List of images to export"; description = "List of images to export";
type = types.listOf types.package; type = types.listOf types.package;
default = [ ]; default = [];
}; };
copyScript = mkOption { copyScript = mkOption {
@ -73,22 +80,29 @@ in
config = { config = {
# define docker feature # define docker feature
_m.features = [ "docker" ]; _m.features = ["docker"];
# propagate docker options if docker feature is enabled # propagate docker options if docker feature is enabled
_m.propagate = [{ _m.propagate = [
features = [ "docker" ]; {
module = { config, name, ... }: { features = ["docker"];
# propagate registry options module = {
docker.registry = cfg.registry; config,
}; name,
}]; ...
}: {
# propagate registry options
docker.registry = cfg.registry;
};
}
];
# pass docker library as param # pass docker library as param
_module.args.docker = import ../lib/docker { inherit lib pkgs; }; _module.args.docker = import ../lib/docker {inherit lib pkgs;};
# list of exported docker images # list of exported docker images
docker.export = mapAttrsToList (_: i: i.image) docker.export =
mapAttrsToList (_: i: i.image)
(filterAttrs (_: i: i.registry != null) config.docker.images); (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,10 +1,13 @@
# helm defines kubenix module with options for using helm charts # helm defines kubenix module with options for using helm charts
# with kubenix # with kubenix
{
{ config, lib, pkgs, helm, ... }: config,
lib,
with lib; pkgs,
let helm,
...
}:
with lib; let
cfg = config.kubernetes.helm; cfg = config.kubernetes.helm;
globalConfig = config; globalConfig = config;
@ -13,26 +16,29 @@ let
name = "recursive-attrs"; name = "recursive-attrs";
description = "recursive attribute set"; description = "recursive attribute set";
check = isAttrs; check = isAttrs;
merge = loc: foldl' (res: def: recursiveUpdate res def.value) { }; merge = loc: foldl' (res: def: recursiveUpdate res def.value) {};
}; };
parseApiVersion = apiVersion: parseApiVersion = apiVersion: let
let splitted = splitString "/" apiVersion;
splitted = splitString "/" apiVersion; in {
in group =
{ if length splitted == 1
group = if length splitted == 1 then "core" else head splitted; then "core"
version = last splitted; else head splitted;
}; version = last splitted;
};
in in {
{ imports = [./k8s.nix];
imports = [ ./k8s.nix ];
options.kubernetes.helm = { options.kubernetes.helm = {
instances = mkOption { instances = mkOption {
description = "Attribute set of helm instances"; description = "Attribute set of helm instances";
type = types.attrsOf (types.submodule ({ config, name, ... }: { type = types.attrsOf (types.submodule ({
config,
name,
...
}: {
options = { options = {
name = mkOption { name = mkOption {
description = "Helm release name"; description = "Helm release name";
@ -54,7 +60,7 @@ in
values = mkOption { values = mkOption {
description = "Values to pass to chart"; description = "Values to pass to chart";
type = recursiveAttrs; type = recursiveAttrs;
default = { }; default = {};
}; };
kubeVersion = mkOption { kubeVersion = mkOption {
@ -66,7 +72,7 @@ in
overrides = mkOption { overrides = mkOption {
description = "Overrides to apply to all chart resources"; description = "Overrides to apply to all chart resources";
type = types.listOf types.unspecified; type = types.listOf types.unspecified;
default = [ ]; default = [];
}; };
overrideNamespace = mkOption { overrideNamespace = mkOption {
@ -78,39 +84,41 @@ in
objects = mkOption { objects = mkOption {
description = "Generated kubernetes objects"; description = "Generated kubernetes objects";
type = types.listOf types.attrs; type = types.listOf types.attrs;
default = [ ]; default = [];
}; };
}; };
config.overrides = mkIf (config.overrideNamespace && config.namespace != null) [{ config.overrides = mkIf (config.overrideNamespace && config.namespace != null) [
metadata.namespace = config.namespace; {
}]; metadata.namespace = config.namespace;
}
];
config.objects = importJSON (helm.chart2json { config.objects = importJSON (helm.chart2json {
inherit (config) chart name namespace values kubeVersion; inherit (config) chart name namespace values kubeVersion;
}); });
})); }));
default = { }; default = {};
}; };
}; };
config = { config = {
# expose helm helper methods as module argument # expose helm helper methods as module argument
_module.args.helm = import ../lib/helm { inherit pkgs; }; _module.args.helm = import ../lib/helm {inherit pkgs;};
kubernetes.api.resources = mkMerge (flatten (mapAttrsToList kubernetes.api.resources = mkMerge (flatten (mapAttrsToList
(_: instance: (
map _: instance:
(object: map
let (object: let
apiVersion = parseApiVersion object.apiVersion; apiVersion = parseApiVersion object.apiVersion;
name = object.metadata.name; name = object.metadata.name;
in in {
{ "${apiVersion.group}"."${apiVersion.version}".${object.kind}."${name}" = mkMerge ([
"${apiVersion.group}"."${apiVersion.version}".${object.kind}."${name}" = mkMerge ([
object object
] ++ instance.overrides); ]
}) ++ instance.overrides);
})
instance.objects instance.objects
) )
cfg.instances)); cfg.instances));

View file

@ -1,16 +1,19 @@
{ lib, definitions }:
with lib;
{ {
"istio_networking_v1alpha3_StringMatch" = recursiveUpdate lib,
(recursiveUpdate definitions,
}:
with lib; {
"istio_networking_v1alpha3_StringMatch" =
recursiveUpdate
(
recursiveUpdate
definitions."istio_networking_v1alpha3_StringMatch_Exact" definitions."istio_networking_v1alpha3_StringMatch_Exact"
definitions."istio_networking_v1alpha3_StringMatch_Prefix" definitions."istio_networking_v1alpha3_StringMatch_Prefix"
) )
definitions."istio_networking_v1alpha3_StringMatch_Regex"; definitions."istio_networking_v1alpha3_StringMatch_Regex";
"istio_networking_v1alpha3_PortSelector" = recursiveUpdate "istio_networking_v1alpha3_PortSelector" =
recursiveUpdate
definitions."istio_networking_v1alpha3_PortSelector_Name" definitions."istio_networking_v1alpha3_PortSelector_Name"
definitions."istio_networking_v1alpha3_PortSelector_Number"; definitions."istio_networking_v1alpha3_PortSelector_Number";
} }

File diff suppressed because it is too large Load diff

View file

@ -1,33 +1,36 @@
# K8S module defines kubernetes definitions for kubenix # K8S module defines kubernetes definitions for kubenix
{
{ options, config, lib, pkgs, k8s, ... }: options,
config,
with lib; lib,
let pkgs,
k8s,
...
}:
with lib; let
cfg = config.kubernetes; cfg = config.kubernetes;
gvkKeyFn = type: "${type.group}/${type.version}/${type.kind}"; gvkKeyFn = type: "${type.group}/${type.version}/${type.kind}";
getDefaults = resource: group: version: kind: getDefaults = resource: group: version: kind:
catAttrs "default" (filter catAttrs "default" (filter
(default: (
(resource == null || default.resource == null || default.resource == resource) && default:
(default.group == null || default.group == group) && (resource == null || default.resource == null || default.resource == resource)
(default.version == null || default.version == version) && && (default.group == null || default.group == group)
(default.kind == null || default.kind == kind) && (default.version == null || default.version == version)
&& (default.kind == null || default.kind == kind)
) )
cfg.api.defaults); cfg.api.defaults);
moduleToAttrs = value: moduleToAttrs = value:
if isAttrs value if isAttrs value
then mapAttrs (n: v: moduleToAttrs v) (filterAttrs (n: v: v != null && !(hasPrefix "_" n)) value) then mapAttrs (n: v: moduleToAttrs v) (filterAttrs (n: v: v != null && !(hasPrefix "_" n)) value)
else if isList value else if isList value
then map (v: moduleToAttrs v) value then map (v: moduleToAttrs v) value
else value; else value;
apiOptions = { config, ... }: { apiOptions = {config, ...}: {
options = { options = {
definitions = mkOption { definitions = mkOption {
description = "Attribute set of kubernetes definitions"; description = "Attribute set of kubernetes definitions";
@ -35,7 +38,7 @@ let
defaults = mkOption { defaults = mkOption {
description = "Kubernetes defaults to apply to resources"; description = "Kubernetes defaults to apply to resources";
type = types.listOf (types.submodule ({ config, ... }: { type = types.listOf (types.submodule ({config, ...}: {
options = { options = {
group = mkOption { group = mkOption {
description = "Group to apply default to (all by default)"; description = "Group to apply default to (all by default)";
@ -70,17 +73,18 @@ let
default = mkOption { default = mkOption {
description = "Default to apply"; description = "Default to apply";
type = types.unspecified; type = types.unspecified;
default = { }; default = {};
}; };
}; };
})); }));
default = [ ]; default = [];
apply = unique; apply = unique;
}; };
types = mkOption { types = mkOption {
description = "List of registered kubernetes types"; description = "List of registered kubernetes types";
type = coerceListOfSubmodulesToAttrs type =
coerceListOfSubmodulesToAttrs
{ {
options = { options = {
group = mkOption { group = mkOption {
@ -110,7 +114,7 @@ let
}; };
} }
gvkKeyFn; gvkKeyFn;
default = { }; default = {};
}; };
}; };
@ -121,17 +125,26 @@ let
}; };
indexOf = lst: value: indexOf = lst: value:
head (filter (v: v != -1) (imap0 (i: v: if v == value then i else -1) lst)); head (filter (v: v != -1) (imap0 (i: v:
if v == value
then i
else -1)
lst));
compareVersions = ver1: ver2: compareVersions = ver1: ver2: let
let getVersion = v: substring 1 10 v;
getVersion = v: substring 1 10 v; splittedVer1 = builtins.splitVersion (getVersion ver1);
splittedVer1 = builtins.splitVersion (getVersion ver1); splittedVer2 = builtins.splitVersion (getVersion ver2);
splittedVer2 = builtins.splitVersion (getVersion ver2);
v1 = if length splittedVer1 == 1 then "${getVersion ver1}prod" else getVersion ver1; v1 =
v2 = if length splittedVer2 == 1 then "${getVersion ver2}prod" else getVersion ver2; if length splittedVer1 == 1
in then "${getVersion ver1}prod"
else getVersion ver1;
v2 =
if length splittedVer2 == 1
then "${getVersion ver2}prod"
else getVersion ver2;
in
builtins.compareVersions v1 v2; builtins.compareVersions v1 v2;
customResourceTypesByAttrName = zipAttrs (mapAttrsToList customResourceTypesByAttrName = zipAttrs (mapAttrsToList
@ -140,20 +153,23 @@ let
}) })
cfg.customTypes); cfg.customTypes);
customResourceTypesByAttrNameSortByVersion = mapAttrs customResourceTypesByAttrNameSortByVersion =
(_: resourceTypes: mapAttrs
reverseList (sort (
(r1: r2: _: resourceTypes:
compareVersions r1.version r2.version > 0 reverseList (sort
) (
resourceTypes) r1: r2:
compareVersions r1.version r2.version > 0
)
resourceTypes)
) )
customResourceTypesByAttrName; customResourceTypesByAttrName;
latestCustomResourceTypes = latestCustomResourceTypes =
mapAttrsToList (_: resources: last resources) customResourceTypesByAttrNameSortByVersion; mapAttrsToList (_: resources: last resources) customResourceTypesByAttrNameSortByVersion;
customResourceModuleForType = config: ct: { name, ... }: { customResourceModuleForType = config: ct: {name, ...}: {
imports = getDefaults ct.name ct.group ct.version ct.kind; imports = getDefaults ct.name ct.group ct.version ct.kind;
options = { options = {
apiVersion = mkOption { apiVersion = mkOption {
@ -174,7 +190,7 @@ let
spec = mkOption { spec = mkOption {
description = "Module spec"; description = "Module spec";
type = types.either types.attrs (types.submodule ct.module); type = types.either types.attrs (types.submodule ct.module);
default = { }; default = {};
}; };
}; };
@ -185,81 +201,78 @@ let
}; };
}; };
customResourceOptions = (mapAttrsToList customResourceOptions =
(_: ct: { config, ... }: (mapAttrsToList
let (_: ct: {config, ...}: let
module = customResourceModuleForType config ct; module = customResourceModuleForType config ct;
in in {
{
options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption { options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption {
description = ct.description; description = ct.description;
type = types.attrsOf (types.submodule module); type = types.attrsOf (types.submodule module);
default = { }; default = {};
}; };
}) })
cfg.customTypes) ++ (map cfg.customTypes)
(ct: { options, config, ... }: ++ (map
let (ct: {
options,
config,
...
}: let
module = customResourceModuleForType config ct; module = customResourceModuleForType config ct;
in in {
{
options.resources.${ct.attrName} = mkOption { options.resources.${ct.attrName} = mkOption {
description = ct.description; description = ct.description;
type = types.attrsOf (types.submodule module); type = types.attrsOf (types.submodule module);
default = { }; default = {};
}; };
config.resources.${ct.group}.${ct.version}.${ct.kind} = config.resources.${ct.group}.${ct.version}.${ct.kind} =
mkAliasDefinitions options.resources.${ct.attrName}; mkAliasDefinitions options.resources.${ct.attrName};
}) })
latestCustomResourceTypes); latestCustomResourceTypes);
coerceListOfSubmodulesToAttrs = submodule: keyFn: coerceListOfSubmodulesToAttrs = submodule: keyFn: let
let mergeValuesByFn = keyFn: values:
mergeValuesByFn = keyFn: values: listToAttrs (map
listToAttrs (map (
(value: value:
nameValuePair (toString (keyFn value)) value nameValuePair (toString (keyFn value)) value
) )
values); values);
# Either value of type `finalType` or `coercedType`, the latter is # Either value of type `finalType` or `coercedType`, the latter is
# converted to `finalType` using `coerceFunc`. # converted to `finalType` using `coerceFunc`.
coercedTo = coercedType: coerceFunc: finalType: coercedTo = coercedType: coerceFunc: finalType:
mkOptionType rec { mkOptionType rec {
name = "coercedTo"; name = "coercedTo";
description = "${finalType.description} or ${coercedType.description}"; description = "${finalType.description} or ${coercedType.description}";
check = x: finalType.check x || coercedType.check x; check = x: finalType.check x || coercedType.check x;
merge = loc: defs: merge = loc: defs: let
let coerceVal = val:
coerceVal = val: if finalType.check val
if finalType.check val then then val
val else let coerced = coerceFunc val; in assert finalType.check coerced; coerced;
else in
let coerced = coerceFunc val; in assert finalType.check coerced; coerced; finalType.merge loc (map (def: def // {value = coerceVal def.value;}) defs);
getSubOptions = finalType.getSubOptions;
in getSubModules = finalType.getSubModules;
finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
getSubOptions = finalType.getSubOptions; typeMerge = t1: t2: null;
getSubModules = finalType.getSubModules; functor = (defaultFunctor name) // {wrapped = finalType;};
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); };
typeMerge = t1: t2: null; in
functor = (defaultFunctor name) // { wrapped = finalType; };
};
in
coercedTo coercedTo
(types.listOf (types.submodule submodule)) (types.listOf (types.submodule submodule))
(mergeValuesByFn keyFn) (mergeValuesByFn keyFn)
(types.attrsOf (types.submodule submodule)); (types.attrsOf (types.submodule submodule));
in {
in imports = [./base.nix];
{
imports = [ ./base.nix ];
options.kubernetes = { options.kubernetes = {
version = mkOption { version = mkOption {
description = "Kubernetes version to use"; description = "Kubernetes version to use";
type = types.enum [ "1.19" "1.20" "1.21" ]; type = types.enum ["1.19" "1.20" "1.21"];
default = "1.21"; default = "1.21";
}; };
@ -280,29 +293,32 @@ in
api = mkOption { api = mkOption {
type = types.submodule { type = types.submodule {
imports = [ imports =
(./generated + ''/v'' + cfg.version + ".nix") [
apiOptions (./generated + ''/v'' + cfg.version + ".nix")
] ++ customResourceOptions; apiOptions
]
++ customResourceOptions;
}; };
default = { }; default = {};
}; };
imports = mkOption { imports = mkOption {
type = types.listOf (types.either types.package types.path); type = types.listOf (types.either types.package types.path);
description = "List of resources to import"; description = "List of resources to import";
default = [ ]; default = [];
}; };
resources = mkOption { resources = mkOption {
description = "Alias for `config.kubernetes.api.resources` options"; description = "Alias for `config.kubernetes.api.resources` options";
default = { }; default = {};
type = types.attrsOf types.attrs; type = types.attrsOf types.attrs;
}; };
customTypes = mkOption { customTypes = mkOption {
description = "List of custom resource types to make API for"; description = "List of custom resource types to make API for";
type = coerceListOfSubmodulesToAttrs type =
coerceListOfSubmodulesToAttrs
{ {
options = { options = {
group = mkOption { group = mkOption {
@ -340,25 +356,29 @@ in
module = mkOption { module = mkOption {
description = "Custom type module"; description = "Custom type module";
type = types.unspecified; type = types.unspecified;
default = { }; default = {};
}; };
}; };
} }
gvkKeyFn; gvkKeyFn;
default = { }; default = {};
}; };
objects = mkOption { objects = mkOption {
description = "List of generated kubernetes objects"; description = "List of generated kubernetes objects";
type = types.listOf types.attrs; type = types.listOf types.attrs;
apply = items: sort apply = items:
(r1: r2: sort
if elem r1.kind cfg.resourceOrder && elem r2.kind cfg.resourceOrder (
then indexOf cfg.resourceOrder r1.kind < indexOf cfg.resourceOrder r2.kind r1: r2:
else if elem r1.kind cfg.resourceOrder then true else false 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); (unique items);
default = [ ]; default = [];
}; };
generated = mkOption { generated = mkOption {
@ -379,26 +399,26 @@ in
config = { config = {
# features that module is defining # features that module is defining
_m.features = [ "k8s" ]; _m.features = ["k8s"];
# module propagation options # module propagation options
_m.propagate = [{ _m.propagate = [
features = [ "k8s" ];
module = { config, ... }: {
# propagate kubernetes version and namespace
kubernetes.version = mkDefault cfg.version;
kubernetes.namespace = mkDefault cfg.namespace;
};
}
{ {
features = [ "k8s" "submodule" ]; features = ["k8s"];
module = { config, ... }: { 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 # set module defaults
kubernetes.api.defaults = ( kubernetes.api.defaults = (
# propagate defaults if default propagation is enabled # propagate defaults if default propagation is enabled
(filter (default: default.propagate) cfg.api.defaults) ++ (filter (default: default.propagate) cfg.api.defaults)
++ [
[
# set module name and version for all kuberentes resources # set module name and version for all kuberentes resources
{ {
default.metadata.labels = { default.metadata.labels = {
@ -409,38 +429,44 @@ in
] ]
); );
}; };
}]; }
];
# expose k8s helper methods as module argument # expose k8s helper methods as module argument
_module.args.k8s = import ../lib/k8s { inherit lib; }; _module.args.k8s = import ../lib/k8s {inherit lib;};
kubernetes.api = mkMerge ([{ kubernetes.api = mkMerge ([
# register custom types {
types = mapAttrsToList # register custom types
(_: cr: { types =
inherit (cr) name group version kind attrName; mapAttrsToList
}) (_: cr: {
cfg.customTypes; inherit (cr) name group version kind attrName;
})
cfg.customTypes;
defaults = [{ defaults = [
default = { {
# set default kubernetes namespace to all resources default = {
metadata.namespace = mkIf (config.kubernetes.namespace != null) # set default kubernetes namespace to all resources
(mkDefault config.kubernetes.namespace); metadata.namespace =
mkIf (config.kubernetes.namespace != null)
(mkDefault config.kubernetes.namespace);
# set project name to all resources # set project name to all resources
metadata.annotations = { metadata.annotations = {
"kubenix/project-name" = config.kubenix.project; "kubenix/project-name" = config.kubenix.project;
"kubenix/k8s-version" = cfg.version; "kubenix/k8s-version" = cfg.version;
}; };
}; };
}]; }
}] ++ ];
}
# import of yaml files ]
(map ++
(i: # import of yaml files
let (map
(i: let
# load yaml file # load yaml file
object = importYAML i; object = importYAML i;
groupVersion = splitString "/" object.apiVersion; groupVersion = splitString "/" object.apiVersion;
@ -448,17 +474,18 @@ in
version = last groupVersion; version = last groupVersion;
group = group =
if version == (head groupVersion) if version == (head groupVersion)
then "core" else head groupVersion; then "core"
else head groupVersion;
kind = object.kind; kind = object.kind;
in in {
{
resources.${group}.${version}.${kind}.${name} = object; resources.${group}.${version}.${kind}.${name} = object;
}) })
cfg.imports)); cfg.imports));
kubernetes.objects = flatten (mapAttrsToList kubernetes.objects = flatten (mapAttrsToList
(_: type: (
mapAttrsToList (name: resource: moduleToAttrs resource) _: type:
mapAttrsToList (name: resource: moduleToAttrs resource)
cfg.api.resources.${type.group}.${type.version}.${type.kind} cfg.api.resources.${type.group}.${type.version}.${type.kind}
) )
cfg.api.types); cfg.api.types);

View file

@ -1,8 +1,10 @@
{ config, lib, ... }:
with lib;
{ {
imports = [ ./base.nix ]; config,
lib,
...
}:
with lib; {
imports = [./base.nix];
options.submodule = { options.submodule = {
name = mkOption { name = mkOption {
@ -25,24 +27,24 @@ with lib;
tags = mkOption { tags = mkOption {
description = "List of submodule tags"; description = "List of submodule tags";
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [];
}; };
exports = mkOption { exports = mkOption {
description = "Attribute set of functions to export"; description = "Attribute set of functions to export";
type = types.attrs; type = types.attrs;
default = { }; default = {};
}; };
passthru = mkOption { passthru = mkOption {
description = "Attribute set to passthru"; description = "Attribute set to passthru";
type = types.attrs; type = types.attrs;
default = { }; default = {};
}; };
args._empty = mkOption { }; args._empty = mkOption {};
}; };
config._module.args.args = config.submodule.args; config._module.args.args = config.submodule.args;
config._m.features = [ "submodule" ]; config._m.features = ["submodule"];
} }

View file

@ -1,140 +1,183 @@
{ config, options, kubenix, pkgs, lib, ... }: {
config,
with lib; options,
let kubenix,
pkgs,
lib,
...
}:
with lib; let
cfg = config.submodules; cfg = config.submodules;
parentConfig = config; parentConfig = config;
matchesVersion = requiredVersion: version: matchesVersion = requiredVersion: version:
if requiredVersion != null then if requiredVersion != null
then
if hasPrefix "~" requiredVersion if hasPrefix "~" requiredVersion
then (builtins.match (removePrefix "~" requiredVersion) version) != null then (builtins.match (removePrefix "~" requiredVersion) version) != null
else requiredVersion == version else requiredVersion == version
else true; else true;
getDefaults = { name, version, tags, features }: getDefaults = {
name,
version,
tags,
features,
}:
catAttrs "default" (filter catAttrs "default" (filter
(submoduleDefault: (
(submoduleDefault.name == null || submoduleDefault.name == name) && submoduleDefault:
(matchesVersion submoduleDefault.version version) && (submoduleDefault.name == null || submoduleDefault.name == name)
( && (matchesVersion submoduleDefault.version version)
(length submoduleDefault.tags == 0) || && (
(length (intersectLists submoduleDefault.tags tags)) > 0 (length submoduleDefault.tags == 0)
) && || (length (intersectLists submoduleDefault.tags tags)) > 0
( )
(length submoduleDefault.features == 0) || && (
(length (intersectLists submoduleDefault.features features)) > 0 (length submoduleDefault.features == 0)
) || (length (intersectLists submoduleDefault.features features)) > 0
)
) )
config.submodules.defaults); config.submodules.defaults);
specialArgs = cfg.specialArgs // { specialArgs =
parentConfig = config; cfg.specialArgs
}; // {
parentConfig = config;
};
findSubmodule = { name, version ? null, latest ? true }: findSubmodule = {
let name,
matchingSubmodules = filter version ? null,
(el: latest ? true,
el.definition.name == name && }: let
(matchesVersion version el.definition.version) matchingSubmodules =
) filter
cfg.imports; (
el:
el.definition.name
== name
&& (matchesVersion version el.definition.version)
)
cfg.imports;
versionSortedSubmodules = sort versionSortedSubmodules =
(s1: s2: sort
(
s1: s2:
if builtins.compareVersions s1.definition.version s2.definition.version > 0 if builtins.compareVersions s1.definition.version s2.definition.version > 0
then true else false then true
) else false
matchingSubmodules; )
matchingSubmodules;
matchingModule = matchingModule =
if length versionSortedSubmodules == 0 if length versionSortedSubmodules == 0
then throw "No module found ${name}/${if version == null then "latest" else version}" then
else head versionSortedSubmodules; throw "No module found ${name}/${
in if version == null
then "latest"
else version
}"
else head versionSortedSubmodules;
in
matchingModule; matchingModule;
passthruConfig = mapAttrsToList passthruConfig =
mapAttrsToList
(name: opt: { (name: opt: {
${name} = mkMerge (mapAttrsToList ${name} = mkMerge (mapAttrsToList
(_: inst: (
if inst.passthru.enable _: inst:
then inst.config.submodule.passthru.${name} or { } if inst.passthru.enable
else { } then inst.config.submodule.passthru.${name} or {}
else {}
) )
config.submodules.instances); config.submodules.instances);
_module.args = mkMerge (mapAttrsToList _module.args = mkMerge (mapAttrsToList
(_: inst: (
if inst.passthru.enable _: inst:
then inst.config.submodule.passthru._module.args or { } if inst.passthru.enable
else { } then inst.config.submodule.passthru._module.args or {}
else {}
) )
config.submodules.instances); config.submodules.instances);
}) })
(removeAttrs options [ "_definedNames" "_module" "_m" "submodules" ]); (removeAttrs options ["_definedNames" "_module" "_m" "submodules"]);
submoduleWithSpecialArgs = opts: specialArgs: submoduleWithSpecialArgs = opts: specialArgs: let
let opts' = toList opts;
opts' = toList opts; inherit (lib.modules) evalModules;
inherit (lib.modules) evalModules; in
in
mkOptionType rec { mkOptionType rec {
name = "submodule"; name = "submodule";
check = x: isAttrs x || isFunction x; check = x: isAttrs x || isFunction x;
merge = loc: defs: merge = loc: defs: let
let coerce = def:
coerce = def: if isFunction def then def else { config = def; }; if isFunction def
modules = opts' ++ map (def: { _file = def.file; imports = [ (coerce def.value) ]; }) defs; then def
in else {config = def;};
modules =
opts'
++ map (def: {
_file = def.file;
imports = [(coerce def.value)];
})
defs;
in
(evalModules { (evalModules {
inherit modules specialArgs; inherit modules specialArgs;
prefix = loc; prefix = loc;
}).config; })
getSubOptions = prefix: (evalModules .config;
{ getSubOptions = prefix:
modules = opts'; inherit prefix specialArgs; (evalModules
# 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" modules = opts';
# attribute to be given to the sub-module. As the option inherit prefix specialArgs;
# evaluation does not have any specific attribute name, we # This is a work-around due to the fact that some sub-modules,
# provide a default one for the documentation. # such as the one included in an attribute set, expects a "args"
# # attribute to be given to the sub-module. As the option
# This is mandatory as some option declaration might use the # evaluation does not have any specific attribute name, we
# "name" attribute given as argument of the submodule and use it # provide a default one for the documentation.
# as the default of option declarations. #
# # This is mandatory as some option declaration might use the
# Using lookalike unicode single angle quotation marks because # "name" attribute given as argument of the submodule and use it
# of the docbook transformation the options receive. In all uses # as the default of option declarations.
# &gt; and &lt; wouldn't be encoded correctly so the encoded values #
# would be used, and use of `<` and `>` would break the XML document. # Using lookalike unicode single angle quotation marks because
# It shouldn't cause an issue since this is cosmetic for the manual. # of the docbook transformation the options receive. In all uses
args.name = "name"; # &gt; and &lt; wouldn't be encoded correctly so the encoded values
}).options; # 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'; getSubModules = opts';
substSubModules = m: submoduleWithSpecialArgs m specialArgs; substSubModules = m: submoduleWithSpecialArgs m specialArgs;
functor = (defaultFunctor name) // { functor =
# Merging of submodules is done as part of mergeOptionDecls, as we have to annotate (defaultFunctor name)
# each submodule with its location. // {
payload = [ ]; # Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
binOp = lhs: rhs: [ ]; # each submodule with its location.
}; payload = [];
binOp = lhs: rhs: [];
};
}; };
in in {
{ imports = [./base.nix];
imports = [ ./base.nix ];
options = { options = {
submodules.specialArgs = mkOption { submodules.specialArgs = mkOption {
description = "Special args to pass to submodules. These arguments can be used for imports"; description = "Special args to pass to submodules. These arguments can be used for imports";
type = types.attrs; type = types.attrs;
default = { }; default = {};
}; };
submodules.defaults = mkOption { submodules.defaults = mkOption {
description = "List of defaults to apply to submodule instances"; description = "List of defaults to apply to submodule instances";
type = types.listOf (types.submodule ({ config, ... }: { type = types.listOf (types.submodule ({config, ...}: {
options = { options = {
name = mkOption { name = mkOption {
description = "Name of the submodule to apply defaults for"; description = "Name of the submodule to apply defaults for";
@ -154,23 +197,23 @@ in
tags = mkOption { tags = mkOption {
description = "List of tags to apply defaults for"; description = "List of tags to apply defaults for";
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [];
}; };
features = mkOption { features = mkOption {
description = "List of features that submodule has to have to apply defaults"; description = "List of features that submodule has to have to apply defaults";
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [];
}; };
default = mkOption { default = mkOption {
description = "Default to apply to submodule instance"; description = "Default to apply to submodule instance";
type = types.unspecified; type = types.unspecified;
default = { }; default = {};
}; };
}; };
})); }));
default = [ ]; default = [];
}; };
submodules.propagate.enable = mkOption { submodules.propagate.enable = mkOption {
@ -183,179 +226,191 @@ in
description = "List of submodule imports"; description = "List of submodule imports";
type = types.listOf ( type = types.listOf (
types.coercedTo types.coercedTo
types.path types.path
(module: { inherit module; }) (module: {inherit module;})
(types.submodule ({ name, config, ... }: (
let types.submodule ({
evaledSubmodule' = evalModules { name,
inherit specialArgs; config,
modules = config.modules ++ [ ./base.nix ]; ...
check = false; }: 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;
}; };
evaledSubmodule = modules = mkOption {
if (!(elem "submodule" evaledSubmodule'.config._m.features)) description = "List of modules defining submodule";
then throw "no submodule defined" type = types.listOf types.unspecified;
else evaledSubmodule'; default = [config.module];
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 = { features = mkOption {
definition = { description = "List of features exposed by submodule";
inherit (evaledSubmodule.config.submodule) name description version tags exports; type = types.listOf types.str;
};
features = evaledSubmodule.config._m.features;
}; };
})
) 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 = [ ]; default = [];
}; };
submodules.instances = mkOption { submodules.instances = mkOption {
description = "Attribute set of submodule instances"; description = "Attribute set of submodule instances";
default = { }; default = {};
type = types.attrsOf (types.submodule ({ name, config, options, ... }: type = types.attrsOf (types.submodule ({
let name,
# submodule associated with config,
submodule = findSubmodule { options,
name = config.submodule; ...
version = config.version; }: 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;
}; };
# definition of a submodule submodule = mkOption {
submoduleDefinition = submodule.definition; description = "Name of the submodule to use";
type = types.str;
# submodule defaults default = name;
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 { version = mkOption {
description = "Name of the submodule to use"; description = ''
type = types.str; Version of submodule to use, if version starts with "~" it is
default = name; threated as regex pattern for example "~1.0.*"
}; '';
type = types.nullOr types.str;
version = mkOption { default = null;
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)";
};
}; };
}));
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 = { }; default = {};
}; };
config = mkMerge ([ config = mkMerge ([
{ {
# register exported functions as args # register exported functions as args
_module.args = mkMerge (map _module.args = mkMerge (map
(submodule: { (submodule: {
${submodule.exportAs} = submodule.definition.exports; ${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) (filter (submodule: submodule.exportAs != null) cfg.imports));
];
}
(mkIf cfg.propagate.enable { _m.features = ["submodules"];
# if propagate is enabled and submodule has submodules included propagage defaults and imports
submodules.defaults = [{ submodules.specialArgs.kubenix = kubenix;
features = [ "submodules" ];
default = { # passthru kubenix.project to submodules
submodules = { submodules.defaults = mkMerge [
defaults = cfg.defaults; [
imports = cfg.imports; {
}; default = {
}; kubenix.project = parentConfig.kubenix.project;
}]; };
}) }
] ++ passthruConfig); ]
(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,11 +1,15 @@
{ config, pkgs, lib, kubenix, ... }: {
config,
with lib; pkgs,
let lib,
kubenix,
...
}:
with lib; let
cfg = config.testing; cfg = config.testing;
testModule = { testModule = {
imports = [ ./evalTest.nix ]; imports = [./evalTest.nix];
# passthru testing configuration # passthru testing configuration
config._module.args = { config._module.args = {
@ -16,9 +20,7 @@ let
isTestEnabled = test: isTestEnabled = test:
(cfg.enabledTests == null || elem test.name cfg.enabledTests) && test.enable; (cfg.enabledTests == null || elem test.name cfg.enabledTests) && test.enable;
in {
in
{
imports = [ imports = [
./docker.nix ./docker.nix
./driver/kubetest.nix ./driver/kubetest.nix
@ -41,28 +43,28 @@ in
common = mkOption { common = mkOption {
description = "List of common options to apply to tests"; description = "List of common options to apply to tests";
type = types.listOf (types.submodule ({ config, ... }: { type = types.listOf (types.submodule ({config, ...}: {
options = { options = {
features = mkOption { features = mkOption {
description = "List of features that test has to have to apply options"; description = "List of features that test has to have to apply options";
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [];
}; };
options = mkOption { options = mkOption {
description = "Options to apply to test"; description = "Options to apply to test";
type = types.unspecified; type = types.unspecified;
default = { }; default = {};
apply = default: { _file = "testing.common"; } // default; apply = default: {_file = "testing.common";} // default;
}; };
}; };
})); }));
default = [ ]; default = [];
}; };
tests = mkOption { tests = mkOption {
description = "List of test cases"; description = "List of test cases";
default = [ ]; default = [];
type = types.listOf (types.coercedTo types.path type = types.listOf (types.coercedTo types.path
(module: { (module: {
inherit module; inherit module;
@ -86,7 +88,7 @@ in
args = mkOption { args = mkOption {
description = "Attribute set of extra args passed to tests"; description = "Attribute set of extra args passed to tests";
type = types.attrs; type = types.attrs;
default = { }; default = {};
}; };
success = mkOption { success = mkOption {

View file

@ -1,16 +1,17 @@
{ config, lib, pkgs, ... }: {
config,
lib,
pkgs,
...
}:
with lib; with lib;
with import ../../lib/docker { inherit lib pkgs; }; with import ../../lib/docker {inherit lib pkgs;}; let
let
testing = config.testing; testing = config.testing;
allImages = unique (flatten (map (t: t.evaled.config.docker.export or [ ]) testing.tests)); allImages = unique (flatten (map (t: t.evaled.config.docker.export or []) testing.tests));
cfg = config.testing.docker; cfg = config.testing.docker;
in {
in
{
options.testing.docker = { options.testing.docker = {
registryUrl = mkOption { registryUrl = mkOption {
description = "Docker registry url"; description = "Docker registry url";
@ -37,11 +38,13 @@ in
}; };
}; };
config.testing.common = [{ config.testing.common = [
features = [ "docker" ]; {
options = { features = ["docker"];
_file = "testing.docker.registryUrl"; options = {
docker.registry.url = cfg.registryUrl; _file = "testing.docker.registryUrl";
}; docker.registry.url = cfg.registryUrl;
}]; };
}
];
} }

View file

@ -1,15 +1,14 @@
{ pkgs ? import <nixpkgs> { } }: {pkgs ? import <nixpkgs> {}}:
with pkgs; with pkgs;
with pkgs.python38Packages; with pkgs.python38Packages;
with pkgs.python38; with pkgs.python38;
pkgs.python38Packages.buildPythonPackage rec { pkgs.python38Packages.buildPythonPackage rec {
pname = "kubetest"; pname = "kubetest";
version = "0.9.5"; version = "0.9.5";
src = fetchPypi { src = fetchPypi {
inherit pname version; inherit pname version;
sha256 = "sha256-TqDHMciAEXv4vMWLJY1YdtXsP4ho+INgdFB3xQQNoZU="; sha256 = "sha256-TqDHMciAEXv4vMWLJY1YdtXsP4ho+INgdFB3xQQNoZU=";
}; };
propagatedBuildInputs = [ pytest kubernetes ]; propagatedBuildInputs = [pytest kubernetes];
doCheck = false; doCheck = false;
} }

View file

@ -1,7 +1,11 @@
{ lib, config, testing, kubenix, ... }: {
lib,
with lib; config,
let testing,
kubenix,
...
}:
with lib; let
modules = [ modules = [
# testing module # testing module
config.module config.module
@ -13,10 +17,12 @@ let
{ {
config = { config = {
kubenix.project = mkDefault config.name; kubenix.project = mkDefault config.name;
_module.args = { _module.args =
inherit kubenix; {
test = evaled.config; inherit kubenix;
} // testing.args; test = evaled.config;
}
// testing.args;
}; };
} }
]; ];
@ -36,28 +42,29 @@ let
# common options that can be applied on this test # common options that can be applied on this test
commonOpts = commonOpts =
filter filter
(d: (
(intersectLists d.features testFeatures) == d.features || d:
(length d.features) == 0 (intersectLists d.features testFeatures)
) == d.features
testing.common; || (length d.features) == 0
)
testing.common;
# add common options modules to all modules # add common options modules to all modules
modulesWithCommonOptions = modules ++ (map (d: d.options) commonOpts); modulesWithCommonOptions = modules ++ (map (d: d.options) commonOpts);
# evaled test # evaled test
evaled = evaled = let
let evaled' = kubenix.evalModules {
evaled' = kubenix.evalModules { modules = modulesWithCommonOptions;
modules = modulesWithCommonOptions; };
}; in
in if testing.doThrowError
if testing.doThrowError then evaled' then evaled'
else if (builtins.tryEval evaled'.config.test.assertions).success else if (builtins.tryEval evaled'.config.test.assertions).success
then evaled' else null; then evaled'
else null;
in in {
{
options = { options = {
module = mkOption { module = mkOption {
description = "Module defining kubenix test"; description = "Module defining kubenix test";
@ -100,7 +107,7 @@ in
description = "Test result"; description = "Test result";
type = types.unspecified; type = types.unspecified;
internal = true; internal = true;
default = [ ]; default = [];
}; };
script = mkOption { script = mkOption {
@ -108,7 +115,6 @@ in
type = types.nullOr (types.either types.lines types.path); type = types.nullOr (types.either types.lines types.path);
internal = true; internal = true;
}; };
}; };
config = mkMerge [ config = mkMerge [

View file

@ -1,7 +1,10 @@
{ lib, config, pkgs, ... }: {
lib,
with lib; config,
let pkgs,
...
}:
with lib; let
testing = config.testing; testing = config.testing;
script = pkgs.writeScript "run-local-k8s-tests-${testing.name}.sh" '' script = pkgs.writeScript "run-local-k8s-tests-${testing.name}.sh" ''
@ -31,8 +34,7 @@ let
echo "--> running tests" echo "--> running tests"
${testing.testScript} --kube-config=$KUBECONFIG ${testing.testScript} --kube-config=$KUBECONFIG
''; '';
in in {
{
options.testing.runtime.local = { options.testing.runtime.local = {
script = mkOption { script = mkOption {
type = types.package; type = types.package;

View file

@ -1,29 +1,32 @@
# nixos-k8s implements nixos kubernetes testing runtime # nixos-k8s implements nixos kubernetes testing runtime
{
{ config config,
, pkgs pkgs,
, lib lib,
, ... ...
}: }:
with lib; let
with lib;
let
testing = config.testing; testing = config.testing;
# kubeconfig = "/etc/${config.services.kubernetes.pki.etcClusterAdminKubeconfig}"; # kubeconfig = "/etc/${config.services.kubernetes.pki.etcClusterAdminKubeconfig}";
kubeconfig = "/etc/kubernetes/cluster-admin.kubeconfig"; kubeconfig = "/etc/kubernetes/cluster-admin.kubeconfig";
kubecerts = "/var/lib/kubernetes/secrets"; kubecerts = "/var/lib/kubernetes/secrets";
# how we differ from the standard configuration of mkKubernetesBaseTest # how we differ from the standard configuration of mkKubernetesBaseTest
extraConfiguration = { config, pkgs, lib, nodes, ... }: { extraConfiguration = {
config,
pkgs,
lib,
nodes,
...
}: {
virtualisation = { virtualisation = {
memorySize = 2048; memorySize = 2048;
}; };
networking = { networking = {
nameservers = [ "10.0.0.254" ]; nameservers = ["10.0.0.254"];
firewall = { firewall = {
trustedInterfaces = [ "docker0" "cni0" ]; trustedInterfaces = ["docker0" "cni0"];
}; };
}; };
@ -32,22 +35,26 @@ let
kubelet = { kubelet = {
seedDockerImages = testing.docker.images; seedDockerImages = testing.docker.images;
networkPlugin = "cni"; networkPlugin = "cni";
cni.config = [{ cni.config = [
name = "mynet"; {
type = "bridge"; name = "mynet";
bridge = "cni0"; type = "bridge";
addIf = true; bridge = "cni0";
ipMasq = true; addIf = true;
isGateway = true; ipMasq = true;
ipam = { isGateway = true;
type = "host-local"; ipam = {
subnet = "10.1.0.0/16"; type = "host-local";
gateway = "10.1.0.1"; subnet = "10.1.0.0/16";
routes = [{ gateway = "10.1.0.1";
dst = "0.0.0.0/0"; routes = [
}]; {
}; dst = "0.0.0.0/0";
}]; }
];
};
}
];
}; };
}; };
@ -57,32 +64,30 @@ let
services.copy-certs = { services.copy-certs = {
description = "Share k8s certificates with host"; description = "Share k8s certificates with host";
script = "cp -rf ${kubecerts} /tmp/xchg/; cp -f ${kubeconfig} /tmp/xchg/;"; script = "cp -rf ${kubecerts} /tmp/xchg/; cp -f ${kubeconfig} /tmp/xchg/;";
after = [ "kubernetes.target" ]; after = ["kubernetes.target"];
wantedBy = [ "multi-user.target" ]; wantedBy = ["multi-user.target"];
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
RemainAfterExit = true; RemainAfterExit = true;
}; };
}; };
}; };
}; };
script = '' script = ''
machine1.succeed("${testing.testScript} --kube-config=${kubeconfig}") machine1.succeed("${testing.testScript} --kube-config=${kubeconfig}")
''; '';
test = test = with import "${pkgs.path}/nixos/tests/kubernetes/base.nix" {
with import "${pkgs.path}/nixos/tests/kubernetes/base.nix" { inherit pkgs; inherit (pkgs) system; }; inherit pkgs;
inherit (pkgs) system;
};
mkKubernetesSingleNodeTest { mkKubernetesSingleNodeTest {
inherit extraConfiguration; inherit extraConfiguration;
inherit (config.testing) name; inherit (config.testing) name;
test = script; test = script;
}; };
in {
in
{
options.testing.runtime.nixos-k8s = { options.testing.runtime.nixos-k8s = {
driver = mkOption { driver = mkOption {
description = "Test driver"; description = "Test driver";

View file

@ -1,11 +1,12 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.test;
in
{ {
lib,
config,
pkgs,
...
}:
with lib; let
cfg = config.test;
in {
options.test = { options.test = {
name = mkOption { name = mkOption {
description = "Test name"; description = "Test name";
@ -38,8 +39,13 @@ in
}; };
}; };
}); });
default = [ ]; default = [];
example = [{ assertion = false; message = "you can't enable this for some reason"; }]; example = [
{
assertion = false;
message = "you can't enable this for some reason";
}
];
description = '' description = ''
This option allows modules to express conditions that must This option allows modules to express conditions that must
hold for the evaluation of the system configuration to hold for the evaluation of the system configuration to
@ -52,6 +58,5 @@ in
type = types.nullOr (types.either types.lines types.path); type = types.nullOr (types.either types.lines types.path);
default = null; default = null;
}; };
}; };
} }

View file

@ -1,5 +1,8 @@
{ stdenv, kubernetes, installShellFiles }: {
stdenv,
kubernetes,
installShellFiles,
}:
stdenv.mkDerivation { stdenv.mkDerivation {
name = "kubectl-${kubernetes.version}"; name = "kubectl-${kubernetes.version}";
@ -7,9 +10,9 @@ stdenv.mkDerivation {
# split out (see homepage) # split out (see homepage)
dontUnpack = true; dontUnpack = true;
nativeBuildInputs = [ installShellFiles ]; nativeBuildInputs = [installShellFiles];
outputs = [ "out" "man" ]; outputs = ["out" "man"];
installPhase = '' installPhase = ''
install -D ${kubernetes}/bin/kubectl -t $out/bin install -D ${kubernetes}/bin/kubectl -t $out/bin
@ -22,8 +25,10 @@ stdenv.mkDerivation {
done done
''; '';
meta = kubernetes.meta // { meta =
description = "Kubernetes CLI"; kubernetes.meta
homepage = "https://github.com/kubernetes/kubectl"; // {
}; description = "Kubernetes CLI";
homepage = "https://github.com/kubernetes/kubectl";
};
} }

View file

@ -1,23 +1,22 @@
{ stdenv {
, lib stdenv,
, fetchFromGitHub lib,
, removeReferencesTo fetchFromGitHub,
, which removeReferencesTo,
, go which,
, makeWrapper go,
, rsync makeWrapper,
, installShellFiles rsync,
installShellFiles,
, components ? [ components ? [
"cmd/kubelet" "cmd/kubelet"
"cmd/kube-apiserver" "cmd/kube-apiserver"
"cmd/kube-controller-manager" "cmd/kube-controller-manager"
"cmd/kube-proxy" "cmd/kube-proxy"
"cmd/kube-scheduler" "cmd/kube-scheduler"
"test/e2e/e2e.test" "test/e2e/e2e.test"
] ],
}: }:
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
pname = "kubernetes"; pname = "kubernetes";
version = "1.20.4"; version = "1.20.4";
@ -29,11 +28,11 @@ stdenv.mkDerivation rec {
hash = "sha256-r9Clwr+87Ns4VXUW9F6cgks+LknY39ngbQgZ5UMZ0Vo="; hash = "sha256-r9Clwr+87Ns4VXUW9F6cgks+LknY39ngbQgZ5UMZ0Vo=";
}; };
nativeBuildInputs = [ removeReferencesTo makeWrapper which go rsync installShellFiles ]; nativeBuildInputs = [removeReferencesTo makeWrapper which go rsync installShellFiles];
outputs = [ "out" "man" "pause" ]; outputs = ["out" "man" "pause"];
patches = [ ./fixup-addonmanager-lib-path.patch ]; patches = [./fixup-addonmanager-lib-path.patch];
postPatch = '' postPatch = ''
# go env breaks the sandbox # go env breaks the sandbox
@ -49,9 +48,10 @@ stdenv.mkDerivation rec {
''; '';
WHAT = lib.concatStringsSep " " ([ WHAT = lib.concatStringsSep " " ([
"cmd/kubeadm" "cmd/kubeadm"
"cmd/kubectl" "cmd/kubectl"
] ++ components); ]
++ components);
postBuild = '' postBuild = ''
./hack/update-generated-docs.sh ./hack/update-generated-docs.sh
@ -84,7 +84,7 @@ stdenv.mkDerivation rec {
description = "Production-Grade Container Scheduling and Management"; description = "Production-Grade Container Scheduling and Management";
license = licenses.asl20; license = licenses.asl20;
homepage = "https://kubernetes.io"; homepage = "https://kubernetes.io";
maintainers = with maintainers; [ johanot offline saschagrunert ]; maintainers = with maintainers; [johanot offline saschagrunert];
platforms = platforms.unix; platforms = platforms.unix;
}; };
} }

View file

@ -1,9 +1,9 @@
{ system ? builtins.currentSystem }: {system ? builtins.currentSystem}: let
let
in in
( (
(import ./compat.nix).flake-compat { (import ./compat.nix).flake-compat {
src = ./.; src = ./.;
inherit system; inherit system;
} }
).shellNix )
.shellNix

View file

@ -1,20 +1,20 @@
{ system ? builtins.currentSystem {
, evalModules ? (import ../. { }).evalModules.${system} system ? builtins.currentSystem,
}: evalModules ? (import ../. {}).evalModules.${system},
}: {
{ k8sVersion ? "1.21" k8sVersion ? "1.21",
, registry ? throw "Registry url not defined" registry ? throw "Registry url not defined",
, doThrowError ? true # whether any testing error should throw an error doThrowError ? true, # whether any testing error should throw an error
, enabledTests ? null enabledTests ? null,
}: }: let
config =
let (evalModules {
config = (evalModules { module = {
kubenix,
module = pkgs,
{ kubenix, pkgs, ... }: { ...
}: {
imports = [ kubenix.modules.testing ]; imports = [kubenix.modules.testing];
testing = { testing = {
inherit doThrowError enabledTests; inherit doThrowError enabledTests;
@ -36,21 +36,20 @@ let
./submodules/passthru.nix ./submodules/passthru.nix
]; ];
args = { images = pkgs.callPackage ./images.nix { }; }; args = {images = pkgs.callPackage ./images.nix {};};
docker.registryUrl = registry; docker.registryUrl = registry;
common = [ common = [
{ {
features = [ "k8s" ]; features = ["k8s"];
options = { options = {
kubernetes.version = k8sVersion; kubernetes.version = k8sVersion;
}; };
} }
]; ];
}; };
}; };
})
}).config; .config;
in in
config.testing // { recurseForDerivations = true; } config.testing // {recurseForDerivations = true;}

View file

@ -1,43 +1,44 @@
{ pkgs, dockerTools, lib, ... }:
with lib;
{ {
pkgs,
dockerTools,
lib,
...
}:
with lib; {
curl = dockerTools.buildLayeredImage { curl = dockerTools.buildLayeredImage {
name = "curl"; name = "curl";
tag = "latest"; tag = "latest";
config.Cmd = [ "${pkgs.bash}" "-c" "sleep infinity" ]; config.Cmd = ["${pkgs.bash}" "-c" "sleep infinity"];
contents = [ pkgs.bash pkgs.curl pkgs.cacert ]; contents = [pkgs.bash pkgs.curl pkgs.cacert];
}; };
nginx = nginx = let
let nginxPort = "80";
nginxPort = "80"; nginxConf = pkgs.writeText "nginx.conf" ''
nginxConf = pkgs.writeText "nginx.conf" '' user nginx nginx;
user nginx nginx; daemon off;
daemon off; error_log /dev/stdout info;
error_log /dev/stdout info; pid /dev/null;
pid /dev/null; events {}
events {} http {
http { access_log /dev/stdout;
access_log /dev/stdout; server {
server { listen ${nginxPort};
listen ${nginxPort}; index index.html;
index index.html; location / {
location / { root ${nginxWebRoot};
root ${nginxWebRoot};
}
} }
} }
''; }
nginxWebRoot = pkgs.writeTextDir "index.html" '' '';
<html><body><h1>Hello from NGINX</h1></body></html> nginxWebRoot = pkgs.writeTextDir "index.html" ''
''; <html><body><h1>Hello from NGINX</h1></body></html>
in '';
in
dockerTools.buildLayeredImage { dockerTools.buildLayeredImage {
name = "xtruder/nginx"; name = "xtruder/nginx";
tag = "latest"; tag = "latest";
contents = [ pkgs.nginx ]; contents = [pkgs.nginx];
extraCommands = '' extraCommands = ''
mkdir -p etc mkdir -p etc
chmod u+w etc chmod u+w etc
@ -49,9 +50,9 @@ with lib;
echo "nginx:x:1000:nginx" > etc/group echo "nginx:x:1000:nginx" > etc/group
''; '';
config = { config = {
Cmd = [ "nginx" "-c" nginxConf ]; Cmd = ["nginx" "-c" nginxConf];
ExposedPorts = { ExposedPorts = {
"${nginxPort}/tcp" = { }; "${nginxPort}/tcp" = {};
}; };
}; };
}; };

View file

@ -1,7 +1,9 @@
{ config, kubenix, ... }:
{ {
imports = with kubenix.modules; [ test k8s istio ]; config,
kubenix,
...
}: {
imports = with kubenix.modules; [test k8s istio];
test = { test = {
name = "istio-bookinfo"; name = "istio-bookinfo";
@ -12,61 +14,72 @@
Gateway."bookinfo-gateway" = { Gateway."bookinfo-gateway" = {
spec = { spec = {
selector.istio = "ingressgateway"; selector.istio = "ingressgateway";
servers = [{ servers = [
port = { {
number = 80; port = {
name = "http"; number = 80;
protocol = "HTTP"; name = "http";
}; protocol = "HTTP";
hosts = [ "*" ]; };
}]; hosts = ["*"];
}
];
}; };
}; };
VirtualService.bookinfo = { VirtualService.bookinfo = {
spec = { spec = {
hosts = [ "*" ]; hosts = ["*"];
gateways = [ "bookinfo-gateway" ]; gateways = ["bookinfo-gateway"];
http = [{ http = [
match = [{ {
uri.exact = "/productpage"; match = [
{
uri.exact = "/productpage";
}
{
uri.exact = "/login";
}
{
uri.exact = "/logout";
}
{
uri.prefix = "/api/v1/products";
}
];
route = [
{
destination = {
host = "productpage";
port.number = 9080;
};
}
];
} }
{ ];
uri.exact = "/login";
}
{
uri.exact = "/logout";
}
{
uri.prefix = "/api/v1/products";
}];
route = [{
destination = {
host = "productpage";
port.number = 9080;
};
}];
}];
}; };
}; };
DestinationRule.productpage = { DestinationRule.productpage = {
spec = { spec = {
host = "productpage"; host = "productpage";
subsets = [{ subsets = [
name = "v1"; {
labels.version = "v1"; name = "v1";
}]; labels.version = "v1";
}
];
}; };
}; };
DestinationRule.reviews = { DestinationRule.reviews = {
spec = { spec = {
host = "reviews"; host = "reviews";
subsets = [{ subsets = [
name = "v1"; {
labels.version = "v1"; name = "v1";
} labels.version = "v1";
}
{ {
name = "v2"; name = "v2";
labels.version = "v2"; labels.version = "v2";
@ -74,17 +87,19 @@
{ {
name = "v3"; name = "v3";
labels.version = "v3"; labels.version = "v3";
}]; }
];
}; };
}; };
DestinationRule.ratings = { DestinationRule.ratings = {
spec = { spec = {
host = "ratings"; host = "ratings";
subsets = [{ subsets = [
name = "v1"; {
labels.version = "v1"; name = "v1";
} labels.version = "v1";
}
{ {
name = "v2"; name = "v2";
labels.version = "v2"; labels.version = "v2";
@ -96,21 +111,24 @@
{ {
name = "v2-mysql-vm"; name = "v2-mysql-vm";
labels.version = "v2-mysql-vm"; labels.version = "v2-mysql-vm";
}]; }
];
}; };
}; };
DestinationRule.details = { DestinationRule.details = {
spec = { spec = {
host = "details"; host = "details";
subsets = [{ subsets = [
name = "v1"; {
labels.version = "v1"; name = "v1";
} labels.version = "v1";
}
{ {
name = "v2"; name = "v2";
labels.version = "v2"; labels.version = "v2";
}]; }
];
}; };
}; };
}; };

View file

@ -1,20 +1,25 @@
{ config, lib, kubenix, pkgs, ... }:
with lib;
let
latestCrontab = config.kubernetes.api.resources.cronTabs.latest;
in
{ {
imports = with kubenix.modules; [ test k8s ]; config,
lib,
kubenix,
pkgs,
...
}:
with lib; let
latestCrontab = config.kubernetes.api.resources.cronTabs.latest;
in {
imports = with kubenix.modules; [test k8s];
test = { test = {
name = "k8s-crd"; name = "k8s-crd";
description = "Simple test tesing CRD"; description = "Simple test tesing CRD";
enable = builtins.compareVersions config.kubernetes.version "1.8" >= 0; enable = builtins.compareVersions config.kubernetes.version "1.8" >= 0;
assertions = [{ assertions = [
message = "Custom resource should have correct version set"; {
assertion = latestCrontab.apiVersion == "stable.example.com/v2"; message = "Custom resource should have correct version set";
}]; assertion = latestCrontab.apiVersion == "stable.example.com/v2";
}
];
script = '' script = ''
@pytest.mark.applymanifest('${config.kubernetes.resultYAML}') @pytest.mark.applymanifest('${config.kubernetes.resultYAML}')
def test_testing_module(kube): def test_testing_module(kube):
@ -50,7 +55,6 @@ in
type = types.str; type = types.str;
}; };
}; };
} }
{ {
group = "stable.example.com"; group = "stable.example.com";

View file

@ -1,20 +1,23 @@
{ config, lib, kubenix, ... }: {
config,
with lib; lib,
let kubenix,
...
}:
with lib; let
pod1 = config.kubernetes.api.resources.pods.pod1; pod1 = config.kubernetes.api.resources.pods.pod1;
pod2 = config.kubernetes.api.resources.pods.pod2; pod2 = config.kubernetes.api.resources.pods.pod2;
in in {
{ imports = with kubenix.modules; [test k8s];
imports = with kubenix.modules; [ test k8s ];
test = { test = {
name = "k8s-defaults"; name = "k8s-defaults";
description = "Simple k8s testing wheter name, apiVersion and kind are preset"; description = "Simple k8s testing wheter name, apiVersion and kind are preset";
assertions = [{ assertions = [
message = "Should have label set with resource"; {
assertion = pod1.metadata.labels.resource-label == "value"; message = "Should have label set with resource";
} assertion = pod1.metadata.labels.resource-label == "value";
}
{ {
message = "Should have default label set with group, version, kind"; message = "Should have default label set with group, version, kind";
assertion = pod1.metadata.labels.gvk-label == "value"; assertion = pod1.metadata.labels.gvk-label == "value";
@ -22,19 +25,21 @@ in
{ {
message = "Should have conditional annotation set"; message = "Should have conditional annotation set";
assertion = pod2.metadata.annotations.conditional-annotation == "value"; assertion = pod2.metadata.annotations.conditional-annotation == "value";
}]; }
];
}; };
kubernetes.resources.pods.pod1 = { }; kubernetes.resources.pods.pod1 = {};
kubernetes.resources.pods.pod2 = { kubernetes.resources.pods.pod2 = {
metadata.labels.custom-label = "value"; metadata.labels.custom-label = "value";
}; };
kubernetes.api.defaults = [{ kubernetes.api.defaults = [
resource = "pods"; {
default.metadata.labels.resource-label = "value"; resource = "pods";
} default.metadata.labels.resource-label = "value";
}
{ {
group = "core"; group = "core";
kind = "Pod"; kind = "Pod";
@ -43,10 +48,11 @@ in
} }
{ {
resource = "pods"; resource = "pods";
default = { config, ... }: { default = {config, ...}: {
config.metadata.annotations = mkIf (config.metadata.labels ? "custom-label") { config.metadata.annotations = mkIf (config.metadata.labels ? "custom-label") {
conditional-annotation = "value"; conditional-annotation = "value";
}; };
}; };
}]; }
];
} }

View file

@ -1,7 +1,13 @@
{ config, lib, pkgs, kubenix, images, test, ... }: {
config,
with lib; lib,
let pkgs,
kubenix,
images,
test,
...
}:
with lib; let
cfg = config.kubernetes.api.resources.deployments.nginx; cfg = config.kubernetes.api.resources.deployments.nginx;
image = images.nginx; image = images.nginx;
@ -12,30 +18,31 @@ let
namespace = config.kubernetes.namespace; namespace = config.kubernetes.namespace;
name = "curl"; name = "curl";
}; };
spec.containers = [{ spec.containers = [
name = "curl"; {
image = config.docker.images.curl.path; name = "curl";
args = [ "curl" "--retry" "20" "--retry-connrefused" "http://nginx" ]; image = config.docker.images.curl.path;
}]; args = ["curl" "--retry" "20" "--retry-connrefused" "http://nginx"];
}
];
spec.restartPolicy = "Never"; spec.restartPolicy = "Never";
}); });
in {
in imports = [kubenix.modules.test kubenix.modules.k8s kubenix.modules.docker];
{
imports = [ kubenix.modules.test kubenix.modules.k8s kubenix.modules.docker ];
test = { test = {
name = "k8s-deployment"; name = "k8s-deployment";
description = "Simple k8s testing a simple deployment"; description = "Simple k8s testing a simple deployment";
assertions = [{ assertions = [
message = "should have correct apiVersion and kind set"; {
assertion = message = "should have correct apiVersion and kind set";
if ((builtins.compareVersions config.kubernetes.version "1.7") <= 0) assertion =
then cfg.apiVersion == "apps/v1beta1" if ((builtins.compareVersions config.kubernetes.version "1.7") <= 0)
else if ((builtins.compareVersions config.kubernetes.version "1.8") <= 0) then cfg.apiVersion == "apps/v1beta1"
then cfg.apiVersion == "apps/v1beta2" else if ((builtins.compareVersions config.kubernetes.version "1.8") <= 0)
else cfg.apiVersion == "apps/v1"; then cfg.apiVersion == "apps/v1beta2"
} else cfg.apiVersion == "apps/v1";
}
{ {
message = "should have corrent kind set"; message = "should have corrent kind set";
assertion = cfg.kind == "Deployment"; assertion = cfg.kind == "Deployment";
@ -43,7 +50,8 @@ in
{ {
message = "should have replicas set"; message = "should have replicas set";
assertion = cfg.spec.replicas == 3; assertion = cfg.spec.replicas == 3;
}]; }
];
script = '' script = ''
import time import time
@ -97,10 +105,12 @@ in
kubernetes.resources.services.nginx = { kubernetes.resources.services.nginx = {
spec = { spec = {
ports = [{ ports = [
name = "http"; {
port = 80; name = "http";
}]; port = 80;
}
];
selector.app = "nginx"; selector.app = "nginx";
}; };
}; };

View file

@ -1,25 +1,29 @@
{ config, lib, kubenix, ... }: {
config,
with lib; lib,
let kubenix,
...
}:
with lib; let
pod = config.kubernetes.api.resources.core.v1.Pod.test; pod = config.kubernetes.api.resources.core.v1.Pod.test;
deployment = config.kubernetes.api.resources.apps.v1.Deployment.nginx-deployment; deployment = config.kubernetes.api.resources.apps.v1.Deployment.nginx-deployment;
in in {
{ imports = with kubenix.modules; [test k8s];
imports = with kubenix.modules; [ test k8s ];
test = { test = {
name = "k8s-imports"; name = "k8s-imports";
description = "Simple k8s testing imports"; description = "Simple k8s testing imports";
enable = builtins.compareVersions config.kubernetes.version "1.10" >= 0; enable = builtins.compareVersions config.kubernetes.version "1.10" >= 0;
assertions = [{ assertions = [
message = "Pod should have name set"; {
assertion = pod.metadata.name == "test"; message = "Pod should have name set";
} assertion = pod.metadata.name == "test";
}
{ {
message = "Deployment should have name set"; message = "Deployment should have name set";
assertion = deployment.metadata.name == "nginx-deployment"; assertion = deployment.metadata.name == "nginx-deployment";
}]; }
];
}; };
kubernetes.imports = [ kubernetes.imports = [

View file

@ -1,22 +1,28 @@
{ config, lib, kubenix, pkgs, ... }:
with lib;
let
cfg = config.kubernetes.api.resources.customResourceDefinitions.crontabs;
in
{ {
imports = with kubenix.modules; [ test k8s ]; config,
lib,
kubenix,
pkgs,
...
}:
with lib; let
cfg = config.kubernetes.api.resources.customResourceDefinitions.crontabs;
in {
imports = with kubenix.modules; [test k8s];
test = { test = {
name = "k8s-order"; name = "k8s-order";
description = "test tesing k8s resource order"; description = "test tesing k8s resource order";
assertions = [{ assertions = [
message = "should have correct order of resources"; {
assertion = message = "should have correct order of resources";
(elemAt config.kubernetes.objects 0).kind == "CustomResourceDefinition" && assertion =
(elemAt config.kubernetes.objects 1).kind == "Namespace" && (elemAt config.kubernetes.objects 0).kind
(elemAt config.kubernetes.objects 2).kind == "CronTab"; == "CustomResourceDefinition"
}]; && (elemAt config.kubernetes.objects 1).kind == "Namespace"
&& (elemAt config.kubernetes.objects 2).kind == "CronTab";
}
];
}; };
kubernetes.resources.customResourceDefinitions.crontabs = { kubernetes.resources.customResourceDefinitions.crontabs = {
@ -24,38 +30,42 @@ in
metadata.name = "crontabs.stable.example.com"; metadata.name = "crontabs.stable.example.com";
spec = { spec = {
group = "stable.example.com"; group = "stable.example.com";
versions = [{ versions = [
name = "v1"; {
served = true; name = "v1";
schema = true; served = true;
}]; schema = true;
}
];
scope = "Namespaced"; scope = "Namespaced";
names = { names = {
plural = "crontabs"; plural = "crontabs";
singular = "crontab"; singular = "crontab";
kind = "CronTab"; kind = "CronTab";
shortNames = [ "ct" ]; shortNames = ["ct"];
}; };
}; };
}; };
kubernetes.customTypes = [{ kubernetes.customTypes = [
name = "crontabs"; {
description = "CronTabs resources"; name = "crontabs";
description = "CronTabs resources";
attrName = "cronTabs"; attrName = "cronTabs";
group = "stable.example.com"; group = "stable.example.com";
version = "v1"; version = "v1";
kind = "CronTab"; kind = "CronTab";
module = { module = {
options.schedule = mkOption { options.schedule = mkOption {
description = "Crontab schedule script"; description = "Crontab schedule script";
type = types.str; type = types.str;
};
}; };
}; }
}]; ];
kubernetes.resources.namespaces.test = { }; kubernetes.resources.namespaces.test = {};
kubernetes.resources."stable.example.com"."v1".CronTab.crontab.spec.schedule = "* * * * *"; kubernetes.resources."stable.example.com"."v1".CronTab.crontab.spec.schedule = "* * * * *";
} }

View file

@ -1,22 +1,26 @@
{ config, kubenix, ... }:
let
cfg = config.kubernetes.api.resources.pods.nginx;
in
{ {
imports = [ kubenix.modules.test kubenix.modules.k8s ]; config,
kubenix,
...
}: let
cfg = config.kubernetes.api.resources.pods.nginx;
in {
imports = [kubenix.modules.test kubenix.modules.k8s];
test = { test = {
name = "k8s-simple"; name = "k8s-simple";
description = "Simple k8s testing wheter name, apiVersion and kind are preset"; description = "Simple k8s testing wheter name, apiVersion and kind are preset";
assertions = [{ assertions = [
message = "should have apiVersion and kind set"; {
assertion = cfg.apiVersion == "v1" && cfg.kind == "Pod"; message = "should have apiVersion and kind set";
} assertion = cfg.apiVersion == "v1" && cfg.kind == "Pod";
}
{ {
message = "should have name set"; message = "should have name set";
assertion = cfg.metadata.name == "nginx"; assertion = cfg.metadata.name == "nginx";
}]; }
];
}; };
kubernetes.resources.pods.nginx = { }; kubernetes.resources.pods.nginx = {};
} }

View file

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

View file

@ -1,7 +1,11 @@
{ name, config, lib, kubenix, ... }: {
name,
with lib; config,
let lib,
kubenix,
...
}:
with lib; let
instance1 = config.submodules.instances.instance1; instance1 = config.submodules.instances.instance1;
instance2 = config.submodules.instances.instance2; instance2 = config.submodules.instances.instance2;
instance3 = config.submodules.instances.instance3; instance3 = config.submodules.instances.instance3;
@ -9,8 +13,8 @@ let
instance5 = config.submodules.instances.instance5; instance5 = config.submodules.instances.instance5;
versioned-submodule = config.submodules.instances.versioned-submodule; versioned-submodule = config.submodules.instances.versioned-submodule;
submodule = { name, ... }: { submodule = {name, ...}: {
imports = [ kubenix.modules.submodule ]; imports = [kubenix.modules.submodule];
options.submodule.args = { options.submodule.args = {
value = mkOption { value = mkOption {
@ -24,17 +28,17 @@ let
}; };
}; };
}; };
in in {
{ imports = with kubenix.modules; [test submodules];
imports = with kubenix.modules; [ test submodules ];
test = { test = {
name = "submodules-defaults"; name = "submodules-defaults";
description = "Simple submodule test"; description = "Simple submodule test";
assertions = [{ assertions = [
message = "should apply defaults by tag1"; {
assertion = instance1.config.submodule.args.value == "value1"; message = "should apply defaults by tag1";
} assertion = instance1.config.submodule.args.value == "value1";
}
{ {
message = "should apply defaults by tag2"; message = "should apply defaults by tag2";
assertion = instance2.config.submodule.args.value == "value2"; assertion = instance2.config.submodule.args.value == "value2";
@ -46,8 +50,9 @@ in
{ {
message = "should apply defaults to all"; message = "should apply defaults to all";
assertion = assertion =
instance1.config.submodule.args.defaultValue == "value" && instance1.config.submodule.args.defaultValue
instance2.config.submodule.args.defaultValue == "value"; == "value"
&& instance2.config.submodule.args.defaultValue == "value";
} }
{ {
message = "instance1 and instance3 should have value of default-value"; message = "instance1 and instance3 should have value of default-value";
@ -64,27 +69,29 @@ in
{ {
message = "should apply defaults to versioned submodule"; message = "should apply defaults to versioned submodule";
assertion = versioned-submodule.config.submodule.args.defaultValue == "versioned-submodule"; assertion = versioned-submodule.config.submodule.args.defaultValue == "versioned-submodule";
}];
};
submodules.imports = [{
modules = [
submodule
{
submodule = {
name = "submodule1";
tags = [ "tag1" ];
};
} }
]; ];
} };
submodules.imports = [
{
modules = [
submodule
{
submodule = {
name = "submodule1";
tags = ["tag1"];
};
}
];
}
{ {
modules = [ modules = [
submodule submodule
{ {
submodule = { submodule = {
name = "submodule2"; name = "submodule2";
tags = [ "tag2" ]; tags = ["tag2"];
}; };
} }
]; ];
@ -95,7 +102,7 @@ in
{ {
submodule = { submodule = {
name = "submodule3"; name = "submodule3";
tags = [ "tag2" ]; tags = ["tag2"];
}; };
} }
]; ];
@ -131,17 +138,19 @@ in
}; };
} }
]; ];
}]; }
];
submodules.defaults = [{ submodules.defaults = [
default.submodule.args.defaultValue = mkDefault "value";
}
{ {
tags = [ "tag1" ]; default.submodule.args.defaultValue = mkDefault "value";
}
{
tags = ["tag1"];
default.submodule.args.value = mkDefault "value1"; default.submodule.args.value = mkDefault "value1";
} }
{ {
tags = [ "tag2" ]; tags = ["tag2"];
default.submodule.args.value = mkDefault "value2"; default.submodule.args.value = mkDefault "value2";
} }
{ {
@ -149,7 +158,7 @@ in
default.submodule.args.value = mkDefault "value4"; default.submodule.args.value = mkDefault "value4";
} }
{ {
default = { config, ... }: { default = {config, ...}: {
submodule.args.defaultValue = mkIf (config.submodule.args.value == "custom-value") "my-custom-value"; submodule.args.defaultValue = mkIf (config.submodule.args.value == "custom-value") "my-custom-value";
}; };
} }
@ -157,7 +166,8 @@ in
name = "versioned-submodule"; name = "versioned-submodule";
version = "2.0.0"; version = "2.0.0";
default.submodule.args.value = mkDefault "versioned"; default.submodule.args.value = mkDefault "versioned";
}]; }
];
submodules.instances.instance1.submodule = "submodule1"; submodules.instances.instance1.submodule = "submodule1";
submodules.instances.instance2.submodule = "submodule2"; submodules.instances.instance2.submodule = "submodule2";

View file

@ -1,9 +1,14 @@
{ name, config, lib, kubenix, subm-lib, ... }: {
name,
with lib; config,
let lib,
kubenix,
subm-lib,
...
}:
with lib; let
submodule = { submodule = {
imports = [ kubenix.modules.submodule ]; imports = [kubenix.modules.submodule];
config.submodule = { config.submodule = {
name = "subm"; name = "subm";
@ -12,21 +17,24 @@ let
}; };
}; };
}; };
in in {
{ imports = with kubenix.modules; [test submodules];
imports = with kubenix.modules; [ test submodules ];
test = { test = {
name = "submodules-exports"; name = "submodules-exports";
description = "Submodules exports test"; description = "Submodules exports test";
assertions = [{ assertions = [
message = "should have library exported"; {
assertion = subm-lib.id 1 == 1; message = "should have library exported";
}]; assertion = subm-lib.id 1 == 1;
}
];
}; };
submodules.imports = [{ submodules.imports = [
modules = [ submodule ]; {
exportAs = "subm-lib"; modules = [submodule];
}]; exportAs = "subm-lib";
}
];
} }

View file

@ -1,24 +1,27 @@
{ name, config, lib, kubenix, ... }: {
name,
with lib; config,
let lib,
submodule = { name, ... }: { kubenix,
imports = [ kubenix.modules.submodule ]; ...
}:
with lib; let
submodule = {name, ...}: {
imports = [kubenix.modules.submodule];
config.submodule = { config.submodule = {
name = "subm"; name = "subm";
passthru.global.${name} = "true"; passthru.global.${name} = "true";
}; };
}; };
in in {
{ imports = with kubenix.modules; [test submodules];
imports = with kubenix.modules; [ test submodules ];
options = { options = {
global = mkOption { global = mkOption {
description = "Global value"; description = "Global value";
type = types.attrs; type = types.attrs;
default = { }; default = {};
}; };
}; };
@ -26,10 +29,11 @@ in
test = { test = {
name = "submodules-passthru"; name = "submodules-passthru";
description = "Submodules passthru test"; description = "Submodules passthru test";
assertions = [{ assertions = [
message = "should passthru values if passthru enabled"; {
assertion = hasAttr "inst1" config.global && config.global.inst1 == "true"; message = "should passthru values if passthru enabled";
} assertion = hasAttr "inst1" config.global && config.global.inst1 == "true";
}
{ {
message = "should not passthru values if passthru not enabled"; message = "should not passthru values if passthru not enabled";
assertion = !(hasAttr "inst2" config.global); assertion = !(hasAttr "inst2" config.global);
@ -37,12 +41,15 @@ in
{ {
message = "should passthru by default"; message = "should passthru by default";
assertion = hasAttr "inst3" config.global && config.global.inst3 == "true"; assertion = hasAttr "inst3" config.global && config.global.inst3 == "true";
}]; }
];
}; };
submodules.imports = [{ submodules.imports = [
modules = [ submodule ]; {
}]; modules = [submodule];
}
];
submodules.instances.inst1 = { submodules.instances.inst1 = {
submodule = "subm"; submodule = "subm";

View file

@ -1,20 +1,24 @@
{ name, config, lib, kubenix, ... }: {
name,
with lib; config,
let lib,
kubenix,
...
}:
with lib; let
cfg = config.submodules.instances.instance; cfg = config.submodules.instances.instance;
args = cfg.config.submodule.args; args = cfg.config.submodule.args;
in in {
{ imports = with kubenix.modules; [test submodules];
imports = with kubenix.modules; [ test submodules ];
test = { test = {
name = "submodules-simple"; name = "submodules-simple";
description = "Simple k8s submodule test"; description = "Simple k8s submodule test";
assertions = [{ assertions = [
message = "Submodule name is set"; {
assertion = cfg.name == "instance"; message = "Submodule name is set";
} assertion = cfg.name == "instance";
}
{ {
message = "Submodule version is set"; message = "Submodule version is set";
assertion = cfg.version == null; assertion = cfg.version == null;
@ -34,32 +38,35 @@ in
{ {
message = "should have tag set"; message = "should have tag set";
assertion = elem "tag" (cfg.config.submodule.tags); assertion = elem "tag" (cfg.config.submodule.tags);
}]; }
];
}; };
submodules.propagate.enable = true; submodules.propagate.enable = true;
submodules.imports = [{ submodules.imports = [
module = { submodule, ... }: { {
imports = [ kubenix.modules.submodule ]; module = {submodule, ...}: {
imports = [kubenix.modules.submodule];
options.submodule.args = { options.submodule.args = {
name = mkOption { name = mkOption {
description = "Submodule name"; description = "Submodule name";
type = types.str; type = types.str;
default = submodule.name; default = submodule.name;
};
value = mkOption {
description = "Submodule argument";
type = types.str;
};
}; };
value = mkOption {
description = "Submodule argument"; config = {
type = types.str; submodule.name = "submodule";
submodule.tags = ["tag"];
}; };
}; };
}
config = { ];
submodule.name = "submodule";
submodule.tags = [ "tag" ];
};
};
}];
submodules.instances.instance = { submodules.instances.instance = {
submodule = "submodule"; submodule = "submodule";

View file

@ -1,13 +1,17 @@
{ name, config, lib, kubenix, ... }: {
name,
with lib; config,
let lib,
kubenix,
...
}:
with lib; let
inst-exact = config.submodules.instances.inst-exact.config; inst-exact = config.submodules.instances.inst-exact.config;
inst-regex = config.submodules.instances.inst-regex.config; inst-regex = config.submodules.instances.inst-regex.config;
inst-latest = config.submodules.instances.inst-latest.config; inst-latest = config.submodules.instances.inst-latest.config;
submodule = { submodule = {
imports = [ kubenix.modules.submodule ]; imports = [kubenix.modules.submodule];
options.version = mkOption { options.version = mkOption {
type = types.str; type = types.str;
@ -16,17 +20,17 @@ let
config.submodule.name = "subm"; config.submodule.name = "subm";
}; };
in in {
{ imports = with kubenix.modules; [test submodules];
imports = with kubenix.modules; [ test submodules ];
test = { test = {
name = "submodules-versioning"; name = "submodules-versioning";
description = "Submodules versioning test"; description = "Submodules versioning test";
assertions = [{ assertions = [
message = "should select exact version"; {
assertion = inst-exact.version == "1.1.0"; message = "should select exact version";
} assertion = inst-exact.version == "1.1.0";
}
{ {
message = "should select regex version"; message = "should select regex version";
assertion = inst-regex.version == "1.2.1"; assertion = inst-regex.version == "1.2.1";
@ -34,37 +38,48 @@ in
{ {
message = "should select latest version"; message = "should select latest version";
assertion = inst-latest.version == "1.2.1"; assertion = inst-latest.version == "1.2.1";
}]; }
];
}; };
submodules.imports = [{ submodules.imports = [
modules = [{
config.submodule.version = "1.0.0";
config.version = "1.0.0";
}
submodule];
}
{ {
modules = [{ modules = [
config.submodule.version = "1.1.0"; {
config.version = "1.1.0"; config.submodule.version = "1.0.0";
} config.version = "1.0.0";
submodule]; }
submodule
];
} }
{ {
modules = [{ modules = [
config.submodule.version = "1.2.0"; {
config.version = "1.2.0"; config.submodule.version = "1.1.0";
} config.version = "1.1.0";
submodule]; }
submodule
];
} }
{ {
modules = [{ modules = [
config.submodule.version = "1.2.1"; {
config.version = "1.2.1"; config.submodule.version = "1.2.0";
} config.version = "1.2.0";
submodule]; }
}]; submodule
];
}
{
modules = [
{
config.submodule.version = "1.2.1";
config.version = "1.2.1";
}
submodule
];
}
];
submodules.instances.inst-exact = { submodules.instances.inst-exact = {
submodule = "subm"; submodule = "subm";