Skip to content
New issue

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

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

Already on GitHub? # to your account

Make gpg passphrase optional in publish method. #345

Closed
Baccata opened this issue May 21, 2018 · 8 comments
Closed

Make gpg passphrase optional in publish method. #345

Baccata opened this issue May 21, 2018 · 8 comments
Labels
solved The issue was fixed/resolved
Milestone

Comments

@Baccata
Copy link
Contributor

Baccata commented May 21, 2018

My team uses a private artifact repository (artifactory) which exposes the same API as sonatype.

However, the signing of the artifacts is not required. I'd like to make the gpg passphrase optional in PublishModule#publish, or have another task (publishWithoutSigning) that would not require the passphrase.

Thoughts ?

@rockjam
Copy link
Contributor

rockjam commented May 21, 2018

What happens when you pass null?

@Baccata
Copy link
Contributor Author

Baccata commented May 21, 2018

What happens when you pass null?

1 targets failed
scribe.recursion.publish java.lang.NullPointerException
    java.lang.ProcessBuilder.start(ProcessBuilder.java:1012)
    ammonite.ops.Shellout$.executeInteractive(Shellout.scala:27)
    ammonite.ops.Shellout$.$anonfun$$percent$1(Shellout.scala:14)
    ammonite.ops.Shellout$.$anonfun$$percent$1$adapted(Shellout.scala:14)
    ammonite.ops.Command.applyDynamic(Shellout.scala:118)
    mill.scalalib.publish.SonatypePublisher.poorMansSign(SonatypePublisher.scala:141)

@rockjam
Copy link
Contributor

rockjam commented May 21, 2018

I think that having optional gpg passphrase is reasonable

@lihaoyi
Copy link
Member

lihaoyi commented May 22, 2018

Maybe we could give publish a separate flag --signed false? The reason is that there are three cases here:

  • Signed with passphrase
  • Signed without passphrase
  • Unsigned

If we're going to change this we might as well make sure we handle all three cases

rockjam pushed a commit that referenced this issue May 23, 2018
* Solves 345 : optional signing

* Made gpgPassphrase optional for publishing
* Added a flag to remove signing of published artifacts altogether

* Handle optional value using null as default param

better than using empty string as default param.
@Baccata
Copy link
Contributor Author

Baccata commented May 24, 2018

Closing this as the problem it describes is solved. There is still a problem with publishing to artifactory with the default PublishModule. I need to have a closer look at how it's API differs from sonatype's API to formulate a new issue.

@Baccata Baccata closed this as completed May 24, 2018
@ddelautre
Copy link

@Baccata Did you succeed publishing to Artifactory?

@Baccata
Copy link
Contributor Author

Baccata commented Dec 11, 2018

@ddelautre, yeah, here's a snippet I used. It might need updating to the latest mill version, but it should do the trick :

import java.math.BigInteger
import java.security.MessageDigest

import ammonite.ops._
import mill.scalalib.publish.{ SonatypeHttpApi, _ }
import mill.util.Logger
import scalaj.http.HttpResponse

// Basically a copy of https://github.com/lihaoyi/mill/blob/0.2.2/scalalib/src/mill/scalalib/publish/SonatypePublisher.scala
// to avoid requiring a gpg passphrase.
class ArtifactoryPublisher(uri: String, snapshotUri: String, credentials: String, log: Logger) {

  private val api = new SonatypeHttpApi(uri, credentials)

  def publish(fileMapping: Seq[(Path, String)], artifact: Artifact): Unit = {
    publishAll(release = true, fileMapping -> artifact)
  }
  def publishAll(release: Boolean, artifacts: (Seq[(Path, String)], Artifact)*): Unit = {

    val mappings = for ((fileMapping0, artifact) <- artifacts) yield {
      val publishPath = Seq(
        artifact.group.replace(".", "/"),
        artifact.id,
        artifact.version
      ).mkString("/")
      val fileMapping = fileMapping0.map { case (file, name) => (file, publishPath + "/" + name) }

      artifact -> fileMapping.flatMap {
        case (file, name) =>
          val content = read.bytes(file)

          Seq(
            name             -> content,
            (name + ".md5")  -> md5hex(content),
            (name + ".sha1") -> sha1hex(content)
          )
      }
    }

    val (snapshots, releases) = mappings.partition(_._1.isSnapshot)
    if (snapshots.nonEmpty) {
      doPublish(snapshots.flatMap(_._2), snapshots.map(_._1), snapshotUri)
    }
    val releaseGroups = releases.groupBy(_._1.group)
    for ((group, groupReleases) <- releaseGroups) {
      doPublish(groupReleases.flatMap(_._2), releases.map(_._1), uri)
    }
  }

  private def doPublish(
    payloads: Seq[(String, Array[Byte])],
    artifacts: Seq[Artifact],
    uri: String
  ): Unit = {

    val publishResults = payloads.map {
      case (fileName, data) =>
        log.info(s"Uploading $fileName")
        val resp = api.upload(s"$uri/$fileName", data)
        resp
    }
    reportPublishResults(publishResults, artifacts)
  }

  private def reportPublishResults(
    publishResults: Seq[HttpResponse[String]],
    artifacts: Seq[Artifact]
  ) = {
    if (publishResults.forall(_.is2xx)) {
      log.info(s"Published ${artifacts.map(_.id).mkString(", ")} to Sonatype")
    } else {
      val errors = publishResults.filterNot(_.is2xx).map { response =>
        s"Code: ${response.code}, message: ${response.body}"
      }
      throw new RuntimeException(
        s"Failed to publish ${artifacts.map(_.id).mkString(", ")} to Sonatype. Errors: \n${errors.mkString("\n")}"
      )
    }
  }

  private def awaitRepoStatus(status: String, stagingRepoId: String, attempts: Int = 20): Unit = {
    def isRightStatus =
      api.getStagingRepoState(stagingRepoId).equalsIgnoreCase(status)
    var attemptsLeft = attempts

    while (attemptsLeft > 0 && !isRightStatus) {
      Thread.sleep(3000)
      attemptsLeft -= 1
      if (attemptsLeft == 0) {
        throw new RuntimeException(s"Couldn't wait for staging repository to be ${status}. Failing")
      }
    }
  }

  private def md5hex(bytes: Array[Byte]): Array[Byte] =
    hexArray(md5.digest(bytes)).getBytes

  private def sha1hex(bytes: Array[Byte]): Array[Byte] =
    hexArray(sha1.digest(bytes)).getBytes

  private def md5 = MessageDigest.getInstance("md5")

  private def sha1 = MessageDigest.getInstance("sha1")

  private def hexArray(arr: Array[Byte]) =
    String.format("%0" + (arr.length << 1) + "x", new BigInteger(1, arr))

}

@lefou lefou added this to the 0.2.3 milestone May 9, 2019
@lefou lefou added the solved The issue was fixed/resolved label Jul 3, 2019
@lefou
Copy link
Member

lefou commented Mar 3, 2020

For those who came here in need to publish to Artifactory. Mill > 0.6.1 now has a artifactory module. See #783

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
solved The issue was fixed/resolved
Projects
None yet
Development

No branches or pull requests

5 participants