kubenix/modules/k8s.nix

533 lines
15 KiB
Nix
Raw Normal View History

# K8S module defines kubernetes definitions for kubenix
2022-04-02 12:40:35 -07:00
{
options,
config,
lib,
pkgs,
k8s,
...
}:
with lib; let
versions = (import ../versions.nix).versions;
2019-02-28 14:04:16 +01:00
cfg = config.kubernetes;
gvkKeyFn = type: "${type.group}/${type.version}/${type.kind}";
2019-02-26 21:22:03 +01:00
getDefaults = resource: group: version: kind:
2021-05-13 17:27:08 -04:00
catAttrs "default" (filter
2022-04-02 12:40:35 -07:00
(
default:
(resource == null || default.resource == null || default.resource == resource)
&& (default.group == null || default.group == group)
&& (default.version == null || default.version == version)
&& (default.kind == null || default.kind == kind)
2021-05-13 17:27:08 -04:00
)
cfg.api.defaults);
2019-02-26 21:22:03 +01:00
moduleToAttrs = value:
if isAttrs value
2022-04-02 13:43:57 -07:00
then mapAttrs (_n: moduleToAttrs) (filterAttrs (n: v: v != null && !(hasPrefix "_" n)) value)
else if isList value
2022-04-02 13:43:57 -07:00
then map moduleToAttrs value
else value;
2022-04-02 12:40:35 -07:00
apiOptions = {config, ...}: {
options = {
definitions = mkOption {
description = "Attribute set of kubernetes definitions";
};
defaults = mkOption {
2019-02-26 21:22:03 +01:00
description = "Kubernetes defaults to apply to resources";
2022-04-02 13:43:57 -07:00
type = types.listOf (types.submodule (_: {
2019-02-26 21:22:03 +01:00
options = {
group = mkOption {
description = "Group to apply default to (all by default)";
type = types.nullOr types.str;
default = null;
};
version = mkOption {
description = "Version to apply default to (all by default)";
type = types.nullOr types.str;
default = null;
};
kind = mkOption {
description = "Kind to apply default to (all by default)";
type = types.nullOr types.str;
default = null;
};
resource = mkOption {
description = "Resource to apply default to (all by default)";
type = types.nullOr types.str;
default = null;
};
propagate = mkOption {
description = "Whether to propagate defaults";
type = types.bool;
default = false;
};
2019-02-26 21:22:03 +01:00
default = mkOption {
description = "Default to apply";
type = types.unspecified;
2022-04-02 12:40:35 -07:00
default = {};
2019-02-26 21:22:03 +01:00
};
};
}));
2022-04-02 12:40:35 -07:00
default = [];
2020-01-14 20:44:30 +00:00
apply = unique;
};
types = mkOption {
description = "List of registered kubernetes types";
2022-04-02 12:40:35 -07:00
type =
coerceListOfSubmodulesToAttrs
2021-05-13 17:27:08 -04:00
{
options = {
group = mkOption {
description = "Resource type group";
type = types.str;
};
2021-05-13 17:27:08 -04:00
version = mkOption {
description = "Resoruce type version";
type = types.str;
};
2021-05-13 17:27:08 -04:00
kind = mkOption {
description = "Resource type kind";
type = types.str;
};
2021-05-13 17:27:08 -04:00
name = mkOption {
description = "Resource type name";
type = types.nullOr types.str;
};
2021-05-13 17:27:08 -04:00
attrName = mkOption {
description = "Name of the nixified attribute";
type = types.str;
};
};
2021-05-13 17:27:08 -04:00
}
gvkKeyFn;
2022-04-02 12:40:35 -07:00
default = {};
};
};
config = {
# apply aliased option
resources = mkAliasDefinitions options.kubernetes.resources;
};
};
2019-02-28 14:04:47 +01:00
indexOf = lst: value:
head (filter (v: v != -1) (imap0
(i: v:
if v == value
then i
else -1)
lst));
2022-04-02 12:40:35 -07:00
compareVersions = ver1: ver2: let
2022-04-02 13:43:57 -07:00
getVersion = substring 1 10;
2022-04-02 12:40:35 -07:00
splittedVer1 = builtins.splitVersion (getVersion ver1);
splittedVer2 = builtins.splitVersion (getVersion ver2);
v1 =
if length splittedVer1 == 1
then "${getVersion ver1}prod"
else getVersion ver1;
v2 =
if length splittedVer2 == 1
then "${getVersion ver2}prod"
else getVersion ver2;
in
2021-05-13 17:27:08 -04:00
builtins.compareVersions v1 v2;
customResourceTypesByAttrName = zipAttrs (mapAttrsToList
(_: resourceType: {
${resourceType.attrName} = resourceType;
})
cfg.customTypes);
2022-04-02 12:40:35 -07:00
customResourceTypesByAttrNameSortByVersion =
mapAttrs
(
_: resourceTypes:
reverseList (sort
(
r1: r2:
compareVersions r1.version r2.version > 0
)
resourceTypes)
2021-05-13 17:27:08 -04:00
)
customResourceTypesByAttrName;
latestCustomResourceTypes =
2022-04-02 13:43:57 -07:00
mapAttrsToList (_: last) customResourceTypesByAttrNameSortByVersion;
2022-04-02 12:40:35 -07:00
customResourceModuleForType = config: ct: {name, ...}: {
imports = getDefaults ct.name ct.group ct.version ct.kind;
options = {
apiVersion = mkOption {
description = "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources";
type = types.nullOr types.str;
};
kind = mkOption {
description = "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds";
type = types.nullOr types.str;
};
metadata = mkOption {
description = "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.";
type = types.nullOr (types.submodule config.definitions."io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta");
};
spec = mkOption {
description = "Module spec";
type = types.either types.attrs (types.submodule ct.module);
2022-04-02 12:40:35 -07:00
default = {};
};
};
config = {
apiVersion = mkOptionDefault "${ct.group}/${ct.version}";
kind = mkOptionDefault ct.kind;
2019-10-21 13:47:21 +02:00
metadata.name = mkDefault name;
};
};
2022-04-02 12:40:35 -07:00
customResourceOptions =
(mapAttrsToList
(_: ct: {config, ...}: let
2021-05-13 17:27:08 -04:00
module = customResourceModuleForType config ct;
2022-04-02 12:40:35 -07:00
in {
2021-05-13 17:27:08 -04:00
options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption {
2022-04-02 13:43:57 -07:00
inherit (ct) description;
2021-05-13 17:27:08 -04:00
type = types.attrsOf (types.submodule module);
2022-04-02 12:40:35 -07:00
default = {};
2021-05-13 17:27:08 -04:00
};
})
2022-04-02 12:40:35 -07:00
cfg.customTypes)
++ (map
(ct: {
options,
config,
...
}: let
2021-05-13 17:27:08 -04:00
module = customResourceModuleForType config ct;
2022-04-02 12:40:35 -07:00
in {
2021-05-13 17:27:08 -04:00
options.resources.${ct.attrName} = mkOption {
2022-04-02 13:43:57 -07:00
inherit (ct) description;
2021-05-13 17:27:08 -04:00
type = types.attrsOf (types.submodule module);
2022-04-02 12:40:35 -07:00
default = {};
2021-05-13 17:27:08 -04:00
};
2021-05-13 17:27:08 -04:00
config.resources.${ct.group}.${ct.version}.${ct.kind} =
mkAliasDefinitions options.resources.${ct.attrName};
})
2022-04-02 12:40:35 -07:00
latestCustomResourceTypes);
2022-04-02 12:40:35 -07:00
coerceListOfSubmodulesToAttrs = submodule: keyFn: let
mergeValuesByFn = keyFn: values:
listToAttrs (map
(
value:
2021-05-31 17:24:59 -05:00
nameValuePair (toString (keyFn value)) value
2022-04-02 12:40:35 -07:00
)
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);
2022-04-02 13:43:57 -07:00
inherit (finalType) getSubOptions;
inherit (finalType) getSubModules;
2022-04-02 12:40:35 -07:00
substSubModules = m: coercedTo coercedType coerceFunc (finalType.substSubModules m);
2022-04-02 13:41:07 -07:00
typeMerge = _t1: _t2: null;
2022-04-02 12:40:35 -07:00
functor = (defaultFunctor name) // {wrapped = finalType;};
};
in
2021-05-31 17:24:59 -05:00
coercedTo
2022-04-02 12:40:35 -07:00
(types.listOf (types.submodule submodule))
(mergeValuesByFn keyFn)
(types.attrsOf (types.submodule submodule));
in {
imports = [./base.nix];
options.kubernetes = {
2022-08-31 21:35:36 -04:00
kubeconfig = mkOption {
description = "path to kubeconfig file (default: use $KUBECONFIG)";
type = types.nullOr types.str;
default = null;
example = "/run/secrets/kubeconfig";
};
version = mkOption {
description = "Kubernetes version to use";
type = types.enum versions;
default = lib.lists.last versions;
2022-08-31 21:38:37 -04:00
example = "1.24";
};
namespace = mkOption {
description = "Default namespace where to deploy kubernetes resources";
2020-04-05 21:25:34 +07:00
type = types.nullOr types.str;
default = null;
2022-08-31 21:38:37 -04:00
example = "default";
};
2019-02-28 14:04:47 +01:00
2022-04-02 16:12:17 -07:00
customResources = mkOption {
description = "Setup custom resources";
type = types.listOf types.attrs;
default = [];
};
resourceOrder = mkOption {
description = "Preffered resource order";
type = types.listOf types.str;
default = [
"CustomResourceDefinition"
"Namespace"
];
};
api = mkOption {
type = types.submodule {
2022-04-02 12:40:35 -07:00
imports =
[
./generated/v${cfg.version}.nix
2022-04-02 12:40:35 -07:00
apiOptions
]
++ customResourceOptions;
};
2022-04-02 12:40:35 -07:00
default = {};
};
imports = mkOption {
type = types.listOf (types.either types.package types.path);
description = "List of resources to import";
2022-04-02 12:40:35 -07:00
default = [];
};
resources = mkOption {
description = "Alias for `config.kubernetes.api.resources` options";
2022-04-02 12:40:35 -07:00
default = {};
type = types.attrsOf types.attrs;
};
customTypes = mkOption {
description = "Custom resource types to make API for";
example = {
2022-08-31 21:38:37 -04:00
helmchartconfig = {
attrName = "helmchartconfig";
kind = "HelmChartConfig";
version = "v1";
group = "helm.cattle.io";
};
};
2022-04-02 12:40:35 -07:00
type =
coerceListOfSubmodulesToAttrs
2021-05-13 17:27:08 -04:00
{
options = {
group = mkOption {
description = "Custom type group";
2022-08-31 21:38:37 -04:00
example = "helm.cattle.io";
2021-05-13 17:27:08 -04:00
type = types.str;
};
2021-05-13 17:27:08 -04:00
version = mkOption {
description = "Custom type version";
2022-08-31 21:38:37 -04:00
example = "v1";
2021-05-13 17:27:08 -04:00
type = types.str;
};
2021-05-13 17:27:08 -04:00
kind = mkOption {
description = "Custom type kind";
2022-08-31 21:38:37 -04:00
example = "HelmChartConfig";
2021-05-13 17:27:08 -04:00
type = types.str;
};
2021-05-13 17:27:08 -04:00
name = mkOption {
description = "Custom type resource name";
type = types.nullOr types.str;
default = null;
};
2021-05-13 17:27:08 -04:00
attrName = mkOption {
description = "Name of the nixified attribute";
2022-08-31 21:38:37 -04:00
# default = name;
2021-05-13 17:27:08 -04:00
type = types.str;
};
2021-05-13 17:27:08 -04:00
description = mkOption {
description = "Custom type description";
type = types.str;
default = "";
};
2021-05-13 17:27:08 -04:00
module = mkOption {
description = "Custom type module";
type = types.unspecified;
2022-04-02 12:40:35 -07:00
default = {};
2021-05-13 17:27:08 -04:00
};
};
2021-05-13 17:27:08 -04:00
}
gvkKeyFn;
2022-04-02 12:40:35 -07:00
default = {};
};
objects = mkOption {
description = "List of generated kubernetes objects";
type = types.listOf types.attrs;
2022-04-02 12:40:35 -07:00
apply = items:
sort
(
r1: r2:
if elem r1.kind cfg.resourceOrder && elem r2.kind cfg.resourceOrder
then indexOf cfg.resourceOrder r1.kind < indexOf cfg.resourceOrder r2.kind
else if elem r1.kind cfg.resourceOrder
then true
else false
2021-05-13 17:27:08 -04:00
)
(unique items);
2022-04-02 12:40:35 -07:00
default = [];
};
generated = mkOption {
description = "Generated kubernetes list object";
type = types.attrs;
};
result = mkOption {
description = "Generated kubernetes JSON file";
type = types.package;
};
2020-04-05 21:25:34 +07:00
resultYAML = mkOption {
description = "Genrated kubernetes YAML file";
type = types.package;
};
};
config = {
# features that module is defining
2022-04-02 12:40:35 -07:00
_m.features = ["k8s"];
# module propagation options
2022-04-02 12:40:35 -07:00
_m.propagate = [
{
features = ["k8s"];
2022-04-02 13:43:57 -07:00
module = _: {
2022-04-02 12:40:35 -07:00
# propagate kubernetes version and namespace
kubernetes.version = mkDefault cfg.version;
kubernetes.namespace = mkDefault cfg.namespace;
};
}
2021-05-13 17:27:08 -04:00
{
2022-04-02 12:40:35 -07:00
features = ["k8s" "submodule"];
module = {config, ...}: {
2021-05-13 17:27:08 -04:00
# set module defaults
2022-04-02 14:42:22 -07:00
kubernetes.api.defaults =
(filter (default: default.propagate) cfg.api.defaults)
2022-04-02 12:40:35 -07:00
++ [
2021-05-13 17:27:08 -04:00
# set module name and version for all kuberentes resources
{
default.metadata.labels = {
"kubenix/module-name" = config.submodule.name;
"kubenix/module-version" = config.submodule.version;
};
}
2022-04-02 13:43:57 -07:00
];
2021-05-13 17:27:08 -04:00
};
2022-04-02 12:40:35 -07:00
}
];
# expose k8s helper methods as module argument
2022-04-02 12:40:35 -07:00
_module.args.k8s = import ../lib/k8s {inherit lib;};
2022-04-02 12:40:35 -07:00
kubernetes.api = mkMerge ([
{
# register custom types
types =
mapAttrsToList
(_: cr: {
inherit (cr) name group version kind attrName;
})
cfg.customTypes;
defaults = [
{
default = {
# set default kubernetes namespace to all resources
metadata.namespace =
mkIf (config.kubernetes.namespace != null)
(mkDefault config.kubernetes.namespace);
# set project name to all resources
metadata.annotations = {
"kubenix/project-name" = config.kubenix.project;
"kubenix/k8s-version" = cfg.version;
};
};
}
];
}
]
++
# import of yaml files
(map
(i: let
2021-05-13 17:27:08 -04:00
# load yaml file
2021-05-31 17:24:59 -05:00
object = importYAML i;
2021-05-13 17:27:08 -04:00
groupVersion = splitString "/" object.apiVersion;
2022-04-02 13:43:57 -07:00
inherit (object.metadata) name;
2021-05-13 17:27:08 -04:00
version = last groupVersion;
group =
if version == (head groupVersion)
2022-04-02 12:40:35 -07:00
then "core"
else head groupVersion;
2022-04-02 13:43:57 -07:00
inherit (object) kind;
2022-04-02 12:40:35 -07:00
in {
2021-05-13 17:27:08 -04:00
resources.${group}.${version}.${kind}.${name} = object;
})
2022-04-02 12:40:35 -07:00
cfg.imports));
2021-05-13 17:27:08 -04:00
kubernetes.objects = flatten (mapAttrsToList
2022-04-02 12:40:35 -07:00
(
_: type:
2022-04-02 13:43:57 -07:00
mapAttrsToList (_name: moduleToAttrs)
2021-05-13 17:27:08 -04:00
cfg.api.resources.${type.group}.${type.version}.${type.kind}
)
cfg.api.types);
kubernetes.generated = k8s.mkHashedList {
items = config.kubernetes.objects;
labels."kubenix/project-name" = config.kubenix.project;
labels."kubenix/k8s-version" = config.kubernetes.version;
};
kubernetes.result =
2020-04-05 21:25:34 +07:00
pkgs.writeText "${config.kubenix.project}-generated.json" (builtins.toJSON cfg.generated);
kubernetes.resultYAML =
2022-04-02 13:43:57 -07:00
toMultiDocumentYaml "${config.kubenix.project}-generated.yaml" config.kubernetes.objects;
};
}