| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 |
|
| 21 | |
| 22 | (unsetenv "http_proxy")
|
| 23 |
|
| 24 | (define-module (test-publish)
|
| 25 | #:use-module (guix scripts publish)
|
| 26 | #:use-module (guix tests)
|
| 27 | #:use-module (guix config)
|
| 28 | #:use-module ((guix utils) #:select (call-with-temporary-directory))
|
| 29 | #:use-module ((guix build utils) #:select (call-with-temporary-output-file))
|
| 30 | #:use-module (gcrypt hash)
|
| 31 | #:use-module (guix store)
|
| 32 | #:use-module (guix derivations)
|
| 33 | #:use-module (guix gexp)
|
| 34 | #:use-module (guix base32)
|
| 35 | #:use-module (guix base64)
|
| 36 | #:use-module ((guix records) #:select (recutils->alist))
|
| 37 | #:use-module ((guix serialization) #:select (restore-file))
|
| 38 | #:use-module (gcrypt pk-crypto)
|
| 39 | #:use-module ((guix pki) #:select (%public-key-file %private-key-file))
|
| 40 | #:use-module (zlib)
|
| 41 | #:use-module (lzlib)
|
| 42 | #:autoload (zstd) (call-with-zstd-input-port)
|
| 43 | #:use-module (web uri)
|
| 44 | #:use-module (web client)
|
| 45 | #:use-module (web request)
|
| 46 | #:use-module (web response)
|
| 47 | #:use-module ((guix http-client) #:select (http-multiple-get))
|
| 48 | #:use-module (rnrs bytevectors)
|
| 49 | #:use-module (ice-9 binary-ports)
|
| 50 | #:use-module (srfi srfi-1)
|
| 51 | #:use-module (srfi srfi-26)
|
| 52 | #:use-module (srfi srfi-64)
|
| 53 | #:use-module (srfi srfi-71)
|
| 54 | #:use-module (ice-9 threads)
|
| 55 | #:use-module (ice-9 format)
|
| 56 | #:use-module (ice-9 match)
|
| 57 | #:use-module (ice-9 rdelim))
|
| 58 |
|
| 59 | (define %store
|
| 60 | (open-connection-for-tests))
|
| 61 |
|
| 62 | (define (zstd-supported?)
|
| 63 | (resolve-module '(zstd) #t #f #:ensure #f))
|
| 64 |
|
| 65 | (define %reference (add-text-to-store %store "ref" "foo"))
|
| 66 |
|
| 67 | (define %item (add-text-to-store %store "item" "bar" (list %reference)))
|
| 68 |
|
| 69 | (define (http-get-body uri)
|
| 70 | (call-with-values (lambda () (http-get uri))
|
| 71 | (lambda (response body) body)))
|
| 72 |
|
| 73 | (define (http-get-port uri)
|
| 74 | (let ((socket (open-socket-for-uri uri)))
|
| 75 | |
| 76 | |
| 77 | (setvbuf socket 'none)
|
| 78 | (call-with-values
|
| 79 | (lambda ()
|
| 80 | (http-get uri #:port socket #:streaming? #t))
|
| 81 | (lambda (response port)
|
| 82 | |
| 83 | |
| 84 | port))))
|
| 85 |
|
| 86 | (define (publish-uri route)
|
| 87 | (string-append "http://localhost:6789" route))
|
| 88 |
|
| 89 | (define-syntax-rule (with-separate-output-ports exp ...)
|
| 90 | |
| 91 | |
| 92 | |
| 93 | (with-output-to-port (duplicate-port (current-output-port) "w")
|
| 94 | (lambda ()
|
| 95 | (with-error-to-port (duplicate-port (current-error-port) "w")
|
| 96 | (lambda ()
|
| 97 | exp ...)))))
|
| 98 |
|
| 99 | |
| 100 | |
| 101 | |
| 102 | (define (call-with-temporary-output-filename proc)
|
| 103 | (call-with-temporary-directory
|
| 104 | (lambda (directory)
|
| 105 | (proc (string-append directory "/output")))))
|
| 106 |
|
| 107 | |
| 108 | |
| 109 | |
| 110 | |
| 111 | |
| 112 | |
| 113 | |
| 114 | (let ((max-processors 64))
|
| 115 | (when (> (current-processor-count)
|
| 116 | max-processors)
|
| 117 | (setaffinity
|
| 118 | (getpid)
|
| 119 | (make-bitvector max-processors #t))))
|
| 120 |
|
| 121 | |
| 122 | (with-separate-output-ports
|
| 123 | (call-with-new-thread
|
| 124 | (lambda ()
|
| 125 | (guix-publish "--port=6789" "-C0")))) |
| 126 |
|
| 127 | (define (wait-until-ready port)
|
| 128 | |
| 129 | (let ((conn (socket PF_INET SOCK_STREAM 0)))
|
| 130 | (let loop ()
|
| 131 | (unless (false-if-exception
|
| 132 | (connect conn AF_INET (inet-pton AF_INET "127.0.0.1") port))
|
| 133 | (loop)))))
|
| 134 |
|
| 135 | (define (wait-for-file file)
|
| 136 | |
| 137 | (let loop ((i 20))
|
| 138 | (cond ((file-exists? file)
|
| 139 | #t)
|
| 140 | ((zero? i)
|
| 141 | (error "file didn't show up" file))
|
| 142 | (else
|
| 143 | (pk 'wait-for-file file)
|
| 144 | (sleep 1)
|
| 145 | (loop (- i 1))))))
|
| 146 |
|
| 147 | (define %gzip-magic-bytes
|
| 148 | |
| 149 | #vu8(#x1f #x8b))
|
| 150 |
|
| 151 | |
| 152 | (wait-until-ready 6789)
|
| 153 |
|
| 154 | |
| 155 | (%public-key (read-file-sexp %public-key-file))
|
| 156 | (%private-key (read-file-sexp %private-key-file))
|
| 157 |
|
| 158 |
|
| 159 | (test-begin "publish")
|
| 160 |
|
| 161 | (test-equal "/nix-cache-info"
|
| 162 | (format #f "StoreDir: ~a\nWantMassQuery: 0\nPriority: 100\n"
|
| 163 | %store-directory)
|
| 164 | (http-get-body (publish-uri "/nix-cache-info")))
|
| 165 |
|
| 166 | (test-equal "/*.narinfo"
|
| 167 | (let* ((info (query-path-info %store %item))
|
| 168 | (unsigned-info
|
| 169 | (format #f
|
| 170 | "StorePath: ~a
|
| 171 | NarHash: sha256:~a
|
| 172 | NarSize: ~d
|
| 173 | References: ~a~%"
|
| 174 | %item
|
| 175 | (bytevector->nix-base32-string
|
| 176 | (path-info-hash info))
|
| 177 | (path-info-nar-size info)
|
| 178 | (basename (first (path-info-references info)))))
|
| 179 | (signature (base64-encode
|
| 180 | (string->utf8
|
| 181 | (canonical-sexp->string
|
| 182 | (signed-string unsigned-info))))))
|
| 183 | (format #f "~aSignature: 1;~a;~a
|
| 184 | URL: nar/~a
|
| 185 | Compression: none
|
| 186 | FileSize: ~a\n"
|
| 187 | unsigned-info (gethostname) signature
|
| 188 | (basename %item)
|
| 189 | (path-info-nar-size info)))
|
| 190 | (utf8->string
|
| 191 | (http-get-body
|
| 192 | (publish-uri
|
| 193 | (string-append "/" (store-path-hash-part %item) ".narinfo")))))
|
| 194 |
|
| 195 | (test-equal "/*.narinfo pipeline"
|
| 196 | (make-list 500 200)
|
| 197 | |
| 198 | |
| 199 | (let* ((uri (string->uri (publish-uri
|
| 200 | (string-append "/"
|
| 201 | (store-path-hash-part %item)
|
| 202 | ".narinfo"))))
|
| 203 | (_ expected (http-get uri #:streaming? #f #:decode-body? #f)))
|
| 204 | (http-multiple-get (string->uri (publish-uri ""))
|
| 205 | (lambda (request response port result)
|
| 206 | (and (bytevector=? expected
|
| 207 | (get-bytevector-n port
|
| 208 | (response-content-length
|
| 209 | response)))
|
| 210 | (cons (response-code response) result)))
|
| 211 | '()
|
| 212 | (make-list 500 (build-request uri))
|
| 213 | #:batch-size 77)))
|
| 214 |
|
| 215 | (test-equal "/*.narinfo with properly encoded '+' sign"
|
| 216 | |
| 217 | (let* ((item (add-text-to-store %store "fake-gtk+" "Congrats!"))
|
| 218 | (info (query-path-info %store item))
|
| 219 | (unsigned-info
|
| 220 | (format #f
|
| 221 | "StorePath: ~a
|
| 222 | NarHash: sha256:~a
|
| 223 | NarSize: ~d
|
| 224 | References: ~%"
|
| 225 | item
|
| 226 | (bytevector->nix-base32-string
|
| 227 | (path-info-hash info))
|
| 228 | (path-info-nar-size info)))
|
| 229 | (signature (base64-encode
|
| 230 | (string->utf8
|
| 231 | (canonical-sexp->string
|
| 232 | (signed-string unsigned-info))))))
|
| 233 | (format #f "~aSignature: 1;~a;~a
|
| 234 | URL: nar/~a
|
| 235 | Compression: none
|
| 236 | FileSize: ~a~%"
|
| 237 | unsigned-info (gethostname) signature
|
| 238 | (uri-encode (basename item))
|
| 239 | (path-info-nar-size info)))
|
| 240 |
|
| 241 | (let ((item (add-text-to-store %store "fake-gtk+" "Congrats!")))
|
| 242 | (utf8->string
|
| 243 | (http-get-body
|
| 244 | (publish-uri
|
| 245 | (string-append "/" (store-path-hash-part item) ".narinfo"))))))
|
| 246 |
|
| 247 | (test-equal "/nar/*"
|
| 248 | "bar"
|
| 249 | (call-with-temporary-output-filename
|
| 250 | (lambda (temp)
|
| 251 | (let ((nar (utf8->string
|
| 252 | (http-get-body
|
| 253 | (publish-uri
|
| 254 | (string-append "/nar/" (basename %item)))))))
|
| 255 | (call-with-input-string nar (cut restore-file <> temp)))
|
| 256 | (call-with-input-file temp read-string))))
|
| 257 |
|
| 258 | (test-equal "/nar/gzip/*"
|
| 259 | "bar"
|
| 260 | (call-with-temporary-output-filename
|
| 261 | (lambda (temp)
|
| 262 | (let ((nar (http-get-port
|
| 263 | (publish-uri
|
| 264 | (string-append "/nar/gzip/" (basename %item))))))
|
| 265 | (call-with-zlib-input-port nar
|
| 266 | (cut restore-file <> temp)
|
| 267 | #:format 'gzip))
|
| 268 | (call-with-input-file temp read-string))))
|
| 269 |
|
| 270 | (test-equal "/nar/gzip/* is really gzip"
|
| 271 | %gzip-magic-bytes
|
| 272 | |
| 273 | |
| 274 | (let ((nar (http-get-port
|
| 275 | (publish-uri
|
| 276 | (string-append "/nar/gzip/" (basename %item))))))
|
| 277 | (get-bytevector-n nar (bytevector-length %gzip-magic-bytes))))
|
| 278 |
|
| 279 | (test-equal "/nar/lzip/*"
|
| 280 | "bar"
|
| 281 | (call-with-temporary-output-filename
|
| 282 | (lambda (temp)
|
| 283 | (let ((nar (http-get-port
|
| 284 | (publish-uri
|
| 285 | (string-append "/nar/lzip/" (basename %item))))))
|
| 286 | (call-with-lzip-input-port nar
|
| 287 | (cut restore-file <> temp)))
|
| 288 | (call-with-input-file temp read-string))))
|
| 289 |
|
| 290 | (unless (zstd-supported?) (test-skip 1))
|
| 291 | (test-equal "/nar/zstd/*"
|
| 292 | "bar"
|
| 293 | (call-with-temporary-output-filename
|
| 294 | (lambda (temp)
|
| 295 | (let ((nar (http-get-port
|
| 296 | (publish-uri
|
| 297 | (string-append "/nar/zstd/" (basename %item))))))
|
| 298 | (call-with-zstd-input-port nar
|
| 299 | (cut restore-file <> temp)))
|
| 300 | (call-with-input-file temp read-string))))
|
| 301 |
|
| 302 | (test-equal "/*.narinfo with compression"
|
| 303 | `(("StorePath" . ,%item)
|
| 304 | ("URL" . ,(string-append "nar/gzip/" (basename %item)))
|
| 305 | ("Compression" . "gzip"))
|
| 306 | (let ((thread (with-separate-output-ports
|
| 307 | (call-with-new-thread
|
| 308 | (lambda ()
|
| 309 | (guix-publish "--port=6799" "-C5"))))))
|
| 310 | (wait-until-ready 6799)
|
| 311 | (let* ((url (string-append "http://localhost:6799/"
|
| 312 | (store-path-hash-part %item) ".narinfo"))
|
| 313 | (body (http-get-port url)))
|
| 314 | (filter (lambda (item)
|
| 315 | (match item
|
| 316 | (("Compression" . _) #t)
|
| 317 | (("StorePath" . _) #t)
|
| 318 | (("URL" . _) #t)
|
| 319 | (_ #f)))
|
| 320 | (recutils->alist body)))))
|
| 321 |
|
| 322 | (test-equal "/*.narinfo with lzip compression"
|
| 323 | `(("StorePath" . ,%item)
|
| 324 | ("URL" . ,(string-append "nar/lzip/" (basename %item)))
|
| 325 | ("Compression" . "lzip"))
|
| 326 | (let ((thread (with-separate-output-ports
|
| 327 | (call-with-new-thread
|
| 328 | (lambda ()
|
| 329 | (guix-publish "--port=6790" "-Clzip"))))))
|
| 330 | (wait-until-ready 6790)
|
| 331 | (let* ((url (string-append "http://localhost:6790/"
|
| 332 | (store-path-hash-part %item) ".narinfo"))
|
| 333 | (body (http-get-port url)))
|
| 334 | (filter (lambda (item)
|
| 335 | (match item
|
| 336 | (("Compression" . _) #t)
|
| 337 | (("StorePath" . _) #t)
|
| 338 | (("URL" . _) #t)
|
| 339 | (_ #f)))
|
| 340 | (recutils->alist body)))))
|
| 341 |
|
| 342 | (test-equal "/*.narinfo for a compressed file"
|
| 343 | '("none" "nar") |
| 344 | |
| 345 | (let* ((item (add-text-to-store %store "fake.tar.gz"
|
| 346 | "This is a fake compressed file."))
|
| 347 | (url (string-append "http://localhost:6799/"
|
| 348 | (store-path-hash-part item) ".narinfo"))
|
| 349 | (body (http-get-port url))
|
| 350 | (info (recutils->alist body)))
|
| 351 | (list (assoc-ref info "Compression")
|
| 352 | (dirname (assoc-ref info "URL")))))
|
| 353 |
|
| 354 | (test-equal "/*.narinfo with lzip + gzip"
|
| 355 | `((("StorePath" . ,%item)
|
| 356 | ("URL" . ,(string-append "nar/gzip/" (basename %item)))
|
| 357 | ("Compression" . "gzip")
|
| 358 | ("URL" . ,(string-append "nar/lzip/" (basename %item)))
|
| 359 | ("Compression" . "lzip"))
|
| 360 | 200
|
| 361 | 200)
|
| 362 | (call-with-temporary-directory
|
| 363 | (lambda (cache)
|
| 364 | (let ((thread (with-separate-output-ports
|
| 365 | (call-with-new-thread
|
| 366 | (lambda ()
|
| 367 | (guix-publish "--port=6793" "-Cgzip:2" "-Clzip:2"))))))
|
| 368 | (wait-until-ready 6793)
|
| 369 | (let* ((base "http://localhost:6793/")
|
| 370 | (part (store-path-hash-part %item))
|
| 371 | (url (string-append base part ".narinfo"))
|
| 372 | (body (http-get-port url)))
|
| 373 | (list (filter (match-lambda
|
| 374 | (("StorePath" . _) #t)
|
| 375 | (("URL" . _) #t)
|
| 376 | (("Compression" . _) #t)
|
| 377 | (_ #f))
|
| 378 | (recutils->alist body))
|
| 379 | (response-code
|
| 380 | (http-get (string-append base "nar/gzip/"
|
| 381 | (basename %item))))
|
| 382 | (response-code
|
| 383 | (http-get (string-append base "nar/lzip/"
|
| 384 | (basename %item))))))))))
|
| 385 |
|
| 386 | (test-equal "custom nar path"
|
| 387 | |
| 388 | (list `(("StorePath" . ,%item)
|
| 389 | ("URL" . ,(string-append "foo/bar/chbouib/" (basename %item)))
|
| 390 | ("Compression" . "none"))
|
| 391 | 200
|
| 392 | 404)
|
| 393 | (let ((thread (with-separate-output-ports
|
| 394 | (call-with-new-thread
|
| 395 | (lambda ()
|
| 396 | (guix-publish "--port=6798" "-C0"
|
| 397 | "--nar-path=///foo/bar//chbouib/"))))))
|
| 398 | (wait-until-ready 6798)
|
| 399 | (let* ((base "http://localhost:6798/")
|
| 400 | (part (store-path-hash-part %item))
|
| 401 | (url (string-append base part ".narinfo"))
|
| 402 | (nar-url (string-append base "foo/bar/chbouib/"
|
| 403 | (basename %item)))
|
| 404 | (body (http-get-port url)))
|
| 405 | (list (filter (lambda (item)
|
| 406 | (match item
|
| 407 | (("Compression" . _) #t)
|
| 408 | (("StorePath" . _) #t)
|
| 409 | (("URL" . _) #t)
|
| 410 | (_ #f)))
|
| 411 | (recutils->alist body))
|
| 412 | (response-code (http-get nar-url))
|
| 413 | (response-code
|
| 414 | (http-get (string-append base "nar/" (basename %item))))))))
|
| 415 |
|
| 416 | (test-equal "/nar/ with properly encoded '+' sign"
|
| 417 | "Congrats!"
|
| 418 | (let ((item (add-text-to-store %store "fake-gtk+" "Congrats!")))
|
| 419 | (call-with-temporary-output-filename
|
| 420 | (lambda (temp)
|
| 421 | (let ((nar (utf8->string
|
| 422 | (http-get-body
|
| 423 | (publish-uri
|
| 424 | (string-append "/nar/" (uri-encode (basename item))))))))
|
| 425 | (call-with-input-string nar (cut restore-file <> temp)))
|
| 426 | (call-with-input-file temp read-string)))))
|
| 427 |
|
| 428 | (test-equal "/nar/invalid"
|
| 429 | 404
|
| 430 | (begin
|
| 431 | (call-with-output-file (string-append (%store-prefix) "/invalid")
|
| 432 | (lambda (port)
|
| 433 | (display "This file is not a valid store item." port)))
|
| 434 | (response-code (http-get (publish-uri (string-append "/nar/invalid"))))))
|
| 435 |
|
| 436 | (test-equal "/file/NAME/sha256/HASH"
|
| 437 | "Hello, Guix world!"
|
| 438 | (let* ((data "Hello, Guix world!")
|
| 439 | (hash (call-with-input-string data port-sha256))
|
| 440 | (drv (run-with-store %store
|
| 441 | (gexp->derivation "the-file.txt"
|
| 442 | #~(call-with-output-file #$output
|
| 443 | (lambda (port)
|
| 444 | (display #$data port)))
|
| 445 | #:hash-algo 'sha256
|
| 446 | #:hash hash)))
|
| 447 | (out (build-derivations %store (list drv))))
|
| 448 | (utf8->string
|
| 449 | (http-get-body
|
| 450 | (publish-uri
|
| 451 | (string-append "/file/the-file.txt/sha256/"
|
| 452 | (bytevector->nix-base32-string hash)))))))
|
| 453 |
|
| 454 | (test-equal "/file/NAME/sha256/INVALID-NIX-BASE32-STRING"
|
| 455 | 404
|
| 456 | (let ((uri (publish-uri
|
| 457 | "/file/the-file.txt/sha256/not-a-nix-base32-string")))
|
| 458 | (response-code (http-get uri))))
|
| 459 |
|
| 460 | (test-equal "/file/NAME/sha256/INVALID-HASH"
|
| 461 | 404
|
| 462 | (let ((uri (publish-uri
|
| 463 | (string-append "/file/the-file.txt/sha256/"
|
| 464 | (bytevector->nix-base32-string
|
| 465 | (call-with-input-string "" port-sha256))))))
|
| 466 | (response-code (http-get uri))))
|
| 467 |
|
| 468 | (test-equal "with cache"
|
| 469 | (list #t
|
| 470 | `(("StorePath" . ,%item)
|
| 471 | ("URL" . ,(string-append "nar/gzip/" (basename %item)))
|
| 472 | ("Compression" . "gzip"))
|
| 473 | 200 |
| 474 | #t |
| 475 | #t |
| 476 | 404) |
| 477 | (call-with-temporary-directory
|
| 478 | (lambda (cache)
|
| 479 | (let ((thread (with-separate-output-ports
|
| 480 | (call-with-new-thread
|
| 481 | (lambda ()
|
| 482 | (guix-publish "--port=6797" "-C2"
|
| 483 | (string-append "--cache=" cache)
|
| 484 | "--cache-bypass-threshold=0"))))))
|
| 485 | (wait-until-ready 6797)
|
| 486 | (let* ((base "http://localhost:6797/")
|
| 487 | (part (store-path-hash-part %item))
|
| 488 | (url (string-append base part ".narinfo"))
|
| 489 | (nar-url (string-append base "nar/gzip/" (basename %item)))
|
| 490 | (cached (string-append cache "/gzip/" (basename %item)
|
| 491 | ".narinfo"))
|
| 492 | (nar (string-append cache "/gzip/"
|
| 493 | (basename %item) ".nar"))
|
| 494 | (response (http-get url)))
|
| 495 | (and (= 404 (response-code response))
|
| 496 |
|
| 497 | |
| 498 | |
| 499 | (match (assq-ref (response-headers response) 'cache-control)
|
| 500 | ((('max-age . ttl))
|
| 501 | (< ttl 3600)))
|
| 502 |
|
| 503 | (wait-for-file cached)
|
| 504 |
|
| 505 | |
| 506 | (= #o444 (logand #o444 (stat:perms (lstat cached))))
|
| 507 | (= #o444 (logand #o444 (stat:perms (lstat nar))))
|
| 508 |
|
| 509 | (let* ((body (http-get-port url))
|
| 510 | (compressed (http-get nar-url))
|
| 511 | (uncompressed (http-get (string-append base "nar/"
|
| 512 | (basename %item))))
|
| 513 | (narinfo (recutils->alist body)))
|
| 514 | (list (file-exists? nar)
|
| 515 | (filter (lambda (item)
|
| 516 | (match item
|
| 517 | (("Compression" . _) #t)
|
| 518 | (("StorePath" . _) #t)
|
| 519 | (("URL" . _) #t)
|
| 520 | (_ #f)))
|
| 521 | narinfo)
|
| 522 | (response-code compressed)
|
| 523 | (= (response-content-length compressed)
|
| 524 | (stat:size (stat nar)))
|
| 525 | (= (string->number
|
| 526 | (assoc-ref narinfo "FileSize"))
|
| 527 | (stat:size (stat nar)))
|
| 528 | (response-code uncompressed)))))))))
|
| 529 |
|
| 530 | (test-equal "with cache, lzip + gzip"
|
| 531 | '(200 200 404)
|
| 532 | (call-with-temporary-directory
|
| 533 | (lambda (cache)
|
| 534 | (let ((thread (with-separate-output-ports
|
| 535 | (call-with-new-thread
|
| 536 | (lambda ()
|
| 537 | (guix-publish "--port=6794" "-Cgzip:2" "-Clzip:2"
|
| 538 | (string-append "--cache=" cache)
|
| 539 | "--cache-bypass-threshold=0"))))))
|
| 540 | (wait-until-ready 6794)
|
| 541 | (let* ((base "http://localhost:6794/")
|
| 542 | (part (store-path-hash-part %item))
|
| 543 | (url (string-append base part ".narinfo"))
|
| 544 | (nar-url (cute string-append "nar/" <> "/"
|
| 545 | (basename %item)))
|
| 546 | (cached (cute string-append cache "/" <> "/"
|
| 547 | (basename %item) ".narinfo"))
|
| 548 | (nar (cute string-append cache "/" <> "/"
|
| 549 | (basename %item) ".nar"))
|
| 550 | (response (http-get url)))
|
| 551 | (wait-for-file (cached "gzip"))
|
| 552 | (let* ((body (http-get-port url))
|
| 553 | (narinfo (recutils->alist body))
|
| 554 | (uncompressed (string-append base "nar/"
|
| 555 | (basename %item))))
|
| 556 | (and (file-exists? (nar "gzip"))
|
| 557 | (file-exists? (nar "lzip"))
|
| 558 | (match (pk 'narinfo/gzip+lzip narinfo)
|
| 559 | ((("StorePath" . path)
|
| 560 | _ ...
|
| 561 | ("Signature" . _)
|
| 562 | ("URL" . gzip-url)
|
| 563 | ("Compression" . "gzip")
|
| 564 | ("FileSize" . (= string->number gzip-size))
|
| 565 | ("URL" . lzip-url)
|
| 566 | ("Compression" . "lzip")
|
| 567 | ("FileSize" . (= string->number lzip-size)))
|
| 568 | (and (string=? gzip-url (nar-url "gzip"))
|
| 569 | (string=? lzip-url (nar-url "lzip"))
|
| 570 | (= gzip-size
|
| 571 | (stat:size (stat (nar "gzip"))))
|
| 572 | (= lzip-size
|
| 573 | (stat:size (stat (nar "lzip")))))))
|
| 574 | (list (response-code
|
| 575 | (http-get (string-append base (nar-url "gzip"))))
|
| 576 | (response-code
|
| 577 | (http-get (string-append base (nar-url "lzip"))))
|
| 578 | (response-code
|
| 579 | (http-get uncompressed))))))))))
|
| 580 |
|
| 581 | (let ((item (add-text-to-store %store "fake-compressed-thing.tar.gz"
|
| 582 | (random-text))))
|
| 583 | (test-equal "with cache, uncompressed"
|
| 584 | (list #t
|
| 585 | (* 42 3600) |
| 586 | `(("StorePath" . ,item)
|
| 587 | ("URL" . ,(string-append "nar/" (basename item)))
|
| 588 | ("Compression" . "none"))
|
| 589 | 200 |
| 590 | (* 42 3600) |
| 591 | (path-info-nar-size
|
| 592 | (query-path-info %store item)) |
| 593 | 404) |
| 594 | (call-with-temporary-directory
|
| 595 | (lambda (cache)
|
| 596 | (let ((thread (with-separate-output-ports
|
| 597 | (call-with-new-thread
|
| 598 | (lambda ()
|
| 599 | (guix-publish "--port=6796" "-C2" "--ttl=42h"
|
| 600 | (string-append "--cache=" cache)
|
| 601 | "--cache-bypass-threshold=0"))))))
|
| 602 | (wait-until-ready 6796)
|
| 603 | (let* ((base "http://localhost:6796/")
|
| 604 | (part (store-path-hash-part item))
|
| 605 | (url (string-append base part ".narinfo"))
|
| 606 | (cached (string-append cache "/none/"
|
| 607 | (basename item) ".narinfo"))
|
| 608 | (nar (string-append cache "/none/"
|
| 609 | (basename item) ".nar"))
|
| 610 | (response (http-get url)))
|
| 611 | (and (= 404 (response-code response))
|
| 612 |
|
| 613 | (wait-for-file cached)
|
| 614 | (let* ((response (http-get url))
|
| 615 | (body (http-get-port url))
|
| 616 | (compressed (http-get (string-append base "nar/gzip/"
|
| 617 | (basename item))))
|
| 618 | (uncompressed (http-get (string-append base "nar/"
|
| 619 | (basename item))))
|
| 620 | (narinfo (recutils->alist body)))
|
| 621 | (list (file-exists? nar)
|
| 622 | (match (assq-ref (response-headers response)
|
| 623 | 'cache-control)
|
| 624 | ((('max-age . ttl)) ttl)
|
| 625 | (_ #f))
|
| 626 |
|
| 627 | (filter (lambda (item)
|
| 628 | (match item
|
| 629 | (("Compression" . _) #t)
|
| 630 | (("StorePath" . _) #t)
|
| 631 | (("URL" . _) #t)
|
| 632 | (_ #f)))
|
| 633 | narinfo)
|
| 634 | (response-code uncompressed)
|
| 635 | (match (assq-ref (response-headers uncompressed)
|
| 636 | 'cache-control)
|
| 637 | ((('max-age . ttl)) ttl)
|
| 638 | (_ #f))
|
| 639 |
|
| 640 | (string->number
|
| 641 | (assoc-ref narinfo "FileSize"))
|
| 642 | (response-code compressed))))))))))
|
| 643 |
|
| 644 | (test-equal "with cache, vanishing item" |
| 645 | 200
|
| 646 | (call-with-temporary-directory
|
| 647 | (lambda (cache)
|
| 648 | (let ((thread (with-separate-output-ports
|
| 649 | (call-with-new-thread
|
| 650 | (lambda ()
|
| 651 | (guix-publish "--port=6795"
|
| 652 | (string-append "--cache=" cache)))))))
|
| 653 | (wait-until-ready 6795)
|
| 654 |
|
| 655 | |
| 656 | |
| 657 | (let* ((base "http://localhost:6795/")
|
| 658 | (item (add-text-to-store %store "random" (random-text)))
|
| 659 | (part (store-path-hash-part item))
|
| 660 | (url (string-append base part ".narinfo"))
|
| 661 | (cached (string-append cache "/gzip/"
|
| 662 | (basename item)
|
| 663 | ".narinfo"))
|
| 664 | (response (http-get url)))
|
| 665 | (and (= 200 (response-code response)) |
| 666 | (wait-for-file cached)
|
| 667 | (begin
|
| 668 | (delete-paths %store (list item))
|
| 669 | (response-code (pk 'response (http-get url))))))))))
|
| 670 |
|
| 671 | (test-equal "with cache, cache bypass"
|
| 672 | 200
|
| 673 | (call-with-temporary-directory
|
| 674 | (lambda (cache)
|
| 675 | (let ((thread (with-separate-output-ports
|
| 676 | (call-with-new-thread
|
| 677 | (lambda ()
|
| 678 | (guix-publish "--port=6788" "-C" "gzip"
|
| 679 | (string-append "--cache=" cache)))))))
|
| 680 | (wait-until-ready 6788)
|
| 681 |
|
| 682 | (let* ((base "http://localhost:6788/")
|
| 683 | (item (add-text-to-store %store "random" (random-text)))
|
| 684 | (part (store-path-hash-part item))
|
| 685 | (narinfo (string-append base part ".narinfo"))
|
| 686 | (nar (string-append base "nar/gzip/" (basename item)))
|
| 687 | (cached (string-append cache "/gzip/" (basename item)
|
| 688 | ".narinfo")))
|
| 689 | |
| 690 | |
| 691 | |
| 692 | (and (let ((response (pk 'r1 (http-get nar))))
|
| 693 | (and (= 200 (response-code response))
|
| 694 | (not (response-content-length response)))) |
| 695 | (= 200 (response-code (http-get narinfo)))
|
| 696 | (begin
|
| 697 | (wait-for-file cached)
|
| 698 | (let ((response (pk 'r2 (http-get nar))))
|
| 699 | (and (> (response-content-length response)
|
| 700 | (stat:size (stat item)))
|
| 701 | (response-code response))))))))))
|
| 702 |
|
| 703 | (test-equal "with cache, cache bypass, unmapped hash part"
|
| 704 | 200
|
| 705 |
|
| 706 | |
| 707 | |
| 708 | |
| 709 | (call-with-temporary-directory
|
| 710 | (lambda (cache)
|
| 711 | (let ((thread (with-separate-output-ports
|
| 712 | (call-with-new-thread
|
| 713 | (lambda ()
|
| 714 | (guix-publish "--port=6787" "-C" "gzip"
|
| 715 | (string-append "--cache=" cache)))))))
|
| 716 | (wait-until-ready 6787)
|
| 717 |
|
| 718 | (let* ((base "http://localhost:6787/")
|
| 719 | (item (add-text-to-store %store "random" (random-text)))
|
| 720 | (part (store-path-hash-part item))
|
| 721 | (narinfo (string-append base part ".narinfo"))
|
| 722 | (nar (string-append base "nar/gzip/" (basename item)))
|
| 723 | (cached (string-append cache "/gzip/" (basename item)
|
| 724 | ".narinfo")))
|
| 725 | |
| 726 | |
| 727 | (and (= (response-code
|
| 728 | (http-get (string-append base "nar/gzip/"
|
| 729 | (make-string 32 #\e)
|
| 730 | "-does-not-exist")))
|
| 731 | 404)
|
| 732 | (= 200 (response-code (http-get nar)))
|
| 733 | (= 200 (response-code (http-get narinfo)))
|
| 734 | (begin
|
| 735 | (wait-for-file cached)
|
| 736 | (response-code (http-get nar)))))))))
|
| 737 |
|
| 738 | (test-equal "/log/NAME"
|
| 739 | `(200 #t text/plain (gzip))
|
| 740 | (let ((drv (run-with-store %store
|
| 741 | (gexp->derivation "with-log"
|
| 742 | #~(call-with-output-file #$output
|
| 743 | (lambda (port)
|
| 744 | (display "Hello, build log!"
|
| 745 | (current-error-port))
|
| 746 | (display #$(random-text) port)))))))
|
| 747 | (build-derivations %store (list drv))
|
| 748 | (let* ((response (http-get
|
| 749 | (publish-uri (string-append "/log/"
|
| 750 | (basename (derivation->output-path drv))))
|
| 751 | #:decode-body? #f))
|
| 752 | (base (basename (derivation-file-name drv)))
|
| 753 | (log (string-append (dirname %state-directory)
|
| 754 | "/log/guix/drvs/" (string-take base 2)
|
| 755 | "/" (string-drop base 2) ".gz")))
|
| 756 | (list (response-code response)
|
| 757 | (= (response-content-length response) (stat:size (stat log)))
|
| 758 | (first (response-content-type response))
|
| 759 | (response-content-encoding response)))))
|
| 760 |
|
| 761 | (test-equal "negative TTL"
|
| 762 | `(404 42)
|
| 763 |
|
| 764 | (call-with-temporary-directory
|
| 765 | (lambda (cache)
|
| 766 | (let ((thread (with-separate-output-ports
|
| 767 | (call-with-new-thread
|
| 768 | (lambda ()
|
| 769 | (guix-publish "--port=6786" "-C0"
|
| 770 | "--negative-ttl=42s"))))))
|
| 771 | (wait-until-ready 6786)
|
| 772 |
|
| 773 | (let* ((base "http://localhost:6786/")
|
| 774 | (url (string-append base (make-string 32 #\z)
|
| 775 | ".narinfo"))
|
| 776 | (response (http-get url)))
|
| 777 | (list (response-code response)
|
| 778 | (match (assq-ref (response-headers response) 'cache-control)
|
| 779 | ((('max-age . ttl)) ttl)
|
| 780 | (_ #f))))))))
|
| 781 |
|
| 782 | (test-equal "no negative TTL"
|
| 783 | `(404 #f)
|
| 784 | (let* ((uri (publish-uri
|
| 785 | (string-append "/" (make-string 32 #\z)
|
| 786 | ".narinfo")))
|
| 787 | (response (http-get uri)))
|
| 788 | (list (response-code response)
|
| 789 | (assq-ref (response-headers response) 'cache-control))))
|
| 790 |
|
| 791 | (test-equal "/log/NAME not found"
|
| 792 | 404
|
| 793 | (let ((uri (publish-uri "/log/does-not-exist")))
|
| 794 | (response-code (http-get uri))))
|
| 795 |
|
| 796 | (test-equal "/signing-key.pub"
|
| 797 | 200
|
| 798 | (response-code (http-get (publish-uri "/signing-key.pub"))))
|
| 799 |
|
| 800 | (test-equal "non-GET query"
|
| 801 | '(200 404)
|
| 802 | (let ((path (string-append "/" (store-path-hash-part %item)
|
| 803 | ".narinfo")))
|
| 804 | (map response-code
|
| 805 | (list (http-get (publish-uri path))
|
| 806 | (http-post (publish-uri path))))))
|
| 807 |
|
| 808 | (test-end "publish")
|
| 809 |
|
| 810 | |
| 811 | |
| 812 | |