ci: Properly construct URLs.

Implement a new function "api-url", which constructs URLs using relative URI
and "resolve-uri-reference" (which implements the algorithm specified in RFC
3986 section 5.2.2) for building the URL, instead of just appending
strings. This avoids issued if the server-url ends with a slash.

Since "api-url" uses URI-objects, it makes sense to also construct the
query-part of the URL here. For this "api-url" accepts optional
key-value-pairs.

New function "json-api-fetch" is a wrapper using "api-url".

* guix/ci.scm (api-url): New function. (build): Use it.
  (json-api-fetch): New function. (queued-builds, latest-builds,
  evaluation, latest-evaluations, evaluation-jobs: Use it.
This commit is contained in:
Hartmut Goebel 2021-07-08 00:52:22 +02:00
parent ccdf7b8006
commit 3ee0f170c8
No known key found for this signature in database
GPG key ID: 634A8DFFD3F631DF

View file

@ -20,9 +20,12 @@
(define-module (guix ci) (define-module (guix ci)
#:use-module (guix http-client) #:use-module (guix http-client)
#:use-module (guix utils) #:use-module (guix utils)
#:use-module ((guix build download)
#:select (resolve-uri-reference))
#:use-module (json) #:use-module (json)
#:use-module (srfi srfi-1) #:use-module (srfi srfi-1)
#:use-module (ice-9 match) #:use-module (ice-9 match)
#:use-module (web uri)
#:use-module (guix i18n) #:use-module (guix i18n)
#:use-module (guix diagnostics) #:use-module (guix diagnostics)
#:autoload (guix channels) (channel) #:autoload (guix channels) (channel)
@ -146,16 +149,44 @@ (define %query-limit
;; Max number of builds requested in queries. ;; Max number of builds requested in queries.
1000) 1000)
(define* (api-url base-url path #:rest query)
"Build a proper API url, taking into account BASE-URL's trailing slashes.
QUERY takes any number of '(\"name\" value) 2-element lists, with VALUE being
either a string or a number (which will be converted to a string). If VALUE
is #f, the respective element will not be added to the query parameters.
Other types of VALUE will raise an error since this low-level function is
api-agnostic."
(define (build-query-string query)
(let lp ((query (or (reverse query) '())) (acc '()))
(match query
(() (string-concatenate acc))
(((_ #f) . rest) (lp rest acc))
(((name val) . rest)
(lp rest (cons*
name "="
(if (string? val) (uri-encode val) (number->string val))
(if (null? acc) "" "&")
acc))))))
(let* ((query-string (build-query-string query))
(base (string->uri base-url))
(ref (build-relative-ref #:path path #:query query-string)))
(resolve-uri-reference ref base)))
(define (json-fetch url) (define (json-fetch url)
(let* ((port (http-fetch url)) (let* ((port (http-fetch url))
(json (json->scm port))) (json (json->scm port)))
(close-port port) (close-port port)
json)) json))
(define* (json-api-fetch base-url path #:rest query)
(json-fetch (apply api-url base-url path query)))
(define* (queued-builds url #:optional (limit %query-limit)) (define* (queued-builds url #:optional (limit %query-limit))
"Return the list of queued derivations on URL." "Return the list of queued derivations on URL."
(let ((queue (json-fetch (string-append url "/api/queue?nr=" (let ((queue
(number->string limit))))) (json-api-fetch url "/api/queue" `("nr" ,limit))))
(map json->build (vector->list queue)))) (map json->build (vector->list queue))))
(define* (latest-builds url #:optional (limit %query-limit) (define* (latest-builds url #:optional (limit %query-limit)
@ -163,28 +194,21 @@ (define* (latest-builds url #:optional (limit %query-limit)
"Return the latest builds performed by the CI server at URL. If EVALUATION "Return the latest builds performed by the CI server at URL. If EVALUATION
is an integer, restrict to builds of EVALUATION. If SYSTEM is true (a system is an integer, restrict to builds of EVALUATION. If SYSTEM is true (a system
string such as \"x86_64-linux\"), restrict to builds for SYSTEM." string such as \"x86_64-linux\"), restrict to builds for SYSTEM."
(define* (option name value #:optional (->string identity)) (let ((latest (json-api-fetch
(if value url "/api/latestbuilds"
(string-append "&" name "=" (->string value)) `("nr" ,limit)
"")) `("evaluation" ,evaluation)
`("system" ,system)
(let ((latest (json-fetch (string-append url "/api/latestbuilds?nr=" `("job" ,job)
(number->string limit) `("status" ,status))))
(option "evaluation" evaluation
number->string)
(option "system" system)
(option "job" job)
(option "status" status
number->string)))))
;; Note: Hydra does not provide a "derivation" field for entries in ;; Note: Hydra does not provide a "derivation" field for entries in
;; 'latestbuilds', but Cuirass does. ;; 'latestbuilds', but Cuirass does.
(map json->build (vector->list latest)))) (map json->build (vector->list latest))))
(define (evaluation url evaluation) (define (evaluation url evaluation)
"Return the given EVALUATION performed by the CI server at URL." "Return the given EVALUATION performed by the CI server at URL."
(let ((evaluation (json-fetch (let ((evaluation
(string-append url "/api/evaluation?id=" (json-api-fetch url "/api/evaluation" `("id" ,evaluation))))
(number->string evaluation)))))
(json->evaluation evaluation))) (json->evaluation evaluation)))
(define* (latest-evaluations url (define* (latest-evaluations url
@ -192,16 +216,10 @@ (define* (latest-evaluations url
#:key spec) #:key spec)
"Return the latest evaluations performed by the CI server at URL. If SPEC "Return the latest evaluations performed by the CI server at URL. If SPEC
is passed, only consider the evaluations for the given SPEC specification." is passed, only consider the evaluations for the given SPEC specification."
(let ((spec (if spec (map json->evaluation
(format #f "&spec=~a" spec) (vector->list
""))) (json-api-fetch
(map json->evaluation url "/api/evaluations" `("nr" ,limit) `("spec" ,spec)))))
(vector->list
(json->scm
(http-fetch
(string-append url "/api/evaluations?nr="
(number->string limit)
spec)))))))
(define* (evaluations-for-commit url commit #:optional (limit %query-limit)) (define* (evaluations-for-commit url commit #:optional (limit %query-limit))
"Return the evaluations among the latest LIMIT evaluations that have COMMIT "Return the evaluations among the latest LIMIT evaluations that have COMMIT
@ -216,16 +234,14 @@ (define (evaluation-jobs url evaluation-id)
"Return the list of jobs of evaluation EVALUATION-ID." "Return the list of jobs of evaluation EVALUATION-ID."
(map json->job (map json->job
(vector->list (vector->list
(json->scm (http-fetch (json-api-fetch url "/api/jobs" `("evaluation" ,evaluation-id)))))
(string-append url "/api/jobs?evaluation="
(number->string evaluation-id)))))))
(define (build url id) (define (build url id)
"Look up build ID at URL and return it. Raise &http-get-error if it is not "Look up build ID at URL and return it. Raise &http-get-error if it is not
found (404)." found (404)."
(json->build (json->build
(http-fetch (string-append url "/build/" ;note: no "/api" here (http-fetch (api-url url (string-append "/build/" ;note: no "/api" here
(number->string id))))) (number->string id))))))
(define (job-build url job) (define (job-build url job)
"Return the build associated with JOB." "Return the build associated with JOB."