deploy: Add --roll-back option.

* guix/scripts/deploy.scm (guix-deploy): Add the --roll-back option.
(show-what-to-deploy): Add #:roll-back? argument.
(roll-back-machine*): New function.
(show-help): Document the --roll-back option.
* doc/guix.texi (Invoking guix deploy): Document the --roll-back option.

Change-Id: Ic5084f287aefb2d1d28380ca4ba1c6971cb913e7
This commit is contained in:
Arun Isaac 2025-02-27 19:02:46 +00:00
parent 8b19b14d5c
commit b0d3a38f4c
No known key found for this signature in database
GPG key ID: 2E25EE8B61802BB3
2 changed files with 72 additions and 11 deletions

View file

@ -45249,6 +45249,14 @@ guix deploy @var{file} -x -- herd restart @var{service}
The @command{guix deploy -x} command returns zero if and only if the The @command{guix deploy -x} command returns zero if and only if the
command succeeded on all the machines. command succeeded on all the machines.
You may also wish to roll back configurations on machines to a previous
generation. You can do that using the @option{--roll-back} or
@option{-r} option like so:
@example
guix deploy --roll-back @var{file}
@end example
@c FIXME/TODO: Separate the API doc from the CLI doc. @c FIXME/TODO: Separate the API doc from the CLI doc.
Below are the data types you need to know about when writing a Below are the data types you need to know about when writing a

View file

@ -3,6 +3,7 @@
;;; Copyright © 2019 Jakob L. Kreuze <zerodaysfordays@sdf.org> ;;; Copyright © 2019 Jakob L. Kreuze <zerodaysfordays@sdf.org>
;;; Copyright © 2020-2022 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2020-2022 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2024 Richard Sent <richard@freakingpenguin.com> ;;; Copyright © 2024 Richard Sent <richard@freakingpenguin.com>
;;; Copyright © 2025 Arun Isaac <arunisaac@systemreboot.net>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -63,6 +64,8 @@ Perform the deployment specified by FILE.\n"))
-e, --expression=EXPR deploy the list of machines EXPR evaluates to")) -e, --expression=EXPR deploy the list of machines EXPR evaluates to"))
(newline) (newline)
(display (G_ " (display (G_ "
-r, --roll-back switch to the previous operating system configuration"))
(display (G_ "
-x, --execute execute the following command on all the machines")) -x, --execute execute the following command on all the machines"))
(newline) (newline)
(display (G_ " (display (G_ "
@ -84,6 +87,9 @@ Perform the deployment specified by FILE.\n"))
(option '(#\n "dry-run") #f #f (option '(#\n "dry-run") #f #f
(lambda (opt name arg result) (lambda (opt name arg result)
(alist-cons 'dry-run? #t result))) (alist-cons 'dry-run? #t result)))
(option '(#\r "roll-back") #f #f
(lambda (opt name arg result)
(alist-cons 'roll-back? #t result)))
(option '(#\x "execute") #f #f (option '(#\x "execute") #f #f
(lambda (opt name arg result) (lambda (opt name arg result)
(alist-cons 'execute-command? #t result))) (alist-cons 'execute-command? #t result)))
@ -118,20 +124,32 @@ Perform the deployment specified by FILE.\n"))
environment-modules)))) environment-modules))))
(load* file module))) (load* file module)))
(define* (show-what-to-deploy machines #:key (dry-run? #f)) (define* (show-what-to-deploy machines #:key (dry-run? #f) (roll-back? #f))
"Show the list of machines to deploy, MACHINES." "Show the list of machines in MACHINES to deploy or roll back."
(let ((count (length machines))) (let ((count (length machines)))
(if dry-run? (if dry-run?
(format (current-error-port) (if roll-back?
(N_ "The following ~d machine would be deployed:~%" (format (current-error-port)
"The following ~d machines would be deployed:~%" (N_ "The following ~d machine would be rolled back:~%"
"The following ~d machines would be rolled back:~%"
count) count)
count) count)
(format (current-error-port) (format (current-error-port)
(N_ "The following ~d machine will be deployed:~%" (N_ "The following ~d machine would be deployed:~%"
"The following ~d machines will be deployed:~%" "The following ~d machines would be deployed:~%"
count)
count))
(if roll-back?
(format (current-error-port)
(N_ "The following ~d machine will be rolled back:~%"
"The following ~d machines will be rolled back:~%"
count)
count) count)
count)) (format (current-error-port)
(N_ "The following ~d machine will be deployed:~%"
"The following ~d machines will be deployed:~%"
count)
count)))
(display (indented-string (display (indented-string
(fill-paragraph (string-join (map machine-display-name machines) (fill-paragraph (string-join (map machine-display-name machines)
", ") ", ")
@ -175,6 +193,35 @@ Perform the deployment specified by FILE.\n"))
(info (G_ "successfully deployed ~a~%") (info (G_ "successfully deployed ~a~%")
(machine-display-name machine)))) (machine-display-name machine))))
(define (roll-back-machine* store machine)
"Roll back MACHINE, taking care of error handling."
(info (G_ "rolling back ~a...~%")
(machine-display-name machine))
(guard* (c
;; On Guile 3.0, exceptions such as 'unbound-variable' are compound
;; and include a '&message'. However, that message only contains
;; the format string. Thus, special-case it here to avoid
;; displaying a bare format string.
(((exception-predicate &exception-with-kind-and-args) c)
(raise c))
((message-condition? c)
(leave (G_ "failed to roll back ~a: ~a~%")
(machine-display-name machine)
(condition-message c)))
((formatted-message? c)
(leave (G_ "failed to roll back ~a: ~a~%")
(machine-display-name machine)
(apply format #f
(gettext (formatted-message-string c)
%gettext-domain)
(formatted-message-arguments c)))))
(run-with-store store (roll-back-machine machine)))
(info (G_ "successfully rolled back ~a~%")
(machine-display-name machine)))
(define (invoke-command store machine command) (define (invoke-command store machine command)
"Invoke COMMAND, a list of strings, on MACHINE. Display its output (if any) "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 and its error code if it's non-zero. Return true if COMMAND succeeded, false
@ -258,6 +305,7 @@ otherwise."
(machines (or (and file (load-source-file file)) (machines (or (and file (load-source-file file))
(and expression (read/eval expression)))) (and expression (read/eval expression))))
(dry-run? (assoc-ref opts 'dry-run?)) (dry-run? (assoc-ref opts 'dry-run?))
(roll-back? (assq-ref opts 'roll-back?))
(execute-command? (assoc-ref opts 'execute-command?))) (execute-command? (assoc-ref opts 'execute-command?)))
(when (and file expression) (when (and file expression)
(leave (G_ "both '--expression' and a deployment file were provided~%"))) (leave (G_ "both '--expression' and a deployment file were provided~%")))
@ -292,8 +340,13 @@ otherwise."
(_ (_
(leave (G_ "'-x' specified but no command given~%")))) (leave (G_ "'-x' specified but no command given~%"))))
(begin (begin
(show-what-to-deploy machines #:dry-run? dry-run?) (show-what-to-deploy machines
#:dry-run? dry-run?
#:roll-back? roll-back?)
(unless dry-run? (unless dry-run?
(map/accumulate-builds store (map/accumulate-builds store
(cut deploy-machine* store <>) (cut (if roll-back?
roll-back-machine*
deploy-machine*)
store <>)
machines))))))))))) machines)))))))))))