-
Notifications
You must be signed in to change notification settings - Fork 360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WM-2454: Private GitHub support on describe api #7365
Changes from 20 commits
4ec82f2
953fb13
5d88d0b
19c1097
a1fd14d
9cbf10a
3483604
6bbea51
19ae950
19205a4
645da1d
4964e9e
12b2f0e
46ea66d
af3effd
6bfffcc
c627c1c
7e7fd59
1c6440b
49f8326
0de17e7
debfc8f
ae2c89c
656975f
109323d
d6ce18d
09b5249
1f0097e
008b396
a66feb7
a27bd41
e4a7fe4
7aad252
832f07d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,6 +10,7 @@ | |
import com.softwaremill.sttp._ | ||
import com.softwaremill.sttp.asynchttpclient.cats.AsyncHttpClientCatsBackend | ||
import com.typesafe.config.ConfigFactory | ||
import com.typesafe.scalalogging.StrictLogging | ||
import common.Checked | ||
import common.transforms.CheckedAtoB | ||
import common.validation.ErrorOr._ | ||
|
@@ -22,11 +23,11 @@ | |
import java.security.MessageDigest | ||
import cromwell.core.WorkflowId | ||
import wom.ResolvedImportRecord | ||
import wom.core.WorkflowSource | ||
import wom.core.{WorkflowSource, WorkflowUrl} | ||
import wom.values._ | ||
|
||
import scala.concurrent.duration._ | ||
import scala.concurrent.{Await, ExecutionContext} | ||
import scala.concurrent.{Await, ExecutionContext, Future} | ||
import scala.util.{Failure, Success, Try} | ||
|
||
object ImportResolver { | ||
|
@@ -37,6 +38,15 @@ | |
resolvedImportRecord: ResolvedImportRecord | ||
) | ||
|
||
trait ImportAuthProvider { | ||
def validHosts: List[String] | ||
def authHeader(): Future[Map[String, String]] | ||
} | ||
|
||
trait GithubImportAuthProvider extends ImportAuthProvider { | ||
override def validHosts: List[String] = List("github.com", "githubusercontent.com", "raw.githubusercontent.com") | ||
Check warning on line 47 in languageFactories/language-factory-core/src/main/scala/cromwell/languages/util/ImportResolver.scala Codecov / codecov/patchlanguageFactories/language-factory-core/src/main/scala/cromwell/languages/util/ImportResolver.scala#L47
|
||
} | ||
|
||
trait ImportResolver { | ||
def name: String | ||
protected def innerResolver(path: String, currentResolvers: List[ImportResolver]): Checked[ResolvedImportBundle] | ||
|
@@ -179,20 +189,23 @@ | |
} | ||
} | ||
|
||
case class HttpResolver(relativeTo: Option[String], headers: Map[String, String], hostAllowlist: Option[List[String]]) | ||
extends ImportResolver { | ||
case class HttpResolver(relativeTo: Option[String], | ||
headers: Map[String, String], | ||
hostAllowlist: Option[List[String]], | ||
authProviders: List[ImportAuthProvider] | ||
) extends ImportResolver | ||
with StrictLogging { | ||
import HttpResolver._ | ||
|
||
override def name: String = relativeTo match { | ||
case Some(relativeToPath) => s"http importer (relative to $relativeToPath)" | ||
case None => "http importer (no 'relative-to' origin)" | ||
|
||
} | ||
|
||
def newResolverList(newRoot: String): List[ImportResolver] = { | ||
val rootWithoutFilename = newRoot.split('/').init.mkString("", "/", "/") | ||
List( | ||
HttpResolver(relativeTo = Some(canonicalize(rootWithoutFilename)), headers, hostAllowlist) | ||
HttpResolver(relativeTo = Some(canonicalize(rootWithoutFilename)), headers, hostAllowlist, authProviders) | ||
) | ||
} | ||
|
||
|
@@ -211,8 +224,13 @@ | |
case None => true | ||
} | ||
|
||
def fetchAuthHeaders(uri: Uri): Future[Map[String, String]] = | ||
authProviders collectFirst { | ||
case provider if provider.validHosts.contains(uri.host) => provider.authHeader() | ||
} getOrElse Future.successful(Map.empty[String, String]) | ||
Check warning on line 230 in languageFactories/language-factory-core/src/main/scala/cromwell/languages/util/ImportResolver.scala Codecov / codecov/patchlanguageFactories/language-factory-core/src/main/scala/cromwell/languages/util/ImportResolver.scala#L228-L230
|
||
|
||
override def innerResolver(str: String, currentResolvers: List[ImportResolver]): Checked[ResolvedImportBundle] = | ||
pathToLookup(str) flatMap { toLookup: WorkflowSource => | ||
pathToLookup(str) flatMap { toLookup: WorkflowUrl => | ||
(Try { | ||
val uri: Uri = uri"$toLookup" | ||
|
||
|
@@ -227,21 +245,37 @@ | |
}).contextualizeErrors(s"download $toLookup") | ||
} | ||
|
||
private def getUri(toLookup: WorkflowSource): Either[NonEmptyList[WorkflowSource], ResolvedImportBundle] = { | ||
implicit val sttpBackend = HttpResolver.sttpBackend() | ||
val responseIO: IO[Response[WorkflowSource]] = sttp.get(uri"$toLookup").headers(headers).send() | ||
|
||
// temporary situation to get functionality working before | ||
private def getUri(toLookup: WorkflowUrl): Checked[ResolvedImportBundle] = { | ||
// Temporary situation to get functionality working before | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Glad we are refreshing the last-modified date of the "temporary situation" 🙂 |
||
// starting in on async-ifying the entire WdlNamespace flow | ||
val result: Checked[WorkflowSource] = Await.result(responseIO.unsafeToFuture(), 15.seconds).body.leftMap { e => | ||
NonEmptyList(e.toString.trim, List.empty) | ||
// Note: this will cause the calling thread to block for up to 20 seconds | ||
// (5 for the auth header lookup, 15 for the http request) | ||
val unauthedAttempt = getUriInner(toLookup, Map.empty) | ||
val result = if (unauthedAttempt.code == StatusCodes.NotFound) { | ||
val authHeaders = Await.result(fetchAuthHeaders(uri"$toLookup"), 5.seconds) | ||
if (authHeaders.nonEmpty) { | ||
getUriInner(toLookup, authHeaders) | ||
Check warning on line 257 in languageFactories/language-factory-core/src/main/scala/cromwell/languages/util/ImportResolver.scala Codecov / codecov/patchlanguageFactories/language-factory-core/src/main/scala/cromwell/languages/util/ImportResolver.scala#L255-L257
|
||
} else { | ||
unauthedAttempt | ||
Check warning on line 259 in languageFactories/language-factory-core/src/main/scala/cromwell/languages/util/ImportResolver.scala Codecov / codecov/patchlanguageFactories/language-factory-core/src/main/scala/cromwell/languages/util/ImportResolver.scala#L259
|
||
} | ||
} else { | ||
unauthedAttempt | ||
} | ||
|
||
result map { | ||
result.body.leftMap { e => | ||
NonEmptyList.of(e.trim) | ||
} map { | ||
ResolvedImportBundle(_, newResolverList(toLookup), ResolvedImportRecord(toLookup)) | ||
} | ||
} | ||
|
||
protected def getUriInner(toLookup: WorkflowUrl, authHeaders: Map[String, String]): Response[WorkflowSource] = { | ||
implicit val sttpBackend = HttpResolver.sttpBackend() | ||
|
||
val responseIO: IO[Response[WorkflowSource]] = sttp.get(uri"$toLookup").headers(headers ++ authHeaders).send() | ||
Await.result(responseIO.unsafeToFuture(), 15.seconds) | ||
} | ||
|
||
override def cleanupIfNecessary(): ErrorOr[Unit] = ().validNel | ||
|
||
override def hashKey: ErrorOr[String] = relativeTo.toString.md5Sum.validNel | ||
|
@@ -252,15 +286,18 @@ | |
import common.util.IntrospectableLazy | ||
import common.util.IntrospectableLazy._ | ||
|
||
def apply(relativeTo: Option[String] = None, headers: Map[String, String] = Map.empty): HttpResolver = { | ||
def apply(relativeTo: Option[String] = None, | ||
headers: Map[String, String] = Map.empty, | ||
authProviders: List[ImportAuthProvider] = List.empty | ||
): HttpResolver = { | ||
val config = ConfigFactory.load().getConfig("languages.WDL.http-allow-list") | ||
val allowListEnabled = config.as[Option[Boolean]]("enabled").getOrElse(false) | ||
val allowList: Option[List[String]] = | ||
if (allowListEnabled) | ||
config.as[Option[List[String]]]("allowed-http-hosts") | ||
else None | ||
|
||
new HttpResolver(relativeTo, headers, allowList) | ||
new HttpResolver(relativeTo, headers, allowList, authProviders) | ||
} | ||
|
||
val sttpBackend: IntrospectableLazy[SttpBackend[IO, Nothing]] = lazily { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick - it seems like
kebab-case
is more standard in this file.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jgainerdewar it seems the configs in this section are already in
kebab-case
. Am I missing something?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant the
GithubAuthVending
:)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Saloni pointed out that the others services configured here also use camelcase, so my case inconsistency grumpiness is out of scope for this PR.