Skip to content

Commit 327504a

Browse files
committed
feat: Add remote hashing for AWS ECR
1 parent d9d467e commit 327504a

File tree

5 files changed

+93
-1
lines changed

5 files changed

+93
-1
lines changed

core/src/main/resources/reference.conf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,13 @@ docker {
412412
max-retries = 3
413413

414414
// Supported registries (Docker Hub, Google, Quay) can have additional configuration set separately
415+
aws {
416+
throttle {
417+
number-of-requests = 1000
418+
per = 100 seconds
419+
}
420+
num-threads = 10
421+
}
415422
azure {
416423
// Worst case `ReadOps per minute` value from official docs
417424
// https://github.com/MicrosoftDocs/azure-docs/blob/main/includes/container-registry-limits.md

dockerHashing/src/main/scala/cromwell/docker/DockerImageIdentifier.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package cromwell.docker
22

3+
import cromwell.docker.registryv2.flows.aws.AwsElasticContainerRegistry.isEcr
34
import cromwell.docker.registryv2.flows.azure.AzureContainerRegistry
45

56
import scala.util.{Failure, Success, Try}
@@ -19,7 +20,9 @@ sealed trait DockerImageIdentifier {
1920
lazy val nameWithDefaultRepository =
2021
// In ACR, the repository is part of the registry domain instead of the path
2122
// e.g. `terrabatchdev.azurecr.io`
22-
if (host.exists(_.contains(AzureContainerRegistry.domain)))
23+
// In ECR, an image with no repository is supported
24+
// e.g. 123456790.dkr.ecr.eu-west-2.amazonaws.com/example-tool
25+
if (host.exists(_.contains(AzureContainerRegistry.domain)) || !host.exists(isEcr))
2326
image
2427
else
2528
repository.getOrElse("library") + s"/$image"

dockerHashing/src/main/scala/cromwell/docker/DockerInfoActor.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import cromwell.core.actor.StreamIntegration.{BackPressure, StreamContext}
1414
import cromwell.core.{Dispatcher, DockerConfiguration}
1515
import cromwell.docker.DockerInfoActor._
1616
import cromwell.docker.registryv2.DockerRegistryV2Abstract
17+
import cromwell.docker.registryv2.flows.aws.AwsElasticContainerRegistry
1718
import cromwell.docker.registryv2.flows.azure.AzureContainerRegistry
1819
import cromwell.docker.registryv2.flows.dockerhub.DockerHubRegistry
1920
import cromwell.docker.registryv2.flows.google.GoogleRegistry
@@ -239,6 +240,7 @@ object DockerInfoActor {
239240

240241
// To add a new registry, simply add it to that list
241242
List(
243+
("aws", { c: DockerRegistryConfig => new AwsElasticContainerRegistry(c) }),
242244
("azure", { c: DockerRegistryConfig => new AzureContainerRegistry(c) }),
243245
("dockerhub", { c: DockerRegistryConfig => new DockerHubRegistry(c) }),
244246
("google", { c: DockerRegistryConfig => new GoogleRegistry(c) }),
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package cromwell.docker.registryv2.flows.aws
2+
3+
import cats.effect.IO
4+
import cromwell.docker.{DockerImageIdentifier, DockerInfoActor, DockerRegistryConfig}
5+
import cromwell.docker.registryv2.DockerRegistryV2Abstract
6+
import cromwell.docker.registryv2.flows.aws.AwsElasticContainerRegistry.{isEcr, isPublicEcr}
7+
import org.http4s.{AuthScheme, Header}
8+
import org.http4s.client.Client
9+
import software.amazon.awssdk.regions.Region
10+
import software.amazon.awssdk.services.ecr.EcrClient
11+
import software.amazon.awssdk.services.ecrpublic.EcrPublicClient
12+
import software.amazon.awssdk.services.ecrpublic.model.GetAuthorizationTokenRequest
13+
14+
import scala.compat.java8.OptionConverters.RichOptionalGeneric
15+
16+
class AwsElasticContainerRegistry(config: DockerRegistryConfig) extends DockerRegistryV2Abstract(config) {
17+
18+
private lazy val ecrClient = EcrClient.create()
19+
private lazy val ecrPublicClient = EcrPublicClient.builder().region(Region.US_EAST_1).build()
20+
21+
override def getAuthorizationScheme(dockerImageIdentifier: DockerImageIdentifier): AuthScheme =
22+
if (isPublicEcr(dockerImageIdentifier.hostAsString)) AuthScheme.Bearer else AuthScheme.Basic
23+
24+
override def accepts(dockerImageIdentifier: DockerImageIdentifier): Boolean =
25+
isEcr(dockerImageIdentifier.hostAsString)
26+
27+
/**
28+
* (e.g registry-1.docker.io)
29+
*/
30+
override def registryHostName(dockerImageIdentifier: DockerImageIdentifier): String =
31+
dockerImageIdentifier.host.getOrElse("")
32+
33+
/**
34+
* (e.g auth.docker.io)
35+
*/
36+
override def authorizationServerHostName(dockerImageIdentifier: DockerImageIdentifier): String =
37+
dockerImageIdentifier.host.getOrElse("")
38+
39+
override def getToken(
40+
dockerInfoContext: DockerInfoActor.DockerInfoContext
41+
)(implicit client: Client[IO]): IO[Option[String]] =
42+
if (isPublicEcr(dockerInfoContext.dockerImageID.hostAsString)) getPublicEcrToken
43+
else getPrivateEcrToken
44+
45+
/**
46+
* Builds the list of headers for the token request
47+
*/
48+
override def buildTokenRequestHeaders(dockerInfoContext: DockerInfoActor.DockerInfoContext): List[Header] =
49+
List.empty
50+
51+
private def getPublicEcrToken: IO[Option[String]] =
52+
IO(
53+
Option(
54+
ecrPublicClient
55+
.getAuthorizationToken(GetAuthorizationTokenRequest.builder().build())
56+
.authorizationData()
57+
.authorizationToken()
58+
)
59+
)
60+
61+
private def getPrivateEcrToken: IO[Option[String]] =
62+
IO(
63+
ecrClient
64+
.getAuthorizationToken()
65+
.authorizationData()
66+
.stream()
67+
.findFirst()
68+
.asScala
69+
.map(_.authorizationToken())
70+
)
71+
72+
}
73+
74+
object AwsElasticContainerRegistry {
75+
def isEcr(host: String): Boolean = isPublicEcr(host) || isPrivateEcr(host)
76+
def isPublicEcr(host: String): Boolean = host.contains("public.ecr.aws")
77+
def isPrivateEcr(host: String): Boolean = host.contains("amazonaws.com")
78+
}

project/Dependencies.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,8 @@ object Dependencies {
400400
"batch",
401401
"core",
402402
"cloudwatchlogs",
403+
"ecr",
404+
"ecrpublic",
403405
"s3",
404406
"sts",
405407
).map(artifactName => "software.amazon.awssdk" % artifactName % awsSdkV)

0 commit comments

Comments
 (0)