about summary refs log tree commit diff
path: root/guix/forge/forge.scm
diff options
context:
space:
mode:
Diffstat (limited to 'guix/forge/forge.scm')
-rw-r--r--guix/forge/forge.scm88
1 files changed, 76 insertions, 12 deletions
diff --git a/guix/forge/forge.scm b/guix/forge/forge.scm
index bad6b8b..d60298f 100644
--- a/guix/forge/forge.scm
+++ b/guix/forge/forge.scm
@@ -26,6 +26,7 @@
   #:use-module ((gnu packages gnupg) #:select (guile-gcrypt))
   #:use-module ((gnu packages guile) #:select (guile-3.0 guile-bytestructures guile-zlib))
   #:use-module ((gnu packages guile-xyz) #:select (guile-lib))
+  #:use-module ((gnu packages mail) #:select (msmtp))
   #:use-module ((gnu packages nss) #:select (nss-certs))
   #:use-module ((gnu packages package-management) #:select (guix))
   #:use-module ((gnu packages version-control) #:select (git-minimal))
@@ -49,10 +50,12 @@
   #:export (forge-service-type
             forge-configuration
             forge-configuration?
+            this-forge-configuration
             forge-configuration-web-domain
             forge-configuration-tissue-web-domain
             forge-configuration-websites-directory
             forge-configuration-web-root
+            forge-configuration-mailer-address
             forge-configuration-projects
             forge-project
             forge-project?
@@ -66,6 +69,7 @@
             forge-project-website-ci-job
             forge-project-ci-jobs
             forge-project-ci-jobs-trigger
+            forge-project-ci-notify-addresses
             forge-project-parallel-ci-job-runs
             derivation-job-gexp
             variable-specification
@@ -93,7 +97,6 @@
   (tissue? forge-project-tissue?
            (default #f))
   (website-ci-job forge-project-website-ci-job
-                  ;; TODO: Add default website builder.
                   (default #f)
                   (thunked))
   (ci-jobs forge-project-ci-jobs
@@ -106,6 +109,8 @@
                              ;; 'cron for remote repositories
                              (else 'cron)))
                    (thunked))
+  (ci-notify-addresses forge-project-ci-notify-addresses
+                       (default '()))
   (parallel-ci-job-runs forge-project-parallel-ci-job-runs
                         (default 1)))
 
@@ -122,6 +127,10 @@
             (default (computed-file "forge-web-root"
                                     (forge-web-root-gexp this-forge-configuration)))
             (thunked))
+  (mailer-address forge-configuration-mailer-address
+                  (default (string-append "mail@"
+                                          (forge-configuration-web-domain this-forge-configuration)))
+                  (thunked))
   (projects forge-configuration-projects
             (default '())))
 
@@ -139,14 +148,21 @@
                 `(html
                   (body
                    ,@(map (match-lambda
-                            ((name description)
-                             `((h2 ,name)
+                            ((name description website-link)
+                             `((h2 ,(if website-link
+                                        `(a (@ (href ,website-link))
+                                            ,name)
+                                        name))
                                ,@(if description
                                      `((p ,description))
                                      '()))))
                           '#$(map (lambda (project)
                                     (list (forge-project-name project)
-                                          (forge-project-description project)))
+                                          (forge-project-description project)
+                                          (and (forge-project-website-ci-job project)
+                                               (if (forge-project-web-domain project)
+                                                   (string-append "https://" (forge-project-web-domain project))
+                                                   (string-append "/" (forge-project-name project) "/")))))
                                   (forge-configuration-projects config))))))))
           (call-with-output-file (string-append #$output "/index.html")
             (cut put-string <> html))))))
@@ -194,7 +210,7 @@
                        (string-append (forge-project-name project)
                                       "-post-receive-hook")
                        (ci-jobs-trigger-gexp
-                        (forge-project-ci-jobs project)
+                        (forge-project-all-ci-jobs project config)
                         #:reason "post-receive hook"))
                       (forge-project-ci-jobs-trigger project)))
               (forge-configuration-projects config))))
@@ -560,15 +576,63 @@ forge configuration @var{config}."
                               projects))))
           (list))))
 
+(define (ci-notify-by-email from to)
+  "Return a G-expression that emails @var{to} addresses from @var{from} address
+about the status of a laminar CI job. The returned G-expression is intended to
+be used as the @code{after} field of a @code{<forge-laminar-job>} object."
+  #~(begin
+      (use-modules (ice-9 popen)
+                   (srfi srfi-26))
+
+      (define (call-with-output-pipe command proc)
+        (let ((port #f))
+          (dynamic-wind
+            (cut set! port (apply open-pipe* OPEN_WRITE command))
+            (cut proc port)
+            (lambda ()
+              (unless (zero? (status:exit-val (close-pipe port)))
+                (error "Command invocation failed" command))))))
+
+      (let ((job (getenv "JOB"))
+            (run (getenv "RUN"))
+            (result (getenv "RESULT"))
+            (last-result (getenv "LAST_RESULT")))
+        (unless (string=? result last-result)
+          (call-with-output-pipe (list #$(file-append msmtp "/bin/msmtp")
+                                       "--host=localhost"
+                                       "--port=587"
+                                       "--read-recipients")
+            (cut format
+                 <>
+                 "From: ~a
+To: ~a
+Subject: Laminar ~a #~a: ~a
+
+See https://ci.systemreboot.net/jobs/~a/~a for details.
+"
+                 #$from
+                 #$(string-join to ",")
+                 job run result job run))))))
+
 (define (forge-project-all-ci-jobs project config)
   "Return list of all CI jobs for @var{project} in forge configuration
 @var{config}."
-  ;; Add project context to CI jobs.
   (map (lambda (job)
          (forge-laminar-job
           (inherit job)
+          ;; Add project context to job.
           (contexts (cons (forge-project-name project)
-                          (forge-laminar-job-contexts job)))))
+                          (forge-laminar-job-contexts job)))
+          ;; Add CI email notification to job.
+          (after #~(begin
+                     #$@(cond
+                         ((forge-laminar-job-after job) => list)
+                         (else (list)))
+                     #$(match (forge-project-ci-notify-addresses project)
+                         (() #f)
+                         (addresses
+                          (ci-notify-by-email (forge-configuration-mailer-address config)
+                                              addresses)))))))
        ;; Prepend website CI job to the other CI jobs.
        (if (forge-project-website-ci-job project)
            (cons (forge-laminar-job
@@ -602,7 +666,7 @@ value of the returned list is a @code{<forge-laminar-job>} object."
 @var{config}. Each element of the returned list is a
 @code{<forge-laminar-group>} object."
   (filter-map (lambda (project)
-                (match (forge-project-ci-jobs project)
+                (match (forge-project-all-ci-jobs project config)
                   (() #f)
                   ((job) #f)
                   (jobs
@@ -631,13 +695,13 @@ mcron job specification."
                 (and (eq? (forge-project-ci-jobs-trigger project)
                           'cron)
                      (any forge-laminar-job-trigger?
-                          (forge-project-ci-jobs project))
+                          (forge-project-all-ci-jobs project config))
                      #~(job '(next-day)
                             #$(program-file
                                (string-append (forge-project-name project)
                                               "-cron-job")
                                (ci-jobs-trigger-gexp
-                                (forge-project-ci-jobs project)
+                                (forge-project-all-ci-jobs project config)
                                 #:reason "Cron job"))
                             #:user "laminar")))
               (forge-configuration-projects config)))
@@ -649,11 +713,11 @@ value is a list of @code{<webhook-hook>} objects."
                 (and (eq? (forge-project-ci-jobs-trigger project)
                           'webhook)
                      (any forge-laminar-job-trigger?
-                          (forge-project-ci-jobs project))
+                          (forge-project-all-ci-jobs project config))
                      (webhook-hook
                       (id (forge-project-name project))
                       (run (ci-jobs-trigger-gexp
-                            (forge-project-ci-jobs project)
+                            (forge-project-all-ci-jobs project config)
                             #:reason "Webhook")))))
               (forge-configuration-projects config)))