diff options
| author | Giacomo Leidi <goodoldpaul@autistici.org> | 2025-05-05 09:57:49 +0200 |
|---|---|---|
| committer | Maxim Cournoyer <maxim.cournoyer@gmail.com> | 2025-05-15 11:09:11 +0900 |
| commit | 30259d52e14ece4d191ef9febf27cccddacf460f (patch) | |
| tree | 0984e905298f045ce5a99e022e16915450cb8929 /gnu/services | |
| parent | 92058c15c807ae67a2062938466db0a4c673d165 (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>
Diffstat (limited to 'gnu/services')
| -rw-r--r-- | gnu/services/containers.scm | 549 | ||||
| -rw-r--r-- | gnu/services/docker.scm | 577 |
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 |
| 194 | pids.") | 243 | pids.") |
| 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 | ||
| 314 | but ~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 | ||
| 326 | found!") | ||
| 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 | ||
| 358 | but ~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 | ||
| 373 | but ~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 | ||
| 401 | the 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 | ||
| 408 | into an OCI compatible tarball. Otherwise this field's value can be a gexp | ||
| 409 | or 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 | ||
| 414 | to 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 | |||
| 426 | If the @code{value} field is an @code{operating-system} record, this field's | ||
| 427 | value 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} | ||
| 454 | is invoked. This is especially useful to pass secrets from the host to the | ||
| 455 | container without having them on the @command{docker run}'s command line: by | ||
| 456 | setting the @code{MYSQL_PASSWORD} on the host and by passing | ||
| 457 | @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is | ||
| 458 | possible to securely set values in the container environment. This field's | ||
| 459 | value 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 | |||
| 466 | Pair members can be strings, gexps or file-like objects. Strings are passed | ||
| 467 | directly 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 | ||
| 472 | or strings, even mixed: | ||
| 473 | |||
| 474 | @lisp | ||
| 475 | (list '(\"LANGUAGE\" . \"eo:ca:eu\") | ||
| 476 | \"JAVA_HOME=/opt/java\") | ||
| 477 | @end lisp | ||
| 478 | |||
| 479 | Pair members can be strings, gexps or file-like objects. Strings are passed | ||
| 480 | directly to the Docker CLI. You can refer to the | ||
| 481 | @url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream} | ||
| 482 | documentation 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 | ||
| 488 | Engine, 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 | ||
| 496 | service.") | ||
| 497 | (log-file | ||
| 498 | (maybe-string) | ||
| 499 | "When @code{log-file} is set, it names the file to which the service’s | ||
| 500 | standard output and standard error are redirected. @code{log-file} is created | ||
| 501 | if 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 | ||
| 505 | is @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 | ||
| 509 | underlying process dies.") | ||
| 510 | (shepherd-actions | ||
| 511 | (list '()) | ||
| 512 | "This is a list of @code{shepherd-action} records defining actions supported | ||
| 513 | by 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 | ||
| 521 | be a list of pairs or strings, even mixed: | ||
| 522 | |||
| 523 | @lisp | ||
| 524 | (list '(\"8080\" . \"80\") | ||
| 525 | \"10443:443\") | ||
| 526 | @end lisp | ||
| 527 | |||
| 528 | Pair members can be strings, gexps or file-like objects. Strings are passed | ||
| 529 | directly to the Docker CLI. You can refer to the | ||
| 530 | @url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream} | ||
| 531 | documentation for semantics." | ||
| 532 | (sanitizer oci-sanitize-ports)) | ||
| 533 | (volumes | ||
| 534 | (list '()) | ||
| 535 | "Set volume mappings for the spawned container. This can be a | ||
| 536 | list 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 | |||
| 543 | Pair members can be strings, gexps or file-like objects. Strings are passed | ||
| 544 | directly to the Docker CLI. You can refer to the | ||
| 545 | @url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream} | ||
| 546 | documentation 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} | ||
| 552 | documentation for semantics.") | ||
| 553 | (workdir | ||
| 554 | (maybe-string) | ||
| 555 | "Set the current working for the spawned Shepherd service. | ||
| 556 | You can refer to the | ||
| 557 | @url{https://docs.docker.com/engine/reference/run/#workdir,upstream} | ||
| 558 | documentation for semantics.") | ||
| 559 | (extra-arguments | ||
| 560 | (list '()) | ||
| 561 | "A list of strings, gexps or file-like objects that will be directly passed | ||
| 562 | to 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, | ||
| 675 | operating-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 | ||
| 324 | but ~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 | ||
| 336 | found!") | ||
| 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 | ||
| 368 | but ~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 | ||
| 383 | but ~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 | ||
| 411 | the 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 | ||
| 418 | into an OCI compatible tarball. Otherwise this field's value can be a gexp | ||
| 419 | or 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 | ||
| 424 | to 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 | |||
| 436 | If the @code{value} field is an @code{operating-system} record, this field's | ||
| 437 | value 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} | ||
| 464 | is invoked. This is especially useful to pass secrets from the host to the | ||
| 465 | container without having them on the @command{docker run}'s command line: by | ||
| 466 | setting the @code{MYSQL_PASSWORD} on the host and by passing | ||
| 467 | @code{--env MYSQL_PASSWORD} through the @code{extra-arguments} field, it is | ||
| 468 | possible to securely set values in the container environment. This field's | ||
| 469 | value 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 | |||
| 476 | Pair members can be strings, gexps or file-like objects. Strings are passed | ||
| 477 | directly 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 | ||
| 482 | or strings, even mixed: | ||
| 483 | |||
| 484 | @lisp | ||
| 485 | (list '(\"LANGUAGE\" . \"eo:ca:eu\") | ||
| 486 | \"JAVA_HOME=/opt/java\") | ||
| 487 | @end lisp | ||
| 488 | |||
| 489 | Pair members can be strings, gexps or file-like objects. Strings are passed | ||
| 490 | directly to the Docker CLI. You can refer to the | ||
| 491 | @url{https://docs.docker.com/engine/reference/commandline/run/#env,upstream} | ||
| 492 | documentation 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 | ||
| 498 | Engine, 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 | ||
| 506 | service.") | ||
| 507 | (log-file | ||
| 508 | (maybe-string) | ||
| 509 | "When @code{log-file} is set, it names the file to which the service’s | ||
| 510 | standard output and standard error are redirected. @code{log-file} is created | ||
| 511 | if 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 | ||
| 515 | is @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 | ||
| 519 | underlying process dies.") | ||
| 520 | (shepherd-actions | ||
| 521 | (list '()) | ||
| 522 | "This is a list of @code{shepherd-action} records defining actions supported | ||
| 523 | by 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 | ||
| 531 | be a list of pairs or strings, even mixed: | ||
| 532 | |||
| 533 | @lisp | ||
| 534 | (list '(\"8080\" . \"80\") | ||
| 535 | \"10443:443\") | ||
| 536 | @end lisp | ||
| 537 | |||
| 538 | Pair members can be strings, gexps or file-like objects. Strings are passed | ||
| 539 | directly to the Docker CLI. You can refer to the | ||
| 540 | @url{https://docs.docker.com/engine/reference/commandline/run/#publish,upstream} | ||
| 541 | documentation for semantics." | ||
| 542 | (sanitizer oci-sanitize-ports)) | ||
| 543 | (volumes | ||
| 544 | (list '()) | ||
| 545 | "Set volume mappings for the spawned container. This can be a | ||
| 546 | list 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 | |||
| 553 | Pair members can be strings, gexps or file-like objects. Strings are passed | ||
| 554 | directly to the Docker CLI. You can refer to the | ||
| 555 | @url{https://docs.docker.com/engine/reference/commandline/run/#volume,upstream} | ||
| 556 | documentation 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} | ||
| 562 | documentation for semantics.") | ||
| 563 | (workdir | ||
| 564 | (maybe-string) | ||
| 565 | "Set the current working for the spawned Shepherd service. | ||
| 566 | You can refer to the | ||
| 567 | @url{https://docs.docker.com/engine/reference/run/#workdir,upstream} | ||
| 568 | documentation for semantics.") | ||
| 569 | (extra-arguments | ||
| 570 | (list '()) | ||
| 571 | "A list of strings, gexps or file-like objects that will be directly passed | ||
| 572 | to 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, | ||
| 685 | operating-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 | ||
