summaryrefslogblamecommitdiff
path: root/scalalib/src/publish/SonatypeHttpApi.scala
blob: 12defa934006eefb6aa3b0224361a534c3683999 (plain) (tree)
1
2
3
4
5
                             


                       
 

















                                                                     
                                             






                                                                                           
                                                                   
                   

                     
           

                                    

                                                                        












                                                                             
                 
 
                                                  







                                                                                                                
                 
 
                                                                        


































































                                                                                                                  
package mill.scalalib.publish

import java.util.Base64



import scala.concurrent.duration._
import scalaj.http.{BaseHttp, HttpOptions, HttpRequest, HttpResponse}

object PatientHttp
    extends BaseHttp(
      options = Seq(
        HttpOptions.connTimeout(5.seconds.toMillis.toInt),
        HttpOptions.readTimeout(1.minute.toMillis.toInt),
        HttpOptions.followRedirects(false)
      )
    )

class SonatypeHttpApi(uri: String, credentials: String) {

  private val base64Creds = base64(credentials)

  private val commonHeaders = Seq(
    "Authorization" -> s"Basic $base64Creds",
    "Accept" -> "application/json",
    "Content-Type" -> "application/json"
  )

  // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles.html
  def getStagingProfileUri(groupId: String): String = {
    val response = withRetry(
      PatientHttp(s"$uri/staging/profiles").headers(commonHeaders))
        .throwError

    val resourceUri =
      ujson
        .read(response.body)("data")
        .arr
        .find(profile =>
          groupId.split('.').startsWith(profile("name").str.split('.')))
        .map(_("resourceURI").str.toString)

    resourceUri.getOrElse(
      throw new RuntimeException(
        s"Could not find staging profile for groupId: ${groupId}")
    )
  }

  def getStagingRepoState(stagingRepoId: String): String = {
    val response = PatientHttp(s"${uri}/staging/repository/${stagingRepoId}")
      .option(HttpOptions.readTimeout(60000))
      .headers(commonHeaders)
      .asString
      .throwError

    ujson.read(response.body)("type").str.toString
  }

  // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_start.html
  def createStagingRepo(profileUri: String, groupId: String): String = {
    val response = withRetry(PatientHttp(s"${profileUri}/start")
      .headers(commonHeaders)
      .postData(
        s"""{"data": {"description": "fresh staging profile for ${groupId}"}}"""))
      .throwError

    ujson.read(response.body)("data")("stagedRepositoryId").str.toString
  }

  // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_finish.html
  def closeStagingRepo(profileUri: String, repositoryId: String): Boolean = {
    val response = withRetry(
      PatientHttp(s"${profileUri}/finish")
        .headers(commonHeaders)
        .postData(
          s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "closing staging repository"}}"""
        ))

    response.code == 201
  }

  // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_promote.html
  def promoteStagingRepo(profileUri: String, repositoryId: String): Boolean = {
    val response = withRetry(
      PatientHttp(s"${profileUri}/promote")
        .headers(commonHeaders)
        .postData(
          s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "promote staging repository"}}"""
        ))

    response.code == 201
  }

  // https://oss.sonatype.org/nexus-staging-plugin/default/docs/path__staging_profiles_-profileIdKey-_drop.html
  def dropStagingRepo(profileUri: String, repositoryId: String): Boolean = {
    val response = withRetry(
      PatientHttp(s"${profileUri}/drop")
        .headers(commonHeaders)
        .postData(
          s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "drop staging repository"}}"""
        ))

    response.code == 201
  }

  private val uploadTimeout = 5.minutes.toMillis.toInt

  def upload(uri: String, data: Array[Byte]): HttpResponse[String] = {
    PatientHttp(uri)
      .option(HttpOptions.readTimeout(uploadTimeout))
      .method("PUT")
      .headers(
        "Content-Type" -> "application/binary",
        "Authorization" -> s"Basic ${base64Creds}"
      )
      .put(data)
      .asString
  }

  private def withRetry(request: HttpRequest,
                        retries: Int = 10): HttpResponse[String] = {
    val resp = request.asString
    if (resp.is5xx && retries > 0) {
      Thread.sleep(500)
      withRetry(request, retries - 1)
    } else {
      resp
    }
  }

  private def base64(s: String) =
    new String(Base64.getEncoder.encode(s.getBytes))

}