From 151ca1d29be71ccf099fae74b3419650892c093e Mon Sep 17 00:00:00 2001 From: Sean Corfield Date: Sat, 13 Jan 2024 15:27:57 -0800 Subject: [PATCH] fix #49 by improving tool invocation (#53) Signed-off-by: Sean Corfield --- CHANGELOG.md | 1 + README.md | 19 ++++- src/clj_watson/cli.clj | 45 ++--------- src/clj_watson/cli_spec.clj | 76 +++++++++++++++++++ .../controller/dependency_check/scanner.clj | 2 +- src/clj_watson/entrypoint.clj | 7 +- 6 files changed, 106 insertions(+), 44 deletions(-) create mode 100644 src/clj_watson/cli_spec.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index d269d77..4ef19e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # CHANGELOG * v5.0.2 in progress + * Address [#49](https://github.com/clj-holmes/clj-watson/issues/49) by improving the `-T` invocation to support short names, symbols for strings, and all the defaults. * Address [#48](https://github.com/clj-holmes/clj-watson/issues/48) by updating all of the project dependencies, including DependencyCheck to 9.0.8. * Address [#47](https://github.com/clj-holmes/clj-watson/issues/47) by printing out the optional properties read from the `clj-watson.properties` file. * Documentation improvements. diff --git a/README.md b/README.md index b3f607d..aa2c627 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,19 @@ clojure -Tclj-watson scan :deps-edn-path '"deps.edn"' :output '"stdout"' clojure -Tclj-watson scan '{:deps-edn-path "deps.edn" :output "stdout"}' ``` -(this is somewhat verbose now but it will be improved over the next few releases) +The tool option keywords match the long-form CLI option names (see below) +but the abbreviations are also supported. In addition, any string option may +be specified as a bare Clojure symbol (if it is legally representable as such), +which means the above command-line can be simplified to just: + +```bash +clojure -Tclj-watson scan :p deps.edn +``` + +`:output` can be omitted because it defaults to `stdout`, and `:deps-edn-path` +can be shortened to `:p` (matching the `-p` short form of `--deps-edn-path`). + +> Note: `:aliases` (or `:a`) should be specified as a vector of keywords (or symbols), e.g., `:a '[:foo :bar]`, whereas it would be specified multiple times (as strings) in the regular CLI, `-a foo -a bar`. # How it works @@ -178,8 +190,13 @@ EDN-style options for the CLI tool which can be a bit unwieldy as present: ```bash clojure -Tclj-watson scan '{:output "stdout" :fail-on-result true :deps-edn-path "deps.edn" :suggest-fix true :aliases ["*"] :database-strategy "dependency-check"}' +# or: +clojure -Tclj-watson scan :f true :p deps.edn :s true :a '[*]' ``` +Both `:output` (`:o`) and `:database-strategy` (`:t`) can be omitted because +they default to `"stdout"` and `"dependency-check"` respectively. + In addition to the CLI tool install, shown above, it can also be invoked directly via the Clojure CLI, by specifying `clj-watson` as a dependency via `-Sdeps`: diff --git a/src/clj_watson/cli.clj b/src/clj_watson/cli.clj index 37dea5c..dd82869 100644 --- a/src/clj_watson/cli.clj +++ b/src/clj_watson/cli.clj @@ -2,46 +2,11 @@ (:gen-class) (:require [cli-matic.core :as cli] + [clj-watson.cli-spec :refer [CONFIGURATION]] [clj-watson.entrypoint :as entrypoint])) -(def CONFIGURATION - {:app {:command "clj-watson" - :description "run clj-holmes" :version (System/getProperty "clj-watson.version")} - :commands [{:command "scan" - :description "Performs a scan on a deps.edn file" - :opts [{:option "deps-edn-path" :short "p" - :type :string - :default :present - :as "path of deps.edn to scan."} - {:option "output" :short "o" - :type #{"json" "edn" "stdout" "stdout-simple" "sarif"} ; keep stdout type to avoid break current automations - :default "stdout" - :as "Output type."} - {:option "aliases" :short "a" - :type :string - :multiple true - :as "Specify a alias that will have the dependencies analysed alongside with the project deps.It's possible to provide multiple aliases. If a * is provided all the aliases are going to be analysed."} - {:option "dependency-check-properties" :short "d" - :type :string - :default nil - :as "[ONLY APPLIED IF USING DEPENDENCY-CHECK STRATEGY] Path of a dependency-check properties file. If not provided uses resources/dependency-check.properties."} - {:option "clj-watson-properties" :short "w" - :type :string - :default nil - :as "[ONLY APPLIED IF USING DEPENDENCY-CHECK STRATEGY] Path of an additional, optional properties file."} - {:option "database-strategy" :short "t" - :type #{"dependency-check" "github-advisory"} - :default "dependency-check" - :as "Vulnerability database strategy."} - {:option "suggest-fix" :short "s" - :type :with-flag - :default false - :as "Suggest a new deps.edn file fixing all vulnerabilities found."} - {:option "fail-on-result" :short "f" - :type :with-flag - :default false - :as "Enable or disable fail if results were found (useful for CI/CD)."}] - :runs entrypoint/scan}]}) - (defn -main [& args] - (cli/run-cmd args CONFIGURATION)) + (cli/run-cmd args + (update-in CONFIGURATION + [:commands 0] + (assoc :runs entrypoint/scan)))) diff --git a/src/clj_watson/cli_spec.clj b/src/clj_watson/cli_spec.clj new file mode 100644 index 0000000..d7a6bfc --- /dev/null +++ b/src/clj_watson/cli_spec.clj @@ -0,0 +1,76 @@ +(ns clj-watson.cli-spec) + +(def CONFIGURATION + {:app {:command "clj-watson" + :description "run clj-holmes" :version (System/getProperty "clj-watson.version")} + :commands [{:command "scan" + :description "Performs a scan on a deps.edn file" + :opts [{:option "deps-edn-path" :short "p" + :type :string + :default :present + :as "path of deps.edn to scan."} + {:option "output" :short "o" + :type #{"json" "edn" "stdout" "stdout-simple" "sarif"} ; keep stdout type to avoid break current automations + :default "stdout" + :as "Output type."} + {:option "aliases" :short "a" + :type :string + :multiple true + :as "Specify a alias that will have the dependencies analysed alongside with the project deps. It's possible to provide multiple aliases. If a * is provided all the aliases are going to be analysed."} + {:option "dependency-check-properties" :short "d" + :type :string + :default nil + :as "[ONLY APPLIED IF USING DEPENDENCY-CHECK STRATEGY] Path of a dependency-check properties file. If not provided uses resources/dependency-check.properties."} + {:option "clj-watson-properties" :short "w" + :type :string + :default nil + :as "[ONLY APPLIED IF USING DEPENDENCY-CHECK STRATEGY] Path of an additional, optional properties file."} + {:option "database-strategy" :short "t" + :type #{"dependency-check" "github-advisory"} + :default "dependency-check" + :as "Vulnerability database strategy."} + {:option "suggest-fix" :short "s" + :type :with-flag + :default false + :as "Suggest a new deps.edn file fixing all vulnerabilities found."} + {:option "fail-on-result" :short "f" + :type :with-flag + :default false + :as "Enable or disable fail if results were found (useful for CI/CD)."}] + ;; injected by clj-watson.cli to avoid circular dependency: + :runs nil #_entrypoint/scan}]}) + +(def DEFAULTS + (into {} + (map (fn [{:keys [option default]}] + [(keyword option) (when-not (= default :present) default)])) + (-> CONFIGURATION :commands first :opts))) + +(def ABBREVIATIONS + (into {} + (map (fn [{:keys [option short]}] + [(keyword short) (keyword option)])) + (-> CONFIGURATION :commands first :opts))) + +(defn clean-options + "Implement defaults for tool invocation and allow for abbreviations and + symbols as strings." + [opts] + (into DEFAULTS + (comp (map (fn [[k v]] ; expand abbreviations first: + (if (and (contains? ABBREVIATIONS k) + (not (contains? opts (get ABBREVIATIONS k)))) + [(get ABBREVIATIONS k) v] + [k v]))) + (map (fn [[k v]] ; supply defaults: + (if (contains? DEFAULTS k) + [k (if (some? v) v (get DEFAULTS k))] + [k v]))) + (map (fn [[k v]] + [k (if (symbol? v) (str v) v)]))) + opts)) + +(comment + (clean-options {:p 'resources/deps.edn + :database-strategy 'dependency-check + :s true})) diff --git a/src/clj_watson/controller/dependency_check/scanner.clj b/src/clj_watson/controller/dependency_check/scanner.clj index 0a73d0c..cd16642 100644 --- a/src/clj_watson/controller/dependency_check/scanner.clj +++ b/src/clj_watson/controller/dependency_check/scanner.clj @@ -53,7 +53,7 @@ (if additional-properties-file-path (->> add-props (.mergeProperties settings)) (some->> add-props slurp .getBytes ByteArrayInputStream. (.mergeProperties settings)))) - (println "No additional properties found.")) + (println "No additional properties found.\n")) settings)) (defn ^:private build-engine [dependency-check-properties clj-watson-properties] diff --git a/src/clj_watson/entrypoint.clj b/src/clj_watson/entrypoint.clj index b63a690..71a243b 100644 --- a/src/clj_watson/entrypoint.clj +++ b/src/clj_watson/entrypoint.clj @@ -1,6 +1,7 @@ (ns clj-watson.entrypoint (:require [clj-watson.adapter.config :as adapter.config] + [clj-watson.cli-spec :as cli-spec] [clj-watson.controller.dependency-check.scanner :as controller.dc.scanner] [clj-watson.controller.dependency-check.vulnerability :as controller.dc.vulnerability] [clj-watson.controller.deps :as controller.deps] @@ -38,8 +39,10 @@ (defmethod scan* :default [opts] (scan* (assoc opts :database-strategy "dependency-check"))) -(defn scan [{:keys [fail-on-result output deps-edn-path] :as opts}] - (let [vulnerabilities (scan* opts) +(defn scan [opts] + (let [opts (cli-spec/clean-options opts) + {:keys [fail-on-result output deps-edn-path]} opts + vulnerabilities (scan* opts) contains-vulnerabilities? (->> vulnerabilities (map (comp empty? :vulnerabilities)) (some false?))]