package com.github.simplesteph.ksm.source import java.io.{Reader, StringReader} import java.nio.charset.Charset import java.util.Base64 import com.fasterxml.jackson.databind.ObjectMapper import com.typesafe.config.Config import org.slf4j.LoggerFactory import skinny.http.{HTTP, HTTPException, Request, Response} class BitbucketServerSourceAcl extends SourceAcl { private val log = LoggerFactory.getLogger(classOf[BitbucketServerSourceAcl]) override val CONFIG_PREFIX: String = "bitbucket-server" final val HOSTNAME_CONFIG = "hostname" final val PORT_CONFIG = "port" final val PROTOCOL_CONFIG = "protocol" final val PROJECT_CONFIG = "project" final val REPO_CONFIG = "repo" final val FILEPATH_CONFIG = "filepath" final val AUTH_USERNAME_CONFIG = "auth.username" final val AUTH_PASSWORD_CONFIG = "auth.password" final val BRANCH_CONFIG = "branch" var lastCommit: Option[String] = None val objectMapper = new ObjectMapper() var http: HTTP = HTTP var hostname: String = _ var port: String = _ var protocol: String = _ var project: String = _ var repo: String = _ var filePath: String = _ var username: String = _ var password: String = _ var branch: Option[String] = _ /** * internal config definition for the module */ override def configure(config: Config): Unit = { hostname = config.getString(HOSTNAME_CONFIG) port = config.getString(PORT_CONFIG) protocol = config.getString(PROTOCOL_CONFIG) project = config.getString(PROJECT_CONFIG) repo = config.getString(REPO_CONFIG) filePath = config.getString(FILEPATH_CONFIG) username = config.getString(AUTH_USERNAME_CONFIG) password = config.getString(AUTH_PASSWORD_CONFIG) branch = Option().filter({ _ => config.hasPath(BRANCH_CONFIG)}).map({_ => config.getString(BRANCH_CONFIG)}) } override def refresh(): Option[Reader] = { // get changes since last commit val url = s"$protocol://$hostname:$port/rest/api/1.0/projects/$project/repos/$repo/commits" val request: Request = new Request(url) // super important in order to properly fail in case a timeout happens for example request.enableThrowingIOException(true) branch.foreach(it => request.queryParam("until", it)) request.queryParam("path", filePath) // optionally add the last commit if available lastCommit.foreach(s => request.queryParam("since", s)) // add authentication header val basicB64 = Base64.getEncoder.encodeToString( s"$username:$password".getBytes(Charset.forName("UTF-8")) ) request.header("Authorization", s"Basic $basicB64") val response: Response = http.get(request) response.status match { case 200 => // we receive a valid response val values = objectMapper.readTree(response.textBody).get("values") val hasNewCommits = values.size() > 0 if (hasNewCommits) { val rawRetrieveUrl = s"$protocol://$hostname:$port/projects/$project/repos/$repo/browse/$filePath?raw" val fileRetrievalRequest = new Request(rawRetrieveUrl) branch.foreach(it => fileRetrievalRequest.queryParam("at", it)) fileRetrievalRequest.header("Authorization", s"Basic $basicB64") val fileResponse = http.get(fileRetrievalRequest) fileResponse.status match { case 200 => // update the last commit id lastCommit = Some(values.get(0).get("id").asText()) val data = fileResponse.textBody Some(new StringReader(data)) case _ => // throw error as you can't retrieve the file log.warn(response.asString) throw HTTPException(Some(response.asString), response) } } else { None } case _ => // uncaught error log.warn(response.asString) throw HTTPException(Some(response.asString), response) } } /** * Close all the necessary underlying objects or connections belonging to this instance */ override def close(): Unit = { // HTTP } }