mirror of
https://github.com/TECHNOFAB11/kubenix.git
synced 2025-12-12 08:00:06 +01:00
Initial refactoring for kubenix 2.0
Implemented features: - Improved and reimplemented submodule system, independent of kubernetes module definitions - Pre-generated kubernetes module definitions with explicit API versioning support
This commit is contained in:
parent
7287c4ed9e
commit
3dc1e615c4
20 changed files with 207916 additions and 751 deletions
11
README.md
11
README.md
|
|
@ -8,12 +8,13 @@ KubeNix is a kubernetes resource builder, that uses nix module system for
|
|||
definition of kubernetes resources and nix build system for building complex
|
||||
kubernetes resources very easyly.
|
||||
|
||||
### Features
|
||||
## Development
|
||||
|
||||
- Loading and override of kubernetes json and yaml files
|
||||
- Support for complex merging of kubernetes resource definitions
|
||||
- No more helm stupid yaml templating, nix is a way better templating language
|
||||
- Support for all kubernetes versions
|
||||
### Building tests
|
||||
|
||||
```shell
|
||||
nix-build release.nix -A test --show-trace
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
110
default.nix
110
default.nix
|
|
@ -1,105 +1,31 @@
|
|||
{
|
||||
pkgs ? import <nixpkgs> {}
|
||||
}:
|
||||
|
||||
with pkgs.lib;
|
||||
with import ./lib.nix { inherit pkgs; inherit (pkgs) lib; };
|
||||
{ pkgs ? import <nixpkgs> {}, lib ? pkgs.lib }:
|
||||
|
||||
let
|
||||
evalKubernetesModules = configuration: evalModules rec {
|
||||
lib' = lib.extend (lib: self: import ./lib.nix { inherit lib pkgs; });
|
||||
|
||||
specialArgs = {
|
||||
inherit kubenix;
|
||||
};
|
||||
|
||||
evalKubernetesModules = configuration: lib'.evalModules rec {
|
||||
modules = [
|
||||
./kubernetes.nix
|
||||
./modules.nix configuration
|
||||
configuration
|
||||
];
|
||||
args = {
|
||||
inherit pkgs;
|
||||
name = "default";
|
||||
k8s = import ./k8s.nix {
|
||||
inherit pkgs;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
module = null;
|
||||
};
|
||||
inherit specialArgs;
|
||||
};
|
||||
|
||||
flattenResources = resources: flatten (
|
||||
mapAttrsToList (name: resourceGroup:
|
||||
mapAttrsToList (name: resource: resource) resourceGroup
|
||||
) resources
|
||||
);
|
||||
buildResources = configuration:
|
||||
(evalKubernetesModules configuration).config.kubernetes.generated;
|
||||
|
||||
filterResources = resourceFilter: resources:
|
||||
mapAttrs (groupName: resources:
|
||||
(filterAttrs (name: resource:
|
||||
resourceFilter groupName name resource
|
||||
) resources)
|
||||
) resources;
|
||||
kubenix = {
|
||||
inherit buildResources kubenix;
|
||||
|
||||
toKubernetesList = resources: {
|
||||
kind = "List";
|
||||
apiVersion = "v1";
|
||||
items = resources;
|
||||
lib = lib';
|
||||
submodules = ./submodules.nix;
|
||||
k8s = ./k8s;
|
||||
};
|
||||
|
||||
removeNixOptions = resources:
|
||||
map (filterAttrs (name: attr: name != "nix")) resources;
|
||||
|
||||
buildResources = {
|
||||
configuration ? {},
|
||||
resourceFilter ? groupName: name: resource: true,
|
||||
withDependencies ? true
|
||||
}: let
|
||||
evaldConfiguration = evalKubernetesModules configuration;
|
||||
|
||||
allResources = moduleToAttrs (
|
||||
evaldConfiguration.config.kubernetes.resources //
|
||||
evaldConfiguration.config.kubernetes.customResources
|
||||
);
|
||||
|
||||
filteredResources = filterResources resourceFilter allResources;
|
||||
|
||||
allDependencies = flatten (
|
||||
mapAttrsToList (groupName: resources:
|
||||
mapAttrsToList (name: resource: resource.nix.dependencies) resources
|
||||
) filteredResources
|
||||
);
|
||||
|
||||
resourceDependencies =
|
||||
filterResources (groupName: name: resource:
|
||||
elem "${groupName}/${name}" allDependencies
|
||||
) allResources;
|
||||
|
||||
finalResources =
|
||||
if withDependencies
|
||||
then recursiveUpdate resourceDependencies filteredResources
|
||||
else filteredResources;
|
||||
|
||||
resources = unique (removeNixOptions (
|
||||
# custom resource definitions have to be allways created first
|
||||
(flattenResources (filterResources (groupName: name: resource:
|
||||
groupName == "customResourceDefinitions"
|
||||
) finalResources)) ++
|
||||
|
||||
# everything but custom resource definitions
|
||||
(flattenResources (filterResources (groupName: name: resource:
|
||||
groupName != "customResourceDefinitions"
|
||||
) finalResources))
|
||||
));
|
||||
|
||||
kubernetesList = toKubernetesList resources;
|
||||
|
||||
listHash = builtins.hashString "sha1" (builtins.toJSON kubernetesList);
|
||||
|
||||
hashedList = kubernetesList // {
|
||||
labels."kubenix/build" = listHash;
|
||||
items = map (resource: recursiveUpdate resource {
|
||||
metadata.labels."kubenix/build" = listHash;
|
||||
}) kubernetesList.items;
|
||||
};
|
||||
in pkgs.writeText "resources.json" (builtins.toJSON hashedList);
|
||||
|
||||
in {
|
||||
inherit buildResources;
|
||||
|
||||
test = buildResources { configuration = ./test/default.nix; };
|
||||
}
|
||||
in kubenix
|
||||
|
|
|
|||
67
examples/module/nginx.nix
Normal file
67
examples/module/nginx.nix
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{ config, lib, pkgs, kubenix, k8s, submodule, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
name = submodule.name;
|
||||
in {
|
||||
imports = [
|
||||
kubenix.k8s
|
||||
];
|
||||
|
||||
options.args = {
|
||||
replicas = mkOption {
|
||||
type = types.int;
|
||||
description = "Number of nginx replicas to run";
|
||||
default = 1;
|
||||
};
|
||||
|
||||
simpleAuth = k8s.mkSecretOption {
|
||||
description = "Simple auth";
|
||||
default = {
|
||||
key = "name";
|
||||
name = "value";
|
||||
};
|
||||
};
|
||||
|
||||
image = mkOption {
|
||||
description = "Image";
|
||||
type = types.str;
|
||||
default = "nginx";
|
||||
};
|
||||
};
|
||||
|
||||
config = {
|
||||
submodule = {
|
||||
name = "nginx";
|
||||
version = "1.0.0";
|
||||
description = "Nginx module";
|
||||
passthru = {
|
||||
kubernetes.objects = config.kubernetes.objects;
|
||||
};
|
||||
};
|
||||
|
||||
kubernetes.api.Deployment.nginx = {
|
||||
metadata = {
|
||||
name = name;
|
||||
labels = {
|
||||
module = config.submodule.name;
|
||||
};
|
||||
};
|
||||
|
||||
spec = {
|
||||
replicas = config.args.replicas;
|
||||
selector.matchLabels.app = "nginx";
|
||||
template.metadata.labels.app = "nginx";
|
||||
template.spec = {
|
||||
containers.nginx = {
|
||||
image = config.args.image;
|
||||
env = {
|
||||
SIMPLE_AUTH = k8s.secretToEnv config.args.simpleAuth;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
55
k8s.nix
55
k8s.nix
|
|
@ -1,55 +0,0 @@
|
|||
{lib, pkgs}:
|
||||
|
||||
with lib;
|
||||
with import ./lib.nix {inherit lib pkgs;};
|
||||
|
||||
rec {
|
||||
loadJSON = path: mkAllDefault (builtins.fromJSON (builtins.readFile path)) 1000;
|
||||
|
||||
loadYAML = path: loadJSON (pkgs.runCommand "yaml-to-json" {
|
||||
} "${pkgs.remarshal}/bin/remarshal -i ${path} -if yaml -of json > $out");
|
||||
|
||||
toYAML = config: builtins.readFile (pkgs.runCommand "to-yaml" {
|
||||
buildInputs = [pkgs.remarshal];
|
||||
} ''
|
||||
remarshal -i ${pkgs.writeText "to-json" (builtins.toJSON config)} -if json -of yaml > $out
|
||||
'');
|
||||
|
||||
toBase64 = value:
|
||||
builtins.readFile
|
||||
(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);
|
||||
|
||||
octalToDecimal = value:
|
||||
(foldr (char: acc: {
|
||||
i = acc.i + 1;
|
||||
value = acc.value + (toInt char) * (exp 8 acc.i);
|
||||
}) {i = 0; value = 0;} (stringToCharacters value)).value;
|
||||
|
||||
mkSecretOption = {...}@options: mkOption (options // {
|
||||
type = types.nullOr (types.submodule {
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Name of the secret where secret is stored";
|
||||
type = types.str;
|
||||
} // optionalAttrs (hasAttr "default" options && options.default != null && hasAttr "name" options.default) {
|
||||
default = options.default.name;
|
||||
};
|
||||
|
||||
key = mkOption {
|
||||
description = "Name of the key where secret is stored";
|
||||
type = types.str;
|
||||
} // optionalAttrs (hasAttr "default" options && options.default != null && hasAttr "key" options.default) {
|
||||
default = options.default.key;
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
secretToEnv = value: {
|
||||
valueFrom.secretKeyRef = {
|
||||
inherit (value) name key;
|
||||
};
|
||||
};
|
||||
}
|
||||
201
k8s/default.nix
Normal file
201
k8s/default.nix
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
isModule = hasAttr "module" config;
|
||||
|
||||
removeKubenixOptions = filterAttrs (name: attr: name != "kubenix");
|
||||
|
||||
moduleToAttrs = value:
|
||||
if isAttrs value
|
||||
then mapAttrs (n: v: moduleToAttrs v) (filterAttrs (n: v: !(hasPrefix "_" n) && v != null) value)
|
||||
|
||||
else if isList value
|
||||
then map (v: moduleToAttrs v) value
|
||||
|
||||
else value;
|
||||
|
||||
flattenResources = resources: flatten (
|
||||
mapAttrsToList (groupName: versions:
|
||||
mapAttrsToList (versionName: kinds:
|
||||
builtins.trace versionName kinds
|
||||
) versions
|
||||
) resources
|
||||
);
|
||||
|
||||
toKubernetesList = resources: {
|
||||
kind = "List";
|
||||
apiVersion = "v1";
|
||||
items = resources;
|
||||
};
|
||||
|
||||
apiOptions = { config, ... }: {
|
||||
options = {
|
||||
definitions = mkOption {
|
||||
description = "Attribute set of kubernetes definitions";
|
||||
};
|
||||
|
||||
defaults = mkOption {
|
||||
description = "Kubernetes defaults";
|
||||
type = types.attrsOf (types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified));
|
||||
default = {};
|
||||
};
|
||||
|
||||
resources = mkOption {
|
||||
type = types.listOf (types.submodule {
|
||||
options = {
|
||||
group = mkOption {
|
||||
description = "Group name";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
description = "Version name";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
kind = mkOption {
|
||||
description = "kind name";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
plural = mkOption {
|
||||
description = "Plural name";
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
});
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
imports = [./lib.nix];
|
||||
|
||||
options.kubernetes.version = mkOption {
|
||||
description = "Kubernetes version to use";
|
||||
type = types.enum ["1.7" "1.8" "1.9" "1.10"];
|
||||
default = "1.10";
|
||||
};
|
||||
|
||||
options.kubernetes.api = mkOption {
|
||||
type = types.submodule {
|
||||
imports = [
|
||||
(./generated + ''/v'' + config.kubernetes.version + ".nix")
|
||||
apiOptions
|
||||
] ++ (map (cr: {config, ...}: {
|
||||
options.${cr.group}.${cr.version}.${cr.kind} = mkOption {
|
||||
description = cr.description;
|
||||
type = types.attrsOf (types.submodule ({name, ...}: {
|
||||
imports = [cr.module];
|
||||
|
||||
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");
|
||||
};
|
||||
};
|
||||
|
||||
config = mkMerge ([{
|
||||
apiVersion = mkOptionDefault "${cr.group}/${cr.version}";
|
||||
kind = mkOptionDefault cr.kind;
|
||||
metadata.name = mkOptionDefault name;
|
||||
}]
|
||||
++ (config.kubernetes.defaults.all or []));
|
||||
}));
|
||||
default = {};
|
||||
};
|
||||
}) config.kubernetes.customResources);
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
|
||||
options.kubernetes.customResources = mkOption {
|
||||
default = [];
|
||||
type = types.listOf (types.submodule ({config, ...}: {
|
||||
options = {
|
||||
group = mkOption {
|
||||
description = "CRD group";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
description = "CRD version";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
kind = mkOption {
|
||||
description = "CRD kind";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
plural = mkOption {
|
||||
description = "CRD plural name";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
description = "CRD description";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
module = mkOption {
|
||||
description = "CRD module";
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
config.kubernetes.api.resources = map (cr: {
|
||||
inherit (cr) group version kind plural;
|
||||
}) config.kubernetes.customResources;
|
||||
|
||||
options.kubernetes.propagateDefaults = mkOption {
|
||||
description = "Whehter to propagate child defaults";
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
options.kubernetes.objects = mkOption {
|
||||
description = "Attribute set of kubernetes objects";
|
||||
type = types.listOf types.attrs;
|
||||
apply = unique;
|
||||
default = [];
|
||||
};
|
||||
|
||||
config.kubernetes.objects = flatten (map (gvk:
|
||||
mapAttrsToList (name: resource:
|
||||
removeKubenixOptions (moduleToAttrs resource)
|
||||
) config.kubernetes.api.${gvk.group}.${gvk.version}.${gvk.kind}
|
||||
) config.kubernetes.api.resources);
|
||||
|
||||
options.kubernetes.generated = mkOption {
|
||||
type = types.package;
|
||||
description = "Generated json file";
|
||||
};
|
||||
|
||||
config.kubernetes.generated = let
|
||||
kubernetesList = toKubernetesList config.kubernetes.objects;
|
||||
|
||||
listHash = builtins.hashString "sha1" (builtins.toJSON kubernetesList);
|
||||
|
||||
hashedList = kubernetesList // {
|
||||
labels."kubenix/build" = listHash;
|
||||
items = map (resource: recursiveUpdate resource {
|
||||
metadata.labels."kubenix/build" = listHash;
|
||||
}) kubernetesList.items;
|
||||
};
|
||||
in pkgs.writeText "resources.json" (builtins.toJSON hashedList);
|
||||
}
|
||||
29987
k8s/generated/v1.10.nix
Normal file
29987
k8s/generated/v1.10.nix
Normal file
File diff suppressed because it is too large
Load diff
29987
k8s/generated/v1.7.nix
Normal file
29987
k8s/generated/v1.7.nix
Normal file
File diff suppressed because it is too large
Load diff
29987
k8s/generated/v1.8.nix
Normal file
29987
k8s/generated/v1.8.nix
Normal file
File diff suppressed because it is too large
Load diff
29987
k8s/generated/v1.9.nix
Normal file
29987
k8s/generated/v1.9.nix
Normal file
File diff suppressed because it is too large
Load diff
373
k8s/generator.nix
Normal file
373
k8s/generator.nix
Normal file
|
|
@ -0,0 +1,373 @@
|
|||
{ pkgs ? import <nixpkgs> {}
|
||||
, lib ? pkgs.lib
|
||||
, spec ? ./specs/1.10/swagger.json
|
||||
, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
gen = rec {
|
||||
mkMerge = values: ''mkMerge [${concatMapStrings (value: "
|
||||
${value}
|
||||
") values}]'';
|
||||
|
||||
toNixString = value: if isAttrs value || isList value
|
||||
then builtins.toJSON value
|
||||
else if isString value
|
||||
then ''"${value}"''
|
||||
else if value == null
|
||||
then "null"
|
||||
else builtins.toString value;
|
||||
|
||||
removeEmptyLines = str: concatStringsSep "\n" (filter (l: (builtins.match "( |)+" l) == null) (splitString "\n" str));
|
||||
|
||||
mkOption = {
|
||||
description ? null,
|
||||
type ? null,
|
||||
default ? null,
|
||||
apply ? null
|
||||
}: removeEmptyLines ''mkOption {
|
||||
${optionalString (description != null) "description = ${builtins.toJSON description};"}
|
||||
${optionalString (type != null) ''type = ${type};''}
|
||||
${optionalString (default != null) ''default = ${toNixString default};''}
|
||||
${optionalString (apply != null) ''apply = ${apply};''}
|
||||
}'';
|
||||
|
||||
mkOverride = priority: value: "mkOverride ${toString priority} ${toNixString value}";
|
||||
|
||||
types = {
|
||||
unspecified = "types.unspecified";
|
||||
str = "types.str";
|
||||
int = "types.int";
|
||||
bool = "types.bool";
|
||||
attrs = "types.attrs";
|
||||
nullOr = val: "(types.nullOr ${val})";
|
||||
attrsOf = val: "(types.attrsOf ${val})";
|
||||
listOf = val: "(types.listOf ${val})";
|
||||
coercedTo = coercedType: coerceFunc: finalType:
|
||||
"(types.coercedTo ${coercedType} ${coerceFunc} ${finalType})";
|
||||
either = val1: val2: "(types.either ${val1} ${val2})";
|
||||
loaOf = type: "(types.loaOf ${type})";
|
||||
};
|
||||
|
||||
hasTypeMapping = def:
|
||||
hasAttr "type" def &&
|
||||
elem def.type ["string" "integer" "boolean" "object"];
|
||||
|
||||
mergeValuesByKey = mergeKey: ''(mergeValuesByKey "${mergeKey}")'';
|
||||
|
||||
mapType = def:
|
||||
if def.type == "string" then
|
||||
if hasAttr "format" def && def.format == "int-or-string"
|
||||
then types.either types.int types.str
|
||||
else types.str
|
||||
else if def.type == "integer" then types.int
|
||||
else if def.type == "number" then types.int
|
||||
else if def.type == "boolean" then types.bool
|
||||
else if def.type == "object" then types.attrs
|
||||
else throw "type ${def.type} not supported";
|
||||
|
||||
submoduleOf = definitions: ref: ''(submoduleOf "${ref}")'';
|
||||
|
||||
submoduleForDefinition = ref: name: kind: group: version:
|
||||
''(submoduleForDefinition "${ref}" "${name}" "${kind}" "${group}" "${version}")'';
|
||||
|
||||
coerceAttrsOfSubmodulesToListByKey = ref: mergeKey:
|
||||
''(coerceAttrsOfSubmodulesToListByKey "${ref}" "${mergeKey}")'';
|
||||
|
||||
attrsToList = "values: if values != null then mapAttrsToList (n: v: v) values else values";
|
||||
|
||||
refDefinition = attr: head (tail (tail (splitString "/" attr."$ref")));
|
||||
};
|
||||
|
||||
refType = attr: head (tail (tail (splitString "/" attr."$ref")));
|
||||
|
||||
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;
|
||||
|
||||
fixJSON = content: replaceStrings ["\\u"] ["u"] content;
|
||||
|
||||
fetchSpecs = path: builtins.fromJSON (fixJSON (builtins.readFile path));
|
||||
|
||||
genDefinitions = swagger: with gen; mapAttrs (name: definition:
|
||||
# if $ref is in definition it means it's an alias of other definition
|
||||
if hasAttr "$ref" definition
|
||||
then definitions."${refDefinition definition}"
|
||||
|
||||
else if !(hasAttr "properties" definition)
|
||||
then {}
|
||||
|
||||
# in other case it's an actual definition
|
||||
else {
|
||||
options = mapAttrs (propName: property:
|
||||
let
|
||||
isRequired = elem propName (definition.required or []);
|
||||
requiredOrNot = type: if isRequired then type else types.nullOr type;
|
||||
optionProperties =
|
||||
|
||||
# if $ref is in property it references other definition,
|
||||
# but if other definition does not have properties, then just take it's type
|
||||
if hasAttr "$ref" property then
|
||||
if hasTypeMapping swagger.definitions.${refDefinition property} then {
|
||||
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 {
|
||||
type = requiredOrNot (types.listOf (submoduleOf definitions (refDefinition property.items)));
|
||||
}
|
||||
|
||||
# in other case it only references a simple type
|
||||
else {
|
||||
type = requiredOrNot (types.listOf (mapType property.items));
|
||||
}
|
||||
|
||||
else if property.type == "object" && hasAttr "additionalProperties" property
|
||||
then
|
||||
# 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;
|
||||
|
||||
genResources = swagger: mapAttrs' (name: path: let
|
||||
ref = refType (head path.post.parameters).schema;
|
||||
group' = path.post."x-kubernetes-group-version-kind".group;
|
||||
version' = path.post."x-kubernetes-group-version-kind".version;
|
||||
in nameValuePair ref {
|
||||
inherit ref;
|
||||
|
||||
kind = path.post."x-kubernetes-group-version-kind".kind;
|
||||
version = if group' != "" then "${group'}/${version'}" else version';
|
||||
plural = last (splitString "/" name);
|
||||
description = swagger.definitions.${ref}.description;
|
||||
group = if group' == "" then "core" else group';
|
||||
defintion = refDefinition (head path.post.parameters).schema;
|
||||
})
|
||||
(filterAttrs (name: path:
|
||||
hasAttr "post" path &&
|
||||
path.post."x-kubernetes-action" == "post"
|
||||
) swagger.paths);
|
||||
|
||||
swagger = fetchSpecs spec;
|
||||
definitions = genDefinitions swagger;
|
||||
resources = genResources swagger;
|
||||
|
||||
resourcesByKind = zipAttrs (mapAttrsToList (name: resource: {
|
||||
${resource.kind} = resource;
|
||||
}) resources);
|
||||
|
||||
resourcesByKindOrderedByVersion = mapAttrs (kind: resources:
|
||||
reverseList (sort (r1: r2:
|
||||
if compareVersions r1.version r2.version < 0
|
||||
then true else false
|
||||
) resources)
|
||||
) resourcesByKind;
|
||||
|
||||
latestResourcesByKind =
|
||||
mapAttrs (kind: resources: last resources) resourcesByKindOrderedByVersion;
|
||||
|
||||
genResourceOptions = resource: with gen; let
|
||||
submoduleForDefinition' = definition: let
|
||||
in submoduleForDefinition
|
||||
definition.ref definition.plural definition.kind definition.group definition.version;
|
||||
in mkOption {
|
||||
description = resource.description;
|
||||
type = types.attrsOf (submoduleForDefinition' resource);
|
||||
default = {};
|
||||
};
|
||||
in pkgs.writeText "gen.nix"
|
||||
"# This file was generated with kubenix k8s generator, do not edit
|
||||
{lib, config, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
types = lib.types // rec {
|
||||
str = mkOptionType {
|
||||
name = \"str\";
|
||||
description = \"string\";
|
||||
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; };
|
||||
};
|
||||
};
|
||||
|
||||
mkOptionDefault = mkOverride 1001;
|
||||
|
||||
extraOptions = {
|
||||
kubenix = {};
|
||||
};
|
||||
|
||||
mergeValuesByKey = mergeKey: values:
|
||||
listToAttrs (map
|
||||
(value: nameValuePair (
|
||||
if isAttrs value.\${mergeKey}
|
||||
then toString value.\${mergeKey}.content
|
||||
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
|
||||
(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)}
|
||||
};
|
||||
in {
|
||||
options = {${concatStrings (mapAttrsToList (name: resource: "
|
||||
\"${resource.group}\".\"${resource.version}\".\"${resource.kind}\" = ${genResourceOptions resource};
|
||||
") resources)}} // {${concatStrings (mapAttrsToList (name: resource: "
|
||||
\"${resource.kind}\" = ${genResourceOptions resource};
|
||||
") latestResourcesByKind)}};
|
||||
|
||||
config = {${concatStrings (mapAttrsToList (name: resource: "
|
||||
\"${resource.group}\".\"${resource.version}\".\"${resource.kind}\" = config.\"${resource.kind}\";
|
||||
") latestResourcesByKind)}} // {
|
||||
inherit definitions;
|
||||
|
||||
resources = [${concatStrings (mapAttrsToList (name: resource: "{
|
||||
group = \"${resource.group}\";
|
||||
version = \"${resource.version}\";
|
||||
kind = \"${resource.kind}\";
|
||||
plural = \"${resource.plural}\";
|
||||
}") resources)}];
|
||||
};
|
||||
}
|
||||
"
|
||||
35
k8s/lib.nix
Normal file
35
k8s/lib.nix
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
{ lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
k8s = {
|
||||
mkSecretOption = {description ? "", default ? {}}: mkOption {
|
||||
inherit description;
|
||||
type = types.nullOr (types.submodule {
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Name of the secret where secret is stored";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
key = mkOption {
|
||||
description = "Name of the key where secret is stored";
|
||||
type = types.str;
|
||||
};
|
||||
};
|
||||
|
||||
config = mkDefault default;
|
||||
});
|
||||
default = {};
|
||||
};
|
||||
|
||||
secretToEnv = value: {
|
||||
valueFrom.secretKeyRef = {
|
||||
inherit (value) name key;
|
||||
};
|
||||
};
|
||||
};
|
||||
in {
|
||||
_module.args.k8s = k8s;
|
||||
}
|
||||
86851
k8s/specs/1.10/swagger.json
Normal file
86851
k8s/specs/1.10/swagger.json
Normal file
File diff suppressed because it is too large
Load diff
362
kubernetes.nix
362
kubernetes.nix
|
|
@ -1,362 +0,0 @@
|
|||
{ config, lib, k8s, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
with import ./lib.nix { inherit pkgs; inherit (pkgs) lib; };
|
||||
|
||||
let
|
||||
fixJSON = content: replaceStrings ["\\u"] ["u"] content;
|
||||
|
||||
fetchSpecs = path: builtins.fromJSON (fixJSON (builtins.readFile path));
|
||||
|
||||
hasTypeMapping = def:
|
||||
hasAttr "type" def &&
|
||||
elem def.type ["string" "integer" "boolean" "object"];
|
||||
|
||||
str = mkOptionType {
|
||||
name = "str";
|
||||
description = "string";
|
||||
check = isString;
|
||||
merge = mergeEqualOption;
|
||||
};
|
||||
|
||||
mapType = def:
|
||||
if def.type == "string" then
|
||||
if hasAttr "format" def && def.format == "int-or-string"
|
||||
then types.either types.int str
|
||||
else str
|
||||
else if def.type == "integer" then types.int
|
||||
else if def.type == "boolean" then types.bool
|
||||
else if def.type == "object" then types.attrs
|
||||
else throw "type ${def.type} not supported";
|
||||
|
||||
# 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; };
|
||||
};
|
||||
|
||||
submoduleOf = definition: types.submodule ({name, ...}: {
|
||||
options = definition.options;
|
||||
config = definition.config;
|
||||
});
|
||||
|
||||
refType = attr: head (tail (tail (splitString "/" attr."$ref")));
|
||||
|
||||
mkOptionDefault = mkOverride 1001;
|
||||
|
||||
extraOptions = {
|
||||
nix.dependencies = mkOption {
|
||||
description = "List of resources that resource depends on";
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
|
||||
definitionsForKubernetesSpecs = path:
|
||||
let
|
||||
swagger = fetchSpecs path;
|
||||
swaggerDefinitions = swagger.definitions;
|
||||
|
||||
definitions = mapAttrs (name: definition:
|
||||
# if $ref is in definition it means it's an alias of other definition
|
||||
if hasAttr "$ref" definition
|
||||
then definitions."${refType definition}"
|
||||
|
||||
else if !(hasAttr "properties" definition)
|
||||
then {}
|
||||
|
||||
# in other case it's an actual definition
|
||||
else {
|
||||
options = mapAttrs (propName: property:
|
||||
let
|
||||
isRequired = elem propName (definition.required or []);
|
||||
requiredOrNot = type: if isRequired then type else types.nullOr type;
|
||||
optionProperties =
|
||||
# if $ref is in property it references other definition,
|
||||
# but if other definition does not have properties, then just take it's type
|
||||
if hasAttr "$ref" property then
|
||||
if hasTypeMapping swaggerDefinitions.${refType property} then {
|
||||
type = requiredOrNot (mapType swaggerDefinitions.${refType property});
|
||||
}
|
||||
else {
|
||||
type = requiredOrNot (submoduleOf definitions.${refType 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 swaggerDefinitions.${refType property.items}
|
||||
then {
|
||||
type = requiredOrNot (types.listOf (mapType swaggerDefinitions.${refType 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";
|
||||
convertName = name:
|
||||
if definitions.${refType property.items}.options.${mergeKey}.type == types.int
|
||||
then toInt name
|
||||
else name;
|
||||
in {
|
||||
type = requiredOrNot (coercedTo
|
||||
(types.listOf (submoduleOf definitions.${refType property.items}))
|
||||
(values:
|
||||
listToAttrs (map
|
||||
(value: nameValuePair (
|
||||
if isAttrs value.${mergeKey}
|
||||
then toString value.${mergeKey}.content
|
||||
else (toString value.${mergeKey})
|
||||
) value)
|
||||
values)
|
||||
)
|
||||
(types.attrsOf (types.submodule (
|
||||
{name, ...}: {
|
||||
options = definitions.${refType property.items}.options;
|
||||
config = definitions.${refType property.items}.config // {
|
||||
${mergeKey} = mkOverride 1002 (convertName name);
|
||||
};
|
||||
}
|
||||
))
|
||||
));
|
||||
apply = values: if values != null then mapAttrsToList (n: v: v) values else values;
|
||||
}
|
||||
|
||||
# in other case it's a simple list
|
||||
else {
|
||||
type = requiredOrNot (types.listOf (submoduleOf definitions.${refType property.items}));
|
||||
}
|
||||
|
||||
# in other case it only references a simple type
|
||||
else {
|
||||
type = requiredOrNot (types.listOf (mapType property.items));
|
||||
}
|
||||
|
||||
else if property.type == "object" && hasAttr "additionalProperties" property
|
||||
then
|
||||
# if it is a reference to simple type
|
||||
if (
|
||||
hasAttr "$ref" property.additionalProperties &&
|
||||
hasTypeMapping swaggerDefinitions.${refType property.additionalProperties}
|
||||
) then {
|
||||
type = requiredOrNot (types.attrsOf (mapType swaggerDefinitions.${refType property.additionalProperties}));
|
||||
}
|
||||
|
||||
# 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 {
|
||||
inherit (definition) description;
|
||||
} // optionProperties // (optionalAttrs (!isRequired) {
|
||||
})
|
||||
) definition.properties;
|
||||
config =
|
||||
let
|
||||
optionalProps = filterAttrs (propName: property:
|
||||
!(elem propName (definition.required or []))
|
||||
) definition.properties;
|
||||
in mapAttrs (name: property: mkOverride 1002 null) optionalProps;
|
||||
}
|
||||
) swaggerDefinitions;
|
||||
|
||||
exportedDefinitions =
|
||||
zipAttrs (
|
||||
mapAttrsToList (name: path: let
|
||||
kind = path.post."x-kubernetes-group-version-kind".kind;
|
||||
|
||||
lastChar = substring ((stringLength kind)-1) (stringLength kind) kind;
|
||||
|
||||
suffix =
|
||||
if lastChar == "y" then "ies"
|
||||
else if hasSuffix "ss" kind then "ses"
|
||||
else if lastChar == "s" then "s"
|
||||
else "${lastChar}s";
|
||||
|
||||
optionName = "${toLower (substring 0 1 kind)}${substring 1 ((stringLength kind)-2) kind}${suffix}";
|
||||
in {
|
||||
${optionName} = refType (head path.post.parameters).schema;
|
||||
})
|
||||
(filterAttrs (name: path:
|
||||
hasAttr "post" path &&
|
||||
path.post."x-kubernetes-action" == "post"
|
||||
) swagger.paths)
|
||||
);
|
||||
|
||||
kubernetesResourceOptions = mapAttrs (groupName: value:
|
||||
let
|
||||
values = if isList value then reverseList value else [value];
|
||||
definitionName = tail values;
|
||||
|
||||
submoduleWithDefaultsOf = definition: swaggerDefinition: let
|
||||
kind = (head swaggerDefinition."x-kubernetes-group-version-kind").kind;
|
||||
group = (head swaggerDefinition."x-kubernetes-group-version-kind").group;
|
||||
version = (head swaggerDefinition."x-kubernetes-group-version-kind").version;
|
||||
groupVersion = if group != "" then "${group}/${version}" else version;
|
||||
in types.submodule ({name, ...}: {
|
||||
options = definition.options // extraOptions;
|
||||
config = mkMerge ([
|
||||
definition.config
|
||||
{
|
||||
kind = mkOptionDefault kind;
|
||||
apiVersion = mkOptionDefault groupVersion;
|
||||
|
||||
# metdata.name cannot use option default, due deep config
|
||||
metadata.name = mkOptionDefault name;
|
||||
}
|
||||
] ++ config.kubernetes.defaults.${groupName}
|
||||
++ config.kubernetes.defaults.all);
|
||||
});
|
||||
|
||||
type =
|
||||
if (length values) > 1
|
||||
then fold (name: other:
|
||||
types.either (submoduleWithDefaultsOf definitions.${name} swaggerDefinitions.${name}) other
|
||||
) (submoduleWithDefaultsOf definitions.${head values} swaggerDefinitions.${head values}) (drop 1 values)
|
||||
else submoduleWithDefaultsOf definitions.${head values} swaggerDefinitions.${head values};
|
||||
in mkOption {
|
||||
description = swaggerDefinitions.${definitionName}.description;
|
||||
type = types.attrsOf type;
|
||||
default = {};
|
||||
}) exportedDefinitions;
|
||||
|
||||
customResourceOptions = mapAttrs (groupName: crd:
|
||||
mkOption {
|
||||
description = "Custom resource for ${groupName}";
|
||||
type = types.attrsOf (types.submodule ({name, ...}: {
|
||||
options = {
|
||||
apiVersion = mkOption {
|
||||
description = "API version of custom resource";
|
||||
type = types.str;
|
||||
default = "${crd.spec.group}/${crd.spec.version}";
|
||||
};
|
||||
|
||||
kind = mkOption {
|
||||
description = "Custom resource kind";
|
||||
type = types.str;
|
||||
default = crd.spec.names.kind;
|
||||
};
|
||||
|
||||
metadata = mkOption {
|
||||
description = "Metadata";
|
||||
type = submoduleOf definitions."io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta";
|
||||
default = {};
|
||||
};
|
||||
|
||||
spec = mkOption {
|
||||
description = "Custom resource specification";
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
};
|
||||
} // extraOptions;
|
||||
config = mkMerge (
|
||||
config.kubernetes.defaults.${groupName} ++
|
||||
config.kubernetes.defaults.all
|
||||
);
|
||||
}));
|
||||
default = {};
|
||||
}
|
||||
) config.kubernetes.resources.customResourceDefinitions;
|
||||
in {
|
||||
inherit swaggerDefinitions definitions exportedDefinitions kubernetesResourceOptions customResourceOptions;
|
||||
};
|
||||
|
||||
versionDefinitions = {
|
||||
"1.7" = definitionsForKubernetesSpecs ./specs/1.7/swagger.json;
|
||||
"1.8" = definitionsForKubernetesSpecs ./specs/1.8/swagger.json;
|
||||
"1.9" = definitionsForKubernetesSpecs ./specs/1.9/swagger.json;
|
||||
};
|
||||
|
||||
versionOptions = {
|
||||
"1.7" = versionDefinitions."1.7" // {
|
||||
kubernetesResourceOptions = versionDefinitions."1.7".kubernetesResourceOptions // {
|
||||
# kubernetes 1.7 supports crd, but does not have swagger definitions for some reason
|
||||
customResourceDefinitions =
|
||||
versionDefinitions."1.8".kubernetesResourceOptions.customResourceDefinitions;
|
||||
};
|
||||
};
|
||||
"1.8" = versionDefinitions."1.8";
|
||||
"1.9" = versionDefinitions."1.9";
|
||||
};
|
||||
|
||||
defaultOptions = mapAttrs (name: value: mkOption {
|
||||
description = "Kubernetes defaults for ${name} resources";
|
||||
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
||||
default = [];
|
||||
}) (
|
||||
(versionOptions.${config.kubernetes.version}.kubernetesResourceOptions) //
|
||||
(versionOptions.${config.kubernetes.version}.customResourceOptions)
|
||||
);
|
||||
in {
|
||||
options.kubernetes.version = mkOption {
|
||||
description = "Kubernetes version to deploy to";
|
||||
type = types.enum (attrNames versionDefinitions);
|
||||
default = "1.9";
|
||||
};
|
||||
|
||||
options.kubernetes.resources = mkOption {
|
||||
type = types.submodule {
|
||||
options = versionOptions.${config.kubernetes.version}.kubernetesResourceOptions;
|
||||
};
|
||||
description = "Attribute set of kubernetes resources";
|
||||
default = {};
|
||||
};
|
||||
|
||||
options.kubernetes.defaults = mkOption {
|
||||
type = types.submodule {
|
||||
options = defaultOptions // {
|
||||
all = mkOption {
|
||||
description = "Kubernetes defaults for all resources";
|
||||
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
description = "";
|
||||
default = {};
|
||||
};
|
||||
|
||||
options.kubernetes.customResources = mkOption {
|
||||
type = types.submodule {
|
||||
options = versionDefinitions.${config.kubernetes.version}.customResourceOptions;
|
||||
};
|
||||
description = "Attribute set of custom kubernetes resources";
|
||||
default = {};
|
||||
};
|
||||
}
|
||||
52
lib.nix
52
lib.nix
|
|
@ -2,7 +2,10 @@
|
|||
|
||||
with lib;
|
||||
|
||||
rec {
|
||||
let
|
||||
in rec {
|
||||
mkOptionDefault = mkOverride 1001;
|
||||
|
||||
mkAllDefault = value: priority:
|
||||
if isAttrs value
|
||||
then mapAttrs (n: v: mkAllDefault v priority) value
|
||||
|
|
@ -12,12 +15,47 @@ rec {
|
|||
|
||||
else mkOverride priority value;
|
||||
|
||||
moduleToAttrs = value:
|
||||
if isAttrs value
|
||||
then mapAttrs (n: v: moduleToAttrs v) (filterAttrs (n: v: !(hasPrefix "_" n) && v != null) value)
|
||||
loadYAML = path: importJSON (pkgs.runCommand "yaml-to-json" {
|
||||
} "${pkgs.remarshal}/bin/remarshal -i ${path} -if yaml -of json > $out");
|
||||
|
||||
else if isList value
|
||||
then map (v: moduleToAttrs v) value
|
||||
toYAML = config: builtins.readFile (pkgs.runCommand "to-yaml" {
|
||||
buildInputs = [pkgs.remarshal];
|
||||
} ''
|
||||
remarshal -i ${pkgs.writeText "to-json" (builtins.toJSON config)} -if json -of yaml > $out
|
||||
'');
|
||||
|
||||
else value;
|
||||
toBase64 = value:
|
||||
builtins.readFile
|
||||
(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);
|
||||
|
||||
octalToDecimal = value:
|
||||
(foldr (char: acc: {
|
||||
i = acc.i + 1;
|
||||
value = acc.value + (toInt char) * (exp 8 acc.i);
|
||||
}) {i = 0; value = 0;} (stringToCharacters value)).value;
|
||||
|
||||
importModule = {module ? null, modules ? [module], config}: let
|
||||
specialArgs = {
|
||||
kubenix = import ./. { inherit pkgs lib; };
|
||||
parentConfig = config;
|
||||
};
|
||||
|
||||
isModule = hasAttr "module" config;
|
||||
|
||||
moduleDefinition = (evalModules {
|
||||
inherit modules specialArgs;
|
||||
check = false;
|
||||
}).config.module.definition;
|
||||
in mkOption {
|
||||
description = "Module ${moduleDefinition.name} version ${moduleDefinition.version}";
|
||||
type = submoduleWithSpecialArgs ({name, ...}: let
|
||||
name' = if isModule then "${config.module.name}-${name}" else name;
|
||||
in {
|
||||
imports = modules;
|
||||
module.name = mkOptionDefault name';
|
||||
}) specialArgs;
|
||||
default = {};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
230
modules.nix
230
modules.nix
|
|
@ -1,230 +0,0 @@
|
|||
{ config, options, lib, pkgs, k8s, module ? null, ... }:
|
||||
|
||||
with lib;
|
||||
with import ./lib.nix { inherit pkgs lib; };
|
||||
|
||||
let
|
||||
globalConfig = config;
|
||||
parentModule = module;
|
||||
|
||||
mkOptionDefault = mkOverride 1001;
|
||||
|
||||
# A submodule (like typed attribute set). See NixOS manual.
|
||||
submodule = opts:
|
||||
let
|
||||
opts' = toList opts;
|
||||
inherit (lib.modules) evalModules;
|
||||
in
|
||||
mkOptionType rec {
|
||||
name = "submodule";
|
||||
check = x: isAttrs x || isFunction x;
|
||||
merge = loc: defs:
|
||||
let
|
||||
coerce = def: if isFunction def then def else { config = def; };
|
||||
modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
|
||||
in (evalModules {
|
||||
inherit modules;
|
||||
prefix = loc;
|
||||
}).config;
|
||||
getSubOptions = prefix: (evalModules
|
||||
{ modules = opts'; inherit prefix;
|
||||
# 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"
|
||||
# attribute to be given to the sub-module. As the option
|
||||
# evaluation does not have any specific attribute name, we
|
||||
# provide a default one for the documentation.
|
||||
#
|
||||
# This is mandatory as some option declaration might use the
|
||||
# "name" attribute given as argument of the submodule and use it
|
||||
# as the default of option declarations.
|
||||
args.name = "<name>";
|
||||
}).options;
|
||||
getSubModules = opts';
|
||||
substSubModules = m: submodule m;
|
||||
functor = (defaultFunctor name) // {
|
||||
# Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
|
||||
# each submodule with its location.
|
||||
payload = [];
|
||||
binOp = lhs: rhs: [];
|
||||
};
|
||||
};
|
||||
|
||||
mkModuleOptions = moduleDefinition: module:
|
||||
let
|
||||
# gets file where module is defined by looking into moduleDefinitions
|
||||
# option.
|
||||
file =
|
||||
elemAt options.kubernetes.moduleDefinitions.files (
|
||||
(findFirst (i: i > 0) 0
|
||||
(imap
|
||||
(i: def: if hasAttr module.module def then i else 0)
|
||||
options.kubernetes.moduleDefinitions.definitions
|
||||
)
|
||||
) - 1
|
||||
);
|
||||
|
||||
injectModuleAttrs = module: attrs: (
|
||||
if isFunction module then args: (applyIfFunction file module args) // attrs
|
||||
else if isAttrs mkOptionDefault.module then module // attrs
|
||||
else module
|
||||
);
|
||||
in [
|
||||
{
|
||||
_module.args.k8s = k8s;
|
||||
_module.args.name = module.name;
|
||||
_module.args.module = module;
|
||||
}
|
||||
./kubernetes.nix
|
||||
./modules.nix
|
||||
(injectModuleAttrs moduleDefinition.module {_file = file;})
|
||||
{
|
||||
config.kubernetes.defaults.all.metadata.namespace = mkOptionDefault module.namespace;
|
||||
}
|
||||
] ++ config.kubernetes.defaultModuleConfiguration.all
|
||||
++ (optionals (hasAttr moduleDefinition.name config.kubernetes.defaultModuleConfiguration)
|
||||
config.kubernetes.defaultModuleConfiguration.${moduleDefinition.name});
|
||||
|
||||
prefixResources = resources: serviceName:
|
||||
mapAttrs (groupName: resources:
|
||||
mapAttrs' (name: resource: nameValuePair "${serviceName}-${name}" resource) resources
|
||||
) resources;
|
||||
|
||||
prefixGroupResources = resources: serviceName:
|
||||
mapAttrs' (groupName: resources:
|
||||
nameValuePair "${serviceName}-${groupName}" resources
|
||||
) resources;
|
||||
|
||||
defaultModuleConfigurationOptions = mapAttrs (name: moduleDefinition: mkOption {
|
||||
description = "Module default configuration for ${name} module";
|
||||
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
||||
default = [];
|
||||
}) config.kubernetes.moduleDefinitions;
|
||||
|
||||
getModuleDefinition = name:
|
||||
if hasAttr name config.kubernetes.moduleDefinitions
|
||||
then config.kubernetes.moduleDefinitions.${name}
|
||||
else throw ''requested kubernetes moduleDefinition with name "${name}" does not exist'';
|
||||
in {
|
||||
options.kubernetes.moduleDefinitions = mkOption {
|
||||
description = "Attribute set of module definitions";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule ({name, ...}: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Module definition name";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
prefixResources = mkOption {
|
||||
description = "Whether resources should be automatically prefixed with module name";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
assignAsDefaults = mkOption {
|
||||
description = "Whether to assign resources as defaults, this is usefull for module that add some functionality";
|
||||
type = types.bool;
|
||||
default = false;
|
||||
};
|
||||
|
||||
module = mkOption {
|
||||
description = "Module definition";
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
options.kubernetes.defaultModuleConfiguration = mkOption {
|
||||
description = "Module default options";
|
||||
type = types.submodule {
|
||||
options = defaultModuleConfigurationOptions // {
|
||||
all = mkOption {
|
||||
description = "Module default configuration for all modules";
|
||||
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
|
||||
options.kubernetes.modules = mkOption {
|
||||
description = "Attribute set of modules";
|
||||
default = {};
|
||||
type = types.attrsOf (types.submodule ({config, name, ...}: {
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Module name";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
namespace = mkOption {
|
||||
description = "Namespace where to deploy module";
|
||||
type = types.str;
|
||||
default =
|
||||
if parentModule != null
|
||||
then parentModule.namespace
|
||||
else "default";
|
||||
};
|
||||
|
||||
labels = mkOption {
|
||||
description = "Attribute set of module lables";
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
};
|
||||
|
||||
configuration = mkOption {
|
||||
description = "Module configuration";
|
||||
type = submodule {
|
||||
imports = mkModuleOptions (getModuleDefinition config.module) config;
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
|
||||
module = mkOption {
|
||||
description = "Name of the module to use";
|
||||
type = types.str;
|
||||
default = config.name;
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
|
||||
config = {
|
||||
kubernetes.resources = mkMerge (
|
||||
mapAttrsToList (name: module: let
|
||||
moduleDefinition = getModuleDefinition module.module;
|
||||
moduleConfig =
|
||||
if moduleDefinition.prefixResources
|
||||
then prefixResources (moduleToAttrs module.configuration.kubernetes.resources) name
|
||||
else moduleToAttrs module.configuration.kubernetes.resources;
|
||||
in
|
||||
if moduleDefinition.assignAsDefaults
|
||||
then mkAllDefault moduleConfig 1000
|
||||
else moduleConfig
|
||||
) config.kubernetes.modules
|
||||
);
|
||||
|
||||
kubernetes.customResources = mkMerge (
|
||||
mapAttrsToList (name: module: let
|
||||
moduleDefinition = getModuleDefinition module.module;
|
||||
moduleConfig =
|
||||
if moduleDefinition.prefixResources
|
||||
then prefixGroupResources (moduleToAttrs module.configuration.kubernetes.customResources) name
|
||||
else moduleToAttrs module.configuration.kubernetes.customResources;
|
||||
in
|
||||
if moduleDefinition.assignAsDefaults
|
||||
then mkAllDefault moduleConfig 1000
|
||||
else moduleConfig
|
||||
) config.kubernetes.modules
|
||||
);
|
||||
|
||||
kubernetes.defaultModuleConfiguration.all = {
|
||||
_file = head options.kubernetes.defaultModuleConfiguration.files;
|
||||
config.kubernetes.version = mkDefault config.kubernetes.version;
|
||||
config.kubernetes.moduleDefinitions = config.kubernetes.moduleDefinitions;
|
||||
};
|
||||
};
|
||||
}
|
||||
147
release.nix
Normal file
147
release.nix
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
{pkgs ? import <nixpkgs> {}}:
|
||||
|
||||
let
|
||||
generate = path: import ./k8s/generator.nix {
|
||||
inherit pkgs;
|
||||
inherit (pkgs) lib;
|
||||
inherit path;
|
||||
};
|
||||
|
||||
kubenix = import ./. { inherit pkgs; };
|
||||
in {
|
||||
generate = pkgs.linkFarm "k8s-generated.nix" [{
|
||||
name = "v1.7.nix";
|
||||
path = generate ./k8s/specs/1.7/swagger.json;
|
||||
} {
|
||||
name = "v1.8.nix";
|
||||
path = generate ./k8s/specs/1.8/swagger.json;
|
||||
} {
|
||||
name = "v1.9.nix";
|
||||
path = generate ./k8s/specs/1.9/swagger.json;
|
||||
} {
|
||||
name = "v1.10.nix";
|
||||
path = generate ./k8s/specs/1.10/swagger.json;
|
||||
}];
|
||||
|
||||
test = kubenix.buildResources ({lib, config, kubenix, ...}: with lib; {
|
||||
imports = [
|
||||
kubenix.k8s
|
||||
kubenix.submodules
|
||||
];
|
||||
|
||||
config = {
|
||||
kubernetes.version = "1.10";
|
||||
|
||||
kubernetes.api.defaults.all.metadata.namespace = mkDefault "my-namespace";
|
||||
|
||||
submodules.defaults = {config, parentConfig, ...}: {
|
||||
kubernetes = mkIf (hasAttr "kubernetes" config) {
|
||||
version = mkDefault parentConfig.kubernetes.version;
|
||||
api.defaults = mkDefault parentConfig.kubernetes.api.defaults;
|
||||
};
|
||||
};
|
||||
|
||||
submodules.imports = [
|
||||
# import nginx submodule
|
||||
./examples/module/nginx.nix
|
||||
|
||||
# import of patched nginx submodule
|
||||
{
|
||||
modules = [./examples/module/nginx.nix ({config, ...}: {
|
||||
config = {
|
||||
submodule.version = mkForce "1.0-xtruder";
|
||||
args.image = "xtruder/nginx";
|
||||
|
||||
submodules.instances.test2 = {
|
||||
submodule = "test";
|
||||
};
|
||||
|
||||
kubernetes.objects = config.submodules.instances.test2.config.kubernetes.objects;
|
||||
};
|
||||
})];
|
||||
}
|
||||
|
||||
# definition of test submodule
|
||||
{
|
||||
module = {submodule, ...}: {
|
||||
submodule.name = "test";
|
||||
|
||||
imports = [
|
||||
kubenix.k8s
|
||||
];
|
||||
|
||||
kubernetes.api.Pod.my-pod = {
|
||||
metadata.name = submodule.name;
|
||||
};
|
||||
};
|
||||
}
|
||||
];
|
||||
|
||||
submodules.instances.nginx-default = {
|
||||
submodule = "nginx";
|
||||
};
|
||||
|
||||
submodules.instances.nginx-xtruder = {
|
||||
submodule = "nginx";
|
||||
version = "1.0-xtruder";
|
||||
|
||||
config = {
|
||||
args.replicas = 9;
|
||||
kubernetes.api.Deployment.nginx.metadata.namespace = "other-namespace";
|
||||
};
|
||||
};
|
||||
|
||||
submodules.instances.test = {
|
||||
submodule = "test";
|
||||
};
|
||||
|
||||
#kubernetes.api."cloud.google.com".v1beta1.BackendConfig.my-backend = {
|
||||
#};
|
||||
|
||||
#modules.nginx1 = {
|
||||
#args = {
|
||||
#replicas = 2;
|
||||
#};
|
||||
|
||||
#kubernetes.api.defaults.deployments = {
|
||||
#spec.replicas = mkForce 3;
|
||||
#};
|
||||
|
||||
#kubernetes.customResources = [{
|
||||
#group = "cloud.google.com";
|
||||
#version = "v1beta1";
|
||||
#kind = "BackendConfig";
|
||||
#plural = "backendconfigs";
|
||||
#description = "Custom resource";
|
||||
#module = {
|
||||
#options.spec = {
|
||||
#cdn = mkOption {
|
||||
#description = "My cdn";
|
||||
#type = types.str;
|
||||
#default = "test";
|
||||
#};
|
||||
#};
|
||||
#};
|
||||
#}];
|
||||
#};
|
||||
|
||||
#modules.nginx2 = {
|
||||
#args = {
|
||||
#replicas = 2;
|
||||
#};
|
||||
|
||||
#kubernetes.api.defaults.deployments = {
|
||||
#spec.replicas = mkForce 3;
|
||||
#};
|
||||
#};
|
||||
|
||||
kubernetes.objects = mkMerge [
|
||||
config.submodules.instances.nginx-default.config.kubernetes.objects
|
||||
config.submodules.instances.nginx-xtruder.config.kubernetes.objects
|
||||
config.submodules.instances.test.config.kubernetes.objects
|
||||
];
|
||||
|
||||
#kubernetes.customResources = config.modules.nginx1.kubernetes.customResources;
|
||||
};
|
||||
});
|
||||
}
|
||||
225
submodules.nix
Normal file
225
submodules.nix
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
{ config, kubenix, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.submodules;
|
||||
|
||||
submoduleWithSpecialArgs = opts: specialArgs:
|
||||
let
|
||||
opts' = toList opts;
|
||||
inherit (lib.modules) evalModules;
|
||||
in
|
||||
mkOptionType rec {
|
||||
name = "submodule";
|
||||
check = x: isAttrs x || isFunction x;
|
||||
merge = loc: defs:
|
||||
let
|
||||
coerce = def: if isFunction def then def else { config = def; };
|
||||
modules = opts' ++ map (def: { _file = def.file; imports = [(coerce def.value)]; }) defs;
|
||||
in (evalModules {
|
||||
inherit modules specialArgs;
|
||||
args.name = last loc;
|
||||
prefix = loc;
|
||||
}).config;
|
||||
getSubOptions = prefix: (evalModules
|
||||
{ modules = opts'; inherit prefix specialArgs;
|
||||
# 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"
|
||||
# attribute to be given to the sub-module. As the option
|
||||
# evaluation does not have any specific attribute name, we
|
||||
# provide a default one for the documentation.
|
||||
#
|
||||
# This is mandatory as some option declaration might use the
|
||||
# "name" attribute given as argument of the submodule and use it
|
||||
# as the default of option declarations.
|
||||
#
|
||||
# Using lookalike unicode single angle quotation marks because
|
||||
# of the docbook transformation the options receive. In all uses
|
||||
# > and < wouldn't be encoded correctly so the encoded values
|
||||
# would be used, and use of `<` and `>` would break the XML document.
|
||||
# It shouldn't cause an issue since this is cosmetic for the manual.
|
||||
args.name = "‹name›";
|
||||
}).options;
|
||||
getSubModules = opts';
|
||||
substSubModules = m: submoduleWithSpecialArgs m specialArgs;
|
||||
functor = (defaultFunctor name) // {
|
||||
# Merging of submodules is done as part of mergeOptionDecls, as we have to annotate
|
||||
# each submodule with its location.
|
||||
payload = [];
|
||||
binOp = lhs: rhs: [];
|
||||
};
|
||||
};
|
||||
|
||||
submoduleDefinitionOptions = {
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Module name";
|
||||
type = types.str;
|
||||
};
|
||||
|
||||
description = mkOption {
|
||||
description = "Module description";
|
||||
type = types.str;
|
||||
default = "";
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
description = "Module version";
|
||||
type = types.str;
|
||||
default = "0.0.0";
|
||||
};
|
||||
|
||||
passthru = mkOption {
|
||||
description = "Submodule passthru";
|
||||
type = types.coercedTo types.attrs (value: [value]) (types.listOf types.attrs);
|
||||
default = [];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
submoduleOptions = {
|
||||
options.submodule = mkOption {
|
||||
description = "Submodule options";
|
||||
type = types.submodule submoduleDefinitionOptions;
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
|
||||
specialArgs = cfg.specialArgs // {
|
||||
parentConfig = config;
|
||||
};
|
||||
|
||||
findModule = {name, version ? null, latest ? true}: let
|
||||
matchingSubmodules = filter (el:
|
||||
el.definition.name == name &&
|
||||
(if version != null then el.definition.version == version else true)
|
||||
) cfg.imports;
|
||||
|
||||
versionSortedSubmodules = sort (s1: s2:
|
||||
if builtins.compareVersions s1.definition.version s2.definition.version > 0
|
||||
then true else false
|
||||
) matchingSubmodules;
|
||||
|
||||
matchingModule =
|
||||
if length versionSortedSubmodules == 0
|
||||
then throw "No module found ${name}/${if version == null then "latest" else version}"
|
||||
else head versionSortedSubmodules;
|
||||
in matchingModule;
|
||||
in {
|
||||
options = {
|
||||
submodules.specialArgs = mkOption {
|
||||
description = "Special args to pass to submodules. These arguments can be used for imports";
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
};
|
||||
|
||||
submodules.defaults = mkOption {
|
||||
description = "Submodule defaults";
|
||||
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
||||
example = literalExample ''{config, ...}: {
|
||||
kubernetes.version = config.kubernetes.version;
|
||||
}'';
|
||||
default = [];
|
||||
};
|
||||
|
||||
submodules.propagateDefaults = mkOption {
|
||||
description = "Whether to propagate defaults to submodules";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
submodules.propagate = mkOption {
|
||||
description = "Whether to propagate defaults and imports to submodule's submodules";
|
||||
type = types.bool;
|
||||
default = true;
|
||||
};
|
||||
|
||||
submodules.imports = mkOption {
|
||||
description = "List of submodule imports";
|
||||
type = types.listOf (
|
||||
types.coercedTo
|
||||
types.path
|
||||
(module: {inherit module;})
|
||||
(types.submodule ({name, config, ...}: let
|
||||
submoduleDefinition = (evalModules {
|
||||
inherit specialArgs;
|
||||
modules = config.modules ++ [submoduleOptions];
|
||||
check = false;
|
||||
}).config.submodule;
|
||||
in {
|
||||
options = {
|
||||
module = mkOption {
|
||||
description = "Module defining submodule";
|
||||
};
|
||||
|
||||
modules = mkOption {
|
||||
description = "List of modules defining submodule";
|
||||
type = types.listOf types.unspecified;
|
||||
default = [config.module];
|
||||
};
|
||||
|
||||
definition = mkOption {
|
||||
type = types.submodule submoduleDefinitionOptions;
|
||||
default = submoduleDefinition;
|
||||
};
|
||||
};
|
||||
})
|
||||
)
|
||||
);
|
||||
default = [];
|
||||
};
|
||||
|
||||
submodules.instances = mkOption {
|
||||
description = "Attribute set of submodule instances";
|
||||
type = types.attrsOf (types.submodule ({name, config, ...}: let
|
||||
submodule = findModule {
|
||||
name = config.submodule;
|
||||
version = config.version;
|
||||
};
|
||||
|
||||
submoduleDefinition = submodule.definition;
|
||||
in {
|
||||
options = {
|
||||
name = mkOption {
|
||||
description = "Submodule instance name";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
submodule = mkOption {
|
||||
description = "Name of the submodule to use";
|
||||
type = types.str;
|
||||
default = name;
|
||||
};
|
||||
|
||||
version = mkOption {
|
||||
description = "Version of submodule to use";
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
description = "Submodule instance ${config.name} for ${submoduleDefinition.name}:${submoduleDefinition.version} config";
|
||||
type = submoduleWithSpecialArgs ({...}: {
|
||||
imports = submodule.modules ++ cfg.defaults ++ [submoduleOptions ./submodules.nix];
|
||||
_module.args.submodule = {
|
||||
name = config.name;
|
||||
};
|
||||
}) specialArgs;
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
}));
|
||||
};
|
||||
default = {};
|
||||
};
|
||||
|
||||
config = {
|
||||
submodules.specialArgs.kubenix = kubenix;
|
||||
submodules.defaults = mkIf cfg.propagate {
|
||||
submodules.defaults = cfg.defaults;
|
||||
submodules.imports = cfg.imports;
|
||||
};
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue