summary refs log tree commit diff
diff options
context:
space:
mode:
authorLudovic Courtès <ludo@gnu.org>2017-03-16 18:02:59 +0100
committerLudovic Courtès <ludo@gnu.org>2017-03-16 22:50:15 +0100
commitb1edfbc37f2f008188d91f594b046c5986485e47 (patch)
treee6ae31922409381c2bba08cdcb50d77d373ee3aa
parent2971f39c3330a69f44d1ac97443e42b0f8e0173e (diff)
downloadguix-b1edfbc37f2f008188d91f594b046c5986485e47.tar.gz
pack: Add '--format' option and Docker output support.
* guix/docker.scm: Remove dependency on (guix store) and (guix utils).
Use (guix build store-copy).  Load (json) lazily.
(build-docker-image): Remove #:system.  Add #:closure, #:compressor, and
'image' parameters.  Use 'uname' to determine the architecture.  Remove
use of 'call-with-temporary-directory'.  Use 'read-reference-graph' to
compute ITEMS.  Honor #:compressor.
* guix/scripts/pack.scm (docker-image): New procedure.
(%default-options): Add 'format'.
(%formats): New variable.
(%options, show-help): Add '--format'.
(guix-pack): Honor '--format'.
* guix/scripts/archive.scm: Remove '--format' option.  This reverts
commits 1545a012cb7cd78e25ed99ecee26df457be590e9,
01445711db6771cea6122859c3f717f130359f55, and
03476a23ff2d4175b7d3c808726178f764359bec.
* doc/guix.texi (Invoking guix pack): Document '--format'.
(Invoking guix archive): Remove documentation of '--format'.
-rw-r--r--doc/guix.texi34
-rw-r--r--guix/docker.scm103
-rw-r--r--guix/scripts/archive.scm31
-rw-r--r--guix/scripts/pack.scm95
4 files changed, 161 insertions, 102 deletions
diff --git a/doc/guix.texi b/doc/guix.texi
index 3382ac414e..45d171c52d 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -2435,6 +2435,22 @@ guix pack -S /opt/gnu/bin=bin guile emacs geiser
 @noindent
 That way, users can happily type @file{/opt/gnu/bin/guile} and enjoy.
 
+Alternatively, you can produce a pack in the Docker image format, as
+described in
+@uref{https://github.com/docker/docker/blob/master/image/spec/v1.2.md,
+version 1.2 of the specification}.  This is what the following command
+does:
+
+@example
+guix pack -f docker guile emacs geiser
+@end example
+
+@noindent
+The result is a tarball that can be passed to the @command{docker load}
+command.  See the
+@uref{https://docs.docker.com/engine/reference/commandline/load/, Docker
+documentation} for more information.
+
 Several command-line options allow you to customize your pack:
 
 @table @code
@@ -2537,7 +2553,7 @@ what you should use in this case (@pxref{Invoking guix copy}).
 
 @cindex nar, archive format
 @cindex normalized archive (nar)
-By default archives are stored in the ``normalized archive'' or ``nar'' format, which is
+Archives are stored in the ``normalized archive'' or ``nar'' format, which is
 comparable in spirit to `tar', but with differences
 that make it more appropriate for our purposes.  First, rather than
 recording all Unix metadata for each file, the nar format only mentions
@@ -2553,9 +2569,6 @@ verifies the signature and rejects the import in case of an invalid
 signature or if the signing key is not authorized.
 @c FIXME: Add xref to daemon doc about signatures.
 
-Optionally, archives can be exported as a Docker image in the tar
-archive format using @code{--format=docker}.
-
 The main options are:
 
 @table @code
@@ -2584,19 +2597,6 @@ Read a list of store file names from the standard input, one per line,
 and write on the standard output the subset of these files missing from
 the store.
 
-@item -f
-@item --format=@var{FMT}
-@cindex docker, export
-@cindex export format
-Specify the export format.  Acceptable arguments are @code{nar} and
-@code{docker}.  The default is the nar format.  When the format is
-@code{docker}, recursively export the specified store directory as a
-Docker image in tar archive format, as specified in
-@uref{https://github.com/docker/docker/blob/master/image/spec/v1.2.md,
-version 1.2.0 of the Docker Image Specification}.  Using
-@code{--format=docker} implies @code{--recursive}.  The generated
-archive can be loaded by Docker using @command{docker load}.
-
 @item --generate-key[=@var{parameters}]
 @cindex signing, archives
 Generate a new key pair for the daemon.  This is a prerequisite before
diff --git a/guix/docker.scm b/guix/docker.scm
index 6dabaf25b0..56a0f7ec2b 100644
--- a/guix/docker.scm
+++ b/guix/docker.scm
@@ -1,5 +1,6 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net>
+;;; Copyright © 2017 Ludovic Courtès <ludo@gnu.org>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -18,17 +19,18 @@
 
 (define-module (guix docker)
   #:use-module (guix hash)
-  #:use-module (guix store)
   #:use-module (guix base16)
-  #:use-module (guix utils)
   #:use-module ((guix build utils)
                 #:select (delete-file-recursively
                           with-directory-excursion))
-  #:use-module (json)
+  #:use-module (guix build store-copy)
   #:use-module (rnrs bytevectors)
   #:use-module (ice-9 match)
   #:export (build-docker-image))
 
+;; Load Guile-JSON at run time to simplify the job of 'imported-modules' & co.
+(module-use! (current-module) (resolve-interface '(json)))
+
 ;; Generate a 256-bit identifier in hexadecimal encoding for the Docker image
 ;; containing the closure at PATH.
 (define docker-id
@@ -81,48 +83,55 @@
     (rootfs . ((type . "layers")
                (diff_ids . (,(layer-diff-id layer)))))))
 
-(define* (build-docker-image path #:key system)
-  "Generate a Docker image archive from the given store PATH.  The image
-contains the closure of the given store item."
-  (let ((id (docker-id path))
+(define* (build-docker-image image path #:key closure compressor)
+  "Write to IMAGE a Docker image archive from the given store PATH.  The image
+contains the closure of PATH, as specified in CLOSURE (a file produced by
+#:references-graphs).  Use COMPRESSOR, a command such as '(\"gzip\" \"-9n\"),
+to compress IMAGE."
+  (let ((directory "/tmp/docker-image")           ;temporary working directory
+        (closure (canonicalize-path closure))
+        (id (docker-id path))
         (time (strftime "%FT%TZ" (localtime (current-time))))
-        (name (string-append (getcwd)
-                             "/docker-image-" (basename path) ".tar"))
-        (arch (match system
-                ("x86_64-linux" "amd64")
-                ("i686-linux" "386")
-                ("armhf-linux" "arm")
-                ("mips64el-linux" "mips64le"))))
-    (and (call-with-temporary-directory
-          (lambda (directory)
-            (with-directory-excursion directory
-              ;; Add symlink from /bin to /gnu/store/.../bin
-              (symlink (string-append path "/bin") "bin")
-
-              (mkdir id)
-              (with-directory-excursion id
-                (with-output-to-file "VERSION"
-                  (lambda () (display schema-version)))
-                (with-output-to-file "json"
-                  (lambda () (scm->json (image-description id time))))
-
-                ;; Wrap it up
-                (let ((items (with-store store
-                               (requisites store (list path)))))
-                  (and (zero? (apply system* "tar" "-cf" "layer.tar"
-                                     (cons "../bin" items)))
-                       (delete-file "../bin"))))
-
-              (with-output-to-file "config.json"
-                (lambda ()
-                  (scm->json (config (string-append id "/layer.tar")
-                                     time arch))))
-              (with-output-to-file "manifest.json"
-                (lambda ()
-                  (scm->json (manifest path id))))
-              (with-output-to-file "repositories"
-                (lambda ()
-                  (scm->json (repositories path id)))))
-            (and (zero? (system* "tar" "-C" directory "-cf" name "."))
-                 (begin (delete-file-recursively directory) #t))))
-         name)))
+        (arch (match (utsname:machine (uname))
+                ("x86_64" "amd64")
+                ("i686"   "386")
+                ("armv7l" "arm")
+                ("mips64" "mips64le"))))
+    ;; Make sure we start with a fresh, empty working directory.
+    (mkdir directory)
+
+    (and (with-directory-excursion directory
+           ;; Add symlink from /bin to /gnu/store/.../bin
+           (symlink (string-append path "/bin") "bin")
+
+           (mkdir id)
+           (with-directory-excursion id
+             (with-output-to-file "VERSION"
+               (lambda () (display schema-version)))
+             (with-output-to-file "json"
+               (lambda () (scm->json (image-description id time))))
+
+             ;; Wrap it up
+             (let ((items (call-with-input-file closure
+                            read-reference-graph)))
+               (and (zero? (apply system* "tar" "-cf" "layer.tar"
+                                  (cons "../bin" items)))
+                    (delete-file "../bin"))))
+
+           (with-output-to-file "config.json"
+             (lambda ()
+               (scm->json (config (string-append id "/layer.tar")
+                                  time arch))))
+           (with-output-to-file "manifest.json"
+             (lambda ()
+               (scm->json (manifest path id))))
+           (with-output-to-file "repositories"
+             (lambda ()
+               (scm->json (repositories path id)))))
+
+         (and (zero? (apply system* "tar" "-C" directory "-cf" image
+                            `(,@(if compressor
+                                    (list "-I" (string-join compressor))
+                                    '())
+                              ".")))
+              (begin (delete-file-recursively directory) #t)))))
diff --git a/guix/scripts/archive.scm b/guix/scripts/archive.scm
index cad279fb50..8137455a9d 100644
--- a/guix/scripts/archive.scm
+++ b/guix/scripts/archive.scm
@@ -1,6 +1,5 @@
 ;;; GNU Guix --- Functional package management for GNU
 ;;; Copyright © 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
-;;; Copyright © 2017 Ricardo Wurmus <rekado@elephly.net>
 ;;;
 ;;; This file is part of GNU Guix.
 ;;;
@@ -45,11 +44,6 @@
   #:export (guix-archive
             options->derivations+files))
 
-;; XXX: Use this hack instead of #:autoload to avoid compilation errors.
-;; See <http://bugs.gnu.org/12202>.
-(module-autoload! (current-module)
-                  '(guix docker) '(build-docker-image))
-
 
 ;;;
 ;;; Command-line options.
@@ -57,8 +51,7 @@
 
 (define %default-options
   ;; Alist of default option values.
-  `((format . "nar")
-    (system . ,(%current-system))
+  `((system . ,(%current-system))
     (substitutes? . #t)
     (graft? . #t)
     (max-silent-time . 3600)
@@ -70,8 +63,6 @@ Export/import one or more packages from/to the store.\n"))
   (display (_ "
       --export           export the specified files/packages to stdout"))
   (display (_ "
-      --format=FMT       export files/packages in the specified format FMT"))
-  (display (_ "
   -r, --recursive        combined with '--export', include dependencies"))
   (display (_ "
       --import           import from the archive passed on stdin"))
@@ -126,9 +117,6 @@ Export/import one or more packages from/to the store.\n"))
          (option '("export") #f #f
                  (lambda (opt name arg result)
                    (alist-cons 'export #t result)))
-         (option '(#\f "format") #t #f
-                 (lambda (opt name arg result . rest)
-                   (alist-cons 'format arg result)))
          (option '(#\r "recursive") #f #f
                  (lambda (opt name arg result)
                    (alist-cons 'export-recursive? #t result)))
@@ -258,21 +246,8 @@ resulting archive to the standard output port."
 
     (if (or (assoc-ref opts 'dry-run?)
             (build-derivations store drv))
-        (match (assoc-ref opts 'format)
-          ("nar"
-           (export-paths store files (current-output-port)
-                         #:recursive? (assoc-ref opts 'export-recursive?)))
-          ("docker"
-           (match files
-             ((file)
-              (let ((system (assoc-ref opts 'system)))
-                (format #t "~a\n"
-                        (build-docker-image file #:system system))))
-             (x
-              ;; TODO: Remove this restriction.
-              (leave (_ "only a single item can be exported to Docker~%")))))
-          (format
-           (leave (_ "~a: unknown archive format~%") format)))
+        (export-paths store files (current-output-port)
+                      #:recursive? (assoc-ref opts 'export-recursive?))
         (leave (_ "unable to export the given packages~%")))))
 
 (define (generate-key-pair parameters)
diff --git a/guix/scripts/pack.scm b/guix/scripts/pack.scm
index e422b3cdda..c6f2145c5c 100644
--- a/guix/scripts/pack.scm
+++ b/guix/scripts/pack.scm
@@ -24,6 +24,7 @@
   #:use-module (guix store)
   #:use-module (guix grafts)
   #:use-module (guix monads)
+  #:use-module (guix modules)
   #:use-module (guix packages)
   #:use-module (guix profiles)
   #:use-module (guix derivations)
@@ -32,6 +33,8 @@
   #:use-module (gnu packages compression)
   #:autoload   (gnu packages base) (tar)
   #:autoload   (gnu packages package-management) (guix)
+  #:autoload   (gnu packages gnupg) (libgcrypt)
+  #:autoload   (gnu packages guile) (guile-json)
   #:use-module (srfi srfi-1)
   #:use-module (srfi srfi-9)
   #:use-module (srfi srfi-37)
@@ -177,6 +180,59 @@ added to the pack."
                     build
                     #:references-graphs `(("profile" ,profile))))
 
+(define* (docker-image name profile
+                       #:key deduplicate?
+                       (compressor (first %compressors))
+                       localstatedir?
+                       (symlinks '())
+                       (tar tar))
+  "Return a derivation to construct a Docker image of PROFILE.  The
+image is a tarball conforming to the Docker Image Specification, compressed
+with COMPRESSOR.  It can be passed to 'docker load'."
+  ;; FIXME: Honor SYMLINKS and LOCALSTATEDIR?.
+  (define not-config?
+    (match-lambda
+      (('guix 'config) #f)
+      (('guix rest ...) #t)
+      (('gnu rest ...) #t)
+      (rest #f)))
+
+  (define config
+    ;; (guix config) module for consumption by (guix gcrypt).
+    (scheme-file "gcrypt-config.scm"
+                 #~(begin
+                     (define-module (guix config)
+                       #:export (%libgcrypt))
+
+                     ;; XXX: Work around <http://bugs.gnu.org/15602>.
+                     (eval-when (expand load eval)
+                       (define %libgcrypt
+                         #+(file-append libgcrypt "/lib/libgcrypt"))))))
+
+  (define build
+    (with-imported-modules `(,@(source-module-closure '((guix docker))
+                                                      #:select? not-config?)
+                             ((guix config) => ,config))
+      #~(begin
+          ;; Guile-JSON is required by (guix docker).
+          (add-to-load-path
+           (string-append #$guile-json "/share/guile/site/"
+                          (effective-version)))
+
+          (use-modules (guix docker))
+
+          (setenv "PATH"
+                  (string-append #$tar "/bin:"
+                                 #$(compressor-package compressor) "/bin"))
+
+          (build-docker-image #$output #$profile
+                              #:closure "profile"
+                              #:compressor '#$(compressor-command compressor)))))
+
+  (gexp->derivation (string-append name ".tar."
+                                   (compressor-extension compressor))
+                    build
+                    #:references-graphs `(("profile" ,profile))))
 
 
 ;;;
@@ -185,7 +241,8 @@ added to the pack."
 
 (define %default-options
   ;; Alist of default option values.
-  `((system . ,(%current-system))
+  `((format . tarball)
+    (system . ,(%current-system))
     (substitutes? . #t)
     (graft? . #t)
     (max-silent-time . 3600)
@@ -193,6 +250,11 @@ added to the pack."
     (symlinks . ())
     (compressor . ,(first %compressors))))
 
+(define %formats
+  ;; Supported pack formats.
+  `((tarball . ,self-contained-tarball)
+    (docker  . ,docker-image)))
+
 (define %options
   ;; Specifications of the command-line options.
   (cons* (option '(#\h "help") #f #f
@@ -206,6 +268,9 @@ added to the pack."
          (option '(#\n "dry-run") #f #f
                  (lambda (opt name arg result)
                    (alist-cons 'dry-run? #t (alist-cons 'graft? #f result))))
+         (option '(#\f "format") #t #f
+                 (lambda (opt name arg result)
+                   (alist-cons 'format (string->symbol arg) result)))
          (option '(#\s "system") #t #f
                  (lambda (opt name arg result)
                    (alist-cons 'system arg
@@ -242,6 +307,8 @@ Create a bundle of PACKAGE.\n"))
   (show-transformation-options-help)
   (newline)
   (display (_ "
+  -f, --format=FORMAT    build a pack in the given FORMAT"))
+  (display (_ "
   -s, --system=SYSTEM    attempt to build for SYSTEM--e.g., \"i686-linux\""))
   (display (_ "
   -C, --compression=TOOL compress using TOOL--e.g., \"lzip\""))
@@ -280,8 +347,16 @@ Create a bundle of PACKAGE.\n"))
                                     (specification->package+output spec))
                                 list))
                             specs))
-             (compressor (assoc-ref opts 'compressor))
-             (symlinks   (assoc-ref opts 'symlinks))
+             (pack-format (assoc-ref opts 'format))
+             (name        (string-append (symbol->string pack-format)
+                                         "-pack"))
+             (compressor  (assoc-ref opts 'compressor))
+             (symlinks    (assoc-ref opts 'symlinks))
+             (build-image (match (assq-ref %formats pack-format)
+                            ((? procedure? proc) proc)
+                            (#f
+                             (leave (_ "~a: unknown pack format")
+                                    format))))
              (localstatedir? (assoc-ref opts 'localstatedir?)))
         (with-store store
           ;; Set the build options before we do anything else.
@@ -290,13 +365,13 @@ Create a bundle of PACKAGE.\n"))
           (run-with-store store
             (mlet* %store-monad ((profile (profile-derivation
                                            (packages->manifest packages)))
-                                 (drv (self-contained-tarball "pack" profile
-                                                              #:compressor
-                                                              compressor
-                                                              #:symlinks
-                                                              symlinks
-                                                              #:localstatedir?
-                                                              localstatedir?)))
+                                 (drv (build-image name profile
+                                                   #:compressor
+                                                   compressor
+                                                   #:symlinks
+                                                   symlinks
+                                                   #:localstatedir?
+                                                   localstatedir?)))
               (mbegin %store-monad
                 (show-what-to-build* (list drv)
                                      #:use-substitutes?