Skip to content

Commit

Permalink
Fix get image auth digest (#530)
Browse files Browse the repository at this point in the history

Signed-off-by: Paolo Di Tommaso <[email protected]>
  • Loading branch information
pditommaso authored Jun 13, 2024
1 parent ec268a9 commit df8ec04
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ class ContainerController {
}

protected BuildTrack checkBuild(BuildRequest build, boolean dryRun) {
final digest = registryProxyService.getImageDigest(build.targetImage)
final digest = registryProxyService.getImageDigest(build)
// check for dry-run execution
if( dryRun ) {
log.debug "== Dry-run build request: $build"
Expand Down
24 changes: 7 additions & 17 deletions src/main/groovy/io/seqera/wave/core/RegistryProxyService.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import io.seqera.wave.http.HttpClientFactory
import io.seqera.wave.model.ContainerCoordinates
import io.seqera.wave.proxy.ProxyClient
import io.seqera.wave.service.CredentialsService
import io.seqera.wave.service.builder.BuildRequest
import io.seqera.wave.service.persistence.PersistenceService
import io.seqera.wave.storage.DigestStore
import io.seqera.wave.storage.Storage
Expand All @@ -45,7 +46,6 @@ import jakarta.inject.Singleton
import reactor.core.publisher.Flux
import static io.seqera.wave.WaveDefault.HTTP_REDIRECT_CODES
import static io.seqera.wave.WaveDefault.HTTP_RETRYABLE_ERRORS

/**
* Proxy service that forwards incoming container request
* to the target repository, resolving credentials and augmentation
Expand Down Expand Up @@ -188,33 +188,23 @@ class RegistryProxyService {
}
}

@Deprecated
boolean isManifestPresent(String image){
try {
return getImageDigest0(image,false) != null
}
catch(Exception e) {
log.warn "Unable to check status for container image '$image' -- cause: ${e.message}"
return false
}
}

String getImageDigest(String image, boolean retryOnNotFound=false) {
String getImageDigest(BuildRequest request, boolean retryOnNotFound=false) {
try {
return getImageDigest0(image, retryOnNotFound)
return getImageDigest0(request, retryOnNotFound)
}
catch(Exception e) {
log.warn "Unable to retrieve digest for image '$image' -- cause: ${e.message}"
log.warn "Unable to retrieve digest for image '${request.getTargetImage()}' -- cause: ${e.message}"
return null
}
}

static private List<Integer> RETRY_ON_NOT_FOUND = HTTP_RETRYABLE_ERRORS + 404

@Cacheable(value = 'cache-20sec', atomic = true)
protected String getImageDigest0(String image, boolean retryOnNotFound) {
protected String getImageDigest0(BuildRequest request, boolean retryOnNotFound) {
final image = request.targetImage
final coords = ContainerCoordinates.parse(image)
final route = RoutePath.v2manifestPath(coords)
final route = RoutePath.v2manifestPath(coords, request.identity)
final proxyClient = client(route)
.withRetryableHttpErrors(retryOnNotFound ? RETRY_ON_NOT_FOUND : HTTP_RETRYABLE_ERRORS)
final resp = proxyClient.head(route.path, WaveDefault.ACCEPT_HEADERS)
Expand Down
5 changes: 3 additions & 2 deletions src/main/groovy/io/seqera/wave/core/RoutePath.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,9 @@ class RoutePath implements ContainerPath {
new RoutePath(type, registry ?: DOCKER_IO, image, ref, "/v2/$image/$type/$ref", request, token)
}

static RoutePath v2manifestPath(ContainerCoordinates container) {
new RoutePath('manifests', container.registry, container.image, container.reference, "/v2/${container.image}/manifests/${container.reference}")
static RoutePath v2manifestPath(ContainerCoordinates container, PlatformId identity=null) {
ContainerRequestData data = identity!=null ? new ContainerRequestData(identity) : null
return new RoutePath('manifests', container.registry, container.image, container.reference, "/v2/${container.image}/manifests/${container.reference}", data)
}

static RoutePath empty() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class DockerBuildStrategy extends BuildStrategy {
final completed = proc.waitFor(buildConfig.buildTimeout.toSeconds(), TimeUnit.SECONDS)
final stdout = proc.inputStream.text
if( completed ) {
final digest = proc.exitValue()==0 ? proxyService.getImageDigest(req.targetImage,true) : null
final digest = proc.exitValue()==0 ? proxyService.getImageDigest(req, true) : null
return BuildResult.completed(req.buildId, proc.exitValue(), stdout, req.startTime, digest)
}
else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class KubeBuildStrategy extends BuildStrategy {
final terminated = k8sService.waitPod(pod, buildConfig.buildTimeout.toMillis())
final stdout = k8sService.logsPod(name)
if( terminated ) {
final digest = terminated.exitCode==0 ? proxyService.getImageDigest(req.targetImage,true) : null
final digest = terminated.exitCode==0 ? proxyService.getImageDigest(req, true) : null
return BuildResult.completed(req.buildId, terminated.exitCode, stdout, req.startTime, digest)
}
else {
Expand Down
29 changes: 19 additions & 10 deletions src/test/groovy/io/seqera/wave/core/RegistryProxyServiceTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,14 @@

package io.seqera.wave.core

import spock.lang.Requires
import spock.lang.Shared
import spock.lang.Specification

import io.micronaut.context.ApplicationContext
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import io.seqera.wave.service.builder.BuildRequest
import io.seqera.wave.tower.PlatformId
import jakarta.inject.Inject
/**
*
Expand All @@ -38,25 +41,31 @@ class RegistryProxyServiceTest extends Specification {
@Inject RegistryProxyService registryProxyService


def 'should check manifest exist' () {
def 'should retrieve image digest' () {
given:
def IMAGE = 'library/hello-world:latest'
def IMAGE = 'library/hello-world@sha256:6352af1ab4ba4b138648f8ee88e63331aae519946d3b67dae50c313c6fc8200f'
def request = Mock(BuildRequest)

when:
def resp1 = registryProxyService.isManifestPresent(IMAGE)

def resp1 = registryProxyService.getImageDigest(request)
then:
resp1
request.getTargetImage() >> IMAGE
then:
resp1 == 'sha256:6352af1ab4ba4b138648f8ee88e63331aae519946d3b67dae50c313c6fc8200f'
}

def 'should retrieve image digest' () {
@Requires({System.getenv('AWS_ACCESS_KEY_ID') && System.getenv('AWS_SECRET_ACCESS_KEY')})
def 'should retrive image digest on ECR' () {

Check failure on line 58 in src/test/groovy/io/seqera/wave/core/RegistryProxyServiceTest.groovy

View workflow job for this annotation

GitHub Actions / Check for spelling errors

retrive ==> retrieve
given:
def IMAGE = 'library/hello-world@sha256:6352af1ab4ba4b138648f8ee88e63331aae519946d3b67dae50c313c6fc8200f'
def IMAGE = '195996028523.dkr.ecr.eu-west-1.amazonaws.com/wave/kaniko:0.1.0'
def request = Mock(BuildRequest)

when:
def resp1 = registryProxyService.getImageDigest(IMAGE)

def resp1 = registryProxyService.getImageDigest(request)
then:
resp1 == 'sha256:6352af1ab4ba4b138648f8ee88e63331aae519946d3b67dae50c313c6fc8200f'
request.getTargetImage() >> IMAGE
request.getIdentity() >> new PlatformId()
then:
resp1 == 'sha256:05f9dc67e6ec879773de726b800d4d5044f8bd8e67da728484fbdea56af1fdff'
}
}
17 changes: 16 additions & 1 deletion src/test/groovy/io/seqera/wave/core/RoutePathTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
package io.seqera.wave.core

import spock.lang.Specification
import spock.lang.Unroll

import io.seqera.wave.model.ContainerCoordinates
import io.seqera.wave.service.ContainerRequestData
import io.seqera.wave.tower.PlatformId
import io.seqera.wave.tower.User

/**
*
* @author Paolo Di Tommaso <[email protected]>
Expand Down Expand Up @@ -72,6 +72,7 @@ class RoutePathTest extends Specification {

}

@Unroll
def 'should get manifest path'() {
expect:
RoutePath.v2manifestPath(ContainerCoordinates.parse(CONTAINER)).path == PATH
Expand All @@ -82,6 +83,20 @@ class RoutePathTest extends Specification {
'quay.io/foo/bar:v1.0' | '/v2/foo/bar/manifests/v1.0'
}

def 'should get manifest path with identity'() {
given:
def CONTAINER = ContainerCoordinates.parse('quay.io/foo/bar:v1.0')
def PATH = '/v2/foo/bar/manifests/v1.0'
def IDENTITY = new PlatformId(new User(id: 1, email: '[email protected]'), 2, 'xyz')

expect:
RoutePath.v2manifestPath(CONTAINER).path == PATH
RoutePath.v2manifestPath(CONTAINER).identity == PlatformId.NULL
and:
RoutePath.v2manifestPath(CONTAINER, IDENTITY).path == PATH
RoutePath.v2manifestPath(CONTAINER, IDENTITY).identity == IDENTITY
}

def 'should parse location' () {
expect:
RoutePath.parse(GIVEN) == RoutePath.v2path(TYPE, REG, IMAGE, REF)
Expand Down
21 changes: 21 additions & 0 deletions src/test/groovy/io/seqera/wave/proxy/ProxyClientTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,27 @@ class ProxyClientTest extends Specification {
resp.statusCode() == 200
}

@Requires({System.getenv('AWS_ACCESS_KEY_ID') && System.getenv('AWS_SECRET_ACCESS_KEY')})
def 'should call head manifest on amazon' () {
given:
def IMAGE = 'wave/kaniko'
def REG = '195996028523.dkr.ecr.eu-west-1.amazonaws.com'
def registry = lookupService.lookup(REG)
def creds = credentialsProvider.getDefaultCredentials(REG)
def httpClient = HttpClientFactory.neverRedirectsHttpClient()
and:
def proxy = new ProxyClient(httpClient, httpConfig)
.withImage(IMAGE)
.withRegistry(registry)
.withLoginService(loginService)
.withCredentials(creds)

when:
def resp = proxy.head("/v2/$IMAGE/manifests/0.1.0")
then:
resp.statusCode() == 200
}

@Requires({System.getenv('AWS_ACCESS_KEY_ID') && System.getenv('AWS_SECRET_ACCESS_KEY')})
def 'should call target manifest on ecr public' () {
given:
Expand Down

0 comments on commit df8ec04

Please sign in to comment.