From 1655930d9f4da8a70103d904d399f12be5cffabb Mon Sep 17 00:00:00 2001 From: Arne Babenhauserheide Date: Sun, 31 Aug 2025 22:31:16 +0200 Subject: [PATCH] graph: Add cyclonedx-json backend. Validated with: guix graph guile -b cyclonedx-json > /tmp/cyc.json && \ docker run -v /tmp/:/tmp/ cyclonedx/cyclonedx-cli validate --input-format json --input-file /tmp/cyc.json * guix/graph.scm (emit-cyclonedx-prologue, emit-cyclonedx-epilogue, emit-cyclonedx-node, emit-cyclonedx-edge): New procedures. (%cyclonedx-backend): New variable. (%graph-backends): Add %cyclonedx-backend. Change-Id: Icc8c33cbc08da0137489d13bdad618ef55a14923 --- guix/graph.scm | 73 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/guix/graph.scm b/guix/graph.scm index e661c95467221929c5ab066244bdcb0c1317a783..42288062da06d5a2cb7482c34bd683e1de62a3b3 100644 --- a/guix/graph.scm +++ b/guix/graph.scm @@ -26,6 +26,7 @@ #:autoload (guix i18n) (G_) #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) + #:use-module (srfi srfi-19) ;; date->string #:use-module (srfi srfi-26) #:use-module (srfi srfi-34) #:use-module (ice-9 match) @@ -256,6 +257,75 @@ NODE1 to NODE2 of the given TYPE. Return #f when there is no path." emit-prologue emit-epilogue emit-node emit-edge)) + +;;; +;;; SBOM CycloneDX JSON export. +;;; +;;; Schema: https://cyclonedx.org/docs/1.6/json/#metadata_tools_oneOf_i0_components_items_version +;;; + + +(define (emit-cyclonedx-prologue name port) + (format port "{ + \"bomFormat\": \"CycloneDX\", + \"specVersion\": \"1.6\", + \"metadata\": { + \"timestamp\": \"~a\", + \"tools\": { + \"components\": [ + { + \"type\": \"operating-system\", + \"name\": \"guix\" + }, + { + \"type\": \"application\", + \"name\": \"guix-graph\" + } + ] + } + }, + \"components\": [ +" + (date->string (current-date 0) "~5Z"))) ;; UTC time, iso date-time + +(define (emit-cyclonedx-epilogue port) + ;; the safety of each tool built on Guix depends on Guix + (display "\n { + \"type\": \"operating-system\", + \"name\": \"guix\" + } + ]\n}\n" port)) + +(define (emit-cyclonedx-node id label port) + (match (if (string-contains label "@") + (string-split label #\@) + (list label "N/A")) + ((name version) + (format port "\n { + \"type\": \"application\", + \"name\": \"~a\", + \"version\": \"~a\" + }," + name version)) + (else ;; more than one @ + (format port "\n { + \"type\": \"application\", + \"name\": \"~a\", + }," + label)))) + +(define (emit-cyclonedx-edge id1 id2 port) + ;; Left empty: does not include edges at the moment. Adding them as + ;; dependencies would require to include a separator between + ;; nodes and edges. + "") + +(define %cyclonedx-backend + (graph-backend "cyclonedx-json" + "Generate an SBOM in CycloneDX JSON format for use with dependencytrack." + emit-cyclonedx-prologue emit-cyclonedx-epilogue + emit-cyclonedx-node emit-cyclonedx-edge)) + ;;; ;;; d3js export. @@ -375,7 +445,8 @@ nodeArray.push(nodes[\"~a\"]);~%" (list %graphviz-backend %d3js-backend %cypher-backend - %graphml-backend)) + %graphml-backend + %cyclonedx-backend)) (define (lookup-backend name) "Return the graph backend called NAME. Raise an error if it is not found."