allow GPG to be invoked unattended with passphrase (+2 squashed commits)
Squashed commits: [81bda1b] allow GPG to be invoked unattended with passphrase [ef54597] allow GPG to be invoked unattended with passphrase
This commit is contained in:
parent
98250defe3
commit
ec921f141e
3 changed files with 79 additions and 25 deletions
|
@ -2,7 +2,6 @@
|
|||
"Functions exposing user-level configuration."
|
||||
(:require [clojure.java.io :as io]
|
||||
[clojure.string :as str]
|
||||
[clojure.java.shell :as shell]
|
||||
[leiningen.core.utils :as utils])
|
||||
(:import (com.hypirion.io Pipe)
|
||||
(org.apache.commons.io.output TeeOutputStream)
|
||||
|
@ -104,9 +103,14 @@
|
|||
[env]
|
||||
(into-array String (map (fn [[k v]] (str (name k) "=" v)) env)))
|
||||
|
||||
(defn gpg
|
||||
"Shells out to (gpg-program) with the given arguments"
|
||||
[& args]
|
||||
(defn gpg-with-passphrase
|
||||
"Shells out to (gpg-program) with the given arguments and, if
|
||||
passphrase is not nil, sends the passphrase on stdin for unattended
|
||||
operations such as signing artifacts for deployment. When a passphrase
|
||||
is provided the caller must include the following args
|
||||
[\"--passphrase-fd\" \"0\" \"--pinentry-mode\" \"loopback\"]
|
||||
along with whatever other args are needed for the gpg command."
|
||||
[passphrase & args]
|
||||
(try
|
||||
(let [proc-env (as-env-strings (get-english-env))
|
||||
proc-args (into-array String (concat [(gpg-program)] args))
|
||||
|
@ -114,10 +118,15 @@
|
|||
(.addShutdownHook (Runtime/getRuntime)
|
||||
(Thread. (fn [] (.destroy proc))))
|
||||
(with-open [out (.getInputStream proc)
|
||||
err (.getErrorStream proc)
|
||||
err-output (ByteArrayOutputStream.)]
|
||||
(let [pump-err (doto (Pipe. (.getErrorStream proc)
|
||||
(TeeOutputStream. System/err err-output))
|
||||
.start)]
|
||||
(if passphrase
|
||||
(with-open [in (.getOutputStream proc)]
|
||||
(io/copy passphrase in)))
|
||||
(let [pump-err (doto (Pipe. err
|
||||
(TeeOutputStream. System/err
|
||||
err-output))
|
||||
.start)]
|
||||
(.join pump-err)
|
||||
(let [exit-code (.waitFor proc)]
|
||||
{:exit exit-code
|
||||
|
@ -126,6 +135,11 @@
|
|||
(catch java.io.IOException e
|
||||
{:exit 1 :out "" :err (.getMessage e)})))
|
||||
|
||||
(defn gpg
|
||||
"Shells out to (gpg-program) with the given arguments"
|
||||
[& args]
|
||||
(apply gpg-with-passphrase nil args))
|
||||
|
||||
(defn gpg-available?
|
||||
"Verifies (gpg-program) exists"
|
||||
[]
|
||||
|
@ -156,25 +170,37 @@
|
|||
(re-find re? (:url settings)))]
|
||||
cred))))
|
||||
|
||||
(defn resolve-env-keyword
|
||||
"Resolve usage of :env and :env/foo in project.clj"
|
||||
[k v]
|
||||
(cond (= :env v)
|
||||
(getenv (str "LEIN_"
|
||||
(-> (name k)
|
||||
(str/upper-case)
|
||||
(str/replace "-" "_"))))
|
||||
|
||||
(and (keyword? v) (= "env" (namespace v)))
|
||||
(getenv (str/upper-case (name v)))
|
||||
|
||||
:else nil))
|
||||
|
||||
(defn- resolve-gpg-keyword
|
||||
"Resolve usage of :gpg in project.clj"
|
||||
[source-settings k v]
|
||||
(cond (= :gpg v)
|
||||
(get (match-credentials source-settings (credentials)) k)))
|
||||
|
||||
(defn- resolve-credential
|
||||
"Resolve key-value pair from result into a credential, updating result."
|
||||
[source-settings result [k v]]
|
||||
(letfn [(resolve [v]
|
||||
(cond (= :env v)
|
||||
(getenv (str "LEIN_" (str/upper-case (name k))))
|
||||
|
||||
(and (keyword? v) (= "env" (namespace v)))
|
||||
(getenv (str/upper-case (name v)))
|
||||
|
||||
(= :gpg v)
|
||||
(get (match-credentials source-settings (credentials)) k)
|
||||
|
||||
(coll? v) ;; collection of places to look
|
||||
(or (resolve-env-keyword k v)
|
||||
(resolve-gpg-keyword source-settings k v)
|
||||
(if (coll? v) ;; collection of places to look
|
||||
(->> (map resolve v)
|
||||
(remove nil?)
|
||||
first)
|
||||
|
||||
:else v))]
|
||||
first))
|
||||
v))]
|
||||
(if (#{:username :password :passphrase :private-key-file} k)
|
||||
(assoc result k (resolve v))
|
||||
(assoc result k v))))
|
||||
|
|
|
@ -76,14 +76,27 @@
|
|||
(defn signing-args
|
||||
"Produce GPG arguments for signing a file."
|
||||
[file opts]
|
||||
(let [key-spec (if-let [key (:gpg-key opts)]
|
||||
["--default-key" key])]
|
||||
`["--yes" "-ab" ~@key-spec "--" ~file]))
|
||||
(let [key-args (concat
|
||||
(if-let [key (:gpg-key opts)]
|
||||
["--default-key" key])
|
||||
(if-let [pass (:gpg-passphrase opts)]
|
||||
["--passphrase-fd" "0"
|
||||
"--pinentry-mode" "loopback"]))]
|
||||
`["--yes" "-ab" ~@key-args "--" ~file]))
|
||||
|
||||
(defn signing-passphrase
|
||||
"Produce GPG passphrase if specified in project"
|
||||
[opts]
|
||||
(if-let [pp (:gpg-passphrase opts)]
|
||||
(or (user/resolve-env-keyword :gpg-passphrase pp)
|
||||
pp)))
|
||||
|
||||
(defn sign
|
||||
"Create a detached signature and return the signature file name."
|
||||
[file opts]
|
||||
(let [{:keys [err exit]} (apply user/gpg (signing-args file opts))]
|
||||
(let [{:keys [err exit]} (apply user/gpg-with-passphrase
|
||||
(cons (signing-passphrase opts)
|
||||
(signing-args file opts)))]
|
||||
(when-not (zero? exit)
|
||||
(main/abort "Could not sign"
|
||||
(str file "\n" err (if err "\n")
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
(:use [clojure.test]
|
||||
[clojure.java.io :only [file]]
|
||||
[leiningen.deploy]
|
||||
[leiningen.core.user :as user]
|
||||
[leiningen.test.helper :only [delete-file-recursively
|
||||
tmp-dir sample-project
|
||||
sample-deploy-project]]))
|
||||
|
@ -66,7 +67,21 @@
|
|||
(is (= (signing-args "foo.jar" nil)
|
||||
["--yes" "-ab" "--" "foo.jar"]))
|
||||
(is (= (signing-args "foo.jar" {:gpg-key "123456"})
|
||||
["--yes" "-ab" "--default-key" "123456" "--" "foo.jar"])))
|
||||
["--yes" "-ab" "--default-key" "123456" "--" "foo.jar"]))
|
||||
(is (= (signing-args "foo.jar" {:gpg-key "123456" :gpg-passphrase "abc"})
|
||||
["--yes" "-ab" "--default-key" "123456"
|
||||
"--passphrase-fd" "0" "--pinentry-mode" "loopback"
|
||||
"--" "foo.jar"]))
|
||||
(is (= (signing-args "foo.jar" {:gpg-passphrase "abc"})
|
||||
["--yes" "-ab"
|
||||
"--passphrase-fd" "0" "--pinentry-mode" "loopback"
|
||||
"--" "foo.jar"]))
|
||||
(is (= (signing-passphrase {}) nil))
|
||||
(is (= (signing-passphrase {:gpg-passphrase "abc"}) "abc"))
|
||||
(with-redefs [user/getenv (fn [v] (if (= v "LEIN_GPG_PASSPHRASE") "abc" nil))]
|
||||
(is (= (signing-passphrase {:gpg-passphrase :env}) "abc")))
|
||||
(with-redefs [user/getenv (fn [v] (if (= v "MYPASS") "abc" nil))]
|
||||
(is (= (signing-passphrase {:gpg-passphrase :env/mypass}) "abc"))))
|
||||
(testing "Key selection"
|
||||
(is (= (:gpg-key (signing-opts {:signing {:gpg-key "key-project"}}
|
||||
["repo" {:signing {:gpg-key "key-repo"}}]))
|
||||
|
|
Loading…
Reference in a new issue