deploy: Add '--execute'.

* guix/scripts/deploy.scm (show-help, %options): Add '--execute'.
(invoke-command): New procedure.
(guix-deploy): Break arguments at "--" and handle '-x' and associated
command.
* doc/guix.texi (Invoking guix deploy): Document it.
This commit is contained in:
Ludovic Courtès 2022-01-23 22:15:16 +01:00
parent f553de6e0e
commit 5c13484646
No known key found for this signature in database
GPG Key ID: 090B11993D9AEBB5
2 changed files with 127 additions and 8 deletions

@ -36001,6 +36001,30 @@ be accomplished with the following operating system configuration snippet:
For more information regarding the format of the @file{sudoers} file,
consult @command{man sudoers}.
Once you've deployed a system on a set of machines, you may find it
useful to run a command on all of them. The @option{--execute} or
@option{-x} option lets you do that; the example below runs
@command{uname -a} on all the machines listed in the deployment file:
@example
guix deploy @var{file} -x -- uname -a
@end example
One thing you may often need to do after deployment is restart specific
services on all the machines, which you can do like so:
@example
guix deploy @var{file} -x -- herd restart @var{service}
@end example
The @command{guix deploy -x} command returns zero if and only if the
command succeeded on all the machines.
@c FIXME/TODO: Separate the API doc from the CLI doc.
Below are the data types you need to know about when writing a
deployment file.
@deftp {Data Type} machine
This is the data type representing a single machine in a heterogeneous Guix
deployment.

@ -1,7 +1,7 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019 David Thompson <davet@gnu.org>
;;; Copyright © 2019 Jakob L. Kreuze <zerodaysfordays@sdf.org>
;;; Copyright © 2020, 2021 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2020-2022 Ludovic Courtès <ludo@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
@ -24,18 +24,21 @@
#:use-module (guix scripts)
#:use-module (guix scripts build)
#:use-module (guix store)
#:use-module (guix gexp)
#:use-module (guix ui)
#:use-module (guix utils)
#:use-module (guix grafts)
#:use-module (guix status)
#:use-module ((guix status) #:select (with-status-verbosity))
#:use-module (guix diagnostics)
#:use-module (guix i18n)
#:use-module (ice-9 format)
#:use-module (ice-9 match)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-26)
#:use-module (srfi srfi-34)
#:use-module (srfi srfi-35)
#:use-module (srfi srfi-37)
#:use-module (srfi srfi-71)
#:export (guix-deploy))
;;; Commentary:
@ -58,6 +61,9 @@ Perform the deployment specified by FILE.\n"))
-V, --version display version information and exit"))
(newline)
(display (G_ "
-x, --execute execute the following command on all the machines"))
(newline)
(display (G_ "
-v, --verbosity=LEVEL use the given verbosity LEVEL"))
(show-bug-report-information))
@ -70,6 +76,9 @@ Perform the deployment specified by FILE.\n"))
(lambda args
(show-version-and-exit "guix deploy")))
(option '(#\x "execute") #f #f
(lambda (opt name arg result)
(alist-cons 'execute-command? #t result)))
(option '(#\s "system") #t #f
(lambda (opt name arg result)
(alist-cons 'system arg
@ -152,6 +161,74 @@ Perform the deployment specified by FILE.\n"))
(info (G_ "successfully deployed ~a~%")
(machine-display-name machine))))
(define (invoke-command store machine command)
"Invoke COMMAND, a list of strings, on MACHINE. Display its output (if any)
and its error code if it's non-zero. Return true if COMMAND succeeded, false
otherwise."
(define invocation
#~(begin
(use-modules (ice-9 match)
(ice-9 rdelim)
(srfi srfi-11))
(define (spawn . command)
;; Spawn COMMAND; return its PID and an input port to read its
;; standard output and standard error.
(match (pipe)
((input . output)
(match (pipe)
((input . output)
(match (primitive-fork)
(0
(dynamic-wind
(const #t)
(lambda ()
(close-port input)
(dup2 (fileno output) 1)
(dup2 (fileno output) 2)
(apply execlp (car command) command))
(lambda ()
(primitive-exit 127))))
(pid
(close-port output)
(values pid input))))))))
;; XXX: 'open-pipe*' is unsuitable here because it does not capture
;; stderr, so roll our own.
(let-values (((pid pipe) (spawn #$@command)))
(let loop ((lines '()))
(match (read-line pipe 'concat)
((? eof-object?)
(list (cdr (waitpid pid))
(string-concatenate-reverse lines)))
(line
(loop (cons line lines))))))))
(match (run-with-store store
(machine-remote-eval machine invocation))
((code output)
(match code
((? zero?)
(info (G_ "~a: command succeeded~%")
(machine-display-name machine)))
((= status:exit-val code)
(report-error (G_ "~a: command exited with code ~a~%")
(machine-display-name machine) code))
((= status:stop-sig signal)
(report-error (G_ "~a: command stopped with signal ~a~%")
signal))
((= status:term-sig signal)
(report-error (G_ "~a: command terminated with signal ~a~%")
signal)))
(unless (string-null? output)
(info (G_ "command output on ~a:~%")
(machine-display-name machine))
(display output)
(newline))
(zero? code))))
(define-command (guix-deploy . args)
(synopsis "deploy operating systems on a set of machines")
@ -159,14 +236,17 @@ Perform the deployment specified by FILE.\n"))
(alist-cons 'file arg result))
(with-error-handling
(let* ((opts (parse-command-line args %options (list %default-options)
(let* ((args command (break (cut string=? "--" <>) args))
(opts (parse-command-line args %options (list %default-options)
#:argument-handler handle-argument))
(file (assq-ref opts 'file))
(machines (and file (load-source-file file))))
(machines (and file (load-source-file file)))
(execute-command? (assoc-ref opts 'execute-command?)))
(unless file
(leave (G_ "missing deployment file argument~%")))
(show-what-to-deploy machines)
(when (and (pair? command) (not execute-command?))
(leave (G_ "'--' was used by '-x' was not specified~%")))
(with-status-verbosity (assoc-ref opts 'verbosity)
(with-store store
@ -176,6 +256,21 @@ Perform the deployment specified by FILE.\n"))
#:verbosity
(assoc-ref opts 'verbosity))
(parameterize ((%graft? (assq-ref opts 'graft?)))
(map/accumulate-builds store
(cut deploy-machine* store <>)
machines))))))))
(if execute-command?
(match command
(("--" command ..1)
;; Exit with zero unless COMMAND failed on one or more
;; machines.
(exit
(fold (lambda (machine result)
(and (invoke-command store machine command)
result))
#t
machines)))
(_
(leave (G_ "'-x' specified but no command given~%"))))
(begin
(show-what-to-deploy machines)
(map/accumulate-builds store
(cut deploy-machine* store <>)
machines))))))))))