diff --git a/modules/nextflow/src/main/groovy/nextflow/fusion/FusionConfig.groovy b/modules/nextflow/src/main/groovy/nextflow/fusion/FusionConfig.groovy index 7caeaa4638..25e50d01ba 100644 --- a/modules/nextflow/src/main/groovy/nextflow/fusion/FusionConfig.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/fusion/FusionConfig.groovy @@ -56,6 +56,8 @@ class FusionConfig implements ConfigScope { final static private Pattern VERSION_JSON = ~/https:\/\/.*\/releases\/v(\d+(?:\.\w+)*)-(\w*)\.json$/ + final static private Pattern VERSION_PATTERN = ~/v(\d+(?:\.\w+)*)/ + @ConfigOption @Description(""" Enable the Fusion file system (default: `false`). @@ -110,6 +112,8 @@ class FusionConfig implements ConfigScope { """) final String tags + final String targetVersion + boolean enabled() { enabled } boolean exportStorageCredentials() { @@ -149,6 +153,7 @@ class FusionConfig implements ConfigScope { this.privileged = opts.privileged == null || opts.privileged as boolean this.cacheSize = opts.cacheSize as MemoryUnit this.snapshots = opts.snapshots as boolean + this.targetVersion = opts.targetVersion as String if( containerConfigUrl && !validProtocol(containerConfigUrl)) throw new IllegalArgumentException("Fusion container config URL should start with 'http:' or 'https:' protocol prefix - offending value: $containerConfigUrl") @@ -201,7 +206,25 @@ class FusionConfig implements ConfigScope { String version() { return enabled - ? retrieveFusionVersion(this.containerConfigUrl ?: DEFAULT_FUSION_AMD64_URL) + ? retrieveFusionVersion(this.containerConfigUrl ?: targetFusionUrl(DEFAULT_FUSION_AMD64_URL)) : null } + + /** + * Replace the version in a Fusion URL with the specified version. + * + * @param url The original URL e.g. {@code https://fusionfs.seqera.io/releases/v2.5-amd64.json} + * @param version The target version e.g. {@code 2.6} + * @return The URL with the version replaced e.g. {@code https://fusionfs.seqera.io/releases/v2.6-amd64.json} + */ + static String replaceVersion(String url, String version) { + return url.replaceFirst(VERSION_PATTERN.pattern(), "v${version}") + } + + /** + * Resolve the default Fusion URL, applying the {@code targetVersion} override if set. + */ + String targetFusionUrl(String url) { + return targetVersion ? replaceVersion(url, targetVersion) : url + } } diff --git a/modules/nextflow/src/test/groovy/nextflow/fusion/FusionConfigTest.groovy b/modules/nextflow/src/test/groovy/nextflow/fusion/FusionConfigTest.groovy index 305d63b40f..6997e2b37a 100644 --- a/modules/nextflow/src/test/groovy/nextflow/fusion/FusionConfigTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/fusion/FusionConfigTest.groovy @@ -151,4 +151,42 @@ class FusionConfigTest extends Specification { 'https://foo.com/releases/v4.0-amd64.json' | true | '4.0' 'https://foo.com/releases/v4.0.1-amd64.json' | true | '4.0.1' } + + @Unroll + def 'should replace version in url' () { + expect: + FusionConfig.replaceVersion(FUSION_URL, VER) == EXPECTED + where: + FUSION_URL | VER | EXPECTED + 'https://fusionfs.seqera.io/releases/v2.5-amd64.json' | '2.6' | 'https://fusionfs.seqera.io/releases/v2.6-amd64.json' + 'https://fusionfs.seqera.io/releases/v2.5-arm64.json' | '2.6' | 'https://fusionfs.seqera.io/releases/v2.6-arm64.json' + 'https://fusionfs.seqera.io/releases/v2.5-snap_amd64.json' | '2.6' | 'https://fusionfs.seqera.io/releases/v2.6-snap_amd64.json' + 'https://fusionfs.seqera.io/releases/v2.5-snap_arm64.json' | '2.6' | 'https://fusionfs.seqera.io/releases/v2.6-snap_arm64.json' + 'https://fusionfs.seqera.io/releases/v2.5-amd64.json' | '3.0' | 'https://fusionfs.seqera.io/releases/v3.0-amd64.json' + } + + @Unroll + def 'should resolve default fusion url with version override' () { + given: + def config = new FusionConfig([targetVersion: TARGET_VER]) + expect: + config.targetFusionUrl(FUSION_URL) == EXPECTED + where: + FUSION_URL | TARGET_VER | EXPECTED + FusionConfig.DEFAULT_FUSION_AMD64_URL | null | FusionConfig.DEFAULT_FUSION_AMD64_URL + FusionConfig.DEFAULT_FUSION_AMD64_URL | '2.6' | 'https://fusionfs.seqera.io/releases/v2.6-amd64.json' + FusionConfig.DEFAULT_FUSION_ARM64_URL | '2.6' | 'https://fusionfs.seqera.io/releases/v2.6-arm64.json' + FusionConfig.DEFAULT_SNAPSHOT_AMD64_URL | '2.6' | 'https://fusionfs.seqera.io/releases/v2.6-snap_amd64.json' + FusionConfig.DEFAULT_SNAPSHOT_ARM64_URL | '2.6' | 'https://fusionfs.seqera.io/releases/v2.6-snap_arm64.json' + } + + def 'should get version with targetVersion override' () { + expect: + new FusionConfig([enabled:ENABLED, targetVersion:TARGET_VER]).version() == EXPECTED + where: + ENABLED | TARGET_VER | EXPECTED + false | '2.6' | null + true | null | '2.5' + true | '2.6' | '2.6' + } } diff --git a/plugins/nf-seqera/src/main/io/seqera/executor/SeqeraExecutor.groovy b/plugins/nf-seqera/src/main/io/seqera/executor/SeqeraExecutor.groovy index 45ace53bfb..55090b3524 100644 --- a/plugins/nf-seqera/src/main/io/seqera/executor/SeqeraExecutor.groovy +++ b/plugins/nf-seqera/src/main/io/seqera/executor/SeqeraExecutor.groovy @@ -56,6 +56,8 @@ class SeqeraExecutor extends Executor implements ExtensionPoint { public static final String SEQERA = 'seqera' + private static final String DEFAULT_FUSION_VERSION = '2.6' + private ExecutorOpts seqeraConfig private SchedClient client @@ -66,9 +68,17 @@ class SeqeraExecutor extends Executor implements ExtensionPoint { @Override protected void register() { + applyFusionDefaults() createClient() } + protected void applyFusionDefaults() { + final fusionConfig = session.config.fusion as Map + if( fusionConfig!=null && !fusionConfig.containerConfigUrl ) { + fusionConfig.put('targetVersion', DEFAULT_FUSION_VERSION) + } + } + @Override void shutdown() { // Flush any pending batch jobs before terminating run diff --git a/plugins/nf-seqera/src/test/io/seqera/executor/SeqeraExecutorTest.groovy b/plugins/nf-seqera/src/test/io/seqera/executor/SeqeraExecutorTest.groovy index 7ee3095688..9b564a555c 100644 --- a/plugins/nf-seqera/src/test/io/seqera/executor/SeqeraExecutorTest.groovy +++ b/plugins/nf-seqera/src/test/io/seqera/executor/SeqeraExecutorTest.groovy @@ -18,6 +18,7 @@ package io.seqera.executor import io.seqera.config.SeqeraConfig import io.seqera.sched.client.SchedClientConfig +import nextflow.Session import nextflow.SysEnv import nextflow.platform.PlatformHelper import spock.lang.Specification @@ -109,6 +110,36 @@ class SeqeraExecutorTest extends Specification { config.refreshToken == 'config-refresh-token' } + def 'should set fusion default version when not configured' () { + given: + SysEnv.push([:]) + def fusionConfig = [enabled: true] + def config = [fusion: fusionConfig] + def session = Mock(Session) { getConfig() >> config } + def executor = new SeqeraExecutor(session: session) + + when: + executor.applyFusionDefaults() + + then: + fusionConfig.targetVersion == '2.6' + } + + def 'should not override fusion version when containerConfigUrl is set' () { + given: + SysEnv.push([:]) + def fusionConfig = [enabled: true, containerConfigUrl: 'https://custom.url/v3.0-amd64.json'] + def config = [fusion: fusionConfig] + def session = Mock(Session) { getConfig() >> config } + def executor = new SeqeraExecutor(session: session) + + when: + executor.applyFusionDefaults() + + then: + fusionConfig.targetVersion == null + } + /** * Builds a SchedClientConfig using the same logic as {@link SeqeraExecutor#createClient()} */ diff --git a/plugins/nf-wave/src/main/io/seqera/wave/plugin/WaveClient.groovy b/plugins/nf-wave/src/main/io/seqera/wave/plugin/WaveClient.groovy index a155de90cd..6788f4368b 100644 --- a/plugins/nf-wave/src/main/io/seqera/wave/plugin/WaveClient.groovy +++ b/plugins/nf-wave/src/main/io/seqera/wave/plugin/WaveClient.groovy @@ -383,15 +383,17 @@ class WaveClient { } protected URL fusionAmd64(boolean snapshots) { - return snapshots - ? URI.create(FusionConfig.DEFAULT_SNAPSHOT_AMD64_URL).toURL() - : URI.create(FusionConfig.DEFAULT_FUSION_AMD64_URL).toURL() + final url = snapshots + ? FusionConfig.DEFAULT_SNAPSHOT_AMD64_URL + : FusionConfig.DEFAULT_FUSION_AMD64_URL + return URI.create(fusion.targetFusionUrl(url)).toURL() } protected URL fusionArm64(boolean snapshots) { - return snapshots - ? URI.create(FusionConfig.DEFAULT_SNAPSHOT_ARM64_URL).toURL() - : URI.create(FusionConfig.DEFAULT_FUSION_ARM64_URL).toURL() + final url = snapshots + ? FusionConfig.DEFAULT_SNAPSHOT_ARM64_URL + : FusionConfig.DEFAULT_FUSION_ARM64_URL + return URI.create(fusion.targetFusionUrl(url)).toURL() } protected URL defaultS5cmdUrl(String platform) { diff --git a/plugins/nf-wave/src/test/io/seqera/wave/plugin/WaveClientTest.groovy b/plugins/nf-wave/src/test/io/seqera/wave/plugin/WaveClientTest.groovy index a011ebcc2a..21aab4020a 100644 --- a/plugins/nf-wave/src/test/io/seqera/wave/plugin/WaveClientTest.groovy +++ b/plugins/nf-wave/src/test/io/seqera/wave/plugin/WaveClientTest.groovy @@ -976,6 +976,25 @@ class WaveClientTest extends Specification { 'linux/arm64' | true | 'https://fusionfs.seqera.io/releases/v2.5-snap_arm64.json' } + @Unroll + def 'should get fusion default url with version override' () { + given: + def sess = Mock(Session) {getConfig() >> [fusion:[snapshots:SNAP, targetVersion:'2.6']] } + and: + def wave = Spy(new WaveClient(sess)) + + expect: + wave.defaultFusionUrl(ARCH).toURI().toString() == EXPECTED + + where: + ARCH | SNAP | EXPECTED + 'linux/amd64' | null | 'https://fusionfs.seqera.io/releases/v2.6-amd64.json' + 'linux/arm64' | null | 'https://fusionfs.seqera.io/releases/v2.6-arm64.json' + and: + 'linux/amd64' | true | 'https://fusionfs.seqera.io/releases/v2.6-snap_amd64.json' + 'linux/arm64' | true | 'https://fusionfs.seqera.io/releases/v2.6-snap_arm64.json' + } + @Unroll def 'should get s5cmd default url' () { given: