diff --git a/lib/extra.nix b/lib/extra.nix index fd2c467..8442535 100644 --- a/lib/extra.nix +++ b/lib/extra.nix @@ -85,4 +85,37 @@ rec { 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)); } diff --git a/modules/k8s.nix b/modules/k8s.nix index e74ce81..0223b7b 100644 --- a/modules/k8s.nix +++ b/modules/k8s.nix @@ -7,6 +7,8 @@ with lib; let cfg = config.kubernetes; + gvkKeyFn = type: "${type.group}/${type.version}/${type.kind}"; + getDefaults = resource: group: version: kind: catAttrs "default" (filter (default: (resource == null || default.resource == null || default.resource == resource) && @@ -76,13 +78,8 @@ let types = mkOption { description = "List of registered kubernetes types"; - type = types.listOf (types.submodule { + type = coerceListOfSubmodulesToAttrs { options = { - name = mkOption { - description = "Resource type name"; - type = types.nullOr types.str; - }; - group = mkOption { description = "Resource type group"; type = types.str; @@ -98,13 +95,18 @@ let type = types.str; }; + name = mkOption { + description = "Resource type name"; + type = types.nullOr types.str; + }; + attrName = mkOption { description = "Name of the nixified attribute"; type = types.str; }; }; - }); - default = []; + } gvkKeyFn; + default = {}; }; }; @@ -126,18 +128,18 @@ let v2 = if length splittedVer2 == 1 then "${getVersion ver2}prod" else getVersion ver2; in builtins.compareVersions v1 v2; - customResourceTypesByKind = zipAttrs (map (resourceType: { - ${resourceType.kind} = resourceType; + customResourceTypesByAttrName = zipAttrs (mapAttrsToList (_: resourceType: { + ${resourceType.attrName} = resourceType; }) cfg.customTypes); - customResourceTypesByKindSortByVersion = mapAttrs (_: resourceTypes: + customResourceTypesByAttrNameSortByVersion = mapAttrs (_: resourceTypes: reverseList (sort (r1: r2: compareVersions r1.version r2.version > 0 ) resourceTypes) - ) customResourceTypesByKind; + ) customResourceTypesByAttrName; latestCustomResourceTypes = - mapAttrsToList (_: resources: last resources) customResourceTypesByKindSortByVersion; + mapAttrsToList (_: resources: last resources) customResourceTypesByAttrNameSortByVersion; customResourceModuleForType = config: ct: { name, ... }: { 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; in { options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption { @@ -182,14 +184,14 @@ let }) cfg.customTypes) ++ (map (ct: { options, config, ... }: let module = customResourceModuleForType config ct; in { - options.resources.${ct.name} = mkOption { + options.resources.${ct.attrName} = mkOption { description = ct.description; type = types.attrsOf (types.submodule module); default = {}; }; config.resources.${ct.group}.${ct.version}.${ct.kind} = - mkAliasDefinitions options.resources.${ct.name}; + mkAliasDefinitions options.resources.${ct.attrName}; }) latestCustomResourceTypes); in { @@ -238,45 +240,56 @@ in { default = {}; }; + createCustomTypesFromCRDs = mkOption { + description = "Whether to create customTypes from custom resource definitions"; + type = types.bool; + default = false; + }; + customTypes = mkOption { - default = []; description = "List of custom resource types to make API for"; - type = types.listOf (types.submodule ({config, ...}: { + type = coerceListOfSubmodulesToAttrs { options = { group = mkOption { - description = "Custom resource definition group"; + description = "Custom type group"; type = types.str; }; version = mkOption { - description = "Custom resource definition version"; + description = "Custom type version"; type = types.str; }; kind = mkOption { - description = "Custom resource definition kind"; + description = "Custom type kind"; type = types.str; }; name = mkOption { - description = "Custom resource definition resource name"; + description = "Custom type resource name"; type = types.nullOr types.str; default = null; }; + attrName = mkOption { + description = "Name of the nixified attribute"; + type = types.str; + }; + description = mkOption { - description = "Custom resource definition description"; + description = "Custom type description"; type = types.str; default = ""; }; module = mkOption { - description = "Custom resource definition module"; + description = "Custom type module"; type = types.unspecified; default = {}; }; }; - })); + } gvkKeyFn; + default = {}; }; objects = mkOption { @@ -334,8 +347,8 @@ in { kubernetes.api = mkMerge ([{ # register custom types - types = map (cr: { - inherit (cr) name group version kind; + types = mapAttrsToList (_: cr: { + inherit (cr) name group version kind attrName; }) cfg.customTypes; defaults = [{ @@ -364,11 +377,22 @@ in { resources.${group}.${version}.${kind}.${name} = object; }) cfg.imports)); - kubernetes.objects = flatten (map (type: + kubernetes.objects = flatten (mapAttrsToList (_: type: mapAttrsToList (name: resource: moduleToAttrs resource) cfg.api.resources.${type.group}.${type.version}.${type.kind} ) 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 { items = config.kubernetes.objects; labels."kubenix/project-name" = config.kubenix.project; diff --git a/modules/legacy.nix b/modules/legacy.nix index f0648ca..72a736b 100644 --- a/modules/legacy.nix +++ b/modules/legacy.nix @@ -164,7 +164,7 @@ in { config = { 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 { default = { imports = default; }; } // (if (attrName == "all") then {} else { @@ -184,6 +184,9 @@ in { # custom resources are now included in normal resources, so just make an alias kubernetes.customResources = mkAliasDefinitions options.kubernetes.resources; + # create custom types from CRDs was old behavior + kubernetes.createCustomTypesFromCRDs = true; + kubernetes.defaultModuleConfiguration.all = { _file = head options.kubernetes.defaultModuleConfiguration.files; config.kubernetes.version = mkDefault config.kubernetes.version; diff --git a/tests/k8s/crd.nix b/tests/k8s/crd.nix index 2ca5add..83627a7 100644 --- a/tests/k8s/crd.nix +++ b/tests/k8s/crd.nix @@ -61,39 +61,64 @@ in { }; }; - kubernetes.customTypes = [{ - 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; - }; + kubernetes.createCustomTypesFromCRDs = true; - command = mkOption { - description = "Command to run"; - type = types.str; + kubernetes.customTypes = mkMerge [ + { + "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.crontabs.latest.spec.schedule = "* * * * *"; + kubernetes.resources.cronTabs.latest.spec.schedule = "* * * * *"; + kubernetes.resources.cronTabsV3.latest.spec.schedule = "* * * * *"; }