Skip to content

Commit afd3630

Browse files
committed
Allow the use of SHA256 container names
Signed-off-by: Paolo Di Tommaso <[email protected]>
1 parent 95d2907 commit afd3630

File tree

8 files changed

+61
-25
lines changed

8 files changed

+61
-25
lines changed

src/main/groovy/io/seqera/wave/controller/ContainerTokenController.groovy

+3-1
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,8 @@ class ContainerTokenController {
147147
ContainerRequestData makeRequestData(SubmitContainerTokenRequest req, User user, String ip) {
148148
if( req.containerImage && req.containerFile )
149149
throw new BadRequestException("Attributes 'containerImage' and 'containerFile' cannot be used in the same request")
150+
if( req.containerImage?.contains('@sha256:') && req.containerConfig )
151+
throw new BadRequestException("Container requests made using a SHA256 as tag does not support the 'containerConfig' attribute")
150152

151153
String targetImage
152154
String targetContent
@@ -179,6 +181,6 @@ class ContainerTokenController {
179181

180182
protected String targetImage(String token, String image) {
181183
final coords = ContainerCoordinates.parse(image)
182-
return "${new URL(serverUrl).getAuthority()}/wt/$token/${coords.image}:${coords.reference}"
184+
return "${new URL(serverUrl).getAuthority()}/wt/$token/${coords.getImageAndTag()}"
183185
}
184186
}

src/main/groovy/io/seqera/wave/controller/RegistryProxyController.groovy

+16-5
Original file line numberDiff line numberDiff line change
@@ -128,18 +128,23 @@ class RegistryProxyController {
128128
}
129129

130130
if( route.tagList ){
131+
log.debug "Handling tag list request '$route.path'"
131132
return handleTagList(route, httpRequest)
132133
}
133134

134135
final type = route.isManifest() ? 'manifest' : 'blob'
135136
final headers = httpRequest.headers.asMap() as Map<String, List<String>>
136137
final resp = proxyService.handleRequest(route, headers)
137138
if( resp.isRedirect() ) {
138-
log.debug "Forwarding $type request '$route.path' to '$resp.location'"
139+
log.debug "Forwarding $type request '${route.getTargetContainer()}' to '${resp.location}'"
139140
return fromRedirectResponse(resp)
140141
}
142+
else if( route.isManifest() ) {
143+
log.debug "Pulling manifest from repository: '${route.getTargetContainer()}'"
144+
return fromManifestResponse(resp)
145+
}
141146
else {
142-
log.debug "Pulling $type from remote host: '$route.path'"
147+
log.debug "Pulling blob from repository: '${route.getTargetContainer()}'"
143148
return fromDelegateResponse(resp)
144149
}
145150
}
@@ -160,7 +165,7 @@ class RegistryProxyController {
160165

161166
MutableHttpResponse<?> handleHead(RoutePath route, HttpRequest httpRequest) {
162167

163-
if (!(route.manifest && route.tag)) {
168+
if ( !route.manifest ) {
164169
throw new DockerRegistryException("Invalid request HEAD '$httpRequest.path'", 400, 'UNKNOWN')
165170
}
166171

@@ -172,7 +177,6 @@ class RegistryProxyController {
172177
}
173178

174179
MutableHttpResponse<?> handleTagList(RoutePath route, HttpRequest httpRequest) {
175-
log.debug "Handling tag list request '$route.path'"
176180
final headers = httpRequest.headers.asMap() as Map<String, List<String>>
177181
final resp = proxyService.handleRequest(route, headers)
178182
HttpResponse
@@ -188,7 +192,7 @@ class RegistryProxyController {
188192
"docker-content-digest", entry.digest,
189193
"etag", entry.digest,
190194
"docker-distribution-api-version", "registry/2.0") as Map<CharSequence, CharSequence>
191-
MutableHttpResponse
195+
HttpResponse
192196
.ok( entry.bytes )
193197
.headers(headers)
194198
}
@@ -211,6 +215,13 @@ class RegistryProxyController {
211215
.headers(toMutableHeaders(delegateResponse.headers))
212216
}
213217

218+
MutableHttpResponse<?> fromManifestResponse(DelegateResponse resp) {
219+
HttpResponse
220+
.status(HttpStatus.valueOf(resp.statusCode))
221+
.body(resp.body.bytes)
222+
.headers(toMutableHeaders(resp.headers))
223+
}
224+
214225
static protected Consumer<MutableHttpHeaders> toMutableHeaders(Map<String,List<String>> headers, Map<String,String> override=Collections.emptyMap()) {
215226
new Consumer<MutableHttpHeaders>() {
216227
@Override

src/main/groovy/io/seqera/wave/core/ContainerAugmenter.groovy

+7
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,13 @@ class ContainerAugmenter {
119119
return digest
120120
}
121121

122+
if( tag.startsWith('sha256:')) {
123+
// container using a digest as tag cannot be augmented because it would
124+
// require to alter the digest itself
125+
final msg = "Operation not allowed for container '$imageName@$tag'"
126+
throw new DockerRegistryException(msg, 400, 'UNSUPPORTED')
127+
}
128+
122129
if( type == ContentType.DOCKER_MANIFEST_V1_JWS_TYPE ) {
123130
final v1Digest = resolveV1Manifest(manifestsList, imageName)
124131
final v1Manifest = storage.getManifest("/v2/$imageName/manifests/$v1Digest").orElse(null)

src/main/groovy/io/seqera/wave/core/RoutePath.groovy

+7-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,13 @@ class RoutePath implements ContainerPath {
3535

3636
String getRepository() { "$registry/$image" }
3737

38-
String getTargetContainer() { "$registry/$image:$reference" }
38+
String getTargetContainer() { registry + '/' + getImageAndTag() }
39+
40+
String getImageAndTag() {
41+
if( !reference ) return image
42+
final sep = isDigest() ? '@' : ':'
43+
return image + sep + reference
44+
}
3945

4046
static RoutePath v2path(String type, String registry, String image, String ref, ContainerRequestData request=null) {
4147
assert type in ALLOWED_TYPES, "Unknown container path type: '$type'"

src/main/groovy/io/seqera/wave/model/ContainerCoordinates.groovy

+9-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ class ContainerCoordinates implements ContainerPath {
2020

2121
String getRepository() { "$registry/$image" }
2222

23-
String getTargetContainer() { "$registry/$image:$reference" }
23+
String getTargetContainer() {
24+
return registry + '/' + getImageAndTag()
25+
}
26+
27+
String getImageAndTag() {
28+
if( !reference ) return image
29+
final sep = reference.startsWith('sha256:') ? '@' : ':'
30+
return image + sep + reference
31+
}
2432

2533
static ContainerCoordinates parse(String path) {
2634

src/main/java/io/seqera/wave/storage/LazyDigestStore.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import io.seqera.wave.storage.reader.ContentReader;
66

77
/**
8-
* Implements a digest store that laods the binary content on-demand
8+
* Implements a digest store that loads the binary content on-demand
99
*
1010
* @author Paolo Di Tommaso <[email protected]>
1111
*/

src/test/groovy/io/seqera/wave/core/RoutePathTest.groovy

+5-3
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ class RoutePathTest extends Specification {
3939
then:
4040
route.repository == REPO
4141
route.targetContainer == TARGET
42+
route.imageAndTag == IMAGE_AND_TAG
4243

4344
where:
44-
TYPE | REG | IMAGE | REF | REPO | TARGET
45-
'blobs' | 'docker.io' | 'busybox' | 'latest' | 'docker.io/busybox' | 'docker.io/busybox:latest'
46-
'blobs' | 'quay.io' | 'busybox' | 'v1' | 'quay.io/busybox' | 'quay.io/busybox:v1'
45+
TYPE | REG | IMAGE | REF | REPO | TARGET | IMAGE_AND_TAG
46+
'blobs' | 'docker.io' | 'busybox' | 'latest' | 'docker.io/busybox' | 'docker.io/busybox:latest' | 'busybox:latest'
47+
'blobs' | 'quay.io' | 'busybox' | 'v1' | 'quay.io/busybox' | 'quay.io/busybox:v1' | 'busybox:v1'
48+
'blobs' | 'quay.io' | 'busybox' | 'sha256:123abc' | 'quay.io/busybox' | 'quay.io/busybox@sha256:123abc'| 'busybox@sha256:123abc'
4749

4850
}
4951

src/test/groovy/io/seqera/wave/model/ContainerCoordinatesTest.groovy

+13-13
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ package io.seqera.wave.model
22

33
import spock.lang.Specification
44
import spock.lang.Unroll
5-
6-
import io.seqera.wave.model.ContainerCoordinates
75
/**
86
*
97
* @author Paolo Di Tommaso <[email protected]>
@@ -19,18 +17,20 @@ class ContainerCoordinatesTest extends Specification {
1917
coords.image == IMAGE
2018
coords.reference == REF
2119
coords.repository == REPO
20+
coords.targetContainer == TARGET
21+
coords.imageAndTag == IMAGE_AND_TAG
2222

2323
where:
24-
STR | REGISTRY | IMAGE | REF | REPO | TARGET
25-
'busybox' | 'docker.io' | 'library/busybox' | 'latest' | 'docker.io/library/busybox' | 'docker.io/library/busybox:latest'
26-
'busybox:1.2.3' | 'docker.io' | 'library/busybox' | '1.2.3' | 'docker.io/library/busybox' | 'docker.io/library/busybox:v1.2.3'
27-
'foo/busybox:bar' | 'docker.io' | 'foo/busybox' | 'bar' | 'docker.io/foo/busybox' | 'docker.io/foo/busybox:bar'
28-
'docker.io/busybox' | 'docker.io' | 'busybox' | 'latest' | 'docker.io/busybox' | 'docker.io/busybox:latest'
29-
'quay.io/busybox' | 'quay.io' | 'busybox' | 'latest' | 'quay.io/busybox' | 'quay.io/busybox:latest'
30-
'quay.io/a/b/c' | 'quay.io' | 'a/b/c' | 'latest' | 'quay.io/a/b/c' | 'quay.io/a/b/c:latest'
31-
'quay.io/a/b/c:v1.1' | 'quay.io' | 'a/b/c' | 'v1.1' | 'quay.io/a/b/c' | 'quay.io/a/b/c:v1.1'
32-
'canonical/ubuntu@sha256:12345' | 'docker.io' | 'canonical/ubuntu'| 'sha256:12345' | 'docker.io/canonical/ubuntu' | 'docker.io/canonical/ubuntu:12345'
33-
'fedora/httpd:version1.0' | 'docker.io' | 'fedora/httpd' | 'version1.0' | 'docker.io/fedora/httpd' | 'docker.io/fedora/httpd:version1.0'
34-
'myregistryhost:5000/fedora/httpd:version1.0' | 'myregistryhost:5000' | 'fedora/httpd' | 'version1.0' | 'myregistryhost:5000/fedora/httpd' | 'myregistryhost:5000/fedora/httpd:version1.0'
24+
STR | REGISTRY | IMAGE | REF | REPO | IMAGE_AND_TAG | TARGET
25+
'busybox' | 'docker.io' | 'library/busybox' | 'latest' | 'docker.io/library/busybox' | 'library/busybox:latest' | 'docker.io/library/busybox:latest'
26+
'busybox:1.2.3' | 'docker.io' | 'library/busybox' | '1.2.3' | 'docker.io/library/busybox' | 'library/busybox:1.2.3' | 'docker.io/library/busybox:1.2.3'
27+
'foo/busybox:bar' | 'docker.io' | 'foo/busybox' | 'bar' | 'docker.io/foo/busybox' | 'foo/busybox:bar' | 'docker.io/foo/busybox:bar'
28+
'docker.io/busybox' | 'docker.io' | 'busybox' | 'latest' | 'docker.io/busybox' | 'busybox:latest' | 'docker.io/busybox:latest'
29+
'quay.io/busybox' | 'quay.io' | 'busybox' | 'latest' | 'quay.io/busybox' | 'busybox:latest' | 'quay.io/busybox:latest'
30+
'quay.io/a/b/c' | 'quay.io' | 'a/b/c' | 'latest' | 'quay.io/a/b/c' | 'a/b/c:latest' | 'quay.io/a/b/c:latest'
31+
'quay.io/a/b/c:v1.1' | 'quay.io' | 'a/b/c' | 'v1.1' | 'quay.io/a/b/c' | 'a/b/c:v1.1' | 'quay.io/a/b/c:v1.1'
32+
'canonical/ubuntu@sha256:12345' | 'docker.io' | 'canonical/ubuntu'| 'sha256:12345' | 'docker.io/canonical/ubuntu' | 'canonical/ubuntu@sha256:12345' | 'docker.io/canonical/ubuntu@sha256:12345'
33+
'fedora/httpd:version1.0' | 'docker.io' | 'fedora/httpd' | 'version1.0' | 'docker.io/fedora/httpd' | 'fedora/httpd:version1.0' | 'docker.io/fedora/httpd:version1.0'
34+
'myregistryhost:5000/fedora/httpd:version1.0' | 'myregistryhost:5000' | 'fedora/httpd' | 'version1.0' | 'myregistryhost:5000/fedora/httpd' | 'fedora/httpd:version1.0' | 'myregistryhost:5000/fedora/httpd:version1.0'
3535
}
3636
}

0 commit comments

Comments
 (0)