Skip to content
This repository was archived by the owner on Oct 25, 2024. It is now read-only.

Commit f331614

Browse files
authored
Merge pull request #268 from polyvariant/unique-webhooks
2 parents 26487d6 + 4c542c6 commit f331614

File tree

4 files changed

+121
-48
lines changed

4 files changed

+121
-48
lines changed

bootstrap/src/main/resources/reflect-config.json

+18
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,24 @@
135135
}
136136
]
137137
},
138+
{
139+
"name": "org.polyvariant.Gitlab$$anon$2",
140+
"fields": [
141+
{
142+
"name": "0bitmap$1",
143+
"allowUnsafeAccess": true
144+
}
145+
]
146+
},
147+
{
148+
"name": "org.polyvariant.Gitlab$Webhook$",
149+
"fields": [
150+
{
151+
"name": "0bitmap$2",
152+
"allowUnsafeAccess": true
153+
}
154+
]
155+
},
138156
{
139157
"name": "sttp.model.Header$",
140158
"fields": [

bootstrap/src/main/scala/org/polyvariant/Gitlab.scala

+70-34
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package org.polyvariant
22

33
import cats.implicits.*
44

5-
import scala.util.chaining._
5+
import scala.util.chaining.*
66
import io.pg.gitlab.graphql.*
77
import sttp.model.Uri
88
import sttp.client3.*
9+
import sttp.client3.circe.*
910
import caliban.client.SelectionBuilder
1011
import caliban.client.CalibanClientError.DecodingError
1112
import io.pg.gitlab.graphql.MergeRequest
@@ -20,23 +21,31 @@ import io.pg.gitlab.graphql.UserCore
2021
import caliban.client.Operations.IsOperation
2122
import sttp.model.Method
2223
import cats.MonadThrow
24+
import io.circe.*
2325

2426
trait Gitlab[F[_]] {
2527
def mergeRequests(projectId: Long): F[List[Gitlab.MergeRequestInfo]]
2628
def deleteMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit]
2729
def createWebhook(projectId: Long, pitgullUrl: Uri): F[Unit]
30+
def listWebhooks(projectId: Long): F[List[Gitlab.Webhook]]
2831
}
2932

3033
object Gitlab {
3134

35+
def apply[F[_]](using ev: Gitlab[F]): Gitlab[F] = ev
36+
3237
def sttpInstance[F[_]: Logger: MonadThrow](
3338
baseUri: Uri,
3439
accessToken: String
3540
)(
3641
using backend: SttpBackend[Identity, Any] // FIXME: https://github.com/polyvariant/pitgull/issues/265
3742
): Gitlab[F] = {
3843
def runRequest[O](request: Request[O, Any]): F[O] =
39-
request.header("Private-Token", accessToken).send(backend).pure[F].map(_.body) // FIXME - change in https://github.com/polyvariant/pitgull/issues/265
44+
request
45+
.header("Private-Token", accessToken)
46+
.send(backend)
47+
.pure[F]
48+
.map(_.body) // FIXME - change in https://github.com/polyvariant/pitgull/issues/265
4049

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

5463
def deleteMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit] = for {
55-
_ <- Logger[F].debug(s"Request to remove $mergeRequestId")
64+
_ <- Logger[F].debug(s"Request to remove $mergeRequestId")
5665
result <- runRequest(
57-
basicRequest.delete(
58-
baseUri
59-
.addPath(
60-
Seq(
61-
"api",
62-
"v4",
63-
"projects",
64-
projectId.toString,
65-
"merge_requests",
66-
mergeRequestId.toString
67-
)
68-
)
69-
)
70-
)
66+
basicRequest.delete(
67+
baseUri
68+
.addPath(
69+
Seq(
70+
"api",
71+
"v4",
72+
"projects",
73+
projectId.toString,
74+
"merge_requests",
75+
mergeRequestId.toString
76+
)
77+
)
78+
)
79+
)
7180
} yield ()
72-
81+
7382
def createWebhook(projectId: Long, pitgullUrl: Uri): F[Unit] = for {
74-
_ <- Logger[F].debug(s"Creating webhook to $pitgullUrl")
83+
_ <- Logger[F].debug(s"Creating webhook to $pitgullUrl")
7584
result <- runRequest(
76-
basicRequest.post(
77-
baseUri
78-
.addPath(
79-
Seq(
80-
"api",
81-
"v4",
82-
"projects",
83-
projectId.toString,
84-
"hooks"
85-
)
86-
)
87-
)
88-
.body(s"""{"merge_requests_events": true, "pipeline_events": true, "note_events": true, "url": "$pitgullUrl"}""")
89-
.contentType("application/json")
90-
)
85+
basicRequest
86+
.post(
87+
baseUri
88+
.addPath(
89+
Seq(
90+
"api",
91+
"v4",
92+
"projects",
93+
projectId.toString,
94+
"hooks"
95+
)
96+
)
97+
)
98+
.body(s"""{"merge_requests_events": true, "pipeline_events": true, "note_events": true, "url": "$pitgullUrl"}""")
99+
.contentType("application/json")
100+
)
91101
} yield ()
102+
103+
def listWebhooks(projectId: Long): F[List[Webhook]] = for {
104+
_ <- Logger[F].debug(s"Listing webhooks for $projectId")
105+
response <- runRequest(
106+
basicRequest
107+
.get(
108+
baseUri
109+
.addPath(
110+
Seq(
111+
"api",
112+
"v4",
113+
"projects",
114+
projectId.toString,
115+
"hooks"
116+
)
117+
)
118+
)
119+
.response(asJson[List[Webhook]])
120+
).flatMap(_.liftTo[F])
121+
_ <- Logger[F].debug(response.toString)
122+
} yield response
92123
}
93124

94125
}
95126

127+
final case class Webhook(
128+
id: Long,
129+
url: String
130+
) derives Codec.AsObject
131+
96132
final case class MergeRequestInfo(
97133
projectId: Long,
98134
mergeRequestIid: Long,

bootstrap/src/main/scala/org/polyvariant/Main.scala

+31-14
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import sttp.monad.MonadError
1212
import cats.MonadThrow
1313
import org.polyvariant.Config.ArgumentsParsingException
1414
import cats.effect.std.Console
15+
import cats.Monad
1516

1617
object Main extends IOApp {
1718

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

23-
private def readConsent[F[_]: Console: Applicative]: F[Boolean] =
24-
Console[F].readLine.map(_.toLowerCase == "y")
24+
private def readConsent[F[_]: Console: MonadThrow]: F[Unit] =
25+
MonadThrow[F]
26+
.ifM(Console[F].readLine.map(_.trim.toLowerCase == "y"))(
27+
ifTrue = MonadThrow[F].pure(()),
28+
ifFalse = MonadThrow[F].raiseError(new Exception("User rejected deletion"))
29+
)
2530

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

29-
private def program[F[_]: Logger: Console: Async: MonadThrow](args: List[String]): F[Unit] = {
34+
private def deleteMergeRequests[F[_]: Gitlab: Logger: Applicative](project: Long, mergeRequests: List[MergeRequestInfo]): F[Unit] =
35+
mergeRequests.traverse(mr => Gitlab[F].deleteMergeRequest(project, mr.mergeRequestIid)).void
36+
37+
private def createWebhook[F[_]: Gitlab: Logger: Applicative](project: Long, webhook: Uri): F[Unit] =
38+
Logger[F].info("Creating webhook") *>
39+
Gitlab[F].createWebhook(project, webhook) *>
40+
Logger[F].info("Webhook created")
41+
42+
private def configureWebhooks[F[_]: Gitlab: Logger: Monad](project: Long, webhook: Uri): F[Unit] = for {
43+
hooks <- Gitlab[F].listWebhooks(project).map(_.filter(_.url == webhook.toString))
44+
_ <- Monad[F]
45+
.ifM(hooks.nonEmpty.pure[F])(
46+
ifTrue = Logger[F].success("Webhook already exists"),
47+
ifFalse = createWebhook(project, webhook)
48+
)
49+
} yield ()
50+
51+
private def program[F[_]: Logger: Console: Async](args: List[String]): F[Unit] = {
3052
given SttpBackend[Identity, Any] = HttpURLConnectionBackend()
3153
val parsedArgs = Args.parse(args)
3254
for {
3355
config <- Config.fromArgs(parsedArgs)
3456
_ <- Logger[F].info("Starting pitgull bootstrap!")
35-
gitlab = Gitlab.sttpInstance[F](config.gitlabUri, config.token)
36-
mrs <- gitlab.mergeRequests(config.project)
57+
given Gitlab[F] = Gitlab.sttpInstance[F](config.gitlabUri, config.token)
58+
mrs <- Gitlab[F].mergeRequests(config.project)
3759
_ <- Logger[F].info(s"Merge requests found: ${mrs.length}")
3860
_ <- printMergeRequests(mrs)
3961
botMrs = qualifyMergeRequestsForDeletion(config.botUser, mrs)
4062
_ <- Logger[F].info(s"Will delete merge requests: ${botMrs.map(_.mergeRequestIid).mkString(", ")}")
4163
_ <- Logger[F].info("Do you want to proceed? y/Y")
42-
_ <- MonadThrow[F]
43-
.ifM(readConsent)(
44-
ifTrue = MonadThrow[F].pure(()),
45-
ifFalse = MonadThrow[F].raiseError(new Exception("User rejected deletion"))
46-
)
47-
_ <- botMrs.traverse(mr => gitlab.deleteMergeRequest(config.project, mr.mergeRequestIid))
64+
_ <- readConsent
65+
_ <- deleteMergeRequests(config.project, botMrs)
4866
_ <- Logger[F].info("Done processing merge requests")
49-
_ <- Logger[F].info("Creating webhook")
50-
_ <- gitlab.createWebhook(config.project, config.pitgullWebhookUrl)
51-
_ <- Logger[F].info("Webhook created")
67+
_ <- Logger[F].info("Configuring webhook")
68+
_ <- configureWebhooks(config.project, config.pitgullWebhookUrl)
5269
_ <- Logger[F].success("Bootstrap finished")
5370
} yield ()
5471
}

build.sbt

+2
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ lazy val bootstrap = project
115115
"org.typelevel" %% "cats-effect" % "3.1.1",
116116
"com.kubukoz" %% "caliban-gitlab" % "0.1.0",
117117
"com.softwaremill.sttp.client3" %% "core" % "3.3.6",
118+
"com.softwaremill.sttp.client3" %% "circe" % "3.3.6",
119+
"io.circe" %% "circe-core" % "0.14.1",
118120
crossPlugin("com.kubukoz" % "better-tostring" % "0.3.3")
119121
),
120122
publish / skip := true,

0 commit comments

Comments
 (0)