This commit is contained in:
David Arnold 2021-05-13 17:27:08 -04:00
parent c3fa598922
commit db6d83c61e
No known key found for this signature in database
GPG key ID: 6D6A936E69C59D08
53 changed files with 1916 additions and 1599 deletions

5
ci.nix
View file

@ -1,6 +1,6 @@
let let
nixpkgsSrc = builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/master.tar.gz"; nixpkgsSrc = builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/master.tar.gz";
pkgs = import nixpkgsSrc {}; pkgs = import nixpkgsSrc { };
lib = pkgs.lib; lib = pkgs.lib;
@ -8,4 +8,5 @@ let
inherit pkgs lib; inherit pkgs lib;
nixosPath = "${nixpkgsSrc}/nixos"; nixosPath = "${nixpkgsSrc}/nixos";
}; };
in pkgs.recurseIntoAttrs release in
pkgs.recurseIntoAttrs release

View file

@ -7,7 +7,6 @@ in
}: }:
with lib; with lib;
let let
kubenixLib = import ./lib { inherit lib pkgs; }; kubenixLib = import ./lib { inherit lib pkgs; };
lib' = lib.extend (lib: self: import ./lib/extra.nix { inherit lib pkgs; }); lib' = lib.extend (lib: self: import ./lib/extra.nix { inherit lib pkgs; });
@ -18,46 +17,54 @@ let
# 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 = { evalModules =
module ? null, { module ? null
modules ? [module], , modules ? [ module ]
specialArgs ? defaultSpecialArgs, ... , specialArgs ? defaultSpecialArgs
}@attrs: let , ...
attrs' = filterAttrs (n: _: n != "module") attrs; }@attrs:
in lib'.evalModules (recursiveUpdate { let
inherit specialArgs modules; attrs' = filterAttrs (n: _: n != "module") attrs;
args = { in
inherit pkgs; lib'.evalModules (recursiveUpdate
name = "default"; {
}; inherit specialArgs modules;
} attrs'); args = {
inherit pkgs;
name = "default";
};
}
attrs');
modules = import ./modules; modules = import ./modules;
# legacy support for buildResources # legacy support for buildResources
buildResources = { buildResources =
configuration ? {}, { configuration ? { }
writeJSON ? true, , writeJSON ? true
writeHash ? true , writeHash ? true
}: let }:
evaled = evalModules { let
modules = [ evaled = evalModules {
configuration modules = [
modules.legacy configuration
]; modules.legacy
}; ];
};
generated = evaled.config.kubernetes.generated; generated = evaled.config.kubernetes.generated;
result = result =
if writeJSON if writeJSON
then pkgs.writeText "resources.json" (builtins.toJSON generated) then pkgs.writeText "resources.json" (builtins.toJSON generated)
else generated; else generated;
in result; in
result;
kubenix = { kubenix = {
inherit evalModules buildResources modules; inherit evalModules buildResources modules;
lib = kubenixLib; lib = kubenixLib;
}; };
in kubenix in
kubenix

View file

@ -1,4 +1,4 @@
{ kubenix ? import ./.. {} }: { kubenix ? import ./.. { } }:
{ {
nginx-deployment = import ./nginx-deployment { inherit kubenix; }; nginx-deployment = import ./nginx-deployment { inherit kubenix; };

View file

@ -1,4 +1,4 @@
{ kubenix ? import ../.. {}, registry ? "docker.io/gatehub" }: { kubenix ? import ../.. { }, registry ? "docker.io/gatehub" }:
with kubenix.lib; with kubenix.lib;

View file

@ -10,9 +10,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,10 +1,10 @@
{ config, lib, pkgs, kubenix, ... }: { config, lib, pkgs, kubenix, ... }:
with lib; with lib;
let let
nginx = pkgs.callPackage ./image.nix { }; nginx = pkgs.callPackage ./image.nix { };
in { in
{
imports = with kubenix.modules; [ k8s docker ]; imports = with kubenix.modules; [ k8s docker ];
docker.images.nginx.image = nginx; docker.images.nginx.image = nginx;

View file

@ -1,14 +1,16 @@
{ pkgs ? import <nixpkgs> {}, lib ? pkgs.lib, spec ? ./istio-schema.json }: { pkgs ? import <nixpkgs> { }, lib ? pkgs.lib, spec ? ./istio-schema.json }:
with lib; with lib;
let let
gen = rec { gen = rec {
mkMerge = values: ''mkMerge [${concatMapStrings (value: " mkMerge = values: ''mkMerge [${concatMapStrings
(value: "
${value} ${value}
") values}]''; ")
values}]'';
toNixString = value: if isAttrs value || isList value toNixString = value:
if isAttrs value || isList value
then builtins.toJSON value then builtins.toJSON value
else if isString value else if isString value
then ''"${value}"'' then ''"${value}"''
@ -18,19 +20,19 @@ let
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 { }: removeEmptyLines ''mkOption {
${optionalString (description != null) "description = ${builtins.toJSON description};"} ${optionalString (description != null) "description = ${builtins.toJSON description};"}
${optionalString (type != null) ''type = ${type};''} ${optionalString (type != null) ''type = ${type};''}
${optionalString (default != null) ''default = ${toNixString default};''} ${optionalString (default != null) ''default = ${toNixString default};''}
${optionalString (apply != null) ''apply = ${apply};''} ${optionalString (apply != null) ''apply = ${apply};''}
}''; }'';
mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}"; mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}";
types = { types = {
unspecified = "types.unspecified"; unspecified = "types.unspecified";
@ -49,7 +51,7 @@ let
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}")'';
@ -77,256 +79,282 @@ let
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 (name: definition: genDefinitions = swagger: with gen; (mapAttrs
# if $ref is in definition it means it's an alias of other definition (name: definition:
if hasAttr "$ref" definition # if $ref is in definition it means it's an alias of other definition
then definitions."${refDefinition definition}" if hasAttr "$ref" definition
then definitions."${refDefinition definition}"
else if !(hasAttr "properties" definition) else if !(hasAttr "properties" definition)
then { then {
type = mapType definition; type = mapType definition;
} }
else { else {
options = mapAttrs (propName: property: options = mapAttrs
let (propName: property:
isRequired = elem propName (definition.required or []); let
requiredOrNot = type: if isRequired then type else types.nullOr type; isRequired = elem propName (definition.required or [ ]);
optionProperties = requiredOrNot = type: if isRequired then type else types.nullOr type;
# if $ref is in property it references other definition, optionProperties =
# but if other definition does not have properties, then just take it's type # if $ref is in property it references other definition,
if hasAttr "$ref" property then # but if other definition does not have properties, then just take it's type
if hasTypeMapping swagger.definitions.${refDefinition property} then { if hasAttr "$ref" property then
type = requiredOrNot (mapType swagger.definitions.${refDefinition property}); if hasTypeMapping swagger.definitions.${refDefinition property} then {
} type = requiredOrNot (mapType swagger.definitions.${refDefinition property});
else { }
type = requiredOrNot (submoduleOf 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; 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 # if property has an array type
else else if property.type == "array" then
# 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 # 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)));
}
# in other case it only references a simple type
else { else {
type = requiredOrNot (types.listOf (submoduleOf definitions (refDefinition property.items))); type = requiredOrNot (types.listOf (mapType property.items));
} }
# in other case it only references a simple type else if property.type == "object" && hasAttr "additionalProperties" property
else { then
type = requiredOrNot (types.listOf (mapType property.items)); # if it is a reference to simple type
} if (
hasAttr "$ref" property.additionalProperties &&
hasTypeMapping swagger.definitions.${refDefinition property.additionalProperties}
) then {
type = requiredOrNot (types.attrsOf (mapType swagger.definitions.${refDefinition property.additionalProperties}));
}
else if property.type == "object" && hasAttr "additionalProperties" property else if hasAttr "$ref" property.additionalProperties
then then {
# if it is a reference to simple type type = requiredOrNot types.attrs;
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 # if is an array
then { else if property.additionalProperties.type == "array"
type = requiredOrNot types.attrs; then {
} type = requiredOrNot (types.loaOf (mapType property.additionalProperties.items));
}
# if is an array else {
else if property.additionalProperties.type == "array" type = requiredOrNot (types.attrsOf (mapType property.additionalProperties));
then { }
type = requiredOrNot (types.loaOf (mapType property.additionalProperties.items));
}
else { # just a simple property
type = requiredOrNot (types.attrsOf (mapType property.additionalProperties)); else {
} type = requiredOrNot (mapType property);
};
in
mkOption ({
description = property.description or "";
} // optionProperties)
)
definition.properties;
config =
let
optionalProps = filterAttrs
(propName: property:
!(elem propName (definition.required or [ ]))
)
definition.properties;
in
mapAttrs (name: property: mkOverride 1002 null) optionalProps;
}
)
swagger.definitions);
# just a simple property genResources = swagger: (mapAttrsToList
else { (name: property: rec {
type = requiredOrNot (mapType property); splittedType = splitString "." (removePrefix "me.snowdrop.istio.api." property.javaType);
}; group = (concatStringsSep "." (take ((length splittedType) - 2) splittedType)) + ".istio.io";
in mkOption ({ kind = removeSuffix "Spec" (last splittedType);
description = property.description or ""; version = last (take ((length splittedType) - 1) splittedType);
} // optionProperties) ref = removePrefix "#/definitions/" property."$ref";
) definition.properties; })
config = (filterAttrs
let (name: property:
optionalProps = filterAttrs (propName: property: (hasPrefix "me.snowdrop.istio.api" property.javaType) &&
!(elem propName (definition.required or [])) hasSuffix "Spec" property.javaType
) definition.properties; )
in mapAttrs (name: property: mkOverride 1002 null) optionalProps; swagger.properties)) ++ (mapAttrsToList
} (name: property: rec {
) swagger.definitions); splittedType = splitString "." (removePrefix "me.snowdrop.istio.mixer." property.javaType);
group = "config.istio.io";
genResources = swagger: (mapAttrsToList (name: property: rec { version = "v1alpha2";
splittedType = splitString "." (removePrefix "me.snowdrop.istio.api." property.javaType); kind = head (tail splittedType);
group = (concatStringsSep "." (take ((length splittedType) - 2) splittedType)) + ".istio.io"; ref = removePrefix "#/definitions/" property."$ref";
kind = removeSuffix "Spec" (last splittedType); })
version = last (take ((length splittedType) - 1) splittedType); (filterAttrs
ref = removePrefix "#/definitions/" property."$ref"; (name: property:
}) (hasPrefix "me.snowdrop.istio.mixer" property.javaType) &&
(filterAttrs (name: property: hasSuffix "Spec" property.javaType
(hasPrefix "me.snowdrop.istio.api" property.javaType) && )
hasSuffix "Spec" property.javaType swagger.properties));
) swagger.properties)) ++ (mapAttrsToList (name: property: rec {
splittedType = splitString "." (removePrefix "me.snowdrop.istio.mixer." property.javaType);
group = "config.istio.io";
version = "v1alpha2";
kind = head (tail splittedType);
ref = removePrefix "#/definitions/" property."$ref";
}) (filterAttrs (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;
in pkgs.writeText "gen.nix" in
"# This file was generated with kubenix k8s generator, do not edit pkgs.writeText "gen.nix" ''
{lib, config, ... }: # This file was generated with kubenix k8s generator, do not edit
{lib, config, ... }:
with lib;
with lib;
let
types = lib.types // rec { let
str = mkOptionType { types = lib.types // rec {
name = \"str\"; str = mkOptionType {
description = \"string\"; name = \"str\";
check = isString; description = \"string\";
merge = mergeEqualOption; check = isString;
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: extraOptions = {
mkOptionType rec { kubenix = {};
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; };
}; };
};
mergeValuesByKey = mergeKey: values:
mkOptionDefault = mkOverride 1001; listToAttrs (map
(value: nameValuePair (
extraOptions = { if isAttrs value.\${mergeKey}
kubenix = {}; then toString value.\${mergeKey}.content
}; else (toString value.\${mergeKey})
) value)
mergeValuesByKey = mergeKey: values: values);
listToAttrs (map
(value: nameValuePair ( submoduleOf = ref: types.submodule ({name, ...}: {
if isAttrs value.\${mergeKey} options = definitions.\"\${ref}\".options;
then toString value.\${mergeKey}.content config = definitions.\"\${ref}\".config;
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 {
definitions = { options = definitions.\"\${ref}\".options;
${concatStrings (mapAttrsToList (name: value: " config = definitions.\"\${ref}\".config // {
\"${name}\" = {${optionalString (hasAttr "options" value) " \${mergeKey} = mkOverride 1002 (convertName name);
options = {${concatStrings (mapAttrsToList (name: value: " };
\"${name}\" = ${value}; });
") value.options)}};
"}${optionalString (hasAttr "config" value) " submoduleForDefinition = ref: resource: kind: group: version:
config = {${concatStrings (mapAttrsToList (name: value: " types.submodule ({name, ...}: {
\"${name}\" = ${value}; options = definitions.\"\${ref}\".options // extraOptions;
") value.config)}}; config = mkMerge ([
"}}; definitions.\"\${ref}\".config
") definitions)} {
} // (import ./overrides.nix {inheirt definitions lib;})); kind = mkOptionDefault kind;
in { apiVersion = mkOptionDefault version;
kubernetes.customResources = [
${concatMapStrings (resource: ''{ # metdata.name cannot use option default, due deep config
group = "${resource.group}"; metadata.name = mkOptionDefault name;
version = "${resource.version}"; }
kind = "${resource.kind}"; ] ++ (config.defaults.\${resource} or [])
description = ""; ++ (config.defaults.all or []));
module = definitions."${resource.ref}"; });
}'') (genResources swagger)}
]; 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) "
options = {${concatStrings (mapAttrsToList
(name: value: "
\"${name}\" = ${value};
")
value.options)}};
"}${optionalString (hasAttr "config" value) "
config = {${concatStrings (mapAttrsToList
(name: value: "
\"${name}\" = ${value};
")
value.config)}};
"}};
")
definitions)}
} // (import ./overrides.nix {inheirt definitions lib;}));
in {
kubernetes.customResources = [
${concatMapStrings
(resource: ''{
group = "${resource.group}";
version = "${resource.version}";
kind = "${resource.kind}";
description = "";
module = definitions."${resource.ref}";
}'')
(genResources swagger)}
];
}
''

View file

@ -1,18 +1,21 @@
{ name ? "k8s" { name ? "k8s"
, pkgs ? import <nixpkgs> {} , pkgs ? import <nixpkgs> { }
, lib ? pkgs.lib , lib ? pkgs.lib
, spec ? ./specs/1.21/swagger.json , spec ? ./specs/1.21/swagger.json
, ... }: , ...
}:
with lib; with lib;
let let
gen = rec { gen = rec {
mkMerge = values: ''mkMerge [${concatMapStrings (value: " mkMerge = values: ''mkMerge [${concatMapStrings
(value: "
${value} ${value}
") values}]''; ")
values}]'';
toNixString = value: if isAttrs value || isList value toNixString = value:
if isAttrs value || isList value
then builtins.toJSON value then builtins.toJSON value
else if isString value else if isString value
then ''"${value}"'' then ''"${value}"''
@ -22,19 +25,19 @@ let
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 { }: removeEmptyLines ''mkOption {
${optionalString (description != null) "description = ${builtins.toJSON description};"} ${optionalString (description != null) "description = ${builtins.toJSON description};"}
${optionalString (type != null) ''type = ${type};''} ${optionalString (type != null) ''type = ${type};''}
${optionalString (default != null) ''default = ${toNixString default};''} ${optionalString (default != null) ''default = ${toNixString default};''}
${optionalString (apply != null) ''apply = ${apply};''} ${optionalString (apply != null) ''apply = ${apply};''}
}''; }'';
mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}"; mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}";
types = { types = {
unspecified = "types.unspecified"; unspecified = "types.unspecified";
@ -53,7 +56,7 @@ let
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}")'';
@ -83,185 +86,220 @@ let
refType = attr: head (tail (tail (splitString "/" attr."$ref"))); refType = attr: head (tail (tail (splitString "/" attr."$ref")));
compareVersions = ver1: ver2: let compareVersions = ver1: ver2:
getVersion = v: substring 1 10 v; let
splitVersion = v: builtins.splitVersion (getVersion v); getVersion = v: substring 1 10 v;
isAlpha = v: elem "alpha" (splitVersion v); splitVersion = v: builtins.splitVersion (getVersion v);
patchVersion = v: isAlpha = v: elem "alpha" (splitVersion v);
if isAlpha v then "" patchVersion = v:
else if length (splitVersion v) == 1 then "${getVersion v}prod" if isAlpha v then ""
else getVersion v; else if length (splitVersion v) == 1 then "${getVersion v}prod"
else getVersion v;
v1 = patchVersion ver1; v1 = patchVersion ver1;
v2 = patchVersion ver2; v2 = patchVersion ver2;
in builtins.compareVersions v1 v2; 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)); fetchSpecs = path: builtins.fromJSON (fixJSON (builtins.readFile path));
genDefinitions = swagger: with gen; mapAttrs (name: definition: genDefinitions = swagger: with gen; mapAttrs
# if $ref is in definition it means it's an alias of other definition (name: definition:
if hasAttr "$ref" definition # if $ref is in definition it means it's an alias of other definition
then definitions."${refDefinition definition}" if hasAttr "$ref" definition
then definitions."${refDefinition definition}"
else if !(hasAttr "properties" definition) else if !(hasAttr "properties" definition)
then {} then { }
# in other case it's an actual definition # in other case it's an actual definition
else { else {
options = mapAttrs (propName: property: options = mapAttrs
let (propName: property:
isRequired = elem propName (definition.required or []); let
requiredOrNot = type: if isRequired then type else types.nullOr type; isRequired = elem propName (definition.required or [ ]);
optionProperties = requiredOrNot = type: if isRequired then type else types.nullOr type;
optionProperties =
# if $ref is in property it references other definition, # if $ref is in property it references other definition,
# but if other definition does not have properties, then just take it's type # but if other definition does not have properties, then just take it's type
if hasAttr "$ref" property then if hasAttr "$ref" property then
if hasTypeMapping swagger.definitions.${refDefinition property} then { if hasTypeMapping swagger.definitions.${refDefinition property} then {
type = requiredOrNot (mapType swagger.definitions.${refDefinition property}); 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
# 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 { else {
type = requiredOrNot (types.listOf (submoduleOf definitions (refDefinition property.items))); type = requiredOrNot (submoduleOf definitions (refDefinition property));
} }
# in other case it only references a simple type # if property has an array type
else { else if property.type == "array" then
type = requiredOrNot (types.listOf (mapType property.items));
}
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 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 # if it is a reference to simple type
then { if hasTypeMapping swagger.definitions.${refDefinition property.items}
type = requiredOrNot types.attrs; then {
} type = requiredOrNot (types.listOf (mapType swagger.definitions.${refDefinition property.items}.type));
}
# if is an array # if a reference is to complex type
else if property.additionalProperties.type == "array" else
then { # if x-kubernetes-patch-merge-key is set then make it an
type = requiredOrNot (types.loaOf (mapType property.additionalProperties.items)); # 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;
}
else { # in other case it's a simple list
type = requiredOrNot (types.attrsOf (mapType property.additionalProperties)); else {
} type = requiredOrNot (types.listOf (submoduleOf definitions (refDefinition property.items)));
}
# just a simple property # in other case it only references a simple type
else { else {
type = requiredOrNot (mapType property); type = requiredOrNot (types.listOf (mapType property.items));
}; }
in mkOption ({
description = property.description or "";
} // optionProperties)
) definition.properties;
config =
let
optionalProps = filterAttrs (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 (i: c1: else if property.type == "object" && hasAttr "additionalProperties" property
f i c1 (if i >= stringLength s2 then "" else elemAt (stringToCharacters s2) i) then
) (stringToCharacters s1)); # if it is a reference to simple type
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);
};
in
mkOption ({
description = property.description or "";
} // optionProperties)
)
definition.properties;
config =
let
optionalProps = filterAttrs
(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
(i: c1:
f i c1 (if i >= stringLength s2 then "" else elemAt (stringToCharacters s2) i)
)
(stringToCharacters s1));
getAttrName = resource: kind: getAttrName = resource: kind:
mapCharPairs (i: c1: c2: mapCharPairs
if hasPrefix "API" kind && i == 0 then "A" (i: c1: c2:
else if i == 0 then c1 if hasPrefix "API" kind && i == 0 then "A"
else if c2 == "" || (toLower c2) != c1 then c1 else if i == 0 then c1
else c2 else if c2 == "" || (toLower c2) != c1 then c1
) resource kind; else c2
)
resource
kind;
genResourceTypes = swagger: mapAttrs' (name: path: let genResourceTypes = swagger: mapAttrs'
ref = refType (head path.post.parameters).schema; (name: path:
group' = path.post."x-kubernetes-group-version-kind".group; let
version' = path.post."x-kubernetes-group-version-kind".version; ref = refType (head path.post.parameters).schema;
kind' = path.post."x-kubernetes-group-version-kind".kind; group' = path.post."x-kubernetes-group-version-kind".group;
name' = last (splitString "/" name); version' = path.post."x-kubernetes-group-version-kind".version;
attrName = getAttrName name' kind'; kind' = path.post."x-kubernetes-group-version-kind".kind;
in nameValuePair ref { name' = last (splitString "/" name);
inherit ref attrName; attrName = getAttrName name' kind';
in
nameValuePair ref {
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 (name: path: (filterAttrs
hasAttr "post" path && (name: path:
path.post."x-kubernetes-action" == "post" hasAttr "post" path &&
) swagger.paths); path.post."x-kubernetes-action" == "post"
)
swagger.paths);
swagger = fetchSpecs spec; swagger = fetchSpecs spec;
definitions = genDefinitions swagger; definitions = genDefinitions swagger;
resourceTypes = genResourceTypes swagger; resourceTypes = genResourceTypes swagger;
resourceTypesByKind = zipAttrs (mapAttrsToList (name: resourceType: { resourceTypesByKind = zipAttrs (mapAttrsToList
${resourceType.kind} = resourceType; (name: resourceType: {
}) resourceTypes); ${resourceType.kind} = resourceType;
})
resourceTypes);
resourcesTypesByKindSortByVersion = mapAttrs (kind: resourceTypes: resourcesTypesByKindSortByVersion = mapAttrs
reverseList (sort (r1: r2: (kind: resourceTypes:
compareVersions r1.version r2.version > 0 reverseList (sort
) resourceTypes) (r1: r2:
) resourceTypesByKind; compareVersions r1.version r2.version > 0
)
resourceTypes)
)
resourceTypesByKind;
latestResourceTypesByKind = latestResourceTypesByKind =
mapAttrs (kind: resources: last resources) resourcesTypesByKindSortByVersion; mapAttrs (kind: resources: last resources) resourcesTypesByKindSortByVersion;
genResourceOptions = resource: with gen; let genResourceOptions = resource: with gen; let
submoduleForDefinition' = definition: let submoduleForDefinition' = definition:
in submoduleForDefinition let
definition.ref definition.name definition.kind definition.group definition.version; in
in mkOption { submoduleForDefinition
definition.ref
definition.name
definition.kind
definition.group
definition.version;
in
mkOption {
description = resource.description; description = resource.description;
type = types.attrsOf (submoduleForDefinition' resource); type = types.attrsOf (submoduleForDefinition' resource);
default = {}; default = { };
}; };
generated = '' generated = ''
@ -415,7 +453,9 @@ let
}; };
} }
''; '';
in pkgs.runCommand "k8s-${name}-gen.nix" { in
pkgs.runCommand "k8s-${name}-gen.nix"
{
buildInputs = [ pkgs.haskellPackages.nixfmt ]; buildInputs = [ pkgs.haskellPackages.nixfmt ];
} '' } ''
cp ${builtins.toFile "k8s-${name}-gen-raw.nix" generated} $out cp ${builtins.toFile "k8s-${name}-gen-raw.nix" generated} $out

View file

@ -3,13 +3,15 @@
with lib; with lib;
{ {
copyDockerImages = { images, dest, args ? "" }: copyDockerImages = { images, dest, args ? "" }:
pkgs.writeScript "copy-docker-images.sh" (concatMapStrings (image: '' pkgs.writeScript "copy-docker-images.sh" (concatMapStrings
#!${pkgs.runtimeShell} (image: ''
#!${pkgs.runtimeShell}
set -e set -e
echo "copying '${image.imageName}:${image.imageTag}' to '${dest}/${image.imageName}:${image.imageTag}'" echo "copying '${image.imageName}:${image.imageTag}' to '${dest}/${image.imageName}:${image.imageTag}'"
${pkgs.skopeo}/bin/skopeo copy ${args} $@ docker-archive:${image} ${dest}/${image.imageName}:${image.imageTag} ${pkgs.skopeo}/bin/skopeo copy ${args} $@ docker-archive:${image} ${dest}/${image.imageName}:${image.imageTag}
'') images); '')
images);
} }

View file

@ -21,31 +21,39 @@ rec {
else mkOverride priority value; else mkOverride priority value;
loadYAML = path: importJSON (pkgs.runCommand "yaml-to-json" { loadYAML = path: importJSON (pkgs.runCommand "yaml-to-json"
} "${pkgs.remarshal}/bin/remarshal -i ${path} -if yaml -of json > $out"); { } "${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"
buildInputs = [pkgs.remarshal]; {
} '' buildInputs = [ pkgs.remarshal ];
} ''
remarshal -i ${pkgs.writeText "to-json" (builtins.toJSON config)} -if json -of yaml > $out 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
buildInputs = [ pkgs.remarshal ]; {
} (concatMapStringsSep "\necho --- >> $out\n" (d: buildInputs = [ pkgs.remarshal ];
"remarshal -i ${builtins.toFile "doc" (builtins.toJSON d)} -if json -of yaml >> $out" }
) documents); (concatMapStringsSep "\necho --- >> $out\n"
(d:
"remarshal -i ${builtins.toFile "doc" (builtins.toJSON d)} -if json -of yaml >> $out"
)
documents);
toBase64 = value: toBase64 = value:
builtins.readFile 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); exp = base: exp: foldr (value: acc: acc * base) 1 (range 1 exp);
octalToDecimal = value: (foldr (char: acc: { octalToDecimal = value: (foldr
i = acc.i + 1; (char: acc: {
value = acc.value + (toInt char) * (exp 8 acc.i); i = acc.i + 1;
}) {i = 0; value = 0;} (stringToCharacters value)).value; value = acc.value + (toInt char) * (exp 8 acc.i);
})
{ i = 0; value = 0; }
(stringToCharacters value)).value;
submoduleWithSpecialArgs = opts: specialArgs: submoduleWithSpecialArgs = opts: specialArgs:
let let
@ -58,13 +66,15 @@ rec {
merge = loc: defs: merge = loc: defs:
let let
coerce = def: if isFunction def then def else { config = def; }; coerce = def: if isFunction def then def else { config = def; };
modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs; modules = opts' ++ map (def: { _file = def.file; imports = [ (coerce def.value) ]; }) defs;
in (evalModules { in
(evalModules {
inherit modules specialArgs; inherit modules specialArgs;
prefix = loc; prefix = loc;
}).config; }).config;
getSubOptions = prefix: (evalModules 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, # 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" # such as the one included in an attribute set, expects a "args"
# attribute to be given to the sub-module. As the option # attribute to be given to the sub-module. As the option
@ -87,16 +97,19 @@ rec {
functor = (defaultFunctor name) // { functor = (defaultFunctor name) // {
# Merging of submodules is done as part of mergeOptionDecls, as we have to annotate # Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
# each submodule with its location. # each submodule with its location.
payload = []; payload = [ ];
binOp = lhs: rhs: []; binOp = lhs: rhs: [ ];
}; };
}; };
coerceListOfSubmodulesToAttrs = submodule: keyFn: let coerceListOfSubmodulesToAttrs = submodule: keyFn:
let
mergeValuesByFn = keyFn: values: mergeValuesByFn = keyFn: values:
listToAttrs (map (value: listToAttrs (map
nameValuePair (toString (keyFn value)) value (value:
) values); nameValuePair (toString (keyFn value)) value
)
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`.
@ -113,14 +126,16 @@ rec {
else else
let coerced = coerceFunc val; in assert finalType.check coerced; coerced; let coerced = coerceFunc val; in assert finalType.check coerced; coerced;
in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); in
finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
getSubOptions = finalType.getSubOptions; getSubOptions = finalType.getSubOptions;
getSubModules = finalType.getSubModules; getSubModules = finalType.getSubModules;
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
typeMerge = t1: t2: null; typeMerge = t1: t2: null;
functor = (defaultFunctor name) // { wrapped = finalType; }; functor = (defaultFunctor name) // { wrapped = finalType; };
}; };
in coercedTo in
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));

View file

@ -13,19 +13,22 @@ with lib;
, namespace ? null , namespace ? null
# values to pass to chart # values to pass to chart
, values ? {} , values ? { }
# kubernetes version to template chart for # kubernetes version to template chart for
, kubeVersion ? null }: let , kubeVersion ? null
}:
let
valuesJsonFile = builtins.toFile "${name}-values.json" (builtins.toJSON values); valuesJsonFile = builtins.toFile "${name}-values.json" (builtins.toJSON values);
in stdenvNoCC.mkDerivation { in
stdenvNoCC.mkDerivation {
name = "${name}.json"; name = "${name}.json";
buildCommand = '' buildCommand = ''
# template helm file and write resources to yaml # template helm file and write resources to yaml
helm template "${name}" \ helm template "${name}" \
${optionalString (kubeVersion != null) "--api-versions ${kubeVersion}"} \ ${optionalString (kubeVersion != null) "--api-versions ${kubeVersion}"} \
${optionalString (namespace != null) "--namespace ${namespace}"} \ ${optionalString (namespace != null) "--namespace ${namespace}"} \
${optionalString (values != {}) "-f ${valuesJsonFile}"} \ ${optionalString (values != { }) "-f ${valuesJsonFile}"} \
${chart} >resources.yaml ${chart} >resources.yaml
# split multy yaml file into multiple files # split multy yaml file into multiple files

View file

@ -1,9 +1,9 @@
{ stdenvNoCC, lib, kubernetes-helm, cacert }: { stdenvNoCC, lib, kubernetes-helm, cacert }:
let let
cleanName = name: lib.replaceStrings ["/"] ["-"] name; cleanName = name: lib.replaceStrings [ "/" ] [ "-" ] name;
in { in
{
# name of the chart # name of the chart
chart chart
@ -13,20 +13,21 @@ in {
# version of the chart # version of the chart
, version ? null , version ? null
# chart hash # chart hash
, sha256 , sha256
# whether to extract chart # whether to extract chart
, untar ? true , untar ? true
# use custom charts repo # use custom charts repo
, repo ? null , repo ? null
# pass --verify to helm chart # pass --verify to helm chart
, verify ? false , verify ? false
# pass --devel to helm chart # pass --devel to helm chart
, devel ? false }: stdenvNoCC.mkDerivation { , devel ? false
}: stdenvNoCC.mkDerivation {
name = "${cleanName chart}-${if version == null then "dev" else version}"; name = "${cleanName chart}-${if version == null then "dev" else version}";
buildCommand = '' buildCommand = ''

View file

@ -1,9 +1,9 @@
{ 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

@ -4,7 +4,7 @@ with lib;
rec { rec {
# TODO: refactor with mkOptionType # TODO: refactor with mkOptionType
mkSecretOption = {description ? "", default ? {}, allowNull ? true}: mkOption { mkSecretOption = { description ? "", default ? { }, allowNull ? true }: mkOption {
inherit description; inherit description;
type = (if allowNull then types.nullOr else id) (types.submodule { type = (if allowNull then types.nullOr else id) (types.submodule {
options = { options = {
@ -24,7 +24,7 @@ rec {
})); }));
}; };
}); });
default = if default == null then null else {}; default = if default == null then null else { };
}; };
secretToEnv = value: { secretToEnv = value: {
@ -34,7 +34,7 @@ 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,19 +42,23 @@ rec {
}; };
# Creates hashed kubernetes list from a list of kubernetes objects # Creates hashed kubernetes list from a list of kubernetes objects
mkHashedList = { items, labels ? {} }: let mkHashedList = { items, labels ? { } }:
hash = builtins.hashString "sha1" (builtins.toJSON items); let
hash = builtins.hashString "sha1" (builtins.toJSON items);
labeledItems = map (item: recursiveUpdate item { labeledItems = map
metadata.labels."kubenix/hash" = hash; (item: recursiveUpdate item {
}) items; metadata.labels."kubenix/hash" = hash;
})
items;
in mkList { in
items = labeledItems; mkList {
labels = { items = labeledItems;
"kubenix/hash" = hash; labels = {
} // labels; "kubenix/hash" = hash;
}; } // labels;
};
toBase64 = lib.toBase64; toBase64 = lib.toBase64;
octalToDecimal = lib.octalToDecimal; octalToDecimal = lib.octalToDecimal;

View file

@ -13,27 +13,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,10 +1,10 @@
{ config, lib, pkgs, docker, ... }: { config, lib, pkgs, docker, ... }:
with lib; with lib;
let let
cfg = config.docker; cfg = config.docker;
in { in
{
imports = [ ./base.nix ]; imports = [ ./base.nix ];
options.docker = { options.docker = {
@ -52,13 +52,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,7 +73,7 @@ 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 = [{

View file

@ -4,7 +4,6 @@
{ config, lib, pkgs, helm, ... }: { config, lib, pkgs, helm, ... }:
with lib; with lib;
let let
cfg = config.kubernetes.helm; cfg = config.kubernetes.helm;
@ -14,17 +13,20 @@ 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: let parseApiVersion = apiVersion:
splitted = splitString "/" apiVersion; let
in { splitted = splitString "/" apiVersion;
group = if length splitted == 1 then "core" else head splitted; in
version = last splitted; {
}; group = if length splitted == 1 then "core" else head splitted;
version = last splitted;
};
in { in
{
imports = [ ./k8s.nix ]; imports = [ ./k8s.nix ];
options.kubernetes.helm = { options.kubernetes.helm = {
@ -52,7 +54,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 {
@ -64,7 +66,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 {
@ -76,7 +78,7 @@ 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 = [ ];
}; };
}; };
@ -88,7 +90,7 @@ in {
inherit (config) chart name namespace values kubeVersion; inherit (config) chart name namespace values kubeVersion;
}); });
})); }));
default = {}; default = { };
}; };
}; };
@ -96,15 +98,21 @@ in {
# 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 (_: instance: kubernetes.api.resources = mkMerge (flatten (mapAttrsToList
map (object: let (_: instance:
apiVersion = parseApiVersion object.apiVersion; map
name = object.metadata.name; (object:
in { let
"${apiVersion.group}"."${apiVersion.version}".${object.kind}."${name}" = mkMerge ([ apiVersion = parseApiVersion object.apiVersion;
object name = object.metadata.name;
] ++ instance.overrides); in
}) instance.objects {
) cfg.instances)); "${apiVersion.group}"."${apiVersion.version}".${object.kind}."${name}" = mkMerge ([
object
] ++ instance.overrides);
})
instance.objects
)
cfg.instances));
}; };
} }

View file

@ -3,11 +3,12 @@
with lib; with lib;
{ {
"istio_networking_v1alpha3_StringMatch" = recursiveUpdate (recursiveUpdate "istio_networking_v1alpha3_StringMatch" = recursiveUpdate
definitions."istio_networking_v1alpha3_StringMatch_Exact" (recursiveUpdate
definitions."istio_networking_v1alpha3_StringMatch_Prefix" definitions."istio_networking_v1alpha3_StringMatch_Exact"
) 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"

View file

@ -1,8 +1,7 @@
# 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 {
@ -15,61 +14,68 @@ let
# 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 then val if finalType.check val then val
else let else
coerced = coerceFunc val; let
in assert finalType.check coerced; coerced; coerced = coerceFunc val;
in
assert finalType.check coerced; coerced;
in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs); in
getSubOptions = finalType.getSubOptions; finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
getSubModules = finalType.getSubModules; getSubOptions = finalType.getSubOptions;
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m); getSubModules = finalType.getSubModules;
typeMerge = t1: t2: null; substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
functor = (defaultFunctor name) // { wrapped = finalType; }; typeMerge = t1: t2: null;
}; functor = (defaultFunctor name) // { wrapped = finalType; };
};
}; };
mkOptionDefault = mkOverride 1001; mkOptionDefault = mkOverride 1001;
extraOptions = { extraOptions = {
kubenix = {}; kubenix = { };
}; };
mergeValuesByKey = mergeKey: values: mergeValuesByKey = mergeKey: values:
listToAttrs (map listToAttrs (map
(value: nameValuePair ( (value: nameValuePair
if isAttrs value.${mergeKey} (
then toString value.${mergeKey}.content if isAttrs value.${mergeKey}
else (toString value.${mergeKey}) then toString value.${mergeKey}.content
) value) else (toString value.${mergeKey})
values); )
value)
values);
submoduleOf = ref: types.submodule ({name, ...}: { submoduleOf = ref: types.submodule ({ name, ... }: {
options = definitions."${ref}".options; options = definitions."${ref}".options;
config = definitions."${ref}".config; config = definitions."${ref}".config;
}); });
submoduleWithMergeOf = ref: mergeKey: types.submodule ({name, ...}: let submoduleWithMergeOf = ref: mergeKey: types.submodule ({ name, ... }:
convertName = name: let
if definitions."${ref}".options.${mergeKey}.type == types.int convertName = name:
then toInt name if definitions."${ref}".options.${mergeKey}.type == types.int
else name; then toInt name
in { else name;
options = definitions."${ref}".options; in
config = definitions."${ref}".config // { {
${mergeKey} = mkOverride 1002 (convertName name); options = definitions."${ref}".options;
}; config = definitions."${ref}".config // {
}); ${mergeKey} = mkOverride 1002 (convertName name);
};
});
submoduleForDefinition = ref: resource: kind: group: version: submoduleForDefinition = ref: resource: kind: group: version:
types.submodule ({name, ...}: { types.submodule ({ name, ... }: {
options = definitions."${ref}".options // extraOptions; options = definitions."${ref}".options // extraOptions;
config = mkMerge ([ config = mkMerge ([
definitions."${ref}".config definitions."${ref}".config
@ -80,8 +86,8 @@ let
# metdata.name cannot use option default, due deep config # metdata.name cannot use option default, due deep config
metadata.name = mkOptionDefault name; metadata.name = mkOptionDefault name;
} }
] ++ (config.defaults.${resource} or []) ] ++ (config.defaults.${resource} or [ ])
++ (config.defaults.all or [])); ++ (config.defaults.all or [ ]));
}); });
coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: (types.coercedTo coerceAttrsOfSubmodulesToListByKey = ref: mergeKey: (types.coercedTo
@ -2846,7 +2852,7 @@ let
}; };
}; };
"istio_mixer_v1_ReportResponse" = {}; "istio_mixer_v1_ReportResponse" = { };
"istio_mixer_v1_RouteDirective" = { "istio_mixer_v1_RouteDirective" = {
options = { options = {
@ -4697,225 +4703,261 @@ let
}; };
}; };
} // (import ./istio-overrides.nix {inherit definitions lib;}); } // (import ./istio-overrides.nix { inherit definitions lib; });
in { in
{
kubernetes.customResources = [ kubernetes.customResources = [
{ {
group = "networking.istio.io"; group = "networking.istio.io";
version = "v1alpha3"; version = "v1alpha3";
kind = "DestinationRule"; kind = "DestinationRule";
description = ""; description = "";
module = definitions."istio_networking_v1alpha3_DestinationRule"; module = definitions."istio_networking_v1alpha3_DestinationRule";
}{ }
group = "networking.istio.io"; {
version = "v1alpha3"; group = "networking.istio.io";
kind = "EnvoyFilter"; version = "v1alpha3";
description = ""; kind = "EnvoyFilter";
module = definitions."istio_networking_v1alpha3_EnvoyFilter"; description = "";
}{ module = definitions."istio_networking_v1alpha3_EnvoyFilter";
group = "networking.istio.io"; }
version = "v1alpha3"; {
kind = "Gateway"; group = "networking.istio.io";
description = ""; version = "v1alpha3";
module = definitions."istio_networking_v1alpha3_Gateway"; kind = "Gateway";
}{ description = "";
group = "authentication.istio.io"; module = definitions."istio_networking_v1alpha3_Gateway";
version = "v1alpha1"; }
kind = "Policy"; {
description = ""; group = "authentication.istio.io";
module = definitions."istio_authentication_v1alpha1_Policy"; version = "v1alpha1";
}{ kind = "Policy";
group = "rbac.istio.io"; description = "";
version = "v1alpha1"; module = definitions."istio_authentication_v1alpha1_Policy";
kind = "RbacConfig"; }
description = ""; {
module = definitions."istio_rbac_v1alpha1_RbacConfig"; group = "rbac.istio.io";
}{ version = "v1alpha1";
group = "policy.istio.io"; kind = "RbacConfig";
version = "v1beta1"; description = "";
kind = "Rule"; module = definitions."istio_rbac_v1alpha1_RbacConfig";
description = ""; }
module = definitions."istio_policy_v1beta1_Rule"; {
}{ group = "policy.istio.io";
group = "networking.istio.io"; version = "v1beta1";
version = "v1alpha3"; kind = "Rule";
kind = "ServiceEntry"; description = "";
description = ""; module = definitions."istio_policy_v1beta1_Rule";
module = definitions."istio_networking_v1alpha3_ServiceEntry"; }
}{ {
group = "rbac.istio.io"; group = "networking.istio.io";
version = "v1alpha1"; version = "v1alpha3";
kind = "ServiceRole"; kind = "ServiceEntry";
description = ""; description = "";
module = definitions."istio_rbac_v1alpha1_ServiceRole"; module = definitions."istio_networking_v1alpha3_ServiceEntry";
}{ }
group = "rbac.istio.io"; {
version = "v1alpha1"; group = "rbac.istio.io";
kind = "ServiceRoleBinding"; version = "v1alpha1";
description = ""; kind = "ServiceRole";
module = definitions."istio_rbac_v1alpha1_ServiceRoleBinding"; description = "";
}{ module = definitions."istio_rbac_v1alpha1_ServiceRole";
group = "networking.istio.io"; }
version = "v1alpha3"; {
kind = "VirtualService"; group = "rbac.istio.io";
description = ""; version = "v1alpha1";
module = definitions."istio_networking_v1alpha3_VirtualService"; kind = "ServiceRoleBinding";
}{ description = "";
group = "config.istio.io"; module = definitions."istio_rbac_v1alpha1_ServiceRoleBinding";
version = "v1alpha2"; }
kind = "apikey"; {
description = ""; group = "networking.istio.io";
module = definitions."istio_mixer_apikey_InstanceMsg"; version = "v1alpha3";
}{ kind = "VirtualService";
group = "config.istio.io"; description = "";
version = "v1alpha2"; module = definitions."istio_networking_v1alpha3_VirtualService";
kind = "authorization"; }
description = ""; {
module = definitions."istio_mixer_authorization_InstanceMsg"; group = "config.istio.io";
}{ version = "v1alpha2";
group = "config.istio.io"; kind = "apikey";
version = "v1alpha2"; description = "";
kind = "bypass"; module = definitions."istio_mixer_apikey_InstanceMsg";
description = ""; }
module = definitions."istio_adapter_bypass_Params"; {
}{ group = "config.istio.io";
group = "config.istio.io"; version = "v1alpha2";
version = "v1alpha2"; kind = "authorization";
kind = "checknothing"; description = "";
description = ""; module = definitions."istio_mixer_authorization_InstanceMsg";
module = definitions."istio_mixer_checknothing_InstanceMsg"; }
}{ {
group = "config.istio.io"; group = "config.istio.io";
version = "v1alpha2"; version = "v1alpha2";
kind = "circonus"; kind = "bypass";
description = ""; description = "";
module = definitions."istio_adapter_circonus_Params"; module = definitions."istio_adapter_bypass_Params";
}{ }
group = "config.istio.io"; {
version = "v1alpha2"; group = "config.istio.io";
kind = "denier"; version = "v1alpha2";
description = ""; kind = "checknothing";
module = definitions."istio_adapter_denier_Params"; description = "";
}{ module = definitions."istio_mixer_checknothing_InstanceMsg";
group = "config.istio.io"; }
version = "v1alpha2"; {
kind = "edge"; group = "config.istio.io";
description = ""; version = "v1alpha2";
module = definitions."istio_mixer_edge_InstanceMsg"; kind = "circonus";
}{ description = "";
group = "config.istio.io"; module = definitions."istio_adapter_circonus_Params";
version = "v1alpha2"; }
kind = "fluentd"; {
description = ""; group = "config.istio.io";
module = definitions."istio_adapter_fluentd_Params"; version = "v1alpha2";
}{ kind = "denier";
group = "config.istio.io"; description = "";
version = "v1alpha2"; module = definitions."istio_adapter_denier_Params";
kind = "kubernetesenv"; }
description = ""; {
module = definitions."istio_adapter_kubernetesenv_Params"; group = "config.istio.io";
}{ version = "v1alpha2";
group = "config.istio.io"; kind = "edge";
version = "v1alpha2"; description = "";
kind = "listentry"; module = definitions."istio_mixer_edge_InstanceMsg";
description = ""; }
module = definitions."istio_mixer_listentry_InstanceMsg"; {
}{ group = "config.istio.io";
group = "config.istio.io"; version = "v1alpha2";
version = "v1alpha2"; kind = "fluentd";
kind = "logentry"; description = "";
description = ""; module = definitions."istio_adapter_fluentd_Params";
module = definitions."istio_mixer_logentry_InstanceMsg"; }
}{ {
group = "config.istio.io"; group = "config.istio.io";
version = "v1alpha2"; version = "v1alpha2";
kind = "memquota"; kind = "kubernetesenv";
description = ""; description = "";
module = definitions."istio_adapter_memquota_Params"; module = definitions."istio_adapter_kubernetesenv_Params";
}{ }
group = "config.istio.io"; {
version = "v1alpha2"; group = "config.istio.io";
kind = "metric"; version = "v1alpha2";
description = ""; kind = "listentry";
module = definitions."istio_mixer_metric_InstanceMsg"; description = "";
}{ module = definitions."istio_mixer_listentry_InstanceMsg";
group = "config.istio.io"; }
version = "v1alpha2"; {
kind = "opa"; group = "config.istio.io";
description = ""; version = "v1alpha2";
module = definitions."istio_adapter_opa_Params"; kind = "logentry";
}{ description = "";
group = "config.istio.io"; module = definitions."istio_mixer_logentry_InstanceMsg";
version = "v1alpha2"; }
kind = "prometheus"; {
description = ""; group = "config.istio.io";
module = definitions."istio_adapter_prometheus_Params"; version = "v1alpha2";
}{ kind = "memquota";
group = "config.istio.io"; description = "";
version = "v1alpha2"; module = definitions."istio_adapter_memquota_Params";
kind = "quota"; }
description = ""; {
module = definitions."istio_mixer_quota_InstanceMsg"; group = "config.istio.io";
}{ version = "v1alpha2";
group = "config.istio.io"; kind = "metric";
version = "v1alpha2"; description = "";
kind = "rbac"; module = definitions."istio_mixer_metric_InstanceMsg";
description = ""; }
module = definitions."istio_adapter_rbac_Params"; {
}{ group = "config.istio.io";
group = "config.istio.io"; version = "v1alpha2";
version = "v1alpha2"; kind = "opa";
kind = "redisquota"; description = "";
description = ""; module = definitions."istio_adapter_opa_Params";
module = definitions."istio_adapter_redisquota_Params"; }
}{ {
group = "config.istio.io"; group = "config.istio.io";
version = "v1alpha2"; version = "v1alpha2";
kind = "reportnothing"; kind = "prometheus";
description = ""; description = "";
module = definitions."istio_mixer_reportnothing_InstanceMsg"; module = definitions."istio_adapter_prometheus_Params";
}{ }
group = "config.istio.io"; {
version = "v1alpha2"; group = "config.istio.io";
kind = "servicecontrol"; version = "v1alpha2";
description = ""; kind = "quota";
module = definitions."istio_adapter_servicecontrol_Params"; description = "";
}{ module = definitions."istio_mixer_quota_InstanceMsg";
group = "config.istio.io"; }
version = "v1alpha2"; {
kind = "signalfx"; group = "config.istio.io";
description = ""; version = "v1alpha2";
module = definitions."istio_adapter_signalfx_Params"; kind = "rbac";
}{ description = "";
group = "config.istio.io"; module = definitions."istio_adapter_rbac_Params";
version = "v1alpha2"; }
kind = "solarwinds"; {
description = ""; group = "config.istio.io";
module = definitions."istio_adapter_solarwinds_Params"; version = "v1alpha2";
}{ kind = "redisquota";
group = "config.istio.io"; description = "";
version = "v1alpha2"; module = definitions."istio_adapter_redisquota_Params";
kind = "stackdriver"; }
description = ""; {
module = definitions."istio_adapter_stackdriver_Params"; group = "config.istio.io";
}{ version = "v1alpha2";
group = "config.istio.io"; kind = "reportnothing";
version = "v1alpha2"; description = "";
kind = "statsd"; module = definitions."istio_mixer_reportnothing_InstanceMsg";
description = ""; }
module = definitions."istio_adapter_statsd_Params"; {
}{ group = "config.istio.io";
group = "config.istio.io"; version = "v1alpha2";
version = "v1alpha2"; kind = "servicecontrol";
kind = "stdio"; description = "";
description = ""; module = definitions."istio_adapter_servicecontrol_Params";
module = definitions."istio_adapter_stdio_Params"; }
}{ {
group = "config.istio.io"; group = "config.istio.io";
version = "v1alpha2"; version = "v1alpha2";
kind = "tracespan"; kind = "signalfx";
description = ""; description = "";
module = definitions."istio_mixer_tracespan_InstanceMsg"; module = definitions."istio_adapter_signalfx_Params";
} }
{
group = "config.istio.io";
version = "v1alpha2";
kind = "solarwinds";
description = "";
module = definitions."istio_adapter_solarwinds_Params";
}
{
group = "config.istio.io";
version = "v1alpha2";
kind = "stackdriver";
description = "";
module = definitions."istio_adapter_stackdriver_Params";
}
{
group = "config.istio.io";
version = "v1alpha2";
kind = "statsd";
description = "";
module = definitions."istio_adapter_statsd_Params";
}
{
group = "config.istio.io";
version = "v1alpha2";
kind = "stdio";
description = "";
module = definitions."istio_adapter_stdio_Params";
}
{
group = "config.istio.io";
version = "v1alpha2";
kind = "tracespan";
description = "";
module = definitions."istio_mixer_tracespan_InstanceMsg";
}
]; ];
} }

View file

@ -4,22 +4,24 @@ stdenv.mkDerivation rec {
pname = "k3s-airgap-images"; pname = "k3s-airgap-images";
version = k3s.version; version = k3s.version;
src = let src =
throwError = throw "Unsupported system ${stdenv.hostPlatform.system}"; let
in { throwError = throw "Unsupported system ${stdenv.hostPlatform.system}";
x86_64-linux = fetchurl { in
url = "https://github.com/rancher/k3s/releases/download/v${version}/k3s-airgap-images-amd64.tar"; {
sha256 = "sha256-6kQmlpNV+4cU1Kn5lyZhutXYK5qYdey0jubzYRRF3vA="; x86_64-linux = fetchurl {
}; url = "https://github.com/rancher/k3s/releases/download/v${version}/k3s-airgap-images-amd64.tar";
aarch64-linux = fetchurl { sha256 = "sha256-6kQmlpNV+4cU1Kn5lyZhutXYK5qYdey0jubzYRRF3vA=";
url = "https://github.com/rancher/k3s/releases/download/v${version}/k3s-airgap-images-arm64.tar"; };
sha256 = "sha256-OlqqdAmBN+azT0kfjZ/Bd0CFbbW5hTg9/8T9U05N0zE="; aarch64-linux = fetchurl {
}; url = "https://github.com/rancher/k3s/releases/download/v${version}/k3s-airgap-images-arm64.tar";
armv7l-linux = fetchurl { sha256 = "sha256-OlqqdAmBN+azT0kfjZ/Bd0CFbbW5hTg9/8T9U05N0zE=";
url = "https://github.com/rancher/k3s/releases/download/v${version}/k3s-airgap-images-arm.tar"; };
sha256 = "sha256-j/ARBtHDnfRk/7BpOvavoHe7L5dmsCZe5+wuZ5t4V/k="; armv7l-linux = fetchurl {
}; url = "https://github.com/rancher/k3s/releases/download/v${version}/k3s-airgap-images-arm.tar";
}.${stdenv.hostPlatform.system} or throwError; sha256 = "sha256-j/ARBtHDnfRk/7BpOvavoHe7L5dmsCZe5+wuZ5t4V/k=";
};
}.${stdenv.hostPlatform.system} or throwError;
preferLocalBuild = true; preferLocalBuild = true;
dontUnpack = true; dontUnpack = true;

View file

@ -3,19 +3,20 @@
{ options, config, lib, pkgs, k8s, ... }: { options, config, lib, pkgs, k8s, ... }:
with lib; with lib;
let 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 (default: catAttrs "default" (filter
(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) &&
) cfg.api.defaults); (default.kind == null || default.kind == kind)
)
cfg.api.defaults);
moduleToAttrs = value: moduleToAttrs = value:
if isAttrs value if isAttrs value
@ -34,7 +35,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)";
@ -69,45 +70,47 @@ 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 = { {
group = mkOption { options = {
description = "Resource type group"; group = mkOption {
type = types.str; description = "Resource type group";
}; type = types.str;
};
version = mkOption { version = mkOption {
description = "Resoruce type version"; description = "Resoruce type version";
type = types.str; type = types.str;
}; };
kind = mkOption { kind = mkOption {
description = "Resource type kind"; description = "Resource type kind";
type = types.str; type = types.str;
}; };
name = mkOption { name = mkOption {
description = "Resource type name"; description = "Resource type name";
type = types.nullOr types.str; type = types.nullOr types.str;
}; };
attrName = mkOption { attrName = mkOption {
description = "Name of the nixified attribute"; description = "Name of the nixified attribute";
type = types.str; type = types.str;
};
}; };
}; }
} gvkKeyFn; gvkKeyFn;
default = {}; default = { };
}; };
}; };
@ -120,24 +123,32 @@ 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: let compareVersions = ver1: ver2:
getVersion = v: substring 1 10 v; let
splittedVer1 = builtins.splitVersion (getVersion ver1); getVersion = v: substring 1 10 v;
splittedVer2 = builtins.splitVersion (getVersion ver2); splittedVer1 = builtins.splitVersion (getVersion ver1);
splittedVer2 = builtins.splitVersion (getVersion ver2);
v1 = if length splittedVer1 == 1 then "${getVersion ver1}prod" else getVersion ver1; v1 = if length splittedVer1 == 1 then "${getVersion ver1}prod" else getVersion ver1;
v2 = if length splittedVer2 == 1 then "${getVersion ver2}prod" else getVersion ver2; v2 = if length splittedVer2 == 1 then "${getVersion ver2}prod" else getVersion ver2;
in builtins.compareVersions v1 v2; in
builtins.compareVersions v1 v2;
customResourceTypesByAttrName = zipAttrs (mapAttrsToList (_: resourceType: { customResourceTypesByAttrName = zipAttrs (mapAttrsToList
${resourceType.attrName} = resourceType; (_: resourceType: {
}) cfg.customTypes); ${resourceType.attrName} = resourceType;
})
cfg.customTypes);
customResourceTypesByAttrNameSortByVersion = mapAttrs (_: resourceTypes: customResourceTypesByAttrNameSortByVersion = mapAttrs
reverseList (sort (r1: r2: (_: resourceTypes:
compareVersions r1.version r2.version > 0 reverseList (sort
) resourceTypes) (r1: r2:
) customResourceTypesByAttrName; compareVersions r1.version r2.version > 0
)
resourceTypes)
)
customResourceTypesByAttrName;
latestCustomResourceTypes = latestCustomResourceTypes =
mapAttrsToList (_: resources: last resources) customResourceTypesByAttrNameSortByVersion; mapAttrsToList (_: resources: last resources) customResourceTypesByAttrNameSortByVersion;
@ -163,7 +174,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 = { };
}; };
}; };
@ -174,34 +185,43 @@ let
}; };
}; };
customResourceOptions = (mapAttrsToList (_: ct: {config, ...}: let customResourceOptions = (mapAttrsToList
module = customResourceModuleForType config ct; (_: ct: { config, ... }:
in { let
options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption { module = customResourceModuleForType config ct;
description = ct.description; in
type = types.attrsOf (types.submodule module); {
default = {}; options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption {
}; description = ct.description;
}) cfg.customTypes) ++ (map (ct: { options, config, ... }: let type = types.attrsOf (types.submodule module);
module = customResourceModuleForType config ct; default = { };
in { };
options.resources.${ct.attrName} = mkOption { })
description = ct.description; cfg.customTypes) ++ (map
type = types.attrsOf (types.submodule module); (ct: { options, config, ... }:
default = {}; let
}; module = customResourceModuleForType config ct;
in
{
options.resources.${ct.attrName} = mkOption {
description = ct.description;
type = types.attrsOf (types.submodule module);
default = { };
};
config.resources.${ct.group}.${ct.version}.${ct.kind} = config.resources.${ct.group}.${ct.version}.${ct.kind} =
mkAliasDefinitions options.resources.${ct.attrName}; mkAliasDefinitions options.resources.${ct.attrName};
}) latestCustomResourceTypes); })
latestCustomResourceTypes);
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";
}; };
@ -227,76 +247,80 @@ in {
apiOptions apiOptions
] ++ customResourceOptions; ] ++ 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 = { {
group = mkOption { options = {
description = "Custom type group"; group = mkOption {
type = types.str; description = "Custom type group";
}; type = types.str;
};
version = mkOption { version = mkOption {
description = "Custom type version"; description = "Custom type version";
type = types.str; type = types.str;
}; };
kind = mkOption { kind = mkOption {
description = "Custom type kind"; description = "Custom type kind";
type = types.str; type = types.str;
}; };
name = mkOption { name = mkOption {
description = "Custom type resource name"; description = "Custom type resource name";
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
}; };
attrName = mkOption { attrName = mkOption {
description = "Name of the nixified attribute"; description = "Name of the nixified attribute";
type = types.str; type = types.str;
}; };
description = mkOption { description = mkOption {
description = "Custom type description"; description = "Custom type description";
type = types.str; type = types.str;
default = ""; default = "";
}; };
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 (r1: r2: apply = items: sort
if elem r1.kind cfg.resourceOrder && elem r2.kind cfg.resourceOrder (r1: r2:
then indexOf cfg.resourceOrder r1.kind < indexOf cfg.resourceOrder r2.kind if elem r1.kind cfg.resourceOrder && elem r2.kind cfg.resourceOrder
else if elem r1.kind cfg.resourceOrder then true else false then indexOf cfg.resourceOrder r1.kind < indexOf cfg.resourceOrder r2.kind
) (unique items); else if elem r1.kind cfg.resourceOrder then true else false
default = []; )
(unique items);
default = [ ];
}; };
generated = mkOption { generated = mkOption {
@ -321,41 +345,44 @@ in {
# module propagation options # module propagation options
_m.propagate = [{ _m.propagate = [{
features = ["k8s"]; features = [ "k8s" ];
module = { config, ... }: { module = { config, ... }: {
# propagate kubernetes version and namespace # propagate kubernetes version and namespace
kubernetes.version = mkDefault cfg.version; kubernetes.version = mkDefault cfg.version;
kubernetes.namespace = mkDefault cfg.namespace; kubernetes.namespace = mkDefault cfg.namespace;
}; };
} { }
features = ["k8s" "submodule"]; {
module = { config, ... }: { features = [ "k8s" "submodule" ];
# set module defaults module = { config, ... }: {
kubernetes.api.defaults = ( # set module defaults
# propagate defaults if default propagation is enabled kubernetes.api.defaults = (
(filter (default: default.propagate) cfg.api.defaults) ++ # propagate defaults if default propagation is enabled
(filter (default: default.propagate) cfg.api.defaults) ++
[ [
# set module name and version for all kuberentes resources # set module name and version for all kuberentes resources
{ {
default.metadata.labels = { default.metadata.labels = {
"kubenix/module-name" = config.submodule.name; "kubenix/module-name" = config.submodule.name;
"kubenix/module-version" = config.submodule.version; "kubenix/module-version" = config.submodule.version;
}; };
} }
] ]
); );
}; };
}]; }];
# expose k8s helper methods as module argument # expose k8s helper methods as module argument
_module.args.k8s = import ../lib/k8s.nix { inherit lib; }; _module.args.k8s = import ../lib/k8s.nix { inherit lib; };
kubernetes.api = mkMerge ([{ kubernetes.api = mkMerge ([{
# register custom types # register custom types
types = mapAttrsToList (_: cr: { types = mapAttrsToList
inherit (cr) name group version kind attrName; (_: cr: {
}) cfg.customTypes; inherit (cr) name group version kind attrName;
})
cfg.customTypes;
defaults = [{ defaults = [{
default = { default = {
@ -373,24 +400,30 @@ in {
}] ++ }] ++
# import of yaml files # import of yaml files
(map (i: let (map
# load yaml file (i:
object = loadYAML i; let
groupVersion = splitString "/" object.apiVersion; # load yaml file
name = object.metadata.name; object = loadYAML i;
version = last groupVersion; groupVersion = splitString "/" object.apiVersion;
group = name = object.metadata.name;
if version == (head groupVersion) version = last groupVersion;
then "core" else head groupVersion; group =
kind = object.kind; if version == (head groupVersion)
in { then "core" else head groupVersion;
resources.${group}.${version}.${kind}.${name} = object; kind = object.kind;
}) cfg.imports)); in
{
resources.${group}.${version}.${kind}.${name} = object;
})
cfg.imports));
kubernetes.objects = flatten (mapAttrsToList (_: type: kubernetes.objects = flatten (mapAttrsToList
mapAttrsToList (name: resource: moduleToAttrs resource) (_: type:
cfg.api.resources.${type.group}.${type.version}.${type.kind} mapAttrsToList (name: resource: moduleToAttrs resource)
) cfg.api.types); cfg.api.resources.${type.group}.${type.version}.${type.kind}
)
cfg.api.types);
kubernetes.generated = k8s.mkHashedList { kubernetes.generated = k8s.mkHashedList {
items = config.kubernetes.objects; items = config.kubernetes.objects;

View file

@ -3,7 +3,6 @@
{ options, config, pkgs, lib, kubenix, ... }: { options, config, pkgs, lib, kubenix, ... }:
with lib; with lib;
let let
parentModule = module; parentModule = module;
globalConfig = config; globalConfig = config;
@ -29,46 +28,50 @@ let
else if isAttrs mkOptionDefault.module then module // attrs else if isAttrs mkOptionDefault.module then module // attrs
else module else module
); );
in [ in
[
{ {
_module.args.name = module.name; _module.args.name = module.name;
_module.args.module = module; _module.args.module = module;
} }
./k8s.nix ./k8s.nix
./legacy.nix ./legacy.nix
(injectModuleAttrs moduleDefinition.module {_file = file;}) (injectModuleAttrs moduleDefinition.module { _file = file; })
{ {
config.kubernetes.namespace = mkOptionDefault module.namespace; config.kubernetes.namespace = mkOptionDefault module.namespace;
config.kubenix.project = mkOptionDefault config.kubenix.project; config.kubenix.project = mkOptionDefault config.kubenix.project;
} }
] ++ config.kubernetes.defaultModuleConfiguration.all ] ++ config.kubernetes.defaultModuleConfiguration.all
++ (optionals (hasAttr moduleDefinition.name config.kubernetes.defaultModuleConfiguration) ++ (optionals (hasAttr moduleDefinition.name config.kubernetes.defaultModuleConfiguration)
config.kubernetes.defaultModuleConfiguration.${moduleDefinition.name}); config.kubernetes.defaultModuleConfiguration.${moduleDefinition.name});
# prefix kubernetes objects with ${serviceName}, this magic was removed in new kubenix # prefix kubernetes objects with ${serviceName}, this magic was removed in new kubenix
prefixResources = resources: serviceName: prefixResources = resources: serviceName:
mapAttrs' (name: resource: nameValuePair "${serviceName}-${name}" resource) resources; mapAttrs' (name: resource: nameValuePair "${serviceName}-${name}" resource) resources;
# TODO: rewrite using mkOptionType # TODO: rewrite using mkOptionType
defaultModuleConfigurationOptions = mapAttrs (name: moduleDefinition: mkOption { defaultModuleConfigurationOptions = mapAttrs
description = "Module default configuration for ${name} module"; (name: moduleDefinition: mkOption {
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified); description = "Module default configuration for ${name} module";
default = []; type = types.coercedTo types.unspecified (value: [ value ]) (types.listOf types.unspecified);
apply = filter (v: v!=[]); default = [ ];
}) config.kubernetes.moduleDefinitions; apply = filter (v: v != [ ]);
})
config.kubernetes.moduleDefinitions;
getModuleDefinition = name: getModuleDefinition = name:
if hasAttr name config.kubernetes.moduleDefinitions if hasAttr name config.kubernetes.moduleDefinitions
then config.kubernetes.moduleDefinitions.${name} then config.kubernetes.moduleDefinitions.${name}
else throw ''requested kubernetes moduleDefinition with name "${name}" does not exist''; else throw ''requested kubernetes moduleDefinition with name "${name}" does not exist'';
in { in
{
imports = [ ./k8s.nix ]; imports = [ ./k8s.nix ];
options.kubernetes.moduleDefinitions = mkOption { options.kubernetes.moduleDefinitions = mkOption {
description = "Legacy kubenix attribute set of module definitions"; description = "Legacy kubenix attribute set of module definitions";
default = {}; default = { };
type = types.attrsOf (types.submodule ({name, ...}: { type = types.attrsOf (types.submodule ({ name, ... }: {
options = { options = {
name = mkOption { name = mkOption {
description = "Module definition name"; description = "Module definition name";
@ -101,19 +104,19 @@ in {
options = defaultModuleConfigurationOptions // { options = defaultModuleConfigurationOptions // {
all = mkOption { all = mkOption {
description = "Module default configuration for all modules"; description = "Module default configuration for all modules";
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified); type = types.coercedTo types.unspecified (value: [ value ]) (types.listOf types.unspecified);
default = []; default = [ ];
apply = filter (v: v != []); apply = filter (v: v != [ ]);
}; };
}; };
}; };
default = {}; default = { };
}; };
options.kubernetes.modules = mkOption { options.kubernetes.modules = mkOption {
description = "Legacy kubenix attribute set of modules"; description = "Legacy kubenix attribute set of modules";
default = {}; default = { };
type = types.attrsOf (types.submodule ({config, name, ...}: { type = types.attrsOf (types.submodule ({ config, name, ... }: {
options = { options = {
name = mkOption { name = mkOption {
description = "Module name"; description = "Module name";
@ -130,17 +133,19 @@ in {
labels = mkOption { labels = mkOption {
description = "Attribute set of module lables"; description = "Attribute set of module lables";
type = types.attrsOf types.str; type = types.attrsOf types.str;
default = {}; default = { };
}; };
configuration = mkOption { configuration = mkOption {
description = "Module configuration"; description = "Module configuration";
type = submoduleWithSpecialArgs { type = submoduleWithSpecialArgs
imports = mkModuleOptions (getModuleDefinition config.module) config; {
} { imports = mkModuleOptions (getModuleDefinition config.module) config;
inherit kubenix; }
}; {
default = {}; inherit kubenix;
};
default = { };
}; };
module = mkOption { module = mkOption {
@ -153,9 +158,9 @@ in {
}; };
options.kubernetes.defaults = mkOption { options.kubernetes.defaults = mkOption {
type = types.attrsOf (types.coercedTo types.attrs (value: [value]) (types.listOf types.attrs)); type = types.attrsOf (types.coercedTo types.attrs (value: [ value ]) (types.listOf types.attrs));
description = "Legacy kubenix kubernetes defaults."; description = "Legacy kubenix kubernetes defaults.";
default = {}; default = { };
}; };
# for back compatibility with kubernetes.customResources # for back compatibility with kubernetes.customResources
@ -163,50 +168,63 @@ in {
config = { config = {
kubernetes = mkMerge [{ kubernetes = mkMerge [{
api.defaults = mapAttrsToList (attrName: default: let api.defaults = mapAttrsToList
type = head (mapAttrsToList (_: v: v) (filterAttrs (_: type: type.attrName == attrName) config.kubernetes.api.types)); (attrName: default:
in { let
default = { imports = default; }; type = head (mapAttrsToList (_: v: v) (filterAttrs (_: type: type.attrName == attrName) config.kubernetes.api.types));
} // (if (attrName == "all") then {} else { in
resource = type.name; {
})) config.kubernetes.defaults; default = { imports = default; };
} // (if (attrName == "all") then { } else {
resource = type.name;
}))
config.kubernetes.defaults;
resources = mkMerge ( resources = mkMerge (
mapAttrsToList (name: module: mapAttrsToList
mapAttrs' (_: type: let (name: module:
moduleDefinition = getModuleDefinition module.module; mapAttrs'
(_: type:
let
moduleDefinition = getModuleDefinition module.module;
moduleResources = module.configuration.kubernetes.api.resources.${type.attrName} or {}; moduleResources = module.configuration.kubernetes.api.resources.${type.attrName} or { };
moduleConfig = moduleConfig =
if moduleDefinition.prefixResources && type.kind != "CustomResourceDefinition" if moduleDefinition.prefixResources && type.kind != "CustomResourceDefinition"
then prefixResources (moduleToAttrs moduleResources) name then prefixResources (moduleToAttrs moduleResources) name
else moduleToAttrs moduleResources; else moduleToAttrs moduleResources;
in nameValuePair type.attrName in
(if moduleDefinition.assignAsDefaults nameValuePair type.attrName
then mkAllDefault moduleConfig 1000 (if moduleDefinition.assignAsDefaults
else moduleConfig) then mkAllDefault moduleConfig 1000
) module.configuration.kubernetes.api.types else moduleConfig)
) config.kubernetes.modules )
module.configuration.kubernetes.api.types
)
config.kubernetes.modules
); );
# custom types created from customResourceDefinitions # custom types created from customResourceDefinitions
customTypes = customTypes =
mapAttrsToList (name: crd: { mapAttrsToList
group = crd.spec.group; (name: crd: {
version = crd.spec.version; group = crd.spec.group;
kind = crd.spec.names.kind; version = crd.spec.version;
name = crd.spec.names.plural; kind = crd.spec.names.kind;
attrName = mkOptionDefault name; name = crd.spec.names.plural;
}) (config.kubernetes.resources.customResourceDefinitions or {}); attrName = mkOptionDefault name;
})
(config.kubernetes.resources.customResourceDefinitions or { });
defaultModuleConfiguration.all = { defaultModuleConfiguration.all = {
_file = head options.kubernetes.defaultModuleConfiguration.files; _file = head options.kubernetes.defaultModuleConfiguration.files;
config.kubernetes.version = mkDefault config.kubernetes.version; config.kubernetes.version = mkDefault config.kubernetes.version;
config.kubernetes.moduleDefinitions = config.kubernetes.moduleDefinitions; config.kubernetes.moduleDefinitions = config.kubernetes.moduleDefinitions;
}; };
} { }
resources = mkAliasDefinitions options.kubernetes.customResources; {
}]; resources = mkAliasDefinitions options.kubernetes.customResources;
}];
}; };
} }

View file

@ -26,24 +26,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,7 +1,6 @@
{ config, options, kubenix, pkgs, lib, ... }: { config, options, kubenix, pkgs, lib, ... }:
with lib; with lib;
let let
cfg = config.submodules; cfg = config.submodules;
parentConfig = config; parentConfig = config;
@ -13,67 +12,82 @@ let
else requiredVersion == version else requiredVersion == version
else true; else true;
getDefaults = {name, version, tags, features}: getDefaults = { name, version, tags, features }:
catAttrs "default" (filter (submoduleDefault: catAttrs "default" (filter
(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 = cfg.specialArgs // {
parentConfig = config; parentConfig = config;
}; };
findSubmodule = {name, version ? null, latest ? true}: let findSubmodule = { name, version ? null, latest ? true }:
matchingSubmodules = filter (el: let
el.definition.name == name && matchingSubmodules = filter
(matchesVersion version el.definition.version) (el:
) cfg.imports; el.definition.name == name &&
(matchesVersion version el.definition.version)
)
cfg.imports;
versionSortedSubmodules = sort (s1: s2: versionSortedSubmodules = sort
if builtins.compareVersions s1.definition.version s2.definition.version > 0 (s1: s2:
then true else false if builtins.compareVersions s1.definition.version s2.definition.version > 0
) matchingSubmodules; then true else false
)
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 throw "No module found ${name}/${if version == null then "latest" else version}"
else head versionSortedSubmodules; else head versionSortedSubmodules;
in matchingModule; in
matchingModule;
passthruConfig = mapAttrsToList (name: opt: { passthruConfig = mapAttrsToList
${name} = mkMerge (mapAttrsToList (_: inst: (name: opt: {
if inst.passthru.enable ${name} = mkMerge (mapAttrsToList
then inst.config.submodule.passthru.${name} or {} (_: inst:
else {} if inst.passthru.enable
) config.submodules.instances); then inst.config.submodule.passthru.${name} or { }
else { }
)
config.submodules.instances);
_module.args = mkMerge (mapAttrsToList (_: inst: _module.args = mkMerge (mapAttrsToList
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 { }
) config.submodules.instances); else { }
}) (removeAttrs options ["_definedNames" "_module" "_m" "submodules"]); )
in { config.submodules.instances);
})
(removeAttrs options [ "_definedNames" "_module" "_m" "submodules" ]);
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";
@ -93,23 +107,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 {
@ -123,139 +137,147 @@ in {
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 ({ name, config, ... }:
evaledSubmodule' = evalModules { let
inherit specialArgs; evaledSubmodule' = evalModules {
modules = config.modules ++ [ ./base.nix ]; inherit specialArgs;
check = false; modules = config.modules ++ [ ./base.nix ];
}; check = false;
evaledSubmodule =
if (!(elem "submodule" evaledSubmodule'.config._m.features))
then throw "no submodule defined"
else evaledSubmodule';
in {
options = {
module = mkOption {
description = "Module defining submodule";
type = types.unspecified;
}; };
modules = mkOption { evaledSubmodule =
description = "List of modules defining submodule"; if (!(elem "submodule" evaledSubmodule'.config._m.features))
type = types.listOf types.unspecified; then throw "no submodule defined"
default = [config.module]; else evaledSubmodule';
in
{
options = {
module = mkOption {
description = "Module defining submodule";
type = types.unspecified;
};
modules = mkOption {
description = "List of modules defining submodule";
type = types.listOf types.unspecified;
default = [ config.module ];
};
features = mkOption {
description = "List of features exposed by submodule";
type = types.listOf types.str;
};
definition = mkOption {
description = "Submodule definition";
type = types.attrs;
};
exportAs = mkOption {
description = "Name under which to register exports";
type = types.nullOr types.str;
default = null;
};
}; };
features = mkOption { config = {
description = "List of features exposed by submodule"; definition = {
type = types.listOf types.str; inherit (evaledSubmodule.config.submodule) name description version tags exports;
}; };
definition = mkOption { features = evaledSubmodule.config._m.features;
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, ...}: let type = types.attrsOf (types.submodule ({ name, config, options, ... }:
# submodule associated with let
submodule = findSubmodule { # submodule associated with
name = config.submodule; submodule = findSubmodule {
version = config.version; name = config.submodule;
}; version = config.version;
# definition of a submodule
submoduleDefinition = submodule.definition;
# submodule defaults
defaults = getDefaults {
name = submoduleDefinition.name;
version = submoduleDefinition.version;
tags = submoduleDefinition.tags;
features = submodule.features;
};
in {
options = {
name = mkOption {
description = "Submodule instance name";
type = types.str;
default = name;
}; };
submodule = mkOption { # definition of a submodule
description = "Name of the submodule to use"; submoduleDefinition = submodule.definition;
type = types.str;
default = name;
};
version = mkOption { # submodule defaults
description = '' defaults = getDefaults {
Version of submodule to use, if version starts with "~" it is name = submoduleDefinition.name;
threated as regex pattern for example "~1.0.*" version = submoduleDefinition.version;
''; tags = submoduleDefinition.tags;
type = types.nullOr types.str; features = submodule.features;
default = null;
}; };
in
{
options = {
name = mkOption {
description = "Submodule instance name";
type = types.str;
default = name;
};
passthru.enable = mkOption { submodule = mkOption {
description = "Whether to passthru submodule resources"; description = "Name of the submodule to use";
type = types.bool; type = types.str;
default = true; default = name;
}; };
config = mkOption { version = mkOption {
description = "Submodule instance ${config.name} for ${submoduleDefinition.name}:${submoduleDefinition.version} config"; description = ''
type = submoduleWithSpecialArgs ({...}: { Version of submodule to use, if version starts with "~" it is
imports = submodule.modules ++ defaults ++ [ ./base.nix ]; threated as regex pattern for example "~1.0.*"
_module.args.pkgs = pkgs; '';
_module.args.name = config.name; type = types.nullOr types.str;
_module.args.submodule = config; default = null;
submodule.args = mkAliasDefinitions options.args; };
}) specialArgs;
default = {};
};
args = mkOption { passthru.enable = mkOption {
description = "Submodule arguments (alias of config.submodule.args)"; 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 (submodule: { _module.args = mkMerge (map
${submodule.exportAs} = submodule.definition.exports; (submodule: {
}) (filter (submodule: submodule.exportAs != null) cfg.imports)); ${submodule.exportAs} = submodule.definition.exports;
})
(filter (submodule: submodule.exportAs != null) cfg.imports));
_m.features = ["submodules"]; _m.features = [ "submodules" ];
submodules.specialArgs.kubenix = kubenix; submodules.specialArgs.kubenix = kubenix;
@ -267,17 +289,19 @@ in {
}; };
}] }]
(map (propagate: { (map
features = propagate.features; (propagate: {
default = propagate.module; features = propagate.features;
}) config._m.propagate) default = propagate.module;
})
config._m.propagate)
]; ];
} }
(mkIf cfg.propagate.enable { (mkIf cfg.propagate.enable {
# if propagate is enabled and submodule has submodules included propagage defaults and imports # if propagate is enabled and submodule has submodules included propagage defaults and imports
submodules.defaults = [{ submodules.defaults = [{
features = ["submodules"]; features = [ "submodules" ];
default = { default = {
submodules = { submodules = {
defaults = cfg.defaults; defaults = cfg.defaults;

View file

@ -1,7 +1,6 @@
{ nixosPath, config, pkgs, lib, kubenix, ... }: { nixosPath, config, pkgs, lib, kubenix, ... }:
with lib; with lib;
let let
cfg = config.testing; cfg = config.testing;
@ -18,7 +17,8 @@ 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
@ -40,30 +40,32 @@ in {
defaults = mkOption { defaults = mkOption {
description = "List of defaults to apply to tests"; description = "List of defaults 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 defaults"; description = "List of features that test 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 test"; description = "Default to apply to test";
type = types.unspecified; type = types.unspecified;
default = {}; 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 (module: { type = types.listOf (types.coercedTo types.path
inherit module; (module: {
}) (types.submodule testModule)); inherit module;
})
(types.submodule testModule));
apply = tests: filter isTestEnabled tests; apply = tests: filter isTestEnabled tests;
}; };
@ -82,7 +84,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,16 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
with lib; with lib;
with import ../../lib/docker.nix { inherit lib pkgs; }; with import ../../lib/docker.nix { inherit lib pkgs; };
let let
testing = config.testing; testing = config.testing;
allImages = flatten (map (t: t.evaled.config.docker.export or []) testing.tests); allImages = 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";
@ -38,7 +38,7 @@ in {
}; };
config.testing.defaults = [{ config.testing.defaults = [{
features = ["docker"]; features = [ "docker" ];
default = { default = {
docker.registry.url = cfg.registryUrl; docker.registry.url = cfg.registryUrl;
}; };

View file

@ -1,12 +1,11 @@
{ lib, config, pkgs, ... }: { lib, config, pkgs, ... }:
with lib; with lib;
let let
testing = config.testing; testing = config.testing;
cfg = testing.driver.kubetest; cfg = testing.driver.kubetest;
kubetest = import ./kubetestdrv.nix {inherit pkgs;}; kubetest = import ./kubetestdrv.nix { inherit pkgs; };
pythonEnv = pkgs.python38.withPackages (ps: with ps; [ pythonEnv = pkgs.python38.withPackages (ps: with ps; [
pytest pytest
@ -16,18 +15,20 @@ let
toTestScript = t: toTestScript = t:
if isString t.script if isString t.script
then pkgs.writeText "${t.name}.py" '' then
${cfg.defaultHeader} pkgs.writeText "${t.name}.py" ''
${t.script} ${cfg.defaultHeader}
'' ${t.script}
''
else t.script; else t.script;
tests = pkgs.linkFarm "${testing.name}-tests" ( tests = pkgs.linkFarm "${testing.name}-tests" (
map (t: { map
path = toTestScript t; (t: {
name = "${t.name}_test.py"; path = toTestScript t;
}) name = "${t.name}_test.py";
( filter (t: t.script != null) testing.tests ) })
(filter (t: t.script != null) testing.tests)
); );
testScript = pkgs.writeScript "test-${testing.name}.sh" '' testScript = pkgs.writeScript "test-${testing.name}.sh" ''
@ -35,7 +36,8 @@ let
${pythonEnv}/bin/pytest -p no:cacheprovider ${tests} $@ ${pythonEnv}/bin/pytest -p no:cacheprovider ${tests} $@
''; '';
in { in
{
options.testing.driver.kubetest = { options.testing.driver.kubetest = {
defaultHeader = mkOption { defaultHeader = mkOption {
type = types.lines; type = types.lines;
@ -48,7 +50,7 @@ in {
extraPackages = mkOption { extraPackages = mkOption {
type = types.listOf types.package; type = types.listOf types.package;
description = "Extra packages to pass to tests"; description = "Extra packages to pass to tests";
default = []; default = [ ];
}; };
}; };

View file

@ -1,7 +1,6 @@
{ lib, config, testing, kubenix, ... }: { lib, config, testing, kubenix, ... }:
with lib; with lib;
let let
modules = [ modules = [
# testing module # testing module
@ -36,25 +35,29 @@ let
# defaults that can be applied on tests # defaults that can be applied on tests
defaults = defaults =
filter (d: filter
(intersectLists d.features testFeatures) == d.features || (d:
(length d.features) == 0 (intersectLists d.features testFeatures) == d.features ||
) testing.defaults; (length d.features) == 0
)
testing.defaults;
# add default modules to all modules # add default modules to all modules
modulesWithDefaults = modules ++ (map (d: d.default) defaults); modulesWithDefaults = modules ++ (map (d: d.default) defaults);
# evaled test # evaled test
evaled = let evaled =
evaled' = kubenix.evalModules { let
modules = modulesWithDefaults; evaled' = kubenix.evalModules {
}; modules = modulesWithDefaults;
in };
in
if testing.throwError then evaled' if testing.throwError 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";
@ -97,7 +100,7 @@ in {
description = "Test result"; description = "Test result";
type = types.unspecified; type = types.unspecified;
internal = true; internal = true;
default = []; default = [ ];
}; };
script = mkOption { script = mkOption {

View file

@ -1,7 +1,6 @@
{ lib, config, pkgs, ... }: { lib, config, pkgs, ... }:
with lib; with lib;
let let
testing = config.testing; testing = config.testing;
@ -32,7 +31,8 @@ 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

@ -9,7 +9,6 @@
}: }:
with lib; with lib;
let let
testing = config.testing; testing = config.testing;
kubeconfig = "/etc/${config.services.kubernetes.pki.etcClusterAdminKubeconfig}"; kubeconfig = "/etc/${config.services.kubernetes.pki.etcClusterAdminKubeconfig}";

View file

@ -1,11 +1,11 @@
{ lib, config, pkgs, ... }: { lib, config, pkgs, ... }:
with lib; with lib;
let let
cfg = config.test; cfg = config.test;
in { in
{
options.test = { options.test = {
name = mkOption { name = mkOption {
description = "Test name"; description = "Test name";
@ -38,8 +38,8 @@ 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

View file

@ -27,4 +27,3 @@ stdenv.mkDerivation {
homepage = "https://github.com/kubernetes/kubectl"; homepage = "https://github.com/kubernetes/kubectl";
}; };
} }

View file

@ -8,7 +8,6 @@ in
}: }:
with lib; with lib;
let let
kubenix = import ./. { inherit pkgs; }; kubenix = import ./. { inherit pkgs; };

View file

@ -1,4 +1,4 @@
{ pkgs ? import <nixpkgs> {} { pkgs ? import <nixpkgs> { }
, lib ? pkgs.lib , lib ? pkgs.lib
, kubenix ? import ../. { inherit pkgs lib; } , kubenix ? import ../. { inherit pkgs lib; }
@ -7,12 +7,12 @@
, k8sVersion ? "1.21" , k8sVersion ? "1.21"
, registryUrl ? throw "Registry url not defined" , registryUrl ? throw "Registry url not defined"
, throwError ? true # whether any testing error should throw an error , throwError ? true # whether any testing error should throw an error
, enabledTests ? null }: , enabledTests ? null
}:
with lib; with lib;
let let
images = pkgs.callPackage ./images.nix {}; images = pkgs.callPackage ./images.nix { };
config = (kubenix.evalModules { config = (kubenix.evalModules {
modules = [ modules = [
@ -34,7 +34,7 @@ let
#./legacy/k8s.nix #./legacy/k8s.nix
#./legacy/crd.nix #./legacy/crd.nix
#./legacy/modules.nix #./legacy/modules.nix
./helm/simple.nix # ./helm/simple.nix
# ./istio/bookinfo.nix # infinite recursion # ./istio/bookinfo.nix # infinite recursion
./submodules/simple.nix ./submodules/simple.nix
./submodules/defaults.nix ./submodules/defaults.nix
@ -48,7 +48,7 @@ let
docker.registryUrl = registryUrl; docker.registryUrl = registryUrl;
defaults = [ defaults = [
{ {
features = ["k8s"]; features = [ "k8s" ];
default = { default = {
kubernetes.version = k8sVersion; kubernetes.version = k8sVersion;
}; };
@ -64,4 +64,5 @@ let
inherit kubenix nixosPath; inherit kubenix nixosPath;
}; };
}).config; }).config;
in pkgs.recurseIntoAttrs config.testing in
pkgs.recurseIntoAttrs config.testing

View file

@ -3,7 +3,6 @@
with lib; with lib;
with kubenix.lib; with kubenix.lib;
with pkgs.dockerTools; with pkgs.dockerTools;
let let
corev1 = config.kubernetes.api.resources.core.v1; corev1 = config.kubernetes.api.resources.core.v1;
appsv1 = config.kubernetes.api.resources.apps.v1; appsv1 = config.kubernetes.api.resources.apps.v1;
@ -31,7 +30,8 @@ let
finalImageName = "docker.io/bitnami/bitnami-shell"; finalImageName = "docker.io/bitnami/bitnami-shell";
finalImageTag = "10"; finalImageTag = "10";
}; };
in { in
{
imports = [ kubenix.modules.test kubenix.modules.helm kubenix.modules.k8s ]; imports = [ kubenix.modules.test kubenix.modules.helm kubenix.modules.k8s ];
test = { test = {
@ -43,15 +43,17 @@ in {
appsv1.StatefulSet ? "app-psql-postgresql-primary" && appsv1.StatefulSet ? "app-psql-postgresql-primary" &&
appsv1.StatefulSet ? "app-psql-postgresql-read" && appsv1.StatefulSet ? "app-psql-postgresql-read" &&
corev1.Secret ? "app-psql-postgresql" && corev1.Secret ? "app-psql-postgresql" &&
corev1.Service ? "app-psql-postgresql-headless" ; corev1.Service ? "app-psql-postgresql-headless";
} { }
message = "should have values passed"; {
assertion = appsv1.StatefulSet.app-psql-postgresql-read.spec.replicas == 2; message = "should have values passed";
} { assertion = appsv1.StatefulSet.app-psql-postgresql-read.spec.replicas == 2;
message = "should have namespace defined"; }
assertion = {
appsv1.StatefulSet.app-psql-postgresql-primary.metadata.namespace == "test"; message = "should have namespace defined";
}]; assertion =
appsv1.StatefulSet.app-psql-postgresql-primary.metadata.namespace == "test";
}];
testScript = '' testScript = ''
kube.wait_until_succeeds("docker load < ${postgresql}") kube.wait_until_succeeds("docker load < ${postgresql}")
kube.wait_until_succeeds("docker load < ${postgresqlExporter}") kube.wait_until_succeeds("docker load < ${postgresqlExporter}")
@ -61,7 +63,7 @@ in {
''; '';
}; };
kubernetes.resources.namespaces.test = {}; kubernetes.resources.namespaces.test = { };
kubernetes.helm.instances.app-psql = { kubernetes.helm.instances.app-psql = {
namespace = "test"; namespace = "test";

View file

@ -10,47 +10,49 @@ with lib;
contents = [ pkgs.bash pkgs.curl pkgs.cacert ]; contents = [ pkgs.bash pkgs.curl pkgs.cacert ];
}; };
nginx = let nginx =
nginxPort = "80"; let
nginxConf = pkgs.writeText "nginx.conf" '' nginxPort = "80";
user nginx nginx; nginxConf = pkgs.writeText "nginx.conf" ''
daemon off; user nginx nginx;
error_log /dev/stdout info; daemon off;
pid /dev/null; error_log /dev/stdout info;
events {} pid /dev/null;
http { events {}
access_log /dev/stdout; http {
server { access_log /dev/stdout;
listen ${nginxPort}; server {
index index.html; listen ${nginxPort};
location / { index index.html;
root ${nginxWebRoot}; location / {
root ${nginxWebRoot};
}
} }
} }
} '';
''; nginxWebRoot = pkgs.writeTextDir "index.html" ''
nginxWebRoot = pkgs.writeTextDir "index.html" '' <html><body><h1>Hello from NGINX</h1></body></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
mkdir -p var/cache/nginx mkdir -p var/cache/nginx
chmod u+w var/cache/nginx chmod u+w var/cache/nginx
mkdir -p var/log/nginx mkdir -p var/log/nginx
chmod u+w var/log/nginx chmod u+w var/log/nginx
echo "nginx:x:1000:1000::/:" > etc/passwd echo "nginx:x:1000:1000::/:" > etc/passwd
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

@ -18,25 +18,28 @@
name = "http"; name = "http";
protocol = "HTTP"; protocol = "HTTP";
}; };
hosts = ["*"]; hosts = [ "*" ];
}]; }];
}; };
}; };
VirtualService.bookinfo = { VirtualService.bookinfo = {
spec = { spec = {
hosts = ["*"]; hosts = [ "*" ];
gateways = ["bookinfo-gateway"]; gateways = [ "bookinfo-gateway" ];
http = [{ http = [{
match = [{ match = [{
uri.exact = "/productpage"; uri.exact = "/productpage";
} { }
uri.exact = "/login"; {
} { uri.exact = "/login";
uri.exact = "/logout"; }
} { {
uri.prefix = "/api/v1/products"; uri.exact = "/logout";
}]; }
{
uri.prefix = "/api/v1/products";
}];
route = [{ route = [{
destination = { destination = {
host = "productpage"; host = "productpage";
@ -63,13 +66,15 @@
subsets = [{ subsets = [{
name = "v1"; name = "v1";
labels.version = "v1"; labels.version = "v1";
} { }
name = "v2"; {
labels.version = "v2"; name = "v2";
} { labels.version = "v2";
name = "v3"; }
labels.version = "v3"; {
}]; name = "v3";
labels.version = "v3";
}];
}; };
}; };
@ -79,16 +84,19 @@
subsets = [{ subsets = [{
name = "v1"; name = "v1";
labels.version = "v1"; labels.version = "v1";
} { }
name = "v2"; {
labels.version = "v2"; name = "v2";
} { labels.version = "v2";
name = "v2-mysql"; }
labels.version = "v2-mysql"; {
} { name = "v2-mysql";
name = "v2-mysql-vm"; labels.version = "v2-mysql";
labels.version = "v2-mysql-vm"; }
}]; {
name = "v2-mysql-vm";
labels.version = "v2-mysql-vm";
}];
}; };
}; };
@ -98,10 +106,11 @@
subsets = [{ subsets = [{
name = "v1"; name = "v1";
labels.version = "v1"; labels.version = "v1";
} { }
name = "v2"; {
labels.version = "v2"; name = "v2";
}]; labels.version = "v2";
}];
}; };
}; };
}; };

View file

@ -1,10 +1,10 @@
{ config, lib, kubenix, pkgs, ... }: { config, lib, kubenix, pkgs, ... }:
with lib; with lib;
let let
latestCrontab = config.kubernetes.api.resources.cronTabs.latest; latestCrontab = config.kubernetes.api.resources.cronTabs.latest;
in { in
{
imports = with kubenix.modules; [ test k8s ]; imports = with kubenix.modules; [ test k8s ];
test = { test = {
@ -37,7 +37,8 @@ in {
}; };
}; };
} { }
{
group = "stable.example.com"; group = "stable.example.com";
version = "v2"; version = "v2";
kind = "CronTab"; kind = "CronTab";
@ -56,7 +57,8 @@ in {
}; };
}; };
}; };
} { }
{
group = "stable.example.com"; group = "stable.example.com";
version = "v3"; version = "v3";
kind = "CronTab"; kind = "CronTab";

View file

@ -1,11 +1,11 @@
{ config, lib, kubenix, ... }: { config, lib, kubenix, ... }:
with lib; with lib;
let 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 = {
@ -14,16 +14,18 @@ in {
assertions = [{ assertions = [{
message = "Should have label set with resource"; message = "Should have label set with resource";
assertion = pod1.metadata.labels.resource-label == "value"; assertion = pod1.metadata.labels.resource-label == "value";
} { }
message = "Should have default label set with group, version, kind"; {
assertion = pod1.metadata.labels.gvk-label == "value"; message = "Should have default label set with group, version, kind";
} { assertion = pod1.metadata.labels.gvk-label == "value";
message = "Should have conditional annotation set"; }
assertion = pod2.metadata.annotations.conditional-annotation == "value"; {
}]; 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 = { kubernetes.resources.pods.pod2 = {
metadata.labels.custom-label = "value"; metadata.labels.custom-label = "value";
@ -32,17 +34,19 @@ in {
kubernetes.api.defaults = [{ kubernetes.api.defaults = [{
resource = "pods"; resource = "pods";
default.metadata.labels.resource-label = "value"; default.metadata.labels.resource-label = "value";
} { }
group = "core"; {
kind = "Pod"; group = "core";
version = "v1"; kind = "Pod";
default.metadata.labels.gvk-label = "value"; version = "v1";
} { default.metadata.labels.gvk-label = "value";
resource = "pods"; }
default = { config, ... }: { {
config.metadata.annotations = mkIf (config.metadata.labels ? "custom-label") { resource = "pods";
conditional-annotation = "value"; default = { config, ... }: {
config.metadata.annotations = mkIf (config.metadata.labels ? "custom-label") {
conditional-annotation = "value";
};
}; };
}; }];
}];
} }

View file

@ -1,7 +1,6 @@
{ config, lib, pkgs, kubenix, images, ... }: { config, lib, pkgs, kubenix, images, ... }:
with lib; with lib;
let let
cfg = config.kubernetes.api.resources.deployments.nginx; cfg = config.kubernetes.api.resources.deployments.nginx;
image = images.nginx; image = images.nginx;
@ -16,12 +15,13 @@ let
spec.containers = [{ spec.containers = [{
name = "curl"; name = "curl";
image = config.docker.images.curl.path; 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"; 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 = {
@ -35,13 +35,15 @@ in {
else if ((builtins.compareVersions config.kubernetes.version "1.8") <= 0) else if ((builtins.compareVersions config.kubernetes.version "1.8") <= 0)
then cfg.apiVersion == "apps/v1beta2" then cfg.apiVersion == "apps/v1beta2"
else cfg.apiVersion == "apps/v1"; else cfg.apiVersion == "apps/v1";
} { }
message = "should have corrent kind set"; {
assertion = cfg.kind == "Deployment"; message = "should have corrent kind set";
} { assertion = cfg.kind == "Deployment";
message = "should have replicas set"; }
assertion = cfg.spec.replicas == 3; {
}]; message = "should have replicas set";
assertion = cfg.spec.replicas == 3;
}];
script = '' script = ''
import time import time

View file

@ -1,11 +1,11 @@
{ config, lib, kubenix, ... }: { config, lib, kubenix, ... }:
with lib; with lib;
let 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 = {
@ -15,10 +15,11 @@ in {
assertions = [{ assertions = [{
message = "Pod should have name set"; message = "Pod should have name set";
assertion = pod.metadata.name == "test"; assertion = pod.metadata.name == "test";
} { }
message = "Deployment should have name set"; {
assertion = deployment.metadata.name == "nginx-deployment"; message = "Deployment should have name set";
}]; assertion = deployment.metadata.name == "nginx-deployment";
}];
}; };
kubernetes.imports = [ kubernetes.imports = [

View file

@ -1,10 +1,10 @@
{ config, lib, kubenix, pkgs, ... }: { config, lib, kubenix, pkgs, ... }:
with lib; with lib;
let let
cfg = config.kubernetes.api.resources.customResourceDefinitions.crontabs; cfg = config.kubernetes.api.resources.customResourceDefinitions.crontabs;
in { in
{
imports = with kubenix.modules; [ test k8s ]; imports = with kubenix.modules; [ test k8s ];
test = { test = {
@ -34,7 +34,7 @@ in {
plural = "crontabs"; plural = "crontabs";
singular = "crontab"; singular = "crontab";
kind = "CronTab"; kind = "CronTab";
shortNames = ["ct"]; shortNames = [ "ct" ];
}; };
}; };
}; };
@ -55,7 +55,7 @@ in {
}; };
}]; }];
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,8 +1,8 @@
{ config, kubenix, ... }: { config, kubenix, ... }:
let let
cfg = config.kubernetes.api.resources.pods.nginx; cfg = config.kubernetes.api.resources.pods.nginx;
in { in
{
imports = [ kubenix.modules.test kubenix.modules.k8s ]; imports = [ kubenix.modules.test kubenix.modules.k8s ];
test = { test = {
@ -11,11 +11,12 @@ in {
assertions = [{ assertions = [{
message = "should have apiVersion and kind set"; message = "should have apiVersion and kind set";
assertion = cfg.apiVersion == "v1" && cfg.kind == "Pod"; assertion = cfg.apiVersion == "v1" && cfg.kind == "Pod";
} { }
message = "should have name set"; {
assertion = cfg.metadata.name == "nginx"; message = "should have name set";
}]; assertion = cfg.metadata.name == "nginx";
}];
}; };
kubernetes.resources.pods.nginx = {}; kubernetes.resources.pods.nginx = { };
} }

View file

@ -1,10 +1,10 @@
{ name, config, lib, kubenix, images, ... }: { name, config, lib, kubenix, images, ... }:
with lib; with lib;
let let
cfg = config.submodules.instances.passthru; cfg = config.submodules.instances.passthru;
in { in
{
imports = with kubenix.modules; [ test submodules k8s docker ]; imports = with kubenix.modules; [ test submodules k8s docker ];
test = { test = {
@ -13,16 +13,17 @@ in {
assertions = [{ assertions = [{
message = "Submodule has correct name set"; message = "Submodule has correct name set";
assertion = (head config.kubernetes.objects).metadata.name == "passthru"; assertion = (head config.kubernetes.objects).metadata.name == "passthru";
} { }
message = "Should expose docker image"; {
assertion = (head config.docker.export).imageName == "xtruder/nginx"; message = "Should expose docker image";
}]; assertion = (head config.docker.export).imageName == "xtruder/nginx";
}];
}; };
kubernetes.namespace = "test-namespace"; kubernetes.namespace = "test-namespace";
submodules.imports = [{ submodules.imports = [{
module = {name, config, ...}: { module = { name, config, ... }: {
imports = with kubenix.modules; [ submodule k8s docker ]; imports = with kubenix.modules; [ submodule k8s docker ];
config = { config = {

View file

@ -1,16 +1,18 @@
{ options, config, lib, kubenix, pkgs, ... }: { options, config, lib, kubenix, pkgs, ... }:
with lib; with lib;
let let
findObject = { kind, name }: filter (object: findObject = { kind, name }: filter
object.kind == kind && object.metadata.name == name (object:
) config.kubernetes.objects; object.kind == kind && object.metadata.name == name
)
config.kubernetes.objects;
getObject = filter: head (findObject filter); getObject = filter: head (findObject filter);
hasObject = { kind, name }: length (findObject { inherit kind name; }) == 1; hasObject = { kind, name }: length (findObject { inherit kind name; }) == 1;
in { in
{
imports = with kubenix.modules; [ test k8s legacy ]; imports = with kubenix.modules; [ test k8s legacy ];
test = { test = {
@ -20,12 +22,13 @@ in {
assertions = [{ assertions = [{
message = "should define crd in module"; message = "should define crd in module";
assertion = assertion =
hasObject {kind = "SecretClaim"; name = "module-claim";}; hasObject { kind = "SecretClaim"; name = "module-claim"; };
} { }
message = "should define crd in root"; {
assertion = message = "should define crd in root";
hasObject {kind = "SecretClaim"; name = "root-claim";}; assertion =
}]; hasObject { kind = "SecretClaim"; name = "root-claim"; };
}];
}; };
kubernetes.namespace = "test"; kubernetes.namespace = "test";
@ -40,7 +43,7 @@ in {
type = mkOption { type = mkOption {
description = "Type of the secret"; description = "Type of the secret";
type = types.enum ["Opaque" "kubernetes.io/tls"]; type = types.enum [ "Opaque" "kubernetes.io/tls" ];
default = "Opaque"; default = "Opaque";
}; };
@ -62,7 +65,7 @@ in {
}; };
}; };
config = { config = {
kubernetes.resources.customResourceDefinitions.secret-claims = { kubernetes.resources.customResourceDefinitions.secret-claims = {
kind = "CustomResourceDefinition"; kind = "CustomResourceDefinition";
apiVersion = "apiextensions.k8s.io/v1beta1"; apiVersion = "apiextensions.k8s.io/v1beta1";
@ -74,7 +77,7 @@ in {
names = { names = {
plural = "secretclaims"; plural = "secretclaims";
kind = "SecretClaim"; kind = "SecretClaim";
shortNames = ["scl"]; shortNames = [ "scl" ];
}; };
}; };
}; };

View file

@ -1,10 +1,10 @@
{ config, lib, kubenix, pkgs, ... }: { config, lib, kubenix, pkgs, ... }:
with lib; with lib;
let let
cfg = config.kubernetes.api.resources.deployments.app; cfg = config.kubernetes.api.resources.deployments.app;
in { in
{
imports = with kubenix.modules; [ test k8s legacy ]; imports = with kubenix.modules; [ test k8s legacy ];
test = { test = {
@ -15,13 +15,14 @@ in {
assertion = assertion =
cfg.kind == "Deployment" && cfg.kind == "Deployment" &&
cfg.metadata.name == "app"; cfg.metadata.name == "app";
} { }
message = "should have correct defaults set"; {
assertion = message = "should have correct defaults set";
cfg.metadata.namespace == "test" && assertion =
cfg.metadata.labels.label1 == "value1" && cfg.metadata.namespace == "test" &&
cfg.metadata.labels.label2 == "value2"; cfg.metadata.labels.label1 == "value1" &&
}]; cfg.metadata.labels.label2 == "value2";
}];
}; };
kubernetes.resources.deployments.app = { kubernetes.resources.deployments.app = {
@ -39,7 +40,7 @@ in {
}; };
kubernetes.resources.configMaps.app = { kubernetes.resources.configMaps.app = {
data."my-conf.json" = builtins.toJSON {}; data."my-conf.json" = builtins.toJSON { };
}; };
kubernetes.defaults = { kubernetes.defaults = {

View file

@ -1,16 +1,18 @@
{ options, config, lib, kubenix, pkgs, ... }: { options, config, lib, kubenix, pkgs, ... }:
with lib; with lib;
let let
findObject = { kind, name }: filter (object: findObject = { kind, name }: filter
object.kind == kind && object.metadata.name == name (object:
) config.kubernetes.objects; object.kind == kind && object.metadata.name == name
)
config.kubernetes.objects;
getObject = filter: head (findObject filter); getObject = filter: head (findObject filter);
hasObject = { kind, name }: length (findObject { inherit kind name; }) == 1; hasObject = { kind, name }: length (findObject { inherit kind name; }) == 1;
in { in
{
imports = with kubenix.modules; [ test k8s legacy ]; imports = with kubenix.modules; [ test k8s legacy ];
test = { test = {
@ -19,21 +21,21 @@ in {
assertions = [{ assertions = [{
message = "should have all objects"; message = "should have all objects";
assertion = assertion =
hasObject {kind = "Deployment"; name = "myapp";} && hasObject { kind = "Deployment"; name = "myapp"; } &&
hasObject {kind = "Deployment"; name = "myapp2";} && hasObject { kind = "Deployment"; name = "myapp2"; } &&
hasObject {kind = "Deployment"; name = "myapp2-app2";}; hasObject { kind = "Deployment"; name = "myapp2-app2"; };
} { }
message = "should have default labels set"; {
assertion = message = "should have default labels set";
(getObject {kind = "Deployment"; name = "myapp2-app2";}) assertion =
.metadata.labels.module-label or false == "value" && (getObject { kind = "Deployment"; name = "myapp2-app2"; }).metadata.labels.module-label or false == "value" &&
(getObject {kind = "Deployment"; name = "myapp2";}) (getObject { kind = "Deployment"; name = "myapp2"; }).metadata.labels.module-label or false == "value";
.metadata.labels.module-label or false == "value"; }
} { {
message = "should passthru resources to root module"; message = "should passthru resources to root module";
assertion = assertion =
config.kubernetes.resources.deployments.myapp2-app2-app.metadata.labels.module-label or false == "value"; config.kubernetes.resources.deployments.myapp2-app2-app.metadata.labels.module-label or false == "value";
}]; }];
}; };
kubernetes.defaults.all.metadata.labels.module-label = "value"; kubernetes.defaults.all.metadata.labels.module-label = "value";

View file

@ -1,11 +1,11 @@
{ name, config, lib, kubenix, images, ... }: { name, config, lib, kubenix, images, ... }:
with lib; with lib;
let let
cfg = config.submodules.instances.test.config; cfg = config.submodules.instances.test.config;
deployment = cfg.kubernetes.api.deployments.nginx; deployment = cfg.kubernetes.api.deployments.nginx;
in { in
{
imports = [ kubenix.modules.test kubenix.module ]; imports = [ kubenix.modules.test kubenix.module ];
test = { test = {
@ -14,13 +14,15 @@ in {
assertions = [{ assertions = [{
message = "Namespace not propagated"; message = "Namespace not propagated";
assertion = deployment.metadata.namespace == "test"; assertion = deployment.metadata.namespace == "test";
} { }
message = "Version not propagated"; {
assertion = cfg.kubernetes.version == config.kubernetes.version; message = "Version not propagated";
} { assertion = cfg.kubernetes.version == config.kubernetes.version;
message = "docker image should be added to exported images"; }
assertion = (head config.docker.export) == images.nginx; {
}]; message = "docker image should be added to exported images";
assertion = (head config.docker.export) == images.nginx;
}];
testScript = '' testScript = ''
kube.wait_until_succeeds("docker load < ${images.nginx}") kube.wait_until_succeeds("docker load < ${images.nginx}")
kube.wait_until_succeeds("kubectl apply -f ${config.kubernetes.result}") kube.wait_until_succeeds("kubectl apply -f ${config.kubernetes.result}")
@ -31,7 +33,7 @@ in {
}; };
submodules.imports = [{ submodules.imports = [{
module = {name, config, ...}: { module = { name, config, ... }: {
submodule.name = "nginx"; submodule.name = "nginx";
kubernetes.api.deployments.nginx = { kubernetes.api.deployments.nginx = {
metadata = { metadata = {
@ -55,7 +57,7 @@ in {
}; };
}]; }];
kubernetes.api.namespaces.test = {}; kubernetes.api.namespaces.test = { };
submodules.instances.test = { submodules.instances.test = {
submodule = "nginx"; submodule = "nginx";

View file

@ -1,7 +1,6 @@
{ name, config, lib, kubenix, ... }: { name, config, lib, kubenix, ... }:
with lib; with lib;
let let
instance1 = config.submodules.instances.instance1; instance1 = config.submodules.instances.instance1;
instance2 = config.submodules.instances.instance2; instance2 = config.submodules.instances.instance2;
@ -10,7 +9,7 @@ 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 = {
@ -25,7 +24,8 @@ let
}; };
}; };
}; };
in { in
{
imports = with kubenix.modules; [ test submodules ]; imports = with kubenix.modules; [ test submodules ];
test = { test = {
@ -34,95 +34,130 @@ in {
assertions = [{ assertions = [{
message = "should apply defaults by tag1"; message = "should apply defaults by tag1";
assertion = instance1.config.submodule.args.value == "value1"; assertion = instance1.config.submodule.args.value == "value1";
} { }
message = "should apply defaults by tag2"; {
assertion = instance2.config.submodule.args.value == "value2"; message = "should apply defaults by tag2";
} { assertion = instance2.config.submodule.args.value == "value2";
message = "should apply defaults by tag2"; }
assertion = instance3.config.submodule.args.value == "value2"; {
} { message = "should apply defaults by tag2";
message = "should apply defaults to all"; assertion = instance3.config.submodule.args.value == "value2";
assertion = }
instance1.config.submodule.args.defaultValue == "value" && {
instance2.config.submodule.args.defaultValue == "value"; message = "should apply defaults to all";
} { assertion =
message = "instance1 and instance3 should have value of default-value"; instance1.config.submodule.args.defaultValue == "value" &&
assertion = instance3.config.submodule.args.defaultValue == "default-value"; instance2.config.submodule.args.defaultValue == "value";
} { }
message = "should apply defaults by submodule name"; {
assertion = instance4.config.submodule.args.value == "value4"; message = "instance1 and instance3 should have value of default-value";
} { assertion = instance3.config.submodule.args.defaultValue == "default-value";
message = "should apply defaults by custom condition"; }
assertion = instance5.config.submodule.args.defaultValue == "my-custom-value"; {
} { message = "should apply defaults by submodule name";
message = "should apply defaults to versioned submodule"; assertion = instance4.config.submodule.args.value == "value4";
assertion = versioned-submodule.config.submodule.args.defaultValue == "versioned-submodule"; }
}]; {
message = "should apply defaults by custom condition";
assertion = instance5.config.submodule.args.defaultValue == "my-custom-value";
}
{
message = "should apply defaults to versioned submodule";
assertion = versioned-submodule.config.submodule.args.defaultValue == "versioned-submodule";
}];
}; };
submodules.imports = [{ submodules.imports = [{
modules = [submodule { modules = [
submodule = { submodule
name = "submodule1"; {
tags = ["tag1"]; submodule = {
}; name = "submodule1";
tags = [ "tag1" ];
};
}
];
}
{
modules = [
submodule
{
submodule = {
name = "submodule2";
tags = [ "tag2" ];
};
}
];
}
{
modules = [
submodule
{
submodule = {
name = "submodule3";
tags = [ "tag2" ];
};
}
];
}
{
modules = [
submodule
{
submodule = {
name = "submodule4";
};
}
];
}
{
modules = [
submodule
{
submodule = {
name = "submodule5";
};
submodule.args.value = "custom-value";
}
];
}
{
modules = [
submodule
{
submodule = {
name = "versioned-submodule";
version = "2.0.0";
};
}
];
}]; }];
} {
modules = [submodule {
submodule = {
name = "submodule2";
tags = ["tag2"];
};
}];
} {
modules = [submodule {
submodule = {
name = "submodule3";
tags = ["tag2"];
};
}];
} {
modules = [submodule {
submodule = {
name = "submodule4";
};
}];
} {
modules = [submodule {
submodule = {
name = "submodule5";
};
submodule.args.value = "custom-value";
}];
} {
modules = [submodule {
submodule = {
name = "versioned-submodule";
version = "2.0.0";
};
}];
}];
submodules.defaults = [{ submodules.defaults = [{
default.submodule.args.defaultValue = mkDefault "value"; default.submodule.args.defaultValue = mkDefault "value";
} { }
tags = ["tag1"]; {
default.submodule.args.value = mkDefault "value1"; tags = [ "tag1" ];
} { default.submodule.args.value = mkDefault "value1";
tags = ["tag2"]; }
default.submodule.args.value = mkDefault "value2"; {
} { tags = [ "tag2" ];
name = "submodule4"; default.submodule.args.value = mkDefault "value2";
default.submodule.args.value = mkDefault "value4"; }
} { {
default = {config, ...}: { name = "submodule4";
submodule.args.defaultValue = mkIf (config.submodule.args.value == "custom-value") "my-custom-value"; default.submodule.args.value = mkDefault "value4";
}; }
} { {
name = "versioned-submodule"; default = { config, ... }: {
version = "2.0.0"; submodule.args.defaultValue = mkIf (config.submodule.args.value == "custom-value") "my-custom-value";
default.submodule.args.value = mkDefault "versioned"; };
}]; }
{
name = "versioned-submodule";
version = "2.0.0";
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,7 +1,6 @@
{ name, config, lib, kubenix, subm-lib, ... }: { name, config, lib, kubenix, subm-lib, ... }:
with lib; with lib;
let let
submodule = { submodule = {
imports = [ kubenix.modules.submodule ]; imports = [ kubenix.modules.submodule ];
@ -13,7 +12,8 @@ let
}; };
}; };
}; };
in { in
{
imports = with kubenix.modules; [ test submodules ]; imports = with kubenix.modules; [ test submodules ];
test = { test = {
@ -26,7 +26,7 @@ in {
}; };
submodules.imports = [{ submodules.imports = [{
modules = [submodule]; modules = [ submodule ];
exportAs = "subm-lib"; exportAs = "subm-lib";
}]; }];
} }

View file

@ -1,7 +1,6 @@
{ name, config, lib, kubenix, ... }: { name, config, lib, kubenix, ... }:
with lib; with lib;
let let
submodule = { name, ... }: { submodule = { name, ... }: {
imports = [ kubenix.modules.submodule ]; imports = [ kubenix.modules.submodule ];
@ -11,14 +10,15 @@ let
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 = { };
}; };
}; };
@ -29,17 +29,19 @@ in {
assertions = [{ assertions = [{
message = "should passthru values if passthru enabled"; message = "should passthru values if passthru enabled";
assertion = hasAttr "inst1" config.global && config.global.inst1 == "true"; assertion = hasAttr "inst1" config.global && config.global.inst1 == "true";
} { }
message = "should not passthru values if passthru not enabled"; {
assertion = !(hasAttr "inst2" config.global); message = "should not passthru values if passthru not enabled";
} { assertion = !(hasAttr "inst2" config.global);
message = "should passthru by default"; }
assertion = hasAttr "inst3" config.global && config.global.inst3 == "true"; {
}]; message = "should passthru by default";
assertion = hasAttr "inst3" config.global && config.global.inst3 == "true";
}];
}; };
submodules.imports = [{ submodules.imports = [{
modules = [submodule]; modules = [ submodule ];
}]; }];
submodules.instances.inst1 = { submodules.instances.inst1 = {

View file

@ -1,11 +1,11 @@
{ name, config, lib, kubenix, ... }: { name, config, lib, kubenix, ... }:
with lib; with lib;
let 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 = {
@ -14,22 +14,27 @@ in {
assertions = [{ assertions = [{
message = "Submodule name is set"; message = "Submodule name is set";
assertion = cfg.name == "instance"; assertion = cfg.name == "instance";
} { }
message = "Submodule version is set"; {
assertion = cfg.version == null; message = "Submodule version is set";
} { assertion = cfg.version == null;
message = "Submodule config has submodule definition"; }
assertion = cfg.config.submodule.name == "submodule"; {
} { message = "Submodule config has submodule definition";
message = "Should have argument set"; assertion = cfg.config.submodule.name == "submodule";
assertion = args.value == "test"; }
} { {
message = "Should have submodule name set"; message = "Should have argument set";
assertion = args.name == "instance"; assertion = args.value == "test";
} { }
message = "should have tag set"; {
assertion = elem "tag" (cfg.config.submodule.tags); message = "Should have submodule name set";
}]; assertion = args.name == "instance";
}
{
message = "should have tag set";
assertion = elem "tag" (cfg.config.submodule.tags);
}];
}; };
submodules.propagate.enable = true; submodules.propagate.enable = true;
@ -51,7 +56,7 @@ in {
config = { config = {
submodule.name = "submodule"; submodule.name = "submodule";
submodule.tags = ["tag"]; submodule.tags = [ "tag" ];
}; };
}; };
}]; }];

View file

@ -1,7 +1,6 @@
{ name, config, lib, kubenix, ... }: { name, config, lib, kubenix, ... }:
with lib; with lib;
let 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;
@ -17,7 +16,8 @@ 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 = {
@ -26,36 +26,45 @@ in {
assertions = [{ assertions = [{
message = "should select exact version"; message = "should select exact version";
assertion = inst-exact.version == "1.1.0"; assertion = inst-exact.version == "1.1.0";
} { }
message = "should select regex version"; {
assertion = inst-regex.version == "1.2.1"; message = "should select regex version";
} { assertion = inst-regex.version == "1.2.1";
message = "should select latest version"; }
assertion = inst-latest.version == "1.2.1"; {
}]; message = "should select latest version";
assertion = inst-latest.version == "1.2.1";
}];
}; };
submodules.imports = [{ submodules.imports = [{
modules = [{ modules = [{
config.submodule.version = "1.0.0"; config.submodule.version = "1.0.0";
config.version = "1.0.0"; config.version = "1.0.0";
} submodule]; }
} { submodule];
modules = [{ }
config.submodule.version = "1.1.0"; {
config.version = "1.1.0"; modules = [{
} submodule]; config.submodule.version = "1.1.0";
} { config.version = "1.1.0";
modules = [{ }
config.submodule.version = "1.2.0"; submodule];
config.version = "1.2.0"; }
} submodule]; {
} { modules = [{
modules = [{ config.submodule.version = "1.2.0";
config.submodule.version = "1.2.1"; config.version = "1.2.0";
config.version = "1.2.1"; }
} 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";