diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 602004145..8926c401c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -30,7 +30,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- java_version: [19]
+ java_version: [21]
steps:
- name: Environment
@@ -66,7 +66,7 @@ jobs:
AWS_SECRET_ACCESS_KEY: ${{secrets.TOWER_CI_AWS_SECRET}}
DOCKER_USER: ${{ secrets.DOCKER_USER }}
DOCKER_PAT: ${{ secrets.DOCKER_PAT }}
- QUAY_USER: ${{ secrets.QUAY_USER }}
+ QUAY_USER: "pditommaso+wave_ci_tests"
QUAY_PAT: ${{ secrets.QUAY_PAT }}
AZURECR_USER: ${{ secrets.AZURECR_USER }}
AZURECR_PAT: ${{ secrets.AZURECR_PAT }}
diff --git a/.github/workflows/security-submit-dependecy-graph.yml b/.github/workflows/security-submit-dependecy-graph.yml
new file mode 100644
index 000000000..e2c3a7aa7
--- /dev/null
+++ b/.github/workflows/security-submit-dependecy-graph.yml
@@ -0,0 +1,24 @@
+name: Generate and submit dependency graph for wave
+on:
+ push:
+ branches: ['master']
+
+permissions:
+ contents: write
+
+jobs:
+ dependency-submission:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: 17
+
+ - name: Generate and submit dependency graph for wave
+ uses: gradle/actions/dependency-submission@v4
+ with:
+ dependency-resolution-task: "dependencies"
+ additional-arguments: "--configuration runtimeClasspath"
+ dependency-graph: generate-and-submit
diff --git a/Makefile b/Makefile
index 5fb8134bc..d5efcd355 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-config ?= compileClasspath
+config ?= runtimeClasspath
ifdef module
mm = :${module}:
diff --git a/README.md b/README.md
index 10d853924..13acb4415 100644
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ images.
* Push and cache built containers to a user-provided container repository;
* Build Singularity native containers both using a Singularity spec file, Conda package(s);
* Push Singularity native container images to OCI-compliant registries;
-
+* Scan container images for security vulnerabilities
### How it works
@@ -34,7 +34,7 @@ container registry where the image is stored, while the instrumented layers are
### Requirements
-* Java 19 or later
+* Java 21 or later
* Linux or macOS
* Redis 6.2 (or later)
* Docker engine (for development)
diff --git a/VERSION b/VERSION
index 80138e714..141f2e805 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.13.4
+1.15.0
diff --git a/build.gradle b/build.gradle
index c47b7649a..92edfd34b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,10 +2,10 @@ import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter
plugins {
- id 'java-library'
+ id 'io.seqera.wave.java-library-conventions'
id 'io.seqera.wave.groovy-application-conventions'
- id "com.github.johnrengelman.shadow" version "7.1.1"
- id "io.micronaut.minimal.application" version "3.7.0"
+ id "com.github.johnrengelman.shadow" version "8.1.1"
+ id "io.micronaut.minimal.application" version "4.1.1"
id "com.google.cloud.tools.jib" version "3.4.2"
id 'org.asciidoctor.jvm.convert' version '3.3.2'
id 'jacoco'
@@ -29,73 +29,81 @@ repositories {
}
dependencies {
- annotationProcessor("io.micronaut:micronaut-http-validation")
- compileOnly("io.micronaut.data:micronaut-data-processor")
- compileOnly("io.micronaut:micronaut-inject-groovy")
- compileOnly("io.micronaut:micronaut-http-validation")
- implementation("jakarta.persistence:jakarta.persistence-api:3.0.0")
- api 'io.seqera:lib-mail:1.0.0'
- api 'io.seqera:wave-api:0.13.3'
- api 'io.seqera:wave-utils:0.14.1'
- implementation("io.micronaut:micronaut-http-client")
- implementation("io.micronaut:micronaut-jackson-databind")
- implementation("io.micronaut.groovy:micronaut-runtime-groovy")
- implementation("io.micronaut.reactor:micronaut-reactor")
- implementation("io.micronaut.reactor:micronaut-reactor-http-client")
- implementation("jakarta.annotation:jakarta.annotation-api")
- implementation("io.micronaut:micronaut-validation")
+ annotationProcessor 'io.micronaut.validation:micronaut-validation-processor'
+ annotationProcessor 'io.micronaut:micronaut-http-validation'
+ compileOnly 'io.micronaut.data:micronaut-data-processor'
+ compileOnly 'io.micronaut:micronaut-inject-groovy'
+ compileOnly 'io.micronaut:micronaut-http-validation'
+ implementation 'jakarta.persistence:jakarta.persistence-api:3.0.0'
+ api 'io.seqera:lib-mail:1.2.0'
+ api 'io.seqera:wave-api:0.14.0'
+ api 'io.seqera:wave-utils:0.15.0'
+ implementation 'io.micronaut:micronaut-http-client'
+ implementation 'io.micronaut:micronaut-jackson-databind'
+ implementation 'io.micronaut.groovy:micronaut-runtime-groovy'
+ implementation 'io.micronaut.reactor:micronaut-reactor'
+ implementation 'io.micronaut.reactor:micronaut-reactor-http-client'
+ implementation 'jakarta.annotation:jakarta.annotation-api'
+ implementation 'io.micronaut.validation:micronaut-validation'
implementation 'io.micronaut.security:micronaut-security'
- implementation "org.codehaus.groovy:groovy-json"
- implementation "org.codehaus.groovy:groovy-nio"
- implementation 'com.google.guava:guava:32.1.2-jre'
+ implementation 'io.micronaut:micronaut-websocket'
+ implementation 'org.apache.groovy:groovy-json'
+ implementation 'org.apache.groovy:groovy-nio'
+ implementation 'com.google.guava:guava:33.3.1-jre'
implementation 'dev.failsafe:failsafe:3.1.0'
- implementation('io.projectreactor:reactor-core')
- implementation("io.seqera:tower-crypto:22.4.0-watson") { transitive = false } // to be replaced with 22.4.0 once released
- implementation 'org.apache.commons:commons-compress:1.24.0'
- implementation 'org.apache.commons:commons-lang3:3.12.0'
+ implementation 'io.micronaut.reactor:micronaut-reactor'
+ implementation 'io.micronaut.reactor:micronaut-reactor-http-client'
+ implementation('io.seqera:tower-crypto:22.4.0-watson') { transitive = false } // to be replaced with 22.4.0 once released
+ implementation 'org.apache.commons:commons-compress:1.27.1'
+ implementation 'org.apache.commons:commons-lang3:3.17.0'
implementation 'io.kubernetes:client-java:19.0.0'
implementation 'io.kubernetes:client-java-api-fluent:18.0.1'
implementation 'com.google.code.gson:gson:2.9.0'
- implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"
+ implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml'
- implementation 'com.squareup.moshi:moshi:1.14.0'
- implementation 'com.squareup.moshi:moshi-adapters:1.14.0'
- implementation 'redis.clients:jedis:5.0.2'
- implementation "io.github.resilience4j:resilience4j-ratelimiter:0.17.0"
+ implementation 'com.squareup.moshi:moshi:1.15.1'
+ implementation 'com.squareup.moshi:moshi-adapters:1.15.1'
+ implementation 'redis.clients:jedis:5.1.3'
+ implementation 'io.github.resilience4j:resilience4j-ratelimiter:0.17.0'
+ implementation 'io.micronaut:micronaut-retry'
// caching deps
- implementation("io.micronaut.cache:micronaut-cache-core")
- implementation("io.micronaut.cache:micronaut-cache-caffeine")
- implementation("io.micronaut.aws:micronaut-aws-parameter-store")
- implementation "software.amazon.awssdk:ecr"
- implementation "software.amazon.awssdk:ecrpublic"
+ implementation 'io.micronaut.cache:micronaut-cache-core'
+ implementation 'io.micronaut.cache:micronaut-cache-caffeine'
+ implementation 'io.micronaut.aws:micronaut-aws-parameter-store'
+ implementation 'software.amazon.awssdk:ecr'
+ implementation 'software.amazon.awssdk:ecrpublic'
implementation 'software.amazon.awssdk:ses'
- implementation 'org.yaml:snakeyaml:2.0'
+ implementation 'org.yaml:snakeyaml:2.2'
implementation 'com.github.ben-manes.caffeine:caffeine:3.1.8'
implementation 'org.luaj:luaj-jse:3.0.1'
//object storage dependency
- implementation("io.micronaut.objectstorage:micronaut-object-storage-aws")
+ implementation 'io.micronaut.objectstorage:micronaut-object-storage-aws'
// include sts to allow the use of service account role - https://stackoverflow.com/a/73306570
// this sts dependency is require by micronaut-aws-parameter-store,
// not directly used by the app, for this reason keeping `runtimeOnly`
- runtimeOnly "software.amazon.awssdk:sts"
-
- runtimeOnly("io.netty:netty-tcnative-boringssl-static:2.0.0.Final")
- runtimeOnly("javax.xml.bind:jaxb-api:2.3.1")
- testImplementation("org.testcontainers:testcontainers")
- testImplementation("org.testcontainers:mysql:1.17.3")
+ runtimeOnly 'software.amazon.awssdk:sts'
+ runtimeOnly 'io.netty:netty-tcnative-boringssl-static:2.0.0.Final'
+ runtimeOnly 'javax.xml.bind:jaxb-api:2.3.1'
+ testImplementation 'org.testcontainers:testcontainers'
+ testImplementation 'org.testcontainers:mysql:1.17.3'
// --
- implementation("ch.qos.logback:logback-classic:1.4.8")
+ implementation 'ch.qos.logback:logback-classic:1.5.12'
// rate limit
- implementation 'com.github.seqeralabs:spillway:7b72700293'
+ implementation 'com.coveo:spillway:3.0.0'
// monitoring
- implementation "io.micronaut.micrometer:micronaut-micrometer-registry-prometheus"
+ implementation 'io.micronaut.micrometer:micronaut-micrometer-registry-prometheus'
// Also required to enable endpoint
- implementation "io.micronaut:micronaut-management"
+ implementation 'io.micronaut:micronaut-management'
//views
- implementation("io.micronaut.views:micronaut-views-handlebars")
+ implementation 'io.micronaut.views:micronaut-views-handlebars'
+
+ // upgrade indirect dependencies
+ runtimeOnly 'org.bouncycastle:bcpkix-jdk18on:1.78'
+ runtimeOnly 'org.bitbucket.b_c:jose4j:0.9.4'
+ runtimeOnly 'io.netty:netty-bom:4.1.115.Final'
}
application {
@@ -148,8 +156,7 @@ jib {
run{
def envs = findProperty('micronautEnvs')
- // note: "--enable-preview" is required to use virtual threads on Java 19 and 20
- def args = ["-Dmicronaut.environments=$envs","--enable-preview"]
+ def args = ["-Dmicronaut.environments=$envs","-Djdk.tracePinnedThreads=short"]
if( environment['JVM_OPTS'] ) args.add(environment['JVM_OPTS'])
jvmArgs args
systemProperties 'DOCKER_USER': project.findProperty('DOCKER_USER') ?: environment['DOCKER_USER'],
diff --git a/buildSrc/src/main/groovy/io.seqera.wave.groovy-application-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.wave.groovy-application-conventions.gradle
index 0cf39fc5e..4ff807f4c 100644
--- a/buildSrc/src/main/groovy/io.seqera.wave.groovy-application-conventions.gradle
+++ b/buildSrc/src/main/groovy/io.seqera.wave.groovy-application-conventions.gradle
@@ -11,8 +11,3 @@ plugins {
}
group = 'io.seqera'
-
-tasks.withType(Test) {
- // note: "--enable-preview" is required to use virtual thread on Java 19 and 20
- jvmArgs (["--enable-preview"])
-}
diff --git a/buildSrc/src/main/groovy/io.seqera.wave.groovy-common-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.wave.groovy-common-conventions.gradle
index da3059f91..45696fae9 100644
--- a/buildSrc/src/main/groovy/io.seqera.wave.groovy-common-conventions.gradle
+++ b/buildSrc/src/main/groovy/io.seqera.wave.groovy-common-conventions.gradle
@@ -14,17 +14,17 @@ repositories {
java {
toolchain {
- languageVersion = JavaLanguageVersion.of(19)
+ languageVersion = JavaLanguageVersion.of(21)
}
}
compileJava {
- options.release.set(11)
+ options.release.set(17)
}
-tasks.withType(GroovyCompile) {
- sourceCompatibility = '11'
- targetCompatibility = '11'
+tasks.withType(GroovyCompile).configureEach {
+ sourceCompatibility = '17'
+ targetCompatibility = '17'
}
group = 'io.seqera'
diff --git a/buildSrc/src/main/groovy/io.seqera.wave.java-library-conventions.gradle b/buildSrc/src/main/groovy/io.seqera.wave.java-library-conventions.gradle
index f6197b641..835199bd4 100644
--- a/buildSrc/src/main/groovy/io.seqera.wave.java-library-conventions.gradle
+++ b/buildSrc/src/main/groovy/io.seqera.wave.java-library-conventions.gradle
@@ -16,17 +16,17 @@ repositories {
java {
toolchain {
- languageVersion = JavaLanguageVersion.of(19)
+ languageVersion = JavaLanguageVersion.of(21)
}
}
compileJava {
- options.release.set(11)
+ options.release.set(17)
}
-tasks.withType(GroovyCompile) {
- sourceCompatibility = '11'
- targetCompatibility = '11'
+tasks.withType(GroovyCompile).configureEach {
+ sourceCompatibility = '17'
+ targetCompatibility = '17'
}
test {
@@ -40,22 +40,21 @@ java {
}
dependencies {
- implementation 'org.slf4j:slf4j-api:1.7.36'
+ implementation 'org.slf4j:slf4j-api:2.0.16'
- testImplementation 'ch.qos.logback:logback-core:1.2.11'
- testImplementation 'ch.qos.logback:logback-classic:1.2.11'
- testImplementation "org.codehaus.groovy:groovy:3.0.15"
- testImplementation "org.codehaus.groovy:groovy-nio:3.0.15"
- testImplementation ("org.codehaus.groovy:groovy-test:3.0.17")
- testImplementation ("cglib:cglib-nodep:3.3.0")
- testImplementation ("org.objenesis:objenesis:3.2")
- testImplementation ("org.spockframework:spock-core:2.3-groovy-3.0") { exclude group: 'org.codehaus.groovy'; exclude group: 'net.bytebuddy' }
- testImplementation ('org.spockframework:spock-junit4:2.3-groovy-3.0') { exclude group: 'org.codehaus.groovy'; exclude group: 'net.bytebuddy' }
+ testImplementation 'ch.qos.logback:logback-core:1.5.12'
+ testImplementation 'ch.qos.logback:logback-classic:1.5.12'
+ testImplementation 'org.apache.groovy:groovy:4.0.15'
+ testImplementation 'org.apache.groovy:groovy-nio:4.0.15'
+ testImplementation 'org.apache.groovy:groovy-test:4.0.15'
+ testImplementation 'org.objenesis:objenesis:3.4'
+ testImplementation 'net.bytebuddy:byte-buddy:1.14.17'
+ testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0'
+ testImplementation 'org.spockframework:spock-junit4:2.3-groovy-4.0'
}
-tasks.withType(Test) {
- jvmArgs ([
- '--enable-preview',
+tasks.withType(Test).configureEach {
+ jvmArgs([
'--add-opens=java.base/java.lang=ALL-UNNAMED',
'--add-opens=java.base/java.io=ALL-UNNAMED',
'--add-opens=java.base/java.nio=ALL-UNNAMED',
diff --git a/changelog.txt b/changelog.txt
index 41b36f097..5e5b7fdad 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,4 +1,64 @@
# Wave changelog
+1.15.0 - 18 Nov 2024
+- Migration to virtual threads - phase 1 (#746) [aaf0420c]
+- Use runAsync instead supplyAsync [ffd0dacd]
+- Remove deprecated ThreadPoolBuilder [7af3046f]
+- Replace Guava cache with Caffeine (#745) [cf813e0a]
+- Update project deps [f24b684d]
+- Bump guava to version 33.3.1-jre [328e9ea3]
+- Bump Netty version 4.1.115.Final [9ba433ce]
+- Bump gradle 8.10.2 [52272fe1]
+
+1.14.1 - 14 Nov 2024
+- Fix creds validation endpoint (#740) [8c0f3a4c]
+
+1.14.0 - 10 Nov 2024
+- Fix K8s env propagation [76f0a456]
+- Remove deprecated K8s methods (#734) [481298bf]
+- Bump to Micronaut 4.6 (#318) [f67e8556]
+- Bump Java 21 as build requirement (#519) [132f9491]
+- Bump bitbucket.b_c:jose4j:0.9.4 [2e10416a]
+- Bump bouncycastle:bcpkix-jdk18on:1.78 [ede22ce5]
+- Bump jedis 5.1.3 (#732) [2ee0854e]
+- Bump logback 1.5.12 [f5fe3fa4]
+- Bump make deps runtimeclasspath [2a342b18]
+- Bump snakeyaml 2.2 [6aeb3c33]
+- Bump spillway 3.0.0 (#731) [1502696d]
+- Bump explicit dep to websocket module [2e413ac2]
+- Enables EKS Pod identity via AWS SDK 2.27.8
+
+1.13.11 - 2 Nov 2024
+- Rename async methods for semantic consistency [38114d75]
+- Save scan record async (#730) [3ad82a3a]
+- Cap number of vulnerabilities reported in scan report to 100 (#728) [2f0d8f9f]
+- Bump org.apache.commons:commons-compress:1.27.1 (#722) [adb75007]
+
+1.13.10 - 29 Oct 2024
+- Log slow processing stream messages [e8a6b7ee]
+- Prevent scan when mode is not defined [d42bcae1]
+
+1.13.9 - 29 Oct 2024
+- Fix inspect view (#725) [dcf41dea] [e38e2c44]
+
+1.13.8 - 26 Oct 2024
+- Fix update scan status synchronously [e767c367]
+- Bump scan warn colour [705141f0]
+- Improve scan logging [f01e4dba]
+
+1.13.7 - 25 Oct 2024
+- Add ability to configure trivy environment & DBs (#720) [0f600306]
+
+1.13.6 - 25 Oct 2024
+- Add scan color for different vuls (#719) [ab81b6dc]
+
+1.13.5 - 23 Oct 2024
+- Fix Do not render inspect url on fail [d96275a1]
+- Fix inspect view empty nodes (#706) [b3473b7e]
+- Fix prevent scan on cached failed builds [4473fe8c]
+- Use JedisPool in place of generic connection pool (#711) [cd16cfd1]
+- Minor page title change [c3be9304]
+- GHA to submit dependency graph to Github (#715) [09c86627]
+
1.13.4 - 20 Oct 2024
- Add scan failure duration setting (#705) [372d6dec]
- Change scan config log to info [f382c51a]
diff --git a/docs/cli/index.mdx b/docs/cli/index.mdx
index 6581f628d..45f1239e2 100644
--- a/docs/cli/index.mdx
+++ b/docs/cli/index.mdx
@@ -27,8 +27,8 @@ The following CLI arguments are available for Seqera Platform integration:
The following environment variables are available for Seqera Platform integration:
-- `TOWER_API_ENDPOINT`: A Seqera Platform auth token so that Wave can access your private registry credentials.
-- `TOWER_ACCESS_TOKEN`: For Enterprise customers, the URL endpoint for your instance, such as `https://api.cloud.seqera.io`.
+- `TOWER_ACCESS_TOKEN`: A Seqera Platform auth token so that Wave can access your private registry credentials.
+- `TOWER_API_ENDPOINT`: For Enterprise customers, the URL endpoint for your instance, such as `https://api.cloud.seqera.io`.
- `TOWER_WORKSPACE_ID`: A Seqera Platform workspace ID, such as `1234567890`, where credentials may be stored.
## Usage limits
diff --git a/gradle.properties b/gradle.properties
index 645585ab7..54b62b375 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -16,5 +16,5 @@
# along with this program. If not, see .
#
-micronautVersion=3.10.3
+micronautVersion=4.6.3
micronautEnvs=dev,h2,mail,aws-ses
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index a4413138c..df97d72b8 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/settings.gradle b/settings.gradle
index 395412606..0605b3681 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,10 @@
+plugins {
+ // required to download the toolchain (jdk) from a remote repository
+ // https://github.com/gradle/foojay-toolchains
+ // https://docs.gradle.org/current/userguide/toolchains.html#sub:download_repositories
+ id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
+}
+
rootProject.name="wave"
// only for development
diff --git a/src/main/groovy/io/seqera/wave/auth/BasicAuthenticationProvider.groovy b/src/main/groovy/io/seqera/wave/auth/BasicAuthenticationProvider.groovy
index b76abda4e..d76ff89ee 100644
--- a/src/main/groovy/io/seqera/wave/auth/BasicAuthenticationProvider.groovy
+++ b/src/main/groovy/io/seqera/wave/auth/BasicAuthenticationProvider.groovy
@@ -19,18 +19,17 @@
package io.seqera.wave.auth
import groovy.util.logging.Slf4j
+import io.micronaut.core.annotation.NonNull
import io.micronaut.core.annotation.Nullable
import io.micronaut.http.HttpRequest
-import io.micronaut.security.authentication.AuthenticationProvider
+import io.micronaut.security.authentication.AuthenticationFailureReason
import io.micronaut.security.authentication.AuthenticationRequest
import io.micronaut.security.authentication.AuthenticationResponse
+import io.micronaut.security.authentication.provider.HttpRequestAuthenticationProvider
import io.seqera.wave.service.account.AccountService
import io.seqera.wave.util.StringUtils
import jakarta.inject.Inject
import jakarta.inject.Singleton
-import org.reactivestreams.Publisher
-import reactor.core.publisher.Flux
-import reactor.core.publisher.FluxSink
/**
* Basic Authentication provider
*
@@ -38,25 +37,22 @@ import reactor.core.publisher.FluxSink
*/
@Slf4j
@Singleton
-class BasicAuthenticationProvider implements AuthenticationProvider {
+class BasicAuthenticationProvider implements HttpRequestAuthenticationProvider {
@Inject
private AccountService accountService
@Override
- Publisher authenticate(@Nullable HttpRequest> httpRequest, AuthenticationRequest, ?> authRequest) {
- Flux.create(emitter -> {
- final user = authRequest.identity?.toString()
- final pass = authRequest.secret?.toString()
- if (accountService.isAuthorised(user, pass)) {
- log.trace "Auth request OK - user '$user'; password: '${StringUtils.redact(pass)}'"
- emitter.next(AuthenticationResponse.success((String) authRequest.identity))
- emitter.complete()
- }
- else {
- log.trace "Auth request FAILED - user '$user'; password: '${StringUtils.redact(pass)}'"
- emitter.error(AuthenticationResponse.exception())
- }
- }, FluxSink.OverflowStrategy.ERROR)
+ AuthenticationResponse authenticate(@Nullable HttpRequest httpRequest, @NonNull AuthenticationRequest authRequest) {
+ final user = authRequest.identity?.toString()
+ final pass = authRequest.secret?.toString()
+ if (accountService.isAuthorised(user, pass)) {
+ log.trace "Auth request OK - user '$user'; password: '${StringUtils.redact(pass)}'"
+ return AuthenticationResponse.success(authRequest.identity)
+ }
+ else {
+ log.trace "Auth request FAILED - user '$user'; password: '${StringUtils.redact(pass)}'"
+ return AuthenticationResponse.failure(AuthenticationFailureReason.CREDENTIALS_DO_NOT_MATCH)
+ }
}
}
diff --git a/src/main/groovy/io/seqera/wave/auth/RegistryAuthServiceImpl.groovy b/src/main/groovy/io/seqera/wave/auth/RegistryAuthServiceImpl.groovy
index d8ea12d26..7ae087db7 100644
--- a/src/main/groovy/io/seqera/wave/auth/RegistryAuthServiceImpl.groovy
+++ b/src/main/groovy/io/seqera/wave/auth/RegistryAuthServiceImpl.groovy
@@ -21,13 +21,12 @@ package io.seqera.wave.auth
import java.net.http.HttpRequest
import java.net.http.HttpResponse
import java.time.Duration
-import java.util.concurrent.ExecutionException
+import java.util.concurrent.CompletionException
import java.util.concurrent.TimeUnit
-import com.google.common.cache.CacheBuilder
-import com.google.common.cache.CacheLoader
-import com.google.common.cache.LoadingCache
-import com.google.common.util.concurrent.UncheckedExecutionException
+import com.github.benmanes.caffeine.cache.AsyncLoadingCache
+import com.github.benmanes.caffeine.cache.CacheLoader
+import com.github.benmanes.caffeine.cache.Caffeine
import groovy.json.JsonSlurper
import groovy.transform.Canonical
import groovy.transform.CompileStatic
@@ -101,11 +100,12 @@ class RegistryAuthServiceImpl implements RegistryAuthService {
return result
}
- private LoadingCache cacheTokens = CacheBuilder
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ private AsyncLoadingCache cacheTokens = Caffeine.newBuilder()
.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(_1_HOUR.toMillis(), TimeUnit.MILLISECONDS)
- .build(loader)
+ .buildAsync(loader)
@Inject
private RegistryLookupService lookupService
@@ -269,9 +269,10 @@ class RegistryAuthServiceImpl implements RegistryAuthService {
protected String getAuthToken(String image, RegistryAuth auth, RegistryCredentials creds) {
final key = new CacheKey(image, auth, creds)
try {
- return cacheTokens.get(key)
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ return cacheTokens.synchronous().get(key)
}
- catch (UncheckedExecutionException | ExecutionException e) {
+ catch (CompletionException e) {
// this catches the exception thrown in the cache loader lookup
// and throws the causing exception that should be `RegistryUnauthorizedAccessException`
throw e.cause
@@ -287,7 +288,8 @@ class RegistryAuthServiceImpl implements RegistryAuthService {
*/
void invalidateAuthorization(String image, RegistryAuth auth, RegistryCredentials creds) {
final key = new CacheKey(image, auth, creds)
- cacheTokens.invalidate(key)
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ cacheTokens.synchronous().invalidate(key)
tokenStore.remove(getStableKey(key))
}
diff --git a/src/main/groovy/io/seqera/wave/auth/RegistryConfig.groovy b/src/main/groovy/io/seqera/wave/auth/RegistryConfig.groovy
index eb43f3dad..560a784a5 100644
--- a/src/main/groovy/io/seqera/wave/auth/RegistryConfig.groovy
+++ b/src/main/groovy/io/seqera/wave/auth/RegistryConfig.groovy
@@ -47,7 +47,7 @@ class RegistryConfig {
* io: [ ... ]
* ]
*/
- private Map registries
+ Map registries
RegistryKeys getRegistryKeys(String registryName) {
final String defaultRegistry = registries.get('default')?.toString() ?: 'docker.io'
diff --git a/src/main/groovy/io/seqera/wave/auth/RegistryLookupServiceImpl.groovy b/src/main/groovy/io/seqera/wave/auth/RegistryLookupServiceImpl.groovy
index 0dc641879..2b15b8efa 100644
--- a/src/main/groovy/io/seqera/wave/auth/RegistryLookupServiceImpl.groovy
+++ b/src/main/groovy/io/seqera/wave/auth/RegistryLookupServiceImpl.groovy
@@ -20,13 +20,12 @@ package io.seqera.wave.auth
import java.net.http.HttpRequest
import java.net.http.HttpResponse
-import java.util.concurrent.ExecutionException
+import java.util.concurrent.CompletionException
import java.util.concurrent.TimeUnit
-import com.google.common.cache.CacheBuilder
-import com.google.common.cache.CacheLoader
-import com.google.common.cache.LoadingCache
-import com.google.common.util.concurrent.UncheckedExecutionException
+import com.github.benmanes.caffeine.cache.AsyncLoadingCache
+import com.github.benmanes.caffeine.cache.CacheLoader
+import com.github.benmanes.caffeine.cache.Caffeine
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.seqera.wave.configuration.HttpClientConfig
@@ -74,11 +73,12 @@ class RegistryLookupServiceImpl implements RegistryLookupService {
}
}
- private LoadingCache cache = CacheBuilder
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ private AsyncLoadingCache cache = Caffeine.newBuilder()
.newBuilder()
.maximumSize(10_000)
.expireAfterAccess(1, TimeUnit.HOURS)
- .build(loader)
+ .buildAsync(loader)
protected RegistryAuth lookup0(URI endpoint) {
final httpClient = HttpClientFactory.followRedirectsHttpClient()
@@ -117,10 +117,11 @@ class RegistryLookupServiceImpl implements RegistryLookupService {
RegistryInfo lookup(String registry) {
try {
final endpoint = registryEndpoint(registry)
- final auth = cache.get(endpoint)
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ final auth = cache.synchronous().get(endpoint)
return new RegistryInfo(registry, endpoint, auth)
}
- catch (UncheckedExecutionException | ExecutionException e) {
+ catch (CompletionException e) {
// this catches the exception thrown in the cache loader lookup
// and throws the causing exception that should be `RegistryUnauthorizedAccessException`
throw e.cause
diff --git a/src/main/groovy/io/seqera/wave/configuration/ScanConfig.groovy b/src/main/groovy/io/seqera/wave/configuration/ScanConfig.groovy
index a0f833f27..0ff20d39c 100644
--- a/src/main/groovy/io/seqera/wave/configuration/ScanConfig.groovy
+++ b/src/main/groovy/io/seqera/wave/configuration/ScanConfig.groovy
@@ -21,16 +21,14 @@ package io.seqera.wave.configuration
import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
-
-import io.micronaut.context.annotation.Requires
-import io.micronaut.core.annotation.Nullable
import javax.annotation.PostConstruct
import groovy.transform.CompileStatic
import groovy.transform.Memoized
import groovy.util.logging.Slf4j
+import io.micronaut.context.annotation.Requires
import io.micronaut.context.annotation.Value
-import io.seqera.wave.util.StringUtils
+import io.micronaut.core.annotation.Nullable
import jakarta.inject.Singleton
/**
* Container Scan service settings
@@ -83,8 +81,12 @@ class ScanConfig {
Duration scanIdDuration
@Nullable
- @Value('${wave.scan.github-token}')
- String githubToken
+ @Value('${wave.scan.environment}')
+ List environment
+
+ @Nullable
+ @Value('${wave.scan.vulnerability.limit:100}')
+ Integer vulnerabilityLimit
String getScanImage() {
return scanImage
@@ -93,7 +95,11 @@ class ScanConfig {
@Memoized
Path getCacheDirectory() {
final result = Path.of(buildDirectory).toAbsolutePath().resolve('.trivy-cache')
- Files.createDirectories(result)
+ try {
+ Files.createDirectories(result)
+ } catch (IOException e) {
+ log.error "Unable to create scan cache directory=${result} - cause: ${e.message}"
+ }
return result
}
@@ -118,8 +124,23 @@ class ScanConfig {
return severity
}
+ List> getEnvironmentAsTuples() {
+ if( !environment )
+ return List.of()
+ final result = new ArrayList>()
+ for( String entry : environment ) {
+ final p=entry.indexOf('=')
+ final name = p!=-1 ? entry.substring(0,p) : entry
+ final value = p!=-1 ? entry.substring(p+1) : ''
+ if( !value )
+ log.warn "Invalid 'wave.scan.environment' value -- offending entry: '$entry'"
+ result.add(new Tuple2(name,value))
+ }
+ return result
+ }
+
@PostConstruct
private void init() {
- log.info("Scanner config: docker image name: ${scanImage}; cache directory: ${cacheDirectory}; timeout=${timeout}; cpus: ${requestsCpu}; mem: ${requestsMemory}; severity: $severity; retry-attempts: $retryAttempts; github-token=${StringUtils.redact(githubToken)}")
+ log.info("Scan config: docker image name: ${scanImage}; cache directory: ${cacheDirectory}; timeout=${timeout}; cpus: ${requestsCpu}; mem: ${requestsMemory}; severity: $severity; vulnerability-limit: $vulnerabilityLimit; retry-attempts: $retryAttempts; env=${environment}")
}
}
diff --git a/src/main/groovy/io/seqera/wave/controller/BuildController.groovy b/src/main/groovy/io/seqera/wave/controller/BuildController.groovy
index 06679fa4a..f99549087 100644
--- a/src/main/groovy/io/seqera/wave/controller/BuildController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/BuildController.groovy
@@ -42,7 +42,7 @@ import jakarta.inject.Inject
@Slf4j
@CompileStatic
@Controller("/")
-@ExecuteOn(TaskExecutors.IO)
+@ExecuteOn(TaskExecutors.BLOCKING)
class BuildController {
@Inject
diff --git a/src/main/groovy/io/seqera/wave/controller/ContainerController.groovy b/src/main/groovy/io/seqera/wave/controller/ContainerController.groovy
index 713ffae89..a65568c40 100644
--- a/src/main/groovy/io/seqera/wave/controller/ContainerController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/ContainerController.groovy
@@ -29,6 +29,7 @@ import io.micronaut.context.annotation.Value
import io.micronaut.core.annotation.Nullable
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
+import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Delete
import io.micronaut.http.annotation.Error
@@ -102,7 +103,7 @@ import static java.util.concurrent.CompletableFuture.completedFuture
@Slf4j
@CompileStatic
@Controller("/")
-@ExecuteOn(TaskExecutors.IO)
+@ExecuteOn(TaskExecutors.BLOCKING)
class ContainerController {
@Inject
@@ -180,14 +181,14 @@ class ContainerController {
@Deprecated
@Post('/container-token')
- @ExecuteOn(TaskExecutors.IO)
- CompletableFuture> getToken(HttpRequest httpRequest, SubmitContainerTokenRequest req) {
+ @ExecuteOn(TaskExecutors.BLOCKING)
+ CompletableFuture> getToken(HttpRequest httpRequest, @Body SubmitContainerTokenRequest req) {
return getContainerImpl(httpRequest, req, false)
}
@Post('/v1alpha2/container')
- @ExecuteOn(TaskExecutors.IO)
- CompletableFuture> getTokenV2(HttpRequest httpRequest, SubmitContainerTokenRequest req) {
+ @ExecuteOn(TaskExecutors.BLOCKING)
+ CompletableFuture> getTokenV2(HttpRequest httpRequest, @Body SubmitContainerTokenRequest req) {
return getContainerImpl(httpRequest, req, true)
}
@@ -284,7 +285,7 @@ class ContainerController {
protected void storeContainerRequest0(SubmitContainerTokenRequest req, ContainerRequest data, TokenData token, String target, String ip) {
try {
final recrd = new WaveContainerRecord(req, data, target, ip, token.expiration)
- persistenceService.saveContainerRequest(recrd)
+ persistenceService.saveContainerRequestAsync(recrd)
}
catch (Throwable e) {
log.error("Unable to store container request with token: ${token}", e)
diff --git a/src/main/groovy/io/seqera/wave/controller/InspectController.groovy b/src/main/groovy/io/seqera/wave/controller/InspectController.groovy
index 88d66f9b8..6093d4dfb 100644
--- a/src/main/groovy/io/seqera/wave/controller/InspectController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/InspectController.groovy
@@ -25,6 +25,7 @@ import groovy.util.logging.Slf4j
import io.micronaut.context.annotation.Value
import io.micronaut.core.annotation.Nullable
import io.micronaut.http.HttpResponse
+import io.micronaut.http.annotation.Body
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.http.annotation.QueryValue
@@ -49,7 +50,7 @@ import static io.seqera.wave.util.ContainerHelper.patchPlatformEndpoint
@Slf4j
@CompileStatic
@Controller("/")
-@ExecuteOn(TaskExecutors.IO)
+@ExecuteOn(TaskExecutors.BLOCKING)
class InspectController {
@Inject
@@ -70,7 +71,7 @@ class InspectController {
private String serverUrl
@Post("/v1alpha1/inspect")
- CompletableFuture> inspect(ContainerInspectRequest req, @Nullable @QueryValue String platform) {
+ CompletableFuture> inspect(@Body ContainerInspectRequest req, @Nullable @QueryValue String platform) {
if( !req.containerImage )
throw new BadRequestException("Missing 'containerImage' attribute")
diff --git a/src/main/groovy/io/seqera/wave/controller/MetricsController.groovy b/src/main/groovy/io/seqera/wave/controller/MetricsController.groovy
index a9ce6f89a..f8008674e 100644
--- a/src/main/groovy/io/seqera/wave/controller/MetricsController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/MetricsController.groovy
@@ -52,7 +52,7 @@ import static io.micronaut.http.HttpHeaders.WWW_AUTHENTICATE
@Requires(property = 'wave.metrics.enabled', value = 'true')
@Secured(SecurityRule.IS_AUTHENTICATED)
@Controller
-@ExecuteOn(TaskExecutors.IO)
+@ExecuteOn(TaskExecutors.BLOCKING)
class MetricsController {
@Inject
diff --git a/src/main/groovy/io/seqera/wave/controller/MirrorController.groovy b/src/main/groovy/io/seqera/wave/controller/MirrorController.groovy
index 8aaf87a51..c9c3bc8f7 100644
--- a/src/main/groovy/io/seqera/wave/controller/MirrorController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/MirrorController.groovy
@@ -36,7 +36,7 @@ import jakarta.inject.Inject
@Slf4j
@CompileStatic
@Controller("/")
-@ExecuteOn(TaskExecutors.IO)
+@ExecuteOn(TaskExecutors.BLOCKING)
class MirrorController {
@Inject
diff --git a/src/main/groovy/io/seqera/wave/controller/RegistryProxyController.groovy b/src/main/groovy/io/seqera/wave/controller/RegistryProxyController.groovy
index ec6416537..263763299 100644
--- a/src/main/groovy/io/seqera/wave/controller/RegistryProxyController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/RegistryProxyController.groovy
@@ -67,7 +67,7 @@ import reactor.core.publisher.Mono
@Slf4j
@CompileStatic
@Controller("/v2")
-@ExecuteOn(TaskExecutors.IO)
+@ExecuteOn(TaskExecutors.BLOCKING)
class RegistryProxyController {
@Inject
diff --git a/src/main/groovy/io/seqera/wave/controller/ScanController.groovy b/src/main/groovy/io/seqera/wave/controller/ScanController.groovy
index 230a8b4a9..43fc38a5d 100644
--- a/src/main/groovy/io/seqera/wave/controller/ScanController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/ScanController.groovy
@@ -39,7 +39,7 @@ import jakarta.inject.Inject
@CompileStatic
@Requires(bean = ContainerScanService)
@Controller("/")
-@ExecuteOn(TaskExecutors.IO)
+@ExecuteOn(TaskExecutors.BLOCKING)
class ScanController {
@Inject
diff --git a/src/main/groovy/io/seqera/wave/controller/ServiceInfoController.groovy b/src/main/groovy/io/seqera/wave/controller/ServiceInfoController.groovy
index 207c6e8d5..7f0a895e4 100644
--- a/src/main/groovy/io/seqera/wave/controller/ServiceInfoController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/ServiceInfoController.groovy
@@ -39,7 +39,7 @@ import io.seqera.wave.util.BuildInfo
@Slf4j
@Controller("/")
@CompileStatic
-@ExecuteOn(TaskExecutors.IO)
+@ExecuteOn(TaskExecutors.BLOCKING)
class ServiceInfoController {
@Value('${wave.landing.url}')
diff --git a/src/main/groovy/io/seqera/wave/controller/ValidateController.groovy b/src/main/groovy/io/seqera/wave/controller/ValidateController.groovy
index 631d0bcc8..fd23fbdc6 100644
--- a/src/main/groovy/io/seqera/wave/controller/ValidateController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/ValidateController.groovy
@@ -18,27 +18,23 @@
package io.seqera.wave.controller
-import javax.validation.Valid
-
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Post
import io.micronaut.scheduling.TaskExecutors
import io.micronaut.scheduling.annotation.ExecuteOn
import io.seqera.wave.auth.RegistryAuthService
import jakarta.inject.Inject
-import reactor.core.publisher.Mono
+import jakarta.validation.Valid
-@ExecuteOn(TaskExecutors.IO)
@Controller("/validate-creds")
+@ExecuteOn(TaskExecutors.BLOCKING)
class ValidateController {
@Inject RegistryAuthService loginService
@Post
- Mono validateCreds(@Valid ValidateRegistryCredsRequest request){
- Mono.just(
- loginService.validateUser(request.registry, request.userName, request.password)
- )
+ Boolean validateCreds(@Valid ValidateRegistryCredsRequest request){
+ loginService.validateUser(request.registry, request.userName, request.password)
}
}
diff --git a/src/main/groovy/io/seqera/wave/controller/ValidateRegistryCredsRequest.groovy b/src/main/groovy/io/seqera/wave/controller/ValidateRegistryCredsRequest.groovy
index db465ef0a..3fbe92d07 100644
--- a/src/main/groovy/io/seqera/wave/controller/ValidateRegistryCredsRequest.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/ValidateRegistryCredsRequest.groovy
@@ -18,10 +18,8 @@
package io.seqera.wave.controller
-import io.micronaut.core.annotation.Nullable
-import javax.validation.constraints.NotBlank
-
import io.micronaut.core.annotation.Introspected
+import jakarta.validation.constraints.NotBlank
@Introspected
class ValidateRegistryCredsRequest {
diff --git a/src/main/groovy/io/seqera/wave/controller/ViewController.groovy b/src/main/groovy/io/seqera/wave/controller/ViewController.groovy
index 1fafad4b4..68328eb36 100644
--- a/src/main/groovy/io/seqera/wave/controller/ViewController.groovy
+++ b/src/main/groovy/io/seqera/wave/controller/ViewController.groovy
@@ -20,6 +20,7 @@ package io.seqera.wave.controller
import java.util.regex.Pattern
+import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.micronaut.context.annotation.Value
@@ -45,6 +46,7 @@ import io.seqera.wave.service.persistence.WaveBuildRecord
import io.seqera.wave.service.persistence.WaveScanRecord
import io.seqera.wave.service.scan.ContainerScanService
import io.seqera.wave.service.scan.ScanEntry
+import io.seqera.wave.service.scan.ScanVulnerability
import io.seqera.wave.util.JacksonHelper
import jakarta.inject.Inject
import static io.seqera.wave.util.DataTimeUtils.formatDuration
@@ -57,7 +59,7 @@ import static io.seqera.wave.util.DataTimeUtils.formatTimestamp
@Slf4j
@CompileStatic
@Controller("/view")
-@ExecuteOn(TaskExecutors.IO)
+@ExecuteOn(TaskExecutors.BLOCKING)
class ViewController {
@Inject
@@ -221,7 +223,7 @@ class ViewController {
binding.scan_url = result.scanId && result.succeeded() ? "$serverUrl/view/scans/${result.scanId}" : null
binding.scan_id = result.scanId
// inspect uri
- binding.inspect_url = "$serverUrl/view/inspect?image=${result.targetImage}&platform=${result.platform}"
+ binding.inspect_url = result.succeeded() ? "$serverUrl/view/inspect?image=${result.targetImage}&platform=${result.platform}" : null
// configure build logs when available
if( buildLogService ) {
final buildLog = buildLogService.fetchLogString(result.buildId)
@@ -408,7 +410,10 @@ class ViewController {
}
Map makeScanViewBinding(WaveScanRecord result, Map binding=new HashMap(10)) {
+ final color = getScanColor(result.vulnerabilities)
binding.should_refresh = !result.done()
+ binding.scan_color_bg = color.background
+ binding.scan_color_fg = color.foreground
binding.scan_id = result.id
binding.scan_container_image = result.containerImage ?: '-'
binding.scan_platform = result.platform?.toString() ?: '-'
@@ -437,4 +442,24 @@ class ViewController {
return binding
}
+ @Canonical
+ static class Colour {
+ final background
+ final foreground
+ }
+
+ protected static Colour getScanColor(List vulnerabilities){
+ boolean hasMedium = vulnerabilities.stream()
+ .anyMatch(v -> v.severity.equals("MEDIUM"))
+ boolean hasHighOrCritical = vulnerabilities.stream()
+ .anyMatch(v -> v.severity.equals("HIGH") || v.severity.equals("CRITICAL"))
+ if(hasHighOrCritical){
+ return new Colour('#ffe4e2', '#e00404')
+ }
+ else if(hasMedium){
+ return new Colour('#fff8c5', "#000000")
+ }
+ return new Colour('#dff0d8', '#3c763d')
+ }
+
}
diff --git a/src/main/groovy/io/seqera/wave/core/ContainerAugmenter.groovy b/src/main/groovy/io/seqera/wave/core/ContainerAugmenter.groovy
index 909cc8d5d..bd3837e87 100644
--- a/src/main/groovy/io/seqera/wave/core/ContainerAugmenter.groovy
+++ b/src/main/groovy/io/seqera/wave/core/ContainerAugmenter.groovy
@@ -280,7 +280,7 @@ class ContainerAugmenter {
return result
}
- synchronized protected Map layerBlob(String image, ContainerLayer layer) {
+ protected Map layerBlob(String image, ContainerLayer layer) {
log.debug "Adding layer: $layer to image: $client.registry.name/$image"
// store the layer blob in the cache
final String path = "$client.registry.name/v2/$image/blobs/$layer.gzipDigest"
@@ -295,7 +295,6 @@ class ContainerAugmenter {
protected Tuple2 updateImageManifest(String imageName, String imageManifest, String newImageConfigDigest, newImageConfigSize, boolean oci) {
-
// turn the json string into a json map
// and append the new layer
final manifest = (Map) new JsonSlurper().parseText(imageManifest)
diff --git a/src/main/groovy/io/seqera/wave/core/RegistryProxyService.groovy b/src/main/groovy/io/seqera/wave/core/RegistryProxyService.groovy
index 1d9bf61de..01ed6bd94 100644
--- a/src/main/groovy/io/seqera/wave/core/RegistryProxyService.groovy
+++ b/src/main/groovy/io/seqera/wave/core/RegistryProxyService.groovy
@@ -18,6 +18,8 @@
package io.seqera.wave.core
+import java.util.concurrent.CompletableFuture
+
import groovy.transform.CompileStatic
import groovy.transform.ToString
import groovy.util.logging.Slf4j
@@ -133,7 +135,7 @@ class RegistryProxyService {
return
try {
- persistenceService.updateContainerRequest(route.token, digest)
+ persistenceService.updateContainerRequestAsync(route.token, digest)
} catch (Throwable t) {
log.error("Unable store container request for token: $route.token", t)
}
@@ -193,7 +195,7 @@ class RegistryProxyService {
String getImageDigest(String containerImage, PlatformId identity, boolean retryOnNotFound=false) {
try {
- return getImageDigest0(containerImage, identity, retryOnNotFound)
+ return getImageDigest0(containerImage, identity, retryOnNotFound).get()
}
catch(Exception e) {
log.warn "Unable to retrieve digest for image '${containerImage}' -- cause: ${e.message}"
@@ -203,8 +205,15 @@ class RegistryProxyService {
static private List RETRY_ON_NOT_FOUND = HTTP_RETRYABLE_ERRORS + 404
+ // note: return a CompletableFuture to force micronaut to use caffeine AsyncCache
+ // that provides a workaround about the use of virtual threads with SyncCache
+ // see https://github.com/ben-manes/caffeine/issues/1468#issuecomment-1906733926
@Cacheable(value = 'cache-registry-proxy', atomic = true, parameters = ['image'])
- protected String getImageDigest0(String image, PlatformId identity, boolean retryOnNotFound) {
+ protected CompletableFuture getImageDigest0(String image, PlatformId identity, boolean retryOnNotFound) {
+ CompletableFuture.completedFuture(getImageDigest1(image, identity, retryOnNotFound))
+ }
+
+ protected String getImageDigest1(String image, PlatformId identity, boolean retryOnNotFound) {
final coords = ContainerCoordinates.parse(image)
final route = RoutePath.v2manifestPath(coords, identity)
final proxyClient = client(route)
diff --git a/src/main/groovy/io/seqera/wave/exchange/PairingRequest.groovy b/src/main/groovy/io/seqera/wave/exchange/PairingRequest.groovy
index b54ac876d..fb17c68ec 100644
--- a/src/main/groovy/io/seqera/wave/exchange/PairingRequest.groovy
+++ b/src/main/groovy/io/seqera/wave/exchange/PairingRequest.groovy
@@ -18,11 +18,10 @@
package io.seqera.wave.exchange
-import javax.validation.constraints.NotBlank
-import javax.validation.constraints.NotNull
-
import groovy.transform.CompileStatic
import io.micronaut.core.annotation.Introspected
+import jakarta.validation.constraints.NotBlank
+import jakarta.validation.constraints.NotNull
/**
* Model the request for a remote service instance to register
diff --git a/src/main/groovy/io/seqera/wave/filter/PullMetricsRequestsFilter.groovy b/src/main/groovy/io/seqera/wave/filter/PullMetricsRequestsFilter.groovy
index da19fc7c6..bb70548ac 100644
--- a/src/main/groovy/io/seqera/wave/filter/PullMetricsRequestsFilter.groovy
+++ b/src/main/groovy/io/seqera/wave/filter/PullMetricsRequestsFilter.groovy
@@ -83,10 +83,10 @@ class PullMetricsRequestsFilter implements HttpServerFilter {
final contentType = response.headers.get(HttpHeaders.CONTENT_TYPE)
if( contentType && contentType in MANIFEST_TYPES ) {
final route = routeHelper.parse(request.path)
- CompletableFuture.supplyAsync(() -> metricsService.incrementPullsCounter(route.identity), executor)
+ CompletableFuture.runAsync(() -> metricsService.incrementPullsCounter(route.identity), executor)
final version = route.request?.containerConfig?.fusionVersion()
if (version) {
- CompletableFuture.supplyAsync(() -> metricsService.incrementFusionPullsCounter(route.identity), executor)
+ CompletableFuture.runAsync(() -> metricsService.incrementFusionPullsCounter(route.identity), executor)
}
}
}
diff --git a/src/main/groovy/io/seqera/wave/http/HttpClientFactory.groovy b/src/main/groovy/io/seqera/wave/http/HttpClientFactory.groovy
index f1301037e..46c9730d2 100644
--- a/src/main/groovy/io/seqera/wave/http/HttpClientFactory.groovy
+++ b/src/main/groovy/io/seqera/wave/http/HttpClientFactory.groovy
@@ -22,6 +22,7 @@ import java.net.http.HttpClient
import java.time.Duration
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
+import java.util.concurrent.locks.ReentrantLock
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
@@ -39,9 +40,9 @@ class HttpClientFactory {
static private Duration timeout = Duration.ofSeconds(20)
- static private final Object l1 = new Object()
+ static private final ReentrantLock l1 = new ReentrantLock()
- static private final Object l2 = new Object()
+ static private final ReentrantLock l2 = new ReentrantLock()
private static HttpClient client1
@@ -51,20 +52,26 @@ class HttpClientFactory {
static HttpClient followRedirectsHttpClient() {
if( client1!=null )
return client1
- synchronized (l1) {
+ l1.lock()
+ try {
if( client1!=null )
return client1
return client1=followRedirectsHttpClient0()
+ } finally {
+ l1.unlock()
}
}
static HttpClient neverRedirectsHttpClient() {
if( client2!=null )
return client2
- synchronized (l2) {
+ l2.lock()
+ try {
if( client2!=null )
return client2
return client2=neverRedirectsHttpClient0()
+ } finally {
+ l2.unlock()
}
}
diff --git a/src/main/groovy/io/seqera/wave/proxy/ErrResponse.groovy b/src/main/groovy/io/seqera/wave/proxy/ErrResponse.groovy
index 7283fb205..5905f539e 100644
--- a/src/main/groovy/io/seqera/wave/proxy/ErrResponse.groovy
+++ b/src/main/groovy/io/seqera/wave/proxy/ErrResponse.groovy
@@ -90,17 +90,17 @@ class ErrResponse implements HttpResponse {
}
static ErrResponse forString(String msg, HttpRequest request) {
- final head = HttpHeaders.of('Content-Type': ['text/plain'], {true})
+ final head = HttpHeaders.of('Content-Type': ['text/plain'], (a, b) -> true)
new ErrResponse(statusCode: 400, body: msg, request: request, uri: request.uri(), headers: head)
}
static ErrResponse forStream(String msg, HttpRequest request) {
- final head = HttpHeaders.of('Content-Type': ['text/plain'], {true})
+ final head = HttpHeaders.of('Content-Type': ['text/plain'], (a, b) -> true)
new ErrResponse(statusCode: 400, body: new ByteArrayInputStream(msg.bytes), request: request, uri: request.uri(), headers: head)
}
static ErrResponse forByteArray(String msg, HttpRequest request) {
- final head = HttpHeaders.of('Content-Type': ['text/plain'], {true})
+ final head = HttpHeaders.of('Content-Type': ['text/plain'], (a, b) -> true)
new ErrResponse(statusCode: 400, body: msg.bytes, request: request, uri: request.uri(), headers: head)
}
}
diff --git a/src/main/groovy/io/seqera/wave/ratelimit/impl/SpillWayStorageFactory.groovy b/src/main/groovy/io/seqera/wave/ratelimit/impl/SpillWayStorageFactory.groovy
index c7431642f..cc6f9d036 100644
--- a/src/main/groovy/io/seqera/wave/ratelimit/impl/SpillWayStorageFactory.groovy
+++ b/src/main/groovy/io/seqera/wave/ratelimit/impl/SpillWayStorageFactory.groovy
@@ -18,8 +18,6 @@
package io.seqera.wave.ratelimit.impl
-import javax.validation.constraints.NotNull
-
import com.coveo.spillway.storage.InMemoryStorage
import com.coveo.spillway.storage.LimitUsageStorage
import com.coveo.spillway.storage.RedisStorage
@@ -27,9 +25,9 @@ import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.micronaut.context.annotation.Factory
import io.micronaut.context.annotation.Requires
-import io.seqera.wave.configuration.RateLimiterConfig
import io.seqera.wave.configuration.RedisConfig
import jakarta.inject.Singleton
+import jakarta.validation.constraints.NotNull
import redis.clients.jedis.JedisPool
/**
diff --git a/src/main/groovy/io/seqera/wave/ratelimit/impl/SpillwayRateLimiter.groovy b/src/main/groovy/io/seqera/wave/ratelimit/impl/SpillwayRateLimiter.groovy
index 358e951dd..e2c9eab60 100644
--- a/src/main/groovy/io/seqera/wave/ratelimit/impl/SpillwayRateLimiter.groovy
+++ b/src/main/groovy/io/seqera/wave/ratelimit/impl/SpillwayRateLimiter.groovy
@@ -18,8 +18,6 @@
package io.seqera.wave.ratelimit.impl
-import javax.validation.constraints.NotNull
-
import com.coveo.spillway.Spillway
import com.coveo.spillway.SpillwayFactory
import com.coveo.spillway.limit.Limit
@@ -34,6 +32,7 @@ import io.seqera.wave.exception.SlowDownException
import io.seqera.wave.ratelimit.AcquireRequest
import io.seqera.wave.ratelimit.RateLimiterService
import jakarta.inject.Singleton
+import jakarta.validation.constraints.NotNull
/**
* This class manage how many requests can be requested from an user during a configurable period
*
@@ -60,7 +59,7 @@ class SpillwayRateLimiter implements RateLimiterService {
init(storage, config)
}
- protected void init(@NotNull LimitUsageStorage storage, @NotNull RateLimiterConfig config){
+ protected void init(LimitUsageStorage storage, RateLimiterConfig config){
SpillwayFactory spillwayFactory = new SpillwayFactory(storage)
initBuilds(spillwayFactory, config)
initPulls(spillwayFactory, config)
diff --git a/src/main/groovy/io/seqera/wave/service/account/AccountServiceImpl.groovy b/src/main/groovy/io/seqera/wave/service/account/AccountServiceImpl.groovy
index 3cccda9ab..f8d6bb4f1 100644
--- a/src/main/groovy/io/seqera/wave/service/account/AccountServiceImpl.groovy
+++ b/src/main/groovy/io/seqera/wave/service/account/AccountServiceImpl.groovy
@@ -36,7 +36,7 @@ import jakarta.annotation.PostConstruct
@ConfigurationProperties('wave')
class AccountServiceImpl implements AccountService {
- private Map accounts = Map.of()
+ Map accounts = Map.of()
@PostConstruct
private dumpAccounts() {
diff --git a/src/main/groovy/io/seqera/wave/service/aws/AwsEcrService.groovy b/src/main/groovy/io/seqera/wave/service/aws/AwsEcrService.groovy
index 17fdb3720..30f9d0e04 100644
--- a/src/main/groovy/io/seqera/wave/service/aws/AwsEcrService.groovy
+++ b/src/main/groovy/io/seqera/wave/service/aws/AwsEcrService.groovy
@@ -21,9 +21,9 @@ package io.seqera.wave.service.aws
import java.util.concurrent.TimeUnit
import java.util.regex.Pattern
-import com.google.common.cache.CacheBuilder
-import com.google.common.cache.CacheLoader
-import com.google.common.cache.LoadingCache
+import com.github.benmanes.caffeine.cache.AsyncLoadingCache
+import com.github.benmanes.caffeine.cache.CacheLoader
+import com.github.benmanes.caffeine.cache.Caffeine
import groovy.transform.Canonical
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
@@ -73,11 +73,12 @@ class AwsEcrService {
}
}
- private LoadingCache cache = CacheBuilder
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ private AsyncLoadingCache cache = Caffeine.newBuilder()
.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(3, TimeUnit.HOURS)
- .build(loader)
+ .buildAsync(loader)
private EcrClient ecrClient(String accessKey, String secretKey, String region) {
@@ -126,7 +127,8 @@ class AwsEcrService {
try {
// get the token from the cache, if missing the it's automatically
// fetch using the AWS ECR client
- return cache.get(new AwsCreds(accessKey,secretKey,region,isPublic))
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ return cache.synchronous().get(new AwsCreds(accessKey,secretKey,region,isPublic))
}
catch (Exception e) {
final type = isPublic ? "ECR public" : "ECR"
diff --git a/src/main/groovy/io/seqera/wave/service/builder/impl/ContainerBuildServiceImpl.groovy b/src/main/groovy/io/seqera/wave/service/builder/impl/ContainerBuildServiceImpl.groovy
index 0d36875d5..895a90210 100644
--- a/src/main/groovy/io/seqera/wave/service/builder/impl/ContainerBuildServiceImpl.groovy
+++ b/src/main/groovy/io/seqera/wave/service/builder/impl/ContainerBuildServiceImpl.groovy
@@ -29,7 +29,6 @@ import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.micronaut.context.event.ApplicationEventPublisher
import io.micronaut.core.annotation.Nullable
-import io.micronaut.runtime.event.annotation.EventListener
import io.micronaut.scheduling.TaskExecutors
import io.seqera.wave.api.BuildContext
import io.seqera.wave.auth.RegistryCredentialsProvider
@@ -191,8 +190,7 @@ class ContainerBuildServiceImpl implements ContainerBuildService, JobHandler metricsService.incrementBuildsCounter(request.identity), executor)
+ CompletableFuture
+ .runAsync(() -> metricsService.incrementBuildsCounter(request.identity), executor)
// launch the build async
CompletableFuture
@@ -326,16 +325,14 @@ class ContainerBuildServiceImpl implements ContainerBuildService, JobHandler implements Runnable {
final private String name0
- final private Cache closedClients = CacheBuilder
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ final private AsyncCache closedClients = Caffeine.newBuilder()
.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES)
- .build()
+ .buildAsync()
AbstractMessageQueue(MessageQueue broker) {
final type = TypeHelper.getGenericType(this, 0)
@@ -149,13 +150,15 @@ abstract class AbstractMessageQueue implements Runnable {
@Override
void run() {
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ final clientsCache = closedClients.synchronous()
while( !thread.isInterrupted() ) {
try {
int sent=0
final clients = new HashMap>(this.clients)
for( Map.Entry> entry : clients ) {
// ignore clients marked as closed
- if( closedClients.getIfPresent(entry.key))
+ if( clientsCache.getIfPresent(entry.key))
continue
// infer the target queue from the client key
final target = targetFromClientKey(entry.key)
@@ -173,7 +176,7 @@ abstract class AbstractMessageQueue implements Runnable {
// offer back the value to be processed again
broker.offer(target, value)
if( e.message?.contains('close') ) {
- closedClients.put(entry.key, true)
+ clientsCache.put(entry.key, true)
}
}
}
diff --git a/src/main/groovy/io/seqera/wave/service/data/stream/AbstractMessageStream.groovy b/src/main/groovy/io/seqera/wave/service/data/stream/AbstractMessageStream.groovy
index 560a81306..ae16d7140 100644
--- a/src/main/groovy/io/seqera/wave/service/data/stream/AbstractMessageStream.groovy
+++ b/src/main/groovy/io/seqera/wave/service/data/stream/AbstractMessageStream.groovy
@@ -98,6 +98,10 @@ abstract class AbstractMessageStream implements Closeable {
* The {@link Predicate} to be invoked when a stream message is consumed (read from) the stream.
*/
void addConsumer(String streamId, MessageConsumer consumer) {
+ // the use of synchronized block is meant to prevent a race condition while
+ // updating the 'listeners' from concurrent invocations.
+ // however, considering the addConsumer is invoked during the initialization phase
+ // (and therefore in the same thread) in should not be really needed.
synchronized (listeners) {
if( listeners.containsKey(streamId))
throw new IllegalStateException("Only one consumer can be defined for each stream - offending streamId=$streamId; consumer=$consumer")
diff --git a/src/main/groovy/io/seqera/wave/service/data/stream/impl/RedisMessageStream.groovy b/src/main/groovy/io/seqera/wave/service/data/stream/impl/RedisMessageStream.groovy
index 609b71061..c417568b3 100644
--- a/src/main/groovy/io/seqera/wave/service/data/stream/impl/RedisMessageStream.groovy
+++ b/src/main/groovy/io/seqera/wave/service/data/stream/impl/RedisMessageStream.groovy
@@ -62,6 +62,9 @@ class RedisMessageStream implements MessageStream {
@Value('${wave.message-stream.claim-timeout:5s}')
private Duration claimTimeout
+ @Value('${wave.message-stream.consume-warn-timeout-millis:4000}')
+ private long consumeWarnTimeoutMillis
+
private String consumerName
@PostConstruct
@@ -102,11 +105,17 @@ class RedisMessageStream implements MessageStream {
@Override
boolean consume(String streamId, MessageConsumer consumer) {
try (Jedis jedis = pool.getResource()) {
+ String msg
+ final long begin = System.currentTimeMillis()
final entry = claimMessage(jedis,streamId) ?: readMessage(jedis, streamId)
- if( entry && consumer.accept(entry.getFields().get(DATA_FIELD)) ) {
+ if( entry && consumer.accept(msg=entry.getFields().get(DATA_FIELD)) ) {
final tx = jedis.multi()
// acknowledge the entry has been processed so that it cannot be claimed anymore
tx.xack(streamId, CONSUMER_GROUP_NAME, entry.getID())
+ final delta = System.currentTimeMillis()-begin
+ if( delta>consumeWarnTimeoutMillis ) {
+ log.warn "Redis message stream - consume processing took ${Duration.ofMillis(delta)} - offending entry=${entry.getID()}; message=${msg}"
+ }
// this remove permanently the entry from the stream
tx.xdel(streamId, entry.getID())
tx.exec()
diff --git a/src/main/groovy/io/seqera/wave/service/job/JobManager.groovy b/src/main/groovy/io/seqera/wave/service/job/JobManager.groovy
index 1c4ec6c03..e10cde8d4 100644
--- a/src/main/groovy/io/seqera/wave/service/job/JobManager.groovy
+++ b/src/main/groovy/io/seqera/wave/service/job/JobManager.groovy
@@ -21,6 +21,7 @@ package io.seqera.wave.service.job
import java.time.Duration
import java.time.Instant
+import com.github.benmanes.caffeine.cache.AsyncCache
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import groovy.transform.CompileStatic
@@ -50,16 +51,19 @@ class JobManager {
@Inject
private JobConfig config
- private Cache debounceCache
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ private AsyncCache debounceCache
@PostConstruct
void init() {
log.info "Creating job manager - config=$config"
- debounceCache = Caffeine.newBuilder().expireAfterWrite(config.graceInterval.multipliedBy(2)).build()
+ debounceCache = Caffeine
+ .newBuilder()
+ .expireAfterWrite(config.graceInterval.multipliedBy(2))
+ .buildAsync()
queue.addConsumer((job)-> processJob(job))
}
-
protected boolean processJob(JobSpec jobSpec) {
try {
return processJob0(jobSpec)
@@ -73,7 +77,8 @@ class JobManager {
}
protected JobState state(JobSpec job) {
- return state0(job, config.graceInterval, debounceCache)
+ // FIXME https://github.com/seqeralabs/wave/issues/747
+ return state0(job, config.graceInterval, debounceCache.synchronous())
}
protected JobState state0(final JobSpec job, final Duration graceInterval, final Cache cache) {
diff --git a/src/main/groovy/io/seqera/wave/service/k8s/K8sService.groovy b/src/main/groovy/io/seqera/wave/service/k8s/K8sService.groovy
index 0d8de9015..ccaedec07 100644
--- a/src/main/groovy/io/seqera/wave/service/k8s/K8sService.groovy
+++ b/src/main/groovy/io/seqera/wave/service/k8s/K8sService.groovy
@@ -23,11 +23,9 @@ import java.time.Duration
import io.kubernetes.client.openapi.models.V1Job
import io.kubernetes.client.openapi.models.V1Pod
-import io.kubernetes.client.openapi.models.V1PodList
import io.seqera.wave.configuration.BlobCacheConfig
-import io.seqera.wave.configuration.ScanConfig
import io.seqera.wave.configuration.MirrorConfig
-
+import io.seqera.wave.configuration.ScanConfig
/**
* Defines Kubernetes operations
*
@@ -43,23 +41,6 @@ interface K8sService {
void deletePod(String name)
- @Deprecated
- V1Pod buildContainer(String name, String containerImage, List args, Path workDir, Path creds, Duration timeout, Map nodeSelector)
-
- @Deprecated
- V1Pod scanContainer(String name, String containerImage, List args, Path workDir, Path creds, ScanConfig scanConfig, Map nodeSelector)
-
- @Deprecated
- Integer waitPodCompletion(V1Pod pod, long timeout)
-
- @Deprecated
- void deletePodWhenReachStatus(String podName, String statusName, long timeout)
-
- @Deprecated
- V1Job createJob(String name, String containerImage, List args)
-
- V1Job getJob(String name)
-
JobStatus getJobStatus(String name)
void deleteJob(String name)
@@ -72,9 +53,6 @@ interface K8sService {
V1Job launchMirrorJob(String name, String containerImage, List args, Path workDir, Path creds, MirrorConfig config)
- @Deprecated
- V1PodList waitJob(V1Job job, Long timeout)
-
V1Pod getLatestPodForJob(String jobName)
}
diff --git a/src/main/groovy/io/seqera/wave/service/k8s/K8sServiceImpl.groovy b/src/main/groovy/io/seqera/wave/service/k8s/K8sServiceImpl.groovy
index ac46c912d..c954794c2 100644
--- a/src/main/groovy/io/seqera/wave/service/k8s/K8sServiceImpl.groovy
+++ b/src/main/groovy/io/seqera/wave/service/k8s/K8sServiceImpl.groovy
@@ -22,7 +22,6 @@ import java.nio.file.Path
import java.time.Duration
import javax.annotation.PostConstruct
-import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import io.kubernetes.client.custom.Quantity
@@ -36,7 +35,6 @@ import io.kubernetes.client.openapi.models.V1JobStatus
import io.kubernetes.client.openapi.models.V1PersistentVolumeClaimVolumeSource
import io.kubernetes.client.openapi.models.V1Pod
import io.kubernetes.client.openapi.models.V1PodBuilder
-import io.kubernetes.client.openapi.models.V1PodList
import io.kubernetes.client.openapi.models.V1ResourceRequirements
import io.kubernetes.client.openapi.models.V1Volume
import io.kubernetes.client.openapi.models.V1VolumeMount
@@ -46,9 +44,9 @@ import io.micronaut.context.annotation.Value
import io.micronaut.core.annotation.Nullable
import io.seqera.wave.configuration.BlobCacheConfig
import io.seqera.wave.configuration.BuildConfig
+import io.seqera.wave.configuration.MirrorConfig
import io.seqera.wave.configuration.ScanConfig
import io.seqera.wave.core.ContainerPlatform
-import io.seqera.wave.configuration.MirrorConfig
import io.seqera.wave.service.scan.Trivy
import jakarta.inject.Inject
import jakarta.inject.Singleton
@@ -136,61 +134,6 @@ class K8sServiceImpl implements K8sService {
}
}
- /**
- * Create a K8s job with the specified name
- *
- * @param name
- * The K8s job name. It must be unique
- * @param containerImage
- * The container image to be used to run the job
- * @param args
- * The command to be executed by the job
- * @return
- * An instance of {@link V1Job}
- */
- @Override
- @CompileDynamic
- @Deprecated
- V1Job createJob(String name, String containerImage, List args) {
-
- V1Job body = new V1JobBuilder()
- .withNewMetadata()
- .withNamespace(namespace)
- .withName(name)
- .endMetadata()
- .withNewSpec()
- .withBackoffLimit(0)
- .withNewTemplate()
- .editOrNewSpec()
- .addNewContainer()
- .withName(name)
- .withImage(containerImage)
- .withArgs(args)
- .endContainer()
- .withRestartPolicy("Never")
- .endSpec()
- .endTemplate()
- .endSpec()
- .build()
-
- return k8sClient
- .batchV1Api()
- .createNamespacedJob(namespace, body, null, null, null,null)
- }
-
- /**
- * Get a Jobs Job.
- *
- * @param name The job name
- * @return An instance of {@link V1Job}
- */
- @Override
- V1Job getJob(String name) {
- k8sClient
- .batchV1Api()
- .readNamespacedJob(name, namespace, null)
- }
-
/**
* Get a Job status
*
@@ -307,31 +250,7 @@ class K8sServiceImpl implements K8sService {
.subPath(rel)
}
- /**
- * Create a container for container image building via buildkit
- *
- * @param name
- * The name of pod
- * @param containerImage
- * The container image to be used
- * @param args
- * The build command to be performed
- * @param workDir
- * The build context directory
- * @param creds
- * The target container repository credentials
- * @return
- * The {@link V1Pod} description the submitted pod
- */
- @Override
@Deprecated
- V1Pod buildContainer(String name, String containerImage, List args, Path workDir, Path creds, Duration timeout, Map nodeSelector) {
- final spec = buildSpec(name, containerImage, args, workDir, creds, timeout, nodeSelector)
- return k8sClient
- .coreV1Api()
- .createNamespacedPod(namespace, spec, null, null, null,null)
- }
-
V1Pod buildSpec(String name, String containerImage, List args, Path workDir, Path credsFile, Duration timeout, Map nodeSelector) {
// dirty dependency to avoid introducing another parameter
@@ -408,47 +327,6 @@ class K8sServiceImpl implements K8sService {
builder.build()
}
- /**
- * Wait for a pod a completion.
- *
- * NOTE: this method assumes the pod is running exactly *one* container.
- *
- * @param pod
- * The pod name
- * @param timeout
- * Max wait time in milliseconds
- * @return
- * An Integer value representing the container exit code or {@code null} if the state cannot be determined
- * or timeout was reached.
- */
- @Override
- @Deprecated
- Integer waitPodCompletion(V1Pod pod, long timeout) {
- final start = System.currentTimeMillis()
- // wait for termination
- while( true ) {
- final phase = pod.status?.phase
- if( phase && phase != 'Pending' ) {
- final status = pod.status.containerStatuses.first()
- if( !status )
- return null
- if( !status.state )
- return null
- if( status.state.terminated ) {
- return status.state.terminated.exitCode
- }
- }
-
- if( phase == 'Failed' )
- return null
- final delta = System.currentTimeMillis()-start
- if( delta > timeout )
- return null
- sleep 5_000
- pod = getPod(pod.metadata.name)
- }
- }
-
/**
* Fetch the logs of a pod.
*
@@ -481,36 +359,6 @@ class K8sServiceImpl implements K8sService {
.deleteNamespacedPod(name, namespace, (String)null, (String)null, (Integer)null, (Boolean)null, (String)null, (V1DeleteOptions)null)
}
- /**
- * Delete a pod where the status is reached
- *
- * @param name The name of the pod to be deleted
- * @param statusName The status to be reached
- * @param timeout The max wait time in milliseconds
- */
- @Override
- @Deprecated
- void deletePodWhenReachStatus(String podName, String statusName, long timeout){
- final pod = getPod(podName)
- final start = System.currentTimeMillis()
- while( (System.currentTimeMillis() - start) < timeout ) {
- if( pod?.status?.phase == statusName ) {
- deletePod(podName)
- return
- }
- sleep 5_000
- }
- }
-
- @Override
- @Deprecated
- V1Pod scanContainer(String name, String containerImage, List args, Path workDir, Path creds, ScanConfig scanConfig, Map nodeSelector) {
- final spec = scanSpec(name, containerImage, args, workDir, creds, scanConfig, nodeSelector)
- return k8sClient
- .coreV1Api()
- .createNamespacedPod(namespace, spec, null, null, null,null)
- }
-
@Deprecated
V1Pod scanSpec(String name, String containerImage, List args, Path workDir, Path credsFile, ScanConfig scanConfig, Map nodeSelector) {
@@ -784,8 +632,11 @@ class K8sServiceImpl implements K8sService {
.withVolumeMounts(mounts)
.withResources(requests)
- if( scanConfig.githubToken ) {
- container.withEnv(new V1EnvVar().name('GITHUB_TOKEN').value(scanConfig.githubToken))
+ final env = scanConfig.environmentAsTuples
+ for( Tuple2 entry : env ) {
+ final String k = entry.v1
+ final String v = entry.v2
+ container.addToEnv(new V1EnvVar().name(k).value(v))
}
// spec section
@@ -862,33 +713,6 @@ class K8sServiceImpl implements K8sService {
return result
}
- /**
- * Wait for a job to complete
- *
- * @param k8s job
- * @param timeout
- * Max wait time in milliseconds
- * @return list of pods created by the job
- */
- @Deprecated
- @Override
- V1PodList waitJob(V1Job job, Long timeout) {
- sleep 5_000
- final startTime = System.currentTimeMillis()
- // wait for termination
- while (System.currentTimeMillis() - startTime < timeout) {
- final name = job.metadata.name
- final status = getJobStatus(name)
- if (status != JobStatus.Pending) {
- return k8sClient
- .coreV1Api()
- .listNamespacedPod(namespace, null, null, null, null, "job-name=$name", null, null, null, null, null, null)
- }
- job = getJob(name)
- }
- return null
- }
-
/**
* Delete a job
*
diff --git a/src/main/groovy/io/seqera/wave/service/mirror/ContainerMirrorServiceImpl.groovy b/src/main/groovy/io/seqera/wave/service/mirror/ContainerMirrorServiceImpl.groovy
index 8d061a86e..1126fe87d 100644
--- a/src/main/groovy/io/seqera/wave/service/mirror/ContainerMirrorServiceImpl.groovy
+++ b/src/main/groovy/io/seqera/wave/service/mirror/ContainerMirrorServiceImpl.groovy
@@ -75,7 +75,7 @@ class ContainerMirrorServiceImpl implements ContainerMirrorService, JobHandler metricsService.incrementMirrorsCounter(request.identity), ioExecutor)
+ CompletableFuture.runAsync(() -> metricsService.incrementMirrorsCounter(request.identity), ioExecutor)
jobService.launchMirror(request)
return new BuildTrack(request.mirrorId, request.targetImage, false, null)
}
@@ -124,9 +124,14 @@ class ContainerMirrorServiceImpl implements ContainerMirrorService, JobHandler mirrorStore = new HashMap<>()
@Override
- void saveBuild(WaveBuildRecord record) {
+ void saveBuildAsync(WaveBuildRecord record) {
buildStore[record.buildId] = record
}
@@ -76,12 +76,12 @@ class LocalPersistenceService implements PersistenceService {
}
@Override
- void saveContainerRequest(WaveContainerRecord data) {
+ void saveContainerRequestAsync(WaveContainerRecord data) {
requestStore.put(data.id, data)
}
@Override
- void updateContainerRequest(String token, ContainerDigestPair digest) {
+ void updateContainerRequestAsync(String token, ContainerDigestPair digest) {
final data = requestStore.get(token)
if( data ) {
requestStore.put(token, new WaveContainerRecord(data, digest.source, digest.target))
@@ -99,7 +99,7 @@ class LocalPersistenceService implements PersistenceService {
}
@Override
- void saveScanRecord(WaveScanRecord scanRecord) {
+ void saveScanRecordAsync(WaveScanRecord scanRecord) {
scanStore.put(scanRecord.id, scanRecord)
}
@@ -129,7 +129,7 @@ class LocalPersistenceService implements PersistenceService {
}
@Override
- void saveMirrorResult(MirrorResult mirror) {
+ void saveMirrorResultAsync(MirrorResult mirror) {
mirrorStore.put(mirror.mirrorId, mirror)
}
diff --git a/src/main/groovy/io/seqera/wave/service/persistence/impl/SurrealClient.groovy b/src/main/groovy/io/seqera/wave/service/persistence/impl/SurrealClient.groovy
index badd7c4aa..2901ec22f 100644
--- a/src/main/groovy/io/seqera/wave/service/persistence/impl/SurrealClient.groovy
+++ b/src/main/groovy/io/seqera/wave/service/persistence/impl/SurrealClient.groovy
@@ -54,9 +54,15 @@ interface SurrealClient {
@Post("/sql")
Flux