Skip to content

Commit 58e6793

Browse files
author
Ender Tunc
committed
update configuration settings and integrate new API calls to gitlab flow
1 parent 5357f28 commit 58e6793

File tree

4 files changed

+154
-28
lines changed

4 files changed

+154
-28
lines changed

modules/core/src/main/scala/org/scalasteward/core/application/Cli.scala

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.monovore.decline.Opts.{flag, option, options}
2323
import com.monovore.decline._
2424
import org.http4s.Uri
2525
import org.http4s.syntax.literals._
26+
import org.scalasteward.core.application.Cli.gitlabMergeRequestApprovalsConfig
2627
import org.scalasteward.core.application.Config._
2728
import org.scalasteward.core.data.Resolver
2829
import org.scalasteward.core.forge.ForgeType
@@ -31,7 +32,9 @@ import org.scalasteward.core.forge.github.GitHubApp
3132
import org.scalasteward.core.git.Author
3233
import org.scalasteward.core.util.Nel
3334
import org.scalasteward.core.util.dateTime.renderFiniteDuration
35+
3436
import scala.concurrent.duration._
37+
import scala.util.{Failure, Success, Try}
3538

3639
object Cli {
3740
final case class EnvVar(name: String, value: String)
@@ -44,6 +47,21 @@ object Cli {
4447
val processTimeout = "process-timeout"
4548
}
4649

50+
implicit val mergeRequestApprovalsConfigArgument: Argument[MergeRequestApprovalsConfig] =
51+
Argument.from("approvals_rule_name=required_approvals") { s =>
52+
s.split(":").toList match {
53+
case approvalRuleName :: requiredApprovalsAsString :: Nil =>
54+
Try(requiredApprovalsAsString.trim.toInt) match {
55+
case Failure(_) =>
56+
s"[$requiredApprovalsAsString] is not a valid Integer".invalidNel
57+
case Success(requiredApprovals) =>
58+
new MergeRequestApprovalsConfig(approvalRuleName.trim, requiredApprovals).validNel
59+
}
60+
case _ =>
61+
s"The value is expected in the following format: APPROVALS_RULE_NAME:REQUIRED_APPROVALS.".invalidNel
62+
}
63+
}
64+
4765
implicit val envVarArgument: Argument[EnvVar] =
4866
Argument.from("name=value") { s =>
4967
s.trim.split('=').toList match {
@@ -276,12 +294,35 @@ object Cli {
276294

277295
private val gitlabRequiredReviewers: Opts[Option[Int]] =
278296
option[Int](
279-
"gitlab-required-reviewers",
297+
"gitlabRequiredReviewers",
280298
"When set, the number of required reviewers for a merge request will be set to this number (non-negative integer). Is only used in the context of gitlab-merge-when-pipeline-succeeds being enabled, and requires that the configured access token have the appropriate privileges. Also requires a Gitlab Premium subscription."
281299
).validate("Required reviewers must be non-negative")(_ >= 0).orNone
282300

301+
private val gitlabMergeRequestApprovalsConfig: Opts[Option[Nel[MergeRequestApprovalsConfig]]] =
302+
options[MergeRequestApprovalsConfig](
303+
"merge-request-level-approval-rule",
304+
s"Additional repo config file $multiple"
305+
)
306+
// ToDo better message
307+
.validate("")(_.forall(_.requiredApproves >= 0) == true)
308+
.orNone
309+
310+
private val gitlabReviewersAndApprovalsConfig
311+
: Opts[Option[Either[Int, Nel[MergeRequestApprovalsConfig]]]] =
312+
((gitlabRequiredReviewers, gitlabMergeRequestApprovalsConfig).tupled.mapValidated {
313+
case (None, None) => None.validNel
314+
case (None, Some(gitlabMergeRequestApprovalsConfig)) =>
315+
Some(gitlabMergeRequestApprovalsConfig.asRight[Int]).validNel
316+
case (Some(requiredReviewers), None) => Some(Left(requiredReviewers)).validNel
317+
case (Some(_), Some(_)) =>
318+
s"You can't use both --gitlabRequiredReviewers and --merge-request-level-approval-rule at the same time".invalidNel
319+
})
320+
283321
private val gitLabCfg: Opts[GitLabCfg] =
284-
(gitlabMergeWhenPipelineSucceeds, gitlabRequiredReviewers).mapN(GitLabCfg.apply)
322+
(gitlabMergeWhenPipelineSucceeds, gitlabReviewersAndApprovalsConfig)
323+
.mapN(
324+
GitLabCfg.apply
325+
)
285326

286327
private val githubAppId: Opts[Long] =
287328
option[Long](

modules/core/src/main/scala/org/scalasteward/core/application/Config.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,11 @@ object Config {
156156
final case class GitHubCfg(
157157
) extends ForgeSpecificCfg
158158

159+
final case class MergeRequestApprovalsConfig(approvalRuleName: String, requiredApproves: Int)
160+
159161
final case class GitLabCfg(
160162
mergeWhenPipelineSucceeds: Boolean,
161-
requiredReviewers: Option[Int]
163+
requiredReviewers: Option[Either[Int, Nel[MergeRequestApprovalsConfig]]]
162164
) extends ForgeSpecificCfg
163165

164166
final case class GiteaCfg(

modules/core/src/main/scala/org/scalasteward/core/forge/gitlab/GitLabApiAlg.scala

Lines changed: 98 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,24 @@
1616

1717
package org.scalasteward.core.forge.gitlab
1818

19-
import cats.{MonadThrow, Parallel}
2019
import cats.syntax.all._
20+
import cats.{MonadThrow, Parallel}
2121
import io.circe._
2222
import io.circe.generic.semiauto._
2323
import io.circe.syntax._
2424
import org.http4s.{Request, Status, Uri}
25-
import org.scalasteward.core.application.Config.{ForgeCfg, GitLabCfg}
25+
import org.scalasteward.core.application.Config.{ForgeCfg, GitLabCfg, MergeRequestApprovalsConfig}
2626
import org.scalasteward.core.data.Repo
2727
import org.scalasteward.core.forge.ForgeApiAlg
2828
import org.scalasteward.core.forge.data._
2929
import org.scalasteward.core.git.{Branch, Sha1}
3030
import org.scalasteward.core.util.uri.uriDecoder
31-
import org.scalasteward.core.util.{intellijThisImportIsUsed, HttpJsonClient, UnexpectedResponse}
31+
import org.scalasteward.core.util.{
32+
intellijThisImportIsUsed,
33+
HttpJsonClient,
34+
Nel,
35+
UnexpectedResponse
36+
}
3237
import org.typelevel.log4cats.Logger
3338

3439
final private[gitlab] case class ForkPayload(id: String, namespace: String)
@@ -44,6 +49,8 @@ final private[gitlab] case class MergeRequestPayload(
4449
target_branch: Branch
4550
)
4651

52+
final private[gitlab] case class UpdateMergeRequestLevelApprovalRulePayload(approvals_required: Int)
53+
4754
private[gitlab] object MergeRequestPayload {
4855
def apply(
4956
id: String,
@@ -81,6 +88,11 @@ final private[gitlab] case class MergeRequestApprovalsOut(
8188
approvalsRequired: Int
8289
)
8390

91+
final private[gitlab] case class MergeRequestLevelApprovalRuleOut(
92+
id: Int,
93+
name: String
94+
)
95+
8496
final private[gitlab] case class CommitId(id: Sha1) {
8597
val commitOut: CommitOut = CommitOut(id)
8698
}
@@ -96,6 +108,8 @@ private[gitlab] object GitLabJsonCodec {
96108
intellijThisImportIsUsed(uriDecoder)
97109

98110
implicit val forkPayloadEncoder: Encoder[ForkPayload] = deriveEncoder
111+
implicit val updateMergeRequestLevelApprovalRulePayloadEncoder
112+
: Encoder[UpdateMergeRequestLevelApprovalRulePayload] = deriveEncoder
99113
implicit val userOutDecoder: Decoder[UserOut] = Decoder.instance {
100114
_.downField("username").as[String].map(UserOut(_))
101115
}
@@ -134,6 +148,14 @@ private[gitlab] object GitLabJsonCodec {
134148
} yield MergeRequestApprovalsOut(requiredReviewers)
135149
}
136150

151+
implicit val mergeRequestLevelApprovalRuleOutDecoder: Decoder[MergeRequestLevelApprovalRuleOut] =
152+
Decoder.instance { c =>
153+
for {
154+
id <- c.downField("id").as[Int]
155+
name <- c.downField("string").as[String]
156+
} yield MergeRequestLevelApprovalRuleOut(id, name)
157+
}
158+
137159
implicit val projectIdDecoder: Decoder[ProjectId] = deriveDecoder
138160
implicit val mergeRequestPayloadEncoder: Encoder[MergeRequestPayload] = deriveEncoder
139161
implicit val updateStateEncoder: Encoder[UpdateState] = Encoder.instance { newState =>
@@ -216,7 +238,13 @@ final class GitLabApiAlg[F[_]: Parallel](
216238
for {
217239
mr <- mergeRequest
218240
mrWithStatus <- waitForMergeRequestStatus(mr.iid)
219-
_ <- maybeSetReviewers(repo, mrWithStatus)
241+
_ <- gitLabCfg.requiredReviewers match {
242+
case Some(Right(approvalRules)) =>
243+
setApprovalRules(repo, mrWithStatus, approvalRules)
244+
case Some(Left(requiredReviewers)) =>
245+
setReviewers(repo, mrWithStatus, requiredReviewers)
246+
case None => F.unit
247+
}
220248
mergedUponSuccess <- mergePipelineUponSuccess(repo, mrWithStatus)
221249
} yield mergedUponSuccess
222250
}
@@ -246,29 +274,74 @@ final class GitLabApiAlg[F[_]: Parallel](
246274
case mr =>
247275
logger.info(s"Unable to automatically merge ${mr.webUrl}").map(_ => mr)
248276
}
277+
import cats.implicits._
249278

250-
private def maybeSetReviewers(repo: Repo, mrOut: MergeRequestOut): F[MergeRequestOut] =
251-
gitLabCfg.requiredReviewers match {
252-
case Some(requiredReviewers) =>
253-
for {
254-
_ <- logger.info(
255-
s"Setting number of required reviewers on ${mrOut.webUrl} to $requiredReviewers"
279+
private def setReviewers(
280+
repo: Repo,
281+
mrOut: MergeRequestOut,
282+
requiredReviewers: Int
283+
): F[MergeRequestOut] =
284+
for {
285+
_ <- logger.info(
286+
s"Setting number of required reviewers on ${mrOut.webUrl} to $requiredReviewers"
287+
)
288+
_ <-
289+
client
290+
.put[MergeRequestApprovalsOut](
291+
url.requiredApprovals(repo, mrOut.iid, requiredReviewers),
292+
modify(repo)
256293
)
257-
_ <-
258-
client
259-
.put[MergeRequestApprovalsOut](
260-
url.requiredApprovals(repo, mrOut.iid, requiredReviewers),
261-
modify(repo)
262-
)
263-
.map(_ => ())
264-
.recoverWith { case UnexpectedResponse(_, _, _, status, body) =>
265-
logger
266-
.warn(s"Unexpected response setting required reviewers: $status: $body")
267-
.as(())
268-
}
269-
} yield mrOut
270-
case None => F.pure(mrOut)
271-
}
294+
.map(_ => ())
295+
.recoverWith { case UnexpectedResponse(_, _, _, status, body) =>
296+
logger
297+
.warn(s"Unexpected response setting required reviewers: $status: $body")
298+
.as(())
299+
}
300+
} yield mrOut
301+
302+
private def setApprovalRules(
303+
repo: Repo,
304+
mrOut: MergeRequestOut,
305+
approvalsConfig: Nel[MergeRequestApprovalsConfig]
306+
): F[MergeRequestOut] =
307+
for {
308+
_ <- logger.info(
309+
s"Adjusting merge request approvals rules on ${mrOut.webUrl} with following config: $approvalsConfig"
310+
)
311+
activeApprovalRules <-
312+
client
313+
.get[List[MergeRequestLevelApprovalRuleOut]](
314+
url.listMergeRequestLevelApprovalRules(repo, mrOut.iid),
315+
modify(repo)
316+
)
317+
.recoverWith { case UnexpectedResponse(_, _, _, status, body) =>
318+
// ToDo better log
319+
logger
320+
.warn(s"Unexpected response setting required reviewers: $status: $body")
321+
.as(List.empty)
322+
}
323+
approvalRuleNamesFromConfig = approvalsConfig.map(_.approvalRuleName)
324+
approvalRulesToUpdate = activeApprovalRules.intersect(approvalRuleNamesFromConfig.toList)
325+
_ <-
326+
approvalRulesToUpdate.map { mergeRequestApprovalConfig =>
327+
client
328+
.putWithBody[Unit, UpdateMergeRequestLevelApprovalRulePayload](
329+
url.updateMergeRequestLevelApprovalRule(
330+
repo,
331+
mrOut.iid,
332+
mergeRequestApprovalConfig.id
333+
),
334+
UpdateMergeRequestLevelApprovalRulePayload(mergeRequestApprovalConfig.id),
335+
modify(repo)
336+
)
337+
.recoverWith { case UnexpectedResponse(_, _, _, status, body) =>
338+
// ToDo better log
339+
logger
340+
.warn(s"Unexpected response setting required reviewers: $status: $body")
341+
.as(List.empty)
342+
}
343+
}.sequence
344+
} yield mrOut
272345

273346
private def getUsernameToUserIdsMapping(repo: Repo, usernames: Set[String]): F[Map[String, Int]] =
274347
usernames.toList

modules/core/src/main/scala/org/scalasteward/core/forge/gitlab/Url.scala

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.scalasteward.core.forge.gitlab
1818

1919
import org.http4s.Uri
20+
import org.scalasteward.core.application.Config.MergeRequestApprovalsConfig
2021
import org.scalasteward.core.data.Repo
2122
import org.scalasteward.core.forge.data.PullRequestNumber
2223
import org.scalasteward.core.git.Branch
@@ -47,6 +48,15 @@ class Url(apiHost: Uri) {
4748
(existingMergeRequest(repo, number) / "approvals")
4849
.withQueryParam("approvals_required", approvalsRequired)
4950

51+
def listMergeRequestLevelApprovalRules(repo: Repo, number: PullRequestNumber): Uri =
52+
existingMergeRequest(repo, number) / "approval_rules"
53+
54+
def updateMergeRequestLevelApprovalRule(
55+
repo: Repo,
56+
number: PullRequestNumber,
57+
approvalRuleId: Int
58+
): Uri = existingMergeRequest(repo, number) / "approval_rules" / approvalRuleId
59+
5060
def listMergeRequests(repo: Repo, source: String, target: String): Uri =
5161
mergeRequest(repo)
5262
.withQueryParam("source_branch", source)

0 commit comments

Comments
 (0)