kubenix/modules/k8s.nix

403 lines
12 KiB
Nix
Raw Normal View History

# K8S module defines kubernetes definitions for kubenix
{ options, config, lib, pkgs, k8s, ... }:
with lib;
let
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:
catAttrs "default" (filter (default:
2019-02-27 14:18:38 +01:00
(resource == null || default.resource == null || default.resource == resource) &&
2019-02-26 21:22:03 +01:00
(default.group == null || default.group == group) &&
(default.version == null || default.version == version) &&
(default.kind == null || default.kind == kind)
2019-02-28 14:04:16 +01:00
) cfg.api.defaults);
2019-02-26 21:22:03 +01:00
moduleToAttrs = value:
if isAttrs value
then mapAttrs (n: v: moduleToAttrs v) (filterAttrs (n: v: v != null && !(hasPrefix "_" n)) value)
else if isList value
then map (v: moduleToAttrs v) value
else value;
apiOptions = { config, ... }: {
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";
type = types.listOf (types.submodule ({config, ...}: {
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;
default = {};
};
};
}));
default = [];
};
types = mkOption {
description = "List of registered kubernetes types";
type = coerceListOfSubmodulesToAttrs {
options = {
group = mkOption {
description = "Resource type group";
type = types.str;
};
version = mkOption {
description = "Resoruce type version";
type = types.str;
};
kind = mkOption {
description = "Resource type kind";
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;
};
};
} gvkKeyFn;
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));
compareVersions = ver1: ver2: let
getVersion = v: substring 1 10 v;
splittedVer1 = builtins.splitVersion (getVersion ver1);
splittedVer2 = builtins.splitVersion (getVersion ver2);
v1 = if length splittedVer1 == 1 then "${getVersion ver1}prod" else getVersion ver1;
v2 = if length splittedVer2 == 1 then "${getVersion ver2}prod" else getVersion ver2;
in builtins.compareVersions v1 v2;
customResourceTypesByAttrName = zipAttrs (mapAttrsToList (_: resourceType: {
${resourceType.attrName} = resourceType;
}) cfg.customTypes);
customResourceTypesByAttrNameSortByVersion = mapAttrs (_: resourceTypes:
reverseList (sort (r1: r2:
compareVersions r1.version r2.version > 0
) resourceTypes)
) customResourceTypesByAttrName;
latestCustomResourceTypes =
mapAttrsToList (_: resources: last resources) customResourceTypesByAttrNameSortByVersion;
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);
default = {};
};
};
config = {
apiVersion = mkOptionDefault "${ct.group}/${ct.version}";
kind = mkOptionDefault ct.kind;
2019-10-21 13:47:21 +02:00
metadata.name = mkDefault name;
};
};
customResourceOptions = (mapAttrsToList (_: ct: {config, ...}: let
module = customResourceModuleForType config ct;
in {
options.resources.${ct.group}.${ct.version}.${ct.kind} = mkOption {
description = ct.description;
type = types.attrsOf (types.submodule module);
default = {};
};
}) cfg.customTypes) ++ (map (ct: { options, config, ... }: 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} =
mkAliasDefinitions options.resources.${ct.attrName};
}) latestCustomResourceTypes);
in {
imports = [ ./base.nix ];
options.kubernetes = {
version = mkOption {
description = "Kubernetes version to use";
type = types.enum ["1.7" "1.8" "1.9" "1.10" "1.11" "1.12" "1.13" "1.14" "1.15"];
default = "1.15";
};
namespace = mkOption {
description = "Default namespace where to deploy kubernetes resources";
type = types.str;
default = "default";
};
2019-02-28 14:04:47 +01:00
resourceOrder = mkOption {
description = "Preffered resource order";
type = types.listOf types.str;
default = [
"CustomResourceDefinition"
"Namespace"
];
};
api = mkOption {
type = types.submodule {
imports = [
(./generated + ''/v'' + cfg.version + ".nix")
apiOptions
] ++ customResourceOptions;
};
default = {};
};
imports = mkOption {
type = types.listOf (types.either types.package types.path);
description = "List of resources to import";
default = [];
};
resources = mkOption {
description = "Alias for `config.kubernetes.api.resources` options";
2019-10-06 21:42:19 +02:00
default = {};
type = types.attrsOf types.attrs;
};
createCustomTypesFromCRDs = mkOption {
description = "Whether to create customTypes from custom resource definitions";
type = types.bool;
default = false;
};
customTypes = mkOption {
description = "List of custom resource types to make API for";
type = coerceListOfSubmodulesToAttrs {
options = {
group = mkOption {
description = "Custom type group";
type = types.str;
};
version = mkOption {
description = "Custom type version";
type = types.str;
};
kind = mkOption {
description = "Custom type kind";
type = types.str;
};
name = mkOption {
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 type description";
type = types.str;
default = "";
};
module = mkOption {
description = "Custom type module";
type = types.unspecified;
default = {};
};
};
} gvkKeyFn;
default = {};
};
objects = mkOption {
description = "List of generated kubernetes objects";
type = types.listOf types.attrs;
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
) (unique items);
default = [];
};
generated = mkOption {
description = "Generated kubernetes list object";
type = types.attrs;
};
};
config = {
# expose k8s helper methods as module argument
_module.args.k8s = import ../lib/k8s.nix { inherit lib; };
# features that module is defining
_module.features = [ "k8s" ];
# module propagation options
_module.propagate = [{
features = ["k8s"];
module = { config, ... }: {
# propagate kubernetes version and namespace
kubernetes.version = mkDefault cfg.version;
kubernetes.namespace = mkDefault cfg.namespace;
};
} {
features = ["k8s" "submodule"];
module = { config, ... }: {
# set module defaults
kubernetes.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
{
default.metadata.labels = {
"kubenix/module-name" = config.submodule.name;
"kubenix/module-version" = config.submodule.version;
};
}
]
);
};
}];
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 = mkDefault config.kubernetes.namespace;
# set project name to all resources
metadata.labels."kubenix/project-name" = config.kubenix.project;
};
}];
}] ++
# import of yaml files
(map (i: let
# load yaml file
object = loadYAML i;
groupVersion = splitString "/" object.apiVersion;
name = object.metadata.name;
version = last groupVersion;
group =
if version == (head groupVersion)
then "core" else head groupVersion;
kind = object.kind;
in {
resources.${group}.${version}.${kind}.${name} = object;
}) cfg.imports));
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 (
2019-10-21 13:47:21 +02:00
mapAttrsToList (name: crd: {
group = crd.spec.group;
version = crd.spec.version;
kind = crd.spec.names.kind;
name = crd.spec.names.plural;
2019-10-21 13:47:21 +02:00
attrName = mkOptionDefault name;
}) (cfg.resources.customResourceDefinitions or {})
);
kubernetes.generated = k8s.mkHashedList {
items = config.kubernetes.objects;
labels."kubenix/project-name" = config.kubenix.project;
};
};
}