summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiacomo Leidi <therewasa@fishinthecalculator.me>2026-03-08 17:27:13 +0100
committerLiliana Marie Prikler <liliana.prikler@gmail.com>2026-04-12 17:23:42 +0200
commit0b8e838208a26fb8d7785b24fe26a4f9c9f744ac (patch)
treea00916cc8df1e360cf1d082bde415d2a82bedad9
parent2abfd1370fbdd2e6e859c7bd0f491c9f7bda4f0b (diff)
services: configuration: Add environment variable serializer.
This patch implements a general API to serialize configuration records to list of pairs representing environment variables. The car of each pair represents the variable name and the cdr the variable value. * gnu/services/configuration/environment-variables.scm: New file. (serialize-string-environment-variable) (serialize-maybe-string-environment-variable) (serialize-boolean-environment-variable) (serialize-maybe-boolean-environment-variable) (serialize-number-environment-variable) (serialize-maybe-number-environment-variable): New variables. (serialize-environment-variables): New variable. * gnu/services/configuration/utils.scm: New file. (uglify-snake-case): New variable. * tests/services/configuration.scm: Add tests for environment serializer. (wrong type for a field): Adjust error location. * doc/guix.texi: Document it. Change-Id: I81a166576f94d3c8f5bf78c82a02183689a3091c Signed-off-by: Liliana Marie Prikler <liliana.prikler@gmail.com>
-rw-r--r--doc/guix.texi57
-rw-r--r--gnu/services/configuration/environment-variables.scm143
-rw-r--r--gnu/services/configuration/utils.scm35
-rw-r--r--tests/services/configuration.scm129
4 files changed, 362 insertions, 2 deletions
diff --git a/doc/guix.texi b/doc/guix.texi
index 733c86afac1..198ecb3413b 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -51663,6 +51663,63 @@ phone-number = 0
51663is-married = true 51663is-married = true
51664@end example 51664@end example
51665 51665
51666@subsubsection Serializing to environment variables
51667@cindex environment variables, serialization of configuration records
51668
51669There are services which expect their configuration as environment variables.
51670The @code{(gnu services configuration environment-variables)} module provides
51671facilities to serialize configuration records from
51672@code{(gnu services configuration)} to list of pairs representing environment
51673variables.
51674
51675For example this configuration record:
51676
51677@lisp
51678(define-configuration/no-serialization server
51679 (ssh-port
51680 (number 22)
51681 "The public SSH port of the server.")
51682 (fqdn
51683 (maybe-string)
51684 "The fully qualified domain name of the server.")
51685 (active?
51686 (boolean #f)
51687 "Whether or not the server should be activated."))
51688
51689(define my-server
51690 (server
51691 (ssh-port 20022)
51692 (active? #t)))
51693@end lisp
51694
51695with this call:
51696
51697@lisp
51698(serialize-environment-variables my-server server-fields
51699 #:true-value "1"
51700 #:false-value "0")
51701@end lisp
51702
51703would yield:
51704
51705@lisp
51706'(("SSH_PORT" . "20022")
51707 ("ACTIVE" . "1"))
51708@end lisp
51709
51710@anchor{serialize-environment-variables-procedure}
51711@deffn {Procedure} serialize-environment-variables @var{config} @var{fields} @
51712 [@var{selection} #f] [@var{negate?} #f] [#:prefix #f] @
51713 [#:true-value "true"] [#:false-value "false"]
51714Serializes the fields whose name is included in SELECTION from CONFIG, a
51715configuration from @code{(gnu services configuration)}, and FIELDS, the
51716list of its field records, to a list of pairs. When NEGATE? is #t all services
51717not included in SELECTION will be serialized. Each pair represents an
51718environment variable. The first element of each pair is the variable name, the
51719second is the value. When PREFIX is a string it is prepended to the variable
51720name. TRUE-VALUE and FALSE-VALUE will be used as a representation for
51721respectfully @code{#t} and @code{#f}.
51722@end deffn
51666 51723
51667@c ********************************************************************* 51724@c *********************************************************************
51668@cindex troubleshooting, Guix System 51725@cindex troubleshooting, Guix System
diff --git a/gnu/services/configuration/environment-variables.scm b/gnu/services/configuration/environment-variables.scm
new file mode 100644
index 00000000000..c22b732defe
--- /dev/null
+++ b/gnu/services/configuration/environment-variables.scm
@@ -0,0 +1,143 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2026 Giacomo Leidi <goodoldpaul@autistici.org>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (gnu services configuration environment-variables)
20 #:use-module (gnu services configuration)
21 #:use-module (gnu services configuration utils)
22 #:use-module (guix diagnostics)
23 #:use-module (guix i18n)
24 #:use-module (ice-9 match)
25 #:use-module (srfi srfi-1)
26 #:export (serialize-string-environment-variable
27 serialize-boolean-environment-variable
28 serialize-number-environment-variable
29 serialize-maybe-string-environment-variable
30 serialize-maybe-boolean-environment-variable
31 serialize-maybe-number-environment-variable
32
33 serialize-environment-variables))
34
35(define* (field-name->environment-variable field-name
36 #:key prefix
37 (uglify uglify-snake-case))
38 "Serializes FIELD-NAME, a field name from @code{(gnu services configuration)},
39to an environment variable name through UGLIFY, by default a procedure that is
40passed FIELD-NAME, and returns a snake case string representation of
41the field name. Trailing @code{?} in the name are dropped and @code{-} get
42replaced by @code{_}. When PREFIX is a string, it is prepended to the result.
43The result of UGLIFY is then upcased and returned.
44
45For example the procedure would convert @code{'a-field} to @code{\"A_FIELD\"}."
46 (let ((variable (string-upcase
47 (uglify field-name))))
48 (if (string? prefix)
49 (string-append prefix variable)
50 variable)))
51
52(define* (serialize-string-environment-variable field-name value
53 #:key prefix
54 #:allow-other-keys)
55 (cons (field-name->environment-variable field-name #:prefix prefix)
56 value))
57
58(define* (serialize-maybe-string-environment-variable field-name value
59 #:key prefix
60 #:allow-other-keys)
61 (if (maybe-value-set? value)
62 (serialize-string-environment-variable field-name value #:prefix prefix)
63 #f))
64
65(define* (serialize-boolean-environment-variable field-name value
66 #:key prefix
67 (true-value "true")
68 (false-value "false")
69 #:allow-other-keys)
70 (serialize-string-environment-variable
71 field-name (if value true-value false-value)
72 #:prefix prefix))
73
74(define* (serialize-maybe-boolean-environment-variable field-name value
75 #:key prefix
76 #:allow-other-keys)
77 (if (maybe-value-set? value)
78 (serialize-boolean-environment-variable field-name value #:prefix prefix)
79 #f))
80
81(define* (serialize-number-environment-variable field-name value
82 #:key prefix
83 #:allow-other-keys)
84 (cons (field-name->environment-variable field-name #:prefix prefix)
85 (number->string value)))
86
87(define* (serialize-maybe-number-environment-variable field-name value
88 #:key prefix
89 #:allow-other-keys)
90 (if (maybe-value-set? value)
91 (serialize-number-environment-variable field-name value #:prefix prefix)
92 #f))
93
94(define (environment-variable-serializer field)
95 (define type (configuration-field-type field))
96 (match type
97 ('string serialize-string-environment-variable)
98 ('maybe-string serialize-maybe-string-environment-variable)
99 ('number serialize-number-environment-variable)
100 ('integer serialize-number-environment-variable)
101 ('positive serialize-number-environment-variable)
102 ('maybe-number serialize-maybe-number-environment-variable)
103 ('maybe-integer serialize-maybe-number-environment-variable)
104 ('maybe-positive serialize-maybe-number-environment-variable)
105 ('boolean serialize-boolean-environment-variable)
106 ('maybe-boolean serialize-boolean-environment-variable)
107 (_
108 (raise
109 (formatted-message
110 (G_ "Unknown environment-variable field type: ~a")
111 type)))))
112
113(define* (serialize-environment-variables config fields
114 #:optional selection negate?
115 #:key prefix
116 (true-value "true")
117 (false-value "false"))
118 "Serializes the fields whose name is included in SELECTION from CONFIG, a
119configuration from @code{(gnu services configuration)}, and FIELDS, the
120list of its field records, to a list of pairs. When NEGATE? is #t all services
121not included in SELECTION will be serialized. Each pair represents an
122environment variable. The first element of each pair is the variable name, the
123second is the value. When PREFIX is a string it is prepended to the variable
124name. TRUE-VALUE and FALSE-VALUE will be used as a representation for
125respectfully @code{#t} and @code{#f}."
126 (define selected-names
127 (or selection
128 (map configuration-field-name fields)))
129 (define filtered
130 (filter-configuration-fields fields selected-names negate?))
131 (define getters
132 (map configuration-field-getter filtered))
133 (define names
134 (map configuration-field-name filtered))
135 (define serializers
136 (map environment-variable-serializer filtered))
137
138 (filter-map (match-lambda ((serializer name getter)
139 (serializer name (getter config)
140 #:prefix prefix
141 #:true-value true-value
142 #:false-value false-value)))
143 (zip serializers names getters)))
diff --git a/gnu/services/configuration/utils.scm b/gnu/services/configuration/utils.scm
new file mode 100644
index 00000000000..cca2f3df0b6
--- /dev/null
+++ b/gnu/services/configuration/utils.scm
@@ -0,0 +1,35 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2026 Giacomo Leidi <goodoldpaul@autistici.org>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (gnu services configuration utils)
20 #:use-module (ice-9 string-fun)
21 #:export (uglify-snake-case))
22
23(define (uglify-snake-case field-name)
24 "Serializes FIELD-NAME, a field name from @code{(gnu services configuration)},
25to a downcased, snake case string representation of the field name. Trailing
26@code{?} in the name are dropped and dashes are replaced with underscores.
27
28For example the procedure would convert @code{'A-Field?} to @code{\"a_field\"}."
29 (define str (symbol->string field-name))
30 (string-downcase
31 (string-replace-substring
32 (if (string-suffix? "?" str)
33 (string-drop-right str 1)
34 str)
35 "-" "_")))
diff --git a/tests/services/configuration.scm b/tests/services/configuration.scm
index f9012b159d5..0c2a0810e12 100644
--- a/tests/services/configuration.scm
+++ b/tests/services/configuration.scm
@@ -3,6 +3,7 @@
3;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz> 3;;; Copyright © 2021 Xinglu Chen <public@yoctocell.xyz>
4;;; Copyright © 2022 Ludovic Courtès <ludo@gnu.org> 4;;; Copyright © 2022 Ludovic Courtès <ludo@gnu.org>
5;;; Copyright © 2023 Bruno Victal <mirai@makinata.eu> 5;;; Copyright © 2023 Bruno Victal <mirai@makinata.eu>
6;;; Copyright © 2026 Giacomo Leidi <therewasa@fishinthecalculator.me>
6;;; 7;;;
7;;; This file is part of GNU Guix. 8;;; This file is part of GNU Guix.
8;;; 9;;;
@@ -21,6 +22,7 @@
21 22
22(define-module (tests services configuration) 23(define-module (tests services configuration)
23 #:use-module (gnu services configuration) 24 #:use-module (gnu services configuration)
25 #:use-module (gnu services configuration environment-variables)
24 #:use-module (guix diagnostics) 26 #:use-module (guix diagnostics)
25 #:use-module (guix gexp) 27 #:use-module (guix gexp)
26 #:autoload (guix i18n) (G_) 28 #:autoload (guix i18n) (G_)
@@ -48,14 +50,14 @@
48 (port-configuration-port (port-configuration))) 50 (port-configuration-port (port-configuration)))
49 51
50(test-equal "wrong type for a field" 52(test-equal "wrong type for a field"
51 '("configuration.scm" 59 11) ;error location 53 '("configuration.scm" 61 11) ;error location
52 (guard (c ((configuration-error? c) 54 (guard (c ((configuration-error? c)
53 (let ((loc (error-location c))) 55 (let ((loc (error-location c)))
54 (list (basename (location-file loc)) 56 (list (basename (location-file loc))
55 (location-line loc) 57 (location-line loc)
56 (location-column loc))))) 58 (location-column loc)))))
57 (port-configuration 59 (port-configuration
58 ;; This is line 58; the test relies on line/column numbers! 60 ;; This is line 60; the test relies on line/column numbers!
59 (port "This is not a number!")))) 61 (port "This is not a number!"))))
60 62
61(define-configuration port-configuration-cs 63(define-configuration port-configuration-cs
@@ -363,3 +365,126 @@
363 (config-with-maybe-string/no-serialization-name 365 (config-with-maybe-string/no-serialization-name
364 (config-with-maybe-string/no-serialization 366 (config-with-maybe-string/no-serialization
365 (name "foo"))))) 367 (name "foo")))))
368
369
370;;;
371;;; environment-variables serializer
372;;;
373
374(define-configuration/no-serialization env-config
375 (port (number 80) "")
376 (count maybe-number "")
377 (name string "")
378 (url maybe-string "")
379 (active? (boolean #f) ""))
380
381(test-group "environment variables serializer"
382
383 (test-equal "basic serialization"
384 '(("PORT" . "70")
385 ("COUNT" . "80")
386 ("NAME" . "Hello World")
387 ("URL" . "https://example.org")
388 ("ACTIVE" . "true"))
389 (serialize-environment-variables
390 (env-config
391 (port 70)
392 (name "Hello World")
393 (url "https://example.org")
394 (active? #t)
395 (count 80))
396 env-config-fields))
397
398 (test-equal "field selection"
399 '(("PORT" . "80"))
400 (serialize-environment-variables
401 (env-config
402 (name "Hello World"))
403 env-config-fields
404 '(port)))
405
406 (test-equal "field negative selection"
407 '(("NAME" . "Hello World")
408 ("ACTIVE" . "false"))
409 (serialize-environment-variables
410 (env-config
411 (name "Hello World"))
412 env-config-fields
413 '(port)
414 #t))
415
416 (test-equal "variable prefixes"
417 '(("TEST_PORT" . "80")
418 ("TEST_NAME" . "Hello World")
419 ("TEST_ACTIVE" . "false"))
420 (serialize-environment-variables
421 (env-config
422 (name "Hello World"))
423 env-config-fields
424 #:prefix "TEST_"))
425
426 (test-equal "boolean serialization"
427 '(("PORT" . "80")
428 ("NAME" . "Hello World")
429 ("ACTIVE" . "1"))
430 (serialize-environment-variables
431 (env-config
432 (name "Hello World")
433 (active? #t))
434 env-config-fields
435 #:true-value "1"
436 #:false-value "0"))
437
438 (test-equal "full record serialization"
439 '(("TEST_COUNT" . "800")
440 ("TEST_NAME" . "Hello World")
441 ("TEST_URL" . "https://example.org")
442 ("TEST_ACTIVE" . "1"))
443 (serialize-environment-variables
444 (env-config
445 (port 90)
446 (name "Hello World")
447 (count 800)
448 (url "https://example.org")
449 (active? #t))
450 env-config-fields
451 '(port)
452 #t
453 #:prefix "TEST_"
454 #:true-value "1"
455 #:false-value "0")))
456
457(define-configuration another-env-config
458 (port
459 (number 80)
460 ""
461 (serializer serialize-number-environment-variable))
462 (count
463 (maybe-number)
464 ""
465 (serializer serialize-maybe-number-environment-variable))
466 (name
467 (string)
468 ""
469 (serializer serialize-string-environment-variable))
470 (url
471 (maybe-string)
472 ""
473 (serializer serialize-maybe-string-environment-variable))
474 (active?
475 (boolean #f)
476 ""
477 (serializer serialize-boolean-environment-variable)))
478
479(test-group "environment variables serializer, with field serializers"
480
481 (test-assert "full record serialization"
482 (gexp?
483 (serialize-configuration
484 (another-env-config
485 (port 90)
486 (name "Hello World")
487 (count 800)
488 (url "https://example.org")
489 (active? #t))
490 another-env-config-fields))))