From 8967c1c5c191b8ee9e5d6bcf923f71fa4c31c4ab Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Mon, 31 May 2021 18:31:27 +0200 Subject: [PATCH 01/26] getting started, issues with CE3 in Scala 3 --- .../src/main/scala/org/polyvariant/Args.scala | 19 +++++++++++++++++++ .../src/main/scala/org/polyvariant/Main.scala | 9 +++++++++ build.sbt | 17 +++++++++++++++++ project/plugins.sbt | 1 + 4 files changed, 46 insertions(+) create mode 100644 bootstrap/src/main/scala/org/polyvariant/Args.scala create mode 100644 bootstrap/src/main/scala/org/polyvariant/Main.scala diff --git a/bootstrap/src/main/scala/org/polyvariant/Args.scala b/bootstrap/src/main/scala/org/polyvariant/Args.scala new file mode 100644 index 00000000..901ed8e7 --- /dev/null +++ b/bootstrap/src/main/scala/org/polyvariant/Args.scala @@ -0,0 +1,19 @@ +package org.polyvariant + +object Args { + private val switch = "-(\\w+)".r + private val option = "--(\\w+)".r + + private def parseNext(pendingArguments: List[String], previousResult: Map[String, String]): Map[String, String] = + pendingArguments match { + case Nil => previousResult + case option(opt) :: value :: tail => parseNext(tail, previousResult ++ Map(opt -> value)) + case switch(opt) :: tail => parseNext(tail, previousResult ++ Map(opt -> null)) + case text :: Nil => previousResult ++ Map(text -> null) + case text :: tail => parseNext(tail, previousResult ++ Map(text -> null)) + } + + def parse(args: Array[String]): Map[String, String] = + parseNext(args.toList, Map()) + +} diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala new file mode 100644 index 00000000..890bc2be --- /dev/null +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -0,0 +1,9 @@ +package org.polyvariant + +import cats.effect.IOApp + +object Main extends IOApp { + + override def run(args: List[String]): IO[ExitCode] = + ??? +} diff --git a/build.sbt b/build.sbt index cb6ebcbb..ff76a4d3 100644 --- a/build.sbt +++ b/build.sbt @@ -100,6 +100,23 @@ lazy val gitlab = project ) .dependsOn(core) +lazy val bootstrap = project + .settings( + scalaVersion := "3.0.0", + libraryDependencies ++= List( + "org.typelevel" %% "cats-core" % "2.6.1", + "org.typelevel" %% "cats-effect" % "3.1.1", + "com.kubukoz" %% "caliban-gitlab" % "0.1.0", + ), + testFrameworks += new TestFramework("weaver.framework.TestFramework"), + publish / skip := true, + Compile / mainClass := Some("org.polyvariant.Main"), + // scalacOptions ++= Seq("-rewrite", "-source", "future-migration") + scalacOptions --= Seq("-source", "future") + ) + .dependsOn(core) + .enablePlugins(NativeImagePlugin) + lazy val core = project.settings(commonSettings).settings(name += "-core") //workaround for docker not accepting + (the default separator in sbt-dynver) diff --git a/project/plugins.sbt b/project/plugins.sbt index 1c48170b..62e682b7 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -4,3 +4,4 @@ addSbtPlugin("com.codecommit" % "sbt-github-actions" % "0.12.0") addSbtPlugin("com.eed3si9n" % "sbt-buildinfo" % "0.10.0") addSbtPlugin("com.dwijnand" % "sbt-dynver" % "4.1.1") addSbtPlugin("ch.epfl.scala" % "sbt-missinglink" % "0.3.2") +addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.0") From a0992d90d343ca73e6773a2ca5e4ea346858bc5d Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Mon, 31 May 2021 18:52:25 +0200 Subject: [PATCH 02/26] fix build --- bootstrap/src/main/scala/org/polyvariant/Args.scala | 2 +- bootstrap/src/main/scala/org/polyvariant/Main.scala | 7 +++++-- build.sbt | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Args.scala b/bootstrap/src/main/scala/org/polyvariant/Args.scala index 901ed8e7..949e986a 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Args.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Args.scala @@ -13,7 +13,7 @@ object Args { case text :: tail => parseNext(tail, previousResult ++ Map(text -> null)) } - def parse(args: Array[String]): Map[String, String] = + def parse(args: List[String]): Map[String, String] = parseNext(args.toList, Map()) } diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 890bc2be..66b03927 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -1,9 +1,12 @@ package org.polyvariant -import cats.effect.IOApp +import cats.implicits.* +import cats.effect.* object Main extends IOApp { override def run(args: List[String]): IO[ExitCode] = - ??? + IO.println("Hello World!") *> + IO.println(Args.parse(args).toString) *> + IO.pure(ExitCode.Success) } diff --git a/build.sbt b/build.sbt index ff76a4d3..60fea5b0 100644 --- a/build.sbt +++ b/build.sbt @@ -114,7 +114,7 @@ lazy val bootstrap = project // scalacOptions ++= Seq("-rewrite", "-source", "future-migration") scalacOptions --= Seq("-source", "future") ) - .dependsOn(core) + // .dependsOn(core) .enablePlugins(NativeImagePlugin) lazy val core = project.settings(commonSettings).settings(name += "-core") From 628ec6fb1de2666e5426ce38075f6c4bffdc78e4 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Mon, 31 May 2021 19:06:58 +0200 Subject: [PATCH 03/26] add merge requests querry --- .../src/main/scala/org/polyvariant/Args.scala | 2 ++ .../src/main/scala/org/polyvariant/Gitlab.scala | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 bootstrap/src/main/scala/org/polyvariant/Gitlab.scala diff --git a/bootstrap/src/main/scala/org/polyvariant/Args.scala b/bootstrap/src/main/scala/org/polyvariant/Args.scala index 949e986a..2ab1cbdc 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Args.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Args.scala @@ -13,6 +13,8 @@ object Args { case text :: tail => parseNext(tail, previousResult ++ Map(text -> null)) } + + // TODO: Consider switching to https://ben.kirw.in/decline/ def parse(args: List[String]): Map[String, String] = parseNext(args.toList, Map()) diff --git a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala new file mode 100644 index 00000000..1c161765 --- /dev/null +++ b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala @@ -0,0 +1,16 @@ +package org.polyvariant +import io.pg.gitlab.graphql.* + +object Gitlab { + def mergeRequestsQuery(projectFullPath: String) = Query.project("") { + Project.mergeRequests()( + MergeRequestConnection.edges{ + MergeRequestEdge.node { + MergeRequest.title ~ MergeRequest.description ~ MergeRequest.author{ + UserCore.name + } + } + } + ) + } +} From ccc3b5dc9c0e0c141967ad31bd5bd257d3ff845b Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Sat, 5 Jun 2021 22:55:17 +0200 Subject: [PATCH 04/26] logger --- .../main/scala/org/polyvariant/Gitlab.scala | 2 +- .../main/scala/org/polyvariant/Logger.scala | 28 +++++++++++++++++++ .../src/main/scala/org/polyvariant/Main.scala | 15 +++++++--- 3 files changed, 40 insertions(+), 5 deletions(-) create mode 100644 bootstrap/src/main/scala/org/polyvariant/Logger.scala diff --git a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala index 1c161765..cc3c51b5 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala @@ -2,7 +2,7 @@ package org.polyvariant import io.pg.gitlab.graphql.* object Gitlab { - def mergeRequestsQuery(projectFullPath: String) = Query.project("") { + def mergeRequestsQuery(projectFullPath: String) = Query.project(projectFullPath) { Project.mergeRequests()( MergeRequestConnection.edges{ MergeRequestEdge.node { diff --git a/bootstrap/src/main/scala/org/polyvariant/Logger.scala b/bootstrap/src/main/scala/org/polyvariant/Logger.scala new file mode 100644 index 00000000..5879426e --- /dev/null +++ b/bootstrap/src/main/scala/org/polyvariant/Logger.scala @@ -0,0 +1,28 @@ +package org.polyvariant + +import cats.syntax.apply +import cats.effect.kernel.Sync +import scala.io.AnsiColor._ + +trait Logger[F[_]] { + def debug(msg: String): F[Unit] + def success(msg: String): F[Unit] + def info(msg: String): F[Unit] + def warn(msg: String): F[Unit] + def error(msg: String): F[Unit] +} + +object Logger { + def apply[F[_]](using ev: Logger[F]): Logger[F] = ev + + def wrappedPrint[F[_]: Sync] = new Logger[F] { + private def colorPrinter(color: String)(msg: String): F[Unit] = + Sync[F].delay(println(s"${color}${msg}${RESET}")) + + override def debug(msg: String): F[Unit] = colorPrinter(CYAN)(msg) + override def success(msg: String): F[Unit] = colorPrinter(GREEN)(msg) + override def info(msg: String): F[Unit] = colorPrinter(WHITE)(msg) + override def warn(msg: String): F[Unit] = colorPrinter(YELLOW)(msg) + override def error(msg: String): F[Unit] = colorPrinter(RED)(msg) + } +} diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 66b03927..2c4b525f 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -5,8 +5,15 @@ import cats.effect.* object Main extends IOApp { - override def run(args: List[String]): IO[ExitCode] = - IO.println("Hello World!") *> - IO.println(Args.parse(args).toString) *> - IO.pure(ExitCode.Success) + private def program[F[_]: Logger: Sync](args: List[String]): F[Unit] = + Logger[F].info("Starting pitgull bootstrap!") *> + Logger[F].debug(Args.parse(args).toString) + + override def run(args: List[String]): IO[ExitCode] = { + given logger: Logger[IO] = Logger.wrappedPrint[IO] + program[IO](args) *> + IO.pure(ExitCode.Success) + } + + } From e906f5ad5afd9c4a9bd60f55f2b98865e54a2508 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Tue, 8 Jun 2021 18:54:59 +0200 Subject: [PATCH 05/26] implement gitlab client --- .../main/scala/org/polyvariant/Gitlab.scala | 113 ++++++++++++++++-- .../src/main/scala/org/polyvariant/Main.scala | 37 ++++-- build.sbt | 4 +- 3 files changed, 136 insertions(+), 18 deletions(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala index cc3c51b5..96934c81 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala @@ -1,16 +1,111 @@ package org.polyvariant + +import cats.implicits.* + + +import scala.util.chaining._ import io.pg.gitlab.graphql.* +import sttp.model.Uri +import sttp.client3.* +import caliban.client.SelectionBuilder +import caliban.client.CalibanClientError.DecodingError +import io.pg.gitlab.graphql.MergeRequest +import io.pg.gitlab.graphql.MergeRequestConnection +import io.pg.gitlab.graphql.MergeRequestState +import io.pg.gitlab.graphql.Pipeline +import io.pg.gitlab.graphql.PipelineStatusEnum +import io.pg.gitlab.graphql.Project +import io.pg.gitlab.graphql.ProjectConnection +import io.pg.gitlab.graphql.Query +import io.pg.gitlab.graphql.UserCore +import cats.MonadError +import caliban.client.Operations.IsOperation +trait Gitlab[F[_]] { + def mergeRequests(projectId: Long): F[List[Gitlab.MergeRequestInfo]] +} object Gitlab { - def mergeRequestsQuery(projectFullPath: String) = Query.project(projectFullPath) { - Project.mergeRequests()( - MergeRequestConnection.edges{ - MergeRequestEdge.node { - MergeRequest.title ~ MergeRequest.description ~ MergeRequest.author{ - UserCore.name + + def sttpInstance[F[_]: Logger]( + baseUri: Uri, + accessToken: String + )( + using backend: SttpBackend[Identity, Any], // FIXME: all cats-effect compatible backends rely on Netty, while netty breaks native-image build + // using backend: SttpBackend[F, Any], + ME: MonadError[F, Throwable] + ): Gitlab[F] = { + def runRequest[O](request: Request[O, Any]): F[O] = + request.header("Private-Token", accessToken).send(backend).pure[F].map(_.body) // FIXME - change to async backend + + def runGraphQLQuery[A: IsOperation, B](a: SelectionBuilder[A, B]): F[B] = + runRequest(a.toRequest(baseUri.addPath("api", "graphql"))).rethrow + + + new Gitlab[F] { + def mergeRequests(projectId: Long): F[List[MergeRequestInfo]] = + Logger[F].info(s"Looking up merge requests for project: $projectId") *> + mergeRequestsQuery(projectId) + .mapEither(_.toRight(DecodingError("Project not found"))) + .pipe(runGraphQLQuery(_)) + .flatTap { result => + Logger[F].info(s"Found merge requests. Size: ${result.size}") } - } - } - ) + } + } + + final case class MergeRequestInfo( + projectId: Long, + mergeRequestIid: Long, + authorUsername: String, + description: Option[String], + needsRebase: Boolean, + hasConflicts: Boolean + ) + + private def flattenTheEarth[A]: Option[List[Option[Option[Option[List[Option[A]]]]]]] => List[A] = + _.toList.flatten.flatten.flatten.flatten.flatten.flatten + + private def mergeRequestInfoSelection(projectId: Long): SelectionBuilder[MergeRequest, MergeRequestInfo] = ( + MergeRequest.iid.mapEither(_.toLongOption.toRight(DecodingError("MR IID wasn't a Long"))) ~ + MergeRequest + .author(UserCore.username) + .mapEither(_.toRight(DecodingError("MR has no author"))) ~ + MergeRequest.description ~ + MergeRequest.shouldBeRebased ~ + MergeRequest.conflicts + ).mapN((buildMergeRequest(projectId) _)) + + private def buildMergeRequest( + projectId: Long + )( + mergeRequestIid: Long, + authorUsername: String, + description: Option[String], + needsRebase: Boolean, + hasConflicts: Boolean + ): MergeRequestInfo = MergeRequestInfo( + projectId = projectId, + mergeRequestIid = mergeRequestIid, + authorUsername = authorUsername, + description = description, + needsRebase = needsRebase, + hasConflicts = hasConflicts + ) + + private def mergeRequestsQuery(projectId: Long) = + Query + .projects(ids = List(show"gid://gitlab/Project/$projectId").some)( + ProjectConnection + .nodes( + Project + .mergeRequests( + state = MergeRequestState.opened.some + )( + MergeRequestConnection + .nodes(mergeRequestInfoSelection(projectId)) + ) + ) + .map(flattenTheEarth) + ) } diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 2c4b525f..ff62134b 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -3,17 +3,38 @@ package org.polyvariant import cats.implicits.* import cats.effect.* +import sttp.model.Uri + +import sttp.client3.* +import org.polyvariant.Gitlab.MergeRequestInfo +import cats.Applicative + object Main extends IOApp { - private def program[F[_]: Logger: Sync](args: List[String]): F[Unit] = - Logger[F].info("Starting pitgull bootstrap!") *> - Logger[F].debug(Args.parse(args).toString) - override def run(args: List[String]): IO[ExitCode] = { + private def printMergeRequests[F[_]: Logger: Applicative](mergeRequests: List[MergeRequestInfo]): F[Unit] = + mergeRequests.traverse { mr => + Logger[F].info(s"[${mr.mergeRequestIid}] ${mr.description.getOrElse("Missing description")}") + }.void + + + private def program[F[_]: Logger: Async](args: List[String]): F[Unit] = + given backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() + for { + _ <- Logger[F].info("Starting pitgull bootstrap!") + parsedArgs = Args.parse(args) + gitlabUri = Uri.unsafeParse(parsedArgs("url")) + token = parsedArgs("token") + project = parsedArgs("project") + gitlab = Gitlab.sttpInstance[F](gitlabUri, token) + mrs <- gitlab.mergeRequests(project.toLong) + _ <- Logger[F].info(s"Merge requests found:") + _ <- printMergeRequests(mrs) + } yield () + + override def run(args: List[String]): IO[ExitCode] = given logger: Logger[IO] = Logger.wrappedPrint[IO] - program[IO](args) *> + program[IO](args) *> IO.pure(ExitCode.Success) - } - - + } diff --git a/build.sbt b/build.sbt index 60fea5b0..021fd111 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ -import com.typesafe.sbt.packager.docker.ExecCmd import com.typesafe.sbt.packager.docker.Cmd +import com.typesafe.sbt.packager.docker.ExecCmd inThisBuild( List( @@ -107,6 +107,8 @@ lazy val bootstrap = project "org.typelevel" %% "cats-core" % "2.6.1", "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" %% "armeria-backend-cats" % "3.3.6" ), testFrameworks += new TestFramework("weaver.framework.TestFramework"), publish / skip := true, From 4be5d69dd320693207e74935dff7642955c2b291 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Tue, 8 Jun 2021 18:59:36 +0200 Subject: [PATCH 06/26] fix workflow --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40d931ac..455a42f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: - run: sbt --client '++${{ matrix.scala }}; test; missinglinkCheck' - name: Compress target directories - run: tar cf targets.tar target gitlab/target core/target project/target + run: tar cf targets.tar target gitlab/target bootstrap/target core/target project/target - name: Upload target directories uses: actions/upload-artifact@v2 From c7d209a6590ee0e69025558b26858507ea611c55 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Tue, 8 Jun 2021 23:35:26 +0200 Subject: [PATCH 07/26] close qualified merge requests --- .../main/scala/org/polyvariant/Gitlab.scala | 50 ++++++++++++++----- .../src/main/scala/org/polyvariant/Main.scala | 29 ++++++++--- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala index 96934c81..0338508c 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala @@ -2,7 +2,6 @@ package org.polyvariant import cats.implicits.* - import scala.util.chaining._ import io.pg.gitlab.graphql.* import sttp.model.Uri @@ -20,10 +19,13 @@ import io.pg.gitlab.graphql.Query import io.pg.gitlab.graphql.UserCore import cats.MonadError import caliban.client.Operations.IsOperation +import sttp.model.Method trait Gitlab[F[_]] { def mergeRequests(projectId: Long): F[List[Gitlab.MergeRequestInfo]] + def closeMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit] } + object Gitlab { def sttpInstance[F[_]: Logger]( @@ -36,24 +38,45 @@ object Gitlab { ): Gitlab[F] = { def runRequest[O](request: Request[O, Any]): F[O] = request.header("Private-Token", accessToken).send(backend).pure[F].map(_.body) // FIXME - change to async backend - + def runGraphQLQuery[A: IsOperation, B](a: SelectionBuilder[A, B]): F[B] = runRequest(a.toRequest(baseUri.addPath("api", "graphql"))).rethrow - new Gitlab[F] { def mergeRequests(projectId: Long): F[List[MergeRequestInfo]] = - Logger[F].info(s"Looking up merge requests for project: $projectId") *> - mergeRequestsQuery(projectId) - .mapEither(_.toRight(DecodingError("Project not found"))) - .pipe(runGraphQLQuery(_)) - .flatTap { result => - Logger[F].info(s"Found merge requests. Size: ${result.size}") - } + Logger[F].info(s"Looking up merge requests for project: $projectId") *> + mergeRequestsQuery(projectId) + .mapEither(_.toRight(DecodingError("Project not found"))) + .pipe(runGraphQLQuery(_)) + .flatTap { result => + Logger[F].info(s"Found merge requests. Size: ${result.size}") + } + + def closeMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit] = for { + _ <- Logger[F].debug(s"Request to: ${baseUri.addPath(Seq("projects",projectId.toString,"merge_requests",mergeRequestId.toString))}") + result <- runRequest( + basicRequest.put( + baseUri + .addPath( + Seq( + "api", + "v4", + "projects", + projectId.toString, + "merge_requests", + mergeRequestId.toString + ) + ) + ) + .body("""{"state_event": "close"}""") + .contentType("application/json") + ) + _ <- Logger[F].info(result.toString) + } yield () } } - + final case class MergeRequestInfo( projectId: Long, mergeRequestIid: Long, @@ -91,9 +114,9 @@ object Gitlab { description = description, needsRebase = needsRebase, hasConflicts = hasConflicts - ) + ) - private def mergeRequestsQuery(projectId: Long) = + private def mergeRequestsQuery(projectId: Long) = Query .projects(ids = List(show"gid://gitlab/Project/$projectId").some)( ProjectConnection @@ -108,4 +131,5 @@ object Gitlab { ) .map(flattenTheEarth) ) + } diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index ff62134b..18d651a7 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -8,28 +8,32 @@ import sttp.model.Uri import sttp.client3.* import org.polyvariant.Gitlab.MergeRequestInfo import cats.Applicative +import sttp.monad.MonadError object Main extends IOApp { - private def printMergeRequests[F[_]: Logger: Applicative](mergeRequests: List[MergeRequestInfo]): F[Unit] = mergeRequests.traverse { mr => Logger[F].info(s"[${mr.mergeRequestIid}] ${mr.description.getOrElse("Missing description")}") }.void + private def qualifyMergeRequests(botUserName: String, mergeRequests: List[MergeRequestInfo]): List[MergeRequestInfo] = + mergeRequests.filter(_.authorUsername == botUserName) private def program[F[_]: Logger: Async](args: List[String]): F[Unit] = given backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() for { _ <- Logger[F].info("Starting pitgull bootstrap!") parsedArgs = Args.parse(args) - gitlabUri = Uri.unsafeParse(parsedArgs("url")) - token = parsedArgs("token") - project = parsedArgs("project") - gitlab = Gitlab.sttpInstance[F](gitlabUri, token) - mrs <- gitlab.mergeRequests(project.toLong) - _ <- Logger[F].info(s"Merge requests found:") + config = Config.fromArgs(parsedArgs) + gitlab = Gitlab.sttpInstance[F](config.gitlabUri, config.token) + mrs <- gitlab.mergeRequests(config.project) + _ <- Logger[F].info(s"Merge requests found: ${mrs.length}") _ <- printMergeRequests(mrs) + botMrs = qualifyMergeRequests(config.botUser, mrs) + _ <- Logger[F].info(s"Will close merge requests: ${botMrs.map(_.mergeRequestIid)}") + _ <- botMrs.traverse(mr => gitlab.closeMergeRequest(config.project, mr.mergeRequestIid)) + _ <- Logger[F].info("Done processing merge requests") } yield () override def run(args: List[String]): IO[ExitCode] = @@ -37,4 +41,15 @@ object Main extends IOApp { program[IO](args) *> IO.pure(ExitCode.Success) + + final case class Config( + gitlabUri: Uri, + token: String, + project: Long, + botUser: String + ) + object Config { + def fromArgs(args: Map[String, String]): Config = // FIXME: this is unsafe + Config(Uri.unsafeParse(args("url")), args("token"), args("project").toLong, args("bot")) + } } From a250e9c0deb1d81f76b45033dab57b5b8bbb8e50 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Tue, 8 Jun 2021 23:53:01 +0200 Subject: [PATCH 08/26] cleanup --- bootstrap/src/main/scala/org/polyvariant/Gitlab.scala | 4 +--- bootstrap/src/main/scala/org/polyvariant/Main.scala | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala index 0338508c..35fbef6b 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala @@ -33,7 +33,6 @@ object Gitlab { accessToken: String )( using backend: SttpBackend[Identity, Any], // FIXME: all cats-effect compatible backends rely on Netty, while netty breaks native-image build - // using backend: SttpBackend[F, Any], ME: MonadError[F, Throwable] ): Gitlab[F] = { def runRequest[O](request: Request[O, Any]): F[O] = @@ -67,11 +66,10 @@ object Gitlab { mergeRequestId.toString ) ) - ) + ) .body("""{"state_event": "close"}""") .contentType("application/json") ) - _ <- Logger[F].info(result.toString) } yield () } diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 18d651a7..10d81b5b 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -36,11 +36,11 @@ object Main extends IOApp { _ <- Logger[F].info("Done processing merge requests") } yield () - override def run(args: List[String]): IO[ExitCode] = + override def run(args: List[String]): IO[ExitCode] = { given logger: Logger[IO] = Logger.wrappedPrint[IO] program[IO](args) *> IO.pure(ExitCode.Success) - + } final case class Config( gitlabUri: Uri, From 8627f8ac6d6b670feb5d40148e9f2de3e7d3ee35 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Wed, 9 Jun 2021 00:02:54 +0200 Subject: [PATCH 09/26] clean up build.sbt --- build.sbt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index 021fd111..20661817 100644 --- a/build.sbt +++ b/build.sbt @@ -107,16 +107,13 @@ lazy val bootstrap = project "org.typelevel" %% "cats-core" % "2.6.1", "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" %% "armeria-backend-cats" % "3.3.6" + "com.softwaremill.sttp.client3" %% "core" % "3.3.6" ), testFrameworks += new TestFramework("weaver.framework.TestFramework"), publish / skip := true, Compile / mainClass := Some("org.polyvariant.Main"), - // scalacOptions ++= Seq("-rewrite", "-source", "future-migration") scalacOptions --= Seq("-source", "future") ) - // .dependsOn(core) .enablePlugins(NativeImagePlugin) lazy val core = project.settings(commonSettings).settings(name += "-core") From 9ff5c41f18b662617b8d2952a85a0b67765e6ddf Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Wed, 9 Jun 2021 17:41:16 +0200 Subject: [PATCH 10/26] delete MR instead of closing it, implement webhook creation API --- .../main/scala/org/polyvariant/Gitlab.scala | 35 ++++++++++++++----- .../src/main/scala/org/polyvariant/Main.scala | 6 ++-- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala index 35fbef6b..bcafb954 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala @@ -20,20 +20,21 @@ import io.pg.gitlab.graphql.UserCore import cats.MonadError import caliban.client.Operations.IsOperation import sttp.model.Method +import cats.MonadThrow trait Gitlab[F[_]] { def mergeRequests(projectId: Long): F[List[Gitlab.MergeRequestInfo]] - def closeMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit] + def deleteMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit] + def createWebhook(projectId: Long, pitgullUrl: Uri): F[Unit] } object Gitlab { - def sttpInstance[F[_]: Logger]( + def sttpInstance[F[_]: Logger: MonadThrow]( baseUri: Uri, accessToken: String )( - using backend: SttpBackend[Identity, Any], // FIXME: all cats-effect compatible backends rely on Netty, while netty breaks native-image build - ME: MonadError[F, Throwable] + using backend: SttpBackend[Identity, Any] // FIXME: all cats-effect compatible backends rely on Netty, while netty breaks native-image build ): Gitlab[F] = { def runRequest[O](request: Request[O, Any]): F[O] = request.header("Private-Token", accessToken).send(backend).pure[F].map(_.body) // FIXME - change to async backend @@ -51,10 +52,10 @@ object Gitlab { Logger[F].info(s"Found merge requests. Size: ${result.size}") } - def closeMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit] = for { - _ <- Logger[F].debug(s"Request to: ${baseUri.addPath(Seq("projects",projectId.toString,"merge_requests",mergeRequestId.toString))}") + def deleteMergeRequest(projectId: Long, mergeRequestId: Long): F[Unit] = for { + _ <- Logger[F].debug(s"Request to remove $mergeRequestId") result <- runRequest( - basicRequest.put( + basicRequest.delete( baseUri .addPath( Seq( @@ -67,7 +68,25 @@ object Gitlab { ) ) ) - .body("""{"state_event": "close"}""") + ) + } yield () + + def createWebhook(projectId: Long, pitgullUrl: Uri): F[Unit] = for { + _ <- Logger[F].debug(s"Creating webhook to $pitgullUrl") + result <- runRequest( + basicRequest.post( + baseUri + .addPath( + Seq( + "api", + "v4", + "projects", + projectId.toString, + "hooks" + ) + ) + ) + .body("""{"merge_requests_events": true, "pipeline_events": true, "note_events": true}""") .contentType("application/json") ) } yield () diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 10d81b5b..86715bc2 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -14,7 +14,7 @@ object Main extends IOApp { private def printMergeRequests[F[_]: Logger: Applicative](mergeRequests: List[MergeRequestInfo]): F[Unit] = mergeRequests.traverse { mr => - Logger[F].info(s"[${mr.mergeRequestIid}] ${mr.description.getOrElse("Missing description")}") + Logger[F].info(s"ID: ${mr.mergeRequestIid} by: ${mr.authorUsername}") }.void private def qualifyMergeRequests(botUserName: String, mergeRequests: List[MergeRequestInfo]): List[MergeRequestInfo] = @@ -31,8 +31,8 @@ object Main extends IOApp { _ <- Logger[F].info(s"Merge requests found: ${mrs.length}") _ <- printMergeRequests(mrs) botMrs = qualifyMergeRequests(config.botUser, mrs) - _ <- Logger[F].info(s"Will close merge requests: ${botMrs.map(_.mergeRequestIid)}") - _ <- botMrs.traverse(mr => gitlab.closeMergeRequest(config.project, mr.mergeRequestIid)) + _ <- Logger[F].info(s"Will delete merge requests: ${botMrs.map(_.mergeRequestIid)}") + _ <- botMrs.traverse(mr => gitlab.deleteMergeRequest(config.project, mr.mergeRequestIid)) _ <- Logger[F].info("Done processing merge requests") } yield () From e73fb2f0f4e41651fb2b9e5db9416cf646ab1aba Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Wed, 9 Jun 2021 17:48:35 +0200 Subject: [PATCH 11/26] exclude bootstrap from publishing artifact --- .github/workflows/ci.yml | 2 +- build.sbt | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 455a42f6..40d931ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,7 +54,7 @@ jobs: - run: sbt --client '++${{ matrix.scala }}; test; missinglinkCheck' - name: Compress target directories - run: tar cf targets.tar target gitlab/target bootstrap/target core/target project/target + run: tar cf targets.tar target gitlab/target core/target project/target - name: Upload target directories uses: actions/upload-artifact@v2 diff --git a/build.sbt b/build.sbt index 20661817..ef2eabbd 100644 --- a/build.sbt +++ b/build.sbt @@ -112,7 +112,8 @@ lazy val bootstrap = project testFrameworks += new TestFramework("weaver.framework.TestFramework"), publish / skip := true, Compile / mainClass := Some("org.polyvariant.Main"), - scalacOptions --= Seq("-source", "future") + scalacOptions --= Seq("-source", "future"), + githubWorkflowArtifactUpload := false ) .enablePlugins(NativeImagePlugin) From b5afe33397dbe33627c9fd9b8a52dff8b04aebd8 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Wed, 9 Jun 2021 18:03:36 +0200 Subject: [PATCH 12/26] cleanup --- bootstrap/src/main/scala/org/polyvariant/Gitlab.scala | 1 - bootstrap/src/main/scala/org/polyvariant/Main.scala | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala index bcafb954..70c01773 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala @@ -17,7 +17,6 @@ import io.pg.gitlab.graphql.Project import io.pg.gitlab.graphql.ProjectConnection import io.pg.gitlab.graphql.Query import io.pg.gitlab.graphql.UserCore -import cats.MonadError import caliban.client.Operations.IsOperation import sttp.model.Method import cats.MonadThrow diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 86715bc2..491e96a4 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -17,7 +17,7 @@ object Main extends IOApp { Logger[F].info(s"ID: ${mr.mergeRequestIid} by: ${mr.authorUsername}") }.void - private def qualifyMergeRequests(botUserName: String, mergeRequests: List[MergeRequestInfo]): List[MergeRequestInfo] = + private def qualifyMergeRequestsForDeletion(botUserName: String, mergeRequests: List[MergeRequestInfo]): List[MergeRequestInfo] = mergeRequests.filter(_.authorUsername == botUserName) private def program[F[_]: Logger: Async](args: List[String]): F[Unit] = @@ -30,14 +30,14 @@ object Main extends IOApp { mrs <- gitlab.mergeRequests(config.project) _ <- Logger[F].info(s"Merge requests found: ${mrs.length}") _ <- printMergeRequests(mrs) - botMrs = qualifyMergeRequests(config.botUser, mrs) + botMrs = qualifyMergeRequestsForDeletion(config.botUser, mrs) _ <- Logger[F].info(s"Will delete merge requests: ${botMrs.map(_.mergeRequestIid)}") _ <- botMrs.traverse(mr => gitlab.deleteMergeRequest(config.project, mr.mergeRequestIid)) _ <- Logger[F].info("Done processing merge requests") } yield () override def run(args: List[String]): IO[ExitCode] = { - given logger: Logger[IO] = Logger.wrappedPrint[IO] + given Logger[IO] = Logger.wrappedPrint[IO] program[IO](args) *> IO.pure(ExitCode.Success) } From d1051a67e17ef74dfec40736037e6b9e9a1bfcbe Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Wed, 9 Jun 2021 18:45:09 +0200 Subject: [PATCH 13/26] fix webhook creation --- bootstrap/src/main/scala/org/polyvariant/Gitlab.scala | 2 +- bootstrap/src/main/scala/org/polyvariant/Main.scala | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala index 70c01773..cff3c394 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala @@ -85,7 +85,7 @@ object Gitlab { ) ) ) - .body("""{"merge_requests_events": true, "pipeline_events": true, "note_events": true}""") + .body(s"""{"merge_requests_events": true, "pipeline_events": true, "note_events": true, "url": "$pitgullUrl"}""") .contentType("application/json") ) } yield () diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 491e96a4..ccdfbfb9 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -34,6 +34,10 @@ object Main extends IOApp { _ <- Logger[F].info(s"Will delete merge requests: ${botMrs.map(_.mergeRequestIid)}") _ <- botMrs.traverse(mr => gitlab.deleteMergeRequest(config.project, mr.mergeRequestIid)) _ <- 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].success("Bootstrap finished") } yield () override def run(args: List[String]): IO[ExitCode] = { @@ -46,10 +50,11 @@ object Main extends IOApp { gitlabUri: Uri, token: String, project: Long, - botUser: String + botUser: String, + pitgullWebhookUrl: Uri ) object Config { def fromArgs(args: Map[String, String]): Config = // FIXME: this is unsafe - Config(Uri.unsafeParse(args("url")), args("token"), args("project").toLong, args("bot")) + Config(Uri.unsafeParse(args("url")), args("token"), args("project").toLong, args("bot"), Uri.unsafeParse(args("webhook"))) } } From d929b2939d73ee179b1f03b719b933dd851890a0 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Wed, 9 Jun 2021 19:06:31 +0200 Subject: [PATCH 14/26] build native image in github actions --- .github/workflows/ci.yml | 9 +++++++++ build.sbt | 15 +++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40d931ac..181e681d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,15 @@ jobs: - run: sbt --client '++${{ matrix.scala }}; test; missinglinkCheck' + - name: Build native image + run: sbt bootstrap/nativeImage + + - name: Upload binary + uses: actions/upload-artifact@v2 + with: + name: pitgull-bootstrap-${{ matrix.os }} + path: ./bootstrap/target/native-image/bootstrap + - name: Compress target directories run: tar cf targets.tar target gitlab/target core/target project/target diff --git a/build.sbt b/build.sbt index ef2eabbd..70d9cdd1 100644 --- a/build.sbt +++ b/build.sbt @@ -56,6 +56,21 @@ ThisBuild / missinglinkExcludedDependencies += moduleFilter( name = "slf4j-api" ) +ThisBuild / githubWorkflowBuild ++= Seq( + WorkflowStep.Run( + List("sbt bootstrap/nativeImage"), + name = Some("Build native image") + ), + WorkflowStep.Use( + UseRef.Public("actions", "upload-artifact", "v2"), + name = Some(s"Upload binary"), + params = Map( + "name" -> s"pitgull-bootstrap-$${{ matrix.os }}", + "path" -> "./bootstrap/target/native-image/bootstrap" + ) + ) +) + def crossPlugin(x: sbt.librarymanagement.ModuleID) = compilerPlugin(x.cross(CrossVersion.full)) From b778495840a7a402e5650d0a57c44abe17b32633 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Wed, 9 Jun 2021 19:35:06 +0200 Subject: [PATCH 15/26] use workflow from https://github.com/scalameta/sbt-native-image\#generate-native-image-from-github-actions --- .github/workflows/ci.yml | 9 ------ .github/workflows/native.yml | 56 ++++++++++++++++++++++++++++++++++++ build.sbt | 15 ---------- 3 files changed, 56 insertions(+), 24 deletions(-) create mode 100644 .github/workflows/native.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 181e681d..40d931ac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,15 +53,6 @@ jobs: - run: sbt --client '++${{ matrix.scala }}; test; missinglinkCheck' - - name: Build native image - run: sbt bootstrap/nativeImage - - - name: Upload binary - uses: actions/upload-artifact@v2 - with: - name: pitgull-bootstrap-${{ matrix.os }} - path: ./bootstrap/target/native-image/bootstrap - - name: Compress target directories run: tar cf targets.tar target gitlab/target core/target project/target diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml new file mode 100644 index 00000000..f66150df --- /dev/null +++ b/.github/workflows/native.yml @@ -0,0 +1,56 @@ +name: Native Image +on: + push: + branches: + - master + - main + pull_request: + release: + types: [published] +jobs: + unix: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macOS-latest, ubuntu-latest, windows-latest] + include: + - os: macOS-latest + uploaded_filename: bootstrap-x86_64-apple-darwin + local_path: bootstrap/target/native-image/bootstrap + - os: ubuntu-latest + uploaded_filename: bootstrap-x86_64-pc-linux + local_path: bootstrap/target/native-image/bootstrap + - os: windows-latest + uploaded_filename: bootstrap-x86_64-pc-win32.exe + local_path: bootstrap\target\native-image\bootstrap.exe + steps: + - uses: actions/checkout@v2 + - uses: olafurpg/setup-scala@v10 + - run: git fetch --tags || true + - run: sbt bootstrap/nativeImage + shell: bash + if: ${{ matrix.os != 'windows-latest' }} + - run: echo $(pwd) + shell: bash + - name: sbt test + shell: cmd + if: ${{ matrix.os == 'windows-latest' }} + run: >- + "C:\Program Files (x86)\Microsoft Visual + Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && sbt + bootstrap/nativeImage + - uses: actions/upload-artifact@v2 + with: + path: ${{ matrix.local_path }} + name: ${{ matrix.uploaded_filename }} + - name: Upload release + if: github.event_name == 'release' + uses: actions/upload-release-asset@v1.0.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ github.event.release.upload_url }} + asset_path: ${{ matrix.local_path }} + asset_name: ${{ matrix.uploaded_filename }} + asset_content_type: application/zip diff --git a/build.sbt b/build.sbt index 70d9cdd1..ef2eabbd 100644 --- a/build.sbt +++ b/build.sbt @@ -56,21 +56,6 @@ ThisBuild / missinglinkExcludedDependencies += moduleFilter( name = "slf4j-api" ) -ThisBuild / githubWorkflowBuild ++= Seq( - WorkflowStep.Run( - List("sbt bootstrap/nativeImage"), - name = Some("Build native image") - ), - WorkflowStep.Use( - UseRef.Public("actions", "upload-artifact", "v2"), - name = Some(s"Upload binary"), - params = Map( - "name" -> s"pitgull-bootstrap-$${{ matrix.os }}", - "path" -> "./bootstrap/target/native-image/bootstrap" - ) - ) -) - def crossPlugin(x: sbt.librarymanagement.ModuleID) = compilerPlugin(x.cross(CrossVersion.full)) From 2ac8fd91b75b5652e8c041260ab9617b74d359e7 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Fri, 11 Jun 2021 12:59:54 +0200 Subject: [PATCH 16/26] add better-tostring, fix reflection config in native-image --- .../src/main/resources/reflect-config.json | 226 ++++++++++++++++++ build.sbt | 16 +- 2 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 bootstrap/src/main/resources/reflect-config.json diff --git a/bootstrap/src/main/resources/reflect-config.json b/bootstrap/src/main/resources/reflect-config.json new file mode 100644 index 00000000..b20f50a1 --- /dev/null +++ b/bootstrap/src/main/resources/reflect-config.json @@ -0,0 +1,226 @@ +[ +{ + "name":"byte[]" +}, +{ + "name":"cats.effect.unsafe.IORuntimeCompanionPlatform", + "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] +}, +{ + "name":"cats.effect.unsafe.WorkStealingThreadPool", + "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] +}, +{ + "name":"com.sun.crypto.provider.AESCipher$General", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.DHParameters", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.circe.Decoder$", + "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] +}, +{ + "name":"io.circe.Encoder$", + "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] +}, +{ + "name":"java.lang.String" +}, +{ + "name":"java.lang.String[]" +}, +{ + "name":"java.lang.invoke.VarHandle", + "methods":[{"name":"releaseFence","parameterTypes":[] }] +}, +{ + "name":"java.security.AlgorithmParametersSpi" +}, +{ + "name":"java.security.KeyStoreSpi" +}, +{ + "name":"java.security.MessageDigestSpi" +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.security.interfaces.RSAPrivateKey" +}, +{ + "name":"java.security.interfaces.RSAPublicKey" +}, +{ + "name":"java.util.Date" +}, +{ + "name":"javax.security.auth.x500.X500Principal", + "fields":[{"name":"thisX500Name"}], + "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] +}, +{ + "name":"sttp.model.Header$", + "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] +}, +{ + "name":"sun.misc.Unsafe", + "allDeclaredFields":true +}, +{ + "name":"sun.security.pkcs.SignerInfo[]" +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.DSA$SHA224withDSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.DSA$SHA256withDSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.JavaKeyStore$DualFormatJKS", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.JavaKeyStore$JKS", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA224", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA2$SHA256", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA384", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.SHA5$SHA512", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.X509Factory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.provider.certpath.PKIXCertPathValidator", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAKeyFactory$Legacy", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSAPSSSignature", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSASignature$SHA224withRSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSASignature$SHA256withRSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.rsa.RSASignature$SHA384withRSA", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.KeyManagerFactoryImpl$SunX509", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.SSLContextImpl$DefaultSSLContext", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.security.util.ObjectIdentifier" +}, +{ + "name":"sun.security.x509.AuthorityInfoAccessExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.AuthorityKeyIdentifierExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.BasicConstraintsExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CRLDistributionPointsExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.CertificateExtensions" +}, +{ + "name":"sun.security.x509.CertificatePoliciesExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.ExtendedKeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.IssuerAlternativeNameExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.KeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.NetscapeCertTypeExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.PrivateKeyUsageExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectAlternativeNameExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +}, +{ + "name":"sun.security.x509.SubjectKeyIdentifierExtension", + "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] +} +] diff --git a/build.sbt b/build.sbt index ef2eabbd..8e4cd827 100644 --- a/build.sbt +++ b/build.sbt @@ -107,13 +107,19 @@ lazy val bootstrap = project "org.typelevel" %% "cats-core" % "2.6.1", "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" %% "core" % "3.3.6", + crossPlugin("com.kubukoz" % "better-tostring" % "0.3.3") ), - testFrameworks += new TestFramework("weaver.framework.TestFramework"), publish / skip := true, - Compile / mainClass := Some("org.polyvariant.Main"), - scalacOptions --= Seq("-source", "future"), - githubWorkflowArtifactUpload := false + // Compile / mainClass := Some("org.polyvariant.Main"), + githubWorkflowArtifactUpload := false, + nativeImageVersion := "21.1.0", + nativeImageOptions ++= Seq( + s"-H:ReflectionConfigurationFiles=${(Compile / resourceDirectory).value / "reflect-config.json"}", + "--enable-url-protocols=https", + "-H:+ReportExceptionStackTraces", + "--no-fallback" + ) ) .enablePlugins(NativeImagePlugin) From 45a7e70dbb554aaa823f5e4b2db0f52daa055760 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Fri, 11 Jun 2021 13:23:33 +0200 Subject: [PATCH 17/26] revert to using sbt workflow configuration instead of native.yml --- .github/workflows/ci.yml | 9 ++++++ .github/workflows/native.yml | 56 ------------------------------------ build.sbt | 15 ++++++++++ 3 files changed, 24 insertions(+), 56 deletions(-) delete mode 100644 .github/workflows/native.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40d931ac..181e681d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,6 +53,15 @@ jobs: - run: sbt --client '++${{ matrix.scala }}; test; missinglinkCheck' + - name: Build native image + run: sbt bootstrap/nativeImage + + - name: Upload binary + uses: actions/upload-artifact@v2 + with: + name: pitgull-bootstrap-${{ matrix.os }} + path: ./bootstrap/target/native-image/bootstrap + - name: Compress target directories run: tar cf targets.tar target gitlab/target core/target project/target diff --git a/.github/workflows/native.yml b/.github/workflows/native.yml deleted file mode 100644 index f66150df..00000000 --- a/.github/workflows/native.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Native Image -on: - push: - branches: - - master - - main - pull_request: - release: - types: [published] -jobs: - unix: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [macOS-latest, ubuntu-latest, windows-latest] - include: - - os: macOS-latest - uploaded_filename: bootstrap-x86_64-apple-darwin - local_path: bootstrap/target/native-image/bootstrap - - os: ubuntu-latest - uploaded_filename: bootstrap-x86_64-pc-linux - local_path: bootstrap/target/native-image/bootstrap - - os: windows-latest - uploaded_filename: bootstrap-x86_64-pc-win32.exe - local_path: bootstrap\target\native-image\bootstrap.exe - steps: - - uses: actions/checkout@v2 - - uses: olafurpg/setup-scala@v10 - - run: git fetch --tags || true - - run: sbt bootstrap/nativeImage - shell: bash - if: ${{ matrix.os != 'windows-latest' }} - - run: echo $(pwd) - shell: bash - - name: sbt test - shell: cmd - if: ${{ matrix.os == 'windows-latest' }} - run: >- - "C:\Program Files (x86)\Microsoft Visual - Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" && sbt - bootstrap/nativeImage - - uses: actions/upload-artifact@v2 - with: - path: ${{ matrix.local_path }} - name: ${{ matrix.uploaded_filename }} - - name: Upload release - if: github.event_name == 'release' - uses: actions/upload-release-asset@v1.0.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ github.event.release.upload_url }} - asset_path: ${{ matrix.local_path }} - asset_name: ${{ matrix.uploaded_filename }} - asset_content_type: application/zip diff --git a/build.sbt b/build.sbt index 8e4cd827..e8ddd277 100644 --- a/build.sbt +++ b/build.sbt @@ -123,6 +123,21 @@ lazy val bootstrap = project ) .enablePlugins(NativeImagePlugin) +ThisBuild / githubWorkflowBuild ++= Seq( + WorkflowStep.Run( + List("sbt bootstrap/nativeImage"), + name = Some("Build native image") + ), + WorkflowStep.Use( + UseRef.Public("actions", "upload-artifact", "v2"), + name = Some(s"Upload binary"), + params = Map( + "name" -> s"pitgull-bootstrap-$${{ matrix.os }}", + "path" -> "./bootstrap/target/native-image/bootstrap" + ) + ) +) + lazy val core = project.settings(commonSettings).settings(name += "-core") //workaround for docker not accepting + (the default separator in sbt-dynver) From 6f3e26f0e32154abd70e0a4970dda175a8bcbeb0 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Fri, 11 Jun 2021 14:23:03 +0200 Subject: [PATCH 18/26] change fixme to link an issue --- bootstrap/src/main/scala/org/polyvariant/Gitlab.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala index cff3c394..1d89678b 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Gitlab.scala @@ -33,10 +33,10 @@ object Gitlab { baseUri: Uri, accessToken: String )( - using backend: SttpBackend[Identity, Any] // FIXME: all cats-effect compatible backends rely on Netty, while netty breaks native-image build + 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 to async backend + 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 From 3f83775ac3574cbf1ff0a36a3571304be11af910 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Fri, 11 Jun 2021 15:16:24 +0200 Subject: [PATCH 19/26] wait for user consent before deleting merge requests --- .../main/scala/org/polyvariant/Console.scala | 17 ++++++++++ .../src/main/scala/org/polyvariant/Main.scala | 31 ++++++++++++------- 2 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 bootstrap/src/main/scala/org/polyvariant/Console.scala diff --git a/bootstrap/src/main/scala/org/polyvariant/Console.scala b/bootstrap/src/main/scala/org/polyvariant/Console.scala new file mode 100644 index 00000000..a384e414 --- /dev/null +++ b/bootstrap/src/main/scala/org/polyvariant/Console.scala @@ -0,0 +1,17 @@ +package org.polyvariant + +import cats.effect.kernel.Sync +import scala.io.StdIn.{readChar => unsafeReadChar} + +trait Console[F[_]] { + def readChar(): F[Char] +} + +object Console { + def apply[F[_]](using ev: Console[F]): Console[F] = ev + + def instance[F[_]: Sync] = new Console[F] { + def readChar(): F[Char] = + Sync[F].delay(unsafeReadChar) + } +} diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index ccdfbfb9..18c04680 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -9,6 +9,7 @@ import sttp.client3.* import org.polyvariant.Gitlab.MergeRequestInfo import cats.Applicative import sttp.monad.MonadError +import cats.MonadThrow object Main extends IOApp { @@ -17,31 +18,37 @@ 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].readChar().map(_.toString.toLowerCase == "y") + private def qualifyMergeRequestsForDeletion(botUserName: String, mergeRequests: List[MergeRequestInfo]): List[MergeRequestInfo] = mergeRequests.filter(_.authorUsername == botUserName) - private def program[F[_]: Logger: Async](args: List[String]): F[Unit] = + private def program[F[_]: Logger: Console: Async: MonadThrow](args: List[String]): F[Unit] = given backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() for { - _ <- Logger[F].info("Starting pitgull bootstrap!") + _ <- Logger[F].info("Starting pitgull bootstrap!") parsedArgs = Args.parse(args) config = Config.fromArgs(parsedArgs) gitlab = Gitlab.sttpInstance[F](config.gitlabUri, config.token) - mrs <- gitlab.mergeRequests(config.project) - _ <- Logger[F].info(s"Merge requests found: ${mrs.length}") - _ <- printMergeRequests(mrs) + mrs <- gitlab.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)}") - _ <- botMrs.traverse(mr => gitlab.deleteMergeRequest(config.project, mr.mergeRequestIid)) - _ <- 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].success("Bootstrap finished") + _ <- Logger[F].info(s"Will delete merge requests: ${botMrs.map(_.mergeRequestIid)}") + _ <- 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)) + _ <- 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].success("Bootstrap finished") } yield () override def run(args: List[String]): IO[ExitCode] = { given Logger[IO] = Logger.wrappedPrint[IO] + given Console[IO] = Console.instance[IO] program[IO](args) *> IO.pure(ExitCode.Success) } From a6c3dedb566799ce7ae94c6097a5367e17cf359d Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Fri, 11 Jun 2021 15:18:09 +0200 Subject: [PATCH 20/26] extend note about blocked decline for argument parsing --- bootstrap/src/main/scala/org/polyvariant/Args.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Args.scala b/bootstrap/src/main/scala/org/polyvariant/Args.scala index 2ab1cbdc..9e0a20ca 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Args.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Args.scala @@ -14,7 +14,7 @@ object Args { } - // TODO: Consider switching to https://ben.kirw.in/decline/ + // TODO: Consider switching to https://ben.kirw.in/decline/ after https://github.com/bkirwi/decline/pull/293 def parse(args: List[String]): Map[String, String] = parseNext(args.toList, Map()) From 66c1ad4389fc2e385acd6bcc4ceeb6a55b96dee6 Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Fri, 11 Jun 2021 16:08:52 +0200 Subject: [PATCH 21/26] add usage info to both program and readme.md --- README.md | 11 +++++ .../main/scala/org/polyvariant/Config.scala | 43 +++++++++++++++++++ .../src/main/scala/org/polyvariant/Main.scala | 23 ++++------ 3 files changed, 63 insertions(+), 14 deletions(-) create mode 100644 bootstrap/src/main/scala/org/polyvariant/Config.scala diff --git a/README.md b/README.md index e197b893..5424278f 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,17 @@ [![Powered by cats](https://img.shields.io/badge/powered%20by-cats-blue.svg)](https://github.com/typelevel/cats) ![Gluten free](https://img.shields.io/badge/gluten-free-orange.svg) +## Integrating with Pitgull + +Along with Pitgull, we provide `pitgull-bootstrap` command line utility. This program prepares your gitlab project for integration with Pitgull by deleting existing Scala Steward mere requests and setting up a webhook for triggering Pitgull. + +CLI Arguments: + --url - your gitlab url like https://gitlab.com/ + --token - your gitlab personal token, needs to have full access to project + --project - project ID, can be found on project main page + --bot - user name of Scala Steward bot user + --webhook - Pitgull target url like https://pitgull.example.com/webhook + ## Development ### Useful commands/links diff --git a/bootstrap/src/main/scala/org/polyvariant/Config.scala b/bootstrap/src/main/scala/org/polyvariant/Config.scala new file mode 100644 index 00000000..f0e38cf0 --- /dev/null +++ b/bootstrap/src/main/scala/org/polyvariant/Config.scala @@ -0,0 +1,43 @@ +package org.polyvariant + +import cats.implicits.* +import cats.MonadThrow +import sttp.model.Uri +import scala.util.Try + +final case class Config( + gitlabUri: Uri, + token: String, + project: Long, + botUser: String, + pitgullWebhookUrl: Uri +) +object Config { + def fromArgs[F[_]: MonadThrow](args: Map[String, String]): F[Config] = + MonadThrow[F].fromTry { + Try { + Config( + Uri.unsafeParse(args("url")), + args("token"), + args("project").toLong, + args("bot"), + Uri.unsafeParse(args("webhook")) + ) + }.recoverWith { + case _ => Try{throw ArgumentsParsingException} + } + } + val usage = """ + |This program prepares your gitlab project for integration with Pitgull + |by deleting existing Scala Steward mere requests and setting up + |a webhook for triggering Pitgull. + | + |CLI Arguments: + | --url - your gitlab url like https://gitlab.com/ + | --token - your gitlab personal token, needs to have full access to project + | --project - project ID, can be found on project main page + | --bot - user name of Scala Steward bot user + | --webhook - Pitgull target url like https://pitgull.example.com/webhook + """.stripMargin + case object ArgumentsParsingException extends Exception("Failed to parse CLI arguments") +} \ No newline at end of file diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 18c04680..947828ed 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -10,6 +10,7 @@ import org.polyvariant.Gitlab.MergeRequestInfo import cats.Applicative import sttp.monad.MonadError import cats.MonadThrow +import org.polyvariant.Config.ArgumentsParsingException object Main extends IOApp { @@ -26,10 +27,10 @@ object Main extends IOApp { private def program[F[_]: Logger: Console: Async: MonadThrow](args: List[String]): F[Unit] = given backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() + val parsedArgs = Args.parse(args) for { + config <- Config.fromArgs(parsedArgs) _ <- Logger[F].info("Starting pitgull bootstrap!") - parsedArgs = Args.parse(args) - config = Config.fromArgs(parsedArgs) gitlab = Gitlab.sttpInstance[F](config.gitlabUri, config.token) mrs <- gitlab.mergeRequests(config.project) _ <- Logger[F].info(s"Merge requests found: ${mrs.length}") @@ -49,19 +50,13 @@ object Main extends IOApp { override def run(args: List[String]): IO[ExitCode] = { given Logger[IO] = Logger.wrappedPrint[IO] given Console[IO] = Console.instance[IO] - program[IO](args) *> + program[IO](args).recoverWith{ + case Config.ArgumentsParsingException => + Logger[IO].info(Config.usage) + case e: Exception => + Logger[IO].error(s"Unexpected error ocurred: $e") + } *> IO.pure(ExitCode.Success) } - final case class Config( - gitlabUri: Uri, - token: String, - project: Long, - botUser: String, - pitgullWebhookUrl: Uri - ) - object Config { - def fromArgs(args: Map[String, String]): Config = // FIXME: this is unsafe - Config(Uri.unsafeParse(args("url")), args("token"), args("project").toLong, args("bot"), Uri.unsafeParse(args("webhook"))) - } } From 3c2c6feb0e5b609d8109e5b5500535bbe14bbe47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pawlik?= Date: Sat, 12 Jun 2021 14:47:24 +0200 Subject: [PATCH 22/26] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Kozłowski --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5424278f..5d5c048f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Integrating with Pitgull -Along with Pitgull, we provide `pitgull-bootstrap` command line utility. This program prepares your gitlab project for integration with Pitgull by deleting existing Scala Steward mere requests and setting up a webhook for triggering Pitgull. +Along with Pitgull, we provide a `pitgull-bootstrap` command line utility. This program prepares your GitLab project for integration with Pitgull by deleting existing Scala Steward merge requests and setting up a webhook for triggering Pitgull. CLI Arguments: --url - your gitlab url like https://gitlab.com/ From a97d0c8d2e69495b025fc6c925b5e22988aebcdf Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Sat, 12 Jun 2021 14:58:58 +0200 Subject: [PATCH 23/26] use cats-effect Console, configure scalafmt for scala 3, format Main --- .scalafmt.conf | 8 ++- .../src/main/scala/org/polyvariant/Args.scala | 1 - .../main/scala/org/polyvariant/Console.scala | 17 ------- .../src/main/scala/org/polyvariant/Main.scala | 50 ++++++++++--------- 4 files changed, 33 insertions(+), 43 deletions(-) delete mode 100644 bootstrap/src/main/scala/org/polyvariant/Console.scala diff --git a/.scalafmt.conf b/.scalafmt.conf index 6c3d5832..aa48ebea 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "2.7.5" +version = "3.0.0-RC5" maxColumn = 140 align.preset = some align.tokens.add = [ @@ -32,3 +32,9 @@ danglingParentheses.exclude = [ ] verticalMultiline.newlineAfterOpenParen = true verticalMultiline.atDefnSite = true + +fileOverride { + "glob:**/bootstrap/src/main/scala/**" { + runner.dialect = scala3 + } +} diff --git a/bootstrap/src/main/scala/org/polyvariant/Args.scala b/bootstrap/src/main/scala/org/polyvariant/Args.scala index 9e0a20ca..6c22803d 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Args.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Args.scala @@ -13,7 +13,6 @@ object Args { case text :: tail => parseNext(tail, previousResult ++ Map(text -> null)) } - // TODO: Consider switching to https://ben.kirw.in/decline/ after https://github.com/bkirwi/decline/pull/293 def parse(args: List[String]): Map[String, String] = parseNext(args.toList, Map()) diff --git a/bootstrap/src/main/scala/org/polyvariant/Console.scala b/bootstrap/src/main/scala/org/polyvariant/Console.scala deleted file mode 100644 index a384e414..00000000 --- a/bootstrap/src/main/scala/org/polyvariant/Console.scala +++ /dev/null @@ -1,17 +0,0 @@ -package org.polyvariant - -import cats.effect.kernel.Sync -import scala.io.StdIn.{readChar => unsafeReadChar} - -trait Console[F[_]] { - def readChar(): F[Char] -} - -object Console { - def apply[F[_]](using ev: Console[F]): Console[F] = ev - - def instance[F[_]: Sync] = new Console[F] { - def readChar(): F[Char] = - Sync[F].delay(unsafeReadChar) - } -} diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 947828ed..156c612d 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -11,49 +11,51 @@ import cats.Applicative import sttp.monad.MonadError import cats.MonadThrow import org.polyvariant.Config.ArgumentsParsingException +import cats.effect.std.Console object Main extends IOApp { - private def printMergeRequests[F[_]: Logger: Applicative](mergeRequests: List[MergeRequestInfo]): F[Unit] = + private def printMergeRequests[F[_]: Logger: Applicative](mergeRequests: List[MergeRequestInfo]): F[Unit] = mergeRequests.traverse { mr => Logger[F].info(s"ID: ${mr.mergeRequestIid} by: ${mr.authorUsername}") }.void - private def readConsent[F[_]: Console: Applicative]: F[Boolean] = - Console[F].readChar().map(_.toString.toLowerCase == "y") + private def readConsent[F[_]: Console: Applicative]: F[Boolean] = + Console[F].readLine.map(_.toLowerCase == "y") - private def qualifyMergeRequestsForDeletion(botUserName: String, mergeRequests: List[MergeRequestInfo]): List[MergeRequestInfo] = + 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] = - given backend: SttpBackend[Identity, Any] = HttpURLConnectionBackend() + private def program[F[_]: Logger: Console: Async: MonadThrow](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!") + config <- Config.fromArgs(parsedArgs) + _ <- Logger[F].info("Starting pitgull bootstrap!") gitlab = Gitlab.sttpInstance[F](config.gitlabUri, config.token) - mrs <- gitlab.mergeRequests(config.project) - _ <- Logger[F].info(s"Merge requests found: ${mrs.length}") - _ <- printMergeRequests(mrs) + mrs <- gitlab.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)}") - _ <- 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)) - _ <- 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].success("Bootstrap finished") + _ <- Logger[F].info(s"Will delete merge requests: ${botMrs.map(_.mergeRequestIid)}") + _ <- 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)) + _ <- 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].success("Bootstrap finished") } yield () + } override def run(args: List[String]): IO[ExitCode] = { given Logger[IO] = Logger.wrappedPrint[IO] - given Console[IO] = Console.instance[IO] - program[IO](args).recoverWith{ - case Config.ArgumentsParsingException => + program[IO](args).recoverWith { + case Config.ArgumentsParsingException => Logger[IO].info(Config.usage) - case e: Exception => + case e: Exception => Logger[IO].error(s"Unexpected error ocurred: $e") } *> IO.pure(ExitCode.Success) From ae4c4179d5d473bf04adfcd5e980f786c9b9b04e Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Sat, 12 Jun 2021 16:50:11 +0200 Subject: [PATCH 24/26] update reflect-config for native-image, apply formatting, some cleanup --- .../src/main/resources/reflect-config.json | 726 ++++++++++++------ .../main/scala/org/polyvariant/Config.scala | 18 +- .../src/main/scala/org/polyvariant/Main.scala | 7 +- build.sbt | 2 +- 4 files changed, 518 insertions(+), 235 deletions(-) diff --git a/bootstrap/src/main/resources/reflect-config.json b/bootstrap/src/main/resources/reflect-config.json index b20f50a1..866c93ba 100644 --- a/bootstrap/src/main/resources/reflect-config.json +++ b/bootstrap/src/main/resources/reflect-config.json @@ -1,226 +1,502 @@ [ -{ - "name":"byte[]" -}, -{ - "name":"cats.effect.unsafe.IORuntimeCompanionPlatform", - "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] -}, -{ - "name":"cats.effect.unsafe.WorkStealingThreadPool", - "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] -}, -{ - "name":"com.sun.crypto.provider.AESCipher$General", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"com.sun.crypto.provider.DHParameters", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"com.sun.crypto.provider.HmacCore$HmacSHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"com.sun.crypto.provider.TlsMasterSecretGenerator", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"io.circe.Decoder$", - "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] -}, -{ - "name":"io.circe.Encoder$", - "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] -}, -{ - "name":"java.lang.String" -}, -{ - "name":"java.lang.String[]" -}, -{ - "name":"java.lang.invoke.VarHandle", - "methods":[{"name":"releaseFence","parameterTypes":[] }] -}, -{ - "name":"java.security.AlgorithmParametersSpi" -}, -{ - "name":"java.security.KeyStoreSpi" -}, -{ - "name":"java.security.MessageDigestSpi" -}, -{ - "name":"java.security.SecureRandomParameters" -}, -{ - "name":"java.security.interfaces.RSAPrivateKey" -}, -{ - "name":"java.security.interfaces.RSAPublicKey" -}, -{ - "name":"java.util.Date" -}, -{ - "name":"javax.security.auth.x500.X500Principal", - "fields":[{"name":"thisX500Name"}], - "methods":[{"name":"","parameterTypes":["sun.security.x509.X500Name"] }] -}, -{ - "name":"sttp.model.Header$", - "fields":[{"name":"0bitmap$1", "allowUnsafeAccess":true}] -}, -{ - "name":"sun.misc.Unsafe", - "allDeclaredFields":true -}, -{ - "name":"sun.security.pkcs.SignerInfo[]" -}, -{ - "name":"sun.security.pkcs12.PKCS12KeyStore", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.DSA$SHA224withDSA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.DSA$SHA256withDSA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.JavaKeyStore$DualFormatJKS", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.JavaKeyStore$JKS", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.NativePRNG", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA2$SHA224", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA2$SHA256", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA5$SHA384", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.SHA5$SHA512", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.X509Factory", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.certpath.PKIXCertPathValidator", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSAKeyFactory$Legacy", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSAPSSSignature", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSASignature$SHA224withRSA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSASignature$SHA256withRSA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.rsa.RSASignature$SHA384withRSA", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.ssl.KeyManagerFactoryImpl$SunX509", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.ssl.SSLContextImpl$DefaultSSLContext", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.util.ObjectIdentifier" -}, -{ - "name":"sun.security.x509.AuthorityInfoAccessExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.AuthorityKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.BasicConstraintsExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.CRLDistributionPointsExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.CertificateExtensions" -}, -{ - "name":"sun.security.x509.CertificatePoliciesExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.ExtendedKeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.IssuerAlternativeNameExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.KeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.NetscapeCertTypeExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.PrivateKeyUsageExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.SubjectAlternativeNameExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -}, -{ - "name":"sun.security.x509.SubjectKeyIdentifierExtension", - "methods":[{"name":"","parameterTypes":["java.lang.Boolean","java.lang.Object"] }] -} -] + { + "name": "byte[]" + }, + { + "name": "cats.Later", + "fields": [ + { + "name": "0bitmap$1", + "allowUnsafeAccess": true + } + ] + }, + { + "name": "cats.effect.unsafe.IORuntimeCompanionPlatform", + "fields": [ + { + "name": "0bitmap$1", + "allowUnsafeAccess": true + } + ] + }, + { + "name": "cats.effect.unsafe.WorkStealingThreadPool", + "fields": [ + { + "name": "0bitmap$1", + "allowUnsafeAccess": true + } + ] + }, + { + "name": "com.sun.crypto.provider.AESCipher$General", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.sun.crypto.provider.DHParameters", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.sun.crypto.provider.HmacCore$HmacSHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "com.sun.crypto.provider.TlsMasterSecretGenerator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "io.circe.Decoder$", + "fields": [ + { + "name": "0bitmap$1", + "allowUnsafeAccess": true + } + ] + }, + { + "name": "io.circe.Encoder$", + "fields": [ + { + "name": "0bitmap$1", + "allowUnsafeAccess": true + } + ] + }, + { + "name": "java.lang.String" + }, + { + "name": "java.lang.String[]" + }, + { + "name": "java.lang.invoke.VarHandle", + "methods": [ + { + "name": "releaseFence", + "parameterTypes": [] + } + ] + }, + { + "name": "java.security.AlgorithmParametersSpi" + }, + { + "name": "java.security.KeyStoreSpi" + }, + { + "name": "java.security.MessageDigestSpi" + }, + { + "name": "java.security.SecureRandomParameters" + }, + { + "name": "java.security.interfaces.RSAPrivateKey" + }, + { + "name": "java.security.interfaces.RSAPublicKey" + }, + { + "name": "java.util.Date" + }, + { + "name": "javax.security.auth.x500.X500Principal", + "fields": [ + { + "name": "thisX500Name" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [ + "sun.security.x509.X500Name" + ] + } + ] + }, + { + "name": "sttp.model.Header$", + "fields": [ + { + "name": "0bitmap$1", + "allowUnsafeAccess": true + } + ] + }, + { + "name": "sun.misc.Unsafe", + "allDeclaredFields": true + }, + { + "name": "sun.security.pkcs.SignerInfo[]" + }, + { + "name": "sun.security.pkcs12.PKCS12KeyStore", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.pkcs12.PKCS12KeyStore$DualFormatPKCS12", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.DSA$SHA224withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.DSA$SHA256withDSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.JavaKeyStore$DualFormatJKS", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.JavaKeyStore$JKS", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.NativePRNG", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.SHA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.SHA2$SHA224", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.SHA2$SHA256", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.SHA5$SHA384", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.SHA5$SHA512", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.X509Factory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.certpath.PKIXCertPathValidator", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.rsa.RSAKeyFactory$Legacy", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.rsa.RSAPSSSignature", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.rsa.RSASignature$SHA224withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.rsa.RSASignature$SHA256withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.rsa.RSASignature$SHA384withRSA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.ssl.KeyManagerFactoryImpl$SunX509", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.ssl.SSLContextImpl$DefaultSSLContext", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.ssl.TrustManagerFactoryImpl$PKIXFactory", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.util.ObjectIdentifier" + }, + { + "name": "sun.security.x509.AuthorityInfoAccessExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.AuthorityKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.BasicConstraintsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.CRLDistributionPointsExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.CertificateExtensions" + }, + { + "name": "sun.security.x509.CertificatePoliciesExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.ExtendedKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.IssuerAlternativeNameExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.KeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.NetscapeCertTypeExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.PrivateKeyUsageExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.SubjectAlternativeNameExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + }, + { + "name": "sun.security.x509.SubjectKeyIdentifierExtension", + "methods": [ + { + "name": "", + "parameterTypes": [ + "java.lang.Boolean", + "java.lang.Object" + ] + } + ] + } +] \ No newline at end of file diff --git a/bootstrap/src/main/scala/org/polyvariant/Config.scala b/bootstrap/src/main/scala/org/polyvariant/Config.scala index f0e38cf0..838b9132 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Config.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Config.scala @@ -12,10 +12,12 @@ final case class Config( botUser: String, pitgullWebhookUrl: Uri ) + object Config { - def fromArgs[F[_]: MonadThrow](args: Map[String, String]): F[Config] = - MonadThrow[F].fromTry { - Try { + + def fromArgs[F[_]: MonadThrow](args: Map[String, String]): F[Config] = + MonadThrow[F] + .catchNonFatal { Config( Uri.unsafeParse(args("url")), args("token"), @@ -23,10 +25,11 @@ object Config { args("bot"), Uri.unsafeParse(args("webhook")) ) - }.recoverWith { - case _ => Try{throw ArgumentsParsingException} } - } + .recoverWith { case _ => + MonadThrow[F].raiseError(ArgumentsParsingException) + } + val usage = """ |This program prepares your gitlab project for integration with Pitgull |by deleting existing Scala Steward mere requests and setting up @@ -39,5 +42,6 @@ object Config { | --bot - user name of Scala Steward bot user | --webhook - Pitgull target url like https://pitgull.example.com/webhook """.stripMargin + case object ArgumentsParsingException extends Exception("Failed to parse CLI arguments") -} \ No newline at end of file +} diff --git a/bootstrap/src/main/scala/org/polyvariant/Main.scala b/bootstrap/src/main/scala/org/polyvariant/Main.scala index 156c612d..52623280 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Main.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Main.scala @@ -37,10 +37,13 @@ object Main extends IOApp { _ <- 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)}") + _ <- 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"))) + .ifM(readConsent)( + ifTrue = MonadThrow[F].pure(()), + ifFalse = MonadThrow[F].raiseError(new Exception("User rejected deletion")) + ) _ <- botMrs.traverse(mr => gitlab.deleteMergeRequest(config.project, mr.mergeRequestIid)) _ <- Logger[F].info("Done processing merge requests") _ <- Logger[F].info("Creating webhook") diff --git a/build.sbt b/build.sbt index e8ddd277..9e647ff5 100644 --- a/build.sbt +++ b/build.sbt @@ -197,4 +197,4 @@ lazy val pitgull = ) ) .dependsOn(core, gitlab) - .aggregate(core, gitlab) + .aggregate(core, gitlab, bootstrap) From 45f699129524ec7a9a26bc16175f95ba481df96e Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Sat, 12 Jun 2021 16:50:43 +0200 Subject: [PATCH 25/26] update readme - add reasoning behind MR deletion --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5d5c048f..54764101 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,12 @@ CLI Arguments: --bot - user name of Scala Steward bot user --webhook - Pitgull target url like https://pitgull.example.com/webhook +### Why delete existing merge requests? + +Pitgull will only take action when it's triggered by a webhook. By deleting merge requests we make sure no Scala Steward MR gets unnoticed. If we'd only close them, Scala Steward wouldn't update them, so no webhook would be triggerd. + +Additionally, if you have some legacy merge requests for single library, this program makes sure to clean them up. When Scala Steward notices that some dependency is out of date and MR is missing - it will recreate it, so no worries about skipping any updates. + ## Development ### Useful commands/links From 2d5d4b12bc8578bf6664596260cefb0dadcff48f Mon Sep 17 00:00:00 2001 From: Michal Pawlik Date: Sat, 12 Jun 2021 16:56:49 +0200 Subject: [PATCH 26/26] scalafmt --- bootstrap/src/main/scala/org/polyvariant/Logger.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bootstrap/src/main/scala/org/polyvariant/Logger.scala b/bootstrap/src/main/scala/org/polyvariant/Logger.scala index 5879426e..47274391 100644 --- a/bootstrap/src/main/scala/org/polyvariant/Logger.scala +++ b/bootstrap/src/main/scala/org/polyvariant/Logger.scala @@ -16,8 +16,8 @@ object Logger { def apply[F[_]](using ev: Logger[F]): Logger[F] = ev def wrappedPrint[F[_]: Sync] = new Logger[F] { - private def colorPrinter(color: String)(msg: String): F[Unit] = - Sync[F].delay(println(s"${color}${msg}${RESET}")) + private def colorPrinter(color: String)(msg: String): F[Unit] = + Sync[F].delay(println(s"$color$msg$RESET")) override def debug(msg: String): F[Unit] = colorPrinter(CYAN)(msg) override def success(msg: String): F[Unit] = colorPrinter(GREEN)(msg) @@ -25,4 +25,5 @@ object Logger { override def warn(msg: String): F[Unit] = colorPrinter(YELLOW)(msg) override def error(msg: String): F[Unit] = colorPrinter(RED)(msg) } + }