Skip to content

Commit

Permalink
Merge branch 'master' into 775-use-tiered-cache-in-place-of-caffeine-…
Browse files Browse the repository at this point in the history
…cache
  • Loading branch information
pditommaso authored Feb 10, 2025
2 parents 8411b20 + a80d718 commit a81d848
Show file tree
Hide file tree
Showing 18 changed files with 353 additions and 112 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.16.8
1.17.2
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ dependencies {
implementation 'jakarta.persistence:jakarta.persistence-api:3.0.0'
implementation 'io.seqera:lib-mail:1.2.1'
implementation 'io.seqera:lib-pool:1.0.0'
implementation 'io.seqera:wave-api:0.14.0'
implementation 'io.seqera:wave-utils:0.15.0'
implementation 'io.seqera:wave-api:0.15.1'
implementation 'io.seqera:wave-utils:0.15.1'
implementation 'io.seqera:lib-crypto:1.0.0'
implementation 'io.micronaut:micronaut-http-client'
implementation 'io.micronaut:micronaut-jackson-databind'
Expand Down
18 changes: 18 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
# Wave changelog
1.17.2 - 10 Feb 2025
- Change threads dump-threshold default to 200 [5d136e45]
- Minor inspect view improvements (#795) [242822fd]
- Bump gradle 8.12.1 [6b85565e]
- Bump MN version 4.7.5 [03febd69]

1.17.1 - 4 Feb 2025
- Add container index support to inspect view (#792) [34428d25]
- Remove server from openapi spec (#793) [9cbdec1d]

1.17.0 - 30 Jan 2025
- Ignore URI dependencies in Conda envs when generating image hash (#786) [c097c8a2]
- Render html from openapi spec generated by typespec (#707) [d3dfee9a]
- Disable proxy cache by default [c91fc31f]
- Update gradle deps [8a2e4209]
- Bump buildkit to version 0.18.2 (#787) [facbf99c]
- Bump java settings [1f6b94da]

1.16.8 - 20 Jab 2025
- Add TraceContextFilter logging context propagation [396c10c2]
- Improve Proxy cache configuration [163e605f]
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#

micronautVersion=4.7.4
micronautVersion=4.7.5
micronautEnvs=dev,h2,mail,aws-ses
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ProxyCacheConfig {
@Value('${wave.proxy-cache.max-size:10000}')
private int maxSize

@Value('${wave.proxy-cache.enabled:true}')
@Value('${wave.proxy-cache.enabled:false}')
private boolean enabled

Duration getDuration() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,6 @@ class ContainerController {
throw new BadRequestException("Attribute `containerFile` and `packages` conflicts each other")
if( v2 && req.condaFile )
throw new BadRequestException("Attribute `condaFile` is deprecated - use `packages` instead")
if( v2 && req.spackFile )
throw new BadRequestException("Attribute `spackFile` is deprecated - use `packages` instead")
if( !v2 && req.packages )
throw new BadRequestException("Attribute `packages` is not allowed")
if( !v2 && req.nameStrategy )
Expand All @@ -254,10 +252,6 @@ class ContainerController {
req = req.copyWith(containerFile: generated.bytes.encodeBase64().toString())
}

if( req.spackFile ) {
throw new BadRequestException("Spack packages are not supported any more")
}

final ip = addressResolver.resolve(httpRequest)
// check the rate limit before continuing
if( rateLimiterService )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class InspectController {
}

protected HttpResponse<ContainerInspectResponse> makeResponse(ContainerInspectRequest req, String platform, PlatformId identity) {
final spec = inspectService.containerSpec(req.containerImage, platform, identity)
final spec = inspectService.containerOrIndexSpec(req.containerImage, platform, identity)
return spec
? HttpResponse.ok(new ContainerInspectResponse(spec))
: HttpResponse.<ContainerInspectResponse>notFound()
Expand Down
25 changes: 17 additions & 8 deletions src/main/groovy/io/seqera/wave/controller/ViewController.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -391,14 +391,23 @@ class ViewController {
HttpResponse<Map<String,Object>> viewInspect(@QueryValue String image, @Nullable @QueryValue String platform) {
final binding = new HashMap(10)
try {
final spec = inspectService.containerSpec(image, platform, null)
binding.imageName = spec.imageName
binding.reference = spec.reference
binding.digest = spec.digest
binding.registry = spec.registry
binding.hostName = spec.hostName
binding.config = JacksonHelper.toJson(spec.config)
binding.manifest = JacksonHelper.toJson(spec.manifest)
final spec = inspectService.containerOrIndexSpec(image, platform, null)
if ( spec.container ){
binding.imageName = spec.container.imageName
binding.reference = spec.container.reference
binding.digest = spec.container.digest
binding.registry = spec.container.registry
binding.hostName = spec.container.hostName
binding.config = JacksonHelper.toJson(spec.container.config)
binding.manifest = JacksonHelper.toJson(spec.container.manifest)
}
else {
binding.imageName = image
binding.digest = spec.index.digest
binding.schemaVersion = spec.index.schemaVersion
binding.mediaType = spec.index.mediaType
binding.manifests = JacksonHelper.toJson(spec.index.manifests)
}
}
catch (Exception e){
binding.error_message = e.getMessage()
Expand Down
76 changes: 49 additions & 27 deletions src/main/groovy/io/seqera/wave/core/ContainerAugmenter.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ import io.seqera.wave.api.ContainerConfig
import io.seqera.wave.api.ContainerLayer
import io.seqera.wave.core.spec.ConfigSpec
import io.seqera.wave.core.spec.ContainerSpec
import io.seqera.wave.core.spec.IndexSpec
import io.seqera.wave.core.spec.ManifestSpec
import io.seqera.wave.exception.BadRequestException
import io.seqera.wave.exception.DockerRegistryException
import io.seqera.wave.model.ContainerOrIndexSpec
import io.seqera.wave.proxy.ProxyClient
import io.seqera.wave.storage.Storage
import io.seqera.wave.util.JacksonHelper
Expand Down Expand Up @@ -67,10 +69,9 @@ class ContainerAugmenter {

private ProxyClient client
private ContainerConfig containerConfig
private ContainerPlatform platform = ContainerPlatform.DEFAULT
private ContainerPlatform platform
private Storage storage


ContainerConfig getContainerConfig() {
return containerConfig
}
Expand All @@ -86,7 +87,7 @@ class ContainerAugmenter {
}

ContainerAugmenter withPlatform(String value) {
this.platform = ContainerPlatform.of(value)
this.platform = value ? ContainerPlatform.of(value) : null
return this
}

Expand All @@ -106,8 +107,7 @@ class ContainerAugmenter {

ContainerDigestPair resolve(RoutePath route, Map<String,List<String>> headers) {
assert route, "Missing route"
if( route.request?.platform )
this.platform = route.request.platform
this.platform = route.request?.platform ?: ContainerPlatform.DEFAULT
// note: do not propagate container config when "freeze" mode is enabled, because it has been already
// applied during the container build phase, and therefore it should be ignored by the augmenter
if( route.request?.containerConfig && !route.request.freeze )
Expand Down Expand Up @@ -146,20 +146,23 @@ class ContainerAugmenter {
// resolve image tag to digest
final resp1 = client.head("/v2/$imageName/manifests/$tag", headers)
final digest = resp1.headers().firstValue('docker-content-digest').orElse(null)
log.trace "Resolve (1): image $imageName:$tag => digest=$digest; response code=${resp1.statusCode()}"
if( log.isTraceEnabled() )
log.trace "Resolve (1): image $imageName:$tag => digest=$digest; response code=${resp1.statusCode()}"
checkResponseCode(resp1, client.route, false)

// get manifest list for digest
final resp2 = client.getString("/v2/$imageName/manifests/$digest", headers)
final type = resp2.headers().firstValue('content-type').orElse(null)
checkResponseCode(resp2, client.route, false)
final manifestsList = resp2.body()
log.trace "Resolve (2): image $imageName:$tag => type=$type; manifests list:\n${JsonOutput.prettyPrint(manifestsList)}"
if( log.isTraceEnabled() )
log.trace "Resolve (2): image $imageName:$tag => type=$type; manifests list:\n${JsonOutput.prettyPrint(manifestsList)}"

// when there's no container config, not much to do
// just cache the manifest content and return the digest
if( !containerConfig ) {
log.trace "Resolve (3): container config provided for image=$imageName:$tag"
if( log.isTraceEnabled() )
log.trace "Resolve (3): container config provided for image=$imageName:$tag"
final target = "$client.registry.name/v2/$imageName/manifests/$digest"
storage.saveManifest(target, manifestsList, type, digest)
return new ContainerDigestPair(digest,digest)
Expand Down Expand Up @@ -188,20 +191,23 @@ class ContainerAugmenter {
final resp5 = client.getString("/v2/$imageName/blobs/$manifestResult.configDigest", headers)
checkResponseCode(resp5, client.route, true)
final imageConfig = resp5.body()
log.trace "Resolve (5): image $imageName:$tag => image config=\n${JsonOutput.prettyPrint(imageConfig)}"
if( log.isTraceEnabled() )
log.trace "Resolve (5): image $imageName:$tag => image config=\n${JsonOutput.prettyPrint(imageConfig)}"

// update the image config adding the new layer
final newConfigResult = updateImageConfig(imageName, imageConfig, manifestResult.oci)
final newConfigDigest = newConfigResult[0]
final newConfigJson = newConfigResult[1]
log.trace "Resolve (6) ==> new config digest: $newConfigDigest => new config=\n${JsonOutput.prettyPrint(newConfigJson)} "
if( log.isTraceEnabled() )
log.trace "Resolve (6) ==> new config digest: $newConfigDigest => new config=\n${JsonOutput.prettyPrint(newConfigJson)} "

// update the image manifest adding a new layer
// returns the updated image manifest digest
final newManifestResult = updateImageManifest(imageName, manifestResult.imageManifest, newConfigDigest, newConfigJson.size(), manifestResult.oci)
final newManifestDigest = newManifestResult.v1
final newManifestSize = newManifestResult.v2
log.trace "Resolve (7) ==> new image digest: $newManifestDigest"
if( log.isTraceEnabled() )
log.trace "Resolve (7) ==> new image digest: $newManifestDigest"

if( !manifestResult.targetDigest ) {
return new ContainerDigestPair(digest, newManifestDigest)
Expand All @@ -210,10 +216,10 @@ class ContainerAugmenter {
// update the manifests list with the new digest
// returns the manifests list digest
final newListDigest = updateImageIndex(imageName, manifestsList, manifestResult.targetDigest, newManifestDigest, newManifestSize, manifestResult.oci)
log.trace "Resolve (8) ==> new list digest: $newListDigest"
if( log.isTraceEnabled() )
log.trace "Resolve (8) ==> new list digest: $newListDigest"
return new ContainerDigestPair(digest, newListDigest)
}

}

protected ManifestInfo parseManifest(String media, String manifest, String targetDigest) {
Expand All @@ -240,7 +246,8 @@ class ContainerAugmenter {
targetDigest = findTargetDigest(json, oci)
final resp3 = client.getString("/v2/$imageName/manifests/$targetDigest", headers)
manifest = resp3.body()
log.trace("Image $imageName:$tag => image manifest=\n${JsonOutput.prettyPrint(manifest)}")
if( log.isTraceEnabled() )
log.trace("Image $imageName:$tag => image manifest=\n${JsonOutput.prettyPrint(manifest)}")
// parse the new manifest
json = new JsonSlurper().parseText(manifest) as Map
media = json.mediaType
Expand Down Expand Up @@ -407,7 +414,8 @@ class ContainerAugmenter {
final result = record.get('digest')
if( !result )
throw new BadRequestException("Cannot find digest entry for platform '${platform}' in the manifest: ${JsonOutput.toJson(json)}")
log.trace "Find target digest platform: $platform ==> digest: $result"
if( log.isTraceEnabled() )
log.trace "Find target digest platform: $platform ==> digest: $result"
return result
}

Expand Down Expand Up @@ -501,56 +509,70 @@ class ContainerAugmenter {
return newManifestDigest
}

ContainerSpec getContainerSpec(String imageName, String tag, Map<String,List<String>> headers) {
ContainerOrIndexSpec getContainerSpec(String imageName, String tag, Map<String,List<String>> headers) {
assert client, "Missing client"
assert platform, "Missing 'platform' parameter"

// resolve image tag to digest
final resp1 = client.head("/v2/$imageName/manifests/$tag", headers)
final digest = resp1.headers().firstValue('docker-content-digest').orElse(null)
log.trace "Config (1): image $imageName:$tag => digest=$digest"
if( log.isTraceEnabled() )
log.trace "Config (1): image $imageName:$tag => digest=$digest"
checkResponseCode(resp1, client.route, false)

// get manifest list for digest
final resp2 = client.getString("/v2/$imageName/manifests/$digest", headers)
final type = resp2.headers().firstValue('content-type').orElse(null)
checkResponseCode(resp2, client.route, false)
final manifestsList = resp2.body()
log.trace "Config (2): image $imageName:$tag => type=$type; manifests list:\n${JsonOutput.prettyPrint(manifestsList)}"
if( log.isTraceEnabled() )
log.trace "Config (2): image $imageName:$tag => type=$type; manifests list:\n${JsonOutput.prettyPrint(manifestsList)}"

// check for legacy docker manifest type
if( type==DOCKER_MANIFEST_V1_JWS_TYPE || type==DOCKER_MANIFEST_V1_TYPE ) {
final json = new JsonSlurper().parseText(manifestsList) as Map
final config = ConfigSpec.parseV1(json)
final manifest = ManifestSpec.parseV1(json)
return new ContainerSpec(
final spec = new ContainerSpec(
client.registry.name,
client.registry.host.toString(),
imageName,
tag,
digest,
config,
manifest )
manifest)
return new ContainerOrIndexSpec(spec)
}

// when the target platform is not specific and it's a index media type
// return the container index specification
if( !platform && (type==DOCKER_IMAGE_INDEX_V2 || type==OCI_IMAGE_INDEX_V1) ) {
final spec = IndexSpec
.parse(manifestsList)
.withDigest(digest)
return new ContainerOrIndexSpec(spec)
}

final manifestResult
= parseManifest(type, manifestsList,digest)
final ManifestInfo manifestResult
= parseManifest(type, manifestsList, digest)
?: findImageManifestAndDigest(manifestsList, imageName, tag, headers)

// fetch the image config
final resp5 = client.getString("/v2/$imageName/blobs/$manifestResult.configDigest", headers)
checkResponseCode(resp5, client.route, true)
final imageConfig = resp5.body()
log.trace "Config (4): image $imageName:$tag => image config=\n${JsonOutput.prettyPrint(imageConfig)}"
if( log.isTraceEnabled() )
log.trace "Config (4): image $imageName:$tag => image config=\n${JsonOutput.prettyPrint(imageConfig)}"

final config = ConfigSpec.parse(imageConfig)
final manifest = manifestResult.manifestSpec
return new ContainerSpec(
final spec = new ContainerSpec(
client.registry.name,
client.registry.host.toString(),
imageName,
tag,
digest,
manifestResult.targetDigest,
config,
manifest )
manifest)
return new ContainerOrIndexSpec(spec)
}
}
8 changes: 4 additions & 4 deletions src/main/groovy/io/seqera/wave/core/ContainerPlatform.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ class ContainerPlatform {

public static final ContainerPlatform DEFAULT = new ContainerPlatform(DEFAULT_OS, DEFAULT_ARCH)

private static List<String> ARM64 = ['arm64', 'aarch64']
private static List<String> V8 = ['8','v8']
private static List<String> AMD64 = ['amd64', 'x86_64', 'x86-64']
private static List<String> ALLOWED_ARCH = AMD64 + ARM64 + ['arm']
private static final List<String> ARM64 = ['arm64', 'aarch64']
private static final List<String> V8 = ['8','v8']
private static final List<String> AMD64 = ['amd64', 'x86_64', 'x86-64']
private static final List<String> ALLOWED_ARCH = AMD64 + ARM64 + ['arm']
public static final String DEFAULT_ARCH = 'amd64'
public static final String DEFAULT_OS = 'linux'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import org.apache.commons.io.FilenameUtils
@CompileStatic
class ThreadMonitorCron {

@Value('${wave.thread-monitor.dump-threshold:500}')
@Value('${wave.thread-monitor.dump-threshold:200}')
private Integer dumpThreshold

@Value('${wave.thread-monitor.dump-file}')
Expand Down
Loading

0 comments on commit a81d848

Please sign in to comment.