diff options
author | Felix Mulder <felix.mulder@gmail.com> | 2017-02-06 17:18:26 +0100 |
---|---|---|
committer | Felix Mulder <felix.mulder@gmail.com> | 2017-02-13 10:53:45 +0100 |
commit | 3f06fe9cc2debaacbb889e33c7339457fc5355cd (patch) | |
tree | a9d67a74543e17de42cff6f75cd57020000487f8 /bot/src | |
parent | 8bdc91f7a5fc3efd93b6be255ec1bfb83787c69b (diff) | |
download | dotty-3f06fe9cc2debaacbb889e33c7339457fc5355cd.tar.gz dotty-3f06fe9cc2debaacbb889e33c7339457fc5355cd.tar.bz2 dotty-3f06fe9cc2debaacbb889e33c7339457fc5355cd.zip |
Add initial steps to dotty-bot
This PR will add a bot whose first purpose is to check the CLA of
contributing PRs. It improves on the old bot in that it checks each
commit individually, and doesn't get upset about 100+ commits.
It would be fun to do this PR with you @OlivierBlanvillain, feel free
to provide feedback/comments and refactor as you like
Diffstat (limited to 'bot/src')
-rw-r--r-- | bot/src/dotty/tools/bot/BotServer.scala | 18 | ||||
-rw-r--r-- | bot/src/dotty/tools/bot/PullRequestService.scala | 120 |
2 files changed, 138 insertions, 0 deletions
diff --git a/bot/src/dotty/tools/bot/BotServer.scala b/bot/src/dotty/tools/bot/BotServer.scala new file mode 100644 index 000000000..5ce8a83e7 --- /dev/null +++ b/bot/src/dotty/tools/bot/BotServer.scala @@ -0,0 +1,18 @@ +package dotty.tools.bot + +import org.http4s.server.{ Server, ServerApp } +import org.http4s.server.blaze._ + +import scalaz.concurrent.Task + +object Main extends ServerApp with PullRequestService { + + /** Services mounted to the server */ + final val services = prService + + override def server(args: List[String]): Task[Server] = + BlazeBuilder + .bindHttp(8080, "localhost") + .mountService(services, "/api") + .start +} diff --git a/bot/src/dotty/tools/bot/PullRequestService.scala b/bot/src/dotty/tools/bot/PullRequestService.scala new file mode 100644 index 000000000..8b568b134 --- /dev/null +++ b/bot/src/dotty/tools/bot/PullRequestService.scala @@ -0,0 +1,120 @@ +package dotty.tools.bot + +import org.http4s._ +import org.http4s.client.blaze._ +import org.http4s.client.Client + +import scalaz.concurrent.Task + +import io.circe._ +import io.circe.generic.auto._ +import io.circe.syntax._ +import org.http4s.circe._ +import org.http4s.dsl._ + +import github4s.Github +import github4s.jvm.Implicits._ +import github4s.free.domain.{ Commit, Issue } + +trait PullRequestService { + + val prService = HttpService { + case request @ POST -> Root => + request.as(jsonOf[Issue]).flatMap(checkPullRequest) + } + + private case class CLASignature( + user: String, + signed: Boolean, + version: String, + currentVersion: String + ) + + private case class Status( + state: String, + target_url: String, + description: String, + context: String = "continuous-integration/CLA" + ) + + def claUrl(userName: String): String = + s"https://www.lightbend.com/contribute/cla/scala/check/$userName" + + def commitsUrl(prNumber: Int): String = + s"https://api.github.com/repos/lampepfl/dotty/pulls/$prNumber/commits" + + def toUri(url: String): Task[Uri] = + Uri.fromString(url).fold(Task.fail, Task.now) + + def getRequest(endpoint: Uri): Task[Request] = Task.now { + Request(uri = endpoint, method = Method.GET) + } + + def postRequest(endpoint: Uri): Task[Request] = Task.now { + Request(uri = endpoint, method = Method.POST) + } + + def shutdownClient(client: Client): Task[Unit] = Task.now { + client.shutdownNow() + } + + def users(xs: List[Commit]): Task[Set[String]] = Task.now { + xs.map(_.login).flatten.toSet + } + + sealed trait CommitStatus { + def commit: Commit + def isValid: Boolean + } + final case class Valid(commit: Commit) extends CommitStatus { def isValid = true } + final case class Invalid(commit: Commit) extends CommitStatus { def isValid = false } + + /** Partitions invalid and valid commits */ + def checkCLA(xs: List[Commit], httpClient: Client): Task[List[CommitStatus]] = { + def checkUser(commit: Commit): Task[CommitStatus] = for { + endpoint <- toUri(claUrl(commit.login.get)) + claReq <- getRequest(endpoint) + claRes <- httpClient.expect(claReq)(jsonOf[CLASignature]) + res = if (claRes.signed) Valid(commit) else Invalid(commit) + } yield res + + Task.gatherUnordered(xs.filter(_.login.isDefined).map(checkUser)) + } + + def sendStatuses(xs: List[CommitStatus], httpClient: Client): Task[Unit] = { + def setStatus(cm: CommitStatus): Task[Unit] = for { + endpoint <- toUri(cm.commit.url.replaceAll("git\\/commits", "statuses")) + + target = claUrl(cm.commit.login.getOrElse("<invalid-user>")) + state = if (cm.isValid) "success" else "failure" + desc = + if (cm.isValid) "User signed CLA" + else "User needs to sign cla: https://www.lightbend.com/contribute/cla/scala" + + statusReq <- postRequest(endpoint).map(_.withBody(Status(state, target, desc).asJson)) + statusRes <- httpClient.expect(statusReq)(jsonOf[String]) + print <- Task.now(println(statusRes)) + } yield print + + Task.gatherUnordered(xs.map(setStatus)).map(_ => ()) + } + + def checkPullRequest(issue: Issue): Task[Response] = { + val httpClient = PooledHttp1Client() + + for { + // First get all the commits from the PR + endpoint <- toUri(commitsUrl(issue.number)) + commitsReq <- getRequest(endpoint) + commitsRes <- httpClient.expect(commitsReq)(jsonOf[List[Commit]]) + + // Then get check the CLA of each commit + statuses <- checkCLA(commitsRes, httpClient) + + // Send statuses to Github and exit + _ <- sendStatuses(statuses, httpClient) + _ <- shutdownClient(httpClient) + resp <- Ok("All statuses checked") + } yield resp + } +} |