From unknown Sat Jun 14 03:57:42 2025 X-Loop: help-debbugs@gnu.org Subject: [bug#40993] cuirass: Add build products download support. Resent-From: Mathieu Othacehe Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Fri, 01 May 2020 08:56:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 40993 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: 40993@debbugs.gnu.org X-Debbugs-Original-To: guix-patches@gnu.org Received: via spool by submit@debbugs.gnu.org id=B.158832331524552 (code B ref -1); Fri, 01 May 2020 08:56:02 +0000 Received: (at submit) by debbugs.gnu.org; 1 May 2020 08:55:15 +0000 Received: from localhost ([127.0.0.1]:48215 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jURRy-0006Nv-Kt for submit@debbugs.gnu.org; Fri, 01 May 2020 04:55:15 -0400 Received: from lists.gnu.org ([209.51.188.17]:60388) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jURRq-0006Ne-02 for submit@debbugs.gnu.org; Fri, 01 May 2020 04:55:13 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:35908) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jURRo-00026Q-0a for guix-patches@gnu.org; Fri, 01 May 2020 04:55:05 -0400 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on eggs.gnu.org X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_FROM, RCVD_IN_DNSWL_NONE,SPF_PASS,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.90_1) (envelope-from ) id 1jURRl-0001BZ-QA for guix-patches@gnu.org; Fri, 01 May 2020 04:55:03 -0400 Received: from mail-wm1-x334.google.com ([2a00:1450:4864:20::334]:36544) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1jURRl-000168-1p for guix-patches@gnu.org; Fri, 01 May 2020 04:55:01 -0400 Received: by mail-wm1-x334.google.com with SMTP id u127so5650411wmg.1 for ; Fri, 01 May 2020 01:55:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id:user-agent:mime-version; bh=7l/8d6KFGZo/1ZEcIVOE8teBEyXqCJdsnTaVz65BbGE=; b=ZWaaicVV33Xn9R3U036aVrFXeLWy3U6S4d7B/SP1B++xgciSTeVq+O/KlK/Hguscnp J0I2+P6kankMaR6qMN6zVOePadr4fRnNJz64Q8CG++8Yjz2hLAsfvz3PIw+ePIXEJefx o/7ZpgqeRKhgM7mTHpOwnZewcLswRTel6CoHG1N76XE3+k2/we7GQfqqnH7yz2afqHpm uWgSRw+oLvH1dCozxYm79gUTYCunAwAr0fiYsUTsQkI1DcoOfhtIgTOtD1buAH9VKHNx g0Qb9x5Q8mlJ5U5aJplM1BBT8CjfXZO5QshX1pxDavKKv0isLyVICvZ9DmdUnA3j8AM8 lSBA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:user-agent :mime-version; bh=7l/8d6KFGZo/1ZEcIVOE8teBEyXqCJdsnTaVz65BbGE=; b=t9/9nDLSrzmVnlOq9S3r/KkauvNSYpf9bVOjEB4+jAcgpmANNVHgTKIG20MWUBGDSY e+/7IE8enPMOxe/HbQt5y023fdjHd+Txpo2n/D5Q6jQ2NDnpfG3pnaXInKgyn1nUGW+K a+dSMMSoCsW6OxPd4V5TNBl2FWicgA7Rue+5iDHkFORxwLemxR9eAXjzgWvWNHA9TjaG R3aMIbS2YnwLzv0L0qAew96EO1F8vzyVkkDv7uVxdFLz5I893lBPnCkYvWI2i9RBimjc ijLUT5HuKCLcga9XEvxpjg+dtIG3txVS+mP+4NXw50S+eaWpnzkypI0o7BLb5r7Fy0+N Rhvg== X-Gm-Message-State: AGi0PuaF+IFJbw0ck4+orUBoWPrz+HyXFDI6T3ymcE9wKOlFfjyXM70R TI9r4TP3/VYnmleFpABWV4Z3Fo/0 X-Google-Smtp-Source: APiQypIdcFyipHgRlp11pBfXwF2BPrigjYRhJfHKfYNm2e/mlDjRGep533qiFcHOB5unXCIEFlXq1w== X-Received: by 2002:a1c:5502:: with SMTP id j2mr3193629wmb.56.1588323298607; Fri, 01 May 2020 01:54:58 -0700 (PDT) Received: from meru ([2a01:cb18:832e:5f00:2134:99dd:8794:8d1e]) by smtp.gmail.com with ESMTPSA id w18sm3255838wrn.55.2020.05.01.01.54.56 for (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 01 May 2020 01:54:57 -0700 (PDT) From: Mathieu Othacehe Date: Fri, 01 May 2020 10:54:56 +0200 Message-ID: <87ees4uja7.fsf@gmail.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Received-SPF: pass client-ip=2a00:1450:4864:20::334; envelope-from=m.othacehe@gmail.com; helo=mail-wm1-x334.google.com X-detected-operating-system: by eggs.gnu.org: Error: [-] PROGRAM ABORT : Malformed IPv6 address (bad octet value). Location : parse_addr6(), p0f-client.c:67 X-Received-From: 2a00:1450:4864:20::334 X-Spam-Score: 0.7 (/) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -2.3 (--) --=-=-= Content-Type: text/plain Hello, Here's a patch adding support for build products downloading in Cuirass. It is inspired by a similar mechanism in Hydra. Attached a screenshot of what I obtained with the following specification: --8<---------------cut here---------------start------------->8--- (define hello-master '((#:name . "guix-master") (#:load-path-inputs . ()) (#:package-path-inputs . ()) (#:proc-input . "guix") (#:proc-file . "build-aux/cuirass/gnu-system.scm") (#:proc . cuirass-jobs) (#:proc-args (subset . "all")) (#:inputs . (((#:name . "guix") (#:url . "https://gitlab.com/mothacehe/guix") (#:load-path . ".") (#:branch . "master") (#:no-compile? . #t)))) (#:build-outputs . (((#:job . "iso9660-image*") (#:type . "iso") (#:output . "out") (#:path . "")))))) (list hello-master) --8<---------------cut here---------------end--------------->8--- Thanks, Mathieu --=-=-= Content-Type: text/x-diff; charset=utf-8 Content-Disposition: inline; filename=0001-Add-support-for-build-products-downloading.patch Content-Transfer-Encoding: quoted-printable >From dbb78929d7c8aa3b9007660795f55232ab47dbfb Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Fri, 1 May 2020 10:32:18 +0200 Subject: [PATCH] Add support for build products downloading. * src/sql/upgrade-7.sql: New file. * Makefile.am: Add it. * src/cuirass/base.scm (create-build-outputs): New procedure, (build-packages): call it, (process-spec): add the new spec argument and pass it to create-build-outpu= ts. * src/cuirass/database.scm (db-add-build-product, db-get-build-product-path, db-get-build-products): New exported procedures. * src/cuirass/http.scm (respond-static-file): Move file sending to ... (respond-file): ... this new procedure, (url-handler): add a new "download/" route, serving the requested file with the new respond-file procedure. Also gather build products and pass th= em to "build-details" for "build//details" route. * src/cuirass/templates.scm (build-details): Honor the new "products" argum= ent to display all the build products associated to the given build. * src/schema.sql (BuildProducts): New table, (Specifications)[build_outputs]: new field. * tests/database.scm: Add empty build-outputs spec. * tests/http.scm: Ditto. * examples/guix-jobs.scm: Ditto. * examples/hello-git.scm: Ditto. * examples/hello-singleton.scm: Ditto. * examples/hello-subset.scm: Ditto. * examples/random.scm: Ditto. * doc/cuirass.texi (overview): Document it. --- Makefile.am | 4 ++- doc/cuirass.texi | 14 +++++++-- examples/guix-jobs.scm | 4 ++- examples/hello-git.scm | 4 ++- examples/hello-singleton.scm | 4 ++- examples/hello-subset.scm | 4 ++- examples/random.scm | 4 ++- src/cuirass/base.scm | 44 ++++++++++++++++++++++++++-- src/cuirass/database.scm | 57 ++++++++++++++++++++++++++++++++---- src/cuirass/http.scm | 36 +++++++++++++++++------ src/cuirass/templates.scm | 37 +++++++++++++++++++++-- src/schema.sql | 13 +++++++- src/sql/upgrade-7.sql | 15 ++++++++++ tests/database.scm | 4 ++- tests/http.scm | 5 ++-- 15 files changed, 218 insertions(+), 31 deletions(-) create mode 100644 src/sql/upgrade-7.sql diff --git a/Makefile.am b/Makefile.am index 65c9a29..f4a3663 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,6 +5,7 @@ # Copyright =C2=A9 2018 Ludovic Court=C3=A8s # Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur # Copyright =C2=A9 2018 Tatiana Sholokhova +# Copyright =C2=A9 2020 Mathieu Othacehe # # This file is part of Cuirass. # @@ -71,7 +72,8 @@ dist_sql_DATA =3D \ src/sql/upgrade-3.sql \ src/sql/upgrade-4.sql \ src/sql/upgrade-5.sql \ - src/sql/upgrade-6.sql + src/sql/upgrade-6.sql \ + src/sql/upgrade-7.sql =20 dist_css_DATA =3D \ src/static/css/cuirass.css \ diff --git a/doc/cuirass.texi b/doc/cuirass.texi index e652e8d..c6f64c9 100644 --- a/doc/cuirass.texi +++ b/doc/cuirass.texi @@ -11,7 +11,7 @@ This manual is for Cuirass version @value{VERSION}, a bui= ld automation server. =20 Copyright @copyright{} 2016, 2017 Mathieu Lirzin@* -Copyright @copyright{} 2017 Mathieu Othacehe@* +Copyright @copyright{} 2017, 2020 Mathieu Othacehe@* Copyright @copyright{} 2018 Ludovic Court=C3=A8s@* Copyright @copyright{} 2018 Cl=C3=A9ment Lassieur =20 @@ -137,7 +137,12 @@ a specification might look like: (#:url . "git://my-custom-packages.git") (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t))))) + (#:no-compile? . #t)))) + (#:build-outputs . + (((#:job . "hello*") + (#:type . "license") + (#:output . "out") + (#:path . "share/doc/hello-2.10/COPYING"))))) @end lisp =20 In this specification the keys are Scheme keywords which have the nice @@ -150,6 +155,11 @@ containing the custom packages (see @code{GUIX_PACKAGE= _PATH}). @code{#:load-path-inputs}, @code{#:package-path-inputs} and @code{#:proc-input} refer to these inputs by their name. =20 +The @code{#:build-outputs} list specifies the files that will be made +available for download, through the Web interface. Here, the +@code{COPYING} file, in the @code{"out"} output, for all jobs whose name +matches @code{"hello*"} regex. + @quotation Note @c This refers to @c . diff --git a/examples/guix-jobs.scm b/examples/guix-jobs.scm index 963c7ff..2f1f1a2 100644 --- a/examples/guix-jobs.scm +++ b/examples/guix-jobs.scm @@ -1,6 +1,7 @@ ;;; guix-jobs.scm -- job specification test for Guix ;;; Copyright =C2=A9 2016 Mathieu Lirzin ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur +;;; Copyright =C2=A9 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -34,7 +35,8 @@ (#:url . "https://git.savannah.gnu.org/git/guix/guix-cui= rass.git") (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t)))))) + (#:no-compile? . #t)))) + (#:build-outputs . ()))) =20 (define guix-master (job-base #:branch "master")) diff --git a/examples/hello-git.scm b/examples/hello-git.scm index 6468452..c5e2ca2 100644 --- a/examples/hello-git.scm +++ b/examples/hello-git.scm @@ -2,6 +2,7 @@ ;;; Copyright =C2=A9 2016 Mathieu Lirzin ;;; Copyright =C2=A9 2016 Jan Nieuwenhuizen ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur +;;; Copyright =C2=A9 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -43,4 +44,5 @@ (#:url . ,(string-append "file://" top-srcdir)) (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t))))))) + (#:no-compile? . #t)))) + (#:build-outputs . ())))) diff --git a/examples/hello-singleton.scm b/examples/hello-singleton.scm index a39191f..2d2d746 100644 --- a/examples/hello-singleton.scm +++ b/examples/hello-singleton.scm @@ -1,6 +1,7 @@ ;;; hello-singleton.scm -- job specification test for hello in master ;;; Copyright =C2=A9 2016 Mathieu Lirzin ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur +;;; Copyright =C2=A9 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -34,6 +35,7 @@ (#:url . "https://git.savannah.gnu.org/git/guix/guix-cui= rass.git") (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t)))))) + (#:no-compile? . #t)))) + (#:build-outputs . ()))) =20 (list hello-master) diff --git a/examples/hello-subset.scm b/examples/hello-subset.scm index 8c0d990..e86668e 100644 --- a/examples/hello-subset.scm +++ b/examples/hello-subset.scm @@ -1,6 +1,7 @@ ;;; hello-subset.scm -- job specification test for hello subset ;;; Copyright =C2=A9 2016 Mathieu Lirzin ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur +;;; Copyright =C2=A9 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -34,7 +35,8 @@ (#:url . "https://git.savannah.gnu.org/git/guix/guix-cui= rass.git") (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t)))))) + (#:no-compile? . #t)))) + (#:build-outputs . ()))) =20 (define guix-master (job-base #:branch "master")) diff --git a/examples/random.scm b/examples/random.scm index 37b97a2..f15e158 100644 --- a/examples/random.scm +++ b/examples/random.scm @@ -1,6 +1,7 @@ ;;; random.scm -- Job specification that creates random build jobs ;;; Copyright =C2=A9 2018 Ludovic Court=C3=A8s ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur +;;; Copyright =C2=A9 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -31,4 +32,5 @@ (#:url . ,(string-append "file://" top-srcdir)) (#:load-path . ".") (#:branch . "master") - (#:no-compile? . #t))))))) + (#:no-compile? . #t)))) + (#:build-outputs . ())))) diff --git a/src/cuirass/base.scm b/src/cuirass/base.scm index 2b18dc6..b745058 100644 --- a/src/cuirass/base.scm +++ b/src/cuirass/base.scm @@ -1,7 +1,7 @@ ;;; base.scm -- Cuirass base module ;;; Copyright =C2=A9 2016, 2017, 2018, 2019 Ludovic Court=C3=A8s ;;; Copyright =C2=A9 2016, 2017 Mathieu Lirzin -;;; Copyright =C2=A9 2017 Mathieu Othacehe +;;; Copyright =C2=A9 2017, 2020 Mathieu Othacehe ;;; Copyright =C2=A9 2017 Ricardo Wurmus ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur ;;; @@ -41,6 +41,7 @@ #:use-module (ice-9 popen) #:use-module (ice-9 rdelim) #:use-module (ice-9 receive) + #:use-module (ice-9 regex) #:use-module (ice-9 atomic) #:use-module (ice-9 ftw) #:use-module (ice-9 threads) @@ -638,7 +639,42 @@ started)." (spawn-builds store valid) (log-message "done with restarted builds")))) =20 -(define (build-packages store jobs eval-id) +(define (create-build-outputs builds product-specs) + "Given BUILDS a list of built derivations, save the build products descr= ibed +by PRODUCT-SPECS." + (define (find-build job-regex) + (find (lambda (build) + (let ((job-name (assq-ref build #:job-name))) + (string-match job-regex job-name))) + builds)) + + (define* (find-product build spec) + (let* ((outputs (assq-ref build #:outputs)) + (output (assq-ref spec #:output)) + (path (assq-ref spec #:path)) + (root (and=3D> (assoc-ref outputs output) + (cut assq-ref <> #:path)))) + (and root + (if (string=3D? path "") + root + (string-append root "/" path))))) + + (define (file-size file) + (stat:size (stat file))) + + (map (lambda (spec) + (let* ((build (find-build (assq-ref spec #:job))) + (product (find-product build spec))) + (when (and product (file-exists? product)) + (db-add-build-product `((#:build . ,(assq-ref build #:id)) + (#:type . (assq-ref spec #:type)) + (#:file-size . ,(file-size product)) + ;; TODO: Implement it. + (#:sha256-hash . "") + (#:path . ,product)))))) + product-specs)) + +(define (build-packages store spec jobs eval-id) "Build JOBS and return a list of Build results." (define (register job) (let* ((name (assq-ref job #:job-name)) @@ -692,6 +728,8 @@ started)." outputs)) outputs)) (fail (- (length derivations) success))) + + (create-build-outputs results (assq-ref spec #:build-outputs)) (log-message "outputs:\n~a" (string-join outs "\n")) (log-message "success: ~a, fail: ~a" success fail) results)) @@ -777,7 +815,7 @@ started)." (let ((jobs (evaluate store spec eval-id checkouts))) (log-message "building ~a jobs for '~a'" (length jobs) name) - (build-packages store jobs eval-id)))))) + (build-packages store spec jobs eval-id)))))) =20 ;; 'spawn-fiber' returns zero values but we need one. *unspecified*)))) diff --git a/src/cuirass/database.scm b/src/cuirass/database.scm index f80585e..0ed0720 100644 --- a/src/cuirass/database.scm +++ b/src/cuirass/database.scm @@ -1,6 +1,6 @@ ;;; database.scm -- store evaluation and build results ;;; Copyright =C2=A9 2016, 2017 Mathieu Lirzin -;;; Copyright =C2=A9 2017 Mathieu Othacehe +;;; Copyright =C2=A9 2017, 2020 Mathieu Othacehe ;;; Copyright =C2=A9 2018, 2020 Ludovic Court=C3=A8s ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur ;;; Copyright =C2=A9 2018 Tatiana Sholokhova @@ -47,6 +47,7 @@ db-get-pending-derivations build-status db-add-build + db-add-build-product db-update-build-status! db-get-output db-get-inputs @@ -65,6 +66,8 @@ db-get-evaluations-id-min db-get-evaluations-id-max db-get-evaluation-specification + db-get-build-product-path + db-get-build-products db-get-evaluation-summary db-get-checkouts read-sql-file @@ -334,7 +337,8 @@ table." (with-db-worker-thread db (sqlite-exec db "\ INSERT OR IGNORE INTO Specifications (name, load_path_inputs, \ -package_path_inputs, proc_input, proc_file, proc, proc_args) \ +package_path_inputs, proc_input, proc_file, proc, proc_args, \ +build_outputs) \ VALUES (" (assq-ref spec #:name) ", " (assq-ref spec #:load-path-inputs) ", " @@ -342,7 +346,8 @@ package_path_inputs, proc_input, proc_file, proc, proc_= args) \ (assq-ref spec #:proc-input) ", " (assq-ref spec #:proc-file) ", " (symbol->string (assq-ref spec #:proc)) ", " - (assq-ref spec #:proc-args) ");") + (assq-ref spec #:proc-args) ", " + (assq-ref spec #:build-outputs) ");") (let ((spec-id (last-insert-rowid db))) (for-each (lambda (input) (db-add-input (assq-ref spec #:name) input)) @@ -386,7 +391,7 @@ DELETE FROM Specifications WHERE name=3D" name ";") (match rows (() specs) ((#(name load-path-inputs package-path-inputs proc-input proc-file= proc - proc-args) + proc-args build-outputs) . rest) (loop rest (cons `((#:name . ,name) @@ -398,7 +403,9 @@ DELETE FROM Specifications WHERE name=3D" name ";") (#:proc-file . ,proc-file) (#:proc . ,(with-input-from-string proc read)) (#:proc-args . ,(with-input-from-string proc-args r= ead)) - (#:inputs . ,(db-get-inputs name))) + (#:inputs . ,(db-get-inputs name)) + (#:build-outputs . + ,(with-input-from-string build-outputs read))) specs))))))) =20 (define (db-add-evaluation spec-name checkouts) @@ -538,6 +545,19 @@ VALUES (" =3D> (sqlite-exec db "ROLLBACK;") #f)))) =20 +(define (db-add-build-product product) + "Insert PRODUCT into BuildProducts table." + (with-db-worker-thread db + (sqlite-exec db "\ +INSERT INTO BuildProducts (build, type, file_size, sha256_hash, +path) VALUES (" + (assq-ref product #:build) ", " + (assq-ref product #:type) ", " + (assq-ref product #:file-size) ", " + (assq-ref product #:sha256-hash) ", " + (assq-ref product #:path) ");") + (last-insert-rowid db))) + (define* (db-update-build-status! drv status #:key log-file) "Update the database so that DRV's status is STATUS. This also updates = the 'starttime' or 'stoptime' fields. If LOG-FILE is true, record it as the b= uild @@ -1066,3 +1086,30 @@ AND (" status " IS NULL OR (" status " =3D 'pending' SELECT specification FROM Evaluations WHERE id =3D " eval))) (and=3D> (expect-one-row rows) (cut vector-ref <> 0))))) + +(define (db-get-build-product-path id) + "Return the build product with the given ID." + (with-db-worker-thread db + (let ((rows (sqlite-exec db " +SELECT path FROM BuildProducts +WHERE rowid =3D " id))) + (and=3D> (expect-one-row rows) (cut vector-ref <> 0))))) + +(define (db-get-build-products build-id) + "Return the build products associated to the given BUILD-ID." + (with-db-worker-thread db + (let loop ((rows (sqlite-exec db " +SELECT rowid, type, file_size, sha256_hash, path from BuildProducts +WHERE build =3D " build-id)) + (products '())) + (match rows + (() (reverse products)) + ((#(id type file-size sha256-hash path) + . rest) + (loop rest + (cons `((#:id . ,id) + (#:type . ,type) + (#:file-size . ,file-size) + (#:sha256-hash . ,sha256-hash) + (#:path . ,path)) + products))))))) diff --git a/src/cuirass/http.scm b/src/cuirass/http.scm index c5901f0..79fa246 100644 --- a/src/cuirass/http.scm +++ b/src/cuirass/http.scm @@ -1,6 +1,6 @@ ;;;; http.scm -- HTTP API ;;; Copyright =C2=A9 2016 Mathieu Lirzin -;;; Copyright =C2=A9 2017 Mathieu Othacehe +;;; Copyright =C2=A9 2017, 2020 Mathieu Othacehe ;;; Copyright =C2=A9 2018, 2019, 2020 Ludovic Court=C3=A8s ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur ;;; Copyright =C2=A9 2018 Tatiana Sholokhova @@ -246,17 +246,29 @@ Hydra format." "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd") (sxml->xml body port)))) =20 + (define* (respond-file file + #:key name) + (let ((content-type (or (assoc-ref %file-mime-types + (file-extension file)) + '(application/octet-stream)))) + (respond `((content-type . ,content-type) + ,@(if name + `((content-disposition + . (form-data (filename . ,name)))) + '())) + ;; FIXME: FILE is potentially big so it'd be better to not = load + ;; it in memory and instead 'sendfile' it. + #:body (call-with-input-file file get-bytevector-all)))) + (define (respond-static-file path) ;; PATH is a list of path components (let ((file-name (string-join path "/")) (file-path (string-join (cons* (%static-directory) path) "/"))) - (if (and (member file-name %file-white-list) + (if (and (member file-name %file-white-list) (file-exists? file-path) (not (file-is-directory? file-path))) - (respond `((content-type . ,(assoc-ref %file-mime-types - (file-extension file-path= )))) - #:body (call-with-input-file file-path get-bytevector-a= ll)) - (respond-not-found file-name)))) + (respond-file file-path) + (respond-not-found file-name)))) =20 (define (respond-gzipped-file file) ;; Return FILE with 'gzip' content-encoding. @@ -318,7 +330,8 @@ Hydra format." (#:url . "https://git.savannah.gnu.org/git/guix.git") (#:load-path . ".") (#:branch . ,name) - (#:no-compile? . #t))))) + (#:no-compile? . #t))) + (#:build-outputs . ()))) (respond (build-response #:code 302 #:headers `((location . ,(string->uri-ref= erence "/admin/specifi= cations")))) @@ -352,11 +365,12 @@ Hydra format." (respond-json (object->json-string hydra-build)) (respond-build-not-found id)))) (('GET "build" build-id "details") - (let ((build (db-get-build (string->number build-id)))) + (let ((build (db-get-build (string->number build-id))) + (products (db-get-build-products build-id))) (if build (respond-html (html-page (string-append "Build " build-id) - (build-details build) + (build-details build products) `(((#:name . ,(assq-ref build #:specification)) (#:link . ,(string-append "/jobset/" (assq-ref b= uild #:specification))))))) (respond-build-not-found build-id)))) @@ -505,6 +519,10 @@ Hydra format." query)) (respond-json-with-error 500 "Query parameter not provided!")))) =20 + (('GET "download" id) + (let ((path (db-get-build-product-path id))) + (respond-file path #:name (basename path)))) + (('GET "static" path ...) (respond-static-file path)) (_ diff --git a/src/cuirass/templates.scm b/src/cuirass/templates.scm index 4104c7b..600d9d8 100644 --- a/src/cuirass/templates.scm +++ b/src/cuirass/templates.scm @@ -2,6 +2,7 @@ ;;; Copyright =C2=A9 2018 Tatiana Sholokhova ;;; Copyright =C2=A9 2018, 2019, 2020 Ludovic Court=C3=A8s ;;; Copyright =C2=A9 2019, 2020 Ricardo Wurmus +;;; Copyright =C2=A9 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -27,6 +28,7 @@ #:use-module (srfi srfi-26) #:use-module (web uri) #:use-module (guix derivations) + #:use-module (guix progress) #:use-module (guix store) #:use-module ((guix utils) #:select (string-replace-substring)) #:use-module ((cuirass database) #:select (build-status)) @@ -212,7 +214,7 @@ system whose names start with " (code "guile-") ":" (br) "Add"))))) '())))) =20 -(define (build-details build) +(define (build-details build products) "Return HTML showing details for the BUILD." (define status (assq-ref build #:status)) (define blocking-outputs @@ -282,7 +284,38 @@ system whose names start with " (code "guile-") ":" (b= r) (tr (th "Outputs") (td ,(map (match-lambda ((out (#:path . path)) `(pre ,path))) - (assq-ref build #:outputs)))))))) + (assq-ref build #:outputs)))) + ,@(if (null? products) + '() + (let ((product-items + (map + (lambda (product) + (let* ((id (assq-ref product #:id)) + (size (assq-ref product #:file-size)) + (type (assq-ref product #:type)) + (path (assq-ref product #:path)) + (href (format #f "/download/~a" id))) + `(a (@ (href ,href)) + (li (@ (class "list-group-item")) + (div + (@ (class "container")) + (div + (@ (class "row")) + (div + (@ (class "col-md-auto")) + (span + (@ (class "oi oi-data-transfer-downloa= d") + (title "Download") + (aria-hidden "true")))) + (div (@ (class "col-md-auto")) + ,path) + (div (@ (class "col-md-auto")) + "(" ,(byte-count->string size) ")")))))= )) + products))) + `((tr (th "Build outputs") + (td + (ul (@ (class "list-group d-flex flex-row")) + ,product-items)))))))))) =20 (define (pagination first-link prev-link next-link last-link) "Return html page navigation buttons with LINKS." diff --git a/src/schema.sql b/src/schema.sql index 1104551..3838f75 100644 --- a/src/schema.sql +++ b/src/schema.sql @@ -7,7 +7,8 @@ CREATE TABLE Specifications ( proc_input TEXT NOT NULL, -- name of the input containing the proc th= at does the evaluation proc_file TEXT NOT NULL, -- file containing the procedure that does = the evaluation, relative to proc_input proc TEXT NOT NULL, -- defined in proc_file - proc_args TEXT NOT NULL -- passed to proc + proc_args TEXT NOT NULL, -- passed to proc + build_outputs TEXT NOT NULL --specify what build outputs should be made = available for download ); =20 CREATE TABLE Inputs ( @@ -65,6 +66,16 @@ CREATE TABLE Builds ( FOREIGN KEY (evaluation) REFERENCES Evaluations (id) ); =20 +CREATE TABLE BuildProducts ( + build INTEGER NOT NULL, + type TEXT NOT NULL, + file_size BIGINT NOT NULL, + sha256_hash TEXT NOT NULL, + path TEXT NOT NULL, + PRIMARY KEY (build, path) + FOREIGN KEY (build) REFERENCES Builds (id) ON DELETE CASCADE +); + CREATE TABLE Events ( id INTEGER PRIMARY KEY, type TEXT NOT NULL, diff --git a/src/sql/upgrade-7.sql b/src/sql/upgrade-7.sql new file mode 100644 index 0000000..02e9c41 --- /dev/null +++ b/src/sql/upgrade-7.sql @@ -0,0 +1,15 @@ +BEGIN TRANSACTION; + +CREATE TABLE BuildProducts ( + build INTEGER NOT NULL, + type TEXT NOT NULL, + file_size BIGINT NOT NULL, + sha256_hash TEXT NOT NULL, + path TEXT NOT NULL, + PRIMARY KEY (build, path) + FOREIGN KEY (build) REFERENCES Builds (id) ON DELETE CASCADE +); + +ALTER TABLE Specifications ADD build_outputs TEXT NOT NULL DEFAULT "()"; + +COMMIT; diff --git a/tests/database.scm b/tests/database.scm index 6098465..98b5012 100644 --- a/tests/database.scm +++ b/tests/database.scm @@ -3,6 +3,7 @@ ;;; Copyright =C2=A9 2016 Mathieu Lirzin ;;; Copyright =C2=A9 2018 Ludovic Court=C3=A8s ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur +;;; Copyright =C2=A9 2020 Mathieu Othacehe ;;; ;;; This file is part of Cuirass. ;;; @@ -45,7 +46,8 @@ (#:branch . "master") (#:tag . #f) (#:commit . #f) - (#:no-compile? . #f)))))) + (#:no-compile? . #f)))) + (#:build-outputs . ()))) =20 (define (make-dummy-checkouts fakesha1 fakesha2) `(((#:commit . ,fakesha1) diff --git a/tests/http.scm b/tests/http.scm index d20a3c3..d69c25c 100644 --- a/tests/http.scm +++ b/tests/http.scm @@ -1,7 +1,7 @@ ;;; http.scm -- tests for (cuirass http) module ;;; Copyright =C2=A9 2016 Mathieu Lirzin ;;; Copyright =C2=A9 2017, 2018, 2019, 2020 Ludovic Court=C3=A8s -;;; Copyright =C2=A9 2017 Mathieu Othacehe +;;; Copyright =C2=A9 2017, 2020 Mathieu Othacehe ;;; Copyright =C2=A9 2018 Cl=C3=A9ment Lassieur ;;; ;;; This file is part of Cuirass. @@ -170,7 +170,8 @@ (#:branch . "master") (#:tag . #f) (#:commit . #f) - (#:no-compile? . #f)))))) + (#:no-compile? . #f)))) + (#:build-outputs . ()))) (checkouts1 '(((#:commit . "fakesha1") (#:input . "savannah") --=20 2.26.0 --=-=-= Content-Type: image/png Content-Disposition: attachment; filename=download.png Content-Transfer-Encoding: base64 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 NzzvS91Rtim29Xx6dvu3HSV19Dw2bJpD1NhoUrJP894zyUSNjiblsGsIRNvhEYaxSTx12KO3hq2U 1x5LUK/3uL/wRkkuaSMS2Npy+TmozssgJS665Z6wpqCmf4e5CSGEuLEpfe6K8sGiGCV4qLH9586p ypYye4+2dunQDHXdB/YqX/d6WkuUZXcZleChJmVeUa9vXFw3V5WPFkUp49aWKG1K16UcZeqdUcq8 I3WKXbmqfH1kiRJ/l0kZ/uQp53JXlXdnmZThdz+h7K9Uv7l05All1F1LlI+cG/o6a6oyfFaOcqVl o9XKqw+YlORDV5R2vt6rxN81W3n7ksf3lS8qMXc9obz99VV1r5U5ypy7ZyivuhfoK3nKnDvjlS2V dsVut7c9DsWuXNwYowy/d53y0SW7otivKBe3zVBG3b1E+ehqy06ULfealPhZs5X4RTnKxUtXlatX rihXWn6vU/ZPC1diVhQoX19VFOVqpfLukzHKqEfzlPZH0kF+OtPy+dp4Zeqb1Yq9aIEy/O51ykX3 NY88oQy/90Xl74qi2Cv3KlPvilfWF19ttxXXtv6eNUMZde865XO3RexFC5ThE2YrydNmK1uKqpUr V68qV65c9ZIW1wolyuq7Tcqc4+pGvs6aqgyfsLlNur7OmtqSLkVRlL9vm6qMuned8tHXVxW7/Ypy 8c3Zyqi7nlDebZcZ55Rld4Ury852tHNFUb5+UYm5c7bybrvD7OqctM2LK5V5ysIJUcqcI3Ut351Z EaWMmrZX+bvznL39ZIwyfOjUtmXH5VKOknzXDGV/m99OKQvvNCmjpr2oXLyqKK5zO3xW63m/cuQJ ZdTdTyhvf21XFOWqcnHbbGXUnSZlzpHu5qd6HY26e4ayuqhaueqWnx+0yU9n2T77rDLmriXKGc9s fHO2MureF5WLdkVRrhQo8+6OUZaddcusr/cq8XfOVt6+oijK1RJl9b3hytSsypZy8XXWVGX4tAOK ++V36c0Zbc67u6vHFyhj7t2s7q+b+3Dl55gHlijvVl5R7Fcrlf2zwpXhXq8j9ZjblfGydcq4O8OV mCcPKJ9fsSv2S3nKvLtNSnxWtdctKFfrlDMbZyijPNPaZjdtr4GW896Szmrl3Sej2uRP2/N+pfW8 H7+qqPcLkzLqyQLlytUCZd6dJiVmY4liv3RAmerKH+fxuc7nR3Zv966ulCvrJ6jbvqooiv3rAmXZ vSYl2KO8qWWnRFk9waTMK/LcQ4my+u4YZeHxOsWuKIr9SrmyZVqUMudI6xnp2X2pE3a7cuXQ7PZl yl6gzLnTpEx9s06xFz+rjLkzSllYdFW5evwJZdS9L6rPLl/vVeLvjFGWFdUpdsWuXDm7Tom50/1a tiufr4hShj/wonLxql2xXylRtjwao4waGq9scS5jL35WGXf3bOXVsiuKXbErl85uVqbeFaOs7+Fz lRBCiN+OPm/5txU8z5oT3wIBRK86TMmXZZR/uJu5Jh9oriLr6R2USTW16E3Vubx0PphFHmPTbaWn qdTdR3pSIBq0hCQt4UF9c7vVtVGPM8s5jjQoKoZQRy3VPZ6sz0bR7r9hS1jSvpu8rQGbxgetVqvu zziD7M9yeNK967Mr4T7OVsk2G6jgUEEDkQuXMDlIAxodYQsXMN5xmiMlrospgMCAZiwNBjZumUFY kBatTodO25pHhywm0lfHE6IFtEamLZ1BUEl++5bVDvITwFG+gw3lMWxM0Xfe47/uBMvn56Jbv5e1 EVqvi9hKXuCpvbDolWeJdFtEow1AW1+BJmU7Kybp0Wm16HTaDvZno3j787ynncGiWHUjIQmJhFpP c6Slld/CkaM1hExNdI4zL+VQXo2anyFaNBodYamb2bN+Br3bG72Lc+JybimmYWFEPLQT25zd7EkK dP5QwcnzNiJTZxDqPGez5ozDe246KN77OtVRT3jtUh+Z+jhhWgAt4xNGQU0Fdc71yk6Vool6nFkh GkBL2MLZRLqt23V+AjTjMM5mxSQ9Wo2OsJTHmUwpJ8vdb/atPRS8CUndzIrAfNbsPs176zKpjN3M 2mgvR+uo4eD8ZRSbtpOdZmwpFyERY9BZcslyjo131J0mK6+KZpuN9n06aji45wIh8x4nzFt6OtiH S2jqOqYZdWi0RmaljAGLKz/b6qiMO5oDeXDpbCJ1GjRBU5kbpaXaUtsujW88ZEb/x0k8dV7Ptn3P ek+rl2tA1UxI0hJnOvVMW3gfOstpzlrBdd5pOe+6ducdICTchE4bSJBO/bcmKJAgTQP1LRnaej5b elR4S2JHLKc5azUwc044WkATEk/61EAvC3aybUcDVhtoA/zQABqdiRXvniM7yUt3jm7clzrlPEbv AgiLCEQTEIyOYCLMWrSBt6O12dSeEiGzOfxJHhsnBaJBgy46nkhtDeU1rmukipOlNue1qkGjC2fR HPd7sIOzB05DwhKeNOvQoCEoegmLopo4lnfjzvEhhBCif/Vx8G/l2OFPaQL8YlfycqoJnUaDNiSG tZkLMALU53OoxAGUsny0ZxfBCjZMdHaNLbBSNN9M9Lov1J+qdjJpWDTLS8BWME/tejdlFVsXJxPu mixr0+mWB7C6w8nOZV7H1UPWludcb+ILlFXvIHZYKkeaAJo5uSAU/UNq925H3Wk2PJZE+IhQZxfr v/DaubbdPsWNwkbRi3/DlrSkXdddq7UBAvRuXbIDCdUHtNuCVhfQ+oCl8QEcNPU0GZZ9vHTewKKF pvYPqOZHSDd+wfKJ0SQ8toqth89T3ZPJ4ay11NkCCAl0j5ADMQZCXY2rXGrQ4ENgVLz3AKGmnrrm T1nxR7euyPftxNJcT02bot1xfkINb2y6QOT6BYR29oTvqGDr/FUU6RewMcHbgzxgPcHypz8mZP1u HvUcKKwBfEzcH9vVDAhWijelklYQyMZXlrQed9B9zDQ3UJTnfEtBeT7H6gzMTHBOQmatp9oWQJB7 fhJIZEIMYb066UIX58QlYiUFHx7hzfX34dg7j1RXN2FbA3U2LTr3dAbqCfI21sL2KW8WwORUb5UD WnQBrd+6Qig15Gigzgo69+tCczsh7hVYXeUnAD7ojMGt+9YEotdBXUNPCnogszKfRnd4IStq7uPl 1eFejqWBk+vmscEyhrXro9r+bl7Ay8sCOTt/vNrN/fET6KPD8aH9vA2Okrc4VD+OuQneTngn+wBA 26bsaHzc89NNZ2Xcp20ea7Q+0Oy5DT3TduZx/G+7STdUsPzxDM62y84OrgEA/AjRu11/AXqCaKC+ QT3GOisEdXbeAa3WNdulBq1GA66BPu3rUDtUl53sfAuA8+99gdtRWhuwEkiQ236DggK7HE7UhmYc c1MDOPZINFFT5rF8+1GK67y0MHRxX+o0nd2iJdAPtYIADWpdr4bWzLJRmfe82mV/dASm0Qt5r8E9 fQ3UWTVtrneN0eR2vddTXddE/YEUt+EkYcw90URDfb2XCi4hhBCir4N/RxUl5eofutDYMW0fmkLG ERkI0ERZaXdeA6RBZwrHHOh8+PC5HXP4KIzuG63KJ+t8Azr97dD0LSUHFpK6qaJ74980BsLCDbge ffz0Y4g2B6CxnWfN9IW8eb4KR9AYIox+WCs+Ztvjc1hTIl0WbjiWfWwtMbBonpeg21tJ8BaEdTGb e9dsHNudiy1pgfexwxojj+4/yfl3NzE33IfKw8tIiJvHe73xKkCPVihtQAcHowF87iPry0pqvnL/ nGKt+2RnneRn3eHnOaJ/mkXmztv2muursBljCCl/hZe8jaN3WHht/vNUx25nm9fgCyDA+fDckXqO LU4h7byBbe/sZVqb4ErH5JRwHKfyKXM4KM77EGvEDCa3BBg9iFx6QYfnxEWjIyjEyPikZ9mz2kTZ 7n0U93AftvNHKdbG8GAX56Z7mnG0uXS6yk9Vb+zZ0VCLFR98rLVUexvr31xLJeMYrzvN1t2es9tr CUvdy/n/KqP0sxIqCl9kckADBOo9JtJ0UJb3MbaoeMZ7S3Sn++jugXSjjHcjw7RBekIjYnhy13Zm kc9LBe6Z0tk14NxBj06K53nvXhq7EpT0IgUfHufkh8c5+WEeG2M732i7NHRJS+SyHEr/4wBrUwxg 2UdaXAJPnWxbgLq6L/U0ne11XmVhzVtN2t4GJmUeofQ/S6j4z91Ma1cX3X6fbb/xwbj0uMc9vJKa /VM76BEkhBDit66Pg/8GrM0APugCPB94tAQ6/9DZrN1rVw1L28vL84LV/+hnsO3tv/Ko+yy7fvfw 0ienKPjgFAWrRuED1OS9RXF3Hh6C4tn29krG+6npjVx6gOzVMWgtH3K2AfBL5OX3D3D43Ty2xQej D9RQXf7z310s+oKVY7tzoYOgW6sNgIYat8n6rFRbGtov2AmNBnA43AIAV8uZG8s+XioxdVABAeDA 4dCgM0YxLS2D7A+Os1ZfypunvPQm8RaX6oIJ0jZQXe8+wV89lnoNIYHtezJ4FWgghCraFmEbVpv7 xdJZftZTlHeBuoJlRI2OUFuunv6Y5oZ8UkYnt05AB/joZ7Bxy4u8vDCAYyszPVorbRRvWkYWC9jj tWW3O6wULZ7Dmvp4st/Z3C4IBdBGzWC841OOlJRy/JSD8ckxrQGgLpgQz/yknrPZb1F0HV+1WLk9 AcNDb7XpLq7x8Wktb9oAdFobdW7pdNRUUd2ujDgoO3kBTON+xuvzAtDp1B4kLSXBVkO1te1OOs1P J2vdt63bcNRTY9UQFNCDM+yoYOvT+ehW55GdVM+Glbntu9L7jGFRZgYvZ87Gkb2Mre7DCqylHMw+ TTUatFoNUM/ZUzXozCaPCRnV7tVh0WO8X6+d7aNbfmEZt50gbXQ0y8+1HTIB4HC4zkvX1wC0LTs0 1FBHAGpnAPW8W2vcbma2Gu8VLr+UNpCQEH3LR9emni4AHfXUuZ3oaktNj6vnHA4HGp2JySlL2La/ gMOpGopyPm3TGt75famLdPaC6pIKHOYZzDLr1LNprW3bA0zjh05rw9rgfr1XuF3vgYToNdRVVLU5 LofVKhP+CSGE6NB1mu3/OtEGEuR8sgqJnao++DZVUflLWlQDDWo3u6bTbH1mBwdPVhG6voBTnxRw OE3e73NDcQbd6R0E3brwcYRYT/PmSSvqLMk7OVTfow6l6AJvR1NT2jITd11eLmdt7o+mVo5t77gC AqAuew7hD++g2Plg7aj7gvJ6DSGBbi3CGi1aTQNlJRasNhvWOgvVVtcjnYmZCYEU786kqM4BDivF u1+hSBvDzOhuPqGGxDPTXM/BTW+px+Kop+i5VGIffqtlWEzn+RnIrP1nOf/JcU4Vqp+C9ePwCYjn 5ff38qiXceYhqS+yIvA0y5873fKwai1YxVOngtm4c/bPfi1Y3eFlLD9vYO3OxwnBhs1mw2ZztG0x 1I7jwVgo3v0KZ4lhZpux4yZmJgVSvHtHS36WZT/PU7s/xeE+LtuhbtMBOHDg8GiSVH934HDWZTps zv938zhCE2IIsuxjTXYp1VYrdZYTrHnxUzRR8YQBYGBSuIbiPfsotjqw1Z3npQOlXrZUT2WNgxBz F/MweKUhMjYcx6l9HLTYcNhqKNqeS6VnK2an+alylObyRrkN9Vr7G0WMYVK3XzvnoHjTKo4FPs3G JD2RyzYzrWEHT2V7r3DVmBfwcqqGgyt3uFX2NnBy9yrWZFdgtdVTvGcVWy0m0ud4vDbRWkWlNZBQ Y+dhufd9dO0Xl3HtGO6PcHBs+wscs9RjtdZQvCeT9+r03B+ldlfv1jUAVB5+Rb3vOOo5tuc0VmO8 c+4BDZGxJmyu8+6od57368wYQ6SuikN7z1PncGCzvMWb59s3DrRcawDNHteiZQcJExfyhuvtJ7Ya Kmua0AXqvVa8eLsvdY+jJR3ORPWol4JOHwD1FepbCBw1vLf9BFYt2FqCfQPR4RqKs3OpdADWUl7a W+p2P9EwPuU+tOdeYcNJtbLOWv4WaX9+kOUnpdO/EEII7/o2+NcEoPMBaMba4NmEYKPB+Tddq/vF /azb0/o5/9A7WvbzswTN5uUt0zH6NWE5sY+1C1KZ9McIEp452umrg8T1ZuW97fmQ9DTTOuo5HjKb bcsMlK2chGF0AsvLx5Ee282WcidN9BLWRtWyYWI0sVOS2WCNZ67Zp+WBzFG+j5fKO2v1h6CUzWw0 VrA8zqy+6m/6K9iSd7NxkvsY/nGkLxyDdfeDRPwxmtjpq8kqdz3QaQhbtptt4fVs+HME+tH3s7xE z8Z964jsdnARyKyde0nX5pM21ox+dApb68ex7dUnnJPcdZ2fGq0Ona71E+TnA/igC+poMr5AZmUu IeT88ywvsAJWig5/TEPDxyy6p+2r0KI2dXfCKitnCy7Q1PQxK+6JxPxH1ycM0zOn3VNLZNI9OCq+ wBGb6JFPGkKX7eXlqG/Z+ucI9CMnkVbgx6L9250VODW8NiUU48gwjCPTON7UxPHHwzCODGt95WD1 DhJGqt8Zp++jpvkCa+9R/5+U3c35QYxLyH41EU3eMhL+NJ7Yh3dQbXqW7PUxznuZlsmrtzNLe4LU P4UR9XgufkkzCGlXf6X2RtHpela2XXRJ69iW4CDroUhME5dx3DibB9q1JHeWn6qg2BjYnYJpRAQJ u21My1zHA91s9raVvMDygkBWPD9VbaXXmFiRmYht9zJes3i78WoIXbiJR31yWb6pVA3idPFs25kI B+YR8ccEnirwY9H+3czyPJaGeqz4EdjlnyEv++hSb5RxHQ9kZrPWWMXWhxOI+FMSTx2FB3budk4S 2t1rQMvkBD2HHovGMDqBDTXhbMxMbOkFoUvazLZYGy89FIlp4kKOG59gmv7ndLv/JUys2LmAoNJl RI+MJmFTPffPG4dfyx3WysGHXNdiKofqmzn5dKR6LY5epQ6PMS7g5YV+HJ8/CcOIUAwT53HIbwF7 lpk62KfnfakbSlYRPky9viPWXaC5Zh/3jwzDODKUpMPdu95DUtYxy+9DUsdGE/XQDqxJ29mWpKdy 04OkFVhRr/fNTONvJI02E/7IWwTMmU2o2/WuiXiW7C1jqNuchGmYmdinT6BbuKft3xIhhBDCXd++ TOCK8vYskxI81KiMerJAafMinUsHlPihRiV4aLiy7KxdafuaPddrasqV9RPU1wK6Xlnk7VV/V48/ oX7n/jqgr/c6tx+vvPq1olxxrXev23pHPNfr7FV/V5WviwuUtzcuUGLuVNMU/2YHr2ISvxJ25cyi cGXMipL+TogQP8+VHCX5zg5e9ddr1FcUul711zX1VX8xHb2qzoO9aIky6u5nlc9/fgJFl04pC++M UlYX93c6xC9y6YAy9c4Zbq9WFEIIIXqmj7v963ggZRx+QNOpTJ7KrsDqAEfdebY+/QoWgMBEZkZo AD/nhF7NVJdWqV1rLRVUelTEt7zcp6keq2eLhLWKMrUPHWU5+er2/UyEBoFGq1U7rlovUGYFsFJc 0r4LqTpfWjM2Z0+F6uxkTCMiCJ//KbqIeGatfpG1CWoTUZ1FZvz/dbGwdUoECZtK1bc4WHJ58zxE xhr6O2FC/IbYqLbUUGe1Umc5zdbdp8Ec8zPmJxBiYLMVLCV87DwOVjsAK2f35lKpjyGyV99CIoQQ 4rdkUF/vQJuwjm0nq0g/8S3nNqcQsdntRx8DczMXOF9HpGdSeABvHm2g5kAKpsM+NDd7eQe7PpAA LtBQ/w4zR3/IpC3n2Ob6sfkCG++L4CU/aGpqBnwwpjxCpAY0phhCfT6lvOlTVvzJzBqfZtpvPlCd /KgBStaNx3D4cfJenUro9ucpObWK2Cm5hGobKCttAgIYH9tRN0JxYzKSvn4B1c8tI2pYA/gFE5my m7XSRVKI66ieoufSyLI04NAEEBKxgD2ZMTI7uRAetAlL2Fiyig3TI1jbpCHQFKPOHdHfCRNCCPGr dZOiKErf78ZGWd4rvJR9mrKab2nSBGCMuI/0pUt4wH0GJFsFr61cRda5Whya2wmNjSeoYh/HayB6 ZzHZCVrAStFzC1mT9wU2ze1M21nAiqaFmJ/+FALuYW5sE0V5F7Bqbics6Vm2rY5pGdNYV5DBU9vz Ka+HAMM4pkU0c/DApzQFPsKRT54lDHBY3uKpp1/hbJ0DbcSzHN4/A53lKFu376OopJaGZj8CDCYe SFvJioSfM6GWEEIIIYQQQghxfV2n4L9v2QrmqcG/WxAvhBBCCCGEEEII1cB61Z8QQgghhBBCCCHa keBfCCGEEEIIIYQY4AZEt38hhBBCCCGEEEJ0TFr+hRBCCCGEEEKIAU6CfyGEEEIIIYQQYoCT4F8I IYQQQgghhBjgJPgXQgghhBBCCCEGOAn+hRBCCCGEEEKIAU6CfyGEEEIIIYQQYoCT4F8IIYQQQggh hBjgJPgXQgghhBBCCCEGOAn+hRBCCCGEEEKIAU6CfyGEEEIIIYQQYoCT4F8IIYQQQgghhBjgJPgX QgghhBBCCCEGOAn+hRBCCCGEEEKIAU6CfyGEEEIIIYQQYoCT4F8IIYQQQgghhBjgJPgXQgghhBBC CCEGOAn+hRBCCCGEEEKIAU6CfyGEEEIIIYQQYoCT4F8IIYQQQgghhBjgJPgXQgghhBBCCCEGuEG9 ubHaS9/15uaEEEIIIYQQQgjRC25SFEXp70QIIYQQQgghhBCi70i3fyGEEEIIIYQQYoCT4F8IIYQQ QgghhBjgJPgXQgghhBBCCCEGOAn+hRBCCCGEEEKIAa5XZ/sXoj9du3aN//3f/6W5uZlr1671d3KE EEIIIcQNYtCgQfj4+PCv//qvDBokIZD4bZLZ/sWAcO3aNb799lv8/f35l3/5F7mpCyGEEEKIFteu XeOHH37AZrNx++23y7Oi+E2S4F8MCFeuXMHX1xetVtvfSRFCCCGEEDeoxsZGmpub+bd/+7f+TooQ 152M+RcDgt1u59Zbb+3vZAghhBBCiBvYrbfeSnNzc38nQ4h+IcG/GBD+7//+j//3/6Q4CyGEEEKI jg0aNEjmhhK/WRItCSGEEEIIIYQQA1wfB/823ns4FP0w948Z09gEUp55i7N1PduatWApUSMiiH3u PDaAklWEDwtFP+IvFHW0Ut1bJA0LRT8sideqf9nRCCGEEEIIIYQQv0b90PLfTFNDLSVHM5k7fRVn bd1fs67kAvXNTdSUV2HtuwT+TA6OzTejHxZK+HOlvb71usPJauXJlNeROgwhhBBCCCGEED1x3YJ/ v6nZ1HxVSc1XZZzbNx09QMMJ3jzf/eg/bFk2WZnbOfTqbEL6LKVCCCGEEEIIIcTA0g8t/xqCoqcS GQjQjM3aREet5mcXq9+ZnjkPQOXehaSvXMbMlfkdt/zbSnltfgLhI8wYxiazPKeKrqsXrBRnryJl YgSGEWZME5N5as/51n1YdhA7LBT9sDkcdH1Z/ToJw0LRD0vmjbpS1owNY9EpdebQhpxU9CP+wjFH BRsmhqIfFkHqcy+QGheBYZiZ8ClLeaPclapSlo9Wh0OknXTt0LVeKKkFVormm4le94X6U9VOJg2L ZnmJM+XnXidtSjSmYaHoR0ST8NgLHKt2dHnEQgghhBBCCCF+O/oh+HdQd+4oxfUAwURGBPbitus5 OH8e207V0tDcDLYvOLI3n5pO17Fx9rkUZm7Op6S+CYCm+i84/mIaSc+c70bFAYAfRvMY9H7q/3wC DEREmNC1/N7EuZxcKgkkyA8aqj5k42PLONatsQsadKZwzIGujd+OOXwURi04yneQ8vhOTlY50JrG YA5yYDn/NxY9soqiHgynEEIIIYQQQggxsF234L/paKpzwr8woh9/hxoCiH5+N4uMvbgTy1tklTYD Adz/ylk/2JjbAAAgAElEQVSq/quMk8+Pw6+zdapz2ZrzrbrOzpNU/Vc5Ja8kEgjUH80ky9KdHRuZ 9epeFkX4AKCNXcnh/U8QqXFbYt5hzhfmceqT3TwYADR9SlZBfbcOKyxtLy/PC1b/o5/Btrf/yqNG qD51mhrAJ2odBe8eIO/97cw1BBOoraXEIq3/QgghhBBCCCFU16/l3y8Ys2kUZpMBfYAP0EDx3n2c 7cWZ+6zlVdQDBN7H3Ek6QENI1LhO5wewVnyKxbVOgtoLQTfpcR7QA9RSXNK9AL0rOmMgGgBtFA9G qdUR1aVV/JIQPcgYiB/QXPIKyzflcqw8gPR3CzhfmMfaCE2X6wshhBBCCCGE+G24fhP+xa4j790c 8t7N49Qnh0nXQ3N9Phv2VvTaPhwOtds+AYFuXe47p8454LmOH4EBzt8ber//vEarBv/NDtsvCv61 CZt4ed4YAqjl5IHnWfTIg0SMTiAtu6KbwxWEEEIIIYQQQvwW9MOYf0Cjx2xQu8hba+p/UQDcZrPO oJqmhm4Hvzqdc52Gerd1mqhvUP+lDdD2UupcHC0VDj4aLRqP33pGx/hlByj97CRHXllHerwBv+Za Tm5eykvlvZRcIYQQQgghhBC/ev0S/DuqT3CkVJ0ZXxsYgAYNWj81DLZZqtT32NsqOFfT3KPt6owm 9RWCNZ9y3Dnm3VZTRV0n62hN4zAC1J8m66Q6BsF6bh/HaqBlQkK/ANQqgFrKLTbAQV1FRbs3DrgC eYetqV0YX33O2Rpfd4JD59XgPzTcgAY/tFqA5pZhAA5LBZUeG9e4tt5Uj9UBYOW9xyIwjY4gpQDC Js1gxa5NzFLfoUh1vYz5F0IIIYQQQgihGnS9dtR0dB6GAoBmml0xvY+BuSnhAIRFj8Hv6Mc0VWQy acQOfJqb6VnoDxhnMDf8LdaWVpH1UAJn9Rrqqmpp6mydkBmsmJrL3KPfcnzBeIr8/GhuUtcIiF/C XCPAGCL1UF7TwJHHoznmA83NnqnTEKQPBGppOrEQ46kxbChc0vJr/dE0zAV++DQ3qcfld49zjoEA JoUH8ObRBmoOpGA67ONl26DVBxLABRrq32Hm6A+ZtOUcG5PGsPX8x5SsSyK2wESQrYriGsAvnPtN Mub/RpS1H56sbf1/2mOQFdx/6RFCCCGEEEL8NlzHlv9mmpudgb9PAPrw6Wx4O5snnbP9axPW8fK8 cQT6+QAagqKmMzf29h7uI5BZr+5leWwwAXxLdYOG8UsfIdqns3W0jN9ymENL71Nfp9fUhF+AgUnz 9pC3K8Y5D4CRRTvXcb8hAB+aQavn/qXTMXtsKXTeOtLDb8cPH/x0AWjd4m/z1MeZFATgQ4DhPja8 vZkHdAAaIlfvZnlsMOqhB2Ce+jj369tuWxPxNBuTRxHgAz4aP3R+oEt4kcM7H2GSwQ9r+aecq4Gg 8OlsfXs704J6mHVCCCGEEKJHLn/f3ym48di/h8b+ToQQwqubFEVR+jsRA1cFGyam8GY9RO8sJjuh t+cPEC7ffPMNQ4YM6e9kdEla/oUQ4rel8RKkHoH3G2B4FFTd298pEr2l/DOI+wKK02FIfyfmBlJc AMmNcGbWjZsvv5bnRiF6W/9M+CeE+HX4EeI2wsrv+jcZu7Ig8rP+TUO3dJFfu7LgpjWQ/WPrd4u3 w5CC3k9K4yVI3Q/+GXBTBhgOQH5vtlB9Bf5rYKXH/CT27yH7I4jcBYXd3db3sPIgDHamdUgW7Krt aqW2CnPVvO3ok/Nj19u4ni5/AYM3Qnan49JaFebCTX+FbzpZ5ptKSPwr+GaA/3ZI/wLsrh+tYO4k f1xlsKv9XL7Yvgz3VH43joWfIKcADJnq/vx3QWqpR2viT5D/EZhdy2z3sszPVFwAN22H4l7YVkYu FA6CN2dDdvjPT0PWfhic2/3j6+m9xV4LQzZCZj/f738tGr+CxHOQMaM1wF25y8v1lQE5P7Vd9/Il SD+g5vfgIx4b7k7Z96LT6x+4XKpuz/dAJ9u6BEPWtC135efU7Z1xHkP+wfbHN3gXpF9s3W7kZEj+ Xs0fu5fdCCH6z3Ub8y+E+PW5/CWc8Yddf+jHRFgh+zKkz+r+8uaXwDATckL7NGXtdJZf31yEjM5m H+1NVojbD5eDIWsmDL4Gu07A1P3wySKYcHPv79L+HaSfgJxa53tLfLu54k+Qvh+yfWHXdDAMguIL 8PQBYAEs7uZ7WyMnw+dj239/pggyfoLIW7qZnuvE/zaYMFQ93t5gr4UJh8B3JORMhssWWPkONA5y Xgf+kJPm5aH/e0h+ByZc52ulKznvQEotLIqHrNvgm69g5QeQ6gv5o9RlCvNg6j9gzmTYpYNvvoDF H6iVCmd6EGT3KSucaYTEmZA67JdtyjAU4ny7f2n1lO+tEDcUDLf20Q4Gkp8g4wMYHA3pt7V+/c0P MDwMVnr06It0u+d+cxEi88B3KCyOB8MdbZftTtn31OX178ZRCzlNkO7XfjuFpfBPj+8GB6jlYrD7 3w1/eG2Ks9LjGpR/CRl56rVXeBdwM2QkQM4ByB7ZNo+EEP1Lgv8+ZWLtJ5Ws7e9kCPEz5VwEw11g 6Mc0lF+Eb4Ih2cuDyo2mw/xqgsVFMCQIKq5DBcCZc1ByK3w+s/WhM/IWMOyHrFqY8AuDEG8av4Mq X8h6DAxfwd0Xuree/R+Q3QgZiyDdGehPuAOKt0DOP7of/PvfBpGeD5hNkPEdxE258bqe+t4BOTN6 b3v55+ByEFS5WiGHgW8jzP0YMkLBcHP7IAOg/Au4HACLb6ThR02QZYHxSbDrLud3wWqAs7gMGkeB fxNkfKkuk+2+TAM8eRG+Cb9Bzvk1teXTvxcqnyZEw4RfvpmO6SCrF8vkQNZogWw7ZIW5ffkjXL4G 5pGdVPR8D6kfwJDJcCbaS0VOd8q+l812ef27FhwEvwOyvoR0z8rSH9V9/+5WuOr29eBQLxXpvurf Edd240LVvwG7voTGu9Q0+gZDegBknoPUP/ddpZUQomek278QfejyV2DIaO0e96RHV+Y9+1t/G3wA qn7yupn+4WpxN7p99yNkOrtn+26EuI8g8wD45rauY16jdjdceUDtcu6bCYmftbY4flPqpRuks6vh 4kvtk5H9JUwIc3vg+RGyjqjdJV1dxLOc6+UfhJteggog91DbrouN36ktzP7OtE94H8rd0uDqzrz4 HBg2gu/B1u6KhefUrsW+GWoAneOt+7y3/HKtXwSF/pB1V/vfPJWfU/eT+pWaL4M76Kbt6m6fnglD ctVj881wDjnQwZyxYHZrqfH1h8GA/RrQBBMyIO5i233nH4Sbslq7YxeXwoTt4OvseurZjdTd4Lug eBakBncc6Li6e2ceUctQZKna+nV5tUeQPwh8BznT6uJZ9gpg5X7w9+wy66aqDM4MgsWuc9KN8glq WUnNch73drU7a1yGOrYXIPOv4J/bvkutb4Y6vODyRTWNKz+DSGf+Df5razkFsH+hfp/VBDSpw0UG H2lNh915TXQ03KWxVr23mD9S1znzHQwJbhvwJo4EzWUo7qiL/k+Q+SVEjqXdBLYd7cf9++S/tpaN lW73tuQM72V2QmnbbReXOvMnA4bsdxuW4gf5q50tiG58bwZcZcKuBlmelRYGf/U317lJzwTz+7D4 QGvZmVDQfshB4UfO6z4DzLlw5oe2v6dngrkAduXCEOd2It+Hb36E/AJ1XdfwmjPO/C7/CG56Bf6b 1nt9Z8MlukrDrqy29yW7tfWedtNGMB+EM50MI2lzb/HGYyjP5YvqMe36AuJ2OcvxLth1SR1WlOzW xTzVs4t5LSRntQ47Mue2L4dVXzjvLxnO+/hFNW8z3YYStblvZ0LyZzfGRHKFX4I9GOLc73U/wGVg iLOi2tu9sqoUzt4Ku+7uIBjuTtn3otvX/yBINkJFKZR7bKPRAoXXIHlo2+/LP4KbMuFMx7sHwN9L c2LiKPinpXeGzwgheocE/0L0ocHD4MxMGN5FH5vfD1WXM/RBd+yfq+oiVN0BiW6tqVmH4NlaiIuH nCQw1EHGP9qvm/0BVA2G7OmQcQe8f+LnjSO110KOHVLdAuqcPHjyH5A8BT6ZCYnAkwfUsdNxU8Ay G4YD904By2NqUGP/DibsUcfdZs2E/HjAAhPegiqPfWZdhNR4yHG2yhQegfs+BvM9kD8TJvwEKftb xz92ll+uY0j/EjL+DEO6KAffXIS4IpgQD9nDgMGQnwafPOb8zIY/3wqaAEh0a/7555dQHqDmd6q/ 2kKYPbbtw2XjJagaBHF3AH5qkH7mS7cH6Z/ULvsRo9QHyKrPYMIJ8A2H/McgM1jtjppa2fkxdOky ZP0AGUmQORS4Wa0s8HWmwd4EOScgH1g8snW1XQfUspc4BQqnw4TGLuYF+Al2lcKQsPbDHDotn03q XAk512CXs6zYP4OP3B68E0fB1X+0LQOFXwJuwYDDDtkWWDkDyhdA6iC1nOZ4C878IGsyNJa1pmPX +3B5sHoePdm/g8SDYDdC4b1qxZj9Gvh6VLr436b+dvmH9tsAuFwG+dfa5nNX+3FZeQIGh0OO8xrc kguFzvzIeMytzD4GLwQDgyDRPVC/DIsvQvJkyImHwd+pLaKu8thSJgD7j2pFQcYlSLzbmQ5nK7Xn 9VZ4CX7vEQRVlMFlI5xZAIWT4fIFteLSFZyVn4PE8zA4DHKmQ7qvWiniqeIC5N+qlotdI6HqAkS+ AosbYGUSHI0H31pIPqFu2xANlpnw78CMJLAsgsQOKsa6m4YWzuEyOT+p6flwCgy+BHEH2t/TwMu9 pbuuQcY5iLtHTVfkNXVIjvkg+N7lTKsODuRBljNot1+CCQegSgfZc+CT6WC4BHHvqMExqPfFuHfg Gx1kTYfMUZB1Av7pHuBaIW4P5Dvv2znRUFwEiaWeibz+Ci+BIdijFf4HaLwG+UfUipJb1sCQA20r ZM7Uwe90kP+OWtFyUwZEHmlb8d9l2fei29f/NZgQDv/eANkele05ZWplbGJ3muivgf0n5+dHOPMZ 7LoMiWPaptFwB/z+Byi2drglIcR1Jt3+hehjrgqACYfgv73U3N+IgT+o3f8m3KO2FgNwCTJr4c/T Ids57jBxKNhfgmyPdc33QL4raAmGM1vgTC3Qw7kDzlwEjBDnljeFtTB8jPqwCDDhD2B/By43qN0M DXb1wcn/VjA4A4Ocj6HCH8pmtraGT9CBYQ9kfAU5bg/DGbNgpasV2qp2tbzXrWtx3B+g6kXItMAE t/GX7fIL1HGhBcAYWPwHtRWrI5e/grgPYHAU5LjGK98MkW5dtcs/gkI7ZMxpO4b0dyPhTGfdKn+E lUXgHwapzlapuJHAB1D4IyTfoj6MF16DDKMz3R/DkDGQ7+qaGgz+P0Dq53A51OM4e8JfDRgjPcv7 dzDkFed400GwaA6kugK7S7CrTi17Wa7zPgwub29f9lwaLZDzA2R6CZ47K59VZfD+NXhtVus41Ql/ gPIXW9c3jARTEeTUQVww8CPkXIK4JOfDtnO5jOmQ6MzvzFnqNjK/hGQvaRoSDpkX1aA6LgwyL8Pi tPZDSOzfQ+oBKP8DFCe1ngeDv1p50xjd+vB9uUkNphs7aDHc9RkMHunRetnFflwWz4JM5/UcNwjy 34IzDer1YbijNd2NX0FqLYyf4tG741Y1MHTte4gVwsrU1sgJboulboQDzig9YjJkdzI3QVUpZDVC pkd36t+HtV5TBh1kW9VhKWfugThg1zn12ihMcK4XCoPtMNUjOPr9SLdlhqkVas/a1UpIVwWT/3cw 8R/qcUTeot6DfFEDMYPb8dudwZ7vzagVVd1MQ4sGKP4BEmeoFXkAE3wh8XOo+hEMbufU670Fdb+u ChDfTv7+ZMyCxc5rIW4QDD4EQ6a0Tl6Y6BymU3jJeY5vhaw5anDcUj5/gD+cgOKfIPFmyDkH/wyA sjmtvU4m+MKQvNb95hd5DF8aBv5WmHgOysM77q3S55rUsf1DPCJx+0/g6wtDhkLmZHUISkaRWslR 9ZhaIVX+PVxthPyRkP0Y+Fph8Qk1X6s87uE9Kfs9uv4HQ3IAZJdC5h3OfX6v9kxKnAn+Xir022mA sOfbfmW6G7I80+ivHnexFejmEC4hRN+Sln8hroOOegDcqIE/lyD/B0h36/53+Tv45yA14G9xMwzx EnEOCXD7zy0weFDHAUiHflJbThPvavtAFHcH/HcZLC5VH6TwU8eZe06w5K6wFoYbPbrB36G2nhdb 2i472G2yq8u1apfd5KFurRy+EOkP5e6tzl7yC9SWwV0/QNY9nY93tDdA4jtweWj7FtaWZS5B8nkw T4aVHpUo/rd2vv3sdyB7EGRPbl1usFENsnKcD3rFFrAPdrakXoZiuzoJnPt2E5Ogyksg2CO+HhNH uQRA4QL4fDY8EwxZByHb2Q3cVfY8u6MO7uSgsz8DjN7niuisfJZfAgZDnHuL8iCPc3IbpAZBobOr c+M/1G6tnunzdb/e/SBuMFTVdjx0YvGfnS2kH4D/GLVXQhvXIOMg5A5SK1Dc7xvJ4cA/1CEKl3+E qkpI/ECdfNFbPtm/guwGddxvu5872Y+LwS1DfP3U/Gn0PLAfIT0PGod6BJ0At7YNUP1vU/fr2aU7 4zH4/DF4826oKuq450ljLSSeAHN8+8nFfD3uu5Gh8Ds7lDeiBtF2tTLMPR/c7wMt2/G4zgb7Abe1 va8Mvo02ww7a+U4dznHL8+rH/0jP0tAiACJvVYcc7KpUhx/4DoPCOW17F3R0b/nmM7jp+dZ0uIa0 tDOobTpc59rgfg/yU7t8u64h39vA7KsO0zFkgv9GGHLCmR7nKme+g38f2jaA9yyn+Zdal3Hdf83B oGmE4m6+JaNPOLv3D/aoNPMdBt+sUfM5cZhayVcYD9RCjqtXhB1+Z4QzM9SeMHHhkHM3/LMM8j2G RXS37EMPrn/nHBTp4fA/ltbeOuWlUHGrWpFk787fan84nAafOz+HJ0PjBS+9MnydZaOD3kdCiOtP Wv6FuE48ewDcsIE/cKZUfWB3bxFstKM+CF6nWdMbLVDoC4UeQX3yTOBjyPwYXvoAfhcA6ZPVSY28 xoI/qQ+l/l5mGx7iC5c7eQ/RZWfwOXcLzPX4TeP28Ostv/ge0j+GCVPcvu/goep//gH/A/AdlP/o pSX2R1icq87eX+il1bgzhe9D+iXYlQYT3Ld7i/qgl/4l2EeplQDm6NbZmxsBf48M9b0FhvTV+b/Z GVD8ASKD4fKLsNI5UZSr7HV70jRnT4HUhI67yXbEbgfNLV1XcCSOUtNX/Ge1rOJ5/r3wvwUczodv r2X1D7D4Dphb20FQ3gC5AIPUGeTd9zckXO3Gm57n7GBzKywaCeVlMMRLEJnzOTQGQ6q31rhO9tMT OXnqdo7+ggqjIX9Qy2RksDqBWYrnBGaoY98TDwJhkN+dWf6dlTmua78R8L9eE4o6K7laWtxvVRPQ 4zTcDFlpYCiCXXnwtB3+PRgy4iHV7d7U0b1lSBiUud1b/f2BjnoZ9JRzmBUjYdcs9e0BjV9C2Met i9jt3u/JLX5Sr/t/XoBbvEwe2mgHfgWTwA4ZqpZV95bvIXe0vR7Mw+D359XKqGT3a7obZb9l2R5c //ZrMGQkjC9Se6wljlLn1hk+Uu3BcqY7wb8vmN16+ETeAYOtMPFj9U0bE1zLDVLvY/K6PyFuHBL8 C3EduSoAki+o4xxvxMCfnyDrH2orr3vw4e+LGhT+CPyCALC7M/7ml6ldkiM9f7gZku9VP/bv1e6j 6YfAnga7vMxmzs1qy0OVl0n6vrG3D3Dd+fsBg2DnY+3T4et6oOogv4o/g7N24B246Z22687dBIvD oPFB9f+awVD4Z8g4oI57rprRNmgtPAF77PBmUs9mMC//DJLLYPFjrTPpu4sLA/sHas+Iwh9gpavl 2tfZktvT3ho/Q+N3kG+FuFFuD8Q3q70rcn5wzpTuKns/qb91Jf8zdbx8urfy0AV/X3A4u8t2FrAO MYK5CPJr1Zb/CVO6rmhobALNbf+/vTsIjfM88wD+b1DSCdhENjaVoSbyIbVU8EYJJGsH6mpzcEwK rZqCo2Rho+JDVboHLT1EZS+6rXJYVoUW7IOJW0jXDcTrGOJ1ffCqKSRqArG3hljbHKKS0qokxBMn YKUW9R5eKRqNJVtyJMv58vuBIUGjmW9mvvk0//d93udd/DMw9U4y9HZyVy0ZOZ30N50HqSX/8VQy +WJZY9/zg/nLJ3q/k/TsKed12+Zk4qXkQNv8mekkybvJyFulxHfB53idx1mKiTeS/vPJ4/80t/Rh yT4sg1EdnUlXw7WmY1OSd8qsa0fDbft/loxvKwMxSxnsmbo08/7OvBGtKWH0pri9adY8SaZu7Bhq G0pPicGU6pjhF5PvHkpafzg3+7/oteXO+a/tShp7o8wiv/qduevmZFMAba0l9YUap86auW5vv69h N4dZLVeX3N9UM9stzi7dmDXxdnK2lvQssLztk4qrdXODygtazrnfZMmf/yRZXyrV+l5PJmrlWjuw hIa019K+JcmZUnmQhgHv+nRZDgHcGpT9w03W9pVk9B9v0eCfEmROtsytI53VtjW5e7qsK/zE5fJF YzlqteSL0/ObENU/bCr3/TA5/HbS1/xl5N3SdXpwZoaqtqE059vbkozNrqdfYEhz77bk/96a3914 6p3kZL3stb6Y9pnnPD5dZjZm/3VtmusnsNjr1bU7OfOD+f/+52vlZ0/vL+uNZ7VtK9vbHX40mTpX OmfPmnwz6TuTfOubDWvgl2DyzaTnVLJ3X1nXuZDWe8qa56GXSlj+pHnapmRnLRltKjMdfTFpf25u TftKmPpz0v980w4Kl5OxetLWWr40t20p78OxprWoC1ZtXEhGzifdD93YFpVd25JMJicby4oXKEfP hqS3LTnyUmlO2FzynzSVwV9IjrxX1kEvVqEy9EJS70zG9iU5N7+DfpKktayvHn48+ft6aSzXfFy1 9WVteeuFZPBMsvP+qweMRl8pPTAGFltDvITHuaYLSd+JsnThwI1sKzmVDB9NBpve7/H3kqxreD6X S2n5sXWlCWR7FjbVVIY/ei75oJZ0tSZpLeXzo00d8K9awrCabuAYJt9IOn5SenYk5TMy8khZzjDW 8GZd69qyWhYaxDjbdC53b0n+8Pb85oTNjSm7tyYT75W/PY3X347WRZYO3SzrSvPWiabjHX8t+fYL Tc9p5jl2zQy+7t02v9w+ScZ/n/ylluzclKWf+4tYyud/Vs8DMw0qTyf1rYtUAS3D2XfKMc6rDqzP 3wEBWHtm/oF5Tr6etHZe3SE9W8q6+u8fTfo/SnrWJSdfTQ7Wky8uY4a1dWvS1ZIMH0/adiV5r8xy Nu4rPHEuGduUHG7+MrK5rP8ceSFp25PsXF/WKp6cLuXSScqa8pZk7Exy7M6ke1vS83By78FSGjzy UNkjfPhUMvHl5Ng1mihlazLcmTzxXJI9Se9M077hU0nHvtI1e7HXq7Y+6Wr6wjM5M0DR0bbwrFv7 /cnht5JvH08Ob0v6MrNm+ssl+I81lOW2tiYdi3yhmprprJ3OpH/9/N9LS9K1ZSaA3pn0bkueOF8a sjXOvA/tTrpOl1nbgS2ladjAmWTnvk+55r9JW2fSuy4ZfC6p7SmzVKOvlF0ehmdLuLfOnXtt02Ud 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 --=-=-=-- From unknown Sat Jun 14 03:57:42 2025 X-Loop: help-debbugs@gnu.org Subject: [bug#40993] cuirass: Add build products download support. Resent-From: Danny Milosavljevic Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Fri, 01 May 2020 10:10:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 40993 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: Mathieu Othacehe Cc: ludo@gnu.org, 40993@debbugs.gnu.org Received: via spool by 40993-submit@debbugs.gnu.org id=B40993.158832776231621 (code B ref 40993); Fri, 01 May 2020 10:10:02 +0000 Received: (at 40993) by debbugs.gnu.org; 1 May 2020 10:09:22 +0000 Received: from localhost ([127.0.0.1]:48271 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jUSbi-0008Dx-E2 for submit@debbugs.gnu.org; Fri, 01 May 2020 06:09:22 -0400 Received: from dd26836.kasserver.com ([85.13.145.193]:36446) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jUSbh-0008Dp-Cw for 40993@debbugs.gnu.org; Fri, 01 May 2020 06:09:21 -0400 Received: from localhost (80-110-127-207.cgn.dynamic.surfer.at [80.110.127.207]) by dd26836.kasserver.com (Postfix) with ESMTPSA id CE81633616E0; Fri, 1 May 2020 12:09:19 +0200 (CEST) Date: Fri, 1 May 2020 12:09:14 +0200 From: Danny Milosavljevic Message-ID: <20200501120914.606ffe02@scratchpost.org> In-Reply-To: <87ees4uja7.fsf@gmail.com> References: <87ees4uja7.fsf@gmail.com> X-Mailer: Claws Mail 3.17.5 (GTK+ 2.24.32; x86_64-unknown-linux-gnu) MIME-Version: 1.0 Content-Type: multipart/signed; boundary="Sig_/Hq4UQoKH2mNRxuI0uCwnvFX"; protocol="application/pgp-signature"; micalg=pgp-sha256 X-Spam-Score: -0.7 (/) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.7 (-) --Sig_/Hq4UQoKH2mNRxuI0uCwnvFX Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: quoted-printable Hi Mathieu, very cool! Though I agree using sendfile would be much better, especially since the us= er can download 800 MB image files there. The guile (web server) module allows passing a procedure as the #:body, but then it makes a bytevector out of the result and hard-codes the content-typ= e :P. Eventually (web server http) http-write is reached, which only supports enc= oding bytevectors and #f, that's it. No files. So we'd have to overwrite http-write. But we are using our own (web server fiberized) impl already. So our impl chould be extended to be able to get and process FDs. client-loop there has (lambda (response body) (write-response response client) (when body (put-bytevector client body)) which means the "when body" part should be extended to also handle files, n= ot just bytevectors. --Sig_/Hq4UQoKH2mNRxuI0uCwnvFX Content-Type: application/pgp-signature Content-Description: OpenPGP digital signature -----BEGIN PGP SIGNATURE----- iQEzBAEBCAAdFiEEds7GsXJ0tGXALbPZ5xo1VCwwuqUFAl6r9UoACgkQ5xo1VCww uqV0Ygf7BNjc81KkTQWXt1QmYRHfzdoPbd+ejt9vYvY4GNmF8IgKMZkPcZoHxPDH Us3I1WRknemIK0hDngXqaRaq6UBaPUQ1TntES0CcmGmpe3CK3UZCEqlgIVwXCIOI GIzgJm7y1zLaTNc7SiL0lSzbOSGtBSyunyQVFchpqKdBNTVgDiYcbhSHNrNSqPxq /+OCuRv1gHM8MGBZFQpUJh4ehQp2O/JXAgyJD2DGKDdHFIR15rn/Voel6DQTLdiJ //vPJWAu/BNhOeNuvvSm/ZagbRnCAfLRQBp2E6Vyu7UO72gIXYwSMn/IwIQyZW+W 76TLr3lzKkVl/KwVFKav2qzbZADFRA== =AN9j -----END PGP SIGNATURE----- --Sig_/Hq4UQoKH2mNRxuI0uCwnvFX-- From unknown Sat Jun 14 03:57:42 2025 X-Loop: help-debbugs@gnu.org Subject: [bug#40993] cuirass: Add build products download support. Resent-From: Mathieu Othacehe Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Fri, 01 May 2020 13:37:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 40993 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: Danny Milosavljevic Cc: ludo@gnu.org, 40993@debbugs.gnu.org Received: via spool by 40993-submit@debbugs.gnu.org id=B40993.158834016228351 (code B ref 40993); Fri, 01 May 2020 13:37:01 +0000 Received: (at 40993) by debbugs.gnu.org; 1 May 2020 13:36:02 +0000 Received: from localhost ([127.0.0.1]:48452 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jUVph-0007N7-L3 for submit@debbugs.gnu.org; Fri, 01 May 2020 09:36:02 -0400 Received: from mail-wm1-f42.google.com ([209.85.128.42]:52595) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jUVpe-0007Md-H1 for 40993@debbugs.gnu.org; Fri, 01 May 2020 09:35:59 -0400 Received: by mail-wm1-f42.google.com with SMTP id 188so5971677wmc.2 for <40993@debbugs.gnu.org>; Fri, 01 May 2020 06:35:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version; bh=nPx3f8qRTuuusvdvoSKde1n43sJ99f97tSp4VCb8pMU=; b=MJ3I3o1UzIsbQoWYOjdv4lhJNPfq6b0XwD9AsfqS//BFnMqjAEnfwYvzQVeXC2gsQo Qwq4s0NMBmv7zQvyc89PoY8DqMLPz/qQIS+nB59EW85+EyNGoxC7qwucx2w4OJkCyliM ppdKWJVN/TTnVlWdAMPprr5Nv7E5IhuWwhFfFb4rJDmvNDHisBkE84nZbLSifOHEB83F DNQ8AsY+BMyRkswRmi9uKFshjhuI8Jb3j0+0l7ZPFIriaVrqT5OVvcSM49WPB3Dq1Ykf WeYc8lHAoRr2rGHkfSbNYzYBMVpLUhm8ZnkDsjwEWcSrDbOC6B3dCWu7RS9iLq6vcATg YUoA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to :message-id:user-agent:mime-version; bh=nPx3f8qRTuuusvdvoSKde1n43sJ99f97tSp4VCb8pMU=; b=Nlr1p2UowmLwiANBBiJvEG7DAHjTIDhTwX1BJ3CQsQpCeSN8eoNAvpFAmV9ywZ8law wwDKfCEcdfct5BGXxVBItJQaHfBdvkj2aGcH6zjWzbXN5uI18YkNlT2PcVA7H4m+9iMt Ksxp2KX7kIsSVLK6qwbAmXUpA+42LLz9wKEJyQByenXHV5nmc8vawHjpZRUH4hq7nR34 axMbTSVhusIughL83Nb5jYLf5IcMaw++w3izccfDR7zcHikGN/qHFeFVh1DE9y7mzH63 INSCvpnwQDt0mS4Z6hcZ6D/LADacTaSfm+u5mXaSRvJcj5/4Ey9bC/K2XMLtrkEWzfPw R4MA== X-Gm-Message-State: AGi0PubPc69e/O/PqcE4mt/EFOteHxcBQ/T8j4oJZC/Y5lLfdKKHu3mI xhcGBDa5HGqMaykV9MbmBnE= X-Google-Smtp-Source: APiQypIJz8gvWh68Cqe5Fnv7arBByLp374a8OWcyac2Iug40B8+xZrzMKEdiJI4wg6MIBsqZBFhwLg== X-Received: by 2002:a05:600c:2314:: with SMTP id 20mr4294486wmo.118.1588340152544; Fri, 01 May 2020 06:35:52 -0700 (PDT) Received: from meru ([2a01:cb18:832e:5f00:5142:5b8b:1d3f:ecc5]) by smtp.gmail.com with ESMTPSA id r2sm3896324wmg.2.2020.05.01.06.35.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 01 May 2020 06:35:51 -0700 (PDT) From: Mathieu Othacehe References: <87ees4uja7.fsf@gmail.com> <20200501120914.606ffe02@scratchpost.org> Date: Fri, 01 May 2020 15:35:50 +0200 In-Reply-To: <20200501120914.606ffe02@scratchpost.org> (Danny Milosavljevic's message of "Fri, 1 May 2020 12:09:14 +0200") Message-ID: <874ksz4w21.fsf@gmail.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: 0.0 (/) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) --=-=-= Content-Type: text/plain Hey Danny, > very cool! Thanks :) > Though I agree using sendfile would be much better, especially since the user > can download 800 MB image files there. > > The guile (web server) module allows passing a procedure as the #:body, but > then it makes a bytevector out of the result and hard-codes the content-type :P. > > Eventually (web server http) http-write is reached, which only supports encoding > bytevectors and #f, that's it. No files. > > So we'd have to overwrite http-write. > > But we are using our own (web server fiberized) impl already. > > So our impl chould be extended to be able to get and process FDs. > > client-loop there has > > (lambda (response body) > (write-response response client) > (when body > (put-bytevector client body)) > > which means the "when body" part should be extended to also handle files, not just bytevectors. The problem is that even with our fiberized implementation, what we pass as "body" is checked in "sanitize-response" procedure of Guile's (web server) module. With the (very) hacky patch attached, I fool sanitize-response, by sending the file name as a bytevector. This allows me to save gigabytes of RAM when downloading disk images. WDYT? Thanks, Mathieu --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0001-cuirass-Use-sendfiles-instead-of-raw-copies.patch >From 0c5e91c170639d50d1cc339fa0b0e68ea4fba68c Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Fri, 1 May 2020 15:03:12 +0200 Subject: [PATCH] cuirass: Use sendfiles instead of raw copies. * src/cuirass/http.scm (respond-file): Send the file name as an UTF8 bytevector, instead of the raw file content, (respond-gzipped-file): ditto. Also set 'content-disposition header. * src/web/server/fiberized.scm (client-loop): Check if 'content-disposition is set. If it's the case, assume that the bytevector is the file name, and use sendfiles to send it. Otherwise, keep the existing behaviour and send directly the received bytevector. --- src/cuirass/http.scm | 25 ++++++++++--------------- src/web/server/fiberized.scm | 21 +++++++++++++++++++-- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/cuirass/http.scm b/src/cuirass/http.scm index 79fa246..bdc780c 100644 --- a/src/cuirass/http.scm +++ b/src/cuirass/http.scm @@ -40,7 +40,8 @@ #:use-module (web uri) #:use-module (fibers) #:use-module (fibers channels) - #:use-module ((rnrs bytevectors) #:select (utf8->string)) + #:use-module ((rnrs bytevectors) #:select (utf8->string + string->utf8)) #:use-module (sxml simple) #:use-module (cuirass templates) #:use-module (guix utils) @@ -246,19 +247,14 @@ Hydra format." "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd") (sxml->xml body port)))) - (define* (respond-file file - #:key name) + (define* (respond-file file) (let ((content-type (or (assoc-ref %file-mime-types (file-extension file)) '(application/octet-stream)))) (respond `((content-type . ,content-type) - ,@(if name - `((content-disposition - . (form-data (filename . ,name)))) - '())) - ;; FIXME: FILE is potentially big so it'd be better to not load - ;; it in memory and instead 'sendfile' it. - #:body (call-with-input-file file get-bytevector-all)))) + (content-disposition + . (form-data (filename . ,(basename file))))) + #:body (string->utf8 file)))) (define (respond-static-file path) ;; PATH is a list of path components @@ -273,10 +269,9 @@ Hydra format." (define (respond-gzipped-file file) ;; Return FILE with 'gzip' content-encoding. (respond `((content-type . (text/plain (charset . "UTF-8"))) - (content-encoding . (gzip))) - ;; FIXME: FILE is potentially big so it'd be better to not load - ;; it in memory and instead 'sendfile' it. - #:body (call-with-input-file file get-bytevector-all))) + (content-encoding . (gzip)) + (content-disposition . (form-data (filename . ,file)))) + #:body (string->utf8 file))) (define (respond-build-not-found build-id) (respond-json-with-error @@ -521,7 +516,7 @@ Hydra format." (('GET "download" id) (let ((path (db-get-build-product-path id))) - (respond-file path #:name (basename path)))) + (respond-file path))) (('GET "static" path ...) (respond-static-file path)) diff --git a/src/web/server/fiberized.scm b/src/web/server/fiberized.scm index 308b642..68ae132 100644 --- a/src/web/server/fiberized.scm +++ b/src/web/server/fiberized.scm @@ -37,6 +37,7 @@ #:use-module (web request) #:use-module (web response) #:use-module (web server) + #:use-module ((rnrs bytevectors) #:select (utf8->string)) #:use-module (ice-9 binary-ports) #:use-module (ice-9 match) #:use-module (fibers) @@ -92,6 +93,8 @@ ((0) (memq 'keep-alive (response-connection response))))) (else #f))))) +(define extend-response (@@ (web server) extend-response)) + (define (client-loop client have-request) ;; Always disable Nagle's algorithm, as we handle buffering ;; ourselves. @@ -119,9 +122,23 @@ #:headers '((content-length . 0))) #vu8())))) (lambda (response body) - (write-response response client) (when body - (put-bytevector client body)) + (let* ((headers (response-headers response)) + (file? (assq-ref headers 'content-disposition)) + (file (and file? (utf8->string body))) + (file-size (and file? (stat:size (stat file))))) + (cond + (file? + (call-with-input-file file + (lambda (port) + (write-response + (extend-response response 'content-length + file-size) + client) + (sendfile client port file-size)))) + (else + (write-response response client) + (put-bytevector client body))))) (force-output client) (if (and (keep-alive? response) (not (eof-object? (peek-char client)))) -- 2.26.0 --=-=-=-- From unknown Sat Jun 14 03:57:42 2025 X-Loop: help-debbugs@gnu.org Subject: [bug#40993] cuirass: Add build products download support. Resent-From: Ludovic =?UTF-8?Q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Fri, 01 May 2020 21:13:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 40993 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: Danny Milosavljevic Cc: Mathieu Othacehe , 40993@debbugs.gnu.org Received: via spool by 40993-submit@debbugs.gnu.org id=B40993.158836752427935 (code B ref 40993); Fri, 01 May 2020 21:13:02 +0000 Received: (at 40993) by debbugs.gnu.org; 1 May 2020 21:12:04 +0000 Received: from localhost ([127.0.0.1]:50796 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jUcwn-0007Fq-PX for submit@debbugs.gnu.org; Fri, 01 May 2020 17:12:04 -0400 Received: from eggs.gnu.org ([209.51.188.92]:37278) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jUcwl-0007Fb-PD for 40993@debbugs.gnu.org; Fri, 01 May 2020 17:11:48 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:37934) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jUcwg-0000K9-3y; Fri, 01 May 2020 17:11:42 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=42732 helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jUcwf-0005GK-Io; Fri, 01 May 2020 17:11:41 -0400 From: Ludovic =?UTF-8?Q?Court=C3=A8s?= References: <87ees4uja7.fsf@gmail.com> <20200501120914.606ffe02@scratchpost.org> X-URL: http://www.fdn.fr/~lcourtes/ X-Revolutionary-Date: 13 =?UTF-8?Q?Flor=C3=A9al?= an 228 de la =?UTF-8?Q?R=C3=A9volution?= X-PGP-Key-ID: 0x090B11993D9AEBB5 X-PGP-Key: http://www.fdn.fr/~lcourtes/ludovic.asc X-PGP-Fingerprint: 3CE4 6455 8A84 FDC6 9DB4 0CFB 090B 1199 3D9A EBB5 X-OS: x86_64-pc-linux-gnu Date: Fri, 01 May 2020 23:11:38 +0200 In-Reply-To: <20200501120914.606ffe02@scratchpost.org> (Danny Milosavljevic's message of "Fri, 1 May 2020 12:09:14 +0200") Message-ID: <87d07ne4xh.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) Hi, Danny Milosavljevic skribis: > Though I agree using sendfile would be much better, especially since the = user > can download 800 MB image files there. > > The guile (web server) module allows passing a procedure as the #:body, b= ut > then it makes a bytevector out of the result and hard-codes the content-t= ype :P. > > Eventually (web server http) http-write is reached, which only supports e= ncoding > bytevectors and #f, that's it. No files. > > So we'd have to overwrite http-write. See how =E2=80=98guix publish=E2=80=99 uses =E2=80=98sendfile=E2=80=99 for = nars. It=E2=80=99s hacky because it works around . Ludo=E2=80=99. From unknown Sat Jun 14 03:57:42 2025 X-Loop: help-debbugs@gnu.org Subject: [bug#40993] cuirass: Add build products download support. Resent-From: Ludovic =?UTF-8?Q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Fri, 01 May 2020 21:18:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 40993 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: Mathieu Othacehe Cc: Danny Milosavljevic , 40993@debbugs.gnu.org Received: via spool by 40993-submit@debbugs.gnu.org id=B40993.158836783928547 (code B ref 40993); Fri, 01 May 2020 21:18:01 +0000 Received: (at 40993) by debbugs.gnu.org; 1 May 2020 21:17:19 +0000 Received: from localhost ([127.0.0.1]:50804 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jUd27-0007QN-CM for submit@debbugs.gnu.org; Fri, 01 May 2020 17:17:19 -0400 Received: from eggs.gnu.org ([209.51.188.92]:39526) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jUd25-0007Q6-EY for 40993@debbugs.gnu.org; Fri, 01 May 2020 17:17:17 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:38034) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jUd1z-0004bX-Vk; Fri, 01 May 2020 17:17:12 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=42742 helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jUd1z-0005mH-6V; Fri, 01 May 2020 17:17:11 -0400 From: Ludovic =?UTF-8?Q?Court=C3=A8s?= References: <87ees4uja7.fsf@gmail.com> <20200501120914.606ffe02@scratchpost.org> <874ksz4w21.fsf@gmail.com> X-URL: http://www.fdn.fr/~lcourtes/ X-Revolutionary-Date: 13 =?UTF-8?Q?Flor=C3=A9al?= an 228 de la =?UTF-8?Q?R=C3=A9volution?= X-PGP-Key-ID: 0x090B11993D9AEBB5 X-PGP-Key: http://www.fdn.fr/~lcourtes/ludovic.asc X-PGP-Fingerprint: 3CE4 6455 8A84 FDC6 9DB4 0CFB 090B 1199 3D9A EBB5 X-OS: x86_64-pc-linux-gnu Date: Fri, 01 May 2020 23:17:09 +0200 In-Reply-To: <874ksz4w21.fsf@gmail.com> (Mathieu Othacehe's message of "Fri, 01 May 2020 15:35:50 +0200") Message-ID: <871ro3e4oa.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Hello! Mathieu Othacehe skribis: > With the (very) hacky patch attached, I fool sanitize-response, by > sending the file name as a bytevector. This allows me to save gigabytes > of RAM when downloading disk images. Yay! This is similar to what =E2=80=98guix publish=E2=80=99 does. :-) > From 0c5e91c170639d50d1cc339fa0b0e68ea4fba68c Mon Sep 17 00:00:00 2001 > From: Mathieu Othacehe > Date: Fri, 1 May 2020 15:03:12 +0200 > Subject: [PATCH] cuirass: Use sendfiles instead of raw copies. > > * src/cuirass/http.scm (respond-file): Send the file name as an UTF8 > bytevector, instead of the raw file content, > (respond-gzipped-file): ditto. Also set 'content-disposition header. > * src/web/server/fiberized.scm (client-loop): Check if 'content-dispositi= on is > set. If it's the case, assume that the bytevector is the file name, and u= se > sendfiles to send it. Otherwise, keep the existing behaviour and send dir= ectly > the received bytevector. > +(define extend-response (@@ (web server) extend-response)) @@ is evil and it=E2=80=99s not guaranteed to work with Guile 3: the proced= ure might be inlined. But you can use these =E2=80=98guix publish=E2=80=99 helper procedures, whi= ch rely on (srfi srfi-9 gnu): (define (strip-headers response) "Return RESPONSE's headers minus 'Content-Length' and our internal head= ers." (fold alist-delete (response-headers response) '(content-length x-raw-file x-nar-compression))) (define (with-content-length response length) "Return RESPONSE with a 'content-length' header set to LENGTH." (set-field response (response-headers) (alist-cons 'content-length length (strip-headers response)))) > + (call-with-input-file file > + (lambda (port) > + (write-response > + (extend-response response 'content-length > + file-size) > + client) > + (sendfile client port file-size)))) I didn=E2=80=99t look at the other patches, but note that =E2=80=98sendfile= =E2=80=99 blocks. Since Cuirass is fiberized, you shouldn=E2=80=99t block a fiber. =E2=80=98guix publish=E2=80=99 doesn=E2=80=99t use Fibers but it shouldn=E2= =80=99t block either while sending a nar, so what it does is spawn a new thread for the =E2=80=98sendf= ile=E2=80=99 call. HTH! Ludo=E2=80=99. From unknown Sat Jun 14 03:57:42 2025 X-Loop: help-debbugs@gnu.org Subject: [bug#40993] cuirass: Add build products download support. Resent-From: Mathieu Othacehe Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 03 Jun 2020 11:55:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 40993 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: Ludovic =?UTF-8?Q?Court=C3=A8s?= Cc: Danny Milosavljevic , 40993@debbugs.gnu.org Received: via spool by 40993-submit@debbugs.gnu.org id=B40993.15911852805016 (code B ref 40993); Wed, 03 Jun 2020 11:55:01 +0000 Received: (at 40993) by debbugs.gnu.org; 3 Jun 2020 11:54:40 +0000 Received: from localhost ([127.0.0.1]:42279 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jgRyi-0001Im-A0 for submit@debbugs.gnu.org; Wed, 03 Jun 2020 07:54:40 -0400 Received: from eggs.gnu.org ([209.51.188.92]:55584) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jgRyg-0001IR-Q5 for 40993@debbugs.gnu.org; Wed, 03 Jun 2020 07:54:39 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:40041) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jgRyb-0004QK-Cm; Wed, 03 Jun 2020 07:54:33 -0400 Received: from [2a01:e0a:fa:a50:7059:39d8:75e4:ec1a] (port=50632 helo=meru) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jgRya-00052g-R4; Wed, 03 Jun 2020 07:54:33 -0400 From: Mathieu Othacehe References: <87ees4uja7.fsf@gmail.com> <20200501120914.606ffe02@scratchpost.org> <874ksz4w21.fsf@gmail.com> <871ro3e4oa.fsf@gnu.org> Date: Wed, 03 Jun 2020 13:54:30 +0200 In-Reply-To: <871ro3e4oa.fsf@gnu.org> ("Ludovic \=\?utf-8\?Q\?Court\=C3\=A8s\=22'\?\= \=\?utf-8\?Q\?s\?\= message of "Fri, 01 May 2020 23:17:09 +0200") Message-ID: <874krs74ax.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" X-Spam-Score: -2.3 (--) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) --=-=-= Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable Hello Ludo, > I didn=E2=80=99t look at the other patches, but note that =E2=80=98sendfi= le=E2=80=99 blocks. > Since Cuirass is fiberized, you shouldn=E2=80=99t block a fiber. > > =E2=80=98guix publish=E2=80=99 doesn=E2=80=99t use Fibers but it shouldn= =E2=80=99t block either while > sending a nar, so what it does is spawn a new thread for the =E2=80=98sen= dfile=E2=80=99 > call. Thanks for your help! I copied what's done in (guix scripts publish), except that I used "non-blocking" instead of using a plain "call-with-new-thread". If you could have a short look to the first patch (introducing build products) and tell me if the concept is ok for you, that would be great :) Thanks, Mathieu --=-=-= Content-Type: text/x-diff Content-Disposition: inline; filename=0001-cuirass-Use-sendfiles-instead-of-raw-copies.patch >From c99cc0314b98e349a577f38870d1271a3f1c3a54 Mon Sep 17 00:00:00 2001 From: Mathieu Othacehe Date: Wed, 3 Jun 2020 13:41:30 +0200 Subject: [PATCH] cuirass: Use sendfiles instead of raw copies. * src/cuirass/http.scm (respond-file): Send the file name as 'x-raw-file header argument, instead of the raw file content, (respond-gzipped-file): ditto. Also set 'content-disposition header. * src/web/server/fiberized.scm (strip-headers, with-content-length): New procedures, (client-loop): Check if 'x-raw-file is set. If it's the case, use sendfiles to send the given file. Otherwise, keep the existing behaviour and send directly the received bytevector. --- src/cuirass/http.scm | 22 ++++++-------- src/web/server/fiberized.scm | 56 +++++++++++++++++++++++++++++------- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/cuirass/http.scm b/src/cuirass/http.scm index 79fa246..0b2f056 100644 --- a/src/cuirass/http.scm +++ b/src/cuirass/http.scm @@ -246,19 +246,14 @@ Hydra format." "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd") (sxml->xml body port)))) - (define* (respond-file file - #:key name) + (define* (respond-file file) (let ((content-type (or (assoc-ref %file-mime-types (file-extension file)) '(application/octet-stream)))) (respond `((content-type . ,content-type) - ,@(if name - `((content-disposition - . (form-data (filename . ,name)))) - '())) - ;; FIXME: FILE is potentially big so it'd be better to not load - ;; it in memory and instead 'sendfile' it. - #:body (call-with-input-file file get-bytevector-all)))) + (content-disposition + . (form-data (filename . ,(basename file)))) + (x-raw-file . ,file))))) (define (respond-static-file path) ;; PATH is a list of path components @@ -273,10 +268,9 @@ Hydra format." (define (respond-gzipped-file file) ;; Return FILE with 'gzip' content-encoding. (respond `((content-type . (text/plain (charset . "UTF-8"))) - (content-encoding . (gzip))) - ;; FIXME: FILE is potentially big so it'd be better to not load - ;; it in memory and instead 'sendfile' it. - #:body (call-with-input-file file get-bytevector-all))) + (content-encoding . (gzip)) + (content-disposition . (form-data (filename . ,file))) + (x-raw-file . ,file)))) (define (respond-build-not-found build-id) (respond-json-with-error @@ -521,7 +515,7 @@ Hydra format." (('GET "download" id) (let ((path (db-get-build-product-path id))) - (respond-file path #:name (basename path)))) + (respond-file path))) (('GET "static" path ...) (respond-static-file path)) diff --git a/src/web/server/fiberized.scm b/src/web/server/fiberized.scm index 308b642..7769202 100644 --- a/src/web/server/fiberized.scm +++ b/src/web/server/fiberized.scm @@ -31,8 +31,12 @@ ;;; Code: (define-module (web server fiberized) - #:use-module ((srfi srfi-1) #:select (fold)) + #:use-module (guix build utils) + #:use-module ((srfi srfi-1) #:select (fold + alist-delete + alist-cons)) #:use-module (srfi srfi-9) + #:use-module (srfi srfi-9 gnu) #:use-module (web http) #:use-module (web request) #:use-module (web response) @@ -41,7 +45,8 @@ #:use-module (ice-9 match) #:use-module (fibers) #:use-module (fibers channels) - #:use-module (cuirass logging)) + #:use-module (cuirass logging) + #:use-module (cuirass utils)) (define (make-default-socket family addr port) (let ((sock (socket PF_INET SOCK_STREAM 0))) @@ -92,6 +97,19 @@ ((0) (memq 'keep-alive (response-connection response))))) (else #f))))) +;; This procedure and the next one are copied from (guix scripts publish). +(define (strip-headers response) + "Return RESPONSE's headers minus 'Content-Length' and our internal headers." + (fold alist-delete + (response-headers response) + '(content-length x-raw-file x-nar-compression))) + +(define (with-content-length response length) + "Return RESPONSE with a 'content-length' header set to LENGTH." + (set-field response (response-headers) + (alist-cons 'content-length length + (strip-headers response)))) + (define (client-loop client have-request) ;; Always disable Nagle's algorithm, as we handle buffering ;; ourselves. @@ -119,14 +137,32 @@ #:headers '((content-length . 0))) #vu8())))) (lambda (response body) - (write-response response client) - (when body - (put-bytevector client body)) - (force-output client) - (if (and (keep-alive? response) - (not (eof-object? (peek-char client)))) - (loop) - (close-port client))))))))) + (match (assoc-ref (response-headers response) 'x-raw-file) + ((? string? file) + (non-blocking + (call-with-input-file file + (lambda (input) + (let* ((size (stat:size (stat input))) + (response (write-response + (with-content-length response size) + client)) + (output (response-port response))) + (setsockopt client SOL_SOCKET SO_SNDBUF + (* 128 1024)) + (if (file-port? output) + (sendfile output input size) + (dump-port input output)) + (close-port output) + (values)))))) + (#f (begin + (write-response response client) + (when body + (put-bytevector client body)) + (force-output client)) + (if (and (keep-alive? response) + (not (eof-object? (peek-char client)))) + (loop) + (close-port client))))))))))) (lambda (k . args) (catch #t (lambda () (close-port client)) -- 2.26.2 --=-=-=-- From unknown Sat Jun 14 03:57:42 2025 X-Loop: help-debbugs@gnu.org Subject: [bug#40993] cuirass: Add build products download support. Resent-From: Ludovic =?UTF-8?Q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 03 Jun 2020 20:15:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 40993 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: Mathieu Othacehe Cc: Danny Milosavljevic , 40993@debbugs.gnu.org Received: via spool by 40993-submit@debbugs.gnu.org id=B40993.15912152684427 (code B ref 40993); Wed, 03 Jun 2020 20:15:02 +0000 Received: (at 40993) by debbugs.gnu.org; 3 Jun 2020 20:14:28 +0000 Received: from localhost ([127.0.0.1]:44610 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jgZmO-00019J-E5 for submit@debbugs.gnu.org; Wed, 03 Jun 2020 16:14:28 -0400 Received: from eggs.gnu.org ([209.51.188.92]:54278) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jgZmK-000193-KT for 40993@debbugs.gnu.org; Wed, 03 Jun 2020 16:14:27 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:47755) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jgZmE-0006or-Sd; Wed, 03 Jun 2020 16:14:18 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=42058 helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jgZmD-0008Na-Jq; Wed, 03 Jun 2020 16:14:18 -0400 From: Ludovic =?UTF-8?Q?Court=C3=A8s?= References: <87ees4uja7.fsf@gmail.com> <20200501120914.606ffe02@scratchpost.org> <874ksz4w21.fsf@gmail.com> <871ro3e4oa.fsf@gnu.org> <874krs74ax.fsf@gnu.org> X-URL: http://www.fdn.fr/~lcourtes/ X-Revolutionary-Date: 16 Prairial an 228 de la =?UTF-8?Q?R=C3=A9volution?= X-PGP-Key-ID: 0x090B11993D9AEBB5 X-PGP-Key: http://www.fdn.fr/~lcourtes/ludovic.asc X-PGP-Fingerprint: 3CE4 6455 8A84 FDC6 9DB4 0CFB 090B 1199 3D9A EBB5 X-OS: x86_64-pc-linux-gnu Date: Wed, 03 Jun 2020 22:14:16 +0200 In-Reply-To: <874krs74ax.fsf@gnu.org> (Mathieu Othacehe's message of "Wed, 03 Jun 2020 13:54:30 +0200") Message-ID: <87h7vrkiuf.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Hi! Mathieu Othacehe skribis: > From c99cc0314b98e349a577f38870d1271a3f1c3a54 Mon Sep 17 00:00:00 2001 > From: Mathieu Othacehe > Date: Wed, 3 Jun 2020 13:41:30 +0200 > Subject: [PATCH] cuirass: Use sendfiles instead of raw copies. > > * src/cuirass/http.scm (respond-file): Send the file name as 'x-raw-file > header argument, instead of the raw file content, > (respond-gzipped-file): ditto. Also set 'content-disposition header. > * src/web/server/fiberized.scm (strip-headers, with-content-length): New = procedures, > (client-loop): Check if 'x-raw-file is set. If it's the case, use sendfil= es to > send the given file. Otherwise, keep the existing behaviour and send dire= ctly > the received bytevector. If it works for you, LGTM! Ludo=E2=80=99. From unknown Sat Jun 14 03:57:42 2025 X-Loop: help-debbugs@gnu.org Subject: [bug#40993] cuirass: Add build products download support. Resent-From: Ludovic =?UTF-8?Q?Court=C3=A8s?= Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 03 Jun 2020 20:28:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 40993 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: Mathieu Othacehe Cc: 40993@debbugs.gnu.org Received: via spool by 40993-submit@debbugs.gnu.org id=B40993.15912160265630 (code B ref 40993); Wed, 03 Jun 2020 20:28:01 +0000 Received: (at 40993) by debbugs.gnu.org; 3 Jun 2020 20:27:06 +0000 Received: from localhost ([127.0.0.1]:44630 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jgZyY-0001Sh-JE for submit@debbugs.gnu.org; Wed, 03 Jun 2020 16:27:06 -0400 Received: from eggs.gnu.org ([209.51.188.92]:55518) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jgZyW-0001SB-H5 for 40993@debbugs.gnu.org; Wed, 03 Jun 2020 16:27:01 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:48008) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jgZyR-00011k-80; Wed, 03 Jun 2020 16:26:55 -0400 Received: from [2a01:e0a:1d:7270:af76:b9b:ca24:c465] (port=42146 helo=ribbon) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jgZyP-0002Dx-Vi; Wed, 03 Jun 2020 16:26:54 -0400 From: Ludovic =?UTF-8?Q?Court=C3=A8s?= References: <87ees4uja7.fsf@gmail.com> Date: Wed, 03 Jun 2020 22:26:51 +0200 In-Reply-To: <87ees4uja7.fsf@gmail.com> (Mathieu Othacehe's message of "Fri, 01 May 2020 10:54:56 +0200") Message-ID: <875zc7ki9g.fsf@gnu.org> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: -2.3 (--) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -3.3 (---) Hello! Mathieu Othacehe skribis: > Here's a patch adding support for build products downloading in > Cuirass. It is inspired by a similar mechanism in Hydra. Neat! > Attached a screenshot of what I obtained with the following > specification: > > (define hello-master > '((#:name . "guix-master") > (#:load-path-inputs . ()) > (#:package-path-inputs . ()) > (#:proc-input . "guix") > (#:proc-file . "build-aux/cuirass/gnu-system.scm") > (#:proc . cuirass-jobs) > (#:proc-args (subset . "all")) > (#:inputs . (((#:name . "guix") > (#:url . "https://gitlab.com/mothacehe/guix") > (#:load-path . ".") > (#:branch . "master") > (#:no-compile? . #t)))) > (#:build-outputs . (((#:job . "iso9660-image*") > (#:type . "iso") > (#:output . "out") > (#:path . "")))))) For the record, in Hydra, build products would be found if there=E2=80=99s a special =E2=80=98nix-support/hydra-build-products=E2=80=99 file in the outp= ut. The advantage is that it=E2=80=99s more flexible, but the downside is that you= =E2=80=99d have to adjust your derivations specifically for that. >>>From dbb78929d7c8aa3b9007660795f55232ab47dbfb Mon Sep 17 00:00:00 2001 > From: Mathieu Othacehe > Date: Fri, 1 May 2020 10:32:18 +0200 > Subject: [PATCH] Add support for build products downloading. > > * src/sql/upgrade-7.sql: New file. > * Makefile.am: Add it. > * src/cuirass/base.scm (create-build-outputs): New procedure, > (build-packages): call it, > (process-spec): add the new spec argument and pass it to create-build-out= puts. > * src/cuirass/database.scm (db-add-build-product, db-get-build-product-pa= th, > db-get-build-products): New exported procedures. > * src/cuirass/http.scm (respond-static-file): Move file sending to ... > (respond-file): ... this new procedure, > (url-handler): add a new "download/" route, serving the requested file > with the new respond-file procedure. Also gather build products and pass = them > to "build-details" for "build//details" route. > * src/cuirass/templates.scm (build-details): Honor the new "products" arg= ument > to display all the build products associated to the given build. > * src/schema.sql (BuildProducts): New table, > (Specifications)[build_outputs]: new field. > * tests/database.scm: Add empty build-outputs spec. > * tests/http.scm: Ditto. > * examples/guix-jobs.scm: Ditto. > * examples/hello-git.scm: Ditto. > * examples/hello-singleton.scm: Ditto. > * examples/hello-subset.scm: Ditto. > * examples/random.scm: Ditto. > * doc/cuirass.texi (overview): Document it. [...] > + (map (lambda (spec) > + (let* ((build (find-build (assq-ref spec #:job))) > + (product (find-product build spec))) > + (when (and product (file-exists? product)) > + (db-add-build-product `((#:build . ,(assq-ref build #:id)) > + (#:type . (assq-ref spec #:type)) > + (#:file-size . ,(file-size product)) > + ;; TODO: Implement it. > + (#:sha256-hash . "") > + (#:path . ,product)))))) > + product-specs)) Use =E2=80=98for-each=E2=80=99 if it=E2=80=99s for effects, as seems to be = the case. Regarding #:sha256-hash: there=E2=80=99s a somewhat standard format to repr= esent hashes and their algorithms as strings, but I forgot the name. Like, you=E2=80=99d write =E2=80=9Csha256-=E2=80=9D followed by a base64 string, = something like that. Perhaps it=E2=80=99d be wiser to use it rather than hard-code sha256? Also, we don=E2=80=99t really have tests for the web UI, I don=E2=80=99t kn= ow how much work it=E2=80=99d be to add tests. Apart from that, I have little to say, other than the fact that it=E2=80=99s really cool. :-) Thank you! Ludo=E2=80=99. From unknown Sat Jun 14 03:57:42 2025 X-Loop: help-debbugs@gnu.org Subject: [bug#40993] cuirass: Add build products download support. Resent-From: Mathieu Othacehe Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Wed, 10 Jun 2020 15:45:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 40993 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: Ludovic =?UTF-8?Q?Court=C3=A8s?= Cc: 40993@debbugs.gnu.org Received: via spool by 40993-submit@debbugs.gnu.org id=B40993.15918038836787 (code B ref 40993); Wed, 10 Jun 2020 15:45:01 +0000 Received: (at 40993) by debbugs.gnu.org; 10 Jun 2020 15:44:43 +0000 Received: from localhost ([127.0.0.1]:35137 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jj2uA-0001lP-SA for submit@debbugs.gnu.org; Wed, 10 Jun 2020 11:44:43 -0400 Received: from mail-wm1-f41.google.com ([209.85.128.41]:56081) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jj2u8-0001lB-Fj for 40993@debbugs.gnu.org; Wed, 10 Jun 2020 11:44:41 -0400 Received: by mail-wm1-f41.google.com with SMTP id c71so2258170wmd.5 for <40993@debbugs.gnu.org>; Wed, 10 Jun 2020 08:44:40 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:references:date:in-reply-to:message-id :user-agent:mime-version:content-transfer-encoding; bh=nRMFeP048dGFaEQxN0zV6JW68ekM8Mouj3bbgJ/3UDQ=; b=PWp7L3M83l6WHjMGXcLASmOOZDuy8EFHUq+3qIOI8FyCSDBqxcFKdyj267KKd/SzN6 ufPlB5bsVLYuZSSGawj/6Po70NFN1/PV+xj6vLy2UN82XuWm9kDCAFSIMHdwYVkLdbhq YcUf68q5kJPbYARqowdu5AGkbym04Fc9YtCztR/c019NeH3vy7hcnjtZPL2Aymkw3160 ee8d6c4BcT+jiIM+7Bz4SoWm9bDKDsFnhUF+GzOsZuosPBU8YGgAQ2tfJUk2kf8M3mSn N9KdnMnyjkWOyBhX7JPLOa5XrMiDmoQRoWtjzWhVn008A/8zEd5gae4OoMzNoGosnfYS e3yQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:references:date:in-reply-to :message-id:user-agent:mime-version:content-transfer-encoding; bh=nRMFeP048dGFaEQxN0zV6JW68ekM8Mouj3bbgJ/3UDQ=; b=tpdJ5dvJRLfSShmfHE0qKG2f4BupygUuWummfNVgCitN4zXBOrKKdmfkEkTNAxfkwd QWs7XTXWHSVGHf5Fe5fETfDYEqGasY+fNCpJ3AV1XuwmlDtg0pFXWpWNMnYWS7/3OC/m b3wAVLmgCL7FdwNcX8B1MTN6nxFE7SeOZbDtg+DhA7we2yo9SgFJtDyYiO0IkKE0u1Nt u2874gor53GeFH5gn+rSPu/AZySaqywQGHyR/vOuRNfkwtBudGDlSejUvWW1q6S5qPGl oO4mUO29HUYUJJGaqUh8x1EBU/J3PFOg/5i8ErMyJdpxeWss5/7YterJqNGDi5zPPRiD Vbew== X-Gm-Message-State: AOAM530gSnEvonClS/b8fLVpQ4LbRNnFKmRdoaKqjXHSZRr6Dy4WCQQC 4cm2y6TcvX9h+y1cDrqpP3Xjqh9a1yg= X-Google-Smtp-Source: ABdhPJxwska1CxjaefUNDtA4NwCBaKZJBtH9b3sbO1CDtevsZb9F9rj/qHbGmwUEPft8sOPyxccuxA== X-Received: by 2002:a1c:a74d:: with SMTP id q74mr3788906wme.177.1591803871139; Wed, 10 Jun 2020 08:44:31 -0700 (PDT) Received: from meru (lfbn-ann-1-136-86.w86-200.abo.wanadoo.fr. [86.200.104.86]) by smtp.gmail.com with ESMTPSA id b14sm153229wmb.20.2020.06.10.08.44.29 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 10 Jun 2020 08:44:30 -0700 (PDT) From: Mathieu Othacehe References: <87ees4uja7.fsf@gmail.com> <875zc7ki9g.fsf@gnu.org> Date: Wed, 10 Jun 2020 17:44:29 +0200 In-Reply-To: <875zc7ki9g.fsf@gnu.org> ("Ludovic \=\?utf-8\?Q\?Court\=C3\=A8s\=22'\?\= \=\?utf-8\?Q\?s\?\= message of "Wed, 03 Jun 2020 22:26:51 +0200") Message-ID: <87h7vjlyci.fsf@gmail.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/26.3 (gnu/linux) MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: quoted-printable X-Spam-Score: 0.0 (/) X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -1.0 (-) Hola! > For the record, in Hydra, build products would be found if there=E2=80=99= s a > special =E2=80=98nix-support/hydra-build-products=E2=80=99 file in the ou= tput. The > advantage is that it=E2=80=99s more flexible, but the downside is that yo= u=E2=80=99d > have to adjust your derivations specifically for that. Yes, I first hesitated to replicate this mechanism but finally opted for the implemented one. I didn't like so much the idea of adding and extra, CI-specific file, to the output of some jobs. > Use =E2=80=98for-each=E2=80=99 if it=E2=80=99s for effects, as seems to b= e the case. Oh you keep repeating that to me, and I keep doing the same mistake! > Regarding #:sha256-hash: there=E2=80=99s a somewhat standard format to re= present > hashes and their algorithms as strings, but I forgot the name. Like, > you=E2=80=99d write =E2=80=9Csha256-=E2=80=9D followed by a base64 string= , something like that. > > Perhaps it=E2=80=99d be wiser to use it rather than hard-code sha256? Yes sure, I changed the database field to "checksum" so that we can use the string representation you're talking about. > Also, we don=E2=80=99t really have tests for the web UI, I don=E2=80=99t = know how much > work it=E2=80=99d be to add tests. If a more web-aware hacker could propose something, it would be great indeed :) > Apart from that, I have little to say, other than the fact that it=E2=80= =99s > really cool. :-) Thanks for reviewing! I just pushed those two commits and updated the Cuirass package. Now I'll see with Marius or Ricardo how to update Berlin, I guess. Thanks, Mathieu From debbugs-submit-bounces@debbugs.gnu.org Wed Jun 10 11:53:44 2020 Received: (at control) by debbugs.gnu.org; 10 Jun 2020 15:53:44 +0000 Received: from localhost ([127.0.0.1]:35158 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jj32u-000200-1K for submit@debbugs.gnu.org; Wed, 10 Jun 2020 11:53:44 -0400 Received: from eggs.gnu.org ([209.51.188.92]:43016) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1jj2ua-0001mf-6U for control@debbugs.gnu.org; Wed, 10 Jun 2020 11:45:08 -0400 Received: from fencepost.gnu.org ([2001:470:142:3::e]:43004) by eggs.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1jj2uS-0001Vy-0j for control@debbugs.gnu.org; Wed, 10 Jun 2020 11:45:02 -0400 Received: from lfbn-ann-1-136-86.w86-200.abo.wanadoo.fr ([86.200.104.86]:51402 helo=meru) by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256) (Exim 4.82) (envelope-from ) id 1jj2uR-0001U8-8e for control@debbugs.gnu.org; Wed, 10 Jun 2020 11:44:59 -0400 Date: Wed, 10 Jun 2020 17:44:55 +0200 Message-Id: <87d067lybs.fsf@meru.i-did-not-set--mail-host-address--so-tickle-me> To: control@debbugs.gnu.org From: Mathieu Othacehe Subject: control message for bug #40993 X-Spam-Score: -1.9 (-) X-Debbugs-Envelope-To: control X-Mailman-Approved-At: Wed, 10 Jun 2020 11:53:42 -0400 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: debbugs-submit-bounces@debbugs.gnu.org Sender: "Debbugs-submit" X-Spam-Score: -2.9 (--) close 40993 quit