···597597- New options for the declarative configuration of the user space part of ALSA have been introduced under [hardware.alsa](options.html#opt-hardware.alsa.enable), including setting the default capture and playback device, defining sound card aliases and volume controls.
598598 Note: these are intended for users not running a sound server like PulseAudio or PipeWire, but having ALSA as their only sound system.
599599600600+- `services.k3s` now provides the `autoDeployCharts` option that allows to automatically deploy Helm charts via the k3s Helm controller.
601601+600602- Caddy can now be built with plugins by using `caddy.withPlugins`, a `passthru` function that accepts an attribute set as a parameter. The `plugins` argument represents a list of Caddy plugins, with each Caddy plugin being a versioned module. The `hash` argument represents the `vendorHash` of the resulting Caddy source code with the plugins added.
601603602604 Example:
+506-161
nixos/modules/services/cluster/k3s/default.nix
···2020 chartDir = "/var/lib/rancher/k3s/server/static/charts";
2121 imageDir = "/var/lib/rancher/k3s/agent/images";
2222 containerdConfigTemplateFile = "/var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl";
2323+ yamlFormat = pkgs.formats.yaml { };
2424+ yamlDocSeparator = builtins.toFile "yaml-doc-separator" "\n---\n";
2525+ # Manifests need a valid YAML suffix to be respected by k3s
2626+ mkManifestTarget =
2727+ name: if (lib.hasSuffix ".yaml" name || lib.hasSuffix ".yml" name) then name else name + ".yaml";
2828+ # Produces a list containing all duplicate manifest names
2929+ duplicateManifests =
3030+ with builtins;
3131+ lib.intersectLists (attrNames cfg.autoDeployCharts) (attrNames cfg.manifests);
3232+ # Produces a list containing all duplicate chart names
3333+ duplicateCharts =
3434+ with builtins;
3535+ lib.intersectLists (attrNames cfg.autoDeployCharts) (attrNames cfg.charts);
23362424- manifestModule =
3737+ # Converts YAML -> JSON -> Nix
3838+ fromYaml =
3939+ path:
4040+ with builtins;
4141+ fromJSON (
4242+ readFile (
4343+ pkgs.runCommand "${path}-converted.json" { nativeBuildInputs = [ yq-go ]; } ''
4444+ yq --no-colors --output-format json ${path} > $out
4545+ ''
4646+ )
4747+ );
4848+4949+ # Replace characters that are problematic in file names
5050+ cleanHelmChartName =
5151+ lib.replaceStrings
5252+ [
5353+ "/"
5454+ ":"
5555+ ]
5656+ [
5757+ "-"
5858+ "-"
5959+ ];
6060+6161+ # Fetch a Helm chart from a public registry. This only supports a basic Helm pull.
6262+ fetchHelm =
6363+ {
6464+ name,
6565+ repo,
6666+ version,
6767+ hash ? lib.fakeHash,
6868+ }:
6969+ pkgs.runCommand (cleanHelmChartName "${lib.removePrefix "https://" repo}-${name}-${version}.tgz")
7070+ {
7171+ inherit (lib.fetchers.normalizeHash { } { inherit hash; }) outputHash outputHashAlgo;
7272+ impureEnvVars = lib.fetchers.proxyImpureEnvVars;
7373+ nativeBuildInputs = with pkgs; [
7474+ kubernetes-helm
7575+ cacert
7676+ ];
7777+ }
7878+ ''
7979+ export HOME="$PWD"
8080+ helm repo add repository ${repo}
8181+ helm pull repository/${name} --version ${version}
8282+ mv ./*.tgz $out
8383+ '';
8484+8585+ # Returns the path to a YAML manifest file
8686+ mkExtraDeployManifest =
8787+ x:
8888+ # x is a derivation that provides a YAML file
8989+ if lib.isDerivation x then
9090+ x.outPath
9191+ # x is an attribute set that needs to be converted to a YAML file
9292+ else if builtins.isAttrs x then
9393+ (yamlFormat.generate "extra-deploy-chart-manifest" x)
9494+ # assume x is a path to a YAML file
9595+ else
9696+ x;
9797+9898+ # Generate a HelmChart custom resource.
9999+ mkHelmChartCR =
100100+ name: value:
25101 let
2626- mkTarget =
2727- name: if (lib.hasSuffix ".yaml" name || lib.hasSuffix ".yml" name) then name else name + ".yaml";
102102+ chartValues = if (lib.isPath value.values) then fromYaml value.values else value.values;
103103+ # use JSON for values as it's a subset of YAML and understood by the k3s Helm controller
104104+ valuesContent = builtins.toJSON chartValues;
28105 in
2929- lib.types.submodule (
3030- {
3131- name,
3232- config,
3333- options,
3434- ...
3535- }:
3636- {
3737- options = {
3838- enable = lib.mkOption {
3939- type = lib.types.bool;
4040- default = true;
4141- description = "Whether this manifest file should be generated.";
4242- };
106106+ # merge with extraFieldDefinitions to allow setting advanced values and overwrite generated
107107+ # values
108108+ lib.recursiveUpdate {
109109+ apiVersion = "helm.cattle.io/v1";
110110+ kind = "HelmChart";
111111+ metadata = {
112112+ inherit name;
113113+ namespace = "kube-system";
114114+ };
115115+ spec = {
116116+ inherit valuesContent;
117117+ inherit (value) targetNamespace createNamespace;
118118+ chart = "https://%{KUBERNETES_API}%/static/charts/${name}.tgz";
119119+ };
120120+ } value.extraFieldDefinitions;
121121+122122+ # Generate a HelmChart custom resource together with extraDeploy manifests. This
123123+ # generates possibly a multi document YAML file that the auto deploy mechanism of k3s
124124+ # deploys.
125125+ mkAutoDeployChartManifest = name: value: {
126126+ # target is the final name of the link created for the manifest file
127127+ target = mkManifestTarget name;
128128+ inherit (value) enable package;
129129+ # source is a store path containing the complete manifest file
130130+ source = pkgs.concatText "auto-deploy-chart-${name}.yaml" (
131131+ [
132132+ (yamlFormat.generate "helm-chart-manifest-${name}.yaml" (mkHelmChartCR name value))
133133+ ]
134134+ # alternate the YAML doc seperator (---) and extraDeploy manifests to create
135135+ # multi document YAMLs
136136+ ++ (lib.concatMap (x: [
137137+ yamlDocSeparator
138138+ (mkExtraDeployManifest x)
139139+ ]) value.extraDeploy)
140140+ );
141141+ };
431424444- target = lib.mkOption {
4545- type = lib.types.nonEmptyStr;
4646- example = lib.literalExpression "manifest.yaml";
4747- description = ''
4848- Name of the symlink (relative to {file}`${manifestDir}`).
4949- Defaults to the attribute name.
5050- '';
5151- };
143143+ autoDeployChartsModule = lib.types.submodule (
144144+ { config, ... }:
145145+ {
146146+ options = {
147147+ enable = lib.mkOption {
148148+ type = lib.types.bool;
149149+ default = true;
150150+ example = false;
151151+ description = ''
152152+ Whether to enable the installation of this Helm chart. Note that setting
153153+ this option to `false` will not uninstall the chart from the cluster, if
154154+ it was previously installed. Please use the the `--disable` flag or `.skip`
155155+ files to delete/disable Helm charts, as mentioned in the
156156+ [docs](https://docs.k3s.io/installation/packaged-components#disabling-manifests).
157157+ '';
158158+ };
521595353- content = lib.mkOption {
5454- type = with lib.types; nullOr (either attrs (listOf attrs));
5555- default = null;
5656- description = ''
5757- Content of the manifest file. A single attribute set will
5858- generate a single document YAML file. A list of attribute sets
5959- will generate multiple documents separated by `---` in a single
6060- YAML file.
6161- '';
160160+ repo = lib.mkOption {
161161+ type = lib.types.nonEmptyStr;
162162+ example = "https://kubernetes.github.io/ingress-nginx";
163163+ description = ''
164164+ The repo of the Helm chart. Only has an effect if `package` is not set.
165165+ The Helm chart is fetched during build time and placed as a `.tgz` archive on the
166166+ filesystem.
167167+ '';
168168+ };
169169+170170+ name = lib.mkOption {
171171+ type = lib.types.nonEmptyStr;
172172+ example = "ingress-nginx";
173173+ description = ''
174174+ The name of the Helm chart. Only has an effect if `package` is not set.
175175+ The Helm chart is fetched during build time and placed as a `.tgz` archive on the
176176+ filesystem.
177177+ '';
178178+ };
179179+180180+ version = lib.mkOption {
181181+ type = lib.types.nonEmptyStr;
182182+ example = "4.7.0";
183183+ description = ''
184184+ The version of the Helm chart. Only has an effect if `package` is not set.
185185+ The Helm chart is fetched during build time and placed as a `.tgz` archive on the
186186+ filesystem.
187187+ '';
188188+ };
189189+190190+ hash = lib.mkOption {
191191+ type = lib.types.str;
192192+ example = "sha256-ej+vpPNdiOoXsaj1jyRpWLisJgWo8EqX+Z5VbpSjsPA=";
193193+ description = ''
194194+ The hash of the packaged Helm chart. Only has an effect if `package` is not set.
195195+ The Helm chart is fetched during build time and placed as a `.tgz` archive on the
196196+ filesystem.
197197+ '';
198198+ };
199199+200200+ package = lib.mkOption {
201201+ type = with lib.types; either path package;
202202+ example = lib.literalExpression "../my-helm-chart.tgz";
203203+ description = ''
204204+ The packaged Helm chart. Overwrites the options `repo`, `name`, `version`
205205+ and `hash` in case of conflicts.
206206+ '';
207207+ };
208208+209209+ targetNamespace = lib.mkOption {
210210+ type = lib.types.nonEmptyStr;
211211+ default = "default";
212212+ example = "kube-system";
213213+ description = "The namespace in which the Helm chart gets installed.";
214214+ };
215215+216216+ createNamespace = lib.mkOption {
217217+ type = lib.types.bool;
218218+ default = false;
219219+ example = true;
220220+ description = "Whether to create the target namespace if not present.";
221221+ };
222222+223223+ values = lib.mkOption {
224224+ type = with lib.types; either path attrs;
225225+ default = { };
226226+ example = {
227227+ replicaCount = 3;
228228+ hostName = "my-host";
229229+ server = {
230230+ name = "nginx";
231231+ port = 80;
232232+ };
62233 };
234234+ description = ''
235235+ Override default chart values via Nix expressions. This is equivalent to setting
236236+ values in a `values.yaml` file.
632376464- source = lib.mkOption {
6565- type = lib.types.path;
6666- example = lib.literalExpression "./manifests/app.yaml";
6767- description = ''
6868- Path of the source `.yaml` file.
6969- '';
238238+ WARNING: The values (including secrets!) specified here are exposed unencrypted
239239+ in the world-readable nix store.
240240+ '';
241241+ };
242242+243243+ extraDeploy = lib.mkOption {
244244+ type = with lib.types; listOf (either path attrs);
245245+ default = [ ];
246246+ example = lib.literalExpression ''
247247+ [
248248+ ../manifests/my-extra-deployment.yaml
249249+ {
250250+ apiVersion = "v1";
251251+ kind = "Service";
252252+ metadata = {
253253+ name = "app-service";
254254+ };
255255+ spec = {
256256+ selector = {
257257+ "app.kubernetes.io/name" = "MyApp";
258258+ };
259259+ ports = [
260260+ {
261261+ name = "name-of-service-port";
262262+ protocol = "TCP";
263263+ port = 80;
264264+ targetPort = "http-web-svc";
265265+ }
266266+ ];
267267+ };
268268+ }
269269+ ];
270270+ '';
271271+ description = "List of extra Kubernetes manifests to deploy with this Helm chart.";
272272+ };
273273+274274+ extraFieldDefinitions = lib.mkOption {
275275+ inherit (yamlFormat) type;
276276+ default = { };
277277+ example = {
278278+ spec = {
279279+ bootstrap = true;
280280+ helmVersion = "v2";
281281+ backOffLimit = 3;
282282+ jobImage = "custom-helm-controller:v0.0.1";
283283+ };
70284 };
285285+ description = ''
286286+ Extra HelmChart field definitions that are merged with the rest of the HelmChart
287287+ custom resource. This can be used to set advanced fields or to overwrite
288288+ generated fields. See https://docs.k3s.io/helm#helmchart-field-definitions
289289+ for possible fields.
290290+ '';
71291 };
292292+ };
722937373- config = {
7474- target = lib.mkDefault (mkTarget name);
7575- source = lib.mkIf (config.content != null) (
7676- let
7777- name' = "k3s-manifest-" + builtins.baseNameOf name;
7878- docName = "k3s-manifest-doc-" + builtins.baseNameOf name;
7979- yamlDocSeparator = builtins.toFile "yaml-doc-separator" "\n---\n";
8080- mkYaml = name: x: (pkgs.formats.yaml { }).generate name x;
8181- mkSource =
8282- value:
8383- if builtins.isList value then
8484- pkgs.concatText name' (
8585- lib.concatMap (x: [
8686- yamlDocSeparator
8787- (mkYaml docName x)
8888- ]) value
8989- )
9090- else
9191- mkYaml name' value;
9292- in
9393- lib.mkDerivedConfig options.content mkSource
9494- );
294294+ config.package = lib.mkDefault (fetchHelm {
295295+ inherit (config)
296296+ repo
297297+ name
298298+ version
299299+ hash
300300+ ;
301301+ });
302302+ }
303303+ );
304304+305305+ manifestModule = lib.types.submodule (
306306+ {
307307+ name,
308308+ config,
309309+ options,
310310+ ...
311311+ }:
312312+ {
313313+ options = {
314314+ enable = lib.mkOption {
315315+ type = lib.types.bool;
316316+ default = true;
317317+ description = "Whether this manifest file should be generated.";
95318 };
9696- }
9797- );
983199999- enabledManifests = lib.filter (m: m.enable) (lib.attrValues cfg.manifests);
100100- linkManifestEntry = m: "${pkgs.coreutils-full}/bin/ln -sfn ${m.source} ${manifestDir}/${m.target}";
101101- linkImageEntry = image: "${pkgs.coreutils-full}/bin/ln -sfn ${image} ${imageDir}/${image.name}";
102102- linkChartEntry =
103103- let
104104- mkTarget = name: if (lib.hasSuffix ".tgz" name) then name else name + ".tgz";
105105- in
106106- name: value:
107107- "${pkgs.coreutils-full}/bin/ln -sfn ${value} ${chartDir}/${mkTarget (builtins.baseNameOf name)}";
320320+ target = lib.mkOption {
321321+ type = lib.types.nonEmptyStr;
322322+ example = "manifest.yaml";
323323+ description = ''
324324+ Name of the symlink (relative to {file}`${manifestDir}`).
325325+ Defaults to the attribute name.
326326+ '';
327327+ };
108328109109- activateK3sContent = pkgs.writeShellScript "activate-k3s-content" ''
110110- ${lib.optionalString (
111111- builtins.length enabledManifests > 0
112112- ) "${pkgs.coreutils-full}/bin/mkdir -p ${manifestDir}"}
113113- ${lib.optionalString (cfg.charts != { }) "${pkgs.coreutils-full}/bin/mkdir -p ${chartDir}"}
114114- ${lib.optionalString (
115115- builtins.length cfg.images > 0
116116- ) "${pkgs.coreutils-full}/bin/mkdir -p ${imageDir}"}
329329+ content = lib.mkOption {
330330+ type = with lib.types; nullOr (either attrs (listOf attrs));
331331+ default = null;
332332+ description = ''
333333+ Content of the manifest file. A single attribute set will
334334+ generate a single document YAML file. A list of attribute sets
335335+ will generate multiple documents separated by `---` in a single
336336+ YAML file.
337337+ '';
338338+ };
117339118118- ${builtins.concatStringsSep "\n" (map linkManifestEntry enabledManifests)}
119119- ${builtins.concatStringsSep "\n" (lib.mapAttrsToList linkChartEntry cfg.charts)}
120120- ${builtins.concatStringsSep "\n" (map linkImageEntry cfg.images)}
340340+ source = lib.mkOption {
341341+ type = lib.types.path;
342342+ example = lib.literalExpression "./manifests/app.yaml";
343343+ description = ''
344344+ Path of the source `.yaml` file.
345345+ '';
346346+ };
347347+ };
121348122122- ${lib.optionalString (cfg.containerdConfigTemplate != null) ''
123123- mkdir -p $(dirname ${containerdConfigTemplateFile})
124124- ${pkgs.coreutils-full}/bin/ln -sfn ${pkgs.writeText "config.toml.tmpl" cfg.containerdConfigTemplate} ${containerdConfigTemplateFile}
125125- ''}
126126- '';
349349+ config = {
350350+ target = lib.mkDefault (mkManifestTarget name);
351351+ source = lib.mkIf (config.content != null) (
352352+ let
353353+ name' = "k3s-manifest-" + builtins.baseNameOf name;
354354+ docName = "k3s-manifest-doc-" + builtins.baseNameOf name;
355355+ mkSource =
356356+ value:
357357+ if builtins.isList value then
358358+ pkgs.concatText name' (
359359+ lib.concatMap (x: [
360360+ yamlDocSeparator
361361+ (yamlFormat.generate docName x)
362362+ ]) value
363363+ )
364364+ else
365365+ yamlFormat.generate name' value;
366366+ in
367367+ lib.mkDerivedConfig options.content mkSource
368368+ );
369369+ };
370370+ }
371371+ );
127372in
128373{
129374 imports = [ (removeOption [ "docker" ] "k3s docker option is no longer supported.") ];
···242487 type = lib.types.attrsOf manifestModule;
243488 default = { };
244489 example = lib.literalExpression ''
245245- deployment.source = ../manifests/deployment.yaml;
246246- my-service = {
247247- enable = false;
248248- target = "app-service.yaml";
249249- content = {
250250- apiVersion = "v1";
251251- kind = "Service";
252252- metadata = {
253253- name = "app-service";
254254- };
255255- spec = {
256256- selector = {
257257- "app.kubernetes.io/name" = "MyApp";
490490+ {
491491+ deployment.source = ../manifests/deployment.yaml;
492492+ my-service = {
493493+ enable = false;
494494+ target = "app-service.yaml";
495495+ content = {
496496+ apiVersion = "v1";
497497+ kind = "Service";
498498+ metadata = {
499499+ name = "app-service";
258500 };
259259- ports = [
260260- {
261261- name = "name-of-service-port";
262262- protocol = "TCP";
263263- port = 80;
264264- targetPort = "http-web-svc";
265265- }
266266- ];
501501+ spec = {
502502+ selector = {
503503+ "app.kubernetes.io/name" = "MyApp";
504504+ };
505505+ ports = [
506506+ {
507507+ name = "name-of-service-port";
508508+ protocol = "TCP";
509509+ port = 80;
510510+ targetPort = "http-web-svc";
511511+ }
512512+ ];
513513+ };
267514 };
268268- }
269269- };
515515+ };
270516271271- nginx.content = [
272272- {
273273- apiVersion = "v1";
274274- kind = "Pod";
275275- metadata = {
276276- name = "nginx";
277277- labels = {
278278- "app.kubernetes.io/name" = "MyApp";
517517+ nginx.content = [
518518+ {
519519+ apiVersion = "v1";
520520+ kind = "Pod";
521521+ metadata = {
522522+ name = "nginx";
523523+ labels = {
524524+ "app.kubernetes.io/name" = "MyApp";
525525+ };
526526+ };
527527+ spec = {
528528+ containers = [
529529+ {
530530+ name = "nginx";
531531+ image = "nginx:1.14.2";
532532+ ports = [
533533+ {
534534+ containerPort = 80;
535535+ name = "http-web-svc";
536536+ }
537537+ ];
538538+ }
539539+ ];
540540+ };
541541+ }
542542+ {
543543+ apiVersion = "v1";
544544+ kind = "Service";
545545+ metadata = {
546546+ name = "nginx-service";
279547 };
280280- };
281281- spec = {
282282- containers = [
283283- {
284284- name = "nginx";
285285- image = "nginx:1.14.2";
286286- ports = [
287287- {
288288- containerPort = 80;
289289- name = "http-web-svc";
290290- }
291291- ];
292292- }
293293- ];
294294- };
295295- }
296296- {
297297- apiVersion = "v1";
298298- kind = "Service";
299299- metadata = {
300300- name = "nginx-service";
301301- };
302302- spec = {
303303- selector = {
304304- "app.kubernetes.io/name" = "MyApp";
548548+ spec = {
549549+ selector = {
550550+ "app.kubernetes.io/name" = "MyApp";
551551+ };
552552+ ports = [
553553+ {
554554+ name = "name-of-service-port";
555555+ protocol = "TCP";
556556+ port = 80;
557557+ targetPort = "http-web-svc";
558558+ }
559559+ ];
305560 };
306306- ports = [
307307- {
308308- name = "name-of-service-port";
309309- protocol = "TCP";
310310- port = 80;
311311- targetPort = "http-web-svc";
312312- }
313313- ];
314314- };
315315- }
316316- ];
561561+ }
562562+ ];
563563+ };
317564 '';
318565 description = ''
319566 Auto-deploying manifests that are linked to {file}`${manifestDir}` before k3s starts.
···337584 Packaged Helm charts that are linked to {file}`${chartDir}` before k3s starts.
338585 The attribute name will be used as the link target (relative to {file}`${chartDir}`).
339586 The specified charts will only be placed on the file system and made available to the
340340- Kubernetes APIServer from within the cluster, you may use the
341341- [k3s Helm controller](https://docs.k3s.io/helm#using-the-helm-controller)
342342- to deploy the charts. This option only makes sense on server nodes
343343- (`role = server`).
587587+ Kubernetes APIServer from within the cluster. See the [](#opt-services.k3s.autoDeployCharts)
588588+ option and the [k3s Helm controller docs](https://docs.k3s.io/helm#using-the-helm-controller)
589589+ to deploy Helm charts. This option only makes sense on server nodes (`role = server`).
344590 '';
345591 };
346592···450696 set the `clientConnection.kubeconfig` if you want to use `extraKubeProxyConfig`.
451697 '';
452698 };
699699+700700+ autoDeployCharts = lib.mkOption {
701701+ type = lib.types.attrsOf autoDeployChartsModule;
702702+ apply = lib.mapAttrs mkAutoDeployChartManifest;
703703+ default = { };
704704+ example = lib.literalExpression ''
705705+ {
706706+ harbor = {
707707+ name = "harbor";
708708+ repo = "https://helm.goharbor.io";
709709+ version = "1.14.0";
710710+ hash = "sha256-fMP7q1MIbvzPGS9My91vbQ1d3OJMjwc+o8YE/BXZaYU=";
711711+ values = {
712712+ existingSecretAdminPassword = "harbor-admin";
713713+ expose = {
714714+ tls = {
715715+ enabled = true;
716716+ certSource = "secret";
717717+ secret.secretName = "my-tls-secret";
718718+ };
719719+ ingress = {
720720+ hosts.core = "example.com";
721721+ className = "nginx";
722722+ };
723723+ };
724724+ };
725725+ };
726726+727727+ custom-chart = {
728728+ package = ../charts/my-chart.tgz;
729729+ values = ../values/my-values.yaml;
730730+ extraFieldDefinitions = {
731731+ spec.timeout = "60s";
732732+ };
733733+ };
734734+ }
735735+ '';
736736+ description = ''
737737+ Auto deploying Helm charts that are installed by the k3s Helm controller. Avoid to use
738738+ attribute names that are also used in the [](#opt-services.k3s.manifests) and
739739+ [](#opt-services.k3s.charts) options. Manifests with the same name will override
740740+ auto deploying charts with the same name. Similiarly, charts with the same name will
741741+ overwrite the Helm chart contained in auto deploying charts. This option only makes
742742+ sense on server nodes (`role = server`). See the
743743+ [k3s Helm documentation](https://docs.k3s.io/helm) for further information.
744744+ '';
745745+ };
453746 };
454747455748 # implementation
···462755 ++ (lib.optional (cfg.role != "server" && cfg.charts != { })
463756 "k3s: Helm charts are only made available to the cluster on server nodes (role == server), they will be ignored by this node."
464757 )
758758+ ++ (lib.optional (cfg.role != "server" && cfg.autoDeployCharts != { })
759759+ "k3s: Auto deploying Helm charts are only installed on server nodes (role == server), they will be ignored by this node."
760760+ )
761761+ ++ (lib.optional (duplicateManifests != [ ])
762762+ "k3s: The following auto deploying charts are overriden by manifests of the same name: ${toString duplicateManifests}."
763763+ )
764764+ ++ (lib.optional (duplicateCharts != [ ])
765765+ "k3s: The following auto deploying charts are overriden by charts of the same name: ${toString duplicateCharts}."
766766+ )
465767 ++ (lib.optional (
466768 cfg.disableAgent && cfg.images != [ ]
467769 ) "k3s: Images are only imported on nodes with an enabled agent, they will be ignored by this node")
···486788487789 environment.systemPackages = [ config.services.k3s.package ];
488790791791+ # Use systemd-tmpfiles to activate k3s content
792792+ systemd.tmpfiles.settings."10-k3s" =
793793+ let
794794+ # Merge manifest with manifests generated from auto deploying charts, keep only enabled manifests
795795+ enabledManifests = lib.filterAttrs (_: v: v.enable) (cfg.autoDeployCharts // cfg.manifests);
796796+ # Merge charts with charts contained in enabled auto deploying charts
797797+ helmCharts =
798798+ (lib.concatMapAttrs (n: v: { ${n} = v.package; }) (
799799+ lib.filterAttrs (_: v: v.enable) cfg.autoDeployCharts
800800+ ))
801801+ // cfg.charts;
802802+ # Make a systemd-tmpfiles rule for a manifest
803803+ mkManifestRule = manifest: {
804804+ name = "${manifestDir}/${manifest.target}";
805805+ value = {
806806+ "L+".argument = "${manifest.source}";
807807+ };
808808+ };
809809+ # Ensure that all chart targets have a .tgz suffix
810810+ mkChartTarget = name: if (lib.hasSuffix ".tgz" name) then name else name + ".tgz";
811811+ # Make a systemd-tmpfiles rule for a chart
812812+ mkChartRule = target: source: {
813813+ name = "${chartDir}/${mkChartTarget target}";
814814+ value = {
815815+ "L+".argument = "${source}";
816816+ };
817817+ };
818818+ # Make a systemd-tmpfiles rule for a container image
819819+ mkImageRule = image: {
820820+ name = "${imageDir}/${image.name}";
821821+ value = {
822822+ "L+".argument = "${image}";
823823+ };
824824+ };
825825+ in
826826+ (lib.mapAttrs' (_: v: mkManifestRule v) enabledManifests)
827827+ // (lib.mapAttrs' (n: v: mkChartRule n v) helmCharts)
828828+ // (builtins.listToAttrs (map mkImageRule cfg.images))
829829+ // (lib.optionalAttrs (cfg.containerdConfigTemplate != null) {
830830+ ${containerdConfigTemplateFile} = {
831831+ "L+".argument = "${pkgs.writeText "config.toml.tmpl" cfg.containerdConfigTemplate}";
832832+ };
833833+ });
834834+489835 systemd.services.k3s =
490836 let
491837 kubeletParams =
···533879 LimitCORE = "infinity";
534880 TasksMax = "infinity";
535881 EnvironmentFile = cfg.environmentFile;
536536- ExecStartPre = activateK3sContent;
537882 ExecStart = lib.concatStringsSep " \\\n " (
538883 [ "${cfg.package}/bin/k3s ${cfg.role}" ]
539884 ++ (lib.optional cfg.clusterInit "--cluster-init")
+135
nixos/tests/k3s/auto-deploy-charts.nix
···11+# Tests whether container images are imported and auto deploying Helm charts work
22+import ../make-test-python.nix (
33+ {
44+ k3s,
55+ lib,
66+ pkgs,
77+ ...
88+ }:
99+ let
1010+ testImageEnv = pkgs.buildEnv {
1111+ name = "k3s-pause-image-env";
1212+ paths = with pkgs; [
1313+ busybox
1414+ hello
1515+ ];
1616+ };
1717+ testImage = pkgs.dockerTools.buildImage {
1818+ name = "test.local/test";
1919+ tag = "local";
2020+ # Slightly reduces the time needed to import image
2121+ compressor = "zstd";
2222+ copyToRoot = testImageEnv;
2323+ };
2424+ # pack the test helm chart as a .tgz archive
2525+ package =
2626+ pkgs.runCommand "k3s-test-chart.tgz"
2727+ {
2828+ nativeBuildInputs = [ pkgs.kubernetes-helm ];
2929+ }
3030+ ''
3131+ helm package ${./k3s-test-chart}
3232+ mv ./*.tgz $out
3333+ '';
3434+ # The common Helm chart that is used in this test
3535+ testChart = {
3636+ inherit package;
3737+ values = {
3838+ runCommand = "hello";
3939+ image = {
4040+ repository = testImage.imageName;
4141+ tag = testImage.imageTag;
4242+ };
4343+ };
4444+ };
4545+ in
4646+ {
4747+ name = "${k3s.name}-auto-deploy-helm";
4848+ meta.maintainers = lib.teams.k3s.members;
4949+ nodes.machine =
5050+ { pkgs, ... }:
5151+ {
5252+ # k3s uses enough resources the default vm fails.
5353+ virtualisation = {
5454+ memorySize = 1536;
5555+ diskSize = 4096;
5656+ };
5757+ environment.systemPackages = [ pkgs.yq-go ];
5858+ services.k3s = {
5959+ enable = true;
6060+ package = k3s;
6161+ # Slightly reduce resource usage
6262+ extraFlags = [
6363+ "--disable coredns"
6464+ "--disable local-storage"
6565+ "--disable metrics-server"
6666+ "--disable servicelb"
6767+ "--disable traefik"
6868+ ];
6969+ images = [
7070+ # Provides the k3s Helm controller
7171+ k3s.airgapImages
7272+ testImage
7373+ ];
7474+ autoDeployCharts = {
7575+ # regular test chart that should get installed
7676+ hello = testChart;
7777+ # disabled chart that should not get installed
7878+ disabled = testChart // {
7979+ enable = false;
8080+ };
8181+ # advanced chart that should get installed in the "test" namespace with a custom
8282+ # timeout and overridden values
8383+ advanced = testChart // {
8484+ # create the "test" namespace via extraDeploy for testing
8585+ extraDeploy = [
8686+ {
8787+ apiVersion = "v1";
8888+ kind = "Namespace";
8989+ metadata.name = "test";
9090+ }
9191+ ];
9292+ extraFieldDefinitions = {
9393+ spec = {
9494+ # overwrite chart values
9595+ valuesContent = ''
9696+ runCommand: "echo 'advanced hello'"
9797+ image:
9898+ repository: ${testImage.imageName}
9999+ tag: ${testImage.imageTag}
100100+ '';
101101+ # overwrite the chart namespace
102102+ targetNamespace = "test";
103103+ # set a custom timeout
104104+ timeout = "69s";
105105+ };
106106+ };
107107+ };
108108+ };
109109+ };
110110+ };
111111+112112+ testScript = # python
113113+ ''
114114+ import json
115115+116116+ machine.wait_for_unit("k3s")
117117+ # check existence/absence of chart manifest files
118118+ machine.succeed("test -e /var/lib/rancher/k3s/server/manifests/hello.yaml")
119119+ machine.succeed("test ! -e /var/lib/rancher/k3s/server/manifests/disabled.yaml")
120120+ machine.succeed("test -e /var/lib/rancher/k3s/server/manifests/advanced.yaml")
121121+ # check that the timeout is set correctly, select only the first doc in advanced.yaml
122122+ advancedManifest = json.loads(machine.succeed("yq -o json 'select(di == 0)' /var/lib/rancher/k3s/server/manifests/advanced.yaml"))
123123+ assert advancedManifest["spec"]["timeout"] == "69s", f"unexpected value for spec.timeout: {advancedManifest["spec"]["timeout"]}"
124124+ # wait for test jobs to complete
125125+ machine.wait_until_succeeds("kubectl wait --for=condition=complete job/hello", timeout=180)
126126+ machine.wait_until_succeeds("kubectl -n test wait --for=condition=complete job/advanced", timeout=180)
127127+ # check output of test jobs
128128+ hello_output = machine.succeed("kubectl logs -l batch.kubernetes.io/job-name=hello")
129129+ advanced_output = machine.succeed("kubectl -n test logs -l batch.kubernetes.io/job-name=advanced")
130130+ # strip the output to remove trailing whitespaces
131131+ assert hello_output.rstrip() == "Hello, world!", f"unexpected output of hello job: {hello_output}"
132132+ assert advanced_output.rstrip() == "advanced hello", f"unexpected output of advanced job: {advanced_output}"
133133+ '';
134134+ }
135135+)
···11+apiVersion: v2
22+name: k3s-test-chart
33+description: A Helm chart that is used in k3s NixOS tests.
44+55+# A chart can be either an 'application' or a 'library' chart.
66+#
77+# Application charts are a collection of templates that can be packaged into versioned archives
88+# to be deployed.
99+#
1010+# Library charts provide useful utilities or functions for the chart developer. They're included as
1111+# a dependency of application charts to inject those utilities and functions into the rendering
1212+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
1313+type: application
1414+1515+# This is the chart version. This version number should be incremented each time you make changes
1616+# to the chart and its templates, including the app version.
1717+# Versions are expected to follow Semantic Versioning (https://semver.org/)
1818+version: 0.1.0
1919+2020+# This is the version number of the application being deployed. This version number should be
2121+# incremented each time you make changes to the application. Versions are not expected to
2222+# follow Semantic Versioning. They should reflect the version the application is using.
2323+# It is recommended to use it with quotes.
2424+appVersion: "1.16.0"