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 }:
let
{system ? builtins.currentSystem}: let
in
(
(
(import ./compat.nix).flake-compat {
src = ./.;
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 }:
let
{
evalModules,
registry,
}: let
# evaluated configuration
config = (evalModules {
module =
{ kubenix, ... }: {
config =
(evalModules {
module = {kubenix, ...}: {
imports = [
kubenix.modules.testing
./module.nix
@ -16,12 +17,12 @@ let
kubernetes.version = "1.21";
testing = {
tests = [ ./test.nix ];
tests = [./test.nix];
docker.registryUrl = "";
# testing commonalities for tests that exhibit the respective feature
common = [
{
features = [ "k8s" ];
features = ["k8s"];
options = {
kubernetes.version = "1.20";
};
@ -29,10 +30,9 @@ let
];
};
};
}).config;
in
{
})
.config;
in {
inherit config;
# config checks

View file

@ -1,8 +1,10 @@
{ dockerTools, nginx }:
{
dockerTools,
nginx,
}:
dockerTools.buildLayeredImage {
name = "nginx";
contents = [ nginx ];
contents = [nginx];
extraCommands = ''
mkdir -p etc
chmod u+w etc
@ -10,9 +12,9 @@ dockerTools.buildLayeredImage {
echo "nginx:x:1000:nginx" > etc/group
'';
config = {
Cmd = [ "nginx" "-c" "/etc/nginx/nginx.conf" ];
Cmd = ["nginx" "-c" "/etc/nginx/nginx.conf"];
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;
@ -54,10 +57,12 @@ in
kubernetes.resources.services.nginx = {
spec = {
ports = [{
ports = [
{
name = "http";
port = 80;
}];
}
];
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 = {
name = "nginx-deployment";

View file

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

View file

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

View file

@ -1,6 +1,7 @@
{ pkgs, lib }:
let
{
pkgs,
lib,
}: let
generateIstio = import ./istio {
inherit
pkgs
@ -8,7 +9,8 @@ let
;
};
generateK8S = name: spec: import ./k8s {
generateK8S = name: spec:
import ./k8s {
inherit
name
pkgs
@ -16,14 +18,13 @@ let
spec
;
};
in
{
istio = pkgs.linkFarm "istio-generated" [{
in {
istio = pkgs.linkFarm "istio-generated" [
{
name = "latest.nix";
path = generateIstio;
}];
}
];
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 }:
with lib;
let
{
pkgs ? import <nixpkgs> {},
lib ? pkgs.lib,
spec ? ./istio-schema.json,
}:
with lib; let
gen = rec {
mkMerge = values: ''mkMerge [${concatMapStrings
(value: "
(value: "
${value}
")
values}]'';
values}]'';
toNixString = value:
if isAttrs value || isList value
@ -20,12 +22,13 @@ values}]'';
removeEmptyLines = str: concatStringsSep "\n" (filter (l: (builtins.match "( |)+" l) == null) (splitString "\n" str));
mkOption =
{ description ? null
, type ? null
, default ? null
, apply ? null
}: removeEmptyLines ''mkOption {
mkOption = {
description ? null,
type ? null,
default ? null,
apply ? null,
}:
removeEmptyLines '' mkOption {
${optionalString (description != null) "description = ${builtins.toJSON description};"}
${optionalString (type != null) ''type = ${type};''}
${optionalString (default != null) ''default = ${toNixString default};''}
@ -43,140 +46,139 @@ values}]'';
nullOr = val: "(types.nullOr ${val})";
attrsOf = val: "(types.attrsOf ${val})";
listOf = val: "(types.listOf ${val})";
coercedTo = coercedType: coerceFunc: finalType:
"(types.coercedTo ${coercedType} ${coerceFunc} ${finalType})";
coercedTo = coercedType: coerceFunc: finalType: "(types.coercedTo ${coercedType} ${coerceFunc} ${finalType})";
either = val1: val2: "(types.either ${val1} ${val2})";
loaOf = type: "(types.loaOf ${type})";
};
hasTypeMapping = def:
hasAttr "type" def &&
elem def.type [ "string" "integer" "boolean" ];
hasAttr "type" def
&& elem def.type ["string" "integer" "boolean"];
mergeValuesByKey = mergeKey: ''(mergeValuesByKey "${mergeKey}")'';
mapType = def:
if def.type == "string" then
if def.type == "string"
then
if hasAttr "format" def && def.format == "int-or-string"
then types.either types.int types.str
else types.str
else if def.type == "integer" then types.int
else if def.type == "number" then types.int
else if def.type == "boolean" then types.bool
else if def.type == "object" then types.attrs
else if def.type == "integer"
then types.int
else if def.type == "number"
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";
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";
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));
genDefinitions = swagger: with gen; (mapAttrs
(name: definition:
genDefinitions = swagger:
with gen; (mapAttrs
(
name: definition:
# if $ref is in definition it means it's an alias of other definition
if hasAttr "$ref" definition
then definitions."${refDefinition definition}"
else if !(hasAttr "properties" definition)
then {
type = mapType definition;
}
else {
options = mapAttrs
(propName: property:
let
isRequired = elem propName (definition.required or [ ]);
requiredOrNot = type: if isRequired then type else types.nullOr type;
options =
mapAttrs
(
propName: property: let
isRequired = elem propName (definition.required or []);
requiredOrNot = type:
if isRequired
then type
else types.nullOr type;
optionProperties =
# if $ref is in property it references other definition,
# but if other definition does not have properties, then just take it's type
if hasAttr "$ref" property then
if hasTypeMapping swagger.definitions.${refDefinition property} then {
if hasAttr "$ref" property
then
if hasTypeMapping swagger.definitions.${refDefinition property}
then {
type = requiredOrNot (mapType swagger.definitions.${refDefinition property});
}
else {
type = requiredOrNot (submoduleOf definitions (refDefinition property));
}
else if !(hasAttr "type" property) then {
else if !(hasAttr "type" property)
then {
type = types.unspecified;
}
# if property has an array type
else if property.type == "array" then
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 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
then let
mergeKey = property."x-kubernetes-patch-merge-key";
in
{
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)));
}
# in other case it only references a simple type
else {
type = requiredOrNot (types.listOf (mapType property.items));
}
else if property.type == "object" && hasAttr "additionalProperties" property
then
# if it is a reference to simple type
if (
hasAttr "$ref" property.additionalProperties &&
hasTypeMapping swagger.definitions.${refDefinition property.additionalProperties}
) then {
if
(
hasAttr "$ref" property.additionalProperties
&& hasTypeMapping swagger.definitions.${refDefinition property.additionalProperties}
)
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);
@ -184,14 +186,16 @@ values}]'';
in
mkOption ({
description = property.description or "";
} // optionProperties)
}
// optionProperties)
)
definition.properties;
config =
let
optionalProps = filterAttrs
(propName: property:
!(elem propName (definition.required or [ ]))
config = let
optionalProps =
filterAttrs
(
propName: property:
!(elem propName (definition.required or []))
)
definition.properties;
in
@ -200,7 +204,8 @@ values}]'';
)
swagger.definitions);
genResources = swagger: (mapAttrsToList
genResources = swagger:
(mapAttrsToList
(name: property: rec {
splittedType = splitString "." (removePrefix "me.snowdrop.istio.api." property.javaType);
group = (concatStringsSep "." (take ((length splittedType) - 2) splittedType)) + ".istio.io";
@ -209,11 +214,13 @@ values}]'';
ref = removePrefix "#/definitions/" property."$ref";
})
(filterAttrs
(name: property:
(hasPrefix "me.snowdrop.istio.api" property.javaType) &&
hasSuffix "Spec" property.javaType
(
name: property:
(hasPrefix "me.snowdrop.istio.api" property.javaType)
&& hasSuffix "Spec" property.javaType
)
swagger.properties)) ++ (mapAttrsToList
swagger.properties))
++ (mapAttrsToList
(name: property: rec {
splittedType = splitString "." (removePrefix "me.snowdrop.istio.mixer." property.javaType);
group = "config.istio.io";
@ -222,9 +229,10 @@ values}]'';
ref = removePrefix "#/definitions/" property."$ref";
})
(filterAttrs
(name: property:
(hasPrefix "me.snowdrop.istio.mixer" property.javaType) &&
hasSuffix "Spec" property.javaType
(
name: property:
(hasPrefix "me.snowdrop.istio.mixer" property.javaType)
&& hasSuffix "Spec" property.javaType
)
swagger.properties));
@ -330,21 +338,24 @@ values}]'';
"${name}" = {${optionalString (hasAttr "options" value) "
options = {${concatStrings (mapAttrsToList (name: value: ''
"${name}" = ${value};
'') value.options)}};
'')
value.options)}};
"}
${optionalString (hasAttr "config" value) ''
config = {${concatStrings (mapAttrsToList (name: value: ''
"${name}" = ${value};
'') value.config)}};
'')
value.config)}};
''}
};
'') definitions)}
'')
definitions)}
} // (import ./overrides.nix {inheirt definitions lib;}));
in {
kubernetes.customResources = [
${concatMapStrings
(resource: ''{
(resource: '' {
group = "${resource.group}";
version = "${resource.version}";
kind = "${resource.kind}";
@ -354,16 +365,16 @@ values}]'';
(genResources swagger)}
];
}
'';
'';
in
pkgs.runCommand "istio-gen.nix"
{
buildInputs = [ pkgs.nixpkgs-fmt ];
} ''
pkgs.runCommand "istio-gen.nix"
{
buildInputs = [pkgs.nixpkgs-fmt];
} ''
cat << 'GENERATED' > ./raw
"${generated}"
GENERATED
nixpkgs-fmt ./raw
cp ./raw $out
''
''

View file

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

View file

@ -1,7 +1,8 @@
{ lib, pkgs }:
{
k8s = import ./k8s { inherit lib; };
docker = import ./docker { inherit lib pkgs; };
helm = import ./helm { inherit pkgs; };
lib,
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
(image: ''
#!${pkgs.runtimeShell}

View file

@ -1,34 +1,34 @@
{ stdenvNoCC, lib, kubernetes-helm, gawk, remarshal, jq }:
with lib;
{
# chart to template
chart
# release name
, name
# namespace to install release into
, namespace ? null
# values to pass to chart
, values ? { }
# kubernetes version to template chart for
, kubeVersion ? null
stdenvNoCC,
lib,
kubernetes-helm,
gawk,
remarshal,
jq,
}:
let
with lib;
{
# chart to template
chart,
# release name
name,
# namespace to install release into
namespace ? null,
# values to pass to chart
values ? {},
# kubernetes version to template chart for
kubeVersion ? null,
}: let
valuesJsonFile = builtins.toFile "${name}-values.json" (builtins.toJSON values);
in
stdenvNoCC.mkDerivation {
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}"} \
${optionalString (values != {}) "-f ${valuesJsonFile}"} \
${chart} >resources.yaml
# split multy yaml file into multiple files
@ -50,5 +50,5 @@ stdenvNoCC.mkDerivation {
.
end)' > $out
'';
nativeBuildInputs = [ kubernetes-helm gawk remarshal jq ];
}
nativeBuildInputs = [kubernetes-helm gawk remarshal jq];
}

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,9 @@
{ config, lib, ... }:
with lib;
{
config,
lib,
...
}:
with lib; {
options = {
kubenix.project = mkOption {
description = "Name of the project";
@ -13,27 +14,27 @@ with lib;
_m.features = mkOption {
description = "List of features exposed by module";
type = types.listOf types.str;
default = [ ];
default = [];
};
_m.propagate = mkOption {
description = "Module propagation options";
type = types.listOf (types.submodule ({ config, ... }: {
type = types.listOf (types.submodule ({config, ...}: {
options = {
features = mkOption {
description = "List of features that submodule has to have to propagate module";
type = types.listOf types.str;
default = [ ];
default = [];
};
module = mkOption {
description = "Module to propagate";
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 = {
registry.url = mkOption {
@ -16,7 +19,11 @@ in
images = mkOption {
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 = {
image = mkOption {
description = "Docker image to publish";
@ -52,13 +59,13 @@ in
};
};
}));
default = { };
default = {};
};
export = mkOption {
description = "List of images to export";
type = types.listOf types.package;
default = [ ];
default = [];
};
copyScript = mkOption {
@ -73,22 +80,29 @@ in
config = {
# define docker feature
_m.features = [ "docker" ];
_m.features = ["docker"];
# propagate docker options if docker feature is enabled
_m.propagate = [{
features = [ "docker" ];
module = { config, name, ... }: {
_m.propagate = [
{
features = ["docker"];
module = {
config,
name,
...
}: {
# propagate registry options
docker.registry = cfg.registry;
};
}];
}
];
# pass docker library as param
_module.args.docker = import ../lib/docker { inherit lib pkgs; };
_module.args.docker = import ../lib/docker {inherit lib pkgs;};
# 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);
};
}

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
# with kubenix
{ config, lib, pkgs, helm, ... }:
with lib;
let
{
config,
lib,
pkgs,
helm,
...
}:
with lib; let
cfg = config.kubernetes.helm;
globalConfig = config;
@ -13,26 +16,29 @@ let
name = "recursive-attrs";
description = "recursive attribute set";
check = isAttrs;
merge = loc: foldl' (res: def: recursiveUpdate res def.value) { };
merge = loc: foldl' (res: def: recursiveUpdate res def.value) {};
};
parseApiVersion = apiVersion:
let
parseApiVersion = apiVersion: let
splitted = splitString "/" apiVersion;
in
{
group = if length splitted == 1 then "core" else head splitted;
in {
group =
if length splitted == 1
then "core"
else head splitted;
version = last splitted;
};
in
{
imports = [ ./k8s.nix ];
in {
imports = [./k8s.nix];
options.kubernetes.helm = {
instances = mkOption {
description = "Attribute set of helm instances";
type = types.attrsOf (types.submodule ({ config, name, ... }: {
type = types.attrsOf (types.submodule ({
config,
name,
...
}: {
options = {
name = mkOption {
description = "Helm release name";
@ -54,7 +60,7 @@ in
values = mkOption {
description = "Values to pass to chart";
type = recursiveAttrs;
default = { };
default = {};
};
kubeVersion = mkOption {
@ -66,7 +72,7 @@ in
overrides = mkOption {
description = "Overrides to apply to all chart resources";
type = types.listOf types.unspecified;
default = [ ];
default = [];
};
overrideNamespace = mkOption {
@ -78,38 +84,40 @@ in
objects = mkOption {
description = "Generated kubernetes objects";
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;
}];
}
];
config.objects = importJSON (helm.chart2json {
inherit (config) chart name namespace values kubeVersion;
});
}));
default = { };
default = {};
};
};
config = {
# 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
(_: instance:
(
_: instance:
map
(object:
let
(object: let
apiVersion = parseApiVersion object.apiVersion;
name = object.metadata.name;
in
{
in {
"${apiVersion.group}"."${apiVersion.version}".${object.kind}."${name}" = mkMerge ([
object
] ++ instance.overrides);
]
++ instance.overrides);
})
instance.objects
)

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -1,100 +1,141 @@
{ config, options, kubenix, pkgs, lib, ... }:
with lib;
let
{
config,
options,
kubenix,
pkgs,
lib,
...
}:
with lib; let
cfg = config.submodules;
parentConfig = config;
matchesVersion = requiredVersion: version:
if requiredVersion != null then
if requiredVersion != null
then
if hasPrefix "~" requiredVersion
then (builtins.match (removePrefix "~" requiredVersion) version) != null
else requiredVersion == version
else true;
getDefaults = { name, version, tags, features }:
getDefaults = {
name,
version,
tags,
features,
}:
catAttrs "default" (filter
(submoduleDefault:
(submoduleDefault.name == null || submoduleDefault.name == name) &&
(matchesVersion submoduleDefault.version version) &&
(
(length submoduleDefault.tags == 0) ||
(length (intersectLists submoduleDefault.tags tags)) > 0
) &&
(
(length submoduleDefault.features == 0) ||
(length (intersectLists submoduleDefault.features features)) > 0
submoduleDefault:
(submoduleDefault.name == null || submoduleDefault.name == name)
&& (matchesVersion submoduleDefault.version version)
&& (
(length submoduleDefault.tags == 0)
|| (length (intersectLists submoduleDefault.tags tags)) > 0
)
&& (
(length submoduleDefault.features == 0)
|| (length (intersectLists submoduleDefault.features features)) > 0
)
)
config.submodules.defaults);
specialArgs = cfg.specialArgs // {
specialArgs =
cfg.specialArgs
// {
parentConfig = config;
};
findSubmodule = { name, version ? null, latest ? true }:
let
matchingSubmodules = filter
(el:
el.definition.name == name &&
(matchesVersion version el.definition.version)
findSubmodule = {
name,
version ? null,
latest ? true,
}: let
matchingSubmodules =
filter
(
el:
el.definition.name
== name
&& (matchesVersion version el.definition.version)
)
cfg.imports;
versionSortedSubmodules = sort
(s1: s2:
versionSortedSubmodules =
sort
(
s1: s2:
if builtins.compareVersions s1.definition.version s2.definition.version > 0
then true else false
then true
else false
)
matchingSubmodules;
matchingModule =
if length versionSortedSubmodules == 0
then throw "No module found ${name}/${if version == null then "latest" else version}"
then
throw "No module found ${name}/${
if version == null
then "latest"
else version
}"
else head versionSortedSubmodules;
in
matchingModule;
passthruConfig = mapAttrsToList
passthruConfig =
mapAttrsToList
(name: opt: {
${name} = mkMerge (mapAttrsToList
(_: inst:
(
_: inst:
if inst.passthru.enable
then inst.config.submodule.passthru.${name} or { }
else { }
then inst.config.submodule.passthru.${name} or {}
else {}
)
config.submodules.instances);
_module.args = mkMerge (mapAttrsToList
(_: inst:
(
_: inst:
if inst.passthru.enable
then inst.config.submodule.passthru._module.args or { }
else { }
then inst.config.submodule.passthru._module.args or {}
else {}
)
config.submodules.instances);
})
(removeAttrs options [ "_definedNames" "_module" "_m" "submodules" ]);
(removeAttrs options ["_definedNames" "_module" "_m" "submodules"]);
submoduleWithSpecialArgs = opts: specialArgs:
let
submoduleWithSpecialArgs = opts: specialArgs: let
opts' = toList opts;
inherit (lib.modules) evalModules;
in
mkOptionType rec {
name = "submodule";
check = x: isAttrs x || isFunction x;
merge = loc: defs:
let
coerce = def: if isFunction def then def else { config = def; };
modules = opts' ++ map (def: { _file = def.file; imports = [ (coerce def.value) ]; }) defs;
merge = loc: defs: let
coerce = def:
if isFunction def
then def
else {config = def;};
modules =
opts'
++ map (def: {
_file = def.file;
imports = [(coerce def.value)];
})
defs;
in
(evalModules {
inherit modules specialArgs;
prefix = loc;
}).config;
getSubOptions = prefix: (evalModules
})
.config;
getSubOptions = prefix:
(evalModules
{
modules = opts'; inherit prefix specialArgs;
modules = opts';
inherit prefix specialArgs;
# This is a work-around due to the fact that some sub-modules,
# such as the one included in an attribute set, expects a "args"
# attribute to be given to the sub-module. As the option
@ -111,30 +152,32 @@ let
# 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;
})
.options;
getSubModules = opts';
substSubModules = m: submoduleWithSpecialArgs m specialArgs;
functor = (defaultFunctor name) // {
functor =
(defaultFunctor name)
// {
# Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
# each submodule with its location.
payload = [ ];
binOp = lhs: rhs: [ ];
payload = [];
binOp = lhs: rhs: [];
};
};
in
{
imports = [ ./base.nix ];
in {
imports = [./base.nix];
options = {
submodules.specialArgs = mkOption {
description = "Special args to pass to submodules. These arguments can be used for imports";
type = types.attrs;
default = { };
default = {};
};
submodules.defaults = mkOption {
description = "List of defaults to apply to submodule instances";
type = types.listOf (types.submodule ({ config, ... }: {
type = types.listOf (types.submodule ({config, ...}: {
options = {
name = mkOption {
description = "Name of the submodule to apply defaults for";
@ -154,23 +197,23 @@ in
tags = mkOption {
description = "List of tags to apply defaults for";
type = types.listOf types.str;
default = [ ];
default = [];
};
features = mkOption {
description = "List of features that submodule has to have to apply defaults";
type = types.listOf types.str;
default = [ ];
default = [];
};
default = mkOption {
description = "Default to apply to submodule instance";
type = types.unspecified;
default = { };
default = {};
};
};
}));
default = [ ];
default = [];
};
submodules.propagate.enable = mkOption {
@ -184,12 +227,16 @@ in
type = types.listOf (
types.coercedTo
types.path
(module: { inherit module; })
(types.submodule ({ name, config, ... }:
let
(module: {inherit module;})
(
types.submodule ({
name,
config,
...
}: let
evaledSubmodule' = evalModules {
inherit specialArgs;
modules = config.modules ++ [ ./base.nix ];
modules = config.modules ++ [./base.nix];
check = false;
};
@ -197,8 +244,7 @@ in
if (!(elem "submodule" evaledSubmodule'.config._m.features))
then throw "no submodule defined"
else evaledSubmodule';
in
{
in {
options = {
module = mkOption {
description = "Module defining submodule";
@ -208,7 +254,7 @@ in
modules = mkOption {
description = "List of modules defining submodule";
type = types.listOf types.unspecified;
default = [ config.module ];
default = [config.module];
};
features = mkOption {
@ -238,14 +284,18 @@ in
})
)
);
default = [ ];
default = [];
};
submodules.instances = mkOption {
description = "Attribute set of submodule instances";
default = { };
type = types.attrsOf (types.submodule ({ name, config, options, ... }:
let
default = {};
type = types.attrsOf (types.submodule ({
name,
config,
options,
...
}: let
# submodule associated with
submodule = findSubmodule {
name = config.submodule;
@ -262,8 +312,7 @@ in
tags = submoduleDefinition.tags;
features = submodule.features;
};
in
{
in {
options = {
name = mkOption {
description = "Submodule instance name";
@ -294,16 +343,17 @@ in
config = mkOption {
description = "Submodule instance ${config.name} for ${submoduleDefinition.name}:${submoduleDefinition.version} config";
type = submoduleWithSpecialArgs
({ ... }: {
imports = submodule.modules ++ defaults ++ [ ./base.nix ];
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 = { };
default = {};
};
args = mkOption {
@ -312,7 +362,7 @@ in
};
}));
};
default = { };
default = {};
};
config = mkMerge ([
@ -324,17 +374,19 @@ in
})
(filter (submodule: submodule.exportAs != null) cfg.imports));
_m.features = [ "submodules" ];
_m.features = ["submodules"];
submodules.specialArgs.kubenix = kubenix;
# passthru kubenix.project to submodules
submodules.defaults = mkMerge [
[{
[
{
default = {
kubenix.project = parentConfig.kubenix.project;
};
}]
}
]
(map
(propagate: {
@ -347,15 +399,18 @@ in
(mkIf cfg.propagate.enable {
# if propagate is enabled and submodule has submodules included propagage defaults and imports
submodules.defaults = [{
features = [ "submodules" ];
submodules.defaults = [
{
features = ["submodules"];
default = {
submodules = {
defaults = cfg.defaults;
imports = cfg.imports;
};
};
}];
}
];
})
] ++ passthruConfig);
]
++ passthruConfig);
}

View file

@ -1,11 +1,15 @@
{ config, pkgs, lib, kubenix, ... }:
with lib;
let
{
config,
pkgs,
lib,
kubenix,
...
}:
with lib; let
cfg = config.testing;
testModule = {
imports = [ ./evalTest.nix ];
imports = [./evalTest.nix];
# passthru testing configuration
config._module.args = {
@ -16,9 +20,7 @@ let
isTestEnabled = test:
(cfg.enabledTests == null || elem test.name cfg.enabledTests) && test.enable;
in
{
in {
imports = [
./docker.nix
./driver/kubetest.nix
@ -41,28 +43,28 @@ in
common = mkOption {
description = "List of common options to apply to tests";
type = types.listOf (types.submodule ({ config, ... }: {
type = types.listOf (types.submodule ({config, ...}: {
options = {
features = mkOption {
description = "List of features that test has to have to apply options";
type = types.listOf types.str;
default = [ ];
default = [];
};
options = mkOption {
description = "Options to apply to test";
type = types.unspecified;
default = { };
apply = default: { _file = "testing.common"; } // default;
default = {};
apply = default: {_file = "testing.common";} // default;
};
};
}));
default = [ ];
default = [];
};
tests = mkOption {
description = "List of test cases";
default = [ ];
default = [];
type = types.listOf (types.coercedTo types.path
(module: {
inherit module;
@ -86,7 +88,7 @@ in
args = mkOption {
description = "Attribute set of extra args passed to tests";
type = types.attrs;
default = { };
default = {};
};
success = mkOption {

View file

@ -1,16 +1,17 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
with lib;
with import ../../lib/docker { inherit lib pkgs; };
let
with import ../../lib/docker {inherit lib pkgs;}; let
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;
in
{
in {
options.testing.docker = {
registryUrl = mkOption {
description = "Docker registry url";
@ -37,11 +38,13 @@ in
};
};
config.testing.common = [{
features = [ "docker" ];
config.testing.common = [
{
features = ["docker"];
options = {
_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.python38Packages;
with pkgs.python38;
pkgs.python38Packages.buildPythonPackage rec {
pkgs.python38Packages.buildPythonPackage rec {
pname = "kubetest";
version = "0.9.5";
src = fetchPypi {
inherit pname version;
sha256 = "sha256-TqDHMciAEXv4vMWLJY1YdtXsP4ho+INgdFB3xQQNoZU=";
};
propagatedBuildInputs = [ pytest kubernetes ];
propagatedBuildInputs = [pytest kubernetes];
doCheck = false;
}
}

View file

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

View file

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

View file

@ -1,29 +1,32 @@
# nixos-k8s implements nixos kubernetes testing runtime
{ config
, pkgs
, lib
, ...
{
config,
pkgs,
lib,
...
}:
with lib;
let
with lib; let
testing = config.testing;
# kubeconfig = "/etc/${config.services.kubernetes.pki.etcClusterAdminKubeconfig}";
kubeconfig = "/etc/kubernetes/cluster-admin.kubeconfig";
kubecerts = "/var/lib/kubernetes/secrets";
# how we differ from the standard configuration of mkKubernetesBaseTest
extraConfiguration = { config, pkgs, lib, nodes, ... }: {
extraConfiguration = {
config,
pkgs,
lib,
nodes,
...
}: {
virtualisation = {
memorySize = 2048;
};
networking = {
nameservers = [ "10.0.0.254" ];
nameservers = ["10.0.0.254"];
firewall = {
trustedInterfaces = [ "docker0" "cni0" ];
trustedInterfaces = ["docker0" "cni0"];
};
};
@ -32,7 +35,8 @@ let
kubelet = {
seedDockerImages = testing.docker.images;
networkPlugin = "cni";
cni.config = [{
cni.config = [
{
name = "mynet";
type = "bridge";
bridge = "cni0";
@ -43,11 +47,14 @@ let
type = "host-local";
subnet = "10.1.0.0/16";
gateway = "10.1.0.1";
routes = [{
routes = [
{
dst = "0.0.0.0/0";
}];
}
];
};
}];
}
];
};
};
@ -57,32 +64,30 @@ let
services.copy-certs = {
description = "Share k8s certificates with host";
script = "cp -rf ${kubecerts} /tmp/xchg/; cp -f ${kubeconfig} /tmp/xchg/;";
after = [ "kubernetes.target" ];
wantedBy = [ "multi-user.target" ];
after = ["kubernetes.target"];
wantedBy = ["multi-user.target"];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
};
};
};
script = ''
machine1.succeed("${testing.testScript} --kube-config=${kubeconfig}")
'';
test =
with import "${pkgs.path}/nixos/tests/kubernetes/base.nix" { inherit pkgs; inherit (pkgs) system; };
test = with import "${pkgs.path}/nixos/tests/kubernetes/base.nix" {
inherit pkgs;
inherit (pkgs) system;
};
mkKubernetesSingleNodeTest {
inherit extraConfiguration;
inherit (config.testing) name;
test = script;
};
in
{
in {
options.testing.runtime.nixos-k8s = {
driver = mkOption {
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 = {
name = mkOption {
description = "Test name";
@ -38,8 +39,13 @@ in
};
};
});
default = [ ];
example = [{ assertion = false; message = "you can't enable this for some reason"; }];
default = [];
example = [
{
assertion = false;
message = "you can't enable this for some reason";
}
];
description = ''
This option allows modules to express conditions that must
hold for the evaluation of the system configuration to
@ -52,6 +58,5 @@ in
type = types.nullOr (types.either types.lines types.path);
default = null;
};
};
}

View file

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

View file

@ -1,23 +1,22 @@
{ stdenv
, lib
, fetchFromGitHub
, removeReferencesTo
, which
, go
, makeWrapper
, rsync
, installShellFiles
, components ? [
{
stdenv,
lib,
fetchFromGitHub,
removeReferencesTo,
which,
go,
makeWrapper,
rsync,
installShellFiles,
components ? [
"cmd/kubelet"
"cmd/kube-apiserver"
"cmd/kube-controller-manager"
"cmd/kube-proxy"
"cmd/kube-scheduler"
"test/e2e/e2e.test"
]
],
}:
stdenv.mkDerivation rec {
pname = "kubernetes";
version = "1.20.4";
@ -29,11 +28,11 @@ stdenv.mkDerivation rec {
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 = ''
# go env breaks the sandbox
@ -51,7 +50,8 @@ stdenv.mkDerivation rec {
WHAT = lib.concatStringsSep " " ([
"cmd/kubeadm"
"cmd/kubectl"
] ++ components);
]
++ components);
postBuild = ''
./hack/update-generated-docs.sh
@ -84,7 +84,7 @@ stdenv.mkDerivation rec {
description = "Production-Grade Container Scheduling and Management";
license = licenses.asl20;
homepage = "https://kubernetes.io";
maintainers = with maintainers; [ johanot offline saschagrunert ];
maintainers = with maintainers; [johanot offline saschagrunert];
platforms = platforms.unix;
};
}

View file

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

View file

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

View file

@ -1,17 +1,18 @@
{ pkgs, dockerTools, lib, ... }:
with lib;
{
pkgs,
dockerTools,
lib,
...
}:
with lib; {
curl = dockerTools.buildLayeredImage {
name = "curl";
tag = "latest";
config.Cmd = [ "${pkgs.bash}" "-c" "sleep infinity" ];
contents = [ pkgs.bash pkgs.curl pkgs.cacert ];
config.Cmd = ["${pkgs.bash}" "-c" "sleep infinity"];
contents = [pkgs.bash pkgs.curl pkgs.cacert];
};
nginx =
let
nginx = let
nginxPort = "80";
nginxConf = pkgs.writeText "nginx.conf" ''
user nginx nginx;
@ -37,7 +38,7 @@ with lib;
dockerTools.buildLayeredImage {
name = "xtruder/nginx";
tag = "latest";
contents = [ pkgs.nginx ];
contents = [pkgs.nginx];
extraCommands = ''
mkdir -p etc
chmod u+w etc
@ -49,9 +50,9 @@ with lib;
echo "nginx:x:1000:nginx" > etc/group
'';
config = {
Cmd = [ "nginx" "-c" nginxConf ];
Cmd = ["nginx" "-c" nginxConf];
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 = {
name = "istio-bookinfo";
@ -12,23 +14,27 @@
Gateway."bookinfo-gateway" = {
spec = {
selector.istio = "ingressgateway";
servers = [{
servers = [
{
port = {
number = 80;
name = "http";
protocol = "HTTP";
};
hosts = [ "*" ];
}];
hosts = ["*"];
}
];
};
};
VirtualService.bookinfo = {
spec = {
hosts = [ "*" ];
gateways = [ "bookinfo-gateway" ];
http = [{
match = [{
hosts = ["*"];
gateways = ["bookinfo-gateway"];
http = [
{
match = [
{
uri.exact = "/productpage";
}
{
@ -39,31 +45,38 @@
}
{
uri.prefix = "/api/v1/products";
}];
route = [{
}
];
route = [
{
destination = {
host = "productpage";
port.number = 9080;
};
}];
}];
}
];
}
];
};
};
DestinationRule.productpage = {
spec = {
host = "productpage";
subsets = [{
subsets = [
{
name = "v1";
labels.version = "v1";
}];
}
];
};
};
DestinationRule.reviews = {
spec = {
host = "reviews";
subsets = [{
subsets = [
{
name = "v1";
labels.version = "v1";
}
@ -74,14 +87,16 @@
{
name = "v3";
labels.version = "v3";
}];
}
];
};
};
DestinationRule.ratings = {
spec = {
host = "ratings";
subsets = [{
subsets = [
{
name = "v1";
labels.version = "v1";
}
@ -96,21 +111,24 @@
{
name = "v2-mysql-vm";
labels.version = "v2-mysql-vm";
}];
}
];
};
};
DestinationRule.details = {
spec = {
host = "details";
subsets = [{
subsets = [
{
name = "v1";
labels.version = "v1";
}
{
name = "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 = {
name = "k8s-crd";
description = "Simple test tesing CRD";
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";
}];
}
];
script = ''
@pytest.mark.applymanifest('${config.kubernetes.resultYAML}')
def test_testing_module(kube):
@ -50,7 +55,6 @@ in
type = types.str;
};
};
}
{
group = "stable.example.com";

View file

@ -1,17 +1,20 @@
{ config, lib, kubenix, ... }:
with lib;
let
{
config,
lib,
kubenix,
...
}:
with lib; let
pod1 = config.kubernetes.api.resources.pods.pod1;
pod2 = config.kubernetes.api.resources.pods.pod2;
in
{
imports = with kubenix.modules; [ test k8s ];
in {
imports = with kubenix.modules; [test k8s];
test = {
name = "k8s-defaults";
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";
}
@ -22,16 +25,18 @@ in
{
message = "Should have conditional annotation set";
assertion = pod2.metadata.annotations.conditional-annotation == "value";
}];
}
];
};
kubernetes.resources.pods.pod1 = { };
kubernetes.resources.pods.pod1 = {};
kubernetes.resources.pods.pod2 = {
metadata.labels.custom-label = "value";
};
kubernetes.api.defaults = [{
kubernetes.api.defaults = [
{
resource = "pods";
default.metadata.labels.resource-label = "value";
}
@ -43,10 +48,11 @@ in
}
{
resource = "pods";
default = { config, ... }: {
default = {config, ...}: {
config.metadata.annotations = mkIf (config.metadata.labels ? "custom-label") {
conditional-annotation = "value";
};
};
}];
}
];
}

View file

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

View file

@ -1,25 +1,29 @@
{ config, lib, kubenix, ... }:
with lib;
let
{
config,
lib,
kubenix,
...
}:
with lib; let
pod = config.kubernetes.api.resources.core.v1.Pod.test;
deployment = config.kubernetes.api.resources.apps.v1.Deployment.nginx-deployment;
in
{
imports = with kubenix.modules; [ test k8s ];
in {
imports = with kubenix.modules; [test k8s];
test = {
name = "k8s-imports";
description = "Simple k8s testing imports";
enable = builtins.compareVersions config.kubernetes.version "1.10" >= 0;
assertions = [{
assertions = [
{
message = "Pod should have name set";
assertion = pod.metadata.name == "test";
}
{
message = "Deployment should have name set";
assertion = deployment.metadata.name == "nginx-deployment";
}];
}
];
};
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 = {
name = "k8s-order";
description = "test tesing k8s resource order";
assertions = [{
assertions = [
{
message = "should have correct order of resources";
assertion =
(elemAt config.kubernetes.objects 0).kind == "CustomResourceDefinition" &&
(elemAt config.kubernetes.objects 1).kind == "Namespace" &&
(elemAt config.kubernetes.objects 2).kind == "CronTab";
}];
(elemAt config.kubernetes.objects 0).kind
== "CustomResourceDefinition"
&& (elemAt config.kubernetes.objects 1).kind == "Namespace"
&& (elemAt config.kubernetes.objects 2).kind == "CronTab";
}
];
};
kubernetes.resources.customResourceDefinitions.crontabs = {
@ -24,22 +30,25 @@ in
metadata.name = "crontabs.stable.example.com";
spec = {
group = "stable.example.com";
versions = [{
versions = [
{
name = "v1";
served = true;
schema = true;
}];
}
];
scope = "Namespaced";
names = {
plural = "crontabs";
singular = "crontab";
kind = "CronTab";
shortNames = [ "ct" ];
shortNames = ["ct"];
};
};
};
kubernetes.customTypes = [{
kubernetes.customTypes = [
{
name = "crontabs";
description = "CronTabs resources";
@ -53,9 +62,10 @@ in
type = types.str;
};
};
}];
}
];
kubernetes.resources.namespaces.test = { };
kubernetes.resources.namespaces.test = {};
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 = {
name = "k8s-simple";
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 name set";
assertion = cfg.metadata.name == "nginx";
}];
}
];
};
kubernetes.resources.pods.nginx = { };
kubernetes.resources.pods.nginx = {};
}

View file

@ -1,30 +1,41 @@
{ 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 = {
name = "k8s-submodule";
description = "Simple k8s submodule test";
assertions = [{
assertions = [
{
message = "Submodule has correct name set";
assertion = (head config.kubernetes.objects).metadata.name == "passthru";
}
{
message = "Should expose docker image";
assertion = (head config.docker.export).imageName == "xtruder/nginx";
}];
}
];
};
kubernetes.namespace = "test-namespace";
submodules.imports = [{
module = { name, config, ... }: {
imports = with kubenix.modules; [ submodule k8s docker ];
submodules.imports = [
{
module = {
name,
config,
...
}: {
imports = with kubenix.modules; [submodule k8s docker];
config = {
submodule = {
@ -43,12 +54,15 @@ in
docker.images.nginx.image = images.nginx;
};
};
}];
}
];
kubernetes.api.defaults = [{
kubernetes.api.defaults = [
{
propagate = true;
default.metadata.labels.my-label = "my-value";
}];
}
];
submodules.instances.passthru = {
submodule = "test-submodule";

View file

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

View file

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

View file

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

View file

@ -1,17 +1,21 @@
{ name, config, lib, kubenix, ... }:
with lib;
let
{
name,
config,
lib,
kubenix,
...
}:
with lib; let
cfg = config.submodules.instances.instance;
args = cfg.config.submodule.args;
in
{
imports = with kubenix.modules; [ test submodules ];
in {
imports = with kubenix.modules; [test submodules];
test = {
name = "submodules-simple";
description = "Simple k8s submodule test";
assertions = [{
assertions = [
{
message = "Submodule name is set";
assertion = cfg.name == "instance";
}
@ -34,13 +38,15 @@ in
{
message = "should have tag set";
assertion = elem "tag" (cfg.config.submodule.tags);
}];
}
];
};
submodules.propagate.enable = true;
submodules.imports = [{
module = { submodule, ... }: {
imports = [ kubenix.modules.submodule ];
submodules.imports = [
{
module = {submodule, ...}: {
imports = [kubenix.modules.submodule];
options.submodule.args = {
name = mkOption {
@ -56,10 +62,11 @@ in
config = {
submodule.name = "submodule";
submodule.tags = [ "tag" ];
submodule.tags = ["tag"];
};
};
}];
}
];
submodules.instances.instance = {
submodule = "submodule";

View file

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