···
chartDir = "/var/lib/rancher/k3s/server/static/charts";
imageDir = "/var/lib/rancher/k3s/agent/images";
containerdConfigTemplateFile = "/var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl";
23
+
yamlFormat = pkgs.formats.yaml { };
24
+
yamlDocSeparator = builtins.toFile "yaml-doc-separator" "\n---\n";
25
+
# Manifests need a valid YAML suffix to be respected by k3s
27
+
name: if (lib.hasSuffix ".yaml" name || lib.hasSuffix ".yml" name) then name else name + ".yaml";
28
+
# Produces a list containing all duplicate manifest names
29
+
duplicateManifests =
31
+
lib.intersectLists (attrNames cfg.autoDeployCharts) (attrNames cfg.manifests);
32
+
# Produces a list containing all duplicate chart names
35
+
lib.intersectLists (attrNames cfg.autoDeployCharts) (attrNames cfg.charts);
37
+
# Converts YAML -> JSON -> Nix
43
+
pkgs.runCommand "${path}-converted.json" { nativeBuildInputs = [ yq-go ]; } ''
44
+
yq --no-colors --output-format json ${path} > $out
49
+
# Replace characters that are problematic in file names
50
+
cleanHelmChartName =
61
+
# Fetch a Helm chart from a public registry. This only supports a basic Helm pull.
67
+
hash ? lib.fakeHash,
69
+
pkgs.runCommand (cleanHelmChartName "${lib.removePrefix "https://" repo}-${name}-${version}.tgz")
71
+
inherit (lib.fetchers.normalizeHash { } { inherit hash; }) outputHash outputHashAlgo;
72
+
impureEnvVars = lib.fetchers.proxyImpureEnvVars;
73
+
nativeBuildInputs = with pkgs; [
80
+
helm repo add repository ${repo}
81
+
helm pull repository/${name} --version ${version}
85
+
# Returns the path to a YAML manifest file
86
+
mkExtraDeployManifest =
88
+
# x is a derivation that provides a YAML file
89
+
if lib.isDerivation x then
91
+
# x is an attribute set that needs to be converted to a YAML file
92
+
else if builtins.isAttrs x then
93
+
(yamlFormat.generate "extra-deploy-chart-manifest" x)
94
+
# assume x is a path to a YAML file
98
+
# Generate a HelmChart custom resource.
27
-
name: if (lib.hasSuffix ".yaml" name || lib.hasSuffix ".yml" name) then name else name + ".yaml";
102
+
chartValues = if (lib.isPath value.values) then fromYaml value.values else value.values;
103
+
# use JSON for values as it's a subset of YAML and understood by the k3s Helm controller
104
+
valuesContent = builtins.toJSON chartValues;
29
-
lib.types.submodule (
38
-
enable = lib.mkOption {
39
-
type = lib.types.bool;
41
-
description = "Whether this manifest file should be generated.";
106
+
# merge with extraFieldDefinitions to allow setting advanced values and overwrite generated
108
+
lib.recursiveUpdate {
109
+
apiVersion = "helm.cattle.io/v1";
110
+
kind = "HelmChart";
113
+
namespace = "kube-system";
116
+
inherit valuesContent;
117
+
inherit (value) targetNamespace createNamespace;
118
+
chart = "https://%{KUBERNETES_API}%/static/charts/${name}.tgz";
120
+
} value.extraFieldDefinitions;
122
+
# Generate a HelmChart custom resource together with extraDeploy manifests. This
123
+
# generates possibly a multi document YAML file that the auto deploy mechanism of k3s
125
+
mkAutoDeployChartManifest = name: value: {
126
+
# target is the final name of the link created for the manifest file
127
+
target = mkManifestTarget name;
128
+
inherit (value) enable package;
129
+
# source is a store path containing the complete manifest file
130
+
source = pkgs.concatText "auto-deploy-chart-${name}.yaml" (
132
+
(yamlFormat.generate "helm-chart-manifest-${name}.yaml" (mkHelmChartCR name value))
134
+
# alternate the YAML doc seperator (---) and extraDeploy manifests to create
135
+
# multi document YAMLs
136
+
++ (lib.concatMap (x: [
138
+
(mkExtraDeployManifest x)
139
+
]) value.extraDeploy)
44
-
target = lib.mkOption {
45
-
type = lib.types.nonEmptyStr;
46
-
example = lib.literalExpression "manifest.yaml";
48
-
Name of the symlink (relative to {file}`${manifestDir}`).
49
-
Defaults to the attribute name.
143
+
autoDeployChartsModule = lib.types.submodule (
147
+
enable = lib.mkOption {
148
+
type = lib.types.bool;
152
+
Whether to enable the installation of this Helm chart. Note that setting
153
+
this option to `false` will not uninstall the chart from the cluster, if
154
+
it was previously installed. Please use the the `--disable` flag or `.skip`
155
+
files to delete/disable Helm charts, as mentioned in the
156
+
[docs](https://docs.k3s.io/installation/packaged-components#disabling-manifests).
53
-
content = lib.mkOption {
54
-
type = with lib.types; nullOr (either attrs (listOf attrs));
57
-
Content of the manifest file. A single attribute set will
58
-
generate a single document YAML file. A list of attribute sets
59
-
will generate multiple documents separated by `---` in a single
160
+
repo = lib.mkOption {
161
+
type = lib.types.nonEmptyStr;
162
+
example = "https://kubernetes.github.io/ingress-nginx";
164
+
The repo of the Helm chart. Only has an effect if `package` is not set.
165
+
The Helm chart is fetched during build time and placed as a `.tgz` archive on the
170
+
name = lib.mkOption {
171
+
type = lib.types.nonEmptyStr;
172
+
example = "ingress-nginx";
174
+
The name of the Helm chart. Only has an effect if `package` is not set.
175
+
The Helm chart is fetched during build time and placed as a `.tgz` archive on the
180
+
version = lib.mkOption {
181
+
type = lib.types.nonEmptyStr;
184
+
The version of the Helm chart. Only has an effect if `package` is not set.
185
+
The Helm chart is fetched during build time and placed as a `.tgz` archive on the
190
+
hash = lib.mkOption {
191
+
type = lib.types.str;
192
+
example = "sha256-ej+vpPNdiOoXsaj1jyRpWLisJgWo8EqX+Z5VbpSjsPA=";
194
+
The hash of the packaged Helm chart. Only has an effect if `package` is not set.
195
+
The Helm chart is fetched during build time and placed as a `.tgz` archive on the
200
+
package = lib.mkOption {
201
+
type = with lib.types; either path package;
202
+
example = lib.literalExpression "../my-helm-chart.tgz";
204
+
The packaged Helm chart. Overwrites the options `repo`, `name`, `version`
205
+
and `hash` in case of conflicts.
209
+
targetNamespace = lib.mkOption {
210
+
type = lib.types.nonEmptyStr;
211
+
default = "default";
212
+
example = "kube-system";
213
+
description = "The namespace in which the Helm chart gets installed.";
216
+
createNamespace = lib.mkOption {
217
+
type = lib.types.bool;
220
+
description = "Whether to create the target namespace if not present.";
223
+
values = lib.mkOption {
224
+
type = with lib.types; either path attrs;
228
+
hostName = "my-host";
235
+
Override default chart values via Nix expressions. This is equivalent to setting
236
+
values in a `values.yaml` file.
64
-
source = lib.mkOption {
65
-
type = lib.types.path;
66
-
example = lib.literalExpression "./manifests/app.yaml";
68
-
Path of the source `.yaml` file.
238
+
WARNING: The values (including secrets!) specified here are exposed unencrypted
239
+
in the world-readable nix store.
243
+
extraDeploy = lib.mkOption {
244
+
type = with lib.types; listOf (either path attrs);
246
+
example = lib.literalExpression ''
248
+
../manifests/my-extra-deployment.yaml
253
+
name = "app-service";
257
+
"app.kubernetes.io/name" = "MyApp";
261
+
name = "name-of-service-port";
264
+
targetPort = "http-web-svc";
271
+
description = "List of extra Kubernetes manifests to deploy with this Helm chart.";
274
+
extraFieldDefinitions = lib.mkOption {
275
+
inherit (yamlFormat) type;
280
+
helmVersion = "v2";
282
+
jobImage = "custom-helm-controller:v0.0.1";
286
+
Extra HelmChart field definitions that are merged with the rest of the HelmChart
287
+
custom resource. This can be used to set advanced fields or to overwrite
288
+
generated fields. See https://docs.k3s.io/helm#helmchart-field-definitions
289
+
for possible fields.
74
-
target = lib.mkDefault (mkTarget name);
75
-
source = lib.mkIf (config.content != null) (
77
-
name' = "k3s-manifest-" + builtins.baseNameOf name;
78
-
docName = "k3s-manifest-doc-" + builtins.baseNameOf name;
79
-
yamlDocSeparator = builtins.toFile "yaml-doc-separator" "\n---\n";
80
-
mkYaml = name: x: (pkgs.formats.yaml { }).generate name x;
83
-
if builtins.isList value then
84
-
pkgs.concatText name' (
93
-
lib.mkDerivedConfig options.content mkSource
294
+
config.package = lib.mkDefault (fetchHelm {
305
+
manifestModule = lib.types.submodule (
314
+
enable = lib.mkOption {
315
+
type = lib.types.bool;
317
+
description = "Whether this manifest file should be generated.";
99
-
enabledManifests = lib.filter (m: m.enable) (lib.attrValues cfg.manifests);
100
-
linkManifestEntry = m: "${pkgs.coreutils-full}/bin/ln -sfn ${m.source} ${manifestDir}/${m.target}";
101
-
linkImageEntry = image: "${pkgs.coreutils-full}/bin/ln -sfn ${image} ${imageDir}/${image.name}";
104
-
mkTarget = name: if (lib.hasSuffix ".tgz" name) then name else name + ".tgz";
107
-
"${pkgs.coreutils-full}/bin/ln -sfn ${value} ${chartDir}/${mkTarget (builtins.baseNameOf name)}";
320
+
target = lib.mkOption {
321
+
type = lib.types.nonEmptyStr;
322
+
example = "manifest.yaml";
324
+
Name of the symlink (relative to {file}`${manifestDir}`).
325
+
Defaults to the attribute name.
109
-
activateK3sContent = pkgs.writeShellScript "activate-k3s-content" ''
110
-
${lib.optionalString (
111
-
builtins.length enabledManifests > 0
112
-
) "${pkgs.coreutils-full}/bin/mkdir -p ${manifestDir}"}
113
-
${lib.optionalString (cfg.charts != { }) "${pkgs.coreutils-full}/bin/mkdir -p ${chartDir}"}
114
-
${lib.optionalString (
115
-
builtins.length cfg.images > 0
116
-
) "${pkgs.coreutils-full}/bin/mkdir -p ${imageDir}"}
329
+
content = lib.mkOption {
330
+
type = with lib.types; nullOr (either attrs (listOf attrs));
333
+
Content of the manifest file. A single attribute set will
334
+
generate a single document YAML file. A list of attribute sets
335
+
will generate multiple documents separated by `---` in a single
118
-
${builtins.concatStringsSep "\n" (map linkManifestEntry enabledManifests)}
119
-
${builtins.concatStringsSep "\n" (lib.mapAttrsToList linkChartEntry cfg.charts)}
120
-
${builtins.concatStringsSep "\n" (map linkImageEntry cfg.images)}
340
+
source = lib.mkOption {
341
+
type = lib.types.path;
342
+
example = lib.literalExpression "./manifests/app.yaml";
344
+
Path of the source `.yaml` file.
122
-
${lib.optionalString (cfg.containerdConfigTemplate != null) ''
123
-
mkdir -p $(dirname ${containerdConfigTemplateFile})
124
-
${pkgs.coreutils-full}/bin/ln -sfn ${pkgs.writeText "config.toml.tmpl" cfg.containerdConfigTemplate} ${containerdConfigTemplateFile}
350
+
target = lib.mkDefault (mkManifestTarget name);
351
+
source = lib.mkIf (config.content != null) (
353
+
name' = "k3s-manifest-" + builtins.baseNameOf name;
354
+
docName = "k3s-manifest-doc-" + builtins.baseNameOf name;
357
+
if builtins.isList value then
358
+
pkgs.concatText name' (
359
+
lib.concatMap (x: [
361
+
(yamlFormat.generate docName x)
365
+
yamlFormat.generate name' value;
367
+
lib.mkDerivedConfig options.content mkSource
imports = [ (removeOption [ "docker" ] "k3s docker option is no longer supported.") ];
···
type = lib.types.attrsOf manifestModule;
example = lib.literalExpression ''
245
-
deployment.source = ../manifests/deployment.yaml;
248
-
target = "app-service.yaml";
253
-
name = "app-service";
257
-
"app.kubernetes.io/name" = "MyApp";
491
+
deployment.source = ../manifests/deployment.yaml;
494
+
target = "app-service.yaml";
499
+
name = "app-service";
261
-
name = "name-of-service-port";
264
-
targetPort = "http-web-svc";
503
+
"app.kubernetes.io/name" = "MyApp";
507
+
name = "name-of-service-port";
510
+
targetPort = "http-web-svc";
278
-
"app.kubernetes.io/name" = "MyApp";
524
+
"app.kubernetes.io/name" = "MyApp";
531
+
image = "nginx:1.14.2";
534
+
containerPort = 80;
535
+
name = "http-web-svc";
546
+
name = "nginx-service";
285
-
image = "nginx:1.14.2";
288
-
containerPort = 80;
289
-
name = "http-web-svc";
300
-
name = "nginx-service";
304
-
"app.kubernetes.io/name" = "MyApp";
550
+
"app.kubernetes.io/name" = "MyApp";
554
+
name = "name-of-service-port";
557
+
targetPort = "http-web-svc";
308
-
name = "name-of-service-port";
311
-
targetPort = "http-web-svc";
Auto-deploying manifests that are linked to {file}`${manifestDir}` before k3s starts.
···
Packaged Helm charts that are linked to {file}`${chartDir}` before k3s starts.
The attribute name will be used as the link target (relative to {file}`${chartDir}`).
The specified charts will only be placed on the file system and made available to the
340
-
Kubernetes APIServer from within the cluster, you may use the
341
-
[k3s Helm controller](https://docs.k3s.io/helm#using-the-helm-controller)
342
-
to deploy the charts. This option only makes sense on server nodes
587
+
Kubernetes APIServer from within the cluster. See the [](#opt-services.k3s.autoDeployCharts)
588
+
option and the [k3s Helm controller docs](https://docs.k3s.io/helm#using-the-helm-controller)
589
+
to deploy Helm charts. This option only makes sense on server nodes (`role = server`).
···
set the `clientConnection.kubeconfig` if you want to use `extraKubeProxyConfig`.
700
+
autoDeployCharts = lib.mkOption {
701
+
type = lib.types.attrsOf autoDeployChartsModule;
702
+
apply = lib.mapAttrs mkAutoDeployChartManifest;
704
+
example = lib.literalExpression ''
708
+
repo = "https://helm.goharbor.io";
709
+
version = "1.14.0";
710
+
hash = "sha256-fMP7q1MIbvzPGS9My91vbQ1d3OJMjwc+o8YE/BXZaYU=";
712
+
existingSecretAdminPassword = "harbor-admin";
716
+
certSource = "secret";
717
+
secret.secretName = "my-tls-secret";
720
+
hosts.core = "example.com";
721
+
className = "nginx";
728
+
package = ../charts/my-chart.tgz;
729
+
values = ../values/my-values.yaml;
730
+
extraFieldDefinitions = {
731
+
spec.timeout = "60s";
737
+
Auto deploying Helm charts that are installed by the k3s Helm controller. Avoid to use
738
+
attribute names that are also used in the [](#opt-services.k3s.manifests) and
739
+
[](#opt-services.k3s.charts) options. Manifests with the same name will override
740
+
auto deploying charts with the same name. Similiarly, charts with the same name will
741
+
overwrite the Helm chart contained in auto deploying charts. This option only makes
742
+
sense on server nodes (`role = server`). See the
743
+
[k3s Helm documentation](https://docs.k3s.io/helm) for further information.
···
++ (lib.optional (cfg.role != "server" && cfg.charts != { })
"k3s: Helm charts are only made available to the cluster on server nodes (role == server), they will be ignored by this node."
758
+
++ (lib.optional (cfg.role != "server" && cfg.autoDeployCharts != { })
759
+
"k3s: Auto deploying Helm charts are only installed on server nodes (role == server), they will be ignored by this node."
761
+
++ (lib.optional (duplicateManifests != [ ])
762
+
"k3s: The following auto deploying charts are overriden by manifests of the same name: ${toString duplicateManifests}."
764
+
++ (lib.optional (duplicateCharts != [ ])
765
+
"k3s: The following auto deploying charts are overriden by charts of the same name: ${toString duplicateCharts}."
cfg.disableAgent && cfg.images != [ ]
) "k3s: Images are only imported on nodes with an enabled agent, they will be ignored by this node")
···
environment.systemPackages = [ config.services.k3s.package ];
791
+
# Use systemd-tmpfiles to activate k3s content
792
+
systemd.tmpfiles.settings."10-k3s" =
794
+
# Merge manifest with manifests generated from auto deploying charts, keep only enabled manifests
795
+
enabledManifests = lib.filterAttrs (_: v: v.enable) (cfg.autoDeployCharts // cfg.manifests);
796
+
# Merge charts with charts contained in enabled auto deploying charts
798
+
(lib.concatMapAttrs (n: v: { ${n} = v.package; }) (
799
+
lib.filterAttrs (_: v: v.enable) cfg.autoDeployCharts
802
+
# Make a systemd-tmpfiles rule for a manifest
803
+
mkManifestRule = manifest: {
804
+
name = "${manifestDir}/${manifest.target}";
806
+
"L+".argument = "${manifest.source}";
809
+
# Ensure that all chart targets have a .tgz suffix
810
+
mkChartTarget = name: if (lib.hasSuffix ".tgz" name) then name else name + ".tgz";
811
+
# Make a systemd-tmpfiles rule for a chart
812
+
mkChartRule = target: source: {
813
+
name = "${chartDir}/${mkChartTarget target}";
815
+
"L+".argument = "${source}";
818
+
# Make a systemd-tmpfiles rule for a container image
819
+
mkImageRule = image: {
820
+
name = "${imageDir}/${image.name}";
822
+
"L+".argument = "${image}";
826
+
(lib.mapAttrs' (_: v: mkManifestRule v) enabledManifests)
827
+
// (lib.mapAttrs' (n: v: mkChartRule n v) helmCharts)
828
+
// (builtins.listToAttrs (map mkImageRule cfg.images))
829
+
// (lib.optionalAttrs (cfg.containerdConfigTemplate != null) {
830
+
${containerdConfigTemplateFile} = {
831
+
"L+".argument = "${pkgs.writeText "config.toml.tmpl" cfg.containerdConfigTemplate}";
···
EnvironmentFile = cfg.environmentFile;
536
-
ExecStartPre = activateK3sContent;
ExecStart = lib.concatStringsSep " \\\n " (
[ "${cfg.package}/bin/k3s ${cfg.role}" ]
++ (lib.optional cfg.clusterInit "--cluster-init")