Skip to content

Commit 01da59f

Browse files
committed
Factor out HTTP client functionality
1 parent 8494fc5 commit 01da59f

File tree

5 files changed

+77
-100
lines changed

5 files changed

+77
-100
lines changed

bot/src/dotty/tools/bot/Main.scala

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import scalaz.concurrent.Task
77

88
object Main extends ServerApp with PullRequestService {
99

10-
val user = sys.env("USER")
11-
val token = sys.env("TOKEN")
12-
val port = sys.env("PORT").toInt
10+
val githubUser = sys.env("GITHUB_USER")
11+
val githubToken = sys.env("GITHUB_TOKEN")
12+
val droneToken = sys.env("DRONE_TOKEN")
13+
val port = sys.env("PORT").toInt
1314

1415
/** Services mounted to the server */
1516
final val services = prService

bot/src/dotty/tools/bot/PullRequestService.scala

Lines changed: 21 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
package dotty.tools.bot
1+
package dotty.tools
2+
package bot
23

34
import org.http4s.{ Status => _, _ }
45
import org.http4s.client.blaze._
@@ -18,39 +19,26 @@ import org.http4s.dsl._
1819
import org.http4s.util._
1920

2021
import model.Github._
21-
22-
object TaskIsApplicative {
23-
implicit val taskIsApplicative = new cats.Applicative[Task] {
24-
def pure[A](x: A): Task[A] = Task.now(x)
25-
def ap[A, B](ff: Task[A => B])(fa: Task[A]): Task[B] =
26-
for(f <- ff; a <- fa) yield f(a)
27-
}
28-
}
29-
import TaskIsApplicative._
22+
import bot.util.TaskIsApplicative._
23+
import bot.util.HttpClientAux._
3024

3125
trait PullRequestService {
3226

3327
/** Username for authorized admin */
34-
def user: String
28+
def githubUser: String
3529

3630
/** OAuth token needed for user to create statuses */
37-
def token: String
31+
def githubToken: String
32+
33+
/** OAuth token for drone, needed to cancel builds */
34+
def droneToken: String
3835

3936
/** Pull Request HTTP service */
4037
val prService = HttpService {
4138
case request @ POST -> Root =>
4239
request.as(jsonOf[Issue]).flatMap(checkPullRequest)
4340
}
4441

45-
private[this] lazy val authHeader = {
46-
val creds = BasicCredentials(user, token)
47-
new Authorization(creds)
48-
}
49-
50-
private[this] lazy val previewAcceptHeader =
51-
Accept.parse("application/vnd.github.black-cat-preview+json")
52-
.getOrElse(throw new Exception("Couldn't initialize accept header"))
53-
5442
private final case class CLASignature(
5543
user: String,
5644
signed: Boolean,
@@ -75,18 +63,6 @@ trait PullRequestService {
7563
def reviewUrl(issueNbr: Int): String =
7664
s"$githubUrl/repos/lampepfl/dotty/pulls/$issueNbr/reviews"
7765

78-
def toUri(url: String): Task[Uri] =
79-
Uri.fromString(url).fold(Task.fail, Task.now)
80-
81-
def getRequest(endpoint: Uri): Task[Request] =
82-
Request(uri = endpoint, method = Method.GET).putHeaders(authHeader)
83-
.pure[Task]
84-
85-
def postRequest(endpoint: Uri): Task[Request] =
86-
Request(uri = endpoint, method = Method.POST)
87-
.putHeaders(authHeader, previewAcceptHeader)
88-
.pure[Task]
89-
9066
def shutdownClient(client: Client): Task[Unit] =
9167
client.shutdownNow().pure[Task]
9268

@@ -104,9 +80,7 @@ trait PullRequestService {
10480
def checkCLA(xs: List[Commit], httpClient: Client): Task[List[CommitStatus]] = {
10581
def checkUser(user: String): Task[Commit => CommitStatus] = {
10682
val claStatus = for {
107-
endpoint <- toUri(claUrl(user))
108-
claReq <- getRequest(endpoint)
109-
claRes <- httpClient.expect(claReq)(jsonOf[CLASignature])
83+
claRes <- httpClient.expect(get(claUrl(user)))(jsonOf[CLASignature])
11084
} yield { (commit: Commit) =>
11185
if (claRes.signed) Valid(Some(user), commit)
11286
else Invalid(user, commit)
@@ -151,9 +125,8 @@ trait PullRequestService {
151125
}
152126

153127
for {
154-
endpoint <- toUri(statusUrl(cm.commit.sha))
155-
req <- postRequest(endpoint).withBody(stat.asJson)
156-
res <- httpClient.expect(req)(jsonOf[StatusResponse])
128+
req <- post(statusUrl(cm.commit.sha)).withAuth(githubUser, githubToken)
129+
res <- httpClient.expect(req.withBody(stat.asJson))(jsonOf[StatusResponse])
157130
} yield res
158131
}
159132

@@ -177,9 +150,7 @@ trait PullRequestService {
177150
def getCommits(issueNbr: Int, httpClient: Client): Task[List[Commit]] = {
178151
def makeRequest(url: String): Task[List[Commit]] =
179152
for {
180-
endpoint <- toUri(url)
181-
req <- getRequest(endpoint)
182-
res <- httpClient.fetch(req){ res =>
153+
res <- httpClient.fetch(get(url)) { res =>
183154
val link = CaseInsensitiveString("Link")
184155
val next = findNext(res.headers.get(link)).map(makeRequest).getOrElse(Task.now(Nil))
185156

@@ -191,12 +162,7 @@ trait PullRequestService {
191162
}
192163

193164
def getComments(issueNbr: Int, httpClient: Client): Task[List[Comment]] =
194-
for {
195-
endpoint <- toUri(issueCommentsUrl(issueNbr))
196-
req <- getRequest(endpoint)
197-
res <- httpClient.expect(req)(jsonOf[List[Comment]])
198-
} yield res
199-
165+
httpClient.expect(get(issueCommentsUrl(issueNbr)))(jsonOf[List[Comment]])
200166

201167
private def usersFromInvalid(xs: List[CommitStatus]) =
202168
xs.collect { case Invalid(user, _) => user }
@@ -273,9 +239,8 @@ trait PullRequestService {
273239
val review = Review.comment(body)
274240

275241
for {
276-
endpoint <- toUri(reviewUrl(issueNbr))
277-
req <- postRequest(endpoint).withBody(review.asJson)
278-
res <- client.expect(req)(jsonOf[ReviewResponse])
242+
req <- post(reviewUrl(issueNbr)).withAuth(githubUser, githubToken)
243+
res <- client.expect(req.withBody(review.asJson))(jsonOf[ReviewResponse])
279244
} yield res
280245
}
281246

@@ -308,41 +273,22 @@ trait PullRequestService {
308273

309274
}
310275

311-
def getStatus(commit: Commit, client: Client): Task[StatusResponse] =
312-
for {
313-
endpoint <- toUri(statusUrl(commit.sha))
314-
req <- getRequest(endpoint)
315-
res <- client.expect(req)(jsonOf[List[StatusResponse]])
316-
} yield res.head
317-
318-
def getStatuses(commits: List[Commit], client: Client): Task[List[StatusResponse]] =
319-
Task.gatherUnordered(commits.map(getStatus(_, client)))
276+
def getStatus(commit: Commit, client: Client): Task[List[StatusResponse]] =
277+
client.expect(get(statusUrl(commit.sha)))(jsonOf[List[StatusResponse]])
320278

321279
private def extractCommitSha(status: StatusResponse): Task[String] =
322280
Task.delay(status.sha)
323281

324-
def recheckCLA(statuses: List[StatusResponse], commits: List[Commit], client: Client): Task[List[CommitStatus]] = {
325-
/** Return the matching commits from the SHAs */
326-
def prunedCommits(shas: List[String]): Task[List[Commit]] =
327-
Task.delay(commits.filter(cm => shas.contains(cm.sha)))
328-
329-
for {
330-
commitShas <- Task.gatherUnordered(statuses.map(extractCommitSha))
331-
commits <- prunedCommits(commitShas)
332-
statuses <- checkCLA(commits, client)
333-
} yield statuses
334-
}
335282

336283
def checkSynchronize(issue: Issue): Task[Response] = {
337284
val httpClient = PooledHttp1Client()
338285

339286
for {
340287
commits <- getCommits(issue.number, httpClient)
341288
statuses <- checkCLA(commits, httpClient)
342-
343-
(_, invalid) = statuses.partition(_.isValid)
344-
345-
_ <- sendStatuses(invalid, httpClient)
289+
invalid = statuses.filterNot(_.isValid)
290+
_ <- sendStatuses(invalid, httpClient)
291+
_ <- cancelBuilds(commits.dropRight(1), httpClient)
346292

347293
// Set final commit status based on `invalid`:
348294
_ <- {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package dotty.tools.bot.util
2+
3+
import org.http4s._
4+
import scalaz.concurrent.Task
5+
import org.http4s.headers.{ Accept, Authorization }
6+
7+
object HttpClientAux {
8+
def uriFromString(url: String): Task[Uri] =
9+
Uri.fromString(url).fold(Task.fail, Task.now)
10+
11+
implicit class RicherTask(val task: Task[Request]) extends AnyVal {
12+
def withOauth2(token: String): Task[Request] =
13+
task.map(_.putHeaders(new Authorization(new OAuth2BearerToken(token))))
14+
15+
def withAuth(user: String, pass: String): Task[Request] =
16+
task.map(_.putHeaders(new Authorization(BasicCredentials(user, pass))))
17+
}
18+
19+
private[this] lazy val previewAcceptHeader =
20+
Accept.parse("application/vnd.github.black-cat-preview+json")
21+
.getOrElse(throw new Exception("Couldn't initialize accept header"))
22+
23+
@inline private def request(method: Method, endpoint: Uri): Request =
24+
Request(uri = endpoint, method = method)
25+
.putHeaders(previewAcceptHeader)
26+
27+
def get(endpoint: String): Task[Request] =
28+
uriFromString(endpoint).map(request(Method.GET, _))
29+
30+
def post(endpoint: String): Task[Request] =
31+
uriFromString(endpoint).map(request(Method.POST, _))
32+
33+
def delete(endpoint: String): Task[Request] =
34+
uriFromString(endpoint).map(request(Method.DELETE, _))
35+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package dotty.tools.bot
2+
package util
3+
4+
import cats.syntax.applicative._
5+
import scalaz.concurrent.Task
6+
7+
object TaskIsApplicative {
8+
implicit val taskIsApplicative = new cats.Applicative[Task] {
9+
def pure[A](x: A): Task[A] = Task.now(x)
10+
def ap[A, B](ff: Task[A => B])(fa: Task[A]): Task[B] =
11+
for(f <- ff; a <- fa) yield f(a)
12+
}
13+
}

bot/test/PRServiceTests.scala

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import org.http4s.client.Client
1414
import scalaz.concurrent.Task
1515

1616
class PRServiceTests extends PullRequestService {
17-
val user = sys.env("USER")
18-
val token = sys.env("TOKEN")
17+
val githubUser = sys.env("GITHUB_USER")
18+
val githubToken = sys.env("GITHUB_TOKEN")
19+
val droneToken = sys.env("DRONE_TOKEN")
1920

2021
private def withClient[A](f: Client => Task[A]): A = {
2122
val httpClient = PooledHttp1Client()
@@ -98,30 +99,11 @@ class PRServiceTests extends PullRequestService {
9899
@Test def canGetStatus = {
99100
val sha = "fa64b4b613fe5e78a5b4185b4aeda89e2f1446ff"
100101
val commit = Commit(sha, Author(None), Author(None), CommitInfo(""))
101-
val status = withClient(getStatus(commit, _))
102+
val status = withClient(getStatus(commit, _)).head
102103

103104
assert(status.sha == sha, "someting wong")
104105
}
105106

106-
@Test def canRecheckCLA = {
107-
val shas =
108-
"1d62587cb3f41dafd796b0c92ec1c22d95b879f9" ::
109-
"ad60a386f488a16612c093576bf7bf4d9f0073bf" ::
110-
Nil
111-
112-
val commits = shas.map { sha =>
113-
Commit(sha, Author(Some("felixmulder")), Author(Some("felixmulder")), CommitInfo(""))
114-
}
115-
116-
val statuses = shas.map { sha =>
117-
StatusResponse("https://api.github.com/repos/lampepfl/dotty/statuses/" + sha, 0, "failure")
118-
}
119-
120-
val rechecked = withClient(recheckCLA(statuses, commits, _))
121-
122-
assert(rechecked.forall(cs => cs.isValid), s"Should have set all statuses to valid, but got: $rechecked")
123-
}
124-
125107
@Test def canPostReview = {
126108
val invalidUsers = "felixmulder" :: "smarter" :: Nil
127109
val commit = Commit("", Author(Some("smarter")), Author(Some("smarter")), CommitInfo("Added stuff"))

0 commit comments

Comments
 (0)