diff options
author | David Thompson <davet@gnu.org> | 2015-06-19 08:57:44 -0400 |
---|---|---|
committer | David Thompson <dthompson2@worcester.edu> | 2015-10-25 20:27:19 -0400 |
commit | f535dcbe198e7f88f3b0cd8aa4d7585191b31080 (patch) | |
tree | ecc716aed8764e046fd3ff4b0ccf297686d3df0e /guix | |
parent | 581176c00b424ea6ddbeec38ba5dfaea43b53bcc (diff) |
scripts: environment: Add --container option.
* guix/scripts/system.scm (specification->file-system-mapping): Move from
here...
* guix/ui.scm (specification->file-system-mapping): ... to here.
* guix/scripts/enviroment.scm (show-help): Show help for new options.
(%options): Add --container --network, --expose, and --share options.
(%network-configuration-files): New variable.
(launch-environment, launch-environment/container, requisites*,
inputs->requisites): New procedures.
(guix-environment): Spawn new process in a container when requested.
* doc/guix.texi (Invoking guix environment): Document it.
* tests/guix-environment-container.sh: New file.
* Makefile.am (SH_TESTS): Add it.
Diffstat (limited to 'guix')
-rw-r--r-- | guix/scripts/environment.scm | 276 | ||||
-rw-r--r-- | guix/scripts/system.scm | 13 | ||||
-rw-r--r-- | guix/ui.scm | 19 |
3 files changed, 253 insertions, 55 deletions
diff --git a/guix/scripts/environment.scm b/guix/scripts/environment.scm index 2408420e18..1d21a768dc 100644 --- a/guix/scripts/environment.scm +++ b/guix/scripts/environment.scm @@ -25,13 +25,19 @@ #:use-module (guix profiles) #:use-module (guix search-paths) #:use-module (guix utils) + #:use-module (guix build utils) #:use-module (guix monads) #:use-module ((guix gexp) #:select (lower-inputs)) #:use-module (guix scripts) #:use-module (guix scripts build) + #:use-module (gnu build linux-container) + #:use-module (gnu system linux-container) + #:use-module (gnu system file-systems) #:use-module (gnu packages) + #:use-module (gnu packages bash) #:use-module (ice-9 format) #:use-module (ice-9 match) + #:use-module (ice-9 rdelim) #:use-module (srfi srfi-1) #:use-module (srfi srfi-11) #:use-module (srfi srfi-26) @@ -60,6 +66,12 @@ OUTPUT) tuples." (define %default-shell (or (getenv "SHELL") "/bin/sh")) +(define %network-configuration-files + '("/etc/resolv.conf" + "/etc/nsswitch.conf" + "/etc/services" + "/etc/hosts")) + (define (purify-environment) "Unset almost all environment variables. A small number of variables such as 'HOME' and 'USER' are left untouched." @@ -124,6 +136,18 @@ COMMAND or an interactive shell in that environment.\n")) --search-paths display needed environment variable definitions")) (display (_ " -s, --system=SYSTEM attempt to build for SYSTEM--e.g., \"i686-linux\"")) + (display (_ " + -C, --container run command within an isolated container")) + (display (_ " + -N, --network allow containers to access the network")) + (display (_ " + --share=SPEC for containers, share writable host file system + according to SPEC")) + (display (_ " + --expose=SPEC for containers, expose read-only host file system + according to SPEC")) + (display (_ " + --bootstrap use bootstrap binaries to build the environment")) (newline) (show-build-options-help) (newline) @@ -176,6 +200,25 @@ COMMAND or an interactive shell in that environment.\n")) (lambda (opt name arg result) (alist-cons 'system arg (alist-delete 'system result eq?)))) + (option '(#\C "container") #f #f + (lambda (opt name arg result) + (alist-cons 'container? #t result))) + (option '(#\N "network") #f #f + (lambda (opt name arg result) + (alist-cons 'network? #t result))) + (option '("share") #t #f + (lambda (opt name arg result) + (alist-cons 'file-system-mapping + (specification->file-system-mapping arg #t) + result))) + (option '("expose") #t #f + (lambda (opt name arg result) + (alist-cons 'file-system-mapping + (specification->file-system-mapping arg #f) + result))) + (option '("bootstrap") #f #f + (lambda (opt name arg result) + (alist-cons 'bootstrap? #t result))) %standard-build-options)) (define (pick-all alist key) @@ -231,6 +274,131 @@ OUTPUT) tuples, using the build options in OPTS." (built-derivations derivations) (return derivations)))))))) +(define requisites* (store-lift requisites)) + +(define (inputs->requisites inputs) + "Convert INPUTS, a list of input tuples or store path strings, into a set of +requisite store items i.e. the union closure of all the inputs." + (define (input->requisites input) + (requisites* + (match input + ((drv output) + (derivation->output-path drv output)) + ((drv) + (derivation->output-path drv)) + ((? direct-store-path? path) + path)))) + + (mlet %store-monad ((reqs (sequence %store-monad + (map input->requisites inputs)))) + (return (delete-duplicates (concatenate reqs))))) + +(define exit/status (compose exit status:exit-val)) +(define primitive-exit/status (compose primitive-exit status:exit-val)) + +(define (launch-environment command inputs paths pure?) + "Run COMMAND in a new environment containing INPUTS, using the native search +paths defined by the list PATHS. When PURE?, pre-existing environment +variables are cleared before setting the new ones." + (create-environment inputs paths pure?) + (apply system* command)) + +(define* (launch-environment/container #:key command bash user-mappings + inputs paths network?) + "Run COMMAND within a Linux container. The environment features INPUTS, a +list of derivations to be shared from the host system. Environment variables +are set according to PATHS, a list of native search paths. The global shell +is BASH, a file name for a GNU Bash binary in the store. When NETWORK?, +access to the host system network is permitted. USER-MAPPINGS, a list of file +system mappings, contains the user-specified host file systems to mount inside +the container." + (mlet %store-monad ((reqs (inputs->requisites + (cons (direct-store-path bash) inputs)))) + (return + (let* ((cwd (getcwd)) + ;; Bind-mount all requisite store items, user-specified mappings, + ;; /bin/sh, the current working directory, and possibly networking + ;; configuration files within the container. + (mappings + (append user-mappings + ;; Current working directory. + (list (file-system-mapping + (source cwd) + (target cwd) + (writable? #t))) + ;; When in Rome, do as Nix build.cc does: Automagically + ;; map common network configuration files. + (if network? + (filter-map (lambda (file) + (and (file-exists? file) + (file-system-mapping + (source file) + (target file) + (writable? #f)))) + %network-configuration-files) + '()) + ;; Mappings for the union closure of all inputs. + (map (lambda (dir) + (file-system-mapping + (source dir) + (target dir) + (writable? #f))) + reqs))) + (file-systems (append %container-file-systems + (map mapping->file-system mappings)))) + (exit/status + (call-with-container (map file-system->spec file-systems) + (lambda () + ;; Setup global shell. + (mkdir-p "/bin") + (symlink bash "/bin/sh") + + ;; Setup directory for temporary files. + (mkdir-p "/tmp") + (for-each (lambda (var) + (setenv var "/tmp")) + ;; The same variables as in Nix's 'build.cc'. + '("TMPDIR" "TEMPDIR" "TMP" "TEMP")) + + ;; From Nix build.cc: + ;; + ;; Set HOME to a non-existing path to prevent certain + ;; programs from using /etc/passwd (or NIS, or whatever) + ;; to locate the home directory (for example, wget looks + ;; for ~/.wgetrc). I.e., these tools use /etc/passwd if + ;; HOME is not set, but they will just assume that the + ;; settings file they are looking for does not exist if + ;; HOME is set but points to some non-existing path. + (setenv "HOME" "/homeless-shelter") + + ;; For convenience, start in the user's current working + ;; directory rather than the root directory. + (chdir cwd) + + (primitive-exit/status + ;; A container's environment is already purified, so no need to + ;; request it be purified again. + (launch-environment command inputs paths #f))) + #:namespaces (if network? + (delq 'net %namespaces) ; share host network + %namespaces))))))) + +(define (environment-bash container? bootstrap? system) + "Return a monadic value in the store monad for the version of GNU Bash +needed in the environment for SYSTEM, if any. If CONTAINER? is #f, return #f. +If CONTAINER? and BOOTSTRAP?, return the store path for the bootstrap Bash. +Otherwise, return the derivation for the Bash package." + (with-monad %store-monad + (cond + ((and container? (not bootstrap?)) + (package->derivation bash)) + ;; Use the bootstrap Bash instead. + ((and container? bootstrap?) + (interned-file + (search-bootstrap-binary "bash" system))) + (else + (return #f))))) + (define (parse-args args) "Parse the list of command line arguments ARGS." (define (handle-argument arg result) @@ -248,52 +416,76 @@ OUTPUT) tuples, using the build options in OPTS." ;; Entry point. (define (guix-environment . args) (with-error-handling - (let* ((opts (parse-args args)) - (pure? (assoc-ref opts 'pure)) - (ad-hoc? (assoc-ref opts 'ad-hoc?)) - (command (assoc-ref opts 'exec)) - (packages (pick-all (options/resolve-packages opts) 'package)) - (inputs (if ad-hoc? - (append-map (match-lambda - ((package output) - (package+propagated-inputs package - output))) - packages) - (append-map (compose bag-transitive-inputs - package->bag - first) - packages))) - (paths (delete-duplicates - (cons $PATH - (append-map (match-lambda - ((label (? package? p) _ ...) - (package-native-search-paths p)) - (_ - '())) - inputs)) - eq?))) + (let* ((opts (parse-args args)) + (pure? (assoc-ref opts 'pure)) + (container? (assoc-ref opts 'container?)) + (network? (assoc-ref opts 'network?)) + (ad-hoc? (assoc-ref opts 'ad-hoc?)) + (bootstrap? (assoc-ref opts 'bootstrap?)) + (system (assoc-ref opts 'system)) + (command (assoc-ref opts 'exec)) + (packages (pick-all (options/resolve-packages opts) 'package)) + (mappings (pick-all opts 'file-system-mapping)) + (inputs (if ad-hoc? + (append-map (match-lambda + ((package output) + (package+propagated-inputs package + output))) + packages) + (append-map (compose bag-transitive-inputs + package->bag + first) + packages))) + (paths (delete-duplicates + (cons $PATH + (append-map (match-lambda + ((label (? package? p) _ ...) + (package-native-search-paths p)) + (_ + '())) + inputs)) + eq?))) (with-store store (run-with-store store - (mlet %store-monad ((inputs (lower-inputs - (map (match-lambda + (mlet* %store-monad ((inputs (lower-inputs + (map (match-lambda ((label item) (list item)) ((label item output) (list item output))) - inputs) - #:system (assoc-ref opts 'system)))) + inputs) + #:system system)) + ;; Containers need a Bourne shell at /bin/sh. + (bash (environment-bash container? + bootstrap? + system))) (mbegin %store-monad - ;; First build INPUTS. This is necessary even for - ;; --search-paths. - (build-inputs inputs opts) - (cond ((assoc-ref opts 'dry-run?) - (return #t)) - ((assoc-ref opts 'search-paths) - (show-search-paths inputs paths pure?) - (return #t)) - (else - (create-environment inputs paths pure?) - (return - (exit - (status:exit-val - (apply system* command))))))))))))) + ;; First build the inputs. This is necessary even for + ;; --search-paths. Additionally, we might need to build bash + ;; for a container. + (build-inputs (if (derivation? bash) + `((,bash "out") ,@inputs) + inputs) + opts) + (cond + ((assoc-ref opts 'dry-run?) + (return #t)) + ((assoc-ref opts 'search-paths) + (show-search-paths inputs paths pure?) + (return #t)) + (container? + (let ((bash-binary + (if bootstrap? + bash + (string-append (derivation->output-path bash) + "/bin/sh")))) + (launch-environment/container #:command command + #:bash bash-binary + #:user-mappings mappings + #:inputs inputs + #:paths paths + #:network? network?))) + (else + (return + (exit/status + (launch-environment command inputs paths pure?)))))))))))) diff --git a/guix/scripts/system.scm b/guix/scripts/system.scm index b5da57a9ce..8775267f80 100644 --- a/guix/scripts/system.scm +++ b/guix/scripts/system.scm @@ -488,19 +488,6 @@ Build the operating system declared in FILE according to ACTION.\n")) (newline) (show-bug-report-information)) -(define (specification->file-system-mapping spec writable?) - "Read the SPEC and return the corresponding <file-system-mapping>." - (let ((index (string-index spec #\=))) - (if index - (file-system-mapping - (source (substring spec 0 index)) - (target (substring spec (+ 1 index))) - (writable? writable?)) - (file-system-mapping - (source spec) - (target spec) - (writable? writable?))))) - (define %options ;; Specifications of the command-line options. (cons* (option '(#\h "help") #f #f diff --git a/guix/ui.scm b/guix/ui.scm index fb8121c213..9cc1908e6e 100644 --- a/guix/ui.scm +++ b/guix/ui.scm @@ -34,6 +34,7 @@ #:use-module (guix serialization) #:use-module ((guix build utils) #:select (mkdir-p)) #:use-module ((guix licenses) #:select (license? license-name)) + #:use-module (gnu system file-systems) #:use-module (srfi srfi-1) #:use-module (srfi srfi-11) #:use-module (srfi srfi-19) @@ -80,6 +81,7 @@ string->recutils package->recutils package-specification->name+version+output + specification->file-system-mapping string->generations string->duration run-guix-command @@ -966,6 +968,23 @@ optionally contain a version number and an output name, as in these examples: (package-name->name+version name))) (values name version sub-drv))) +(define (specification->file-system-mapping spec writable?) + "Read the SPEC and return the corresponding <file-system-mapping>. SPEC is +a string of the form \"SOURCE\" or \"SOURCE=TARGET\". The former specifies +that SOURCE from the host should be mounted at SOURCE in the other system. +The latter format specifies that SOURCE from the host should be mounted at +TARGET in the other system." + (let ((index (string-index spec #\=))) + (if index + (file-system-mapping + (source (substring spec 0 index)) + (target (substring spec (+ 1 index))) + (writable? writable?)) + (file-system-mapping + (source spec) + (target spec) + (writable? writable?))))) + ;;; ;;; Command-line option processing. |