mirror of
https://github.com/TECHNOFAB11/kubenix.git
synced 2025-12-12 16:10:05 +01:00
Merge branch 'kubenix-2.0'
This commit is contained in:
commit
ef78b957fd
85 changed files with 243643 additions and 216471 deletions
|
|
@ -1,2 +1,3 @@
|
||||||
language: nix
|
language: nix
|
||||||
script: nix-build -A tests
|
script:
|
||||||
|
- nix eval -f ./ci.nix --arg release.e2e false test-check
|
||||||
|
|
|
||||||
20
LICENSE
Normal file
20
LICENSE
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2003-2019 Jaka Hudoklin and the X-Truder contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
31
README.md
31
README.md
|
|
@ -5,19 +5,38 @@ It will be merged into master in following weeks. For progress and features take
|
||||||
|
|
||||||
> Kubernetes resource builder written in nix
|
> Kubernetes resource builder written in nix
|
||||||
|
|
||||||
|
[](https://travis-ci.com/xtruder/kubenix)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
KubeNix is a kubernetes resource builder, that uses nix module system for
|
KubeNix is a kubernetes resource builder, that uses nix module system for
|
||||||
definition of kubernetes resources and nix build system for building complex
|
definition of kubernetes resources and nix build system for building complex
|
||||||
kubernetes resources very easily.
|
kubernetes resources very easily.
|
||||||
|
|
||||||
### Features
|
## Development
|
||||||
|
|
||||||
- Loading and override of kubernetes json and yaml files
|
### Building tests
|
||||||
- Support for complex merging of kubernetes resource definitions
|
|
||||||
- No more helm stupid yaml templating, nix is a way better templating language
|
```shell
|
||||||
- Support for all kubernetes versions
|
nix-build release.nix -A test-results --show-trace
|
||||||
|
```
|
||||||
|
|
||||||
|
**Building single e2e test**
|
||||||
|
|
||||||
|
```
|
||||||
|
nix-build release.nix -A tests.k8s-1_10.testsByName.k8s-crd.test
|
||||||
|
nix-build release.nix -A tests.k8s-1_10.testsByName.<test-name>.test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Debugging e2e test**
|
||||||
|
|
||||||
|
```
|
||||||
|
nix-build release.nix -A tests.k8s-1_10.testsByName.k8s-crd.test.driver
|
||||||
|
nix-build release.nix -A tests.k8s-1_10.testsByName.<test-name>.test.driver
|
||||||
|
resut/bin/nixos-test-driver
|
||||||
|
testScript;
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT © [Jaka Hudoklin](https://x-truder.net)
|
[MIT](LICENSE) © [Jaka Hudoklin](https://x-truder.net)
|
||||||
|
|
|
||||||
11
ci.nix
Normal file
11
ci.nix
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
let
|
||||||
|
nixpkgsSrc = builtins.fetchTarball "https://github.com/NixOS/nixpkgs/archive/master.tar.gz";
|
||||||
|
pkgs = import nixpkgsSrc {};
|
||||||
|
|
||||||
|
lib = pkgs.lib;
|
||||||
|
|
||||||
|
release = import ./release.nix {
|
||||||
|
inherit pkgs lib;
|
||||||
|
nixosPath = "${nixpkgsSrc}/nixos";
|
||||||
|
};
|
||||||
|
in pkgs.recurseIntoAttrs release
|
||||||
147
default.nix
147
default.nix
|
|
@ -1,126 +1,57 @@
|
||||||
{
|
{ pkgs ? import <nixpkgs> {}, nixosPath ? toString <nixpkgs/nixos>, lib ? pkgs.lib }:
|
||||||
pkgs ? import <nixpkgs> {}
|
|
||||||
}:
|
|
||||||
|
|
||||||
with pkgs.lib;
|
with lib;
|
||||||
with import ./lib.nix { inherit pkgs; inherit (pkgs) lib; };
|
|
||||||
|
|
||||||
let
|
let
|
||||||
evalKubernetesModules = configuration: evalModules rec {
|
kubenixLib = import ./lib { inherit lib pkgs; };
|
||||||
modules = [
|
lib' = lib.extend (lib: self: import ./lib/extra.nix { inherit lib pkgs; });
|
||||||
./kubernetes.nix
|
|
||||||
./modules.nix configuration
|
defaultSpecialArgs = {
|
||||||
];
|
inherit kubenix nixosPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
# evalModules with same interface as lib.evalModules and kubenix as
|
||||||
|
# special argument
|
||||||
|
evalModules = {
|
||||||
|
module ? null,
|
||||||
|
modules ? [module],
|
||||||
|
specialArgs ? defaultSpecialArgs, ...
|
||||||
|
}@attrs: let
|
||||||
|
attrs' = filterAttrs (n: _: n != "module") attrs;
|
||||||
|
in lib'.evalModules (recursiveUpdate {
|
||||||
|
inherit specialArgs modules;
|
||||||
args = {
|
args = {
|
||||||
inherit pkgs;
|
inherit pkgs;
|
||||||
name = "default";
|
name = "default";
|
||||||
k8s = import ./k8s.nix {
|
|
||||||
inherit pkgs;
|
|
||||||
inherit (pkgs) lib;
|
|
||||||
};
|
|
||||||
module = null;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
} attrs');
|
||||||
|
|
||||||
flattenResources = resources: flatten (
|
modules = import ./modules;
|
||||||
mapAttrsToList (name: resourceGroup:
|
|
||||||
mapAttrsToList (name: resource: resource) resourceGroup
|
|
||||||
) resources
|
|
||||||
);
|
|
||||||
|
|
||||||
filterResources = resourceFilter: resources:
|
|
||||||
mapAttrs (groupName: resources:
|
|
||||||
(filterAttrs (name: resource:
|
|
||||||
resourceFilter groupName name resource
|
|
||||||
) resources)
|
|
||||||
) resources;
|
|
||||||
|
|
||||||
toKubernetesList = resources: {
|
|
||||||
kind = "List";
|
|
||||||
apiVersion = "v1";
|
|
||||||
items = resources;
|
|
||||||
};
|
|
||||||
|
|
||||||
removeNixOptions = resources:
|
|
||||||
map (filterAttrs (name: attr: name != "nix")) resources;
|
|
||||||
|
|
||||||
|
# legacy support for buildResources
|
||||||
buildResources = {
|
buildResources = {
|
||||||
configuration ? {},
|
configuration ? {},
|
||||||
resourceFilter ? groupName: name: resource: true,
|
|
||||||
withDependencies ? true,
|
|
||||||
writeJSON ? true,
|
writeJSON ? true,
|
||||||
writeHash ? true
|
writeHash ? true
|
||||||
}: let
|
}: let
|
||||||
evaldConfiguration = evalKubernetesModules configuration;
|
evaled = evalModules {
|
||||||
|
modules = [
|
||||||
allResources = moduleToAttrs (
|
configuration
|
||||||
evaldConfiguration.config.kubernetes.resources //
|
modules.legacy
|
||||||
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 // optionalAttrs (writeHash) {
|
|
||||||
labels."kubenix/build" = listHash;
|
|
||||||
items = map (resource: recursiveUpdate resource {
|
|
||||||
metadata.labels."kubenix/build" = listHash;
|
|
||||||
}) kubernetesList.items;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
result = if writeJSON then
|
generated = evaled.config.kubernetes.generated;
|
||||||
pkgs.writeText "resources.json" (builtins.toJSON hashedList)
|
|
||||||
else hashedList;
|
|
||||||
in
|
|
||||||
result;
|
|
||||||
|
|
||||||
buildTest = test: version: buildResources {
|
result =
|
||||||
configuration = {
|
if writeJSON
|
||||||
require = [test {
|
then pkgs.writeText "resources.json" (builtins.toJSON generated)
|
||||||
config.kubernetes.version = version;
|
else generated;
|
||||||
}];
|
in result;
|
||||||
|
|
||||||
|
kubenix = {
|
||||||
|
inherit evalModules buildResources modules;
|
||||||
|
|
||||||
|
lib = kubenixLib;
|
||||||
};
|
};
|
||||||
};
|
in kubenix
|
||||||
|
|
||||||
in {
|
|
||||||
inherit buildResources;
|
|
||||||
|
|
||||||
tests."k8s-1_7" = buildTest ./test/default.nix "1.7";
|
|
||||||
tests."k8s-1_8" = buildTest ./test/default.nix "1.8";
|
|
||||||
tests."k8s-1_9" = buildTest ./test/default.nix "1.9";
|
|
||||||
tests."k8s-1_10" = buildTest ./test/default.nix "1.10";
|
|
||||||
tests."k8s-1_11" = buildTest ./test/default.nix "1.11";
|
|
||||||
tests."k8s-1_12" = buildTest ./test/default.nix "1.12";
|
|
||||||
tests."k8s-1_13" = buildTest ./test/default.nix "1.13";
|
|
||||||
}
|
|
||||||
|
|
|
||||||
5
examples/default.nix
Normal file
5
examples/default.nix
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{ kubenix ? import ./.. {} }:
|
||||||
|
|
||||||
|
{
|
||||||
|
nginx-deployment = import ./nginx-deployment { inherit kubenix; };
|
||||||
|
}
|
||||||
28
examples/nginx-deployment/README.md
Normal file
28
examples/nginx-deployment/README.md
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
# Example: kubernetes nginx deployment
|
||||||
|
|
||||||
|
A simple example creating kubernetes nginx deployment and associated docker
|
||||||
|
image
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Building and applying kubernetes configuration
|
||||||
|
|
||||||
|
```
|
||||||
|
nix eval -f ./. --json result | kubectl apply -f -
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building and pushing docker images
|
||||||
|
|
||||||
|
```
|
||||||
|
nix run -f ./. pushDockerImages -c copy-docker-images
|
||||||
|
```
|
||||||
|
|
||||||
|
### Running tests
|
||||||
|
|
||||||
|
Test will spawn vm with kubernetes and run test script, which checks if everyting
|
||||||
|
works as expected.
|
||||||
|
|
||||||
|
```
|
||||||
|
nix build -f ./. test-script
|
||||||
|
cat result | jq '.'
|
||||||
|
```
|
||||||
40
examples/nginx-deployment/default.nix
Normal file
40
examples/nginx-deployment/default.nix
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{ kubenix ? import ../.. {}, registry ? "docker.io/gatehub" }:
|
||||||
|
|
||||||
|
with kubenix.lib;
|
||||||
|
|
||||||
|
rec {
|
||||||
|
# evaluated configuration
|
||||||
|
config = (kubenix.evalModules {
|
||||||
|
modules = [
|
||||||
|
./module.nix
|
||||||
|
{ docker.registry.url = registry; }
|
||||||
|
|
||||||
|
kubenix.modules.testing
|
||||||
|
{
|
||||||
|
testing.tests = [ ./test.nix ];
|
||||||
|
testing.defaults = ({ lib, ... }: with lib; {
|
||||||
|
docker.registry.url = mkForce "";
|
||||||
|
kubernetes.version = config.kubernetes.version;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}).config;
|
||||||
|
|
||||||
|
# e2e test
|
||||||
|
test = config.testing.result;
|
||||||
|
|
||||||
|
# nixos test script for running the test
|
||||||
|
test-script = config.testing.testsByName.nginx-deployment.test;
|
||||||
|
|
||||||
|
# genreated kubernetes List object
|
||||||
|
generated = config.kubernetes.generated;
|
||||||
|
|
||||||
|
# JSON file you can deploy to kubernetes
|
||||||
|
result = config.kubernetes.result;
|
||||||
|
|
||||||
|
# Exported docker images
|
||||||
|
images = config.docker.export;
|
||||||
|
|
||||||
|
# script to push docker images to registry
|
||||||
|
pushDockerImages = config.docker.copyScript;
|
||||||
|
}
|
||||||
18
examples/nginx-deployment/image.nix
Normal file
18
examples/nginx-deployment/image.nix
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{ dockerTools, nginx }:
|
||||||
|
|
||||||
|
dockerTools.buildLayeredImage {
|
||||||
|
name = "nginx";
|
||||||
|
contents = [ nginx ];
|
||||||
|
extraCommands = ''
|
||||||
|
mkdir -p etc
|
||||||
|
chmod u+w etc
|
||||||
|
echo "nginx:x:1000:1000::/:" > etc/passwd
|
||||||
|
echo "nginx:x:1000:nginx" > etc/group
|
||||||
|
'';
|
||||||
|
config = {
|
||||||
|
Cmd = ["nginx" "-c" "/etc/nginx/nginx.conf"];
|
||||||
|
ExposedPorts = {
|
||||||
|
"80/tcp" = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
64
examples/nginx-deployment/module.nix
Normal file
64
examples/nginx-deployment/module.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{ config, lib, pkgs, kubenix, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
nginx = pkgs.callPackage ./image.nix { };
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ k8s docker ];
|
||||||
|
|
||||||
|
docker.images.nginx.image = nginx;
|
||||||
|
|
||||||
|
kubernetes.resources.deployments.nginx = {
|
||||||
|
spec = {
|
||||||
|
replicas = 10;
|
||||||
|
selector.matchLabels.app = "nginx";
|
||||||
|
template = {
|
||||||
|
metadata.labels.app = "nginx";
|
||||||
|
spec = {
|
||||||
|
securityContext.fsGroup = 1000;
|
||||||
|
containers.nginx = {
|
||||||
|
image = config.docker.images.nginx.path;
|
||||||
|
imagePullPolicy = "IfNotPresent";
|
||||||
|
volumeMounts."/etc/nginx".name = "config";
|
||||||
|
volumeMounts."/var/lib/html".name = "static";
|
||||||
|
};
|
||||||
|
volumes.config.configMap.name = "nginx-config";
|
||||||
|
volumes.static.configMap.name = "nginx-static";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.resources.configMaps.nginx-config.data."nginx.conf" = ''
|
||||||
|
user nginx nginx;
|
||||||
|
daemon off;
|
||||||
|
error_log /dev/stdout info;
|
||||||
|
pid /dev/null;
|
||||||
|
events {}
|
||||||
|
http {
|
||||||
|
access_log /dev/stdout;
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
index index.html;
|
||||||
|
location / {
|
||||||
|
root /var/lib/html;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
|
||||||
|
kubernetes.resources.configMaps.nginx-static.data."index.html" = ''
|
||||||
|
<html><body><h1>Hello from NGINX</h1></body></html>
|
||||||
|
'';
|
||||||
|
|
||||||
|
kubernetes.resources.services.nginx = {
|
||||||
|
spec = {
|
||||||
|
ports = [{
|
||||||
|
name = "http";
|
||||||
|
port = 80;
|
||||||
|
}];
|
||||||
|
selector.app = "nginx";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
20
examples/nginx-deployment/test.nix
Normal file
20
examples/nginx-deployment/test.nix
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
{ config, lib, pkgs, kubenix, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [ kubenix.modules.test ./module.nix ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "nginx-deployment";
|
||||||
|
description = "Test testing nginx deployment";
|
||||||
|
testScript = ''
|
||||||
|
$kube->waitUntilSucceeds("docker load < ${config.docker.images.nginx.image}");
|
||||||
|
$kube->waitUntilSucceeds("kubectl apply -f ${config.kubernetes.result}");
|
||||||
|
|
||||||
|
$kube->succeed("kubectl get deployment | grep -i nginx");
|
||||||
|
$kube->waitUntilSucceeds("kubectl get deployment -o go-template nginx --template={{.status.readyReplicas}} | grep 10");
|
||||||
|
$kube->waitUntilSucceeds("${pkgs.curl}/bin/curl http://nginx.default.svc.cluster.local | grep -i hello");
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
{
|
|
||||||
kubernetes.resources.deployments.nginx = {
|
|
||||||
metadata.labels.app = "nginx";
|
|
||||||
spec = {
|
|
||||||
replicas = 3;
|
|
||||||
selector.matchLabels.app = "nginx";
|
|
||||||
template = {
|
|
||||||
metadata.labels.app = "nginx";
|
|
||||||
spec.containers.nginx = {
|
|
||||||
name = "nginx";
|
|
||||||
image = "nginx:1.7.9";
|
|
||||||
ports."80" = {};
|
|
||||||
resources.requests.cpu = "100m";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
kubernetes.resources.services.nginx = {
|
|
||||||
spec.selector.app = "nginx";
|
|
||||||
spec.ports."80".targetPort = 80;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
332
generators/istio/default.nix
Normal file
332
generators/istio/default.nix
Normal file
|
|
@ -0,0 +1,332 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {}, lib ? pkgs.lib, spec ? ./istio-schema.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"];
|
||||||
|
|
||||||
|
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")));
|
||||||
|
};
|
||||||
|
|
||||||
|
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 {
|
||||||
|
type = mapType 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));
|
||||||
|
}
|
||||||
|
|
||||||
|
else if !(hasAttr "type" property) then {
|
||||||
|
type = types.unspecified;
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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: (mapAttrsToList (name: property: rec {
|
||||||
|
splittedType = splitString "." (removePrefix "me.snowdrop.istio.api." property.javaType);
|
||||||
|
group = (concatStringsSep "." (take ((length splittedType) - 2) splittedType)) + ".istio.io";
|
||||||
|
kind = removeSuffix "Spec" (last splittedType);
|
||||||
|
version = last (take ((length splittedType) - 1) splittedType);
|
||||||
|
ref = removePrefix "#/definitions/" property."$ref";
|
||||||
|
})
|
||||||
|
(filterAttrs (name: property:
|
||||||
|
(hasPrefix "me.snowdrop.istio.api" property.javaType) &&
|
||||||
|
hasSuffix "Spec" property.javaType
|
||||||
|
) swagger.properties)) ++ (mapAttrsToList (name: property: rec {
|
||||||
|
splittedType = splitString "." (removePrefix "me.snowdrop.istio.mixer." property.javaType);
|
||||||
|
group = "config.istio.io";
|
||||||
|
version = "v1alpha2";
|
||||||
|
kind = head (tail splittedType);
|
||||||
|
ref = removePrefix "#/definitions/" property."$ref";
|
||||||
|
}) (filterAttrs (name: property:
|
||||||
|
(hasPrefix "me.snowdrop.istio.mixer" property.javaType) &&
|
||||||
|
hasSuffix "Spec" property.javaType
|
||||||
|
) swagger.properties));
|
||||||
|
|
||||||
|
swagger = fetchSpecs spec;
|
||||||
|
|
||||||
|
definitions = genDefinitions swagger;
|
||||||
|
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)}
|
||||||
|
} // (import ./overrides.nix {inheirt definitions lib;}));
|
||||||
|
in {
|
||||||
|
kubernetes.customResources = [
|
||||||
|
${concatMapStrings (resource: ''{
|
||||||
|
group = "${resource.group}";
|
||||||
|
version = "${resource.version}";
|
||||||
|
kind = "${resource.kind}";
|
||||||
|
description = "";
|
||||||
|
module = definitions."${resource.ref}";
|
||||||
|
}'') (genResources swagger)}
|
||||||
|
];
|
||||||
|
}
|
||||||
|
"
|
||||||
4314
generators/istio/istio-schema.json
Normal file
4314
generators/istio/istio-schema.json
Normal file
File diff suppressed because it is too large
Load diff
423
generators/k8s/default.nix
Normal file
423
generators/k8s/default.nix
Normal file
|
|
@ -0,0 +1,423 @@
|
||||||
|
{ name ? "k8s"
|
||||||
|
, 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"];
|
||||||
|
|
||||||
|
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;
|
||||||
|
splitVersion = v: builtins.splitVersion (getVersion v);
|
||||||
|
isAlpha = v: elem "alpha" (splitVersion v);
|
||||||
|
patchVersion = v:
|
||||||
|
if isAlpha v then ""
|
||||||
|
else if length (splitVersion v) == 1 then "${getVersion v}prod"
|
||||||
|
else getVersion v;
|
||||||
|
|
||||||
|
v1 = patchVersion ver1;
|
||||||
|
v2 = patchVersion 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;
|
||||||
|
|
||||||
|
mapCharPairs = f: s1: s2: concatStrings (imap0 (i: c1:
|
||||||
|
f i c1 (if i >= stringLength s2 then "" else elemAt (stringToCharacters s2) i)
|
||||||
|
) (stringToCharacters s1));
|
||||||
|
|
||||||
|
getAttrName = resource: kind:
|
||||||
|
mapCharPairs (i: c1: c2:
|
||||||
|
if hasPrefix "API" kind && i == 0 then "A"
|
||||||
|
else if i == 0 then c1
|
||||||
|
else if c2 == "" || (toLower c2) != c1 then c1
|
||||||
|
else c2
|
||||||
|
) resource kind;
|
||||||
|
|
||||||
|
genResourceTypes = 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;
|
||||||
|
kind' = path.post."x-kubernetes-group-version-kind".kind;
|
||||||
|
name' = last (splitString "/" name);
|
||||||
|
attrName = getAttrName name' kind';
|
||||||
|
in nameValuePair ref {
|
||||||
|
inherit ref attrName;
|
||||||
|
|
||||||
|
name = name';
|
||||||
|
group = if group' == "" then "core" else group';
|
||||||
|
version = version';
|
||||||
|
kind = kind';
|
||||||
|
description = swagger.definitions.${ref}.description;
|
||||||
|
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;
|
||||||
|
resourceTypes = genResourceTypes swagger;
|
||||||
|
|
||||||
|
resourceTypesByKind = zipAttrs (mapAttrsToList (name: resourceType: {
|
||||||
|
${resourceType.kind} = resourceType;
|
||||||
|
}) resourceTypes);
|
||||||
|
|
||||||
|
resourcesTypesByKindSortByVersion = mapAttrs (kind: resourceTypes:
|
||||||
|
reverseList (sort (r1: r2:
|
||||||
|
compareVersions r1.version r2.version > 0
|
||||||
|
) resourceTypes)
|
||||||
|
) resourceTypesByKind;
|
||||||
|
|
||||||
|
latestResourceTypesByKind =
|
||||||
|
mapAttrs (kind: resources: last resources) resourcesTypesByKindSortByVersion;
|
||||||
|
|
||||||
|
genResourceOptions = resource: with gen; let
|
||||||
|
submoduleForDefinition' = definition: let
|
||||||
|
in submoduleForDefinition
|
||||||
|
definition.ref definition.name definition.kind definition.group definition.version;
|
||||||
|
in mkOption {
|
||||||
|
description = resource.description;
|
||||||
|
type = types.attrsOf (submoduleForDefinition' resource);
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
generated = ''
|
||||||
|
# This file was generated with kubenix k8s generator, do not edit
|
||||||
|
{ lib, options, config, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
getDefaults = resource: group: version: kind:
|
||||||
|
catAttrs "default" (filter (default:
|
||||||
|
(default.resource == null || default.resource == resource) &&
|
||||||
|
(default.group == null || default.group == group) &&
|
||||||
|
(default.version == null || default.version == version) &&
|
||||||
|
(default.kind == null || default.kind == kind)
|
||||||
|
) config.defaults);
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 or {};
|
||||||
|
config = definitions."''${ref}".config or {};
|
||||||
|
});
|
||||||
|
|
||||||
|
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: let
|
||||||
|
apiVersion = if group == "core" then version else "''${group}/''${version}";
|
||||||
|
in types.submodule ({name, ...}: {
|
||||||
|
imports = getDefaults resource group version kind;
|
||||||
|
options = definitions."''${ref}".options;
|
||||||
|
config = mkMerge [
|
||||||
|
definitions."''${ref}".config
|
||||||
|
{
|
||||||
|
kind = mkOptionDefault kind;
|
||||||
|
apiVersion = mkOptionDefault apiVersion;
|
||||||
|
|
||||||
|
# metdata.name cannot use option default, due deep config
|
||||||
|
metadata.name = mkOptionDefault name;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
});
|
||||||
|
|
||||||
|
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 {
|
||||||
|
# all resource versions
|
||||||
|
options = {
|
||||||
|
resources = {
|
||||||
|
${concatStrings (mapAttrsToList (_: rt: ''
|
||||||
|
"${rt.group}"."${rt.version}"."${rt.kind}" = ${genResourceOptions rt};
|
||||||
|
'') resourceTypes)}
|
||||||
|
} // {
|
||||||
|
${concatStrings (mapAttrsToList (_: rt: ''
|
||||||
|
"${rt.attrName}" = ${genResourceOptions rt};
|
||||||
|
'') latestResourceTypesByKind)}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# expose resource definitions
|
||||||
|
inherit definitions;
|
||||||
|
|
||||||
|
# register resource types
|
||||||
|
types = [${concatStrings (mapAttrsToList (_: rt: ''{
|
||||||
|
name = "${rt.name}";
|
||||||
|
group = "${rt.group}";
|
||||||
|
version = "${rt.version}";
|
||||||
|
kind = "${rt.kind}";
|
||||||
|
attrName = "${rt.attrName}";
|
||||||
|
}'') resourceTypes)}];
|
||||||
|
|
||||||
|
resources = {
|
||||||
|
${concatStrings (mapAttrsToList (_: rt: ''
|
||||||
|
"${rt.group}"."${rt.version}"."${rt.kind}" =
|
||||||
|
mkAliasDefinitions options.resources."${rt.attrName}";
|
||||||
|
'') latestResourceTypesByKind)}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
in pkgs.runCommand "k8s-${name}-gen.nix" {
|
||||||
|
buildInputs = [ pkgs.haskellPackages.nixfmt ];
|
||||||
|
} ''
|
||||||
|
cp ${builtins.toFile "k8s-${name}-gen-raw.nix" generated} $out
|
||||||
|
nixfmt -w 100 $out
|
||||||
|
''
|
||||||
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;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
370
kubernetes.nix
370
kubernetes.nix
|
|
@ -1,370 +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;
|
|
||||||
"1.10" = definitionsForKubernetesSpecs ./specs/1.10/swagger.json;
|
|
||||||
"1.11" = definitionsForKubernetesSpecs ./specs/1.11/swagger.json;
|
|
||||||
"1.12" = definitionsForKubernetesSpecs ./specs/1.12/swagger.json;
|
|
||||||
"1.13" = definitionsForKubernetesSpecs ./specs/1.13/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";
|
|
||||||
"1.10" = versionDefinitions."1.10";
|
|
||||||
"1.11" = versionDefinitions."1.11";
|
|
||||||
"1.12" = versionDefinitions."1.12";
|
|
||||||
"1.13" = versionDefinitions."1.13";
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = {};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
23
lib.nix
23
lib.nix
|
|
@ -1,23 +0,0 @@
|
||||||
{lib, pkgs}:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
|
|
||||||
rec {
|
|
||||||
mkAllDefault = value: priority:
|
|
||||||
if isAttrs value
|
|
||||||
then mapAttrs (n: v: mkAllDefault v priority) value
|
|
||||||
|
|
||||||
else if isList value
|
|
||||||
then map (v: mkAllDefault v priority) value
|
|
||||||
|
|
||||||
else mkOverride priority value;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
7
lib/default.nix
Normal file
7
lib/default.nix
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{ lib, pkgs }:
|
||||||
|
|
||||||
|
(import ./extra.nix { inherit pkgs lib; }) // {
|
||||||
|
k8s = import ./k8s.nix { inherit lib; };
|
||||||
|
docker = import ./docker.nix { inherit lib pkgs; };
|
||||||
|
helm = import ./helm { inherit pkgs; };
|
||||||
|
}
|
||||||
15
lib/docker.nix
Normal file
15
lib/docker.nix
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{ lib, pkgs }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
{
|
||||||
|
copyDockerImages = { images, dest, args ? "" }:
|
||||||
|
pkgs.writeScriptBin "copy-docker-images" (concatMapStrings (image: ''
|
||||||
|
#!${pkgs.bash}/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "copying ${image.imageName}:${image.imageTag}"
|
||||||
|
${pkgs.skopeo}/bin/skopeo copy ${args} $@ docker-archive:${image} ${dest}/${image.imageName}:${image.imageTag}
|
||||||
|
'') images);
|
||||||
|
}
|
||||||
121
lib/extra.nix
Normal file
121
lib/extra.nix
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
{ lib, pkgs }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
rec {
|
||||||
|
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;
|
||||||
|
|
||||||
|
mkAllDefault = value: priority:
|
||||||
|
if isAttrs value
|
||||||
|
then mapAttrs (n: v: mkAllDefault v priority) value
|
||||||
|
|
||||||
|
else if isList value
|
||||||
|
then map (v: mkAllDefault v priority) value
|
||||||
|
|
||||||
|
else mkOverride priority value;
|
||||||
|
|
||||||
|
loadYAML = path: importJSON (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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
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: [];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
51
lib/helm/chart2json.nix
Normal file
51
lib/helm/chart2json.nix
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
{ stdenvNoCC, lib, kubernetes-helm, gawk, remarshal, jq }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
{
|
||||||
|
# chart to template
|
||||||
|
chart
|
||||||
|
|
||||||
|
# release name
|
||||||
|
, name
|
||||||
|
|
||||||
|
# namespace to install release into
|
||||||
|
, namespace ? null
|
||||||
|
|
||||||
|
# values to pass to chart
|
||||||
|
, values ? {}
|
||||||
|
|
||||||
|
# kubernetes version to template chart for
|
||||||
|
, kubeVersion ? null }: let
|
||||||
|
valuesJsonFile = builtins.toFile "${name}-values.json" (builtins.toJSON values);
|
||||||
|
in stdenvNoCC.mkDerivation {
|
||||||
|
name = "${name}.json";
|
||||||
|
buildCommand = ''
|
||||||
|
# template helm file and write resources to yaml
|
||||||
|
helm template "${name}" \
|
||||||
|
${optionalString (kubeVersion != null) "--api-versions ${kubeVersion}"} \
|
||||||
|
${optionalString (namespace != null) "--namespace ${namespace}"} \
|
||||||
|
${optionalString (values != {}) "-f ${valuesJsonFile}"} \
|
||||||
|
${chart} >resources.yaml
|
||||||
|
|
||||||
|
# split multy yaml file into multiple files
|
||||||
|
awk 'BEGIN{i=1}{line[i++]=$0}END{j=1;n=0; while (j<i) {if (line[j] ~ /^---/) n++; else print line[j] >>"resource-"n".yaml"; j++}}' resources.yaml
|
||||||
|
|
||||||
|
# join multiple yaml files in jsonl file
|
||||||
|
for file in ./resource-*.yaml
|
||||||
|
do
|
||||||
|
remarshal -i $file -if yaml -of json >>resources.jsonl
|
||||||
|
done
|
||||||
|
|
||||||
|
# convert jsonl file to json array, remove null values and write to $out
|
||||||
|
cat resources.jsonl | jq -Scs 'walk(
|
||||||
|
if type == "object" then
|
||||||
|
with_entries(select(.value != null))
|
||||||
|
elif type == "array" then
|
||||||
|
map(select(. != null))
|
||||||
|
else
|
||||||
|
.
|
||||||
|
end)' > $out
|
||||||
|
'';
|
||||||
|
nativeBuildInputs = [ kubernetes-helm gawk remarshal jq ];
|
||||||
|
}
|
||||||
6
lib/helm/default.nix
Normal file
6
lib/helm/default.nix
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{ pkgs }:
|
||||||
|
|
||||||
|
{
|
||||||
|
chart2json = pkgs.callPackage ./chart2json.nix { };
|
||||||
|
fetch = pkgs.callPackage ./fetchhelm.nix { };
|
||||||
|
}
|
||||||
50
lib/helm/fetchhelm.nix
Normal file
50
lib/helm/fetchhelm.nix
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
{ stdenvNoCC, lib, kubernetes-helm, cacert }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cleanName = name: lib.replaceStrings ["/"] ["-"] name;
|
||||||
|
|
||||||
|
in {
|
||||||
|
# name of the chart
|
||||||
|
chart
|
||||||
|
|
||||||
|
# chart url to fetch from custom location
|
||||||
|
, chartUrl ? null
|
||||||
|
|
||||||
|
# version of the chart
|
||||||
|
, version ? null
|
||||||
|
|
||||||
|
# chart hash
|
||||||
|
, sha256
|
||||||
|
|
||||||
|
# whether to extract chart
|
||||||
|
, untar ? true
|
||||||
|
|
||||||
|
# use custom charts repo
|
||||||
|
, repo ? null
|
||||||
|
|
||||||
|
# pass --verify to helm chart
|
||||||
|
, verify ? false
|
||||||
|
|
||||||
|
# pass --devel to helm chart
|
||||||
|
, devel ? false }: stdenvNoCC.mkDerivation {
|
||||||
|
name = "${cleanName chart}-${if version == null then "dev" else version}";
|
||||||
|
|
||||||
|
buildCommand = ''
|
||||||
|
export HOME="$PWD"
|
||||||
|
helm init >/dev/null
|
||||||
|
echo "adding helm repo"
|
||||||
|
${if repo == null then "" else "helm repo add repository ${repo}"}
|
||||||
|
echo "fetching helm chart"
|
||||||
|
helm fetch -d ./chart \
|
||||||
|
${if untar then "--untar" else ""} \
|
||||||
|
${if version == null then "" else "--version ${version}"} \
|
||||||
|
${if devel then "--devel" else ""} \
|
||||||
|
${if verify then "--verify" else ""} \
|
||||||
|
${if chartUrl == null then (if repo == null then chart else "repository/${chart}") else chartUrl}
|
||||||
|
cp -r chart/*/ $out
|
||||||
|
'';
|
||||||
|
outputHashMode = "recursive";
|
||||||
|
outputHashAlgo = "sha256";
|
||||||
|
outputHash = sha256;
|
||||||
|
nativeBuildInputs = [ kubernetes-helm cacert ];
|
||||||
|
}
|
||||||
43
lib/helm/test.nix
Normal file
43
lib/helm/test.nix
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
|
||||||
|
let
|
||||||
|
fetchhelm = pkgs.callPackage ./fetchhelm.nix { };
|
||||||
|
chart2json = pkgs.callPackage ./chart2json.nix { };
|
||||||
|
in rec {
|
||||||
|
postgresql-chart = fetchhelm {
|
||||||
|
chart = "stable/postgresql";
|
||||||
|
version = "0.18.1";
|
||||||
|
sha256 = "1p3gfmaakxrqb4ncj6nclyfr5afv7xvcdw95c6qyazfg72h3zwjn";
|
||||||
|
};
|
||||||
|
|
||||||
|
istio-chart = fetchhelm {
|
||||||
|
chart = "istio";
|
||||||
|
version = "1.1.0";
|
||||||
|
repo = "https://storage.googleapis.com/istio-release/releases/1.1.0-rc.0/charts";
|
||||||
|
sha256 = "0ippv2914hwpsb3kkhk8d839dii5whgrhxjwhpb9vdwgji5s7yfl";
|
||||||
|
};
|
||||||
|
|
||||||
|
istio-official-chart = pkgs.fetchgit {
|
||||||
|
url = "https://github.com/fyery-chen/istio-helm";
|
||||||
|
rev = "47e235e775314daeb88a3a53689ed66c396ecd3f";
|
||||||
|
sha256 = "190sfyvhdskw6ijy8cprp6hxaazn7s7mg5ids4snshk1pfdg2q8h";
|
||||||
|
};
|
||||||
|
|
||||||
|
postgresql-json = chart2json {
|
||||||
|
name = "postgresql";
|
||||||
|
chart = postgresql-chart;
|
||||||
|
values = {
|
||||||
|
networkPolicy.enabled = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
istio-json = chart2json {
|
||||||
|
name = "istio";
|
||||||
|
chart = istio-chart;
|
||||||
|
};
|
||||||
|
|
||||||
|
istio-official-json = chart2json {
|
||||||
|
name = "istio-official";
|
||||||
|
chart = "${istio-official-chart}/istio-official";
|
||||||
|
};
|
||||||
|
}
|
||||||
61
lib/k8s.nix
Normal file
61
lib/k8s.nix
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
{ lib }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
rec {
|
||||||
|
# TODO: refactor with mkOptionType
|
||||||
|
mkSecretOption = {description ? "", default ? {}, allowNull ? true}: mkOption {
|
||||||
|
inherit description;
|
||||||
|
type = (if allowNull then types.nullOr else id) (types.submodule {
|
||||||
|
options = {
|
||||||
|
name = mkOption ({
|
||||||
|
description = "Name of the secret where secret is stored";
|
||||||
|
type = types.str;
|
||||||
|
default = default.name;
|
||||||
|
} // (optionalAttrs (default ? "name") {
|
||||||
|
default = default.name;
|
||||||
|
}));
|
||||||
|
|
||||||
|
key = mkOption ({
|
||||||
|
description = "Name of the key where secret is stored";
|
||||||
|
type = types.str;
|
||||||
|
} // (optionalAttrs (default ? "key") {
|
||||||
|
default = default.key;
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = if default == null then null else {};
|
||||||
|
};
|
||||||
|
|
||||||
|
secretToEnv = value: {
|
||||||
|
valueFrom.secretKeyRef = {
|
||||||
|
inherit (value) name key;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Creates kubernetes list from a list of kubernetes objects
|
||||||
|
mkList = { items, labels ? {} }: {
|
||||||
|
kind = "List";
|
||||||
|
apiVersion = "v1";
|
||||||
|
|
||||||
|
inherit items labels;
|
||||||
|
};
|
||||||
|
|
||||||
|
# Creates hashed kubernetes list from a list of kubernetes objects
|
||||||
|
mkHashedList = { items, labels ? {} }: let
|
||||||
|
hash = builtins.hashString "sha1" (builtins.toJSON items);
|
||||||
|
|
||||||
|
labeledItems = map (item: recursiveUpdate item {
|
||||||
|
metadata.labels."kubenix/hash" = hash;
|
||||||
|
}) items;
|
||||||
|
|
||||||
|
in mkList {
|
||||||
|
items = labeledItems;
|
||||||
|
labels = {
|
||||||
|
"kubenix/hash" = hash;
|
||||||
|
} // labels;
|
||||||
|
};
|
||||||
|
|
||||||
|
toBase64 = lib.toBase64;
|
||||||
|
octalToDecimal = lib.octalToDecimal;
|
||||||
|
}
|
||||||
39
modules/base.nix
Normal file
39
modules/base.nix
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
{
|
||||||
|
options = {
|
||||||
|
kubenix.project = mkOption {
|
||||||
|
description = "Name of the project";
|
||||||
|
type = types.str;
|
||||||
|
default = "kubenix";
|
||||||
|
};
|
||||||
|
|
||||||
|
_module.features = mkOption {
|
||||||
|
description = "List of features exposed by module";
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
_module.propagate = mkOption {
|
||||||
|
description = "Module propagation options";
|
||||||
|
type = types.listOf (types.submodule ({config, ...}: {
|
||||||
|
options = {
|
||||||
|
features = mkOption {
|
||||||
|
description = "List of features that submodule has to have to propagate module";
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
module = mkOption {
|
||||||
|
description = "Module to propagate";
|
||||||
|
type = types.unspecified;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
12
modules/default.nix
Normal file
12
modules/default.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
k8s = ./k8s.nix;
|
||||||
|
istio = ./istio.nix;
|
||||||
|
submodules = ./submodules.nix;
|
||||||
|
submodule = ./submodule.nix;
|
||||||
|
helm = ./helm.nix;
|
||||||
|
docker = ./docker.nix;
|
||||||
|
testing = ./testing.nix;
|
||||||
|
test = ./test.nix;
|
||||||
|
module = ./module.nix;
|
||||||
|
legacy = ./legacy.nix;
|
||||||
|
}
|
||||||
94
modules/docker.nix
Normal file
94
modules/docker.nix
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
{ config, lib, pkgs, docker, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.docker;
|
||||||
|
in {
|
||||||
|
imports = [ ./base.nix ];
|
||||||
|
|
||||||
|
options.docker = {
|
||||||
|
registry.url = mkOption {
|
||||||
|
description = "Default registry url where images are published";
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
images = mkOption {
|
||||||
|
description = "Attribute set of docker images that should be published";
|
||||||
|
type = types.attrsOf (types.submodule ({ name, config, ... }: {
|
||||||
|
options = {
|
||||||
|
image = mkOption {
|
||||||
|
description = "Docker image to publish";
|
||||||
|
type = types.nullOr types.package;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
name = mkOption {
|
||||||
|
description = "Desired docker image name";
|
||||||
|
type = types.str;
|
||||||
|
default = builtins.unsafeDiscardStringContext config.image.imageName;
|
||||||
|
};
|
||||||
|
|
||||||
|
tag = mkOption {
|
||||||
|
description = "Desired docker image tag";
|
||||||
|
type = types.str;
|
||||||
|
default = builtins.unsafeDiscardStringContext config.image.imageTag;
|
||||||
|
};
|
||||||
|
|
||||||
|
registry = mkOption {
|
||||||
|
description = "Docker registry url where image is published";
|
||||||
|
type = types.str;
|
||||||
|
default = cfg.registry.url;
|
||||||
|
};
|
||||||
|
|
||||||
|
path = mkOption {
|
||||||
|
description = "Full docker image path";
|
||||||
|
type = types.str;
|
||||||
|
default =
|
||||||
|
if config.registry != ""
|
||||||
|
then "${config.registry}/${config.name}:${config.tag}"
|
||||||
|
else "${config.name}:${config.tag}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
export = mkOption {
|
||||||
|
description = "List of images to export";
|
||||||
|
type = types.listOf types.package;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
copyScript = mkOption {
|
||||||
|
description = "Image copy script";
|
||||||
|
type = types.package;
|
||||||
|
default = docker.copyDockerImages {
|
||||||
|
dest = "docker://${cfg.registry.url}";
|
||||||
|
images = cfg.export;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# define docker feature
|
||||||
|
_module.features = ["docker"];
|
||||||
|
|
||||||
|
# pass docker library as param
|
||||||
|
_module.args.docker = import ../lib/docker.nix { inherit lib pkgs; };
|
||||||
|
|
||||||
|
# propagate docker options if docker feature is enabled
|
||||||
|
_module.propagate = [{
|
||||||
|
features = [ "docker" ];
|
||||||
|
module = { config, name, ... }: {
|
||||||
|
# propagate registry options
|
||||||
|
docker.registry = cfg.registry;
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
|
||||||
|
# list of exported docker images
|
||||||
|
docker.export = mapAttrsToList (_: i: i.image)
|
||||||
|
(filterAttrs (_: i: i.registry != null) config.docker.images);
|
||||||
|
};
|
||||||
|
}
|
||||||
28968
modules/generated/v1.10.nix
Executable file
28968
modules/generated/v1.10.nix
Executable file
File diff suppressed because it is too large
Load diff
29520
modules/generated/v1.11.nix
Executable file
29520
modules/generated/v1.11.nix
Executable file
File diff suppressed because it is too large
Load diff
30298
modules/generated/v1.12.nix
Executable file
30298
modules/generated/v1.12.nix
Executable file
File diff suppressed because it is too large
Load diff
30956
modules/generated/v1.13.nix
Executable file
30956
modules/generated/v1.13.nix
Executable file
File diff suppressed because it is too large
Load diff
21525
modules/generated/v1.14.nix
Normal file
21525
modules/generated/v1.14.nix
Normal file
File diff suppressed because it is too large
Load diff
21867
modules/generated/v1.15.nix
Normal file
21867
modules/generated/v1.15.nix
Normal file
File diff suppressed because it is too large
Load diff
12898
modules/generated/v1.7.nix
Executable file
12898
modules/generated/v1.7.nix
Executable file
File diff suppressed because it is too large
Load diff
25892
modules/generated/v1.8.nix
Executable file
25892
modules/generated/v1.8.nix
Executable file
File diff suppressed because it is too large
Load diff
27984
modules/generated/v1.9.nix
Executable file
27984
modules/generated/v1.9.nix
Executable file
File diff suppressed because it is too large
Load diff
109
modules/helm.nix
Normal file
109
modules/helm.nix
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
# helm defines kubenix module with options for using helm charts
|
||||||
|
# with kubenix
|
||||||
|
|
||||||
|
{ config, lib, pkgs, helm, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.kubernetes.helm;
|
||||||
|
|
||||||
|
globalConfig = config;
|
||||||
|
|
||||||
|
recursiveAttrs = mkOptionType {
|
||||||
|
name = "recursive-attrs";
|
||||||
|
description = "recursive attribute set";
|
||||||
|
check = isAttrs;
|
||||||
|
merge = loc: foldl' (res: def: recursiveUpdate res def.value) {};
|
||||||
|
};
|
||||||
|
|
||||||
|
parseApiVersion = apiVersion: let
|
||||||
|
splitted = splitString "/" apiVersion;
|
||||||
|
in {
|
||||||
|
group = if length splitted == 1 then "core" else head splitted;
|
||||||
|
version = last splitted;
|
||||||
|
};
|
||||||
|
|
||||||
|
in {
|
||||||
|
imports = [ ./k8s.nix ];
|
||||||
|
|
||||||
|
options.kubernetes.helm = {
|
||||||
|
instances = mkOption {
|
||||||
|
description = "Attribute set of helm instances";
|
||||||
|
type = types.attrsOf (types.submodule ({ config, name, ... }: {
|
||||||
|
options = {
|
||||||
|
name = mkOption {
|
||||||
|
description = "Helm release name";
|
||||||
|
type = types.str;
|
||||||
|
default = name;
|
||||||
|
};
|
||||||
|
|
||||||
|
chart = mkOption {
|
||||||
|
description = "Helm chart to use";
|
||||||
|
type = types.package;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace = mkOption {
|
||||||
|
description = "Namespace to install helm chart to";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
values = mkOption {
|
||||||
|
description = "Values to pass to chart";
|
||||||
|
type = recursiveAttrs;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubeVersion = mkOption {
|
||||||
|
description = "Kubernetes version to build chart for";
|
||||||
|
type = types.str;
|
||||||
|
default = globalConfig.kubernetes.version;
|
||||||
|
};
|
||||||
|
|
||||||
|
overrides = mkOption {
|
||||||
|
description = "Overrides to apply to all chart resources";
|
||||||
|
type = types.listOf types.unspecified;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
overrideNamespace = mkOption {
|
||||||
|
description = "Whether to apply namespace override";
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
objects = mkOption {
|
||||||
|
description = "Generated kubernetes objects";
|
||||||
|
type = types.listOf types.attrs;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config.overrides = mkIf (config.overrideNamespace && config.namespace != null) [{
|
||||||
|
metadata.namespace = config.namespace;
|
||||||
|
}];
|
||||||
|
|
||||||
|
config.objects = importJSON (helm.chart2json {
|
||||||
|
inherit (config) chart name namespace values kubeVersion;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
# expose helm helper methods as module argument
|
||||||
|
_module.args.helm = import ../lib/helm { inherit pkgs; };
|
||||||
|
|
||||||
|
kubernetes.api.resources = mkMerge (flatten (mapAttrsToList (_: instance:
|
||||||
|
map (object: let
|
||||||
|
apiVersion = parseApiVersion object.apiVersion;
|
||||||
|
name = object.metadata.name;
|
||||||
|
in {
|
||||||
|
"${apiVersion.group}"."${apiVersion.version}".${object.kind}."${name}" = mkMerge ([
|
||||||
|
object
|
||||||
|
] ++ instance.overrides);
|
||||||
|
}) instance.objects
|
||||||
|
) cfg.instances));
|
||||||
|
};
|
||||||
|
}
|
||||||
15
modules/istio-overrides.nix
Normal file
15
modules/istio-overrides.nix
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{ lib, definitions }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
{
|
||||||
|
"istio_networking_v1alpha3_StringMatch" = recursiveUpdate (recursiveUpdate
|
||||||
|
definitions."istio_networking_v1alpha3_StringMatch_Exact"
|
||||||
|
definitions."istio_networking_v1alpha3_StringMatch_Prefix"
|
||||||
|
)
|
||||||
|
definitions."istio_networking_v1alpha3_StringMatch_Regex";
|
||||||
|
|
||||||
|
"istio_networking_v1alpha3_PortSelector" = recursiveUpdate
|
||||||
|
definitions."istio_networking_v1alpha3_PortSelector_Name"
|
||||||
|
definitions."istio_networking_v1alpha3_PortSelector_Number";
|
||||||
|
}
|
||||||
4921
modules/istio.nix
Normal file
4921
modules/istio.nix
Normal file
File diff suppressed because it is too large
Load diff
412
modules/k8s.nix
Normal file
412
modules/k8s.nix
Normal file
|
|
@ -0,0 +1,412 @@
|
||||||
|
# K8S module defines kubernetes definitions for kubenix
|
||||||
|
|
||||||
|
{ options, config, lib, pkgs, k8s, ... }:
|
||||||
|
|
||||||
|
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) &&
|
||||||
|
(default.group == null || default.group == group) &&
|
||||||
|
(default.version == null || default.version == version) &&
|
||||||
|
(default.kind == null || default.kind == kind)
|
||||||
|
) cfg.api.defaults);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
default = mkOption {
|
||||||
|
description = "Default to apply";
|
||||||
|
type = types.unspecified;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = [];
|
||||||
|
apply = unique;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
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";
|
||||||
|
};
|
||||||
|
|
||||||
|
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";
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
result = mkOption {
|
||||||
|
description = "Generated kubernetes JSON file";
|
||||||
|
type = types.package;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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 (
|
||||||
|
mapAttrsToList (name: crd: {
|
||||||
|
group = crd.spec.group;
|
||||||
|
version = crd.spec.version;
|
||||||
|
kind = crd.spec.names.kind;
|
||||||
|
name = crd.spec.names.plural;
|
||||||
|
attrName = mkOptionDefault name;
|
||||||
|
}) (cfg.resources.customResourceDefinitions or {})
|
||||||
|
);
|
||||||
|
|
||||||
|
kubernetes.generated = k8s.mkHashedList {
|
||||||
|
items = config.kubernetes.objects;
|
||||||
|
labels."kubenix/project-name" = config.kubenix.project;
|
||||||
|
labels."kubenix/k8s-version" = config.kubernetes.version;
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.result =
|
||||||
|
pkgs.writeText "kubenix-generated.json" (builtins.toJSON cfg.generated);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,54 +1,15 @@
|
||||||
{ config, options, lib, pkgs, k8s, module ? null, ... }:
|
# support for legacy kubenix
|
||||||
|
|
||||||
|
{ options, config, pkgs, lib, kubenix, ... }:
|
||||||
|
|
||||||
with lib;
|
with lib;
|
||||||
with import ./lib.nix { inherit pkgs lib; };
|
|
||||||
|
|
||||||
let
|
let
|
||||||
globalConfig = config;
|
|
||||||
parentModule = module;
|
parentModule = module;
|
||||||
|
globalConfig = config;
|
||||||
|
|
||||||
mkOptionDefault = mkOverride 1001;
|
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:
|
mkModuleOptions = moduleDefinition: module:
|
||||||
let
|
let
|
||||||
# gets file where module is defined by looking into moduleDefinitions
|
# gets file where module is defined by looking into moduleDefinitions
|
||||||
|
|
@ -70,43 +31,42 @@ let
|
||||||
);
|
);
|
||||||
in [
|
in [
|
||||||
{
|
{
|
||||||
_module.args.k8s = k8s;
|
|
||||||
_module.args.name = module.name;
|
_module.args.name = module.name;
|
||||||
_module.args.module = module;
|
_module.args.module = module;
|
||||||
}
|
}
|
||||||
./kubernetes.nix
|
./k8s.nix
|
||||||
./modules.nix
|
./legacy.nix
|
||||||
(injectModuleAttrs moduleDefinition.module {_file = file;})
|
(injectModuleAttrs moduleDefinition.module {_file = file;})
|
||||||
{
|
{
|
||||||
config.kubernetes.defaults.all.metadata.namespace = mkOptionDefault module.namespace;
|
config.kubernetes.namespace = mkOptionDefault module.namespace;
|
||||||
|
config.kubenix.project = mkOptionDefault config.kubenix.project;
|
||||||
}
|
}
|
||||||
] ++ config.kubernetes.defaultModuleConfiguration.all
|
] ++ config.kubernetes.defaultModuleConfiguration.all
|
||||||
++ (optionals (hasAttr moduleDefinition.name config.kubernetes.defaultModuleConfiguration)
|
++ (optionals (hasAttr moduleDefinition.name config.kubernetes.defaultModuleConfiguration)
|
||||||
config.kubernetes.defaultModuleConfiguration.${moduleDefinition.name});
|
config.kubernetes.defaultModuleConfiguration.${moduleDefinition.name});
|
||||||
|
|
||||||
|
# prefix kubernetes objects with ${serviceName}, this magic was removed in new kubenix
|
||||||
prefixResources = resources: serviceName:
|
prefixResources = resources: serviceName:
|
||||||
mapAttrs (groupName: resources:
|
mapAttrs' (name: resource: nameValuePair "${serviceName}-${name}" resource) resources;
|
||||||
mapAttrs' (name: resource: nameValuePair "${serviceName}-${name}" resource) resources
|
|
||||||
) resources;
|
|
||||||
|
|
||||||
prefixGroupResources = resources: serviceName:
|
|
||||||
mapAttrs' (groupName: resources:
|
|
||||||
nameValuePair "${serviceName}-${groupName}" resources
|
|
||||||
) resources;
|
|
||||||
|
|
||||||
|
# TODO: rewrite using mkOptionType
|
||||||
defaultModuleConfigurationOptions = mapAttrs (name: moduleDefinition: mkOption {
|
defaultModuleConfigurationOptions = mapAttrs (name: moduleDefinition: mkOption {
|
||||||
description = "Module default configuration for ${name} module";
|
description = "Module default configuration for ${name} module";
|
||||||
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
||||||
default = [];
|
default = [];
|
||||||
|
apply = filter (v: v!=[]);
|
||||||
}) config.kubernetes.moduleDefinitions;
|
}) config.kubernetes.moduleDefinitions;
|
||||||
|
|
||||||
getModuleDefinition = name:
|
getModuleDefinition = name:
|
||||||
if hasAttr name config.kubernetes.moduleDefinitions
|
if hasAttr name config.kubernetes.moduleDefinitions
|
||||||
then config.kubernetes.moduleDefinitions.${name}
|
then config.kubernetes.moduleDefinitions.${name}
|
||||||
else throw ''requested kubernetes moduleDefinition with name "${name}" does not exist'';
|
else throw ''requested kubernetes moduleDefinition with name "${name}" does not exist'';
|
||||||
|
|
||||||
in {
|
in {
|
||||||
|
imports = [ ./k8s.nix ];
|
||||||
|
|
||||||
options.kubernetes.moduleDefinitions = mkOption {
|
options.kubernetes.moduleDefinitions = mkOption {
|
||||||
description = "Attribute set of module definitions";
|
description = "Legacy kubenix attribute set of module definitions";
|
||||||
default = {};
|
default = {};
|
||||||
type = types.attrsOf (types.submodule ({name, ...}: {
|
type = types.attrsOf (types.submodule ({name, ...}: {
|
||||||
options = {
|
options = {
|
||||||
|
|
@ -136,13 +96,14 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
options.kubernetes.defaultModuleConfiguration = mkOption {
|
options.kubernetes.defaultModuleConfiguration = mkOption {
|
||||||
description = "Module default options";
|
description = "Legacy kubenix module default options";
|
||||||
type = types.submodule {
|
type = types.submodule {
|
||||||
options = defaultModuleConfigurationOptions // {
|
options = defaultModuleConfigurationOptions // {
|
||||||
all = mkOption {
|
all = mkOption {
|
||||||
description = "Module default configuration for all modules";
|
description = "Module default configuration for all modules";
|
||||||
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
||||||
default = [];
|
default = [];
|
||||||
|
apply = filter (v: v != []);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
@ -150,7 +111,7 @@ in {
|
||||||
};
|
};
|
||||||
|
|
||||||
options.kubernetes.modules = mkOption {
|
options.kubernetes.modules = mkOption {
|
||||||
description = "Attribute set of modules";
|
description = "Legacy kubenix attribute set of modules";
|
||||||
default = {};
|
default = {};
|
||||||
type = types.attrsOf (types.submodule ({config, name, ...}: {
|
type = types.attrsOf (types.submodule ({config, name, ...}: {
|
||||||
options = {
|
options = {
|
||||||
|
|
@ -163,10 +124,7 @@ in {
|
||||||
namespace = mkOption {
|
namespace = mkOption {
|
||||||
description = "Namespace where to deploy module";
|
description = "Namespace where to deploy module";
|
||||||
type = types.str;
|
type = types.str;
|
||||||
default =
|
default = globalConfig.kubernetes.namespace;
|
||||||
if parentModule != null
|
|
||||||
then parentModule.namespace
|
|
||||||
else "default";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
labels = mkOption {
|
labels = mkOption {
|
||||||
|
|
@ -177,8 +135,10 @@ in {
|
||||||
|
|
||||||
configuration = mkOption {
|
configuration = mkOption {
|
||||||
description = "Module configuration";
|
description = "Module configuration";
|
||||||
type = submodule {
|
type = submoduleWithSpecialArgs {
|
||||||
imports = mkModuleOptions (getModuleDefinition config.module) config;
|
imports = mkModuleOptions (getModuleDefinition config.module) config;
|
||||||
|
} {
|
||||||
|
inherit kubenix;
|
||||||
};
|
};
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
|
|
@ -192,39 +152,54 @@ in {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
options.kubernetes.defaults = mkOption {
|
||||||
|
type = types.attrsOf (types.coercedTo types.attrs (value: [value]) (types.listOf types.attrs));
|
||||||
|
description = "Legacy kubenix kubernetes defaults.";
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
# for back compatibility with kubernetes.customResources
|
||||||
|
options.kubernetes.customResources = options.kubernetes.resources;
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
kubernetes.resources = mkMerge (
|
kubernetes = mkMerge [{
|
||||||
mapAttrsToList (name: module: let
|
api.defaults = mapAttrsToList (attrName: default: let
|
||||||
|
type = head (mapAttrsToList (_: v: v) (filterAttrs (_: type: type.attrName == attrName) config.kubernetes.api.types));
|
||||||
|
in {
|
||||||
|
default = { imports = default; };
|
||||||
|
} // (if (attrName == "all") then {} else {
|
||||||
|
resource = type.name;
|
||||||
|
})) config.kubernetes.defaults;
|
||||||
|
|
||||||
|
resources = mkMerge (
|
||||||
|
mapAttrsToList (name: module:
|
||||||
|
mapAttrs' (_: type: let
|
||||||
moduleDefinition = getModuleDefinition module.module;
|
moduleDefinition = getModuleDefinition module.module;
|
||||||
|
|
||||||
|
moduleResources = module.configuration.kubernetes.api.resources.${type.attrName} or {};
|
||||||
|
|
||||||
moduleConfig =
|
moduleConfig =
|
||||||
if moduleDefinition.prefixResources
|
if moduleDefinition.prefixResources && type.kind != "CustomResourceDefinition"
|
||||||
then prefixResources (moduleToAttrs module.configuration.kubernetes.resources) name
|
then prefixResources (moduleToAttrs moduleResources) name
|
||||||
else moduleToAttrs module.configuration.kubernetes.resources;
|
else moduleToAttrs moduleResources;
|
||||||
in
|
in nameValuePair type.attrName
|
||||||
if moduleDefinition.assignAsDefaults
|
(if moduleDefinition.assignAsDefaults
|
||||||
then mkAllDefault moduleConfig 1000
|
then mkAllDefault moduleConfig 1000
|
||||||
else moduleConfig
|
else moduleConfig)
|
||||||
|
) module.configuration.kubernetes.api.types
|
||||||
) config.kubernetes.modules
|
) config.kubernetes.modules
|
||||||
);
|
);
|
||||||
|
|
||||||
kubernetes.customResources = mkMerge (
|
# create custom types from CRDs was old behavior
|
||||||
mapAttrsToList (name: module: let
|
createCustomTypesFromCRDs = true;
|
||||||
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 = {
|
defaultModuleConfiguration.all = {
|
||||||
_file = head options.kubernetes.defaultModuleConfiguration.files;
|
_file = head options.kubernetes.defaultModuleConfiguration.files;
|
||||||
config.kubernetes.version = mkDefault config.kubernetes.version;
|
config.kubernetes.version = mkDefault config.kubernetes.version;
|
||||||
config.kubernetes.moduleDefinitions = config.kubernetes.moduleDefinitions;
|
config.kubernetes.moduleDefinitions = config.kubernetes.moduleDefinitions;
|
||||||
};
|
};
|
||||||
|
} {
|
||||||
|
resources = mkAliasDefinitions options.kubernetes.customResources;
|
||||||
|
}];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
49
modules/submodule.nix
Normal file
49
modules/submodule.nix
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
{ config, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [ ./base.nix ];
|
||||||
|
|
||||||
|
options.submodule = {
|
||||||
|
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 = "1.0.0";
|
||||||
|
};
|
||||||
|
|
||||||
|
tags = mkOption {
|
||||||
|
description = "List of submodule tags";
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
exports = mkOption {
|
||||||
|
description = "Attribute set of functions to export";
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
passthru = mkOption {
|
||||||
|
description = "Attribute set to passthru";
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
args._empty = mkOption {};
|
||||||
|
};
|
||||||
|
|
||||||
|
config._module.features = ["submodule"];
|
||||||
|
config._module.args.args = config.submodule.args;
|
||||||
|
}
|
||||||
290
modules/submodules.nix
Normal file
290
modules/submodules.nix
Normal file
|
|
@ -0,0 +1,290 @@
|
||||||
|
{ config, options, kubenix, pkgs, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.submodules;
|
||||||
|
parentConfig = config;
|
||||||
|
|
||||||
|
matchesVersion = requiredVersion: version:
|
||||||
|
if requiredVersion != null then
|
||||||
|
if hasPrefix "~" requiredVersion
|
||||||
|
then (builtins.match (removePrefix "~" requiredVersion) version) != null
|
||||||
|
else requiredVersion == version
|
||||||
|
else true;
|
||||||
|
|
||||||
|
getDefaults = {name, version, tags, features}:
|
||||||
|
catAttrs "default" (filter (submoduleDefault:
|
||||||
|
(submoduleDefault.name == null || submoduleDefault.name == name) &&
|
||||||
|
(matchesVersion submoduleDefault.version version) &&
|
||||||
|
(
|
||||||
|
(length submoduleDefault.tags == 0) ||
|
||||||
|
(length (intersectLists submoduleDefault.tags tags)) > 0
|
||||||
|
) &&
|
||||||
|
(
|
||||||
|
(length submoduleDefault.features == 0) ||
|
||||||
|
(length (intersectLists submoduleDefault.features features)) > 0
|
||||||
|
)
|
||||||
|
) config.submodules.defaults);
|
||||||
|
|
||||||
|
specialArgs = cfg.specialArgs // {
|
||||||
|
parentConfig = config;
|
||||||
|
};
|
||||||
|
|
||||||
|
findSubmodule = {name, version ? null, latest ? true}: let
|
||||||
|
matchingSubmodules = filter (el:
|
||||||
|
el.definition.name == name &&
|
||||||
|
(matchesVersion version el.definition.version)
|
||||||
|
) 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;
|
||||||
|
|
||||||
|
passthruConfig = mapAttrsToList (name: opt: {
|
||||||
|
${name} = mkMerge (mapAttrsToList (_: inst:
|
||||||
|
if inst.passthru.enable
|
||||||
|
then inst.config.submodule.passthru.${name} or {}
|
||||||
|
else {}
|
||||||
|
) config.submodules.instances);
|
||||||
|
|
||||||
|
_module.args = mkMerge (mapAttrsToList (_: inst:
|
||||||
|
if inst.passthru.enable
|
||||||
|
then inst.config.submodule.passthru._module.args or {}
|
||||||
|
else {}
|
||||||
|
) config.submodules.instances);
|
||||||
|
}) (removeAttrs options ["_definedNames" "_module" "submodules"]);
|
||||||
|
in {
|
||||||
|
imports = [ ./base.nix ];
|
||||||
|
|
||||||
|
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 = "List of defaults to apply to submodule instances";
|
||||||
|
type = types.listOf (types.submodule ({config, ...}: {
|
||||||
|
options = {
|
||||||
|
name = mkOption {
|
||||||
|
description = "Name of the submodule to apply defaults for";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
version = mkOption {
|
||||||
|
description = ''
|
||||||
|
Version of submodule to apply defaults for. If version starts with
|
||||||
|
"~" it is threated as regex pattern for example "~1.0.*
|
||||||
|
'';
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
tags = mkOption {
|
||||||
|
description = "List of tags to apply defaults for";
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
features = mkOption {
|
||||||
|
description = "List of features that submodule has to have to apply defaults";
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
default = mkOption {
|
||||||
|
description = "Default to apply to submodule instance";
|
||||||
|
type = types.unspecified;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.propagate.enable = mkOption {
|
||||||
|
description = "Whether to propagate defaults and imports from parent to child";
|
||||||
|
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
|
||||||
|
evaledSubmodule' = evalModules {
|
||||||
|
inherit specialArgs;
|
||||||
|
modules = config.modules ++ [ ./base.nix ];
|
||||||
|
check = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
evaledSubmodule =
|
||||||
|
if (!(elem "submodule" evaledSubmodule'.config._module.features))
|
||||||
|
then throw "no submodule defined"
|
||||||
|
else evaledSubmodule';
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
module = mkOption {
|
||||||
|
description = "Module defining submodule";
|
||||||
|
type = types.unspecified;
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = mkOption {
|
||||||
|
description = "List of modules defining submodule";
|
||||||
|
type = types.listOf types.unspecified;
|
||||||
|
default = [config.module];
|
||||||
|
};
|
||||||
|
|
||||||
|
features = mkOption {
|
||||||
|
description = "List of features exposed by submodule";
|
||||||
|
type = types.listOf types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
definition = mkOption {
|
||||||
|
description = "Submodule definition";
|
||||||
|
type = types.attrs;
|
||||||
|
};
|
||||||
|
|
||||||
|
exportAs = mkOption {
|
||||||
|
description = "Name under which to register exports";
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
definition = {
|
||||||
|
inherit (evaledSubmodule.config.submodule) name description version tags exports;
|
||||||
|
};
|
||||||
|
|
||||||
|
features = evaledSubmodule.config._module.features;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.instances = mkOption {
|
||||||
|
description = "Attribute set of submodule instances";
|
||||||
|
default = {};
|
||||||
|
type = types.attrsOf (types.submodule ({name, config, options, ...}: let
|
||||||
|
# submodule associated with
|
||||||
|
submodule = findSubmodule {
|
||||||
|
name = config.submodule;
|
||||||
|
version = config.version;
|
||||||
|
};
|
||||||
|
|
||||||
|
# definition of a submodule
|
||||||
|
submoduleDefinition = submodule.definition;
|
||||||
|
|
||||||
|
# submodule defaults
|
||||||
|
defaults = getDefaults {
|
||||||
|
name = submoduleDefinition.name;
|
||||||
|
version = submoduleDefinition.version;
|
||||||
|
tags = submoduleDefinition.tags;
|
||||||
|
features = submodule.features;
|
||||||
|
};
|
||||||
|
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, if version starts with "~" it is
|
||||||
|
threated as regex pattern for example "~1.0.*"
|
||||||
|
'';
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
passthru.enable = mkOption {
|
||||||
|
description = "Whether to passthru submodule resources";
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkOption {
|
||||||
|
description = "Submodule instance ${config.name} for ${submoduleDefinition.name}:${submoduleDefinition.version} config";
|
||||||
|
type = submoduleWithSpecialArgs ({...}: {
|
||||||
|
imports = submodule.modules ++ defaults ++ [ ./base.nix ];
|
||||||
|
_module.args.pkgs = pkgs;
|
||||||
|
_module.args.name = config.name;
|
||||||
|
_module.args.submodule = config;
|
||||||
|
submodule.args = mkAliasDefinitions options.args;
|
||||||
|
}) specialArgs;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
args = mkOption {
|
||||||
|
description = "Submodule arguments (alias of config.submodule.args)";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkMerge ([
|
||||||
|
{
|
||||||
|
# register exported functions as args
|
||||||
|
_module.args = mkMerge (map (submodule: {
|
||||||
|
${submodule.exportAs} = submodule.definition.exports;
|
||||||
|
}) (filter (submodule: submodule.exportAs != null) cfg.imports));
|
||||||
|
|
||||||
|
_module.features = ["submodules"];
|
||||||
|
|
||||||
|
submodules.specialArgs.kubenix = kubenix;
|
||||||
|
|
||||||
|
# passthru kubenix.project to submodules
|
||||||
|
submodules.defaults = mkMerge [
|
||||||
|
[{
|
||||||
|
default = {
|
||||||
|
kubenix.project = parentConfig.kubenix.project;
|
||||||
|
};
|
||||||
|
}]
|
||||||
|
|
||||||
|
(map (propagate: {
|
||||||
|
features = propagate.features;
|
||||||
|
default = propagate.module;
|
||||||
|
}) config._module.propagate)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
(mkIf cfg.propagate.enable {
|
||||||
|
# if propagate is enabled and submodule has submodules included propagage defaults and imports
|
||||||
|
submodules.defaults = [{
|
||||||
|
features = ["submodules"];
|
||||||
|
default = {
|
||||||
|
submodules = {
|
||||||
|
defaults = cfg.defaults;
|
||||||
|
imports = cfg.imports;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
})
|
||||||
|
] ++ passthruConfig);
|
||||||
|
}
|
||||||
67
modules/test.nix
Normal file
67
modules/test.nix
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
{ lib, config, pkgs, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.test;
|
||||||
|
in {
|
||||||
|
options.test = {
|
||||||
|
name = mkOption {
|
||||||
|
description = "Test name";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
description = mkOption {
|
||||||
|
description = "Test description";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
enable = mkOption {
|
||||||
|
description = "Whether to enable test";
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
assertions = mkOption {
|
||||||
|
type = types.listOf (types.submodule {
|
||||||
|
options = {
|
||||||
|
assertion = mkOption {
|
||||||
|
description = "assertion value";
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
message = mkOption {
|
||||||
|
description = "assertion message";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
});
|
||||||
|
default = [];
|
||||||
|
example = [ { assertion = false; message = "you can't enable this for that reason"; } ];
|
||||||
|
description = ''
|
||||||
|
This option allows modules to express conditions that must
|
||||||
|
hold for the evaluation of the system configuration to
|
||||||
|
succeed, along with associated error messages for the user.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
extraCheckInputs = mkOption {
|
||||||
|
description = "Extra check inputs";
|
||||||
|
type = types.listOf types.package;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = mkOption {
|
||||||
|
description = "Script to run as part of testing";
|
||||||
|
type = types.nullOr types.lines;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
extraConfiguration = mkOption {
|
||||||
|
description = "Extra configuration for running test";
|
||||||
|
type = types.unspecified;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
291
modules/testing.nix
Normal file
291
modules/testing.nix
Normal file
|
|
@ -0,0 +1,291 @@
|
||||||
|
{ nixosPath, config, pkgs, lib, kubenix, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.testing;
|
||||||
|
|
||||||
|
toJSONFile = content: builtins.toFile "json" (builtins.toJSON content);
|
||||||
|
|
||||||
|
nixosTesting = import "${nixosPath}/lib/testing.nix" {
|
||||||
|
inherit pkgs;
|
||||||
|
system = "x86_64-linux";
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetesBaseConfig = { modulesPath, config, pkgs, lib, nodes, ... }: let
|
||||||
|
master = findFirst
|
||||||
|
(node: any (role: role == "master") node.config.services.kubernetes.roles)
|
||||||
|
(throw "no master node")
|
||||||
|
(attrValues nodes);
|
||||||
|
extraHosts = ''
|
||||||
|
${master.config.networking.primaryIPAddress} etcd.${config.networking.domain}
|
||||||
|
${master.config.networking.primaryIPAddress} api.${config.networking.domain}
|
||||||
|
${concatMapStringsSep "\n"
|
||||||
|
(node: let n = node.config.networking; in "${n.primaryIPAddress} ${n.hostName}.${n.domain}")
|
||||||
|
(attrValues nodes)}
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
imports = [ "${toString modulesPath}/profiles/minimal.nix" ];
|
||||||
|
|
||||||
|
config = mkMerge [{
|
||||||
|
boot.postBootCommands = "rm -fr /var/lib/kubernetes/secrets /tmp/shared/*";
|
||||||
|
virtualisation.memorySize = mkDefault 2048;
|
||||||
|
virtualisation.cores = mkDefault 16;
|
||||||
|
virtualisation.diskSize = mkDefault 4096;
|
||||||
|
networking = {
|
||||||
|
inherit extraHosts;
|
||||||
|
domain = "my.xzy";
|
||||||
|
nameservers = ["10.0.0.254"];
|
||||||
|
firewall = {
|
||||||
|
allowedTCPPorts = [
|
||||||
|
10250 # kubelet
|
||||||
|
];
|
||||||
|
trustedInterfaces = ["docker0" "cni0"];
|
||||||
|
|
||||||
|
extraCommands = concatMapStrings (node: ''
|
||||||
|
iptables -A INPUT -s ${node.config.networking.primaryIPAddress} -j ACCEPT
|
||||||
|
'') (attrValues nodes);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
environment.systemPackages = [ pkgs.kubectl ];
|
||||||
|
environment.variables.KUBECONFIG = "/etc/kubernetes/cluster-admin.kubeconfig";
|
||||||
|
services.flannel.iface = "eth1";
|
||||||
|
services.kubernetes = {
|
||||||
|
easyCerts = true;
|
||||||
|
apiserver = {
|
||||||
|
securePort = 443;
|
||||||
|
advertiseAddress = master.config.networking.primaryIPAddress;
|
||||||
|
};
|
||||||
|
masterAddress = "${master.config.networking.hostName}.${master.config.networking.domain}";
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.extraConfig = "DefaultLimitNOFILE=1048576";
|
||||||
|
}
|
||||||
|
(mkIf (any (role: role == "master") config.services.kubernetes.roles) {
|
||||||
|
networking.firewall.allowedTCPPorts = [
|
||||||
|
443 # kubernetes apiserver
|
||||||
|
];
|
||||||
|
})];
|
||||||
|
};
|
||||||
|
|
||||||
|
mkKubernetesSingleNodeTest = { name, testScript, extraConfiguration ? {} }:
|
||||||
|
nixosTesting.makeTest {
|
||||||
|
inherit name;
|
||||||
|
|
||||||
|
nodes.kube = { config, pkgs, nodes, ... }: {
|
||||||
|
imports = [ kubernetesBaseConfig extraConfiguration ];
|
||||||
|
services.kubernetes = {
|
||||||
|
roles = ["master" "node"];
|
||||||
|
flannel.enable = false;
|
||||||
|
kubelet = {
|
||||||
|
networkPlugin = "cni";
|
||||||
|
cni.config = [{
|
||||||
|
name = "mynet";
|
||||||
|
type = "bridge";
|
||||||
|
bridge = "cni0";
|
||||||
|
addIf = true;
|
||||||
|
ipMasq = true;
|
||||||
|
isGateway = true;
|
||||||
|
ipam = {
|
||||||
|
type = "host-local";
|
||||||
|
subnet = "10.1.0.0/16";
|
||||||
|
gateway = "10.1.0.1";
|
||||||
|
routes = [{
|
||||||
|
dst = "0.0.0.0/0";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
networking.primaryIPAddress = mkForce "192.168.1.1";
|
||||||
|
};
|
||||||
|
|
||||||
|
testScript = ''
|
||||||
|
startAll;
|
||||||
|
|
||||||
|
$kube->waitUntilSucceeds("kubectl get node kube.my.xzy | grep -w Ready");
|
||||||
|
|
||||||
|
${testScript}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
testOptions = { config, ... }: let
|
||||||
|
modules = [config.module ./test.nix ./base.nix {
|
||||||
|
config = {
|
||||||
|
kubenix.project = mkDefault config.name;
|
||||||
|
_module.args = {
|
||||||
|
test = config;
|
||||||
|
} // cfg.args;
|
||||||
|
};
|
||||||
|
}] ++ cfg.defaults;
|
||||||
|
|
||||||
|
test = (kubenix.evalModules {
|
||||||
|
check = false;
|
||||||
|
inherit modules;
|
||||||
|
}).config.test;
|
||||||
|
|
||||||
|
evaled' = kubenix.evalModules {
|
||||||
|
inherit modules;
|
||||||
|
};
|
||||||
|
|
||||||
|
evaled =
|
||||||
|
if cfg.throwError then evaled'
|
||||||
|
else if (builtins.tryEval evaled'.config.test.assertions).success then evaled' else null;
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
name = mkOption {
|
||||||
|
description = "test name";
|
||||||
|
type = types.str;
|
||||||
|
internal = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
description = mkOption {
|
||||||
|
description = "test description";
|
||||||
|
type = types.str;
|
||||||
|
internal = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
enable = mkOption {
|
||||||
|
description = "Whether to enable test";
|
||||||
|
type = types.bool;
|
||||||
|
internal = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
module = mkOption {
|
||||||
|
description = "Module defining submodule";
|
||||||
|
type = types.unspecified;
|
||||||
|
};
|
||||||
|
|
||||||
|
evaled = mkOption {
|
||||||
|
description = "Test evaulation result";
|
||||||
|
type = types.nullOr types.attrs;
|
||||||
|
internal = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
success = mkOption {
|
||||||
|
description = "Whether test was success";
|
||||||
|
type = types.bool;
|
||||||
|
internal = true;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
assertions = mkOption {
|
||||||
|
description = "Test result";
|
||||||
|
type = types.unspecified;
|
||||||
|
internal = true;
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
test = mkOption {
|
||||||
|
description = "Test derivation to run";
|
||||||
|
type = types.nullOr types.package;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
result = mkOption {
|
||||||
|
description = "Test result";
|
||||||
|
type = types.package;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkMerge [{
|
||||||
|
inherit evaled;
|
||||||
|
inherit (test) name description enable;
|
||||||
|
result = pkgs.runCommand "${cfg.name}-${config.name}-test.json" {
|
||||||
|
buildInputs = [ pkgs.jshon pkgs.jq ];
|
||||||
|
} ''
|
||||||
|
jshon -n object \
|
||||||
|
-s "${config.name}" -i name \
|
||||||
|
-s "${config.description}" -i description \
|
||||||
|
-n "${if config.success then "true" else "false"}" -i success \
|
||||||
|
${if config.test == null then "-n null" else "-s '${config.test}'"} -i test > result.json
|
||||||
|
|
||||||
|
jq -s '.[0].assertions = .[1] | .[0]' result.json ${toJSONFile (map (getAttrs ["assertion" "message"]) config.assertions)} > $out
|
||||||
|
'';
|
||||||
|
} (mkIf (config.evaled != null) {
|
||||||
|
inherit (evaled.config.test) assertions;
|
||||||
|
success = all (el: el.assertion) config.assertions;
|
||||||
|
test =
|
||||||
|
if cfg.e2e && evaled.config.test.testScript != null
|
||||||
|
then mkKubernetesSingleNodeTest {
|
||||||
|
name = config.name;
|
||||||
|
inherit (evaled.config.test) testScript extraConfiguration;
|
||||||
|
} else null;
|
||||||
|
})];
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
options = {
|
||||||
|
testing.name = mkOption {
|
||||||
|
description = "Testing suite name";
|
||||||
|
type = types.str;
|
||||||
|
default = "default";
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.throwError = mkOption {
|
||||||
|
description = "Whether to throw error";
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.e2e = mkOption {
|
||||||
|
description = "Whether to enable e2e tests";
|
||||||
|
type = types.bool;
|
||||||
|
default = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.defaults = mkOption {
|
||||||
|
description = "Testing defaults";
|
||||||
|
type = types.coercedTo types.unspecified (value: [value]) (types.listOf types.unspecified);
|
||||||
|
example = literalExample ''{config, ...}: {
|
||||||
|
kubernetes.version = config.kubernetes.version;
|
||||||
|
}'';
|
||||||
|
apply = filter (v: v!=[]);
|
||||||
|
default = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.tests = mkOption {
|
||||||
|
description = "List of test cases";
|
||||||
|
default = [];
|
||||||
|
type = types.listOf (types.coercedTo types.path (module: {inherit module;}) (types.submodule testOptions));
|
||||||
|
apply = tests: filter (test: test.enable) tests;
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.args = mkOption {
|
||||||
|
description = "Attribute set of extra args passed to tests";
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.testsByName = mkOption {
|
||||||
|
description = "Tests by name";
|
||||||
|
type = types.attrsOf types.attrs;
|
||||||
|
default = listToAttrs (map (test: nameValuePair test.name test) cfg.tests);
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.success = mkOption {
|
||||||
|
description = "Whether testing was a success";
|
||||||
|
type = types.bool;
|
||||||
|
default = all (test: test.success) cfg.tests;
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.results = mkOption {
|
||||||
|
description = "Test results";
|
||||||
|
type = types.attrsOf types.package;
|
||||||
|
default = mapAttrs (_: t: t.result) cfg.testsByName;
|
||||||
|
};
|
||||||
|
|
||||||
|
testing.result = mkOption {
|
||||||
|
description = "Testing results";
|
||||||
|
type = types.package;
|
||||||
|
default = pkgs.runCommand "${cfg.name}-test-results.json" {
|
||||||
|
buildInputs = [ pkgs.jq ];
|
||||||
|
} ''
|
||||||
|
jq -s -r '.' ${concatMapStringsSep " " (t: t.result) cfg.tests} > tests.json
|
||||||
|
jq -n \
|
||||||
|
--rawfile tests tests.json \
|
||||||
|
--argjson success `jq -s -r 'if all(.success) == true then true else false end' ${concatMapStringsSep " " (t: t.result) cfg.tests}` \
|
||||||
|
'{"success": $success, "tests": $tests | fromjson }' > $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
148
release.nix
Normal file
148
release.nix
Normal file
|
|
@ -0,0 +1,148 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {}, nixosPath ? toString <nixpkgs/nixos>, lib ? pkgs.lib
|
||||||
|
, e2e ? true, throwError ? true }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
kubenix = import ./. { inherit pkgs; };
|
||||||
|
|
||||||
|
lib = kubenix.lib;
|
||||||
|
|
||||||
|
generateK8S = name: spec: import ./generators/k8s {
|
||||||
|
inherit name;
|
||||||
|
inherit pkgs;
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
inherit spec;
|
||||||
|
};
|
||||||
|
|
||||||
|
generateIstio = import ./generators/istio {
|
||||||
|
inherit pkgs;
|
||||||
|
inherit (pkgs) lib;
|
||||||
|
};
|
||||||
|
|
||||||
|
runK8STests = k8sVersion: import ./tests {
|
||||||
|
inherit pkgs lib kubenix k8sVersion e2e throwError nixosPath;
|
||||||
|
};
|
||||||
|
in rec {
|
||||||
|
generate.k8s = pkgs.linkFarm "k8s-generated.nix" [
|
||||||
|
{
|
||||||
|
name = "v1.7.nix";
|
||||||
|
path = generateK8S "v1.7" "${pkgs.fetchFromGitHub {
|
||||||
|
owner = "kubernetes";
|
||||||
|
repo = "kubernetes";
|
||||||
|
rev = "v1.7.16";
|
||||||
|
sha256 = "1ksalw3hzbcca89n9h3pas9nqj2n5gq3rbpdx633ycqb8g46h1iw";
|
||||||
|
}}/api/openapi-spec/swagger.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "v1.8.nix";
|
||||||
|
path = generateK8S "v1.8" "${pkgs.fetchFromGitHub {
|
||||||
|
owner = "kubernetes";
|
||||||
|
repo = "kubernetes";
|
||||||
|
rev = "v1.8.15";
|
||||||
|
sha256 = "1mwaafnkimr4kwqws4qli11wbavpmf27i6pjq77sfsapw9sz54j4";
|
||||||
|
}}/api/openapi-spec/swagger.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "v1.9.nix";
|
||||||
|
path = generateK8S "v1.9" "${pkgs.fetchFromGitHub {
|
||||||
|
owner = "kubernetes";
|
||||||
|
repo = "kubernetes";
|
||||||
|
rev = "v1.9.11";
|
||||||
|
sha256 = "1wl944ci7k8knrkdrc328agyq4c953j9dm0sn314s42j18lfd7rv";
|
||||||
|
}}/api/openapi-spec/swagger.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "v1.10.nix";
|
||||||
|
path = generateK8S "v1.10" "${pkgs.fetchFromGitHub {
|
||||||
|
owner = "kubernetes";
|
||||||
|
repo = "kubernetes";
|
||||||
|
rev = "v1.10.13";
|
||||||
|
sha256 = "07hwcamlc1kh5flwv4ahfkcg2lyhnbs8q2xczaws6v3sjxaycrrn";
|
||||||
|
}}/api/openapi-spec/swagger.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "v1.11.nix";
|
||||||
|
path = generateK8S "v1.11" "${pkgs.fetchFromGitHub {
|
||||||
|
owner = "kubernetes";
|
||||||
|
repo = "kubernetes";
|
||||||
|
rev = "v1.11.8";
|
||||||
|
sha256 = "1q6x38zdycd4ai31gn666hg41bs4q32dyz2d07x76hj33fkzqs1f";
|
||||||
|
}}/api/openapi-spec/swagger.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "v1.12.nix";
|
||||||
|
path = generateK8S "v1.12" "${pkgs.fetchFromGitHub {
|
||||||
|
owner = "kubernetes";
|
||||||
|
repo = "kubernetes";
|
||||||
|
rev = "v1.12.6";
|
||||||
|
sha256 = "0p9wh264xfm4c0inz99jclf603c414807vn19gfn62bfls3jcmgf";
|
||||||
|
}}/api/openapi-spec/swagger.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "v1.13.nix";
|
||||||
|
path = generateK8S "v1.13" "${pkgs.fetchFromGitHub {
|
||||||
|
owner = "kubernetes";
|
||||||
|
repo = "kubernetes";
|
||||||
|
rev = "v1.13.4";
|
||||||
|
sha256 = "1q3dc416fr9nzy64pl7rydahygnird0vpk9yflssw7v9gx84m6x9";
|
||||||
|
}}/api/openapi-spec/swagger.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "v1.14.nix";
|
||||||
|
path = generateK8S "v1.14" "${pkgs.fetchFromGitHub {
|
||||||
|
owner = "kubernetes";
|
||||||
|
repo = "kubernetes";
|
||||||
|
rev = "v1.14.10";
|
||||||
|
sha256 = "0lkajm0qfi0qgcqm465z9bi04f778pg3qwnnkxlq38p7ibvi5vn4";
|
||||||
|
}}/api/openapi-spec/swagger.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
name = "v1.15.nix";
|
||||||
|
path = generateK8S "v1.15" "${pkgs.fetchFromGitHub {
|
||||||
|
owner = "kubernetes";
|
||||||
|
repo = "kubernetes";
|
||||||
|
rev = "v1.15.7";
|
||||||
|
sha256 = "1hp6231c1l1fx9s182ivy1s6cgqlk208dj95dbhajd3qq8fdabqc";
|
||||||
|
}}/api/openapi-spec/swagger.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
];
|
||||||
|
|
||||||
|
generate.istio = pkgs.linkFarm "istio-generated.nix" [{
|
||||||
|
name = "latest.nix";
|
||||||
|
path = generateIstio;
|
||||||
|
}];
|
||||||
|
|
||||||
|
tests = {
|
||||||
|
k8s-1_7 = runK8STests "1.7";
|
||||||
|
k8s-1_8 = runK8STests "1.8";
|
||||||
|
k8s-1_9 = runK8STests "1.9";
|
||||||
|
k8s-1_10 = runK8STests "1.10";
|
||||||
|
k8s-1_11 = runK8STests "1.11";
|
||||||
|
k8s-1_12 = runK8STests "1.12";
|
||||||
|
k8s-1_13 = runK8STests "1.13";
|
||||||
|
k8s-1_14 = runK8STests "1.14";
|
||||||
|
k8s-1_15 = runK8STests "1.15";
|
||||||
|
};
|
||||||
|
|
||||||
|
test-results = pkgs.recurseIntoAttrs (mapAttrs (_: t: pkgs.recurseIntoAttrs {
|
||||||
|
results = pkgs.recurseIntoAttrs t.results;
|
||||||
|
result = t.result;
|
||||||
|
}) tests);
|
||||||
|
|
||||||
|
test-check =
|
||||||
|
if !(all (test: test.success) (attrValues tests))
|
||||||
|
then throw "tests failed"
|
||||||
|
else true;
|
||||||
|
|
||||||
|
examples = import ./examples {};
|
||||||
|
}
|
||||||
56391
specs/1.7/swagger.json
56391
specs/1.7/swagger.json
File diff suppressed because it is too large
Load diff
73741
specs/1.8/swagger.json
73741
specs/1.8/swagger.json
File diff suppressed because it is too large
Load diff
85340
specs/1.9/swagger.json
85340
specs/1.9/swagger.json
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +0,0 @@
|
||||||
{
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"data": {
|
|
||||||
"game.properties": "enemies=aliens\nlives=3\nenemies.cheat=true\nenemies.cheat.level=noGoodRotten\nsecret.code.passphrase=UUDDLRLRBABAS\nsecret.code.allowed=true\nsecret.code.lives=30\n",
|
|
||||||
"ui.properties": "color.good=purple\ncolor.bad=yellow\nallow.textmode=true\nhow.nice.to.look=fairlyNice\n"
|
|
||||||
},
|
|
||||||
"kind": "ConfigMap"
|
|
||||||
}
|
|
||||||
11
test/cr.json
11
test/cr.json
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"apiVersion": "stable.example.com/v1",
|
|
||||||
"kind": "CronTab",
|
|
||||||
"metadata": {
|
|
||||||
"name": "my-new-cron-object"
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"cronSpec": "* * * * */5",
|
|
||||||
"image": "my-awesome-cron-image"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
{
|
|
||||||
"apiVersion": "apiextensions.k8s.io/v1beta1",
|
|
||||||
"kind": "CustomResourceDefinition",
|
|
||||||
"metadata": {
|
|
||||||
"name": "crontabs.stable.example.com"
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"group": "stable.example.com",
|
|
||||||
"version": "v1",
|
|
||||||
"scope": "Namespaced",
|
|
||||||
"names": {
|
|
||||||
"plural": "crontabs",
|
|
||||||
"singular": "crontab",
|
|
||||||
"kind": "CronTab",
|
|
||||||
"shortNames": [
|
|
||||||
"ct"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
{
|
|
||||||
"kind": "DaemonSet",
|
|
||||||
"metadata": {
|
|
||||||
"labels": {
|
|
||||||
"k8s-app": "fluentd-logging"
|
|
||||||
},
|
|
||||||
"name": "fluentd-elasticsearch",
|
|
||||||
"namespace": "kube-system"
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"selector": {
|
|
||||||
"matchLabels": {
|
|
||||||
"name": "fluentd-elasticsearch"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"metadata": {
|
|
||||||
"labels": {
|
|
||||||
"name": "fluentd-elasticsearch"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"containers": [
|
|
||||||
{
|
|
||||||
"image": "gcr.io/google-containers/fluentd-elasticsearch:1.20",
|
|
||||||
"name": "fluentd-elasticsearch",
|
|
||||||
"resources": {
|
|
||||||
"limits": {
|
|
||||||
"memory": "200Mi"
|
|
||||||
},
|
|
||||||
"requests": {
|
|
||||||
"cpu": "100m",
|
|
||||||
"memory": "200Mi"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"volumeMounts": [
|
|
||||||
{
|
|
||||||
"mountPath": "/var/log",
|
|
||||||
"name": "varlog"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"mountPath": "/var/lib/docker/containers",
|
|
||||||
"name": "varlibdockercontainers",
|
|
||||||
"readOnly": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"terminationGracePeriodSeconds": 30,
|
|
||||||
"volumes": [
|
|
||||||
{
|
|
||||||
"hostPath": {
|
|
||||||
"path": "/var/log"
|
|
||||||
},
|
|
||||||
"name": "varlog"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"hostPath": {
|
|
||||||
"path": "/var/lib/docker/containers"
|
|
||||||
},
|
|
||||||
"name": "varlibdockercontainers"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{ config, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
require = [./modules.nix ./deployment.nix];
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
{
|
|
||||||
"metadata": {
|
|
||||||
"name": "nginx-deployment",
|
|
||||||
"labels": {
|
|
||||||
"app": "nginx"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"selector": {
|
|
||||||
"matchLabels": {
|
|
||||||
"app": "nginx"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"template": {
|
|
||||||
"metadata": {
|
|
||||||
"labels": {
|
|
||||||
"app": "nginx"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"containers": {
|
|
||||||
"nginx": {
|
|
||||||
"name": "nginx",
|
|
||||||
"image": "nginx:1.7.9",
|
|
||||||
"ports": {
|
|
||||||
"80": {}
|
|
||||||
},
|
|
||||||
"resources": {
|
|
||||||
"requests": {
|
|
||||||
"cpu": "100m"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
{lib, k8s, ...}:
|
|
||||||
|
|
||||||
with lib;
|
|
||||||
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
kubernetes.resources = {
|
|
||||||
deployments.deployment = mkMerge [
|
|
||||||
(k8s.loadJSON ./deployment.json)
|
|
||||||
{
|
|
||||||
metadata.name = "abcd";
|
|
||||||
nix.dependencies = ["configMaps/configmap"];
|
|
||||||
}
|
|
||||||
];
|
|
||||||
configMaps.configmap = k8s.loadJSON ./configMap.json;
|
|
||||||
namespaces.namespace = k8s.loadJSON ./namespace.json;
|
|
||||||
daemonSets.daemonset = k8s.loadJSON ./daemonset.json;
|
|
||||||
services.service = k8s.loadJSON ./service.json;
|
|
||||||
customResourceDefinitions.cron = k8s.loadJSON ./crd.json;
|
|
||||||
};
|
|
||||||
|
|
||||||
kubernetes.customResources.cron.my-awesome-cron-object = k8s.loadJSON ./cr.json;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
110
test/modules.nix
110
test/modules.nix
|
|
@ -1,110 +0,0 @@
|
||||||
{lib, k8s, config, ...}:
|
|
||||||
|
|
||||||
with k8s;
|
|
||||||
with lib;
|
|
||||||
|
|
||||||
{
|
|
||||||
config = {
|
|
||||||
kubernetes.moduleDefinitions.nginx.module = {name, config, ...}: {
|
|
||||||
options = {
|
|
||||||
port = mkOption {
|
|
||||||
description = "Port for nginx to listen on";
|
|
||||||
type = types.int;
|
|
||||||
default = 80;
|
|
||||||
};
|
|
||||||
|
|
||||||
password = mkSecretOption {
|
|
||||||
description = "Nginx simple auth credentials";
|
|
||||||
default = null;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
kubernetes.resources.deployments.nginx = mkMerge [
|
|
||||||
(loadJSON ./deployment.json)
|
|
||||||
{
|
|
||||||
metadata.name = "${name}-nginx";
|
|
||||||
|
|
||||||
spec.template.spec.containers.nginx.ports."80" = {
|
|
||||||
containerPort = config.port;
|
|
||||||
};
|
|
||||||
|
|
||||||
spec.template.spec.containers.nginx.env.name =
|
|
||||||
mkIf (config.password != null) (secretToEnv config.password);
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
kubernetes.resources.configMaps.nginx = mkMerge [
|
|
||||||
(loadJSON ./configMap.json)
|
|
||||||
{
|
|
||||||
metadata.name = mkForce "${name}-nginx";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
kubernetes.modules.app-v1 = {
|
|
||||||
module = "nginx";
|
|
||||||
configuration.password.name = "test2";
|
|
||||||
configuration.password.key = "password";
|
|
||||||
|
|
||||||
configuration.kubernetes.resources.customResourceDefinitions.secret-claims = {
|
|
||||||
kind = "CustomResourceDefinition";
|
|
||||||
apiVersion = "apiextensions.k8s.io/v1beta1";
|
|
||||||
metadata.name = "secretclaims.vaultproject.io";
|
|
||||||
spec = {
|
|
||||||
group = "vaultproject.io";
|
|
||||||
version = "v1";
|
|
||||||
scope = "Namespaced";
|
|
||||||
names = {
|
|
||||||
plural = "secretclaims";
|
|
||||||
kind = "SecretClaim";
|
|
||||||
shortNames = ["scl"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
configuration.kubernetes.customResources.secret-claims.claim = {
|
|
||||||
metadata.name = "test";
|
|
||||||
};
|
|
||||||
|
|
||||||
};
|
|
||||||
kubernetes.modules.app-v2 = {
|
|
||||||
module = "nginx";
|
|
||||||
configuration.port = 8080;
|
|
||||||
|
|
||||||
configuration.kubernetes.modules.subsubmodule = {
|
|
||||||
module = "nginx";
|
|
||||||
configuration.kubernetes.resources.customResourceDefinitions.secret-claims = {
|
|
||||||
kind = "CustomResourceDefinition";
|
|
||||||
apiVersion = "apiextensions.k8s.io/v1beta1";
|
|
||||||
metadata.name = "secretclaims.vaultproject.io";
|
|
||||||
spec = {
|
|
||||||
group = "vaultproject.io";
|
|
||||||
version = "v1";
|
|
||||||
scope = "Namespaced";
|
|
||||||
names = {
|
|
||||||
plural = "secretclaims";
|
|
||||||
kind = "SecretClaim";
|
|
||||||
shortNames = ["scl"];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
configuration.kubernetes.customResources.secret-claims.claim = {
|
|
||||||
metadata.name = "test";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
kubernetes.resources.services.nginx = loadJSON ./service.json;
|
|
||||||
|
|
||||||
kubernetes.defaultModuleConfiguration.all = [{
|
|
||||||
config.kubernetes.defaults.deployments.spec.replicas = mkDefault 3;
|
|
||||||
}];
|
|
||||||
|
|
||||||
kubernetes.defaultModuleConfiguration.nginx = {config, name, ...}: {
|
|
||||||
kubernetes.defaults.deployments.spec.replicas = 4;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"kind": "Namespace",
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"metadata": {
|
|
||||||
"name": "openshift-origin",
|
|
||||||
"labels": {
|
|
||||||
"name": "openshift-origin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
{
|
|
||||||
"kind": "Service",
|
|
||||||
"apiVersion": "v1",
|
|
||||||
"metadata": {
|
|
||||||
"name": "nginx"
|
|
||||||
},
|
|
||||||
"spec": {
|
|
||||||
"selector": {
|
|
||||||
"app": "nginx"
|
|
||||||
},
|
|
||||||
"ports": [
|
|
||||||
{
|
|
||||||
"name": "http",
|
|
||||||
"protocol": "TCP",
|
|
||||||
"port": 80,
|
|
||||||
"targetPort": 80
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "https",
|
|
||||||
"protocol": "TCP",
|
|
||||||
"port": 443,
|
|
||||||
"targetPort": 443
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
56
tests/default.nix
Normal file
56
tests/default.nix
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
{ pkgs ? import <nixpkgs> {}
|
||||||
|
, lib ? pkgs.lib
|
||||||
|
, kubenix ? import ../. { inherit pkgs lib; }
|
||||||
|
, k8sVersion ? "1.13"
|
||||||
|
, nixosPath ? toString <nixpkgs/nixos>
|
||||||
|
|
||||||
|
# whether any testing error should throw an error
|
||||||
|
, throwError ? true
|
||||||
|
, e2e ? true }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
images = pkgs.callPackage ./images.nix {};
|
||||||
|
|
||||||
|
test = (kubenix.evalModules {
|
||||||
|
modules = [
|
||||||
|
kubenix.modules.testing
|
||||||
|
|
||||||
|
{
|
||||||
|
testing.name = "k8s-${k8sVersion}";
|
||||||
|
testing.throwError = throwError;
|
||||||
|
testing.e2e = e2e;
|
||||||
|
testing.tests = [
|
||||||
|
./k8s/simple.nix
|
||||||
|
./k8s/deployment.nix
|
||||||
|
./k8s/crd.nix
|
||||||
|
./k8s/1.13/crd.nix
|
||||||
|
./k8s/defaults.nix
|
||||||
|
./k8s/order.nix
|
||||||
|
./k8s/submodule.nix
|
||||||
|
./k8s/imports.nix
|
||||||
|
./legacy/k8s.nix
|
||||||
|
./legacy/crd.nix
|
||||||
|
./legacy/modules.nix
|
||||||
|
./helm/simple.nix
|
||||||
|
./istio/bookinfo.nix
|
||||||
|
./submodules/simple.nix
|
||||||
|
./submodules/defaults.nix
|
||||||
|
./submodules/versioning.nix
|
||||||
|
./submodules/exports.nix
|
||||||
|
./submodules/passthru.nix
|
||||||
|
];
|
||||||
|
testing.args = {
|
||||||
|
inherit images k8sVersion;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
args = {
|
||||||
|
inherit pkgs;
|
||||||
|
};
|
||||||
|
specialArgs = {
|
||||||
|
inherit kubenix nixosPath;
|
||||||
|
};
|
||||||
|
}).config;
|
||||||
|
in pkgs.recurseIntoAttrs test.testing
|
||||||
98
tests/helm/simple.nix
Normal file
98
tests/helm/simple.nix
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
{ config, lib, pkgs, kubenix, helm, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
with kubenix.lib;
|
||||||
|
with pkgs.dockerTools;
|
||||||
|
|
||||||
|
let
|
||||||
|
corev1 = config.kubernetes.api.resources.core.v1;
|
||||||
|
appsv1beta2 = config.kubernetes.api.resources.apps.v1beta2;
|
||||||
|
|
||||||
|
postgresql = pullImage {
|
||||||
|
imageName = "docker.io/bitnami/postgresql";
|
||||||
|
imageDigest = "sha256:16485a9b19696958ab7259e0d2c3efa0ef7f300b6fd1beb13a6643e120970a05";
|
||||||
|
sha256 = "0hqdkpk7s3wcy5qsy6dzgzqc0rbpavpghly350p97j1janxbyhc7";
|
||||||
|
finalImageTag = "10.7.0";
|
||||||
|
};
|
||||||
|
|
||||||
|
postgresqlExporter = pullImage {
|
||||||
|
imageName = "docker.io/wrouesnel/postgres_exporter";
|
||||||
|
imageDigest = "sha256:dd8051322ceb8995d3d7f116041a2116815e01e88232a90f635ebde8dcc4d3f4";
|
||||||
|
sha256 = "09mva5jx1g4v47s4lr1pkpfzzmxc7z9dnajfizffm3rxwl0qzjji";
|
||||||
|
finalImageTag = "v0.4.7";
|
||||||
|
};
|
||||||
|
|
||||||
|
minideb = pullImage {
|
||||||
|
imageName = "docker.io/bitnami/minideb";
|
||||||
|
imageDigest = "sha256:363011b4ad5308e7f2aee505b80730cbaadf9d41ff87879403f567dd98cfb5cf";
|
||||||
|
sha256 = "1vfyfdhmgidi7hc8kjflpq91vkzdqi9sj78g51ci8nyarclr808q";
|
||||||
|
finalImageTag = "latest";
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
imports = [ kubenix.modules.test kubenix.modules.helm kubenix.modules.k8s ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "helm-simple";
|
||||||
|
description = "Simple k8s testing wheter name, apiVersion and kind are preset";
|
||||||
|
enable = builtins.compareVersions config.kubernetes.version "1.8" >= 0;
|
||||||
|
assertions = [{
|
||||||
|
message = "should have generated resources";
|
||||||
|
assertion =
|
||||||
|
appsv1beta2.StatefulSet ? "app-psql-postgresql-master" &&
|
||||||
|
appsv1beta2.StatefulSet ? "app-psql-postgresql-slave" &&
|
||||||
|
corev1.ConfigMap ? "app-psql-postgresql-init-scripts" &&
|
||||||
|
corev1.Secret ? "app-psql-postgresql" &&
|
||||||
|
corev1.Service ? "app-psql-postgresql-headless" ;
|
||||||
|
} {
|
||||||
|
message = "should have values passed";
|
||||||
|
assertion = appsv1beta2.StatefulSet.app-psql-postgresql-slave.spec.replicas == 2;
|
||||||
|
} {
|
||||||
|
message = "should have namespace defined";
|
||||||
|
assertion =
|
||||||
|
appsv1beta2.StatefulSet.app-psql-postgresql-master.metadata.namespace == "test";
|
||||||
|
}];
|
||||||
|
testScript = ''
|
||||||
|
$kube->waitUntilSucceeds("docker load < ${postgresql}");
|
||||||
|
$kube->waitUntilSucceeds("docker load < ${postgresqlExporter}");
|
||||||
|
$kube->waitUntilSucceeds("docker load < ${minideb}");
|
||||||
|
$kube->waitUntilSucceeds("kubectl apply -f ${config.kubernetes.result}");
|
||||||
|
$kube->waitUntilSucceeds("PGPASSWORD=postgres ${pkgs.postgresql}/bin/psql -h app-psql-postgresql.test.svc.cluster.local -U postgres -l");
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.resources.namespaces.test = {};
|
||||||
|
|
||||||
|
kubernetes.helm.instances.app-psql = {
|
||||||
|
namespace = "test";
|
||||||
|
chart = helm.fetch {
|
||||||
|
repo = "https://kubernetes-charts.storage.googleapis.com/";
|
||||||
|
chart = "postgresql";
|
||||||
|
version = "3.0.0";
|
||||||
|
sha256 = "06dkn4fgvgqr27hcnbbax1ylvr4sld3rcmy1w5kanljsajbph57m";
|
||||||
|
};
|
||||||
|
|
||||||
|
values = {
|
||||||
|
image = {
|
||||||
|
repository = "bitnami/postgresql";
|
||||||
|
tag = "10.7.0";
|
||||||
|
pullPolicy = "IfNotPresent";
|
||||||
|
};
|
||||||
|
volumePermissions.image = {
|
||||||
|
repository = "bitnami/minideb";
|
||||||
|
tag = "latest";
|
||||||
|
pullPolicy = "IfNotPresent";
|
||||||
|
};
|
||||||
|
metrics.image = {
|
||||||
|
repository = "wrouesnel/postgres_exporter";
|
||||||
|
tag = "v0.4.7";
|
||||||
|
pullPolicy = "IfNotPresent";
|
||||||
|
};
|
||||||
|
replication.enabled = true;
|
||||||
|
replication.slaveReplicas = 2;
|
||||||
|
postgresqlPassword = "postgres";
|
||||||
|
persistence.enabled = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
45
tests/images.nix
Normal file
45
tests/images.nix
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
{ pkgs, dockerTools, lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
{
|
||||||
|
nginx = let
|
||||||
|
nginxPort = "80";
|
||||||
|
nginxConf = pkgs.writeText "nginx.conf" ''
|
||||||
|
user nginx nginx;
|
||||||
|
daemon off;
|
||||||
|
error_log /dev/stdout info;
|
||||||
|
pid /dev/null;
|
||||||
|
events {}
|
||||||
|
http {
|
||||||
|
access_log /dev/stdout;
|
||||||
|
server {
|
||||||
|
listen ${nginxPort};
|
||||||
|
index index.html;
|
||||||
|
location / {
|
||||||
|
root ${nginxWebRoot};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
nginxWebRoot = pkgs.writeTextDir "index.html" ''
|
||||||
|
<html><body><h1>Hello from NGINX</h1></body></html>
|
||||||
|
'';
|
||||||
|
in dockerTools.buildLayeredImage {
|
||||||
|
name = "xtruder/nginx";
|
||||||
|
tag = "latest";
|
||||||
|
contents = [pkgs.nginx];
|
||||||
|
extraCommands = ''
|
||||||
|
mkdir -p etc
|
||||||
|
chmod u+w etc
|
||||||
|
echo "nginx:x:1000:1000::/:" > etc/passwd
|
||||||
|
echo "nginx:x:1000:nginx" > etc/group
|
||||||
|
'';
|
||||||
|
config = {
|
||||||
|
Cmd = ["nginx" "-c" nginxConf];
|
||||||
|
ExposedPorts = {
|
||||||
|
"${nginxPort}/tcp" = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
110
tests/istio/bookinfo.nix
Normal file
110
tests/istio/bookinfo.nix
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
{ config, kubenix, k8sVersion, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = with kubenix.modules; [ test k8s istio ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "istio-bookinfo";
|
||||||
|
description = "Simple istio bookinfo application (WIP)";
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.api."networking.istio.io"."v1alpha3" = {
|
||||||
|
Gateway."bookinfo-gateway" = {
|
||||||
|
spec = {
|
||||||
|
selector.istio = "ingressgateway";
|
||||||
|
servers = [{
|
||||||
|
port = {
|
||||||
|
number = 80;
|
||||||
|
name = "http";
|
||||||
|
protocol = "HTTP";
|
||||||
|
};
|
||||||
|
hosts = ["*"];
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
VirtualService.bookinfo = {
|
||||||
|
spec = {
|
||||||
|
hosts = ["*"];
|
||||||
|
gateways = ["bookinfo-gateway"];
|
||||||
|
http = [{
|
||||||
|
match = [{
|
||||||
|
uri.exact = "/productpage";
|
||||||
|
} {
|
||||||
|
uri.exact = "/login";
|
||||||
|
} {
|
||||||
|
uri.exact = "/logout";
|
||||||
|
} {
|
||||||
|
uri.prefix = "/api/v1/products";
|
||||||
|
}];
|
||||||
|
route = [{
|
||||||
|
destination = {
|
||||||
|
host = "productpage";
|
||||||
|
port.number = 9080;
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
DestinationRule.productpage = {
|
||||||
|
spec = {
|
||||||
|
host = "productpage";
|
||||||
|
subsets = [{
|
||||||
|
name = "v1";
|
||||||
|
labels.version = "v1";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
DestinationRule.reviews = {
|
||||||
|
spec = {
|
||||||
|
host = "reviews";
|
||||||
|
subsets = [{
|
||||||
|
name = "v1";
|
||||||
|
labels.version = "v1";
|
||||||
|
} {
|
||||||
|
name = "v2";
|
||||||
|
labels.version = "v2";
|
||||||
|
} {
|
||||||
|
name = "v3";
|
||||||
|
labels.version = "v3";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
DestinationRule.ratings = {
|
||||||
|
spec = {
|
||||||
|
host = "ratings";
|
||||||
|
subsets = [{
|
||||||
|
name = "v1";
|
||||||
|
labels.version = "v1";
|
||||||
|
} {
|
||||||
|
name = "v2";
|
||||||
|
labels.version = "v2";
|
||||||
|
} {
|
||||||
|
name = "v2-mysql";
|
||||||
|
labels.version = "v2-mysql";
|
||||||
|
} {
|
||||||
|
name = "v2-mysql-vm";
|
||||||
|
labels.version = "v2-mysql-vm";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
DestinationRule.details = {
|
||||||
|
spec = {
|
||||||
|
host = "details";
|
||||||
|
subsets = [{
|
||||||
|
name = "v1";
|
||||||
|
labels.version = "v1";
|
||||||
|
} {
|
||||||
|
name = "v2";
|
||||||
|
labels.version = "v2";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
40
tests/k8s/1.13/crd.nix
Normal file
40
tests/k8s/1.13/crd.nix
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
{ config, lib, kubenix, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.kubernetes.resources.customResourceDefinitions.crontabs;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test k8s ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "k8s-1-13-crd";
|
||||||
|
description = "Simple test testing CRD for k8s 1.13";
|
||||||
|
enable = builtins.compareVersions config.kubernetes.version "1.13" >= 0;
|
||||||
|
assertions = [{
|
||||||
|
message = "should have versions set";
|
||||||
|
assertion = (head cfg.spec.versions).name == "v1";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.resources.customResourceDefinitions.crontabs = {
|
||||||
|
metadata.name = "crontabs.stable.example.com";
|
||||||
|
spec = {
|
||||||
|
group = "stable.example.com";
|
||||||
|
versions = [{
|
||||||
|
name = "v1";
|
||||||
|
served = true;
|
||||||
|
storage = true;
|
||||||
|
}];
|
||||||
|
scope = "Namespaced";
|
||||||
|
names = {
|
||||||
|
plural = "crontabs";
|
||||||
|
singular = "crontab";
|
||||||
|
kind = "CronTab";
|
||||||
|
shortNames = ["ct"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
124
tests/k8s/crd.nix
Normal file
124
tests/k8s/crd.nix
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
{ config, lib, kubenix, pkgs, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
crd = config.kubernetes.api.resources.customResourceDefinitions.crontabs;
|
||||||
|
latestCrontab = config.kubernetes.api.resources.cronTabs.latest;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test k8s ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "k8s-crd";
|
||||||
|
description = "Simple test tesing CRD";
|
||||||
|
enable = builtins.compareVersions config.kubernetes.version "1.8" >= 0;
|
||||||
|
assertions = [{
|
||||||
|
message = "CRD should have group and version set";
|
||||||
|
assertion =
|
||||||
|
crd.spec.group == "stable.example.com" &&
|
||||||
|
crd.spec.version == "v1";
|
||||||
|
} {
|
||||||
|
message = "Custom resource should have correct version set";
|
||||||
|
assertion = latestCrontab.apiVersion == "stable.example.com/v2";
|
||||||
|
}];
|
||||||
|
testScript = ''
|
||||||
|
$kube->waitUntilSucceeds("kubectl apply -f ${config.kubernetes.result}");
|
||||||
|
$kube->succeed("kubectl get crds | grep -i crontabs");
|
||||||
|
$kube->succeed("kubectl get crontabs | grep -i versioned");
|
||||||
|
$kube->succeed("kubectl get crontabs | grep -i latest");
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.resources.customResourceDefinitions.crontabs = {
|
||||||
|
metadata.name = "crontabs.stable.example.com";
|
||||||
|
spec = {
|
||||||
|
group = "stable.example.com";
|
||||||
|
version = "v1";
|
||||||
|
scope = "Namespaced";
|
||||||
|
names = {
|
||||||
|
plural = "crontabs";
|
||||||
|
singular = "crontab";
|
||||||
|
kind = "CronTab";
|
||||||
|
shortNames = ["ct"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.resources.customResourceDefinitions.crontabsv2 = {
|
||||||
|
metadata.name = "crontabs.stable.example.com";
|
||||||
|
spec = {
|
||||||
|
group = "stable.example.com";
|
||||||
|
version = "v2";
|
||||||
|
scope = "Namespaced";
|
||||||
|
names = {
|
||||||
|
plural = "crontabs";
|
||||||
|
singular = "crontab";
|
||||||
|
kind = "CronTab";
|
||||||
|
shortNames = ["ct"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.createCustomTypesFromCRDs = true;
|
||||||
|
|
||||||
|
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.cronTabsV3.latest.spec.schedule = "* * * * *";
|
||||||
|
}
|
||||||
50
tests/k8s/defaults.nix
Normal file
50
tests/k8s/defaults.nix
Normal file
|
|
@ -0,0 +1,50 @@
|
||||||
|
{ config, lib, kubenix, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
pod1 = config.kubernetes.api.resources.pods.pod1;
|
||||||
|
pod2 = config.kubernetes.api.resources.pods.pod2;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test k8s ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "k8s-defaults";
|
||||||
|
description = "Simple k8s testing wheter name, apiVersion and kind are preset";
|
||||||
|
assertions = [{
|
||||||
|
message = "Should have label set with resource";
|
||||||
|
assertion = pod1.metadata.labels.resource-label == "value";
|
||||||
|
} {
|
||||||
|
message = "Should have default label set with group, version, kind";
|
||||||
|
assertion = pod1.metadata.labels.gvk-label == "value";
|
||||||
|
} {
|
||||||
|
message = "Should have conditional annotation set";
|
||||||
|
assertion = pod2.metadata.annotations.conditional-annotation == "value";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.resources.pods.pod1 = {};
|
||||||
|
|
||||||
|
kubernetes.resources.pods.pod2 = {
|
||||||
|
metadata.labels.custom-label = "value";
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.api.defaults = [{
|
||||||
|
resource = "pods";
|
||||||
|
default.metadata.labels.resource-label = "value";
|
||||||
|
} {
|
||||||
|
group = "core";
|
||||||
|
kind = "Pod";
|
||||||
|
version = "v1";
|
||||||
|
default.metadata.labels.gvk-label = "value";
|
||||||
|
} {
|
||||||
|
resource = "pods";
|
||||||
|
default = { config, ... }: {
|
||||||
|
config.metadata.annotations = mkIf (config.metadata.labels ? "custom-label") {
|
||||||
|
conditional-annotation = "value";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
}
|
||||||
69
tests/k8s/deployment.nix
Normal file
69
tests/k8s/deployment.nix
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
{ config, lib, pkgs, kubenix, images, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.kubernetes.api.resources.deployments.nginx;
|
||||||
|
image = images.nginx;
|
||||||
|
in {
|
||||||
|
imports = [ kubenix.modules.test kubenix.modules.k8s kubenix.modules.docker ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "k8s-deployment";
|
||||||
|
description = "Simple k8s testing a simple deployment";
|
||||||
|
assertions = [{
|
||||||
|
message = "should have correct apiVersion and kind set";
|
||||||
|
assertion =
|
||||||
|
if ((builtins.compareVersions config.kubernetes.version "1.7") <= 0)
|
||||||
|
then cfg.apiVersion == "apps/v1beta1"
|
||||||
|
else if ((builtins.compareVersions config.kubernetes.version "1.8") <= 0)
|
||||||
|
then cfg.apiVersion == "apps/v1beta2"
|
||||||
|
else cfg.apiVersion == "apps/v1";
|
||||||
|
} {
|
||||||
|
message = "should have corrent kind set";
|
||||||
|
assertion = cfg.kind == "Deployment";
|
||||||
|
} {
|
||||||
|
message = "should have replicas set";
|
||||||
|
assertion = cfg.spec.replicas == 10;
|
||||||
|
}];
|
||||||
|
extraConfiguration = {
|
||||||
|
environment.systemPackages = [ pkgs.curl ];
|
||||||
|
services.kubernetes.kubelet.seedDockerImages = config.docker.export;
|
||||||
|
};
|
||||||
|
testScript = ''
|
||||||
|
$kube->waitUntilSucceeds("kubectl apply -f ${config.kubernetes.result}");
|
||||||
|
|
||||||
|
$kube->succeed("kubectl get deployment | grep -i nginx");
|
||||||
|
$kube->waitUntilSucceeds("kubectl get deployment -o go-template nginx --template={{.status.readyReplicas}} | grep 10");
|
||||||
|
$kube->waitUntilSucceeds("curl http://nginx.default.svc.cluster.local | grep -i hello");
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
docker.images.nginx.image = image;
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.resources.deployments.nginx = {
|
||||||
|
spec = {
|
||||||
|
replicas = 10;
|
||||||
|
selector.matchLabels.app = "nginx";
|
||||||
|
template.metadata.labels.app = "nginx";
|
||||||
|
template.spec = {
|
||||||
|
containers.nginx = {
|
||||||
|
image = config.docker.images.nginx.path;
|
||||||
|
imagePullPolicy = "Never";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.resources.services.nginx = {
|
||||||
|
spec = {
|
||||||
|
ports = [{
|
||||||
|
name = "http";
|
||||||
|
port = 80;
|
||||||
|
}];
|
||||||
|
selector.app = "nginx";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
21
tests/k8s/deployment.yaml
Normal file
21
tests/k8s/deployment.yaml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: nginx-deployment
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
replicas: 3
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:1.7.9
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
||||||
30
tests/k8s/imports.nix
Normal file
30
tests/k8s/imports.nix
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
{ config, lib, kubenix, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
pod = config.kubernetes.api.resources.core.v1.Pod.test;
|
||||||
|
deployment = config.kubernetes.api.resources.apps.v1.Deployment.nginx-deployment;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test k8s ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "k8s-imports";
|
||||||
|
description = "Simple k8s testing imports";
|
||||||
|
enable = builtins.compareVersions config.kubernetes.version "1.10" >= 0;
|
||||||
|
assertions = [{
|
||||||
|
message = "Pod should have name set";
|
||||||
|
assertion = pod.metadata.name == "test";
|
||||||
|
} {
|
||||||
|
message = "Deployment should have name set";
|
||||||
|
assertion = deployment.metadata.name == "nginx-deployment";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.imports = [
|
||||||
|
./pod.json
|
||||||
|
./deployment.yaml
|
||||||
|
];
|
||||||
|
}
|
||||||
59
tests/k8s/order.nix
Normal file
59
tests/k8s/order.nix
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{ config, lib, kubenix, pkgs, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.kubernetes.api.resources.customResourceDefinitions.crontabs;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test k8s ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "k8s-order";
|
||||||
|
description = "test tesing k8s resource order";
|
||||||
|
enable = builtins.compareVersions config.kubernetes.version "1.8" >= 0;
|
||||||
|
assertions = [{
|
||||||
|
message = "should have correct order of resources";
|
||||||
|
assertion =
|
||||||
|
(elemAt config.kubernetes.objects 0).kind == "CustomResourceDefinition" &&
|
||||||
|
(elemAt config.kubernetes.objects 1).kind == "Namespace" &&
|
||||||
|
(elemAt config.kubernetes.objects 2).kind == "CronTab";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.resources.customResourceDefinitions.crontabs = {
|
||||||
|
metadata.name = "crontabs.stable.example.com";
|
||||||
|
spec = {
|
||||||
|
group = "stable.example.com";
|
||||||
|
version = "v1";
|
||||||
|
scope = "Namespaced";
|
||||||
|
names = {
|
||||||
|
plural = "crontabs";
|
||||||
|
singular = "crontab";
|
||||||
|
kind = "CronTab";
|
||||||
|
shortNames = ["ct"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.customTypes = [{
|
||||||
|
name = "crontabs";
|
||||||
|
description = "CronTabs resources";
|
||||||
|
|
||||||
|
attrName = "cronTabs";
|
||||||
|
group = "stable.example.com";
|
||||||
|
version = "v1";
|
||||||
|
kind = "CronTab";
|
||||||
|
module = {
|
||||||
|
options.schedule = mkOption {
|
||||||
|
description = "Crontab schedule script";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
|
||||||
|
kubernetes.resources.namespaces.test = {};
|
||||||
|
|
||||||
|
kubernetes.resources."stable.example.com"."v1".CronTab.crontab.spec.schedule = "* * * * *";
|
||||||
|
}
|
||||||
13
tests/k8s/pod.json
Normal file
13
tests/k8s/pod.json
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"kind": "Pod",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [{
|
||||||
|
"name": "test",
|
||||||
|
"image": "busybox"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
23
tests/k8s/simple.nix
Normal file
23
tests/k8s/simple.nix
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
{ config, kubenix, k8sVersion, ... }:
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.kubernetes.api.resources.pods.nginx;
|
||||||
|
in {
|
||||||
|
imports = [ kubenix.modules.test kubenix.modules.k8s ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "k8s-simple";
|
||||||
|
description = "Simple k8s testing wheter name, apiVersion and kind are preset";
|
||||||
|
assertions = [{
|
||||||
|
message = "should have apiVersion and kind set";
|
||||||
|
assertion = cfg.apiVersion == "v1" && cfg.kind == "Pod";
|
||||||
|
} {
|
||||||
|
message = "should have name set";
|
||||||
|
assertion = cfg.metadata.name == "nginx";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.resources.pods.nginx = {};
|
||||||
|
}
|
||||||
60
tests/k8s/submodule.nix
Normal file
60
tests/k8s/submodule.nix
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
{ name, config, lib, kubenix, images, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.submodules.instances.passthru;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test submodules k8s docker ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "k8s-submodule";
|
||||||
|
description = "Simple k8s submodule test";
|
||||||
|
assertions = [{
|
||||||
|
message = "Submodule has correct name set";
|
||||||
|
assertion = (head config.kubernetes.objects).metadata.name == "passthru";
|
||||||
|
} {
|
||||||
|
message = "Should expose docker image";
|
||||||
|
assertion = (head config.docker.export).imageName == "xtruder/nginx";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.namespace = "test-namespace";
|
||||||
|
|
||||||
|
submodules.imports = [{
|
||||||
|
module = {name, config, ...}: {
|
||||||
|
imports = with kubenix.modules; [ submodule k8s docker ];
|
||||||
|
|
||||||
|
config = {
|
||||||
|
submodule = {
|
||||||
|
name = "test-submodule";
|
||||||
|
passthru = {
|
||||||
|
kubernetes.objects = config.kubernetes.objects;
|
||||||
|
docker.images = config.docker.images;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.resources.pods.nginx = {
|
||||||
|
metadata.name = name;
|
||||||
|
spec.containers.nginx.image = config.docker.images.nginx.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
docker.images.nginx.image = images.nginx;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
|
||||||
|
kubernetes.api.defaults = [{
|
||||||
|
propagate = true;
|
||||||
|
default.metadata.labels.my-label = "my-value";
|
||||||
|
}];
|
||||||
|
|
||||||
|
submodules.instances.passthru = {
|
||||||
|
submodule = "test-submodule";
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.instances.no-passthru = {
|
||||||
|
submodule = "test-submodule";
|
||||||
|
passthru.enable = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
106
tests/legacy/crd.nix
Normal file
106
tests/legacy/crd.nix
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
{ options, config, lib, kubenix, pkgs, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
findObject = { kind, name }: filter (object:
|
||||||
|
object.kind == kind && object.metadata.name == name
|
||||||
|
) config.kubernetes.objects;
|
||||||
|
|
||||||
|
getObject = filter: head (findObject filter);
|
||||||
|
|
||||||
|
hasObject = { kind, name }: length (findObject { inherit kind name; }) == 1;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test k8s legacy ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "legacy-crd";
|
||||||
|
description = "Simple test tesing kubenix legacy integration with crds crd";
|
||||||
|
enable = builtins.compareVersions config.kubernetes.version "1.13" >= 0;
|
||||||
|
assertions = [{
|
||||||
|
message = "should define crd in module";
|
||||||
|
assertion =
|
||||||
|
hasObject {kind = "SecretClaim"; name = "module-claim";};
|
||||||
|
} {
|
||||||
|
message = "should define crd in root";
|
||||||
|
assertion =
|
||||||
|
hasObject {kind = "SecretClaim"; name = "root-claim";};
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
kubernetes.namespace = "test";
|
||||||
|
|
||||||
|
kubernetes.moduleDefinitions.secret-claim.module = { config, k8s, module, ... }: {
|
||||||
|
options = {
|
||||||
|
name = mkOption {
|
||||||
|
description = "Name of the secret claim";
|
||||||
|
type = types.str;
|
||||||
|
default = module.name;
|
||||||
|
};
|
||||||
|
|
||||||
|
type = mkOption {
|
||||||
|
description = "Type of the secret";
|
||||||
|
type = types.enum ["Opaque" "kubernetes.io/tls"];
|
||||||
|
default = "Opaque";
|
||||||
|
};
|
||||||
|
|
||||||
|
path = mkOption {
|
||||||
|
description = "Secret path";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
renew = mkOption {
|
||||||
|
description = "Renew time in seconds";
|
||||||
|
type = types.nullOr types.int;
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
data = mkOption {
|
||||||
|
type = types.nullOr types.attrs;
|
||||||
|
description = "Data to pass to get secrets";
|
||||||
|
default = null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
kubernetes.resources.customResourceDefinitions.secret-claims = {
|
||||||
|
kind = "CustomResourceDefinition";
|
||||||
|
apiVersion = "apiextensions.k8s.io/v1beta1";
|
||||||
|
metadata.name = "secretclaims.vaultproject.io";
|
||||||
|
spec = {
|
||||||
|
group = "vaultproject.io";
|
||||||
|
version = "v1";
|
||||||
|
scope = "Namespaced";
|
||||||
|
names = {
|
||||||
|
plural = "secretclaims";
|
||||||
|
kind = "SecretClaim";
|
||||||
|
shortNames = ["scl"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.customResources.secret-claims.claim = {
|
||||||
|
metadata.name = config.name;
|
||||||
|
spec = {
|
||||||
|
inherit (config) type path;
|
||||||
|
} // (optionalAttrs (config.renew != null) {
|
||||||
|
inherit (config) renew;
|
||||||
|
}) // (optionalAttrs (config.data != null) {
|
||||||
|
inherit (config) data;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.modules.module-claim = {
|
||||||
|
module = "secret-claim";
|
||||||
|
configuration.path = "tokens/test";
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.customResources.secret-claims.root-claim = {
|
||||||
|
spec = {
|
||||||
|
path = "secrets/test2";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
57
tests/legacy/k8s.nix
Normal file
57
tests/legacy/k8s.nix
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
{ config, lib, kubenix, pkgs, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.kubernetes.api.resources.deployments.app;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test k8s legacy ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "legacy-k8s";
|
||||||
|
description = "Simple test kubenix legacy kubernetes support";
|
||||||
|
assertions = [{
|
||||||
|
message = "should have correct resource options set";
|
||||||
|
assertion =
|
||||||
|
cfg.kind == "Deployment" &&
|
||||||
|
cfg.metadata.name == "app";
|
||||||
|
} {
|
||||||
|
message = "should have correct defaults set";
|
||||||
|
assertion =
|
||||||
|
cfg.metadata.namespace == "test" &&
|
||||||
|
cfg.metadata.labels.label1 == "value1" &&
|
||||||
|
cfg.metadata.labels.label2 == "value2";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.resources.deployments.app = {
|
||||||
|
spec = {
|
||||||
|
replicas = 2;
|
||||||
|
selector = {
|
||||||
|
matchLabels.app = "app";
|
||||||
|
};
|
||||||
|
template.spec = {
|
||||||
|
containers.app = {
|
||||||
|
image = "hello-world";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.resources.configMaps.app = {
|
||||||
|
data."my-conf.json" = builtins.toJSON {};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.defaults = {
|
||||||
|
all = [{
|
||||||
|
metadata.namespace = "test";
|
||||||
|
metadata.labels.label1 = "value1";
|
||||||
|
}];
|
||||||
|
|
||||||
|
deployments = [{
|
||||||
|
metadata.labels.label2 = "value2";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
}
|
||||||
108
tests/legacy/modules.nix
Normal file
108
tests/legacy/modules.nix
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
{ options, config, lib, kubenix, pkgs, k8sVersion, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
findObject = { kind, name }: filter (object:
|
||||||
|
object.kind == kind && object.metadata.name == name
|
||||||
|
) config.kubernetes.objects;
|
||||||
|
|
||||||
|
getObject = filter: head (findObject filter);
|
||||||
|
|
||||||
|
hasObject = { kind, name }: length (findObject { inherit kind name; }) == 1;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test k8s legacy ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "legacy-modules";
|
||||||
|
description = "Simple test tesing kubenix legacy modules";
|
||||||
|
assertions = [{
|
||||||
|
message = "should have all objects";
|
||||||
|
assertion =
|
||||||
|
hasObject {kind = "Deployment"; name = "myapp";} &&
|
||||||
|
hasObject {kind = "Deployment"; name = "myapp2";} &&
|
||||||
|
hasObject {kind = "Deployment"; name = "myapp2-app2";};
|
||||||
|
} {
|
||||||
|
message = "should have default labels set";
|
||||||
|
assertion =
|
||||||
|
(getObject {kind = "Deployment"; name = "myapp2-app2";})
|
||||||
|
.metadata.labels.module-label or false == "value" &&
|
||||||
|
(getObject {kind = "Deployment"; name = "myapp2";})
|
||||||
|
.metadata.labels.module-label or false == "value";
|
||||||
|
} {
|
||||||
|
message = "should passthru resources to root module";
|
||||||
|
assertion =
|
||||||
|
config.kubernetes.resources.deployments.myapp2-app2-app.metadata.labels.module-label or false == "value";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.version = k8sVersion;
|
||||||
|
|
||||||
|
kubernetes.defaults.all.metadata.labels.module-label = "value";
|
||||||
|
|
||||||
|
# propagate default module configuration and defaults
|
||||||
|
kubernetes.defaultModuleConfiguration = {
|
||||||
|
all.kubernetes.defaultModuleConfiguration = mkAliasDefinitions options.kubernetes.defaultModuleConfiguration;
|
||||||
|
all.kubernetes.defaults = mkAliasDefinitions options.kubernetes.defaults;
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.moduleDefinitions.app1.module = { config, k8s, module, ... }: {
|
||||||
|
config.kubernetes.resources.deployments.app = {
|
||||||
|
metadata.name = module.name;
|
||||||
|
spec = {
|
||||||
|
selector = {
|
||||||
|
matchLabels.app = "app";
|
||||||
|
};
|
||||||
|
template.spec = {
|
||||||
|
containers.app = {
|
||||||
|
image = "hello-world";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.moduleDefinitions.app2.module = { name, config, k8s, module, ... }: {
|
||||||
|
options = {
|
||||||
|
replicas = mkOption {
|
||||||
|
description = "Number of replicas to run";
|
||||||
|
type = types.int;
|
||||||
|
default = 2;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
kubernetes.resources.deployments.app = {
|
||||||
|
metadata.name = module.name;
|
||||||
|
spec = {
|
||||||
|
replicas = config.replicas;
|
||||||
|
selector = {
|
||||||
|
matchLabels.app = "app";
|
||||||
|
};
|
||||||
|
template.spec = {
|
||||||
|
containers.app = {
|
||||||
|
image = "hello-world";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.modules.app2 = {
|
||||||
|
name = "${name}-app2";
|
||||||
|
module = "app1";
|
||||||
|
namespace = module.namespace;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.modules.myapp = {
|
||||||
|
module = "app1";
|
||||||
|
namespace = "test";
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.modules.myapp2 = {
|
||||||
|
module = "app2";
|
||||||
|
namespace = "test";
|
||||||
|
configuration.replicas = 3;
|
||||||
|
};
|
||||||
|
}
|
||||||
25
tests/metacontroller/compositecontroller.nix
Normal file
25
tests/metacontroller/compositecontroller.nix
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
{ config, kubenix, ... }:
|
||||||
|
|
||||||
|
{
|
||||||
|
imports = [ kubenix.modules.test kubenix.modules.metacontroller ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "metacontroller-controllers";
|
||||||
|
description = "Testing metacontroller custom resources";
|
||||||
|
};
|
||||||
|
|
||||||
|
kubernetes.api.compositecontrollers.test = {
|
||||||
|
spec = {
|
||||||
|
generateSelector = true;
|
||||||
|
parentResource = {
|
||||||
|
apiVersion = "ctl.enisoc.com/v1";
|
||||||
|
resource = "things";
|
||||||
|
};
|
||||||
|
childResources = [{
|
||||||
|
apiVersion = "v1";
|
||||||
|
resource = "pods";
|
||||||
|
}];
|
||||||
|
hooks.sync.webhook.url = "http://thing-controller.metacontroller/sync";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
64
tests/module.nix
Normal file
64
tests/module.nix
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
{ name, config, lib, kubenix, images, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.submodules.instances.test.config;
|
||||||
|
deployment = cfg.kubernetes.api.deployments.nginx;
|
||||||
|
in {
|
||||||
|
imports = [ kubenix.modules.test kubenix.module ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "module";
|
||||||
|
description = "Test testing kubenix module";
|
||||||
|
assertions = [{
|
||||||
|
message = "Namespace not propagated";
|
||||||
|
assertion = deployment.metadata.namespace == "test";
|
||||||
|
} {
|
||||||
|
message = "Version not propagated";
|
||||||
|
assertion = cfg.kubernetes.version == config.kubernetes.version;
|
||||||
|
} {
|
||||||
|
message = "docker image should be added to exported images";
|
||||||
|
assertion = (head config.docker.export) == images.nginx;
|
||||||
|
}];
|
||||||
|
testScript = ''
|
||||||
|
$kube->waitUntilSucceeds("docker load < ${images.nginx}");
|
||||||
|
$kube->waitUntilSucceeds("kubectl apply -f ${config.kubernetes.result}");
|
||||||
|
|
||||||
|
$kube->succeed("kubectl get deployment -n test | grep -i test-nginx");
|
||||||
|
$kube->waitUntilSucceeds("kubectl get deployment -n test -o go-template test-nginx --template={{.status.readyReplicas}} | grep 1");
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.imports = [{
|
||||||
|
module = {name, config, ...}: {
|
||||||
|
submodule.name = "nginx";
|
||||||
|
kubernetes.api.deployments.nginx = {
|
||||||
|
metadata = {
|
||||||
|
name = "${name}-nginx";
|
||||||
|
labels.app = name;
|
||||||
|
};
|
||||||
|
spec = {
|
||||||
|
replicas = 1;
|
||||||
|
selector.matchLabels.app = "nginx";
|
||||||
|
template.metadata.labels.app = "nginx";
|
||||||
|
template.spec = {
|
||||||
|
containers.nginx = {
|
||||||
|
image = config.docker.images.nginx.path;
|
||||||
|
imagePullPolicy = "Never";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
docker.images.nginx.image = images.nginx;
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
|
||||||
|
kubernetes.api.namespaces.test = {};
|
||||||
|
|
||||||
|
submodules.instances.test = {
|
||||||
|
submodule = "nginx";
|
||||||
|
namespace = "test";
|
||||||
|
};
|
||||||
|
}
|
||||||
139
tests/submodules/defaults.nix
Normal file
139
tests/submodules/defaults.nix
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
{ name, config, lib, kubenix, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
instance1 = config.submodules.instances.instance1;
|
||||||
|
instance2 = config.submodules.instances.instance2;
|
||||||
|
instance3 = config.submodules.instances.instance3;
|
||||||
|
instance4 = config.submodules.instances.instance4;
|
||||||
|
instance5 = config.submodules.instances.instance5;
|
||||||
|
versioned-submodule = config.submodules.instances.versioned-submodule;
|
||||||
|
|
||||||
|
submodule = {name, ...}: {
|
||||||
|
imports = [ kubenix.modules.submodule ];
|
||||||
|
|
||||||
|
options.submodule.args = {
|
||||||
|
value = mkOption {
|
||||||
|
description = "Submodule value";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
|
||||||
|
defaultValue = mkOption {
|
||||||
|
description = "Submodule default value";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test submodules ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "submodules-defaults";
|
||||||
|
description = "Simple submodule test";
|
||||||
|
assertions = [{
|
||||||
|
message = "should apply defaults by tag1";
|
||||||
|
assertion = instance1.config.submodule.args.value == "value1";
|
||||||
|
} {
|
||||||
|
message = "should apply defaults by tag2";
|
||||||
|
assertion = instance2.config.submodule.args.value == "value2";
|
||||||
|
} {
|
||||||
|
message = "should apply defaults by tag2";
|
||||||
|
assertion = instance3.config.submodule.args.value == "value2";
|
||||||
|
} {
|
||||||
|
message = "should apply defaults to all";
|
||||||
|
assertion =
|
||||||
|
instance1.config.submodule.args.defaultValue == "value" &&
|
||||||
|
instance2.config.submodule.args.defaultValue == "value";
|
||||||
|
} {
|
||||||
|
message = "instance1 and instance3 should have value of default-value";
|
||||||
|
assertion = instance3.config.submodule.args.defaultValue == "default-value";
|
||||||
|
} {
|
||||||
|
message = "should apply defaults by submodule name";
|
||||||
|
assertion = instance4.config.submodule.args.value == "value4";
|
||||||
|
} {
|
||||||
|
message = "should apply defaults by custom condition";
|
||||||
|
assertion = instance5.config.submodule.args.defaultValue == "my-custom-value";
|
||||||
|
} {
|
||||||
|
message = "should apply defaults to versioned submodule";
|
||||||
|
assertion = versioned-submodule.config.submodule.args.defaultValue == "versioned-submodule";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.imports = [{
|
||||||
|
modules = [submodule {
|
||||||
|
submodule = {
|
||||||
|
name = "submodule1";
|
||||||
|
tags = ["tag1"];
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
} {
|
||||||
|
modules = [submodule {
|
||||||
|
submodule = {
|
||||||
|
name = "submodule2";
|
||||||
|
tags = ["tag2"];
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
} {
|
||||||
|
modules = [submodule {
|
||||||
|
submodule = {
|
||||||
|
name = "submodule3";
|
||||||
|
tags = ["tag2"];
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
} {
|
||||||
|
modules = [submodule {
|
||||||
|
submodule = {
|
||||||
|
name = "submodule4";
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
} {
|
||||||
|
modules = [submodule {
|
||||||
|
submodule = {
|
||||||
|
name = "submodule5";
|
||||||
|
};
|
||||||
|
submodule.args.value = "custom-value";
|
||||||
|
}];
|
||||||
|
} {
|
||||||
|
modules = [submodule {
|
||||||
|
submodule = {
|
||||||
|
name = "versioned-submodule";
|
||||||
|
version = "2.0.0";
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
}];
|
||||||
|
|
||||||
|
submodules.defaults = [{
|
||||||
|
default.submodule.args.defaultValue = mkDefault "value";
|
||||||
|
} {
|
||||||
|
tags = ["tag1"];
|
||||||
|
default.submodule.args.value = mkDefault "value1";
|
||||||
|
} {
|
||||||
|
tags = ["tag2"];
|
||||||
|
default.submodule.args.value = mkDefault "value2";
|
||||||
|
} {
|
||||||
|
name = "submodule4";
|
||||||
|
default.submodule.args.value = mkDefault "value4";
|
||||||
|
} {
|
||||||
|
default = {config, ...}: {
|
||||||
|
submodule.args.defaultValue = mkIf (config.submodule.args.value == "custom-value") "my-custom-value";
|
||||||
|
};
|
||||||
|
} {
|
||||||
|
name = "versioned-submodule";
|
||||||
|
version = "2.0.0";
|
||||||
|
default.submodule.args.value = mkDefault "versioned";
|
||||||
|
}];
|
||||||
|
|
||||||
|
submodules.instances.instance1.submodule = "submodule1";
|
||||||
|
submodules.instances.instance2.submodule = "submodule2";
|
||||||
|
submodules.instances.instance3 = {
|
||||||
|
submodule = "submodule3";
|
||||||
|
args.defaultValue = "default-value";
|
||||||
|
};
|
||||||
|
submodules.instances.instance4.submodule = "submodule4";
|
||||||
|
submodules.instances.instance5.submodule = "submodule5";
|
||||||
|
submodules.instances.versioned-submodule = {
|
||||||
|
submodule = "versioned-submodule";
|
||||||
|
args.defaultValue = "versioned-submodule";
|
||||||
|
};
|
||||||
|
}
|
||||||
32
tests/submodules/exports.nix
Normal file
32
tests/submodules/exports.nix
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
{ name, config, lib, kubenix, subm-lib, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
submodule = {
|
||||||
|
imports = [ kubenix.modules.submodule ];
|
||||||
|
|
||||||
|
config.submodule = {
|
||||||
|
name = "subm";
|
||||||
|
exports = {
|
||||||
|
inherit id;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test submodules ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "submodules-exports";
|
||||||
|
description = "Submodules exports test";
|
||||||
|
assertions = [{
|
||||||
|
message = "should have library exported";
|
||||||
|
assertion = subm-lib.id 1 == 1;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.imports = [{
|
||||||
|
modules = [submodule];
|
||||||
|
exportAs = "subm-lib";
|
||||||
|
}];
|
||||||
|
}
|
||||||
59
tests/submodules/passthru.nix
Normal file
59
tests/submodules/passthru.nix
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
{ name, config, lib, kubenix, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
submodule = { name, ... }: {
|
||||||
|
imports = [ kubenix.modules.submodule ];
|
||||||
|
|
||||||
|
config.submodule = {
|
||||||
|
name = "subm";
|
||||||
|
passthru.global.${name} = "true";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test submodules ];
|
||||||
|
|
||||||
|
options = {
|
||||||
|
global = mkOption {
|
||||||
|
description = "Global value";
|
||||||
|
type = types.attrs;
|
||||||
|
default = {};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
test = {
|
||||||
|
name = "submodules-passthru";
|
||||||
|
description = "Submodules passthru test";
|
||||||
|
assertions = [{
|
||||||
|
message = "should passthru values if passthru enabled";
|
||||||
|
assertion = hasAttr "inst1" config.global && config.global.inst1 == "true";
|
||||||
|
} {
|
||||||
|
message = "should not passthru values if passthru not enabled";
|
||||||
|
assertion = !(hasAttr "inst2" config.global);
|
||||||
|
} {
|
||||||
|
message = "should passthru by default";
|
||||||
|
assertion = hasAttr "inst3" config.global && config.global.inst3 == "true";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.imports = [{
|
||||||
|
modules = [submodule];
|
||||||
|
}];
|
||||||
|
|
||||||
|
submodules.instances.inst1 = {
|
||||||
|
submodule = "subm";
|
||||||
|
passthru.enable = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.instances.inst2 = {
|
||||||
|
submodule = "subm";
|
||||||
|
passthru.enable = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.instances.inst3 = {
|
||||||
|
submodule = "subm";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
65
tests/submodules/simple.nix
Normal file
65
tests/submodules/simple.nix
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
{ name, config, lib, kubenix, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
cfg = config.submodules.instances.instance;
|
||||||
|
args = cfg.config.submodule.args;
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test submodules ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "submodules-simple";
|
||||||
|
description = "Simple k8s submodule test";
|
||||||
|
assertions = [{
|
||||||
|
message = "Submodule name is set";
|
||||||
|
assertion = cfg.name == "instance";
|
||||||
|
} {
|
||||||
|
message = "Submodule version is set";
|
||||||
|
assertion = cfg.version == null;
|
||||||
|
} {
|
||||||
|
message = "Submodule config has submodule definition";
|
||||||
|
assertion = cfg.config.submodule.name == "submodule";
|
||||||
|
} {
|
||||||
|
message = "Should have argument set";
|
||||||
|
assertion = args.value == "test";
|
||||||
|
} {
|
||||||
|
message = "Should have submodule name set";
|
||||||
|
assertion = args.name == "instance";
|
||||||
|
} {
|
||||||
|
message = "should have tag set";
|
||||||
|
assertion = elem "tag" (cfg.config.submodule.tags);
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.propagate.enable = true;
|
||||||
|
submodules.imports = [{
|
||||||
|
module = { submodule, ... }: {
|
||||||
|
imports = [ kubenix.modules.submodule ];
|
||||||
|
|
||||||
|
options.submodule.args = {
|
||||||
|
name = mkOption {
|
||||||
|
description = "Submodule name";
|
||||||
|
type = types.str;
|
||||||
|
default = submodule.name;
|
||||||
|
};
|
||||||
|
value = mkOption {
|
||||||
|
description = "Submodule argument";
|
||||||
|
type = types.str;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = {
|
||||||
|
submodule.name = "submodule";
|
||||||
|
submodule.tags = ["tag"];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}];
|
||||||
|
|
||||||
|
submodules.instances.instance = {
|
||||||
|
submodule = "submodule";
|
||||||
|
args = {
|
||||||
|
value = "test";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
71
tests/submodules/versioning.nix
Normal file
71
tests/submodules/versioning.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
{ name, config, lib, kubenix, ... }:
|
||||||
|
|
||||||
|
with lib;
|
||||||
|
|
||||||
|
let
|
||||||
|
inst-exact = config.submodules.instances.inst-exact.config;
|
||||||
|
inst-regex = config.submodules.instances.inst-regex.config;
|
||||||
|
inst-latest = config.submodules.instances.inst-latest.config;
|
||||||
|
|
||||||
|
submodule = {
|
||||||
|
imports = [ kubenix.modules.submodule ];
|
||||||
|
|
||||||
|
options.version = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "undefined";
|
||||||
|
};
|
||||||
|
|
||||||
|
config.submodule.name = "subm";
|
||||||
|
};
|
||||||
|
in {
|
||||||
|
imports = with kubenix.modules; [ test submodules ];
|
||||||
|
|
||||||
|
test = {
|
||||||
|
name = "submodules-versioning";
|
||||||
|
description = "Submodules versioning test";
|
||||||
|
assertions = [{
|
||||||
|
message = "should select exact version";
|
||||||
|
assertion = inst-exact.version == "1.1.0";
|
||||||
|
} {
|
||||||
|
message = "should select regex version";
|
||||||
|
assertion = inst-regex.version == "1.2.1";
|
||||||
|
} {
|
||||||
|
message = "should select latest version";
|
||||||
|
assertion = inst-latest.version == "1.2.1";
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.imports = [{
|
||||||
|
modules = [{
|
||||||
|
config.submodule.version = "1.0.0";
|
||||||
|
config.version = "1.0.0";
|
||||||
|
} submodule];
|
||||||
|
} {
|
||||||
|
modules = [{
|
||||||
|
config.submodule.version = "1.1.0";
|
||||||
|
config.version = "1.1.0";
|
||||||
|
} submodule];
|
||||||
|
} {
|
||||||
|
modules = [{
|
||||||
|
config.submodule.version = "1.2.0";
|
||||||
|
config.version = "1.2.0";
|
||||||
|
} submodule];
|
||||||
|
} {
|
||||||
|
modules = [{
|
||||||
|
config.submodule.version = "1.2.1";
|
||||||
|
config.version = "1.2.1";
|
||||||
|
} submodule];
|
||||||
|
}];
|
||||||
|
|
||||||
|
submodules.instances.inst-exact = {
|
||||||
|
submodule = "subm";
|
||||||
|
version = "1.1.0";
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.instances.inst-regex = {
|
||||||
|
submodule = "subm";
|
||||||
|
version = "~1.2.*";
|
||||||
|
};
|
||||||
|
|
||||||
|
submodules.instances.inst-latest.submodule = "subm";
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue