diff options
| -rw-r--r-- | doc/guix.texi | 46 | ||||
| -rw-r--r-- | guix/scripts/publish.scm | 197 | ||||
| -rw-r--r-- | tests/publish.scm | 54 |
3 files changed, 280 insertions, 17 deletions
diff --git a/doc/guix.texi b/doc/guix.texi index fd3483ee5d4..bbb2ba732d4 100644 --- a/doc/guix.texi +++ b/doc/guix.texi | |||
| @@ -6522,6 +6522,13 @@ archive}), the daemon may download substitutes from it: | |||
| 6522 | guix-daemon --substitute-urls=http://example.org:8080 | 6522 | guix-daemon --substitute-urls=http://example.org:8080 |
| 6523 | @end example | 6523 | @end example |
| 6524 | 6524 | ||
| 6525 | By default, @command{guix publish} compresses archives on the fly as it | ||
| 6526 | serves them. This ``on-the-fly'' mode is convenient in that it requires | ||
| 6527 | no setup and is immediately available. However, when serving lots of | ||
| 6528 | clients, we recommend using the @option{--cache} option, which enables | ||
| 6529 | caching of the archives before they are sent to clients---see below for | ||
| 6530 | details. | ||
| 6531 | |||
| 6525 | As a bonus, @command{guix publish} also serves as a content-addressed | 6532 | As a bonus, @command{guix publish} also serves as a content-addressed |
| 6526 | mirror for source files referenced in @code{origin} records | 6533 | mirror for source files referenced in @code{origin} records |
| 6527 | (@pxref{origin Reference}). For instance, assuming @command{guix | 6534 | (@pxref{origin Reference}). For instance, assuming @command{guix |
| @@ -6559,10 +6566,43 @@ disable compression. The range 1 to 9 corresponds to different gzip | |||
| 6559 | compression levels: 1 is the fastest, and 9 is the best (CPU-intensive). | 6566 | compression levels: 1 is the fastest, and 9 is the best (CPU-intensive). |
| 6560 | The default is 3. | 6567 | The default is 3. |
| 6561 | 6568 | ||
| 6562 | Compression occurs on the fly and the compressed streams are not | 6569 | Unless @option{--cache} is used, compression occurs on the fly and |
| 6570 | the compressed streams are not | ||
| 6563 | cached. Thus, to reduce load on the machine that runs @command{guix | 6571 | cached. Thus, to reduce load on the machine that runs @command{guix |
| 6564 | publish}, it may be a good idea to choose a low compression level, or to | 6572 | publish}, it may be a good idea to choose a low compression level, to |
| 6565 | run @command{guix publish} behind a caching proxy. | 6573 | run @command{guix publish} behind a caching proxy, or to use |
| 6574 | @option{--cache}. Using @option{--cache} has the advantage that it | ||
| 6575 | allows @command{guix publish} to add @code{Content-Length} HTTP header | ||
| 6576 | to its responses. | ||
| 6577 | |||
| 6578 | @item --cache=@var{directory} | ||
| 6579 | @itemx -c @var{directory} | ||
| 6580 | Cache archives and meta-data (@code{.narinfo} URLs) to @var{directory} | ||
| 6581 | and only serve archives that are in cache. | ||
| 6582 | |||
| 6583 | When this option is omitted, archives and meta-data are created | ||
| 6584 | on-the-fly. This can reduce the available bandwidth, especially when | ||
| 6585 | compression is enabled, since this may become CPU-bound. Another | ||
| 6586 | drawback of the default mode is that the length of archives is not known | ||
| 6587 | in advance, so @command{guix publish} does not add a | ||
| 6588 | @code{Content-Length} HTTP header to its responses, which in turn | ||
| 6589 | prevents clients from knowing the amount of data being downloaded. | ||
| 6590 | |||
| 6591 | Conversely, when @option{--cache} is used, the first request for a store | ||
| 6592 | item (@i{via} a @code{.narinfo} URL) returns 404 and triggers a | ||
| 6593 | background process to @dfn{bake} the archive---computing its | ||
| 6594 | @code{.narinfo} and compressing the archive, if needed. Once the | ||
| 6595 | archive is cached in @var{directory}, subsequent requests succeed and | ||
| 6596 | are served directly from the cache, which guarantees that clients get | ||
| 6597 | the best possible bandwidth. | ||
| 6598 | |||
| 6599 | The ``baking'' process is performed by worker threads. By default, one | ||
| 6600 | thread per CPU core is created, but this can be customized. See | ||
| 6601 | @option{--workers} below. | ||
| 6602 | |||
| 6603 | @item --workers=@var{N} | ||
| 6604 | When @option{--cache} is used, request the allocation of @var{N} worker | ||
| 6605 | threads to ``bake'' archives. | ||
| 6566 | 6606 | ||
| 6567 | @item --ttl=@var{ttl} | 6607 | @item --ttl=@var{ttl} |
| 6568 | Produce @code{Cache-Control} HTTP headers that advertise a time-to-live | 6608 | Produce @code{Cache-Control} HTTP headers that advertise a time-to-live |
diff --git a/guix/scripts/publish.scm b/guix/scripts/publish.scm index f54757b4c90..70d914d60c4 100644 --- a/guix/scripts/publish.scm +++ b/guix/scripts/publish.scm | |||
| @@ -24,6 +24,7 @@ | |||
| 24 | #:use-module (ice-9 match) | 24 | #:use-module (ice-9 match) |
| 25 | #:use-module (ice-9 regex) | 25 | #:use-module (ice-9 regex) |
| 26 | #:use-module (ice-9 rdelim) | 26 | #:use-module (ice-9 rdelim) |
| 27 | #:use-module (ice-9 threads) | ||
| 27 | #:use-module (rnrs bytevectors) | 28 | #:use-module (rnrs bytevectors) |
| 28 | #:use-module (srfi srfi-1) | 29 | #:use-module (srfi srfi-1) |
| 29 | #:use-module (srfi srfi-2) | 30 | #:use-module (srfi srfi-2) |
| @@ -45,13 +46,15 @@ | |||
| 45 | #:use-module (guix hash) | 46 | #:use-module (guix hash) |
| 46 | #:use-module (guix pki) | 47 | #:use-module (guix pki) |
| 47 | #:use-module (guix pk-crypto) | 48 | #:use-module (guix pk-crypto) |
| 49 | #:use-module (guix workers) | ||
| 48 | #:use-module (guix store) | 50 | #:use-module (guix store) |
| 49 | #:use-module ((guix serialization) #:select (write-file)) | 51 | #:use-module ((guix serialization) #:select (write-file)) |
| 50 | #:use-module (guix zlib) | 52 | #:use-module (guix zlib) |
| 51 | #:use-module (guix ui) | 53 | #:use-module (guix ui) |
| 52 | #:use-module (guix scripts) | 54 | #:use-module (guix scripts) |
| 53 | #:use-module ((guix utils) #:select (compressed-file?)) | 55 | #:use-module ((guix utils) |
| 54 | #:use-module ((guix build utils) #:select (dump-port)) | 56 | #:select (with-atomic-file-output compressed-file?)) |
| 57 | #:use-module ((guix build utils) #:select (dump-port mkdir-p)) | ||
| 55 | #:export (%public-key | 58 | #:export (%public-key |
| 56 | %private-key | 59 | %private-key |
| 57 | 60 | ||
| @@ -70,6 +73,10 @@ Publish ~a over HTTP.\n") %store-directory) | |||
| 70 | -C, --compression[=LEVEL] | 73 | -C, --compression[=LEVEL] |
| 71 | compress archives at LEVEL")) | 74 | compress archives at LEVEL")) |
| 72 | (display (_ " | 75 | (display (_ " |
| 76 | -c, --cache=DIRECTORY cache published items to DIRECTORY")) | ||
| 77 | (display (_ " | ||
| 78 | --workers=N use N workers to bake items")) | ||
| 79 | (display (_ " | ||
| 73 | --ttl=TTL announce narinfos can be cached for TTL seconds")) | 80 | --ttl=TTL announce narinfos can be cached for TTL seconds")) |
| 74 | (display (_ " | 81 | (display (_ " |
| 75 | --nar-path=PATH use PATH as the prefix for nar URLs")) | 82 | --nar-path=PATH use PATH as the prefix for nar URLs")) |
| @@ -154,6 +161,13 @@ if ITEM is already compressed." | |||
| 154 | (warning (_ "zlib support is missing; \ | 161 | (warning (_ "zlib support is missing; \ |
| 155 | compression disabled~%")) | 162 | compression disabled~%")) |
| 156 | result)))))) | 163 | result)))))) |
| 164 | (option '(#\c "cache") #t #f | ||
| 165 | (lambda (opt name arg result) | ||
| 166 | (alist-cons 'cache arg result))) | ||
| 167 | (option '("workers") #t #f | ||
| 168 | (lambda (opt name arg result) | ||
| 169 | (alist-cons 'workers (string->number* arg) | ||
| 170 | result))) | ||
| 157 | (option '("ttl") #t #f | 171 | (option '("ttl") #t #f |
| 158 | (lambda (opt name arg result) | 172 | (lambda (opt name arg result) |
| 159 | (let ((duration (string->duration arg))) | 173 | (let ((duration (string->duration arg))) |
| @@ -190,6 +204,9 @@ compression disabled~%")) | |||
| 190 | %default-gzip-compression | 204 | %default-gzip-compression |
| 191 | %no-compression)) | 205 | %no-compression)) |
| 192 | 206 | ||
| 207 | ;; Default number of workers when caching is enabled. | ||
| 208 | (workers . ,(current-processor-count)) | ||
| 209 | |||
| 193 | (address . ,(make-socket-address AF_INET INADDR_ANY 0)) | 210 | (address . ,(make-socket-address AF_INET INADDR_ANY 0)) |
| 194 | (repl . #f))) | 211 | (repl . #f))) |
| 195 | 212 | ||
| @@ -308,6 +325,121 @@ appropriate duration. NAR-PATH specifies the prefix for nar URLs." | |||
| 308 | #:compression compression) | 325 | #:compression compression) |
| 309 | <>))))) | 326 | <>))))) |
| 310 | 327 | ||
| 328 | (define* (nar-cache-file directory item | ||
| 329 | #:key (compression %no-compression)) | ||
| 330 | (string-append directory "/" | ||
| 331 | (symbol->string (compression-type compression)) | ||
| 332 | "/" (basename item) ".nar")) | ||
| 333 | |||
| 334 | (define* (narinfo-cache-file directory item | ||
| 335 | #:key (compression %no-compression)) | ||
| 336 | (string-append directory "/" | ||
| 337 | (symbol->string (compression-type compression)) | ||
| 338 | "/" (basename item) | ||
| 339 | ".narinfo")) | ||
| 340 | |||
| 341 | (define run-single-baker | ||
| 342 | (let ((baking (make-weak-value-hash-table)) | ||
| 343 | (mutex (make-mutex))) | ||
| 344 | (lambda (item thunk) | ||
| 345 | "Run THUNK, which is supposed to bake ITEM, but make sure only one | ||
| 346 | thread is baking ITEM at a given time." | ||
| 347 | (define selected? | ||
| 348 | (with-mutex mutex | ||
| 349 | (and (not (hash-ref baking item)) | ||
| 350 | (begin | ||
| 351 | (hash-set! baking item (current-thread)) | ||
| 352 | #t)))) | ||
| 353 | |||
| 354 | (when selected? | ||
| 355 | (dynamic-wind | ||
| 356 | (const #t) | ||
| 357 | thunk | ||
| 358 | (lambda () | ||
| 359 | (with-mutex mutex | ||
| 360 | (hash-remove! baking item)))))))) | ||
| 361 | |||
| 362 | (define-syntax-rule (single-baker item exp ...) | ||
| 363 | "Bake ITEM by evaluating EXP, but make sure there's only one baker for ITEM | ||
| 364 | at a time." | ||
| 365 | (run-single-baker item (lambda () exp ...))) | ||
| 366 | |||
| 367 | |||
| 368 | (define* (render-narinfo/cached store request hash | ||
| 369 | #:key ttl (compression %no-compression) | ||
| 370 | (nar-path "nar") | ||
| 371 | cache pool) | ||
| 372 | "Respond to the narinfo request for REQUEST. If the narinfo is available in | ||
| 373 | CACHE, then send it; otherwise, return 404 and \"bake\" that nar and narinfo | ||
| 374 | requested using POOL." | ||
| 375 | (let* ((item (hash-part->path store hash)) | ||
| 376 | (compression (actual-compression item compression)) | ||
| 377 | (cached (and (not (string-null? item)) | ||
| 378 | (narinfo-cache-file cache item | ||
| 379 | #:compression compression)))) | ||
| 380 | (cond ((string-null? item) | ||
| 381 | (not-found request)) | ||
| 382 | ((file-exists? cached) | ||
| 383 | ;; Narinfo is in cache, send it. | ||
| 384 | (values `((content-type . (application/x-nix-narinfo)) | ||
| 385 | ,@(if ttl | ||
| 386 | `((cache-control (max-age . ,ttl))) | ||
| 387 | '())) | ||
| 388 | (lambda (port) | ||
| 389 | (display (call-with-input-file cached | ||
| 390 | read-string) | ||
| 391 | port)))) | ||
| 392 | ((valid-path? store item) | ||
| 393 | ;; Nothing in cache: bake the narinfo and nar in the background and | ||
| 394 | ;; return 404. | ||
| 395 | (eventually pool | ||
| 396 | (single-baker item | ||
| 397 | ;; (format #t "baking ~s~%" item) | ||
| 398 | (bake-narinfo+nar cache item | ||
| 399 | #:ttl ttl | ||
| 400 | #:compression compression | ||
| 401 | #:nar-path nar-path))) | ||
| 402 | (not-found request)) | ||
| 403 | (else | ||
| 404 | (not-found request))))) | ||
| 405 | |||
| 406 | (define* (bake-narinfo+nar cache item | ||
| 407 | #:key ttl (compression %no-compression) | ||
| 408 | (nar-path "/nar")) | ||
| 409 | "Write the narinfo and nar for ITEM to CACHE." | ||
| 410 | (let* ((compression (actual-compression item compression)) | ||
| 411 | (nar (nar-cache-file cache item | ||
| 412 | #:compression compression)) | ||
| 413 | (narinfo (narinfo-cache-file cache item | ||
| 414 | #:compression compression))) | ||
| 415 | |||
| 416 | (mkdir-p (dirname nar)) | ||
| 417 | (match (compression-type compression) | ||
| 418 | ('gzip | ||
| 419 | ;; Note: the file port gets closed along with the gzip port. | ||
| 420 | (call-with-gzip-output-port (open-output-file (string-append nar ".tmp")) | ||
| 421 | (lambda (port) | ||
| 422 | (write-file item port)) | ||
| 423 | #:level (compression-level compression)) | ||
| 424 | (rename-file (string-append nar ".tmp") nar)) | ||
| 425 | ('none | ||
| 426 | ;; When compression is disabled, we retrieve files directly from the | ||
| 427 | ;; store; no need to cache them. | ||
| 428 | #t)) | ||
| 429 | |||
| 430 | (mkdir-p (dirname narinfo)) | ||
| 431 | (with-atomic-file-output narinfo | ||
| 432 | (lambda (port) | ||
| 433 | ;; Open a new connection to the store. We cannot reuse the main | ||
| 434 | ;; thread's connection to the store since we would end up sending | ||
| 435 | ;; stuff concurrently on the same channel. | ||
| 436 | (with-store store | ||
| 437 | (display (narinfo-string store item | ||
| 438 | (%private-key) | ||
| 439 | #:nar-path nar-path | ||
| 440 | #:compression compression) | ||
| 441 | port)))))) | ||
| 442 | |||
| 311 | ;; XXX: Declare the 'Guix-Compression' HTTP header, which is in fact for | 443 | ;; XXX: Declare the 'Guix-Compression' HTTP header, which is in fact for |
| 312 | ;; internal consumption: it allows us to pass the compression info to | 444 | ;; internal consumption: it allows us to pass the compression info to |
| 313 | ;; 'http-write', as part of the workaround to <http://bugs.gnu.org/21093>. | 445 | ;; 'http-write', as part of the workaround to <http://bugs.gnu.org/21093>. |
| @@ -339,6 +471,21 @@ appropriate duration. NAR-PATH specifies the prefix for nar URLs." | |||
| 339 | store-path) | 471 | store-path) |
| 340 | (not-found request)))) | 472 | (not-found request)))) |
| 341 | 473 | ||
| 474 | (define* (render-nar/cached store cache request store-item | ||
| 475 | #:key (compression %no-compression)) | ||
| 476 | "Respond to REQUEST with a nar for STORE-ITEM. If the nar is in CACHE, | ||
| 477 | return it; otherwise, return 404." | ||
| 478 | (let ((cached (nar-cache-file cache store-item | ||
| 479 | #:compression compression))) | ||
| 480 | (if (file-exists? cached) | ||
| 481 | (values `((content-type . (application/octet-stream | ||
| 482 | (charset . "ISO-8859-1")))) | ||
| 483 | ;; XXX: We're not returning the actual contents, deferring | ||
| 484 | ;; instead to 'http-write'. This is a hack to work around | ||
| 485 | ;; <http://bugs.gnu.org/21093>. | ||
| 486 | cached) | ||
| 487 | (not-found request)))) | ||
| 488 | |||
| 342 | (define (render-content-addressed-file store request | 489 | (define (render-content-addressed-file store request |
| 343 | name algo hash) | 490 | name algo hash) |
| 344 | "Return the content of the result of the fixed-output derivation NAME that | 491 | "Return the content of the result of the fixed-output derivation NAME that |
| @@ -495,6 +642,7 @@ blocking." | |||
| 495 | 642 | ||
| 496 | (define* (make-request-handler store | 643 | (define* (make-request-handler store |
| 497 | #:key | 644 | #:key |
| 645 | cache pool | ||
| 498 | narinfo-ttl | 646 | narinfo-ttl |
| 499 | (nar-path "nar") | 647 | (nar-path "nar") |
| 500 | (compression %no-compression)) | 648 | (compression %no-compression)) |
| @@ -515,10 +663,17 @@ blocking." | |||
| 515 | (((= extract-narinfo-hash (? string? hash))) | 663 | (((= extract-narinfo-hash (? string? hash))) |
| 516 | ;; TODO: Register roots for HASH that will somehow remain for | 664 | ;; TODO: Register roots for HASH that will somehow remain for |
| 517 | ;; NARINFO-TTL. | 665 | ;; NARINFO-TTL. |
| 518 | (render-narinfo store request hash | 666 | (if cache |
| 519 | #:ttl narinfo-ttl | 667 | (render-narinfo/cached store request hash |
| 520 | #:nar-path nar-path | 668 | #:cache cache |
| 521 | #:compression compression)) | 669 | #:pool pool |
| 670 | #:ttl narinfo-ttl | ||
| 671 | #:nar-path nar-path | ||
| 672 | #:compression compression) | ||
| 673 | (render-narinfo store request hash | ||
| 674 | #:ttl narinfo-ttl | ||
| 675 | #:nar-path nar-path | ||
| 676 | #:compression compression))) | ||
| 522 | ;; /nar/file/NAME/sha256/HASH | 677 | ;; /nar/file/NAME/sha256/HASH |
| 523 | (("file" name "sha256" hash) | 678 | (("file" name "sha256" hash) |
| 524 | (guard (c ((invalid-base32-character? c) | 679 | (guard (c ((invalid-base32-character? c) |
| @@ -534,13 +689,16 @@ blocking." | |||
| 534 | ;; /nar/gzip/<store-item> | 689 | ;; /nar/gzip/<store-item> |
| 535 | ((components ... "gzip" store-item) | 690 | ((components ... "gzip" store-item) |
| 536 | (if (and (nar-path? components) (zlib-available?)) | 691 | (if (and (nar-path? components) (zlib-available?)) |
| 537 | (render-nar store request store-item | 692 | (let ((compression (match compression |
| 538 | #:compression | 693 | (($ <compression> 'gzip) |
| 539 | (match compression | 694 | compression) |
| 540 | (($ <compression> 'gzip) | 695 | (_ |
| 541 | compression) | 696 | %default-gzip-compression)))) |
| 542 | (_ | 697 | (if cache |
| 543 | %default-gzip-compression))) | 698 | (render-nar/cached store cache request store-item |
| 699 | #:compression compression) | ||
| 700 | (render-nar store request store-item | ||
| 701 | #:compression compression))) | ||
| 544 | (not-found request))) | 702 | (not-found request))) |
| 545 | 703 | ||
| 546 | ;; /nar/<store-item> | 704 | ;; /nar/<store-item> |
| @@ -555,8 +713,11 @@ blocking." | |||
| 555 | 713 | ||
| 556 | (define* (run-publish-server socket store | 714 | (define* (run-publish-server socket store |
| 557 | #:key (compression %no-compression) | 715 | #:key (compression %no-compression) |
| 558 | (nar-path "nar") narinfo-ttl) | 716 | (nar-path "nar") narinfo-ttl |
| 717 | cache pool) | ||
| 559 | (run-server (make-request-handler store | 718 | (run-server (make-request-handler store |
| 719 | #:cache cache | ||
| 720 | #:pool pool | ||
| 560 | #:nar-path nar-path | 721 | #:nar-path nar-path |
| 561 | #:narinfo-ttl narinfo-ttl | 722 | #:narinfo-ttl narinfo-ttl |
| 562 | #:compression compression) | 723 | #:compression compression) |
| @@ -606,6 +767,8 @@ blocking." | |||
| 606 | (socket (open-server-socket address)) | 767 | (socket (open-server-socket address)) |
| 607 | (nar-path (assoc-ref opts 'nar-path)) | 768 | (nar-path (assoc-ref opts 'nar-path)) |
| 608 | (repl-port (assoc-ref opts 'repl)) | 769 | (repl-port (assoc-ref opts 'repl)) |
| 770 | (cache (assoc-ref opts 'cache)) | ||
| 771 | (workers (assoc-ref opts 'workers)) | ||
| 609 | 772 | ||
| 610 | ;; Read the key right away so that (1) we fail early on if we can't | 773 | ;; Read the key right away so that (1) we fail early on if we can't |
| 611 | ;; access them, and (2) we can then drop privileges. | 774 | ;; access them, and (2) we can then drop privileges. |
| @@ -631,6 +794,12 @@ consider using the '--user' option!~%"))) | |||
| 631 | (repl:spawn-server (repl:make-tcp-server-socket #:port repl-port))) | 794 | (repl:spawn-server (repl:make-tcp-server-socket #:port repl-port))) |
| 632 | (with-store store | 795 | (with-store store |
| 633 | (run-publish-server socket store | 796 | (run-publish-server socket store |
| 797 | #:cache cache | ||
| 798 | #:pool (and cache (make-pool workers)) | ||
| 634 | #:nar-path nar-path | 799 | #:nar-path nar-path |
| 635 | #:compression compression | 800 | #:compression compression |
| 636 | #:narinfo-ttl ttl)))))) | 801 | #:narinfo-ttl ttl)))))) |
| 802 | |||
| 803 | ;;; Local Variables: | ||
| 804 | ;;; eval: (put 'single-baker 'scheme-indent-function 1) | ||
| 805 | ;;; End: | ||
diff --git a/tests/publish.scm b/tests/publish.scm index ea0f4a34776..233b71ce93a 100644 --- a/tests/publish.scm +++ b/tests/publish.scm | |||
| @@ -314,4 +314,58 @@ References: ~%" | |||
| 314 | (call-with-input-string "" port-sha256)))))) | 314 | (call-with-input-string "" port-sha256)))))) |
| 315 | (response-code (http-get uri)))) | 315 | (response-code (http-get uri)))) |
| 316 | 316 | ||
| 317 | (unless (zlib-available?) | ||
| 318 | (test-skip 1)) | ||
| 319 | (test-equal "with cache" | ||
| 320 | (list #t | ||
| 321 | `(("StorePath" . ,%item) | ||
| 322 | ("URL" . ,(string-append "nar/gzip/" (basename %item))) | ||
| 323 | ("Compression" . "gzip")) | ||
| 324 | 200 ;nar/gzip/… | ||
| 325 | #t ;Content-Length | ||
| 326 | 200) ;nar/… | ||
| 327 | (call-with-temporary-directory | ||
| 328 | (lambda (cache) | ||
| 329 | (define (wait-for-file file) | ||
| 330 | (let loop ((i 20)) | ||
| 331 | (or (file-exists? file) | ||
| 332 | (begin | ||
| 333 | (pk 'wait-for-file file) | ||
| 334 | (sleep 1) | ||
| 335 | (loop (- i 1)))))) | ||
| 336 | |||
| 337 | (let ((thread (with-separate-output-ports | ||
| 338 | (call-with-new-thread | ||
| 339 | (lambda () | ||
| 340 | (guix-publish "--port=6797" "-C2" | ||
| 341 | (string-append "--cache=" cache))))))) | ||
| 342 | (wait-until-ready 6797) | ||
| 343 | (let* ((base "http://localhost:6797/") | ||
| 344 | (part (store-path-hash-part %item)) | ||
| 345 | (url (string-append base part ".narinfo")) | ||
| 346 | (nar-url (string-append base "/nar/gzip/" (basename %item))) | ||
| 347 | (cached (string-append cache "/gzip/" (basename %item) | ||
| 348 | ".narinfo")) | ||
| 349 | (nar (string-append cache "/gzip/" | ||
| 350 | (basename %item) ".nar")) | ||
| 351 | (response (http-get url))) | ||
| 352 | (and (= 404 (response-code response)) | ||
| 353 | (wait-for-file cached) | ||
| 354 | (let ((body (http-get-port url)) | ||
| 355 | (compressed (http-get nar-url)) | ||
| 356 | (uncompressed (http-get (string-append base "nar/" | ||
| 357 | (basename %item))))) | ||
| 358 | (list (file-exists? nar) | ||
| 359 | (filter (lambda (item) | ||
| 360 | (match item | ||
| 361 | (("Compression" . _) #t) | ||
| 362 | (("StorePath" . _) #t) | ||
| 363 | (("URL" . _) #t) | ||
| 364 | (_ #f))) | ||
| 365 | (recutils->alist body)) | ||
| 366 | (response-code compressed) | ||
| 367 | (= (response-content-length compressed) | ||
| 368 | (stat:size (stat nar))) | ||
| 369 | (response-code uncompressed))))))))) | ||
| 370 | |||
| 317 | (test-end "publish") | 371 | (test-end "publish") |
