aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Spiewak <djspiewak@gmail.com>2018-08-14 18:20:26 -0600
committerJakob Odersky <jakob@odersky.com>2018-08-14 18:20:26 -0600
commitbecc47cb5ecfec59d828a041a188624a03b8b88f (patch)
tree66b4911dcef2b76ee3a62e3c8163a62eda545eb8
parentb8a11f179e5771f026231f7bb95637d7950bc73f (diff)
downloadsbt-gpg-becc47cb5ecfec59d828a041a188624a03b8b88f.tar.gz
sbt-gpg-becc47cb5ecfec59d828a041a188624a03b8b88f.tar.bz2
sbt-gpg-becc47cb5ecfec59d828a041a188624a03b8b88f.zip
Added gpgWarnOnFailure (#6)
* Added gpgWarnOnFailure key (name subject to bikeshedding) and associated semantics * Added stderr redirect to gpg command * Corrected test to use `publish / packagedArtifacts` * Updated README for new fail-on-failure default for publish (and tweaked a couple other things)
-rw-r--r--README.md14
-rw-r--r--src/main/scala/Gpg.scala15
-rw-r--r--src/main/scala/SbtGpg.scala71
-rw-r--r--src/sbt-test/sbt-gpg/simple/build.sbt2
4 files changed, 68 insertions, 34 deletions
diff --git a/README.md b/README.md
index bf0e2fb..65cbe3a 100644
--- a/README.md
+++ b/README.md
@@ -17,10 +17,10 @@ promoting secure builds.
## Highlights
- Uses the system command `gpg` to do all operations. *This enables
- advanced features such as use of smartcards or cutting-edge
+ advanced features such as use of smartcards, key splitting, or cutting-edge
ciphers.*
-- Hooks into the `publish` and `publishLocal` tasks. *All artrifacts
+- Hooks into the `publish` and `publishLocal` tasks. *All artifacts
will be signed; there is no need to run a separate `publishSigned`
task.*
@@ -28,9 +28,9 @@ promoting secure builds.
`credentials` mechanism, thus enabling global configuration without
the need of adding a global plugin.*
-- Works out-of-the-box. *Publishing falls back to unsigned artifacts
+- Works out-of-the-box. *`publishLocal` falls back to unsigned artifacts
in case key material cannot be found, after emitting an explicit
- warning.*
+ warning. `publish` will fail the build by default if signing fails to avoid accidentally publishing unsigned artifacts, though you can override this with a setting.*
## Requirements
@@ -40,13 +40,13 @@ promoting secure builds.
functionality provided by this plugin)
## Getting started
-```scala
+```sbt
addSbtPlugin("io.crashbox" % "sbt-gpg" % "<latest_tag>")
```
Copy the above snippet to an sbt configuration file. E.g.
- `project/plugins.sbt` to enable the plugin on a per-project basis
-- `~/.sbt/1.0/plugins/gpg.sbt` to enable the plugin globally
+- `~/.sbt/1.0/plugins/gpg.sbt` to enable the plugin globally (not recommended)
That's it! The autoplugin "SbtGpg" will now be enabled for the given
project(s). It will modify the `publish` and `publishLocal` tasks to
@@ -62,7 +62,7 @@ By default, all signing operations will use `gpg`'s default key. A
specific key can be used by setting sbt `Credentials` for the host
"gpg".
-```scala
+```sbt
credentials += Credentials(
"GnuPG Key ID",
"gpg",
diff --git a/src/main/scala/Gpg.scala b/src/main/scala/Gpg.scala
index cde4dba..edd1935 100644
--- a/src/main/scala/Gpg.scala
+++ b/src/main/scala/Gpg.scala
@@ -5,19 +5,22 @@ import java.io.File
import scala.util.control.NonFatal
import sys.process._
-class Gpg(
- command: String,
- options: Seq[String] = Seq.empty,
- keyId: Option[String] = None)(log: String => Unit = System.err.println) {
+class Gpg(command: String,
+ options: Seq[String] = Seq.empty,
+ keyId: Option[String] = None)(
+ info: String => Unit = System.out.println,
+ warn: String => Unit = System.err.println) {
+
+ private val logger = ProcessLogger(info, info) // gpg uses stderr for everything; redirect to info
def run(params: String*): Int =
try {
val idOption = keyId.toSeq.flatMap(id => Seq("--local-user", id))
- val process = Process(command, options ++ idOption ++ params).run()
+ val process = Process(command, options ++ idOption ++ params).run(logger)
process.exitValue()
} catch {
case NonFatal(ex) =>
- log(ex.getMessage)
+ warn(ex.getMessage)
127
}
diff --git a/src/main/scala/SbtGpg.scala b/src/main/scala/SbtGpg.scala
index 17eff8d..d3d82b9 100644
--- a/src/main/scala/SbtGpg.scala
+++ b/src/main/scala/SbtGpg.scala
@@ -10,48 +10,79 @@ object SbtGpg extends AutoPlugin {
override def trigger = allRequirements
object autoImport {
+
+ val gpgWarnOnFailure = settingKey[Boolean](
+ "If true, only issue a warning when signing fails. If false, error " +
+ "and fail the build. Defaults to true in publishLocal, false in publish.")
+
val gpgCommand = settingKey[String]("Path to GnuPG executable.")
+
val gpgOptions =
settingKey[Seq[String]]("Additional global options to pass to gpg.")
+
val gpgKey = taskKey[Option[String]](
"Key ID used to sign artifacts. Setting this to None will " +
"cause sbt-gpg to fall back to using gpg's default key. When set, " +
"it is equivalent to gpg's `--local-user` option.")
+
val gpg =
taskKey[Gpg]("Utility wrapper to the underlying gpg executable.")
}
+
+ def packagedArtifactsImpl(
+ arts: Map[Artifact, File],
+ gpg: Gpg,
+ warnOnFailure: Boolean)(warn: String => Unit): Map[Artifact, File] = {
+
+ val (signatures, failure) = arts.foldLeft((Map[Artifact, File](), false)) {
+ case ((acc, false), (art, file)) =>
+ gpg.sign(file) match {
+ case Some(signed) =>
+ (acc + (art.withExtension(art.extension + ".asc") -> signed), false)
+
+ case None =>
+ val report: String => Unit =
+ if (warnOnFailure) warn else sys.error(_)
+
+ report("GPG reported an error. Artifacts won't be signed.")
+ (acc, true)
+ }
+
+ case (pair @ (_, true), _) => pair
+ }
+
+ // if we fail the signing part-way through, we throw out *all* the signatures
+ if (failure) arts else signatures ++ arts
+ }
+
import autoImport._
lazy val gpgSettings: Seq[Setting[_]] = Seq(
+ gpgWarnOnFailure := false,
+ publishLocal / gpgWarnOnFailure := true,
gpgCommand := "gpg",
gpgOptions := Seq("--yes"),
gpgKey := Credentials.forHost(credentials.value, "gpg").map(_.userName),
gpg := {
val log = streams.value.log
- new Gpg(gpgCommand.value, gpgOptions.value, gpgKey.value)(log.warn(_))
+ new Gpg(gpgCommand.value, gpgOptions.value, gpgKey.value)(log.info(_),
+ log.warn(_))
}
)
lazy val signingSettings: Seq[Setting[_]] = Seq(
- packagedArtifacts := {
- val log = streams.value.log
- val arts: Map[Artifact, File] = packagedArtifacts.value
- var failed = false
- arts.flatMap {
- case (art, file) if !failed =>
- gpg.value.sign(file) match {
- case Some(signed) =>
- Map(
- art -> file,
- art.withExtension(art.extension + ".asc") -> signed
- )
- case None =>
- log.warn("GPG reported an error. Artifacts won't be signed.")
- failed = true
- Map(art -> file)
- }
- case (art, file) => Map(art -> file)
- }
+ publish / packagedArtifacts := {
+ packagedArtifactsImpl(
+ (publish / packagedArtifacts).value,
+ gpg.value,
+ (publish / gpgWarnOnFailure).value)(streams.value.log.warn(_))
+ },
+ publishLocal / packagedArtifacts := {
+ packagedArtifactsImpl(
+ (publishLocal / packagedArtifacts).value,
+ gpg.value,
+ (publishLocal / gpgWarnOnFailure).value)(streams.value.log.warn(_))
+
}
)
diff --git a/src/sbt-test/sbt-gpg/simple/build.sbt b/src/sbt-test/sbt-gpg/simple/build.sbt
index 1f5dce9..8901fd5 100644
--- a/src/sbt-test/sbt-gpg/simple/build.sbt
+++ b/src/sbt-test/sbt-gpg/simple/build.sbt
@@ -17,7 +17,7 @@ lazy val root = project
.settings(
TaskKey[Unit]("check") := {
val artifacts: Map[Artifact, java.io.File] =
- packagedArtifacts.value
+ (publish / packagedArtifacts).value
// check that every artifact is signed and that the actual signature file
// exists