Skip to content

Commit

Permalink
Merge pull request #268 from polyvariant/unique-webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
kubukoz committed Jun 13, 2021
2 parents 26487d6 + 4c542c6 commit f331614
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 48 deletions.
18 changes: 18 additions & 0 deletions bootstrap/src/main/resources/reflect-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,24 @@
}
]
},
{
"name": "org.polyvariant.Gitlab$$anon$2",
"fields": [
{
"name": "0bitmap$1",
"allowUnsafeAccess": true
}
]
},
{
"name": "org.polyvariant.Gitlab$Webhook$",
"fields": [
{
"name": "0bitmap$2",
"allowUnsafeAccess": true
}
]
},
{
"name": "sttp.model.Header$",
"fields": [
Expand Down
104 changes: 70 additions & 34 deletions bootstrap/src/main/scala/org/polyvariant/Gitlab.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package org.polyvariant

import cats.implicits.*

import scala.util.chaining._
import scala.util.chaining.*
import io.pg.gitlab.graphql.*
import sttp.model.Uri
import sttp.client3.*
import sttp.client3.circe.*
import caliban.client.SelectionBuilder
import caliban.client.CalibanClientError.DecodingError
import io.pg.gitlab.graphql.MergeRequest
Expand All @@ -20,23 +21,31 @@ import io.pg.gitlab.graphql.UserCore
import caliban.client.Operations.IsOperation
import sttp.model.Method
import cats.MonadThrow
import io.circe.*

trait Gitlab[F[_]] {
def mergeRequests(projectId: Long): F[List[Gitlab.MergeRequestInfo]]
def deleteMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit]
def createWebhook(projectId: Long, pitgullUrl: Uri): F[Unit]
def listWebhooks(projectId: Long): F[List[Gitlab.Webhook]]
}

object Gitlab {

def apply[F[_]](using ev: Gitlab[F]): Gitlab[F] = ev

def sttpInstance[F[_]: Logger: MonadThrow](
baseUri: Uri,
accessToken: String
)(
using backend: SttpBackend[Identity, Any] // FIXME: https://github.com/polyvariant/pitgull/issues/265
): Gitlab[F] = {
def runRequest[O](request: Request[O, Any]): F[O] =
request.header("Private-Token", accessToken).send(backend).pure[F].map(_.body) // FIXME - change in https://github.com/polyvariant/pitgull/issues/265
request
.header("Private-Token", accessToken)
.send(backend)
.pure[F]
.map(_.body) // FIXME - change in https://github.com/polyvariant/pitgull/issues/265

def runGraphQLQuery[A: IsOperation, B](a: SelectionBuilder[A, B]): F[B] =
runRequest(a.toRequest(baseUri.addPath("api", "graphql"))).rethrow
Expand All @@ -52,47 +61,74 @@ object Gitlab {
}

def deleteMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit] = for {
_ <- Logger[F].debug(s"Request to remove $mergeRequestId")
_ <- Logger[F].debug(s"Request to remove $mergeRequestId")
result <- runRequest(
basicRequest.delete(
baseUri
.addPath(
Seq(
"api",
"v4",
"projects",
projectId.toString,
"merge_requests",
mergeRequestId.toString
)
)
)
)
basicRequest.delete(
baseUri
.addPath(
Seq(
"api",
"v4",
"projects",
projectId.toString,
"merge_requests",
mergeRequestId.toString
)
)
)
)
} yield ()

def createWebhook(projectId: Long, pitgullUrl: Uri): F[Unit] = for {
_ <- Logger[F].debug(s"Creating webhook to $pitgullUrl")
_ <- Logger[F].debug(s"Creating webhook to $pitgullUrl")
result <- runRequest(
basicRequest.post(
baseUri
.addPath(
Seq(
"api",
"v4",
"projects",
projectId.toString,
"hooks"
)
)
)
.body(s"""{"merge_requests_events": true, "pipeline_events": true, "note_events": true, "url": "$pitgullUrl"}""")
.contentType("application/json")
)
basicRequest
.post(
baseUri
.addPath(
Seq(
"api",
"v4",
"projects",
projectId.toString,
"hooks"
)
)
)
.body(s"""{"merge_requests_events": true, "pipeline_events": true, "note_events": true, "url": "$pitgullUrl"}""")
.contentType("application/json")
)
} yield ()

def listWebhooks(projectId: Long): F[List[Webhook]] = for {
_ <- Logger[F].debug(s"Listing webhooks for $projectId")
response <- runRequest(
basicRequest
.get(
baseUri
.addPath(
Seq(
"api",
"v4",
"projects",
projectId.toString,
"hooks"
)
)
)
.response(asJson[List[Webhook]])
).flatMap(_.liftTo[F])
_ <- Logger[F].debug(response.toString)
} yield response
}

}

final case class Webhook(
id: Long,
url: String
) derives Codec.AsObject

final case class MergeRequestInfo(
projectId: Long,
mergeRequestIid: Long,
Expand Down
45 changes: 31 additions & 14 deletions bootstrap/src/main/scala/org/polyvariant/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import sttp.monad.MonadError
import cats.MonadThrow
import org.polyvariant.Config.ArgumentsParsingException
import cats.effect.std.Console
import cats.Monad

object Main extends IOApp {

Expand All @@ -20,35 +21,51 @@ object Main extends IOApp {
Logger[F].info(s"ID: ${mr.mergeRequestIid} by: ${mr.authorUsername}")
}.void

private def readConsent[F[_]: Console: Applicative]: F[Boolean] =
Console[F].readLine.map(_.toLowerCase == "y")
private def readConsent[F[_]: Console: MonadThrow]: F[Unit] =
MonadThrow[F]
.ifM(Console[F].readLine.map(_.trim.toLowerCase == "y"))(
ifTrue = MonadThrow[F].pure(()),
ifFalse = MonadThrow[F].raiseError(new Exception("User rejected deletion"))
)

private def qualifyMergeRequestsForDeletion(botUserName: String, mergeRequests: List[MergeRequestInfo]): List[MergeRequestInfo] =
mergeRequests.filter(_.authorUsername == botUserName)

private def program[F[_]: Logger: Console: Async: MonadThrow](args: List[String]): F[Unit] = {
private def deleteMergeRequests[F[_]: Gitlab: Logger: Applicative](project: Long, mergeRequests: List[MergeRequestInfo]): F[Unit] =
mergeRequests.traverse(mr => Gitlab[F].deleteMergeRequest(project, mr.mergeRequestIid)).void

private def createWebhook[F[_]: Gitlab: Logger: Applicative](project: Long, webhook: Uri): F[Unit] =
Logger[F].info("Creating webhook") *>
Gitlab[F].createWebhook(project, webhook) *>
Logger[F].info("Webhook created")

private def configureWebhooks[F[_]: Gitlab: Logger: Monad](project: Long, webhook: Uri): F[Unit] = for {
hooks <- Gitlab[F].listWebhooks(project).map(_.filter(_.url == webhook.toString))
_ <- Monad[F]
.ifM(hooks.nonEmpty.pure[F])(
ifTrue = Logger[F].success("Webhook already exists"),
ifFalse = createWebhook(project, webhook)
)
} yield ()

private def program[F[_]: Logger: Console: Async](args: List[String]): F[Unit] = {
given SttpBackend[Identity, Any] = HttpURLConnectionBackend()
val parsedArgs = Args.parse(args)
for {
config <- Config.fromArgs(parsedArgs)
_ <- Logger[F].info("Starting pitgull bootstrap!")
gitlab = Gitlab.sttpInstance[F](config.gitlabUri, config.token)
mrs <- gitlab.mergeRequests(config.project)
given Gitlab[F] = Gitlab.sttpInstance[F](config.gitlabUri, config.token)
mrs <- Gitlab[F].mergeRequests(config.project)
_ <- Logger[F].info(s"Merge requests found: ${mrs.length}")
_ <- printMergeRequests(mrs)
botMrs = qualifyMergeRequestsForDeletion(config.botUser, mrs)
_ <- Logger[F].info(s"Will delete merge requests: ${botMrs.map(_.mergeRequestIid).mkString(", ")}")
_ <- Logger[F].info("Do you want to proceed? y/Y")
_ <- MonadThrow[F]
.ifM(readConsent)(
ifTrue = MonadThrow[F].pure(()),
ifFalse = MonadThrow[F].raiseError(new Exception("User rejected deletion"))
)
_ <- botMrs.traverse(mr => gitlab.deleteMergeRequest(config.project, mr.mergeRequestIid))
_ <- readConsent
_ <- deleteMergeRequests(config.project, botMrs)
_ <- Logger[F].info("Done processing merge requests")
_ <- Logger[F].info("Creating webhook")
_ <- gitlab.createWebhook(config.project, config.pitgullWebhookUrl)
_ <- Logger[F].info("Webhook created")
_ <- Logger[F].info("Configuring webhook")
_ <- configureWebhooks(config.project, config.pitgullWebhookUrl)
_ <- Logger[F].success("Bootstrap finished")
} yield ()
}
Expand Down
2 changes: 2 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ lazy val bootstrap = project
"org.typelevel" %% "cats-effect" % "3.1.1",
"com.kubukoz" %% "caliban-gitlab" % "0.1.0",
"com.softwaremill.sttp.client3" %% "core" % "3.3.6",
"com.softwaremill.sttp.client3" %% "circe" % "3.3.6",
"io.circe" %% "circe-core" % "0.14.1",
crossPlugin("com.kubukoz" % "better-tostring" % "0.3.3")
),
publish / skip := true,
Expand Down

0 comments on commit f331614

Please sign in to comment.