Skip to content

Commit

Permalink
update configuration settings and integrate new API calls to gitlab flow
Browse files Browse the repository at this point in the history
  • Loading branch information
Ender Tunc committed Mar 16, 2023
1 parent 5357f28 commit 58e6793
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.monovore.decline.Opts.{flag, option, options}
import com.monovore.decline._
import org.http4s.Uri
import org.http4s.syntax.literals._
import org.scalasteward.core.application.Cli.gitlabMergeRequestApprovalsConfig
import org.scalasteward.core.application.Config._
import org.scalasteward.core.data.Resolver
import org.scalasteward.core.forge.ForgeType
Expand All @@ -31,7 +32,9 @@ import org.scalasteward.core.forge.github.GitHubApp
import org.scalasteward.core.git.Author
import org.scalasteward.core.util.Nel
import org.scalasteward.core.util.dateTime.renderFiniteDuration

import scala.concurrent.duration._
import scala.util.{Failure, Success, Try}

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

implicit val mergeRequestApprovalsConfigArgument: Argument[MergeRequestApprovalsConfig] =
Argument.from("approvals_rule_name=required_approvals") { s =>
s.split(":").toList match {
case approvalRuleName :: requiredApprovalsAsString :: Nil =>
Try(requiredApprovalsAsString.trim.toInt) match {
case Failure(_) =>
s"[$requiredApprovalsAsString] is not a valid Integer".invalidNel
case Success(requiredApprovals) =>
new MergeRequestApprovalsConfig(approvalRuleName.trim, requiredApprovals).validNel
}
case _ =>
s"The value is expected in the following format: APPROVALS_RULE_NAME:REQUIRED_APPROVALS.".invalidNel
}
}

implicit val envVarArgument: Argument[EnvVar] =
Argument.from("name=value") { s =>
s.trim.split('=').toList match {
Expand Down Expand Up @@ -276,12 +294,35 @@ object Cli {

private val gitlabRequiredReviewers: Opts[Option[Int]] =
option[Int](
"gitlab-required-reviewers",
"gitlabRequiredReviewers",
"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."
).validate("Required reviewers must be non-negative")(_ >= 0).orNone

private val gitlabMergeRequestApprovalsConfig: Opts[Option[Nel[MergeRequestApprovalsConfig]]] =
options[MergeRequestApprovalsConfig](
"merge-request-level-approval-rule",
s"Additional repo config file $multiple"
)
// ToDo better message
.validate("")(_.forall(_.requiredApproves >= 0) == true)
.orNone

private val gitlabReviewersAndApprovalsConfig
: Opts[Option[Either[Int, Nel[MergeRequestApprovalsConfig]]]] =
((gitlabRequiredReviewers, gitlabMergeRequestApprovalsConfig).tupled.mapValidated {
case (None, None) => None.validNel
case (None, Some(gitlabMergeRequestApprovalsConfig)) =>
Some(gitlabMergeRequestApprovalsConfig.asRight[Int]).validNel
case (Some(requiredReviewers), None) => Some(Left(requiredReviewers)).validNel
case (Some(_), Some(_)) =>
s"You can't use both --gitlabRequiredReviewers and --merge-request-level-approval-rule at the same time".invalidNel
})

private val gitLabCfg: Opts[GitLabCfg] =
(gitlabMergeWhenPipelineSucceeds, gitlabRequiredReviewers).mapN(GitLabCfg.apply)
(gitlabMergeWhenPipelineSucceeds, gitlabReviewersAndApprovalsConfig)
.mapN(
GitLabCfg.apply
)

private val githubAppId: Opts[Long] =
option[Long](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,11 @@ object Config {
final case class GitHubCfg(
) extends ForgeSpecificCfg

final case class MergeRequestApprovalsConfig(approvalRuleName: String, requiredApproves: Int)

final case class GitLabCfg(
mergeWhenPipelineSucceeds: Boolean,
requiredReviewers: Option[Int]
requiredReviewers: Option[Either[Int, Nel[MergeRequestApprovalsConfig]]]
) extends ForgeSpecificCfg

final case class GiteaCfg(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,24 @@

package org.scalasteward.core.forge.gitlab

import cats.{MonadThrow, Parallel}
import cats.syntax.all._
import cats.{MonadThrow, Parallel}
import io.circe._
import io.circe.generic.semiauto._
import io.circe.syntax._
import org.http4s.{Request, Status, Uri}
import org.scalasteward.core.application.Config.{ForgeCfg, GitLabCfg}
import org.scalasteward.core.application.Config.{ForgeCfg, GitLabCfg, MergeRequestApprovalsConfig}
import org.scalasteward.core.data.Repo
import org.scalasteward.core.forge.ForgeApiAlg
import org.scalasteward.core.forge.data._
import org.scalasteward.core.git.{Branch, Sha1}
import org.scalasteward.core.util.uri.uriDecoder
import org.scalasteward.core.util.{intellijThisImportIsUsed, HttpJsonClient, UnexpectedResponse}
import org.scalasteward.core.util.{
intellijThisImportIsUsed,
HttpJsonClient,
Nel,
UnexpectedResponse
}
import org.typelevel.log4cats.Logger

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

final private[gitlab] case class UpdateMergeRequestLevelApprovalRulePayload(approvals_required: Int)

private[gitlab] object MergeRequestPayload {
def apply(
id: String,
Expand Down Expand Up @@ -81,6 +88,11 @@ final private[gitlab] case class MergeRequestApprovalsOut(
approvalsRequired: Int
)

final private[gitlab] case class MergeRequestLevelApprovalRuleOut(
id: Int,
name: String
)

final private[gitlab] case class CommitId(id: Sha1) {
val commitOut: CommitOut = CommitOut(id)
}
Expand All @@ -96,6 +108,8 @@ private[gitlab] object GitLabJsonCodec {
intellijThisImportIsUsed(uriDecoder)

implicit val forkPayloadEncoder: Encoder[ForkPayload] = deriveEncoder
implicit val updateMergeRequestLevelApprovalRulePayloadEncoder
: Encoder[UpdateMergeRequestLevelApprovalRulePayload] = deriveEncoder
implicit val userOutDecoder: Decoder[UserOut] = Decoder.instance {
_.downField("username").as[String].map(UserOut(_))
}
Expand Down Expand Up @@ -134,6 +148,14 @@ private[gitlab] object GitLabJsonCodec {
} yield MergeRequestApprovalsOut(requiredReviewers)
}

implicit val mergeRequestLevelApprovalRuleOutDecoder: Decoder[MergeRequestLevelApprovalRuleOut] =
Decoder.instance { c =>
for {
id <- c.downField("id").as[Int]
name <- c.downField("string").as[String]
} yield MergeRequestLevelApprovalRuleOut(id, name)
}

implicit val projectIdDecoder: Decoder[ProjectId] = deriveDecoder
implicit val mergeRequestPayloadEncoder: Encoder[MergeRequestPayload] = deriveEncoder
implicit val updateStateEncoder: Encoder[UpdateState] = Encoder.instance { newState =>
Expand Down Expand Up @@ -216,7 +238,13 @@ final class GitLabApiAlg[F[_]: Parallel](
for {
mr <- mergeRequest
mrWithStatus <- waitForMergeRequestStatus(mr.iid)
_ <- maybeSetReviewers(repo, mrWithStatus)
_ <- gitLabCfg.requiredReviewers match {
case Some(Right(approvalRules)) =>
setApprovalRules(repo, mrWithStatus, approvalRules)
case Some(Left(requiredReviewers)) =>
setReviewers(repo, mrWithStatus, requiredReviewers)
case None => F.unit
}
mergedUponSuccess <- mergePipelineUponSuccess(repo, mrWithStatus)
} yield mergedUponSuccess
}
Expand Down Expand Up @@ -246,29 +274,74 @@ final class GitLabApiAlg[F[_]: Parallel](
case mr =>
logger.info(s"Unable to automatically merge ${mr.webUrl}").map(_ => mr)
}
import cats.implicits._

private def maybeSetReviewers(repo: Repo, mrOut: MergeRequestOut): F[MergeRequestOut] =
gitLabCfg.requiredReviewers match {
case Some(requiredReviewers) =>
for {
_ <- logger.info(
s"Setting number of required reviewers on ${mrOut.webUrl} to $requiredReviewers"
private def setReviewers(
repo: Repo,
mrOut: MergeRequestOut,
requiredReviewers: Int
): F[MergeRequestOut] =
for {
_ <- logger.info(
s"Setting number of required reviewers on ${mrOut.webUrl} to $requiredReviewers"
)
_ <-
client
.put[MergeRequestApprovalsOut](
url.requiredApprovals(repo, mrOut.iid, requiredReviewers),
modify(repo)
)
_ <-
client
.put[MergeRequestApprovalsOut](
url.requiredApprovals(repo, mrOut.iid, requiredReviewers),
modify(repo)
)
.map(_ => ())
.recoverWith { case UnexpectedResponse(_, _, _, status, body) =>
logger
.warn(s"Unexpected response setting required reviewers: $status: $body")
.as(())
}
} yield mrOut
case None => F.pure(mrOut)
}
.map(_ => ())
.recoverWith { case UnexpectedResponse(_, _, _, status, body) =>
logger
.warn(s"Unexpected response setting required reviewers: $status: $body")
.as(())
}
} yield mrOut

private def setApprovalRules(
repo: Repo,
mrOut: MergeRequestOut,
approvalsConfig: Nel[MergeRequestApprovalsConfig]
): F[MergeRequestOut] =
for {
_ <- logger.info(
s"Adjusting merge request approvals rules on ${mrOut.webUrl} with following config: $approvalsConfig"
)
activeApprovalRules <-
client
.get[List[MergeRequestLevelApprovalRuleOut]](
url.listMergeRequestLevelApprovalRules(repo, mrOut.iid),
modify(repo)
)
.recoverWith { case UnexpectedResponse(_, _, _, status, body) =>
// ToDo better log
logger
.warn(s"Unexpected response setting required reviewers: $status: $body")
.as(List.empty)
}
approvalRuleNamesFromConfig = approvalsConfig.map(_.approvalRuleName)
approvalRulesToUpdate = activeApprovalRules.intersect(approvalRuleNamesFromConfig.toList)
_ <-
approvalRulesToUpdate.map { mergeRequestApprovalConfig =>
client
.putWithBody[Unit, UpdateMergeRequestLevelApprovalRulePayload](
url.updateMergeRequestLevelApprovalRule(
repo,
mrOut.iid,
mergeRequestApprovalConfig.id
),
UpdateMergeRequestLevelApprovalRulePayload(mergeRequestApprovalConfig.id),
modify(repo)
)
.recoverWith { case UnexpectedResponse(_, _, _, status, body) =>
// ToDo better log
logger
.warn(s"Unexpected response setting required reviewers: $status: $body")
.as(List.empty)
}
}.sequence
} yield mrOut

private def getUsernameToUserIdsMapping(repo: Repo, usernames: Set[String]): F[Map[String, Int]] =
usernames.toList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.scalasteward.core.forge.gitlab

import org.http4s.Uri
import org.scalasteward.core.application.Config.MergeRequestApprovalsConfig
import org.scalasteward.core.data.Repo
import org.scalasteward.core.forge.data.PullRequestNumber
import org.scalasteward.core.git.Branch
Expand Down Expand Up @@ -47,6 +48,15 @@ class Url(apiHost: Uri) {
(existingMergeRequest(repo, number) / "approvals")
.withQueryParam("approvals_required", approvalsRequired)

def listMergeRequestLevelApprovalRules(repo: Repo, number: PullRequestNumber): Uri =
existingMergeRequest(repo, number) / "approval_rules"

def updateMergeRequestLevelApprovalRule(
repo: Repo,
number: PullRequestNumber,
approvalRuleId: Int
): Uri = existingMergeRequest(repo, number) / "approval_rules" / approvalRuleId

def listMergeRequests(repo: Repo, source: String, target: String): Uri =
mergeRequest(repo)
.withQueryParam("source_branch", source)
Expand Down

0 comments on commit 58e6793

Please sign in to comment.