summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiacomo Leidi <goodoldpaul@autistici.org>2025-05-05 09:57:49 +0200
committerMaxim Cournoyer <maxim.cournoyer@gmail.com>2025-05-15 11:09:11 +0900
commit30259d52e14ece4d191ef9febf27cccddacf460f (patch)
tree0984e905298f045ce5a99e022e16915450cb8929
parent92058c15c807ae67a2062938466db0a4c673d165 (diff)
services: oci-container-configuration: Move to (gnu services containers).
This patch moves the oci-container-configuration and related configuration records to (gnu services containers). Public symbols are still exported for backwards compatibility but since the oci-container-service-type will be deprecated in favor of the more general oci-service-type, everything is moved outside of the docker related module. * gnu/services/docker.scm: Move everything related to oci-container-configuration to... * gnu/services/containers.scm: ...here.scm. Change-Id: Iae599dd5cc7442eb632f0c1b3b12f6b928397ae7 Signed-off-by: Maxim Cournoyer <maxim.cournoyer@gmail.com>
-rw-r--r--gnu/services/containers.scm549
-rw-r--r--gnu/services/docker.scm577
2 files changed, 584 insertions, 542 deletions
diff --git a/gnu/services/containers.scm b/gnu/services/containers.scm
index d5a211765a6..24f31c756b8 100644
--- a/gnu/services/containers.scm
+++ b/gnu/services/containers.scm
@@ -1,5 +1,5 @@
1;;; GNU Guix --- Functional package management for GNU 1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2024 Giacomo Leidi <goodoldpaul@autistici.org> 2;;; Copyright © 2024, 2025 Giacomo Leidi <goodoldpaul@autistici.org>
3;;; 3;;;
4;;; This file is part of GNU Guix. 4;;; This file is part of GNU Guix.
5;;; 5;;;
@@ -17,19 +17,31 @@
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. 17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18 18
19(define-module (gnu services containers) 19(define-module (gnu services containers)
20 #:use-module (gnu image)
21 #:use-module (gnu packages admin)
20 #:use-module (gnu packages bash) 22 #:use-module (gnu packages bash)
21 #:use-module (gnu packages containers) 23 #:use-module (gnu packages containers)
24 #:use-module (gnu packages docker)
22 #:use-module (gnu packages file-systems) 25 #:use-module (gnu packages file-systems)
23 #:use-module (gnu services) 26 #:use-module (gnu services)
24 #:use-module (gnu services base) 27 #:use-module (gnu services base)
25 #:use-module (gnu services configuration) 28 #:use-module (gnu services configuration)
26 #:use-module (gnu services shepherd) 29 #:use-module (gnu services shepherd)
30 #:use-module (gnu system)
27 #:use-module (gnu system accounts) 31 #:use-module (gnu system accounts)
32 #:use-module (gnu system image)
28 #:use-module (gnu system shadow) 33 #:use-module (gnu system shadow)
29 #:use-module (gnu system pam) 34 #:use-module (gnu system pam)
35 #:use-module (guix diagnostics)
30 #:use-module (guix gexp) 36 #:use-module (guix gexp)
37 #:use-module (guix i18n)
38 #:use-module (guix monads)
31 #:use-module (guix packages) 39 #:use-module (guix packages)
40 #:use-module (guix profiles)
41 #:use-module ((guix scripts pack) #:prefix pack:)
42 #:use-module (guix store)
32 #:use-module (srfi srfi-1) 43 #:use-module (srfi srfi-1)
44 #:use-module (ice-9 match)
33 #:export (rootless-podman-configuration 45 #:export (rootless-podman-configuration
34 rootless-podman-configuration? 46 rootless-podman-configuration?
35 rootless-podman-configuration-fields 47 rootless-podman-configuration-fields
@@ -48,7 +60,44 @@
48 rootless-podman-shepherd-services 60 rootless-podman-shepherd-services
49 rootless-podman-service-etc 61 rootless-podman-service-etc
50 62
51 rootless-podman-service-type)) 63 rootless-podman-service-type
64
65 oci-image
66 oci-image?
67 oci-image-fields
68 oci-image-repository
69 oci-image-tag
70 oci-image-value
71 oci-image-pack-options
72 oci-image-target
73 oci-image-system
74 oci-image-grafts?
75
76 oci-container-configuration
77 oci-container-configuration?
78 oci-container-configuration-fields
79 oci-container-configuration-user
80 oci-container-configuration-group
81 oci-container-configuration-command
82 oci-container-configuration-entrypoint
83 oci-container-configuration-host-environment
84 oci-container-configuration-environment
85 oci-container-configuration-image
86 oci-container-configuration-provision
87 oci-container-configuration-requirement
88 oci-container-configuration-log-file
89 oci-container-configuration-auto-start?
90 oci-container-configuration-respawn?
91 oci-container-configuration-shepherd-actions
92 oci-container-configuration-network
93 oci-container-configuration-ports
94 oci-container-configuration-volumes
95 oci-container-configuration-container-user
96 oci-container-configuration-workdir
97 oci-container-configuration-extra-arguments
98
99 oci-container-shepherd-service
100 %oci-container-accounts))
52 101
53(define (gexp-or-string? value) 102(define (gexp-or-string? value)
54 (or (gexp? value) 103 (or (gexp? value)
@@ -190,7 +239,7 @@ available for each configured user."))
190 rootless-podman-shared-root-fs)) 239 rootless-podman-shared-root-fs))
191 (one-shot? #t) 240 (one-shot? #t)
192 (documentation 241 (documentation
193 "Allow setting cgroups limits: cpu, cpuset, memory and 242 "Allow setting cgroups limits: cpu, cpuset, io, memory and
194pids.") 243pids.")
195 (start 244 (start
196 #~(make-forkexec-constructor 245 #~(make-forkexec-constructor
@@ -244,3 +293,497 @@ to be shared. This service sets it so.")
244 (default-value (rootless-podman-configuration)) 293 (default-value (rootless-podman-configuration))
245 (description 294 (description
246 "This service configures rootless @code{podman} on the Guix System."))) 295 "This service configures rootless @code{podman} on the Guix System.")))
296
297
298;;;
299;;; OCI container.
300;;;
301
302(define (oci-sanitize-pair pair delimiter)
303 (define (valid? member)
304 (or (string? member)
305 (gexp? member)
306 (file-like? member)))
307 (match pair
308 (((? valid? key) . (? valid? value))
309 #~(string-append #$key #$delimiter #$value))
310 (_
311 (raise
312 (formatted-message
313 (G_ "pair members must contain only strings, gexps or file-like objects
314but ~a was found")
315 pair)))))
316
317(define (oci-sanitize-mixed-list name value delimiter)
318 (map
319 (lambda (el)
320 (cond ((string? el) el)
321 ((pair? el) (oci-sanitize-pair el delimiter))
322 (else
323 (raise
324 (formatted-message
325 (G_ "~a members must be either a string or a pair but ~a was
326found!")
327 name el)))))
328 value))
329
330(define (oci-sanitize-host-environment value)
331 ;; Expected spec format:
332 ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
333 (oci-sanitize-mixed-list "host-environment" value "="))
334
335(define (oci-sanitize-environment value)
336 ;; Expected spec format:
337 ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
338 (oci-sanitize-mixed-list "environment" value "="))
339
340(define (oci-sanitize-ports value)
341 ;; Expected spec format:
342 ;; '(("8088" . "80") "2022:22")
343 (oci-sanitize-mixed-list "ports" value ":"))
344
345(define (oci-sanitize-volumes value)
346 ;; Expected spec format:
347 ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
348 (oci-sanitize-mixed-list "volumes" value ":"))
349
350(define (oci-sanitize-shepherd-actions value)
351 (map
352 (lambda (el)
353 (if (shepherd-action? el)
354 el
355 (raise
356 (formatted-message
357 (G_ "shepherd-actions may only be shepherd-action records
358but ~a was found") el))))
359 value))
360
361(define (oci-sanitize-extra-arguments value)
362 (define (valid? member)
363 (or (string? member)
364 (gexp? member)
365 (file-like? member)))
366 (map
367 (lambda (el)
368 (if (valid? el)
369 el
370 (raise
371 (formatted-message
372 (G_ "extra arguments may only be strings, gexps or file-like objects
373but ~a was found") el))))
374 value))
375
376(define (oci-image-reference image)
377 (if (string? image)
378 image
379 (string-append (oci-image-repository image)
380 ":" (oci-image-tag image))))
381
382(define (oci-lowerable-image? image)
383 (or (manifest? image)
384 (operating-system? image)
385 (gexp? image)
386 (file-like? image)))
387
388(define (string-or-oci-image? image)
389 (or (string? image)
390 (oci-image? image)))
391
392(define list-of-symbols?
393 (list-of symbol?))
394
395(define-maybe/no-serialization string)
396
397(define-configuration/no-serialization oci-image
398 (repository
399 (string)
400 "A string like @code{myregistry.local:5000/testing/test-image} that names
401the OCI image.")
402 (tag
403 (string "latest")
404 "A string representing the OCI image tag. Defaults to @code{latest}.")
405 (value
406 (oci-lowerable-image)
407 "A @code{manifest} or @code{operating-system} record that will be lowered
408into an OCI compatible tarball. Otherwise this field's value can be a gexp
409or a file-like object that evaluates to an OCI compatible tarball.")
410 (pack-options
411 (list '())
412 "An optional set of keyword arguments that will be passed to the
413@code{docker-image} procedure from @code{guix scripts pack}. They can be used
414to replicate @command{guix pack} behavior:
415
416@lisp
417(oci-image
418 (repository \"guile\")
419 (tag \"3\")
420 (manifest (specifications->manifest '(\"guile\")))
421 (pack-options
422 '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
423 #:max-layers 2)))
424@end lisp
425
426If the @code{value} field is an @code{operating-system} record, this field's
427value will be ignored.")
428 (system
429 (maybe-string)
430 "Attempt to build for a given system, e.g. \"i686-linux\"")
431 (target
432 (maybe-string)
433 "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
434 (grafts?
435 (boolean #f)
436 "Whether to allow grafting or not in the pack build."))
437
438(define-configuration/no-serialization oci-container-configuration
439 (user
440 (string "oci-container")
441 "The user under whose authority docker commands will be run.")
442 (group
443 (string "docker")
444 "The group under whose authority docker commands will be run.")
445 (command
446 (list-of-strings '())
447 "Overwrite the default command (@code{CMD}) of the image.")
448 (entrypoint
449 (maybe-string)
450 "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
451 (host-environment
452 (list '())
453 "Set environment variables in the host environment where @command{docker run}
454is invoked. This is especially useful to pass secrets from the host to the
455container without having them on the @command{docker run}'s command line: by
456setting the @code{MYSQL_PASSWORD} on the host and by passing
457@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
458possible to securely set values in the container environment. This field's
459value can be a list of pairs or strings, even mixed:
460
461@lisp
462(list '(\"LANGUAGE\" . \"eo:ca:eu\")
463 \"JAVA_HOME=/opt/java\")
464@end lisp
465
466Pair members can be strings, gexps or file-like objects. Strings are passed
467directly to @code{make-forkexec-constructor}."
468 (sanitizer oci-sanitize-host-environment))
469 (environment
470 (list '())
471 "Set environment variables inside the container. This can be a list of pairs
472or strings, even mixed:
473
474@lisp
475(list '(\"LANGUAGE\" . \"eo:ca:eu\")
476 \"JAVA_HOME=/opt/java\")
477@end lisp
478
479Pair members can be strings, gexps or file-like objects. Strings are passed
480directly to the Docker CLI. You can refer to the
481@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
482documentation for semantics."
483 (sanitizer oci-sanitize-environment))
484 (image
485 (string-or-oci-image)
486 "The image used to build the container. It can be a string or an
487@code{oci-image} record. Strings are resolved by the Docker
488Engine, and follow the usual format
489@code{myregistry.local:5000/testing/test-image:tag}.")
490 (provision
491 (maybe-string)
492 "Set the name of the provisioned Shepherd service.")
493 (requirement
494 (list-of-symbols '())
495 "Set additional Shepherd services dependencies to the provisioned Shepherd
496service.")
497 (log-file
498 (maybe-string)
499 "When @code{log-file} is set, it names the file to which the service’s
500standard output and standard error are redirected. @code{log-file} is created
501if it does not exist, otherwise it is appended to.")
502 (auto-start?
503 (boolean #t)
504 "Whether this service should be started automatically by the Shepherd. If it
505is @code{#f} the service has to be started manually with @command{herd start}.")
506 (respawn?
507 (boolean #f)
508 "Whether to restart the service when it stops, for instance when the
509underlying process dies.")
510 (shepherd-actions
511 (list '())
512 "This is a list of @code{shepherd-action} records defining actions supported
513by the service."
514 (sanitizer oci-sanitize-shepherd-actions))
515 (network
516 (maybe-string)
517 "Set a Docker network for the spawned container.")
518 (ports
519 (list '())
520 "Set the port or port ranges to expose from the spawned container. This can
521be a list of pairs or strings, even mixed:
522
523@lisp
524(list '(\"8080\" . \"80\")
525 \"10443:443\")
526@end lisp
527
528Pair members can be strings, gexps or file-like objects. Strings are passed
529directly to the Docker CLI. You can refer to the
530@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
531documentation for semantics."
532 (sanitizer oci-sanitize-ports))
533 (volumes
534 (list '())
535 "Set volume mappings for the spawned container. This can be a
536list of pairs or strings, even mixed:
537
538@lisp
539(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
540 \"/gnu/store:/gnu/store\")
541@end lisp
542
543Pair members can be strings, gexps or file-like objects. Strings are passed
544directly to the Docker CLI. You can refer to the
545@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
546documentation for semantics."
547 (sanitizer oci-sanitize-volumes))
548 (container-user
549 (maybe-string)
550 "Set the current user inside the spawned container. You can refer to the
551@url{https://docs.docker.com/engine/reference/run/#user,upstream}
552documentation for semantics.")
553 (workdir
554 (maybe-string)
555 "Set the current working for the spawned Shepherd service.
556You can refer to the
557@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
558documentation for semantics.")
559 (extra-arguments
560 (list '())
561 "A list of strings, gexps or file-like objects that will be directly passed
562to the @command{docker run} invokation."
563 (sanitizer oci-sanitize-extra-arguments)))
564
565(define oci-container-configuration->options
566 (lambda (config)
567 (let ((entrypoint
568 (oci-container-configuration-entrypoint config))
569 (network
570 (oci-container-configuration-network config))
571 (user
572 (oci-container-configuration-container-user config))
573 (workdir
574 (oci-container-configuration-workdir config)))
575 (apply append
576 (filter (compose not unspecified?)
577 `(,(if (maybe-value-set? entrypoint)
578 `("--entrypoint" ,entrypoint)
579 '())
580 ,(append-map
581 (lambda (spec)
582 (list "--env" spec))
583 (oci-container-configuration-environment config))
584 ,(if (maybe-value-set? network)
585 `("--network" ,network)
586 '())
587 ,(if (maybe-value-set? user)
588 `("--user" ,user)
589 '())
590 ,(if (maybe-value-set? workdir)
591 `("--workdir" ,workdir)
592 '())
593 ,(append-map
594 (lambda (spec)
595 (list "-p" spec))
596 (oci-container-configuration-ports config))
597 ,(append-map
598 (lambda (spec)
599 (list "-v" spec))
600 (oci-container-configuration-volumes config))))))))
601
602(define* (get-keyword-value args keyword #:key (default #f))
603 (let ((kv (memq keyword args)))
604 (if (and kv (>= (length kv) 2))
605 (cadr kv)
606 default)))
607
608(define (lower-operating-system os target system)
609 (mlet* %store-monad
610 ((tarball
611 (lower-object
612 (system-image (os->image os #:type docker-image-type))
613 system
614 #:target target)))
615 (return tarball)))
616
617(define (lower-manifest name image target system)
618 (define value (oci-image-value image))
619 (define options (oci-image-pack-options image))
620 (define image-reference
621 (oci-image-reference image))
622 (define image-tag
623 (let* ((extra-options
624 (get-keyword-value options #:extra-options))
625 (image-tag-option
626 (and extra-options
627 (get-keyword-value extra-options #:image-tag))))
628 (if image-tag-option
629 '()
630 `(#:extra-options (#:image-tag ,image-reference)))))
631
632 (mlet* %store-monad
633 ((_ (set-grafting
634 (oci-image-grafts? image)))
635 (guile (set-guile-for-build (default-guile)))
636 (profile
637 (profile-derivation value
638 #:target target
639 #:system system
640 #:hooks '()
641 #:locales? #f))
642 (tarball (apply pack:docker-image
643 `(,name ,profile
644 ,@options
645 ,@image-tag
646 #:localstatedir? #t))))
647 (return tarball)))
648
649(define (lower-oci-image name image)
650 (define value (oci-image-value image))
651 (define image-target (oci-image-target image))
652 (define image-system (oci-image-system image))
653 (define target
654 (if (maybe-value-set? image-target)
655 image-target
656 (%current-target-system)))
657 (define system
658 (if (maybe-value-set? image-system)
659 image-system
660 (%current-system)))
661 (with-store store
662 (run-with-store store
663 (match value
664 ((? manifest? value)
665 (lower-manifest name image target system))
666 ((? operating-system? value)
667 (lower-operating-system value target system))
668 ((or (? gexp? value)
669 (? file-like? value))
670 value)
671 (_
672 (raise
673 (formatted-message
674 (G_ "oci-image value must contain only manifest,
675operating-system, gexp or file-like records but ~a was found")
676 value))))
677 #:target target
678 #:system system)))
679
680(define (%oci-image-loader name image tag)
681 (let ((docker (file-append docker-cli "/bin/docker"))
682 (tarball (lower-oci-image name image)))
683 (with-imported-modules '((guix build utils))
684 (program-file (format #f "~a-image-loader" name)
685 #~(begin
686 (use-modules (guix build utils)
687 (ice-9 popen)
688 (ice-9 rdelim))
689
690 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
691 (define line
692 (read-line
693 (open-input-pipe
694 (string-append #$docker " load -i " #$tarball))))
695
696 (unless (or (eof-object? line)
697 (string-null? line))
698 (format #t "~a~%" line)
699 (let ((repository&tag
700 (string-drop line
701 (string-length
702 "Loaded image: "))))
703
704 (invoke #$docker "tag" repository&tag #$tag)
705 (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
706
707(define (oci-container-shepherd-service config)
708 (define (guess-name name image)
709 (if (maybe-value-set? name)
710 name
711 (string-append "docker-"
712 (basename
713 (if (string? image)
714 (first (string-split image #\:))
715 (oci-image-repository image))))))
716
717 (let* ((docker (file-append docker-cli "/bin/docker"))
718 (actions (oci-container-configuration-shepherd-actions config))
719 (auto-start?
720 (oci-container-configuration-auto-start? config))
721 (user (oci-container-configuration-user config))
722 (group (oci-container-configuration-group config))
723 (host-environment
724 (oci-container-configuration-host-environment config))
725 (command (oci-container-configuration-command config))
726 (log-file (oci-container-configuration-log-file config))
727 (provision (oci-container-configuration-provision config))
728 (requirement (oci-container-configuration-requirement config))
729 (respawn?
730 (oci-container-configuration-respawn? config))
731 (image (oci-container-configuration-image config))
732 (image-reference (oci-image-reference image))
733 (options (oci-container-configuration->options config))
734 (name (guess-name provision image))
735 (extra-arguments
736 (oci-container-configuration-extra-arguments config)))
737
738 (shepherd-service (provision `(,(string->symbol name)))
739 (requirement `(dockerd user-processes ,@requirement))
740 (respawn? respawn?)
741 (auto-start? auto-start?)
742 (documentation
743 (string-append
744 "Docker backed Shepherd service for "
745 (if (oci-image? image) name image) "."))
746 (start
747 #~(lambda ()
748 #$@(if (oci-image? image)
749 #~((invoke #$(%oci-image-loader
750 name image image-reference)))
751 #~())
752 (fork+exec-command
753 ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
754 (list #$docker "run" "--rm" "--name" #$name
755 #$@options #$@extra-arguments
756 #$image-reference #$@command)
757 #:user #$user
758 #:group #$group
759 #$@(if (maybe-value-set? log-file)
760 (list #:log-file log-file)
761 '())
762 #:environment-variables
763 (list #$@host-environment))))
764 (stop
765 #~(lambda _
766 (invoke #$docker "rm" "-f" #$name)))
767 (actions
768 (if (oci-image? image)
769 '()
770 (append
771 (list
772 (shepherd-action
773 (name 'pull)
774 (documentation
775 (format #f "Pull ~a's image (~a)."
776 name image))
777 (procedure
778 #~(lambda _
779 (invoke #$docker "pull" #$image)))))
780 actions))))))
781
782(define %oci-container-accounts
783 (list (user-account
784 (name "oci-container")
785 (comment "OCI services account")
786 (group "docker")
787 (system? #t)
788 (home-directory "/var/empty")
789 (shell (file-append shadow "/sbin/nologin")))))
diff --git a/gnu/services/docker.scm b/gnu/services/docker.scm
index 9ab3e583345..828ceea313a 100644
--- a/gnu/services/docker.scm
+++ b/gnu/services/docker.scm
@@ -5,7 +5,7 @@
5;;; Copyright © 2020 Efraim Flashner <efraim@flashner.co.il> 5;;; Copyright © 2020 Efraim Flashner <efraim@flashner.co.il>
6;;; Copyright © 2020 Jesse Dowell <jessedowell@gmail.com> 6;;; Copyright © 2020 Jesse Dowell <jessedowell@gmail.com>
7;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re> 7;;; Copyright © 2021 Brice Waegeneire <brice@waegenei.re>
8;;; Copyright © 2023, 2024 Giacomo Leidi <goodoldpaul@autistici.org> 8;;; Copyright © 2023, 2024, 2025 Giacomo Leidi <goodoldpaul@autistici.org>
9;;; 9;;;
10;;; This file is part of GNU Guix. 10;;; This file is part of GNU Guix.
11;;; 11;;;
@@ -23,72 +23,60 @@
23;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. 23;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
24 24
25(define-module (gnu services docker) 25(define-module (gnu services docker)
26 #:use-module (gnu image)
27 #:use-module (gnu services) 26 #:use-module (gnu services)
28 #:use-module (gnu services configuration) 27 #:use-module (gnu services configuration)
29 #:use-module (gnu services base) 28 #:use-module (gnu services containers)
30 #:use-module (gnu services dbus)
31 #:use-module (gnu services shepherd) 29 #:use-module (gnu services shepherd)
32 #:use-module (gnu system)
33 #:use-module (gnu system image)
34 #:use-module (gnu system privilege) 30 #:use-module (gnu system privilege)
35 #:use-module (gnu system shadow) 31 #:use-module (gnu system shadow)
36 #:use-module (gnu packages admin) ;shadow
37 #:use-module (gnu packages docker) 32 #:use-module (gnu packages docker)
38 #:use-module (gnu packages linux) ;singularity 33 #:use-module (gnu packages linux) ;singularity
39 #:use-module (guix records)
40 #:use-module (guix diagnostics)
41 #:use-module (guix gexp) 34 #:use-module (guix gexp)
42 #:use-module (guix i18n) 35 #:use-module (guix records)
43 #:use-module (guix monads)
44 #:use-module (guix packages)
45 #:use-module (guix profiles)
46 #:use-module ((guix scripts pack) #:prefix pack:)
47 #:use-module (guix store)
48 #:use-module (srfi srfi-1) 36 #:use-module (srfi srfi-1)
49 #:use-module (ice-9 format) 37 #:use-module (ice-9 format)
50 #:use-module (ice-9 match) 38 #:use-module (ice-9 match)
39 #:re-export (oci-image ;for backwards compatibility, until the
40 oci-image? ;oci-container-service-type is fully deprecated
41 oci-image-fields
42 oci-image-repository
43 oci-image-tag
44 oci-image-value
45 oci-image-pack-options
46 oci-image-target
47 oci-image-system
48 oci-image-grafts?
49 oci-container-configuration
50 oci-container-configuration?
51 oci-container-configuration-fields
52 oci-container-configuration-user
53 oci-container-configuration-group
54 oci-container-configuration-command
55 oci-container-configuration-entrypoint
56 oci-container-configuration-host-environment
57 oci-container-configuration-environment
58 oci-container-configuration-image
59 oci-container-configuration-provision
60 oci-container-configuration-requirement
61 oci-container-configuration-log-file
62 oci-container-configuration-auto-start?
63 oci-container-configuration-respawn?
64 oci-container-configuration-shepherd-actions
65 oci-container-configuration-network
66 oci-container-configuration-ports
67 oci-container-configuration-volumes
68 oci-container-configuration-container-user
69 oci-container-configuration-workdir
70 oci-container-configuration-extra-arguments
71 oci-container-shepherd-service
72 %oci-container-accounts)
51 73
52 #:export (containerd-configuration 74 #:export (containerd-configuration
53 containerd-service-type 75 containerd-service-type
54 docker-configuration 76 docker-configuration
55 docker-service-type 77 docker-service-type
56 singularity-service-type 78 singularity-service-type
57 oci-image 79 oci-container-service-type))
58 oci-image?
59 oci-image-fields
60 oci-image-repository
61 oci-image-tag
62 oci-image-value
63 oci-image-pack-options
64 oci-image-target
65 oci-image-system
66 oci-image-grafts?
67 oci-container-configuration
68 oci-container-configuration?
69 oci-container-configuration-fields
70 oci-container-configuration-user
71 oci-container-configuration-group
72 oci-container-configuration-command
73 oci-container-configuration-entrypoint
74 oci-container-configuration-host-environment
75 oci-container-configuration-environment
76 oci-container-configuration-image
77 oci-container-configuration-provision
78 oci-container-configuration-requirement
79 oci-container-configuration-log-file
80 oci-container-configuration-auto-start?
81 oci-container-configuration-respawn?
82 oci-container-configuration-shepherd-actions
83 oci-container-configuration-network
84 oci-container-configuration-ports
85 oci-container-configuration-volumes
86 oci-container-configuration-container-user
87 oci-container-configuration-workdir
88 oci-container-configuration-extra-arguments
89 oci-container-service-type
90 oci-container-shepherd-service
91 %oci-container-accounts))
92 80
93(define-maybe file-like) 81(define-maybe file-like)
94 82
@@ -309,495 +297,6 @@ bundles in Docker containers.")
309;;; OCI container. 297;;; OCI container.
310;;; 298;;;
311 299
312(define (oci-sanitize-pair pair delimiter)
313 (define (valid? member)
314 (or (string? member)
315 (gexp? member)
316 (file-like? member)))
317 (match pair
318 (((? valid? key) . (? valid? value))
319 #~(string-append #$key #$delimiter #$value))
320 (_
321 (raise
322 (formatted-message
323 (G_ "pair members must contain only strings, gexps or file-like objects
324but ~a was found")
325 pair)))))
326
327(define (oci-sanitize-mixed-list name value delimiter)
328 (map
329 (lambda (el)
330 (cond ((string? el) el)
331 ((pair? el) (oci-sanitize-pair el delimiter))
332 (else
333 (raise
334 (formatted-message
335 (G_ "~a members must be either a string or a pair but ~a was
336found!")
337 name el)))))
338 value))
339
340(define (oci-sanitize-host-environment value)
341 ;; Expected spec format:
342 ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
343 (oci-sanitize-mixed-list "host-environment" value "="))
344
345(define (oci-sanitize-environment value)
346 ;; Expected spec format:
347 ;; '(("HOME" . "/home/nobody") "JAVA_HOME=/java")
348 (oci-sanitize-mixed-list "environment" value "="))
349
350(define (oci-sanitize-ports value)
351 ;; Expected spec format:
352 ;; '(("8088" . "80") "2022:22")
353 (oci-sanitize-mixed-list "ports" value ":"))
354
355(define (oci-sanitize-volumes value)
356 ;; Expected spec format:
357 ;; '(("/mnt/dir" . "/dir") "/run/current-system/profile:/java")
358 (oci-sanitize-mixed-list "volumes" value ":"))
359
360(define (oci-sanitize-shepherd-actions value)
361 (map
362 (lambda (el)
363 (if (shepherd-action? el)
364 el
365 (raise
366 (formatted-message
367 (G_ "shepherd-actions may only be shepherd-action records
368but ~a was found") el))))
369 value))
370
371(define (oci-sanitize-extra-arguments value)
372 (define (valid? member)
373 (or (string? member)
374 (gexp? member)
375 (file-like? member)))
376 (map
377 (lambda (el)
378 (if (valid? el)
379 el
380 (raise
381 (formatted-message
382 (G_ "extra arguments may only be strings, gexps or file-like objects
383but ~a was found") el))))
384 value))
385
386(define (oci-image-reference image)
387 (if (string? image)
388 image
389 (string-append (oci-image-repository image)
390 ":" (oci-image-tag image))))
391
392(define (oci-lowerable-image? image)
393 (or (manifest? image)
394 (operating-system? image)
395 (gexp? image)
396 (file-like? image)))
397
398(define (string-or-oci-image? image)
399 (or (string? image)
400 (oci-image? image)))
401
402(define list-of-symbols?
403 (list-of symbol?))
404
405(define-maybe/no-serialization string)
406
407(define-configuration/no-serialization oci-image
408 (repository
409 (string)
410 "A string like @code{myregistry.local:5000/testing/test-image} that names
411the OCI image.")
412 (tag
413 (string "latest")
414 "A string representing the OCI image tag. Defaults to @code{latest}.")
415 (value
416 (oci-lowerable-image)
417 "A @code{manifest} or @code{operating-system} record that will be lowered
418into an OCI compatible tarball. Otherwise this field's value can be a gexp
419or a file-like object that evaluates to an OCI compatible tarball.")
420 (pack-options
421 (list '())
422 "An optional set of keyword arguments that will be passed to the
423@code{docker-image} procedure from @code{guix scripts pack}. They can be used
424to replicate @command{guix pack} behavior:
425
426@lisp
427(oci-image
428 (repository \"guile\")
429 (tag \"3\")
430 (manifest (specifications->manifest '(\"guile\")))
431 (pack-options
432 '(#:symlinks ((\"/bin/guile\" -> \"bin/guile\"))
433 #:max-layers 2)))
434@end lisp
435
436If the @code{value} field is an @code{operating-system} record, this field's
437value will be ignored.")
438 (system
439 (maybe-string)
440 "Attempt to build for a given system, e.g. \"i686-linux\"")
441 (target
442 (maybe-string)
443 "Attempt to cross-build for a given triple, e.g. \"aarch64-linux-gnu\"")
444 (grafts?
445 (boolean #f)
446 "Whether to allow grafting or not in the pack build."))
447
448(define-configuration/no-serialization oci-container-configuration
449 (user
450 (string "oci-container")
451 "The user under whose authority docker commands will be run.")
452 (group
453 (string "docker")
454 "The group under whose authority docker commands will be run.")
455 (command
456 (list-of-strings '())
457 "Overwrite the default command (@code{CMD}) of the image.")
458 (entrypoint
459 (maybe-string)
460 "Overwrite the default entrypoint (@code{ENTRYPOINT}) of the image.")
461 (host-environment
462 (list '())
463 "Set environment variables in the host environment where @command{docker run}
464is invoked. This is especially useful to pass secrets from the host to the
465container without having them on the @command{docker run}'s command line: by
466setting the @code{MYSQL_PASSWORD} on the host and by passing
467@code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is
468possible to securely set values in the container environment. This field's
469value can be a list of pairs or strings, even mixed:
470
471@lisp
472(list '(\"LANGUAGE\" . \"eo:ca:eu\")
473 \"JAVA_HOME=/opt/java\")
474@end lisp
475
476Pair members can be strings, gexps or file-like objects. Strings are passed
477directly to @code{make-forkexec-constructor}."
478 (sanitizer oci-sanitize-host-environment))
479 (environment
480 (list '())
481 "Set environment variables inside the container. This can be a list of pairs
482or strings, even mixed:
483
484@lisp
485(list '(\"LANGUAGE\" . \"eo:ca:eu\")
486 \"JAVA_HOME=/opt/java\")
487@end lisp
488
489Pair members can be strings, gexps or file-like objects. Strings are passed
490directly to the Docker CLI. You can refer to the
491@url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream}
492documentation for semantics."
493 (sanitizer oci-sanitize-environment))
494 (image
495 (string-or-oci-image)
496 "The image used to build the container. It can be a string or an
497@code{oci-image} record. Strings are resolved by the Docker
498Engine, and follow the usual format
499@code{myregistry.local:5000/testing/test-image:tag}.")
500 (provision
501 (maybe-string)
502 "Set the name of the provisioned Shepherd service.")
503 (requirement
504 (list-of-symbols '())
505 "Set additional Shepherd services dependencies to the provisioned Shepherd
506service.")
507 (log-file
508 (maybe-string)
509 "When @code{log-file} is set, it names the file to which the service’s
510standard output and standard error are redirected. @code{log-file} is created
511if it does not exist, otherwise it is appended to.")
512 (auto-start?
513 (boolean #t)
514 "Whether this service should be started automatically by the Shepherd. If it
515is @code{#f} the service has to be started manually with @command{herd start}.")
516 (respawn?
517 (boolean #f)
518 "Whether to restart the service when it stops, for instance when the
519underlying process dies.")
520 (shepherd-actions
521 (list '())
522 "This is a list of @code{shepherd-action} records defining actions supported
523by the service."
524 (sanitizer oci-sanitize-shepherd-actions))
525 (network
526 (maybe-string)
527 "Set a Docker network for the spawned container.")
528 (ports
529 (list '())
530 "Set the port or port ranges to expose from the spawned container. This can
531be a list of pairs or strings, even mixed:
532
533@lisp
534(list '(\"8080\" . \"80\")
535 \"10443:443\")
536@end lisp
537
538Pair members can be strings, gexps or file-like objects. Strings are passed
539directly to the Docker CLI. You can refer to the
540@url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream}
541documentation for semantics."
542 (sanitizer oci-sanitize-ports))
543 (volumes
544 (list '())
545 "Set volume mappings for the spawned container. This can be a
546list of pairs or strings, even mixed:
547
548@lisp
549(list '(\"/root/data/grafana\" . \"/var/lib/grafana\")
550 \"/gnu/store:/gnu/store\")
551@end lisp
552
553Pair members can be strings, gexps or file-like objects. Strings are passed
554directly to the Docker CLI. You can refer to the
555@url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream}
556documentation for semantics."
557 (sanitizer oci-sanitize-volumes))
558 (container-user
559 (maybe-string)
560 "Set the current user inside the spawned container. You can refer to the
561@url{https://docs.docker.com/engine/reference/run/#user,upstream}
562documentation for semantics.")
563 (workdir
564 (maybe-string)
565 "Set the current working for the spawned Shepherd service.
566You can refer to the
567@url{https://docs.docker.com/engine/reference/run/#workdir,upstream}
568documentation for semantics.")
569 (extra-arguments
570 (list '())
571 "A list of strings, gexps or file-like objects that will be directly passed
572to the @command{docker run} invocation."
573 (sanitizer oci-sanitize-extra-arguments)))
574
575(define oci-container-configuration->options
576 (lambda (config)
577 (let ((entrypoint
578 (oci-container-configuration-entrypoint config))
579 (network
580 (oci-container-configuration-network config))
581 (user
582 (oci-container-configuration-container-user config))
583 (workdir
584 (oci-container-configuration-workdir config)))
585 (apply append
586 (filter (compose not unspecified?)
587 `(,(if (maybe-value-set? entrypoint)
588 `("--entrypoint" ,entrypoint)
589 '())
590 ,(append-map
591 (lambda (spec)
592 (list "--env" spec))
593 (oci-container-configuration-environment config))
594 ,(if (maybe-value-set? network)
595 `("--network" ,network)
596 '())
597 ,(if (maybe-value-set? user)
598 `("--user" ,user)
599 '())
600 ,(if (maybe-value-set? workdir)
601 `("--workdir" ,workdir)
602 '())
603 ,(append-map
604 (lambda (spec)
605 (list "-p" spec))
606 (oci-container-configuration-ports config))
607 ,(append-map
608 (lambda (spec)
609 (list "-v" spec))
610 (oci-container-configuration-volumes config))))))))
611
612(define* (get-keyword-value args keyword #:key (default #f))
613 (let ((kv (memq keyword args)))
614 (if (and kv (>= (length kv) 2))
615 (cadr kv)
616 default)))
617
618(define (lower-operating-system os target system)
619 (mlet* %store-monad
620 ((tarball
621 (lower-object
622 (system-image (os->image os #:type docker-image-type))
623 system
624 #:target target)))
625 (return tarball)))
626
627(define (lower-manifest name image target system)
628 (define value (oci-image-value image))
629 (define options (oci-image-pack-options image))
630 (define image-reference
631 (oci-image-reference image))
632 (define image-tag
633 (let* ((extra-options
634 (get-keyword-value options #:extra-options))
635 (image-tag-option
636 (and extra-options
637 (get-keyword-value extra-options #:image-tag))))
638 (if image-tag-option
639 '()
640 `(#:extra-options (#:image-tag ,image-reference)))))
641
642 (mlet* %store-monad
643 ((_ (set-grafting
644 (oci-image-grafts? image)))
645 (guile (set-guile-for-build (default-guile)))
646 (profile
647 (profile-derivation value
648 #:target target
649 #:system system
650 #:hooks '()
651 #:locales? #f))
652 (tarball (apply pack:docker-image
653 `(,name ,profile
654 ,@options
655 ,@image-tag
656 #:localstatedir? #t))))
657 (return tarball)))
658
659(define (lower-oci-image name image)
660 (define value (oci-image-value image))
661 (define image-target (oci-image-target image))
662 (define image-system (oci-image-system image))
663 (define target
664 (if (maybe-value-set? image-target)
665 image-target
666 (%current-target-system)))
667 (define system
668 (if (maybe-value-set? image-system)
669 image-system
670 (%current-system)))
671 (with-store store
672 (run-with-store store
673 (match value
674 ((? manifest? value)
675 (lower-manifest name image target system))
676 ((? operating-system? value)
677 (lower-operating-system value target system))
678 ((or (? gexp? value)
679 (? file-like? value))
680 value)
681 (_
682 (raise
683 (formatted-message
684 (G_ "oci-image value must contain only manifest,
685operating-system, gexp or file-like records but ~a was found")
686 value))))
687 #:target target
688 #:system system)))
689
690(define (%oci-image-loader name image tag)
691 (let ((docker (file-append docker-cli "/bin/docker"))
692 (tarball (lower-oci-image name image)))
693 (with-imported-modules '((guix build utils))
694 (program-file (format #f "~a-image-loader" name)
695 #~(begin
696 (use-modules (guix build utils)
697 (ice-9 popen)
698 (ice-9 rdelim))
699
700 (format #t "Loading image for ~a from ~a...~%" #$name #$tarball)
701 (define line
702 (read-line
703 (open-input-pipe
704 (string-append #$docker " load -i " #$tarball))))
705
706 (unless (or (eof-object? line)
707 (string-null? line))
708 (format #t "~a~%" line)
709 (let ((repository&tag
710 (string-drop line
711 (string-length
712 "Loaded image: "))))
713
714 (invoke #$docker "tag" repository&tag #$tag)
715 (format #t "Tagged ~a with ~a...~%" #$tarball #$tag))))))))
716
717(define (oci-container-shepherd-service config)
718 (define (guess-name name image)
719 (if (maybe-value-set? name)
720 name
721 (string-append "docker-"
722 (basename
723 (if (string? image)
724 (first (string-split image #\:))
725 (oci-image-repository image))))))
726
727 (let* ((docker (file-append docker-cli "/bin/docker"))
728 (actions (oci-container-configuration-shepherd-actions config))
729 (auto-start?
730 (oci-container-configuration-auto-start? config))
731 (user (oci-container-configuration-user config))
732 (group (oci-container-configuration-group config))
733 (host-environment
734 (oci-container-configuration-host-environment config))
735 (command (oci-container-configuration-command config))
736 (log-file (oci-container-configuration-log-file config))
737 (provision (oci-container-configuration-provision config))
738 (requirement (oci-container-configuration-requirement config))
739 (respawn?
740 (oci-container-configuration-respawn? config))
741 (image (oci-container-configuration-image config))
742 (image-reference (oci-image-reference image))
743 (options (oci-container-configuration->options config))
744 (name (guess-name provision image))
745 (extra-arguments
746 (oci-container-configuration-extra-arguments config)))
747
748 (shepherd-service (provision `(,(string->symbol name)))
749 (requirement `(dockerd user-processes ,@requirement))
750 (respawn? respawn?)
751 (auto-start? auto-start?)
752 (documentation
753 (string-append
754 "Docker backed Shepherd service for "
755 (if (oci-image? image) name image) "."))
756 (start
757 #~(lambda ()
758 #$@(if (oci-image? image)
759 #~((invoke #$(%oci-image-loader
760 name image image-reference)))
761 #~())
762 (fork+exec-command
763 ;; docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
764 (list #$docker "run" "--rm" "--name" #$name
765 #$@options #$@extra-arguments
766 #$image-reference #$@command)
767 #:user #$user
768 #:group #$group
769 #$@(if (maybe-value-set? log-file)
770 (list #:log-file log-file)
771 '())
772 #:environment-variables
773 (list #$@host-environment))))
774 (stop
775 #~(lambda _
776 (invoke #$docker "rm" "-f" #$name)))
777 (actions
778 (if (oci-image? image)
779 '()
780 (append
781 (list
782 (shepherd-action
783 (name 'pull)
784 (documentation
785 (format #f "Pull ~a's image (~a)."
786 name image))
787 (procedure
788 #~(lambda _
789 (invoke #$docker "pull" #$image)))))
790 actions))))))
791
792(define %oci-container-accounts
793 (list (user-account
794 (name "oci-container")
795 (comment "OCI services account")
796 (group "docker")
797 (system? #t)
798 (home-directory "/var/empty")
799 (shell (file-append shadow "/sbin/nologin")))))
800
801(define (configs->shepherd-services configs) 300(define (configs->shepherd-services configs)
802 (map oci-container-shepherd-service configs)) 301 (map oci-container-shepherd-service configs))
803 302