Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added gpgWarnOnFailure #6

Merged
merged 4 commits into from
Aug 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,20 @@ 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.*

- Unobtrusive configuration. *Key selection can be done through sbt's
`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

Expand All @@ -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
Expand All @@ -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",
Expand Down
15 changes: 9 additions & 6 deletions src/main/scala/Gpg.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
71 changes: 51 additions & 20 deletions src/main/scala/SbtGpg.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(_))

}
)

Expand Down
2 changes: 1 addition & 1 deletion src/sbt-test/sbt-gpg/simple/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down