publish: Handle '/file' URLs, for content-addressed files.
* guix/scripts/publish.scm (render-content-addressed-file): New procedure. (http-write): Add 'application/octet-stream' case. (make-request-handler): Add /file/NAME/sha256/HASH URLs. * tests/publish.scm ("/file/NAME/sha256/HASH") ("/file/NAME/sha256/INVALID-NIX-BASE32-STRING") ("/file/NAME/sha256/INVALID-HASH"): New tests. * doc/guix.texi (Invoking guix publish): Mention the /file URLs.
This commit is contained in:
parent
260bc60f83
commit
ff6638d112
|
@ -5633,6 +5633,20 @@ archive}), the daemon may download substitutes from it:
|
||||||
guix-daemon --substitute-urls=http://example.org:8080
|
guix-daemon --substitute-urls=http://example.org:8080
|
||||||
@end example
|
@end example
|
||||||
|
|
||||||
|
As a bonus, @command{guix publish} also serves as a content-addressed
|
||||||
|
mirror for source files referenced in @code{origin} records
|
||||||
|
(@pxref{origin Reference}). For instance, assuming @command{guix
|
||||||
|
publish} is running on @code{example.org}, the following URL returns the
|
||||||
|
raw @file{hello-2.10.tar.gz} file with the given SHA256 hash
|
||||||
|
(represented in @code{nix-base32} format, @pxref{Invoking guix hash}):
|
||||||
|
|
||||||
|
@example
|
||||||
|
http://example.org/file/hello-2.10.tar.gz/sha256/0ssi1@dots{}ndq1i
|
||||||
|
@end example
|
||||||
|
|
||||||
|
Obviously, these URLs only work for files that are in the store; in
|
||||||
|
other cases, they return 404 (``Not Found'').
|
||||||
|
|
||||||
The following options are available:
|
The following options are available:
|
||||||
|
|
||||||
@table @code
|
@table @code
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
#:use-module (srfi srfi-9 gnu)
|
#:use-module (srfi srfi-9 gnu)
|
||||||
#:use-module (srfi srfi-19)
|
#:use-module (srfi srfi-19)
|
||||||
#:use-module (srfi srfi-26)
|
#:use-module (srfi srfi-26)
|
||||||
|
#:use-module (srfi srfi-34)
|
||||||
#:use-module (srfi srfi-37)
|
#:use-module (srfi srfi-37)
|
||||||
#:use-module (web http)
|
#:use-module (web http)
|
||||||
#:use-module (web request)
|
#:use-module (web request)
|
||||||
|
@ -49,6 +50,7 @@
|
||||||
#:use-module (guix zlib)
|
#:use-module (guix zlib)
|
||||||
#:use-module (guix ui)
|
#:use-module (guix ui)
|
||||||
#:use-module (guix scripts)
|
#:use-module (guix scripts)
|
||||||
|
#:use-module ((guix build utils) #:select (dump-port))
|
||||||
#:export (guix-publish))
|
#:export (guix-publish))
|
||||||
|
|
||||||
(define (show-help)
|
(define (show-help)
|
||||||
|
@ -308,6 +310,25 @@ appropriate duration."
|
||||||
store-path)
|
store-path)
|
||||||
(not-found request))))
|
(not-found request))))
|
||||||
|
|
||||||
|
(define (render-content-addressed-file store request
|
||||||
|
name algo hash)
|
||||||
|
"Return the content of the result of the fixed-output derivation NAME that
|
||||||
|
has the given HASH of type ALGO."
|
||||||
|
;; TODO: Support other hash algorithms.
|
||||||
|
(if (and (eq? algo 'sha256) (= 32 (bytevector-length hash)))
|
||||||
|
(let ((item (fixed-output-path name hash
|
||||||
|
#:hash-algo algo
|
||||||
|
#:recursive? #f)))
|
||||||
|
(if (valid-path? store item)
|
||||||
|
(values `((content-type . (application/octet-stream
|
||||||
|
(charset . "ISO-8859-1"))))
|
||||||
|
;; XXX: We're not returning the actual contents, deferring
|
||||||
|
;; instead to 'http-write'. This is a hack to work around
|
||||||
|
;; <http://bugs.gnu.org/21093>.
|
||||||
|
item)
|
||||||
|
(not-found request)))
|
||||||
|
(not-found request)))
|
||||||
|
|
||||||
(define extract-narinfo-hash
|
(define extract-narinfo-hash
|
||||||
(let ((regexp (make-regexp "^([a-df-np-sv-z0-9]{32}).narinfo$")))
|
(let ((regexp (make-regexp "^([a-df-np-sv-z0-9]{32}).narinfo$")))
|
||||||
(lambda (str)
|
(lambda (str)
|
||||||
|
@ -398,6 +419,34 @@ blocking."
|
||||||
(swallow-zlib-error
|
(swallow-zlib-error
|
||||||
(close-port port))
|
(close-port port))
|
||||||
(values)))))
|
(values)))))
|
||||||
|
(('application/octet-stream . _)
|
||||||
|
;; Send a raw file in a separate thread.
|
||||||
|
(call-with-new-thread
|
||||||
|
(lambda ()
|
||||||
|
(catch 'system-error
|
||||||
|
(lambda ()
|
||||||
|
(call-with-input-file (utf8->string body)
|
||||||
|
(lambda (input)
|
||||||
|
(let* ((size (stat:size (stat input)))
|
||||||
|
(headers (alist-cons 'content-length size
|
||||||
|
(alist-delete 'content-length
|
||||||
|
(response-headers response)
|
||||||
|
eq?)))
|
||||||
|
(response (write-response (set-field response
|
||||||
|
(response-headers)
|
||||||
|
headers)
|
||||||
|
client))
|
||||||
|
(output (response-port response)))
|
||||||
|
(dump-port input output)
|
||||||
|
(close-port output)
|
||||||
|
(values)))))
|
||||||
|
(lambda args
|
||||||
|
;; If the file was GC'd behind our back, that's fine. Likewise if
|
||||||
|
;; the client closes the connection.
|
||||||
|
(unless (memv (system-error-errno args)
|
||||||
|
(list ENOENT EPIPE ECONNRESET))
|
||||||
|
(apply throw args))
|
||||||
|
(values))))))
|
||||||
(_
|
(_
|
||||||
;; Handle other responses sequentially.
|
;; Handle other responses sequentially.
|
||||||
(%http-write server client response body))))
|
(%http-write server client response body))))
|
||||||
|
@ -450,6 +499,14 @@ blocking."
|
||||||
(_
|
(_
|
||||||
%default-gzip-compression)))
|
%default-gzip-compression)))
|
||||||
(not-found request)))
|
(not-found request)))
|
||||||
|
|
||||||
|
;; /nar/file/NAME/sha256/HASH
|
||||||
|
(("file" name "sha256" hash)
|
||||||
|
(guard (c ((invalid-base32-character? c)
|
||||||
|
(not-found request)))
|
||||||
|
(let ((hash (nix-base32-string->bytevector hash)))
|
||||||
|
(render-content-addressed-file store request
|
||||||
|
name 'sha256 hash))))
|
||||||
(_ (not-found request)))
|
(_ (not-found request)))
|
||||||
(not-found request))))
|
(not-found request))))
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
#:use-module (guix utils)
|
#:use-module (guix utils)
|
||||||
#:use-module (guix hash)
|
#:use-module (guix hash)
|
||||||
#:use-module (guix store)
|
#:use-module (guix store)
|
||||||
|
#:use-module (guix derivations)
|
||||||
|
#:use-module (guix gexp)
|
||||||
#:use-module (guix base32)
|
#:use-module (guix base32)
|
||||||
#:use-module (guix base64)
|
#:use-module (guix base64)
|
||||||
#:use-module ((guix records) #:select (recutils->alist))
|
#:use-module ((guix records) #:select (recutils->alist))
|
||||||
|
@ -210,4 +212,36 @@ References: ~%"
|
||||||
(display "This file is not a valid store item." port)))
|
(display "This file is not a valid store item." port)))
|
||||||
(response-code (http-get (publish-uri (string-append "/nar/invalid"))))))
|
(response-code (http-get (publish-uri (string-append "/nar/invalid"))))))
|
||||||
|
|
||||||
|
(test-equal "/file/NAME/sha256/HASH"
|
||||||
|
"Hello, Guix world!"
|
||||||
|
(let* ((data "Hello, Guix world!")
|
||||||
|
(hash (call-with-input-string data port-sha256))
|
||||||
|
(drv (run-with-store %store
|
||||||
|
(gexp->derivation "the-file.txt"
|
||||||
|
#~(call-with-output-file #$output
|
||||||
|
(lambda (port)
|
||||||
|
(display #$data port)))
|
||||||
|
#:hash-algo 'sha256
|
||||||
|
#:hash hash)))
|
||||||
|
(out (build-derivations %store (list drv))))
|
||||||
|
(utf8->string
|
||||||
|
(http-get-body
|
||||||
|
(publish-uri
|
||||||
|
(string-append "/file/the-file.txt/sha256/"
|
||||||
|
(bytevector->nix-base32-string hash)))))))
|
||||||
|
|
||||||
|
(test-equal "/file/NAME/sha256/INVALID-NIX-BASE32-STRING"
|
||||||
|
404
|
||||||
|
(let ((uri (publish-uri
|
||||||
|
"/file/the-file.txt/sha256/not-a-nix-base32-string")))
|
||||||
|
(response-code (http-get uri))))
|
||||||
|
|
||||||
|
(test-equal "/file/NAME/sha256/INVALID-HASH"
|
||||||
|
404
|
||||||
|
(let ((uri (publish-uri
|
||||||
|
(string-append "/file/the-file.txt/sha256/"
|
||||||
|
(bytevector->nix-base32-string
|
||||||
|
(call-with-input-string "" port-sha256))))))
|
||||||
|
(response-code (http-get uri))))
|
||||||
|
|
||||||
(test-end "publish")
|
(test-end "publish")
|
||||||
|
|
Loading…
Reference in New Issue