Skip to content

Commit

Permalink
Add support for custom CI service auth strategies (#274)
Browse files Browse the repository at this point in the history
  • Loading branch information
ruippeixotog authored Aug 5, 2023
1 parent 10ff249 commit 292581b
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 15 deletions.
36 changes: 30 additions & 6 deletions src/main/scala/org/scoverage/coveralls/CIService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,28 @@ trait CIService {
def jobId: Option[String]
def pullRequest: Option[String]
def currentBranch: Option[String]

// default behavior for CI services is for a repo token to be required
def coverallsAuth(userRepoToken: Option[String]): Option[CoverallsAuth] =
userRepoToken.map(CoverallsRepoToken)
}

case object TravisCI extends CIService {
val name = "travis-ci"
trait TravisBase extends CIService {
val jobId: Option[String] = sys.env.get("TRAVIS_JOB_ID")
val pullRequest: Option[String] = sys.env.get("CI_PULL_REQUEST")
val currentBranch: Option[String] = sys.env.get("CI_BRANCH")

// If a user token exists, use it; otherwise, Travis doesn't seem to need a token at all
override def coverallsAuth(userRepoToken: Option[String]): Option[CoverallsAuth] =
Some(userRepoToken.fold[CoverallsAuth](NoTokenNeeded)(CoverallsRepoToken))
}

case object TravisCI extends TravisBase {
val name = "travis-ci"
}

case object TravisPro extends CIService {
case object TravisPro extends TravisBase {
val name = "travis-pro"
val jobId: Option[String] = sys.env.get("TRAVIS_JOB_ID")
val pullRequest: Option[String] = sys.env.get("CI_PULL_REQUEST")
val currentBranch: Option[String] = sys.env.get("CI_BRANCH")
}

case object GitHubActions extends CIService {
Expand Down Expand Up @@ -69,4 +77,20 @@ case object GitHubActions extends CIService {
case Left(_) => None
}
}

override def coverallsAuth(userRepoToken: Option[String]): Option[CoverallsAuth] = {
userRepoToken match {
case Some(token) if token.matches("gh._.+") =>
// The token passed in COVERALLS_REPO_TOKEN is a GitHub token (legacy behavior)
// (https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/#identifiable-prefixes)
Some(CIServiceToken(token))
case Some(token) =>
// The token passed is a Coveralls one
Some(CoverallsRepoToken(token))
case None =>
// COVERALLS_REPO_TOKEN is not defined; lookup GITHUB_TOKEN (available on all GitHub
// Actions jobs) instead as a last resort
sys.env.get("GITHUB_TOKEN").map(CIServiceToken)
}
}
}
14 changes: 11 additions & 3 deletions src/main/scala/org/scoverage/coveralls/CoverallPayloadWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import sbt.Logger
class CoverallPayloadWriter(
repoRootDir: File,
coverallsFile: File,
repoToken: Option[String],
coverallsAuth: CoverallsAuth,
service: Option[CIService],
parallel: Boolean,
gitClient: GitClient
Expand All @@ -31,8 +31,16 @@ class CoverallPayloadWriter(
def writeOpt(fieldName: String, holder: Option[String]) =
holder foreach { gen.writeStringField(fieldName, _) }

writeOpt("repo_token", repoToken)
writeOpt("service_name", service.map(_.name))
coverallsAuth match {
case CoverallsRepoToken(token) =>
gen.writeStringField("repo_token", token)
case CIServiceToken(token) =>
gen.writeStringField("repo_token", token)
writeOpt("service_name", service.map(_.name))
case NoTokenNeeded =>
writeOpt("service_name", service.map(_.name))
}

writeOpt("service_job_id", service.flatMap(_.jobId))
writeOpt("service_pull_request", service.flatMap(_.pullRequest))
writeOpt("flag_name", sys.env.get("COVERALLS_FLAG_NAME"))
Expand Down
24 changes: 24 additions & 0 deletions src/main/scala/org/scoverage/coveralls/CoverallsAuth.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.scoverage.coveralls

import scala.io.Source
import io.circe._
import io.circe.parser
import io.circe.generic.auto._

/** The strategy to use when authenticating against Coveralls.
*/
sealed trait CoverallsAuth

/** Auth strategy where a Coveralls-specific token is used. Works with every CI
* service.
*/
case class CoverallsRepoToken(token: String) extends CoverallsAuth

/** Auth strategy where a token specific to the CI service is used, such as a
* GitHub token. Works on selected CI services supported by Coveralls.
*/
case class CIServiceToken(token: String) extends CoverallsAuth

/** Auth strategy where no token is passed. This seems to work for Travis.
*/
case object NoTokenNeeded extends CoverallsAuth
14 changes: 10 additions & 4 deletions src/main/scala/org/scoverage/coveralls/CoverallsPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,16 @@ object CoverallsPlugin extends AutoPlugin {
val repoToken =
userRepoToken(coverallsToken.value, coverallsTokenFile.value)

if (travisJobIdent.isEmpty && repoToken.isEmpty) {
val coverallsAuthOpt = coverallsService.value match {
case Some(ciService) => ciService.coverallsAuth(repoToken)
case None => repoToken.map(CoverallsRepoToken)
}

val coverallsAuth = coverallsAuthOpt.getOrElse {
sys.error("""
|Could not find coveralls repo token or determine travis job id
| - If running from travis, make sure the TRAVIS_JOB_ID env variable is set
|Could not find any way to authenticate against Coveralls.
| - If running from Travis, make sure the TRAVIS_JOB_ID env variable is set
| - If running from GitHub CI, set the GITHUB_TOKEN env variable to ${{ secrets.GITHUB_TOKEN }}
| - Otherwise, to set up your repo token read https://github.com/scoverage/sbt-coveralls#specifying-your-repo-token
""".stripMargin)
}
Expand All @@ -98,7 +104,7 @@ object CoverallsPlugin extends AutoPlugin {
val writer = new CoverallPayloadWriter(
repoRootDirectory,
coverallsFile.value,
repoToken,
coverallsAuth,
coverallsService.value,
coverallsParallel.value,
new GitClient(repoRootDirectory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class CoverallPayloadWriterTest
val payloadWriter = new CoverallPayloadWriter(
new File(".").getAbsoluteFile,
new File("."),
tokenIn,
service.flatMap(_.coverallsAuth(tokenIn)).getOrElse(CoverallsRepoToken(tokenIn.get)),
service,
parallel,
testGitClient
Expand Down Expand Up @@ -76,7 +76,7 @@ class CoverallPayloadWriterTest
payloadWriter.flush()

writer.toString should equal(
"""{"repo_token":"testRepoToken","service_name":"my-service","service_job_id":"testServiceJob","parallel":false,""" +
"""{"repo_token":"testRepoToken","service_job_id":"testServiceJob","parallel":false,""" +
expectedGit +
""","source_files":["""
)
Expand All @@ -96,6 +96,34 @@ class CoverallPayloadWriterTest
)
}

"generate a correct starting payload with a CI specific auth token" in {
val testService: CIService = new CIService {
override def name = "my-service"
override def jobId = Some("testServiceJob")
override def pullRequest = None
override def currentBranch = None

override def coverallsAuth(userRepoToken: Option[String]) =
Some(CIServiceToken("hardcodedToken"))
}

val (payloadWriter, writer) = coverallsWriter(
new StringWriter(),
Some("testRepoToken"),
Some(testService),
false
)

payloadWriter.start
payloadWriter.flush()

writer.toString should equal(
"""{"repo_token":"hardcodedToken","service_name":"my-service","service_job_id":"testServiceJob","parallel":false,""" +
expectedGit +
""","source_files":["""
)
}

"add source files correctly" in {
val sourceFile = Utils.mkFileFromPath(
resourceDir,
Expand Down

0 comments on commit 292581b

Please sign in to comment.