From 8d4d1b0f0b984ea39a92094e775e82bbb2bd9863 Mon Sep 17 00:00:00 2001 From: Christopher Vogt Date: Sat, 4 Mar 2017 02:44:02 +0000 Subject: improve sonatype plugin - reduce required tasks in favor of using SonatypeLib.copy - improve log messages - automatically release non-snapshots after uploading --- plugins/sonatype-release/src/SonatypeRelease.scala | 29 +----- .../sonatype-release/src/sonatype/HttpUtils.scala | 2 +- .../src/sonatype/SonatypeHttpApi.scala | 36 ++++--- .../src/sonatype/SonatypeLib.scala | 108 ++++++++------------- plugins/sonatype-release/src/sonatype/models.scala | 4 +- 5 files changed, 70 insertions(+), 109 deletions(-) (limited to 'plugins/sonatype-release') diff --git a/plugins/sonatype-release/src/SonatypeRelease.scala b/plugins/sonatype-release/src/SonatypeRelease.scala index e53a5a1..5d908f9 100644 --- a/plugins/sonatype-release/src/SonatypeRelease.scala +++ b/plugins/sonatype-release/src/SonatypeRelease.scala @@ -17,31 +17,10 @@ import cbt.sonatype.SonatypeLib * - promotes staging repository to Central repository; * - drops staging repository after release. */ -trait SonatypeRelease extends Publish { +trait SonatypeRelease extends Publish{ + protected def sonatypeLib = SonatypeLib(groupId) - def profileName: String = groupId + def publishSonatype = sonatypeLib.publishSigned( publishedArtifacts, releaseFolder ) - def sonatypeServiceURI: String = SonatypeLib.sonatypeServiceURI - - def sonatypeSnapshotsURI: String = SonatypeLib.sonatypeSnapshotsURI - - def sonatypeCredentials: String = SonatypeLib.sonatypeCredentials - - def sonatypePublishSigned: ExitCode = { - sonatypeLib.sonatypePublishSigned( - sourceFiles, - `package` :+ pom, - groupId, - artifactId, - version, - isSnapshot, - scalaMajorVersion - ) - } - - def sonatypeRelease: ExitCode = - sonatypeLib.sonatypeRelease(groupId, artifactId, version) - - private def sonatypeLib = - new SonatypeLib(sonatypeServiceURI, sonatypeSnapshotsURI, sonatypeCredentials, profileName)(lib, logger.log("sonatype-release",_)) + override def publish = {super.publish; publishSonatype} } diff --git a/plugins/sonatype-release/src/sonatype/HttpUtils.scala b/plugins/sonatype-release/src/sonatype/HttpUtils.scala index 9d23744..737fea9 100644 --- a/plugins/sonatype-release/src/sonatype/HttpUtils.scala +++ b/plugins/sonatype-release/src/sonatype/HttpUtils.scala @@ -21,7 +21,7 @@ private[sonatype] object HttpUtils { conn.setReadTimeout(60000) // 1 minute conn.setConnectTimeout(30000) // 30 seconds - headers foreach { case (k,v) => + (Map("User-Agent" -> "CBT build tool") ++ headers) foreach { case (k,v) => conn.setRequestProperty(k, v) } conn.setRequestMethod(method) diff --git a/plugins/sonatype-release/src/sonatype/SonatypeHttpApi.scala b/plugins/sonatype-release/src/sonatype/SonatypeHttpApi.scala index 6c6f4e8..d4163a8 100644 --- a/plugins/sonatype-release/src/sonatype/SonatypeHttpApi.scala +++ b/plugins/sonatype-release/src/sonatype/SonatypeHttpApi.scala @@ -12,7 +12,7 @@ import scala.xml.XML * Publish proccess via HTTP API described here: * https://support.sonatype.com/hc/en-us/articles/213465868-Uploading-to-a-Staging-Repository-via-REST-API?page=1#comment_204178478 */ -private final class SonatypeHttpApi(sonatypeURI: String, sonatypeCredentials: String, profileName: String)(log: String => Unit) { +final class SonatypeHttpApi(sonatypeURI: String, sonatypeCredentials: String, profileName: String)(log: String => Unit) { import HttpUtils._ private val base64Credentials = new String(Base64.getEncoder.encode(sonatypeCredentials.getBytes)) @@ -20,21 +20,25 @@ private final class SonatypeHttpApi(sonatypeURI: String, sonatypeCredentials: St // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles.html def getStagingProfile: StagingProfile = { log(s"Retrieving info for profile: $profileName") - val (_, response) = GET( - uri = s"$sonatypeURI/staging/profiles", - headers = Map("Authorization" -> s"Basic $base64Credentials") - ) + try{ + val (_, response) = GET( + uri = s"$sonatypeURI/staging/profiles", + headers = Map("Authorization" -> s"Basic $base64Credentials") + ) - val currentProfile = (XML.loadString(response) \\ "stagingProfile" find { profile => - (profile \ "name").headOption.exists(_.text == profileName) - }).getOrElse(throw new Exception(s"Failed to get profile with name: $profileName")) + val currentProfile = (XML.loadString(response) \\ "stagingProfile" find { profile => + (profile \ "name").headOption.exists(_.text == profileName) + }).getOrElse(throw new Exception(s"Failed to get profile with name: $profileName")) - StagingProfile( - id = (currentProfile \ "id").head.text, - name = (currentProfile \ "name").head.text, - repositoryTargetId = (currentProfile \ "repositoryTargetId").head.text, - resourceURI = (currentProfile \ "resourceURI").head.text - ) + StagingProfile( + id = (currentProfile \ "id").head.text, + name = (currentProfile \ "name").head.text, + repositoryTargetId = (currentProfile \ "repositoryTargetId").head.text, + resourceURI = (currentProfile \ "resourceURI").head.text + ) + } catch { + case e: Exception => throw new Exception(s"Failed to get info for profile: $profileName", e) + } } // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profile_repositories_-profileIdKey-.html @@ -51,7 +55,7 @@ private final class SonatypeHttpApi(sonatypeURI: String, sonatypeCredentials: St } // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_repository_-repositoryIdKey-.html - private def getStagingRepoById(repoId: StagingRepositoryId): StagingRepository = { + def getStagingRepoById(repoId: StagingRepositoryId): StagingRepository = { log(s"Retrieving staging repo with id: ${repoId.repositoryId}") val (_, response) = GET( uri = s"$sonatypeURI/staging/repository/${repoId.repositoryId}", @@ -68,7 +72,7 @@ private final class SonatypeHttpApi(sonatypeURI: String, sonatypeCredentials: St log(s"Creating staging repositories for profile: $profileName") val (responseCode, response) = POST( uri = profile.resourceURI + "/start", - body = createRequestBody("Create staging repository [CBT]").getBytes, + body = createRequestBody("CBT staging repository").getBytes, headers = Map( "Authorization" -> s"Basic $base64Credentials", "Content-Type" -> "application/xml" diff --git a/plugins/sonatype-release/src/sonatype/SonatypeLib.scala b/plugins/sonatype-release/src/sonatype/SonatypeLib.scala index 4666950..317c60f 100644 --- a/plugins/sonatype-release/src/sonatype/SonatypeLib.scala +++ b/plugins/sonatype-release/src/sonatype/SonatypeLib.scala @@ -1,12 +1,11 @@ -package cbt.sonatype +package cbt +package sonatype import java.io.File import java.net.URL import java.nio.file.Files._ import java.nio.file.Paths -import cbt.{ ExitCode, Lib } - /** * Sonatype release process is: * • get your profile info to publish artifacts @@ -19,9 +18,9 @@ import cbt.{ ExitCode, Lib } object SonatypeLib { - val sonatypeServiceURI: String = "https://oss.sonatype.org/service/local" + val serviceURI: String = "https://oss.sonatype.org/service/local" - val sonatypeSnapshotsURI: String = "https://oss.sonatype.org/content/repositories/snapshots" + val snapshotsURI: String = "https://oss.sonatype.org/content/repositories/snapshots" /** * login:password for Sonatype access. @@ -29,7 +28,7 @@ object SonatypeLib { * • environment variables SONATYPE_USERNAME and SONATYPE_PASSWORD * • ~/.cbt/sonatype-credentials */ - def sonatypeCredentials: String = { + def credentials: String = { def fromEnv = for { username <- Option(System.getenv("SONATYPE_USERNAME")) password <- Option(System.getenv("SONATYPE_PASSWORD")) @@ -51,55 +50,45 @@ object SonatypeLib { } } -final class SonatypeLib( - sonatypeServiceURI: String, - sonatypeSnapshotsURI: String, - sonatypeCredentials: String, - profileName: String -)( lib: Lib, log: String => Unit ) { - - private val sonatypeApi = new SonatypeHttpApi(sonatypeServiceURI, sonatypeCredentials, profileName)(sonatypeLogger) +final case class SonatypeLib( + profileName: String, + serviceURI: String = SonatypeLib.serviceURI, + snapshotsURI: String = SonatypeLib.snapshotsURI, + credentials: String = SonatypeLib.credentials +)( implicit logger: Logger ){ + private val lib: Lib = new Lib(logger) + private def log: String => Unit = logger.log("sonatype-release",_) + val api = new SonatypeHttpApi(serviceURI, credentials, profileName)(log) /* * Signed publish steps: * • create new staging repo * • create artifacts and sign them * • publish jars to created repo */ - def sonatypePublishSigned( - sourceFiles: Seq[File], - artifacts: Seq[File], - groupId: String, - artifactId: String, - version: String, - isSnapshot: Boolean, - scalaMajorVersion: String - ): ExitCode = { - if(sourceFiles.nonEmpty) { - System.err.println(lib.blue("Staring publishing to Sonatype.")) + def publishSigned( artifacts: Seq[File], releaseFolder: String ) = { + import api._ + System.err.println(lib.blue("Publishing to Sonatype")) - val profile = getStagingProfile() + def publish(deployURI: String) = lib.publishSigned( + artifacts, new URL(deployURI ++ releaseFolder), Some(credentials) + ) - val deployURI = (if (isSnapshot) { - sonatypeSnapshotsURI - } else { - val repoId = sonatypeApi.createStagingRepo(profile) - s"${sonatypeServiceURI}/staging/deployByRepositoryId/${repoId.repositoryId}" - }) + s"/${groupId.replace(".", "/")}/${artifactId}_${scalaMajorVersion}/${version}" - - lib.publishSigned( - artifacts = artifacts, - url = new URL(deployURI), - credentials = Some(sonatypeCredentials) - ) - System.err.println(lib.green("Successfully published artifacts to Sonatype.")) - ExitCode.Success + if (releaseFolder.endsWith("-SNAPSHOT")){ + publish(snapshotsURI) } else { - System.err.println(lib.red("Sources are empty, won't publish empty jar.")) - ExitCode.Failure + val profile = getStagingProfile + val repoId = createStagingRepo(profile) + publish( + serviceURI ++ "/staging/deployByRepositoryId/" ++ repoId.string + ) + finishRelease( getStagingRepoById(repoId), profile ) } + + System.err.println(lib.green("Successfully published to Sonatype!")) } + /* /** * Release is: * • find staging repo related to current profile; @@ -107,28 +96,24 @@ final class SonatypeLib( * • wait until this repo is released; * • drop this repo. */ - def sonatypeRelease( - groupId: String, - artifactId: String, - version: String - ): ExitCode = { + private def release: ExitCode = { val profile = getStagingProfile() - sonatypeApi.getStagingRepos(profile).toList match { + getStagingRepos(profile).toList match { case Nil => System.err.println(lib.red("No staging repositories found, you need to publish artifacts first.")) ExitCode.Failure case repo :: Nil => - sonatypeApi.finishRelease(repo, profile) - System.err.println(lib.green(s"Successfully released ${groupId}/${artifactId} v:${version}")) + finishRelease(repo, profile) + log(lib.green(s"Successfully released artifact")) ExitCode.Success case repos => - val showRepo = { r: StagingRepository => s"${r.repositoryId} in state: ${r.state}" } - val toRelease = lib.pickOne(lib.blue(s"More than one staging repo found. Select one of them:"), repos)(showRepo) - - toRelease map { repo => - sonatypeApi.finishRelease(repo, profile) - System.err.println(lib.green(s"Successfully released ${groupId}/${artifactId} v:${version}")) + lib.pickOne( + lib.blue(s"More than one staging repo found. Select one of them:"), + repos + ){ repo => s"${repo.repositoryId} in state: ${repo.state}" }.map{ repo => + finishRelease(repo, profile) + log(lib.green(s"Successfully released artifact")) ExitCode.Success } getOrElse { System.err.println(lib.red("Wrong repository number, try again please.")) @@ -136,14 +121,5 @@ final class SonatypeLib( } } } - - private def getStagingProfile() = - try { - sonatypeApi.getStagingProfile - } catch { - case e: Exception => throw new Exception(s"Failed to get info for profile: $profileName", e) - } - - private def sonatypeLogger: String => Unit = lib.logger.log("Sonatype", _) - + */ } diff --git a/plugins/sonatype-release/src/sonatype/models.scala b/plugins/sonatype-release/src/sonatype/models.scala index 88ec3b5..fd02774 100644 --- a/plugins/sonatype-release/src/sonatype/models.scala +++ b/plugins/sonatype-release/src/sonatype/models.scala @@ -7,7 +7,9 @@ case class StagingProfile( resourceURI: String ) -case class StagingRepositoryId(repositoryId: String) +case class StagingRepositoryId( string: String ){ + def repositoryId = string // deprecated +} object RepositoryState { val fromString: String => RepositoryState = { -- cgit v1.2.3