summaryrefslogblamecommitdiff
path: root/scalalib/src/publish/SonatypeHttpApi.scala
blob: 4b9d239e83c84cb7478933bbf41fcc8b34fc25b1 (plain) (tree)
1
2
3
4
5
6
7
8
9
                             


                       
 

                                  
 
 






                                                                                                           



                                               
                                             






                                                                                           








                                                                                   

                     
           
                                         
            

                                                                        








                                                                  

                                                    


                                                       



                                                                                                                
                                       


                                                                                     
      
 




                                                                                   




                                                                                                                 





                                                                                                                      
 
                              




                                                                                                                  





                                                                                                                      
 
                              




                                                                                                               






                                                                                                                   



                                                      




                                                                   

                                                  


                 

   


                                                                 











                                                    
package mill.scalalib.publish

import java.util.Base64



import scala.concurrent.duration._


class SonatypeHttpApi(
  uri: String,
  credentials: String,
  readTimeout: Int,
  connectTimeout: Int
) {
  val http = requests.Session(readTimeout = readTimeout, connectTimeout = connectTimeout, maxRedirects = 0)

  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(
      http.get(
        s"$uri/staging/profiles",
        headers = commonHeaders
      )
    )

    if (!response.is2xx) {
      throw new Exception(s"$uri/staging/profiles returned ${response.statusCode}")
    }

    val resourceUri =
      ujson
        .read(response.data.text)("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 = http.get(
      s"${uri}/staging/repository/${stagingRepoId}",
      headers = commonHeaders
    )
    ujson.read(response.data.text)("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(http.post(
      s"${profileUri}/start",
      headers = commonHeaders,
      data = s"""{"data": {"description": "fresh staging profile for ${groupId}"}}"""
    ))

    if (!response.is2xx) {
      throw new Exception(s"$uri/staging/profiles returned ${response.statusCode}")
    }

    ujson.read(response.data.text)("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(
      http.post(
        s"${profileUri}/finish",
        headers = commonHeaders,
        data = s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "closing staging repository"}}"""
      )
    )

    response.statusCode == 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(
      http.post(
        s"${profileUri}/promote",
        headers = commonHeaders,
        data = s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "promote staging repository"}}"""
      )
    )

    response.statusCode == 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(
      http.post(
        s"${profileUri}/drop",
        headers = commonHeaders,
        data = s"""{"data": {"stagedRepositoryId": "${repositoryId}", "description": "drop staging repository"}}"""
      )
    )
    response.statusCode == 201
  }

  private val uploadTimeout = 5.minutes.toMillis.toInt

  def upload(uri: String, data: Array[Byte]): requests.Response = {
    http.put(
      uri,
      readTimeout = uploadTimeout,
      headers = Seq(
        "Content-Type" -> "application/binary",
        "Authorization" -> s"Basic ${base64Creds}"
      ),
      data = data
    )
  }

  private def withRetry(request: => requests.Response,
                        retries: Int = 10): requests.Response = {
    val resp = request
    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))

}