Skip to content

Commit

Permalink
Allow picking up credentials from environment variables (#629)
Browse files Browse the repository at this point in the history
* Upgrade Coursier to respect COURSIER_CREDENTIALS string

* Make sure we respect COURSIER_REPOSITORIES

* Add tests for Coursier env variables with a local server
  • Loading branch information
keynmol committed Aug 7, 2023
1 parent 8480863 commit 0fe40e9
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 14 deletions.
12 changes: 9 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ lazy val V =
val protobuf = "3.15.6"
val protoc =
"3.17.3" // the oldest protoc version with Apple M1 support, see https://github.com/scalapb/ScalaPB/issues/1024#issuecomment-860126568
val coursier = "2.0.8"
val coursier = "2.1.5"
val scalaXml = "2.1.0"
val bsp = "2.0.0-M13"
val moped = "0.1.11"
val gradle = "7.0"
Expand Down Expand Up @@ -250,7 +251,7 @@ lazy val cli = project
List(
"io.get-coursier" %% "coursier" % V.coursier,
"org.scalameta" % "mtags-interfaces" % V.metals,
"org.scala-lang.modules" %% "scala-xml" % "1.3.0",
"org.scala-lang.modules" %% "scala-xml" % V.scalaXml,
"com.lihaoyi" %% "requests" % V.requests,
"org.scalameta" %% "moped" % V.moped,
"org.scalameta" %% "ascii-graphs" % "0.1.2",
Expand Down Expand Up @@ -502,7 +503,12 @@ lazy val buildTools = project
s"-Dsemanticdb.pluginpath=${(javacPlugin / Compile / Keys.`package`).value}",
s"-Dsemanticdb.sourceroot=${(ThisBuild / baseDirectory).value}",
s"-Dsemanticdb.targetroot=${(agent / Compile / target).value / "semanticdb-targetroot"}"
)
),
Test / envVars ++=
Map(
"SCIP_JAVA_CLI" -> ((cli / pack).value / "bin" / "scip-java").toString
),
Test / fork := true
)
.dependsOn(agent, unit)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ case class Dependencies(

object Dependencies {
val empty = Dependencies(Nil, Fetch.Result(), Fetch.Result())

private val cachePolicies = List(CachePolicy.LocalOnly, CachePolicy.Update)
private val cache: FileCache[Task] = FileCache[Task]()
.noCredentials
.withCachePolicies(List(CachePolicy.LocalOnly, CachePolicy.Update))
.withCachePolicies(cachePolicies)
.withTtl(Duration.Inf)
.withChecksums(Nil)

private val defaultExtraRepositories = List[Repository](
Repositories.google,
Repositories.clojars,
Expand Down Expand Up @@ -79,10 +81,17 @@ object Dependencies {
val deps = dependencies.map(parseDependency)
val provided = deps.flatMap(d => resolveProvidedDeps(d))
def nonTransitiveDeps = deps.map(_.withTransitive(false))
val fetch = Fetch[Task](Cache.default)
.addDependencies(deps: _*)
.addDependencies(provided: _*)
.addRepositories(defaultExtraRepositories: _*)

val fetch = {
val fetch0 = Fetch[Task](cache)
.addDependencies(deps: _*)
.addDependencies(provided: _*)

if (!sys.env.contains("COURSIER_REPOSITORIES")) {
fetch0.addRepositories(defaultExtraRepositories: _*)
} else
fetch0
}

val classpath = fetch.runResult()
val sources = fetch
Expand All @@ -102,11 +111,16 @@ object Dependencies {
}

def resolveProvidedDeps(dep: Dependency): Seq[Dependency] = {
val artifacts = Resolve[Task](Cache.default)
.addDependencies(dep)
.addRepositories(defaultExtraRepositories: _*)
.run()
.artifacts()
val resolve = {
val resolve0 = Resolve[Task](cache).addDependencies(dep)
if (!sys.env.contains("COURSIER_REPOSITORIES")) {
resolve0.addRepositories(defaultExtraRepositories: _*)
} else
resolve0
}

val artifacts = resolve.run().artifacts()

for {
artifact <- artifacts
metadata <- artifact.extra.get("metadata").toList
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package tests

import java.net.URI

import scala.collection.mutable.ListBuffer
import scala.jdk.CollectionConverters._

import com.sun.net.httpserver._

case class PasswordProtectedServer(user: String, pwd: String) {
import PasswordProtectedServer._
private class MyHandler(storage: ListBuffer[SimpleHttpRequest])
extends HttpHandler {
val auth =
new BasicAuthenticator("") {
override def checkCredentials(
username: String,
password: String
): Boolean = username == user && password == pwd
}
override def handle(t: HttpExchange): Unit = {
val headers = t
.getRequestHeaders()
.asScala
.toMap
.flatMap { case (k, v) =>
v.asScala.headOption.map(k -> _)
}

val url = t.getRequestURI()
storage.synchronized {
storage.addOne(SimpleHttpRequest(url, headers))
}

auth.authenticate(t) match {
case f: Authenticator.Failure =>
t.sendResponseHeaders(f.getResponseCode(), 0L)
case r: Authenticator.Retry =>
t.sendResponseHeaders(r.getResponseCode(), 0L)
case r: Authenticator.Success =>
t.sendResponseHeaders(404, 0L)
}

t.close()
}
}
def runWith[A](f: RunningServer => A): (List[SimpleHttpRequest], A) = {
val storage = ListBuffer.empty[SimpleHttpRequest]
val server = HttpServer
.create(new java.net.InetSocketAddress("localhost", 0), 0);
val result =
try {
server.createContext("/", new MyHandler(storage))
server.setExecutor(null) // creates a default executor
server.start()
f(
RunningServer(
new URI(
s"http://${server.getAddress().getHostName()}:${server.getAddress().getPort()}"
),
() => server.stop(0)
)
)
} finally {
server.stop(0)
}

storage.toList -> result
}
}

object PasswordProtectedServer {
case class SimpleHttpRequest(url: URI, simpleHeaders: Map[String, String])

case class RunningServer(address: URI, shutdown: () => Unit)
}
65 changes: 65 additions & 0 deletions tests/buildTools/src/test/scala/tests/ScipBuildToolSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,71 @@ import com.sourcegraph.scip_java.{BuildInfo => V}

class ScipBuildToolSuite extends BaseBuildToolSuite {
override def tags = List(SkipWindows)

test("COURSIER_CREDENTIALS and COURSIER_REPOSITORIES are respected") {

val cli = sys.env.getOrElse("SCIP_JAVA_CLI", fail("wwaaaa"))

val Username = "hello"
val Password = "world"

val (requests, _) = PasswordProtectedServer(Username, Password).runWith {
run =>
val env = Map(
"COURSIER_REPOSITORIES" -> run.address.toString(),
"COURSIER_CREDENTIALS" -> s"localhost $Username:$Password"
)

val tmp = os.temp.dir(prefix = "scip-java")
os.write(
tmp / "lsif-java.json",
// We use non-existent library to make sure caches are never used
s""" {"dependencies": ["bla.bla.nonexistent-library:junit:4.13.1"]} """
.trim
)
os.write(
tmp / "foo" / "Example.java",
"package foo;\npublic class Example{}",
createFolders = true
)

val result = os
.proc(cli, "index", "--build-tool=scip")
.call(cwd = tmp, env = env, check = false)

os.remove.all(tmp)

assertNotEquals(result.exitCode, 0)
}

assert(
requests.nonEmpty,
"No requests were sent to the local server - suggesting that COURSIER_REPOSITORIES is not respected by ScipBuildTool"
)

assert(
clue(requests)
.filter { r =>
r.simpleHeaders.contains("Authorization")
}
.nonEmpty,
"No requests with Authorization header were sent to local server - suggesting that COURSIER_CREDENTIALS is not respected"
)

requests.flatMap(_.simpleHeaders.get("Authorization")).distinct match {
case List(value) =>
assertEquals(clue(value), "Basic " + base64("hello:world"))
case other =>
fail(
s"Multiple credential variations were passed to local server: $other"
)
}

}

private def base64(str: String) =
new String(java.util.Base64.getEncoder().encode(str.getBytes))

checkBuild(
"basic",
"""|/lsif-java.json
Expand Down

0 comments on commit 0fe40e9

Please sign in to comment.