diff options
author | Chris Rebert <code@rebertia.com> | 2014-11-21 09:11:09 +0300 |
---|---|---|
committer | Chris Rebert <code@rebertia.com> | 2014-11-21 09:11:09 +0300 |
commit | 7deb6a16177ecc6681ed0c0909d443d64f0ef9f1 (patch) | |
tree | 9261b2ba808f9fa1836d03bc3529b62adbf61066 /src | |
parent | e4d8a307811a99e8fdaae3300b5192a6d2f1aa7c (diff) |
Fixes #8
Diffstat (limited to 'src')
-rw-r--r-- | src/main/resources/application.conf | 1 | ||||
-rw-r--r-- | src/main/scala/com/getbootstrap/savage/github/GitHubJsonProtocol.scala | 43 | ||||
-rw-r--r-- | src/main/scala/com/getbootstrap/savage/server/GitHubWebHooksDirectives.scala (renamed from src/main/scala/com/getbootstrap/savage/server/GitHubPullRequestWebHooksDirectives.scala) | 14 | ||||
-rw-r--r-- | src/main/scala/com/getbootstrap/savage/server/PullRequestEventHandler.scala | 36 | ||||
-rw-r--r-- | src/main/scala/com/getbootstrap/savage/server/SavageWebService.scala | 8 | ||||
-rw-r--r-- | src/main/scala/com/getbootstrap/savage/server/Settings.scala | 1 |
6 files changed, 99 insertions, 4 deletions
diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf index 03bfcdc..e914dff 100644 --- a/src/main/resources/application.conf +++ b/src/main/resources/application.conf @@ -24,6 +24,7 @@ savage { github-repo-to-watch = "twbs/bootstrap" github-test-repo = "twbs-savage/bootstrap" ignore-branches-from-watched-repo = true + trusted-orgs = [ "twbs" ] whitelist = [ "**.md", "/bower.json", diff --git a/src/main/scala/com/getbootstrap/savage/github/GitHubJsonProtocol.scala b/src/main/scala/com/getbootstrap/savage/github/GitHubJsonProtocol.scala new file mode 100644 index 0000000..ab5bd01 --- /dev/null +++ b/src/main/scala/com/getbootstrap/savage/github/GitHubJsonProtocol.scala @@ -0,0 +1,43 @@ +package com.getbootstrap.savage.github + +import spray.json._ +import org.eclipse.egit.github.core.RepositoryId + +case class GitHubRepository(fullName: String) extends AnyVal { + def id: RepositoryId = RepositoryId.createFromId(fullName) +} +case class GitHubUser(username: String) extends AnyVal +case class IssueOrComment( + number: Option[Int], // issue number + body: String, + user: GitHubUser +) +case class IssueOrCommentEvent( + repository: GitHubRepository, + comment: Option[IssueOrComment], + issue: IssueOrComment +) { + def prNumber: Option[PullRequestNumber] = issue.number.flatMap{ PullRequestNumber(_) } +} + +object GitHubJsonProtocol extends DefaultJsonProtocol { + implicit object RepoJsonFormat extends JsonFormat[GitHubRepository] { + override def write(repo: GitHubRepository) = JsObject("full_name" -> JsString(repo.fullName)) + override def read(value: JsValue) = { + value.asJsObject.getFields("full_name") match { + case Seq(JsString(fullName)) => new GitHubRepository(fullName) + case _ => throw new DeserializationException("GitHubRepository expected") + } + } + } + implicit object UserFormat extends JsonFormat[GitHubUser] { + override def write(user: GitHubUser) = JsObject("login" -> JsString(user.username)) + override def read(value: JsValue) = { + value.asJsObject.getFields("login") match { + case Seq(JsString(username)) => new GitHubUser(username) + } + } + } + implicit val issueOrCommentFormat = jsonFormat3(IssueOrComment.apply) + implicit val issueOrCommentEventFormat = jsonFormat3(IssueOrCommentEvent.apply) +} diff --git a/src/main/scala/com/getbootstrap/savage/server/GitHubPullRequestWebHooksDirectives.scala b/src/main/scala/com/getbootstrap/savage/server/GitHubWebHooksDirectives.scala index d61a294..9ef26ee 100644 --- a/src/main/scala/com/getbootstrap/savage/server/GitHubPullRequestWebHooksDirectives.scala +++ b/src/main/scala/com/getbootstrap/savage/server/GitHubWebHooksDirectives.scala @@ -1,15 +1,18 @@ package com.getbootstrap.savage.server import scala.util.{Success, Failure, Try} +import spray.json._ import spray.routing.{Directive1, ValidationRejection} import spray.routing.directives.{BasicDirectives, RouteDirectives} import org.eclipse.egit.github.core.event.PullRequestPayload import org.eclipse.egit.github.core.client.GsonUtils +import com.getbootstrap.savage.github.{GitHubJsonProtocol,IssueOrCommentEvent} -trait GitHubPullRequestWebHooksDirectives { +trait GitHubWebHooksDirectives { import RouteDirectives.reject import BasicDirectives.provide import HubSignatureDirectives.stringEntityMatchingHubSignature + import GitHubJsonProtocol._ def authenticatedPullRequestEvent(secretKey: Array[Byte]): Directive1[PullRequestPayload] = stringEntityMatchingHubSignature(secretKey).flatMap{ entityJsonString => Try { GsonUtils.fromJson(entityJsonString, classOf[PullRequestPayload]) } match { @@ -17,6 +20,13 @@ trait GitHubPullRequestWebHooksDirectives { case Success(payload) => provide(payload) } } + + def authenticatedIssueOrCommentEvent(secretKey: Array[Byte]): Directive1[IssueOrCommentEvent] = stringEntityMatchingHubSignature(secretKey).flatMap{ entityJsonString => + Try{ entityJsonString.parseJson.convertTo[IssueOrCommentEvent] } match { + case Failure(err) => reject(ValidationRejection("JSON either malformed or does not match expected schema!")) + case Success(event) => provide(event) + } + } } -object GitHubPullRequestWebHooksDirectives extends GitHubPullRequestWebHooksDirectives +object GitHubWebHooksDirectives extends GitHubWebHooksDirectives diff --git a/src/main/scala/com/getbootstrap/savage/server/PullRequestEventHandler.scala b/src/main/scala/com/getbootstrap/savage/server/PullRequestEventHandler.scala index 244b5d4..1a4ceff 100644 --- a/src/main/scala/com/getbootstrap/savage/server/PullRequestEventHandler.scala +++ b/src/main/scala/com/getbootstrap/savage/server/PullRequestEventHandler.scala @@ -1,11 +1,12 @@ package com.getbootstrap.savage.server import java.nio.file.Path +import java.util.regex.Pattern import scala.collection.JavaConverters._ import scala.util.{Try,Success,Failure} import akka.actor.ActorRef import org.eclipse.egit.github.core._ -import org.eclipse.egit.github.core.service.CommitService +import org.eclipse.egit.github.core.service.{CommitService, OrganizationService, PullRequestService} import com.getbootstrap.savage.github._ import com.getbootstrap.savage.github.util._ import com.getbootstrap.savage.util.UnixFileSystemString @@ -39,11 +40,44 @@ class PullRequestEventHandler(protected val pusher: ActorRef) extends GitHubActo settings.Watchlist.anyInterestingIn(paths) } + private def isTrusted(user: GitHubUser): Boolean = { + val orgService = new OrganizationService(gitHubClient) + settings.TrustedOrganizations.exists{ org => Try{ orgService.isPublicMember(org, user.username) }.toOption.getOrElse(false) } + } + private def logPrInfo(msg: String)(implicit prNum: PullRequestNumber) { log.info(s"PR #${prNum.number} : ${msg}") } + private val RetryCommentRegex = ("(?i)^" + Pattern.quote(s"@${settings.BotUsername}") + "\\s+retry").r + override def receive = { + case commentEvent: IssueOrCommentEvent => { + commentEvent.repository.id match { + case settings.MainRepoId => { + commentEvent.prNumber.foreach{ prNum => { + commentEvent.comment.foreach{ comment => { + if (isTrusted(comment.user)) { + comment.body match { + case RetryCommentRegex(_*) => { + val prService = new PullRequestService(gitHubClient) + Try{ prService.getPullRequest(settings.MainRepoId, prNum.number) } match { + case Failure(exc) => log.error(exc, s"Error getting ${prNum} for repo ${settings.MainRepoId}!") + case Success(pullReq) => { + log.info(s"Initiating retry of ${prNum} due to request from trusted user ${comment.user}") + self ! pullReq + } + } + } + case _ => {} + } + } + }} + }} + } + case otherRepo => log.error(s"Received event from GitHub about irrelevant repository: ${otherRepo}") + } + } case pr: PullRequest => { implicit val prNum = pr.number val bsBase = pr.getBase diff --git a/src/main/scala/com/getbootstrap/savage/server/SavageWebService.scala b/src/main/scala/com/getbootstrap/savage/server/SavageWebService.scala index 0d3ec23..a300ff4 100644 --- a/src/main/scala/com/getbootstrap/savage/server/SavageWebService.scala +++ b/src/main/scala/com/getbootstrap/savage/server/SavageWebService.scala @@ -12,7 +12,7 @@ class SavageWebService( protected val pullRequestCommenter: ActorRef, protected val branchDeleter: ActorRef ) extends ActorWithLogging with HttpService { - import GitHubPullRequestWebHooksDirectives.authenticatedPullRequestEvent + import GitHubWebHooksDirectives.{authenticatedPullRequestEvent,authenticatedIssueOrCommentEvent} import TravisWebHookDirectives.authenticatedTravisEvent private val settings = Settings(context.system) @@ -35,6 +35,12 @@ class SavageWebService( log.info("Successfully received GitHub webhook ping.") complete(StatusCodes.OK) } + case "issue_comment" => { + authenticatedIssueOrCommentEvent(settings.GitHubWebHookSecretKey.toArray) { event => { + pullRequestEventHandler ! event + complete(StatusCodes.OK) + }} + } case "pull_request" => { authenticatedPullRequestEvent(settings.GitHubWebHookSecretKey.toArray) { event => event.getAction match { diff --git a/src/main/scala/com/getbootstrap/savage/server/Settings.scala b/src/main/scala/com/getbootstrap/savage/server/Settings.scala index c6989d4..f647557 100644 --- a/src/main/scala/com/getbootstrap/savage/server/Settings.scala +++ b/src/main/scala/com/getbootstrap/savage/server/Settings.scala @@ -23,6 +23,7 @@ class SettingsImpl(config: Config) extends Extension { val Watchlist: FilePathWatchlist = new FilePathWatchlist(config.getStringList("savage.file-watchlist").asScala) val BranchPrefix: String = config.getString("savage.branch-prefix") val IgnoreBranchesFromMainRepo: Boolean = config.getBoolean("savage.ignore-branches-from-watched-repo") + val TrustedOrganizations: Set[String] = config.getStringList("savage.trusted-orgs").asScala.toSet } object Settings extends ExtensionId[SettingsImpl] with ExtensionIdProvider { override def lookup() = Settings |