feat(k8s): allow to define customTypes from CRDs

This commit is contained in:
Jaka Hudoklin 2019-10-20 13:36:10 +02:00
parent baea3cc3b3
commit 086780088c
No known key found for this signature in database
GPG key ID: D1F18234B07BD6E2
4 changed files with 144 additions and 59 deletions

View file

@ -85,4 +85,37 @@ rec {
binOp = lhs: rhs: []; binOp = lhs: rhs: [];
}; };
}; };
coerceListOfSubmodulesToAttrs = submodule: keyFn: let
mergeValuesByFn = keyFn: values:
listToAttrs (map (value:
nameValuePair (toString (keyFn value)) value
) values);
# Either value of type `finalType` or `coercedType`, the latter is
# converted to `finalType` using `coerceFunc`.
coercedTo = coercedType: coerceFunc: finalType:
mkOptionType rec {
name = "coercedTo";
description = "${finalType.description} or ${coercedType.description}";
check = x: finalType.check x || coercedType.check x;
merge = loc: defs:
let
coerceVal = val:
if finalType.check val then
val
else
let coerced = coerceFunc val; in assert finalType.check coerced; coerced;
in finalType.merge loc (map (def: def // { value = coerceVal def.value; }) defs);
getSubOptions = finalType.getSubOptions;
getSubModules = finalType.getSubModules;
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
typeMerge = t1: t2: null;
functor = (defaultFunctor name) // { wrapped = finalType; };
};
in coercedTo
(types.listOf (types.submodule submodule))
(mergeValuesByFn keyFn)
(types.attrsOf (types.submodule submodule));
} }

View file

@ -7,6 +7,8 @@ with lib;
let let
cfg = config.kubernetes; cfg = config.kubernetes;
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 (default:
(resource == null || default.resource == null || default.resource == resource) && (resource == null || default.resource == null || default.resource == resource) &&
@ -76,13 +78,8 @@ let
types = mkOption { types = mkOption {
description = "List of registered kubernetes types"; description = "List of registered kubernetes types";
type = types.listOf (types.submodule { type = coerceListOfSubmodulesToAttrs {
options = { options = {
name = mkOption {
description = "Resource type name";
type = types.nullOr types.str;
};
group = mkOption { group = mkOption {
description = "Resource type group"; description = "Resource type group";
type = types.str; type = types.str;
@ -98,13 +95,18 @@ let
type = types.str; type = types.str;
}; };
name = mkOption {
description = "Resource type name";
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;
default = []; default = {};
}; };
}; };
@ -126,18 +128,18 @@ let
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;
customResourceTypesByKind = zipAttrs (map (resourceType: { customResourceTypesByAttrName = zipAttrs (mapAttrsToList (_: resourceType: {
${resourceType.kind} = resourceType; ${resourceType.attrName} = resourceType;
}) cfg.customTypes); }) cfg.customTypes);
customResourceTypesByKindSortByVersion = mapAttrs (_: resourceTypes: customResourceTypesByAttrNameSortByVersion = mapAttrs (_: resourceTypes:
reverseList (sort (r1: r2: reverseList (sort (r1: r2:
compareVersions r1.version r2.version > 0 compareVersions r1.version r2.version > 0
) resourceTypes) ) resourceTypes)
) customResourceTypesByKind; ) customResourceTypesByAttrName;
latestCustomResourceTypes = latestCustomResourceTypes =
mapAttrsToList (_: resources: last resources) customResourceTypesByKindSortByVersion; mapAttrsToList (_: resources: last resources) customResourceTypesByAttrNameSortByVersion;
customResourceModuleForType = config: ct: { name, ... }: { customResourceModuleForType = config: ct: { name, ... }: {
imports = getDefaults ct.name ct.group ct.version ct.kind; imports = getDefaults ct.name ct.group ct.version ct.kind;
@ -171,7 +173,7 @@ let
}; };
}; };
customResourceOptions = (map (ct: {config, ...}: let customResourceOptions = (mapAttrsToList (_: ct: {config, ...}: let
module = customResourceModuleForType config ct; module = customResourceModuleForType config ct;
in { in {
options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption { options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption {
@ -182,14 +184,14 @@ let
}) cfg.customTypes) ++ (map (ct: { options, config, ... }: let }) cfg.customTypes) ++ (map (ct: { options, config, ... }: let
module = customResourceModuleForType config ct; module = customResourceModuleForType config ct;
in { in {
options.resources.${ct.name} = mkOption { options.resources.${ct.attrName} = mkOption {
description = ct.description; description = ct.description;
type = types.attrsOf (types.submodule module); type = types.attrsOf (types.submodule module);
default = {}; default = {};
}; };
config.resources.${ct.group}.${ct.version}.${ct.kind} = config.resources.${ct.group}.${ct.version}.${ct.kind} =
mkAliasDefinitions options.resources.${ct.name}; mkAliasDefinitions options.resources.${ct.attrName};
}) latestCustomResourceTypes); }) latestCustomResourceTypes);
in { in {
@ -238,45 +240,56 @@ in {
default = {}; default = {};
}; };
createCustomTypesFromCRDs = mkOption {
description = "Whether to create customTypes from custom resource definitions";
type = types.bool;
default = false;
};
customTypes = mkOption { customTypes = mkOption {
default = [];
description = "List of custom resource types to make API for"; description = "List of custom resource types to make API for";
type = types.listOf (types.submodule ({config, ...}: { type = coerceListOfSubmodulesToAttrs {
options = { options = {
group = mkOption { group = mkOption {
description = "Custom resource definition group"; description = "Custom type group";
type = types.str; type = types.str;
}; };
version = mkOption { version = mkOption {
description = "Custom resource definition version"; description = "Custom type version";
type = types.str; type = types.str;
}; };
kind = mkOption { kind = mkOption {
description = "Custom resource definition kind"; description = "Custom type kind";
type = types.str; type = types.str;
}; };
name = mkOption { name = mkOption {
description = "Custom resource definition resource name"; description = "Custom type resource name";
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
}; };
attrName = mkOption {
description = "Name of the nixified attribute";
type = types.str;
};
description = mkOption { description = mkOption {
description = "Custom resource definition description"; description = "Custom type description";
type = types.str; type = types.str;
default = ""; default = "";
}; };
module = mkOption { module = mkOption {
description = "Custom resource definition module"; description = "Custom type module";
type = types.unspecified; type = types.unspecified;
default = {}; default = {};
}; };
}; };
})); } gvkKeyFn;
default = {};
}; };
objects = mkOption { objects = mkOption {
@ -334,8 +347,8 @@ in {
kubernetes.api = mkMerge ([{ kubernetes.api = mkMerge ([{
# register custom types # register custom types
types = map (cr: { types = mapAttrsToList (_: cr: {
inherit (cr) name group version kind; inherit (cr) name group version kind attrName;
}) cfg.customTypes; }) cfg.customTypes;
defaults = [{ defaults = [{
@ -364,11 +377,22 @@ in {
resources.${group}.${version}.${kind}.${name} = object; resources.${group}.${version}.${kind}.${name} = object;
}) cfg.imports)); }) cfg.imports));
kubernetes.objects = flatten (map (type: kubernetes.objects = flatten (mapAttrsToList (_: type:
mapAttrsToList (name: resource: moduleToAttrs resource) mapAttrsToList (name: resource: moduleToAttrs resource)
cfg.api.resources.${type.group}.${type.version}.${type.kind} cfg.api.resources.${type.group}.${type.version}.${type.kind}
) cfg.api.types); ) cfg.api.types);
# custom types created from customResourceDefinitions
kubernetes.customTypes = mkIf cfg.createCustomTypesFromCRDs (
mapAttrsToList (_: crd: {
group = crd.spec.group;
version = crd.spec.version;
kind = crd.spec.names.kind;
name = crd.spec.names.plural;
attrName = mkDefault crd.spec.names.plural;
}) (cfg.resources.customResourceDefinitions or {})
);
kubernetes.generated = k8s.mkHashedList { kubernetes.generated = k8s.mkHashedList {
items = config.kubernetes.objects; items = config.kubernetes.objects;
labels."kubenix/project-name" = config.kubenix.project; labels."kubenix/project-name" = config.kubenix.project;

View file

@ -164,7 +164,7 @@ in {
config = { config = {
kubernetes.api.defaults = mapAttrsToList (attrName: default: let kubernetes.api.defaults = mapAttrsToList (attrName: default: let
type = head (filter (type: type.attrName == attrName) config.kubernetes.api.types); type = head (mapAttrsToList (_: v: v) (filterAttrs (_: type: type.attrName == attrName) config.kubernetes.api.types));
in { in {
default = { imports = default; }; default = { imports = default; };
} // (if (attrName == "all") then {} else { } // (if (attrName == "all") then {} else {
@ -184,6 +184,9 @@ in {
# custom resources are now included in normal resources, so just make an alias # custom resources are now included in normal resources, so just make an alias
kubernetes.customResources = mkAliasDefinitions options.kubernetes.resources; kubernetes.customResources = mkAliasDefinitions options.kubernetes.resources;
# create custom types from CRDs was old behavior
kubernetes.createCustomTypesFromCRDs = true;
kubernetes.defaultModuleConfiguration.all = { kubernetes.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;

View file

@ -61,39 +61,64 @@ in {
}; };
}; };
kubernetes.customTypes = [{ kubernetes.createCustomTypesFromCRDs = true;
name = "crontabs";
group = "stable.example.com";
version = "v1";
kind = "CronTab";
description = "CronTabs resources";
module = {
options.schedule = mkOption {
description = "Crontab schedule script";
type = types.str;
};
};
} {
name = "crontabs";
group = "stable.example.com";
version = "v2";
kind = "CronTab";
description = "CronTabs resources";
module = {
options = {
schedule = mkOption {
description = "Crontab schedule script";
type = types.str;
};
command = mkOption { kubernetes.customTypes = mkMerge [
description = "Command to run"; {
type = types.str; "stable.example.com/v1/CronTab" = {
attrName = "cronTabs";
description = "CronTabs resources";
module = {
options.schedule = mkOption {
description = "Crontab schedule script";
type = types.str;
};
}; };
}; };
}; }
}]; {
"stable.example.com/v2/CronTab" = {
description = "CronTabs resources";
attrName = "cronTabs";
module = {
options = {
schedule = mkOption {
description = "Crontab schedule script";
type = types.str;
};
command = mkOption {
description = "Command to run";
type = types.str;
};
};
};
};
}
[{
group = "stable.example.com";
version = "v3";
kind = "CronTab";
description = "CronTabs resources";
attrName = "cronTabsV3";
module = {
options = {
schedule = mkOption {
description = "Crontab schedule script";
type = types.str;
};
command = mkOption {
description = "Command to run";
type = types.str;
};
};
};
}]
];
kubernetes.resources."stable.example.com"."v1".CronTab.versioned.spec.schedule = "* * * * *"; kubernetes.resources."stable.example.com"."v1".CronTab.versioned.spec.schedule = "* * * * *";
kubernetes.resources.crontabs.latest.spec.schedule = "* * * * *"; kubernetes.resources.cronTabs.latest.spec.schedule = "* * * * *";
kubernetes.resources.cronTabsV3.latest.spec.schedule = "* * * * *";
} }