From c12ed8570e9b55e037009a917f4bd9c32f3d2718 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Thu, 24 Aug 2023 15:23:25 +0200 Subject: [PATCH 1/9] Add dry-run option Signed-off-by: Paolo Di Tommaso --- .../wave/api/SubmitContainerTokenRequest.java | 13 +++++++++++++ .../wave/api/SubmitContainerTokenRequestTest.groovy | 13 ++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/wave-api/src/main/java/io/seqera/wave/api/SubmitContainerTokenRequest.java b/wave-api/src/main/java/io/seqera/wave/api/SubmitContainerTokenRequest.java index 2af3718..73de946 100644 --- a/wave-api/src/main/java/io/seqera/wave/api/SubmitContainerTokenRequest.java +++ b/wave-api/src/main/java/io/seqera/wave/api/SubmitContainerTokenRequest.java @@ -109,6 +109,11 @@ public class SubmitContainerTokenRequest implements Cloneable { */ public String format; + /** + * When {@code true} build requests are carried out in dry-run mode. + */ + public Boolean dryRun; + public SubmitContainerTokenRequest copyWith(Map opts) { try { final SubmitContainerTokenRequest copy = (SubmitContainerTokenRequest) this.clone(); @@ -146,6 +151,8 @@ public SubmitContainerTokenRequest copyWith(Map opts) { copy.buildContext = (BuildContext) opts.get("buildContext"); if( opts.containsKey("format") ) copy.format = (String) opts.get("format"); + if( opts.containsKey("dryRun") ) + copy.dryRun = (Boolean) opts.get("dryRun"); // done return copy; } @@ -244,6 +251,11 @@ public SubmitContainerTokenRequest withFormat(String value) { return this; } + public SubmitContainerTokenRequest withDryRun(Boolean dryRun) { + this.dryRun = dryRun; + return this; + } + public boolean formatSingularity() { return "sif".equals(format); } @@ -268,6 +280,7 @@ public String toString() { ", freeze=" + freeze + ", buildContext=" + buildContext + ", type=" + format + + ", dryRun=" + dryRun + '}'; } } diff --git a/wave-api/src/test/groovy/io/seqera/wave/api/SubmitContainerTokenRequestTest.groovy b/wave-api/src/test/groovy/io/seqera/wave/api/SubmitContainerTokenRequestTest.groovy index 25ec321..f7e00dc 100644 --- a/wave-api/src/test/groovy/io/seqera/wave/api/SubmitContainerTokenRequestTest.groovy +++ b/wave-api/src/test/groovy/io/seqera/wave/api/SubmitContainerTokenRequestTest.groovy @@ -37,7 +37,8 @@ class SubmitContainerTokenRequestTest extends Specification { timestamp: 'a13', fingerprint: 'a14', freeze: true, - format: 'sif' + format: 'sif', + dryRun: true ) when: @@ -59,7 +60,9 @@ class SubmitContainerTokenRequestTest extends Specification { copy.fingerprint == req.fingerprint copy.freeze == req.freeze copy.format == req.format - + copy.dryRun == req.dryRun + and: + copy.formatSingularity() when: def other = req.copyWith( @@ -78,7 +81,8 @@ class SubmitContainerTokenRequestTest extends Specification { timestamp: 'b13', fingerprint: 'b14', freeze: false, - format: 'foo' + format: 'foo', + dryRun: false ) then: other.towerAccessToken == 'b1' @@ -97,6 +101,9 @@ class SubmitContainerTokenRequestTest extends Specification { other.fingerprint == 'b14' other.freeze == false other.format == 'foo' + other.dryRun == false + and: + !other.formatSingularity() } } From 4a653981f839186b111cae1fc4c6ae43768b2321 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Thu, 24 Aug 2023 15:23:46 +0200 Subject: [PATCH 2/9] [release] bump wave-api@0.5.0 Signed-off-by: Paolo Di Tommaso --- wave-api/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wave-api/VERSION b/wave-api/VERSION index 1d0ba9e..8f0916f 100644 --- a/wave-api/VERSION +++ b/wave-api/VERSION @@ -1 +1 @@ -0.4.0 +0.5.0 From 81edea799fe805dcfa3b7db83e052a4bab0ee931 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Sat, 26 Aug 2023 10:19:45 +0200 Subject: [PATCH 3/9] Add condaFileFromPackages helper fun Signed-off-by: Paolo Di Tommaso --- .../io/seqera/wave/util/DockerHelper.java | 142 ++++++++++- .../seqera/wave/util/DockerHelperTest.groovy | 236 +++++++++++++++++- 2 files changed, 369 insertions(+), 9 deletions(-) diff --git a/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java b/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java index f0b1b79..ff7231b 100644 --- a/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java +++ b/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java @@ -30,7 +30,9 @@ import io.seqera.wave.config.CondaOpts; import io.seqera.wave.config.SpackOpts; import org.apache.commons.lang3.StringUtils; +import org.yaml.snakeyaml.DumperOptions; import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.representer.Representer; /** * Helper class to create Dockerfile for Conda and Spack package managers @@ -39,6 +41,130 @@ */ public class DockerHelper { + /** + * Create a Conda environment file starting from one or more Conda package names + * + * @param packages + * A string listing or more Conda package names separated with a blank character + * e.g. {@code samtools=1.0 bedtools=2.0} + * @param condaChannels + * A list of Conda channels + * @param opts + * An instance of {@link CondaOpts} object holding the options for the Conda environment. + * @return + * A path to the Conda environment YAML file. The file is automatically deleted then the JVM exit. + */ + static public Path condaFileFromPackages(String packages, List condaChannels, CondaOpts opts) { + final String yaml = condaPackagesToCondaYaml(packages, condaChannels, opts); + if (yaml == null || yaml.length() == 0) + return null; + return toYamlTempFile(yaml); + } + + static List condaPackagesToList(String packages) { + if (packages == null || packages.isEmpty()) + return null; + return Arrays.asList(packages.split(" ")); + } + + static String condaPackagesToCondaYaml(String packages, List channels, CondaOpts opts) { + final List base = condaPackagesToList(opts.basePackages); + final List custom = condaPackagesToList(packages); + if (base == null && custom == null) + return null; + + final List deps = new ArrayList<>(); + if (custom != null) + deps.addAll(custom); + if (base != null) + deps.addAll(base); + + final Map conda = new LinkedHashMap<>(); + if (channels != null && channels.size() > 0) { + conda.put("channels", channels); + } + conda.put("dependencies", deps); + + return dumpCondaYaml(conda); + } + + static private String dumpCondaYaml(Map conda) { + DumperOptions dumperOpts = new DumperOptions(); + dumperOpts.setPrettyFlow(false); // Disable pretty formatting + dumperOpts.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); // Use block style + return new Yaml(new Representer(dumperOpts), dumperOpts).dump(conda); + } + + /** + * Get a Conda environment file from a string path. + * + * @param condaFile + * A file system path where the Conda environment file is located. + * @param channels + * A list of Conda channels. If provided the channels are added to the ones + * specified in the Conda environment files. + * @param opts + * An instance of {@link CondaOpts} holding the options for the Conda environment. + * @return + * A {@link Path} to the Conda environment file. It can be the same file as specified + * via the condaFile argument or a temporary file if the environment was modified due to + * the channels or options specified. + */ + public static Path condaFileFromPath(String condaFile, List channels, CondaOpts opts) { + if( StringUtils.isEmpty(condaFile) ) + throw new IllegalArgumentException("Argument 'condaFile' cannot be empty"); + + final Path condaEnvPath = Path.of(condaFile); + + // make sure the file exists + if( !Files.exists(condaEnvPath) ) { + throw new IllegalArgumentException("The specified Conda environment file cannot be found: " + condaFile); + } + + // if there's nothing to be marged just return the conda file path + if( StringUtils.isEmpty(opts.basePackages) && channels==null ) { + return condaEnvPath; + } + + // => parse the conda file yaml, add the base packages to it + final Yaml yaml = new Yaml(); + try { + // 1. parse the file + Map root = yaml.load(new FileReader(condaFile)); + // 2. parse the base packages + final List base = condaPackagesToList(opts.basePackages); + // 3. append to the specs + if( base!=null ) { + List dependencies0 = (List)root.get("dependencies"); + if( dependencies0==null ) { + dependencies0 = new ArrayList<>(); + root.put("dependencies", dependencies0); + } + for( String it : base ) { + if( !dependencies0.contains(it) ) + dependencies0.add(it); + } + } + // 4. append channels + if( channels!=null ) { + List channels0 = (List)root.get("channels"); + if( channels0==null ) { + channels0 = new ArrayList<>(); + root.put("channels", channels0); + } + for( String it : channels ) { + if( !channels0.contains(it) ) + channels0.add(it); + } + } + // 5. return it as a new temp file + return toYamlTempFile( dumpCondaYaml(root) ); + } + catch (FileNotFoundException e) { + throw new IllegalArgumentException("The specified Conda environment file cannot be found: " + condaFile, e); + } + } + static public List spackPackagesToList(String packages) { if( packages==null || packages.isEmpty() ) return null; @@ -94,19 +220,19 @@ static public Path spackPackagesToSpackFile(String packages, SpackOpts opts) { final String yaml = spackPackagesToSpackYaml(packages, opts); if( yaml==null || yaml.length()==0 ) return null; - return toYamlFile(yaml); + return toYamlTempFile(yaml); } - static private Path toYamlFile(String yaml) { + static private Path toYamlTempFile(String yaml) { try { - final File tempFile = File.createTempFile("nf-spack", ".yaml"); + final File tempFile = File.createTempFile("nf-temp", ".yaml"); tempFile.deleteOnExit(); final Path result = tempFile.toPath(); Files.write(result, yaml.getBytes()); return result; } catch (IOException e) { - throw new IllegalStateException("Unable to write temporary Spack environment file - Reason: " + e.getMessage(), e); + throw new IllegalStateException("Unable to write temporary file - Reason: " + e.getMessage(), e); } } @@ -125,6 +251,7 @@ static private Map spackBinding(SpackOpts opts) { return binding; } + @Deprecated static public String condaPackagesToDockerFile(String packages, List condaChannels, CondaOpts opts) { return condaPackagesTemplate0( "/templates/conda/dockerfile-conda-packages.txt", @@ -133,6 +260,7 @@ static public String condaPackagesToDockerFile(String packages, List con opts); } + @Deprecated static public String condaPackagesToSingularityFile(String packages, List condaChannels, CondaOpts opts) { return condaPackagesTemplate0( "/templates/conda/singularityfile-conda-packages.txt", @@ -241,7 +369,7 @@ public static Path addPackagesToSpackFile(String spackFile, SpackOpts opts) { // make sure the file exists if( !Files.exists(spackEnvPath) ) { - throw new IllegalArgumentException("The specific Spack environment file cannot be found: " + spackFile); + throw new IllegalArgumentException("The specified Spack environment file cannot be found: " + spackFile); } // Case C - if not base packages are given just return the spack file as a path @@ -269,10 +397,10 @@ public static Path addPackagesToSpackFile(String spackFile, SpackOpts opts) { } specs.addAll(base); // 5. return it as a new temp file - return toYamlFile( yaml.dump(data) ); + return toYamlTempFile( yaml.dump(data) ); } catch (FileNotFoundException e) { - throw new IllegalArgumentException("The specific Spack environment file cannot be found: " + spackFile, e); + throw new IllegalArgumentException("The specified Spack environment file cannot be found: " + spackFile, e); } } diff --git a/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy b/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy index 6c88979..ab199d4 100644 --- a/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy +++ b/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy @@ -11,19 +11,251 @@ package io.seqera.wave.util -import spock.lang.Specification import java.nio.file.Files import io.seqera.wave.config.CondaOpts import io.seqera.wave.config.SpackOpts - +import spock.lang.Specification /** * * @author Paolo Di Tommaso */ class DockerHelperTest extends Specification { + def 'should create conda yaml file' () { + expect: + DockerHelper.condaPackagesToCondaYaml('foo=1.0 bar=2.0', null, new CondaOpts()) + == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + '''.stripIndent(true) + + and: + DockerHelper.condaPackagesToCondaYaml('foo=1.0 bar=2.0', null, new CondaOpts(basePackages: 'alpha=0.1 omega=0.9')) + == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + - alpha=0.1 + - omega=0.9 + '''.stripIndent(true) + + and: + DockerHelper.condaPackagesToCondaYaml(null, null, new CondaOpts(basePackages: 'alpha=0.1 omega=0.9')) + == '''\ + dependencies: + - alpha=0.1 + - omega=0.9 + '''.stripIndent(true) + + and: + DockerHelper.condaPackagesToCondaYaml('foo=1.0 bar=2.0', ['channel_a','channel_b'], new CondaOpts()) + == '''\ + channels: + - channel_a + - channel_b + dependencies: + - foo=1.0 + - bar=2.0 + '''.stripIndent(true) + + } + + def 'should add conda packages to conda file /1' () { + given: + def condaFile = Files.createTempFile('conda','yaml') + condaFile.text = '''\ + dependencies: + - foo=1.0 + - bar=2.0 + '''.stripIndent(true) + + when: + def result = DockerHelper.condaFileFromPath(condaFile.toString(), null, new CondaOpts()) + then: + result.text == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + '''.stripIndent(true) + + when: + result = DockerHelper.condaFileFromPath(condaFile.toString(), ['ch1', 'ch2'], new CondaOpts()) + then: + result.text == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + channels: + - ch1 + - ch2 + '''.stripIndent(true) + + when: + result = DockerHelper.condaFileFromPath(condaFile.toString(), null, new CondaOpts(basePackages: 'foo=1.0 alpha=1 omega=2')) + then: + result.text == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + - alpha=1 + - omega=2 + '''.stripIndent(true) + + + when: + result = DockerHelper.condaFileFromPath(condaFile.toString(), ['bioconda'], new CondaOpts(basePackages: 'alpha=1 omega=2')) + then: + result.text == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + - alpha=1 + - omega=2 + channels: + - bioconda + '''.stripIndent(true) + + + cleanup: + if( condaFile ) Files.delete(condaFile) + } + + def 'should add conda packages to conda file /2' () { + given: + def condaFile = Files.createTempFile('conda', 'yaml') + condaFile.text = '''\ + dependencies: + - foo=1.0 + - bar=2.0 + channels: + - hola + - ciao + '''.stripIndent(true) + + when: + def result = DockerHelper.condaFileFromPath(condaFile.toString(), null, new CondaOpts()) + then: + result.text == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + channels: + - hola + - ciao + '''.stripIndent(true) + + when: + result = DockerHelper.condaFileFromPath(condaFile.toString(), ['ch1', 'ch2'], new CondaOpts()) + then: + result.text == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + channels: + - hola + - ciao + - ch1 + - ch2 + '''.stripIndent(true) + + when: + result = DockerHelper.condaFileFromPath(condaFile.toString(), null, new CondaOpts(basePackages: 'foo=1.0 alpha=1 omega=2')) + then: + result.text == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + - alpha=1 + - omega=2 + channels: + - hola + - ciao + '''.stripIndent(true) + + + when: + result = DockerHelper.condaFileFromPath(condaFile.toString(), ['bioconda'], new CondaOpts(basePackages: 'alpha=1 omega=2')) + then: + result.text == '''\ + dependencies: + - foo=1.0 + - bar=2.0 + - alpha=1 + - omega=2 + channels: + - hola + - ciao + - bioconda + '''.stripIndent(true) + + + cleanup: + if (condaFile) Files.delete(condaFile) + } + + def 'should add conda packages to conda file /3' () { + given: + def condaFile = Files.createTempFile('conda', 'yaml') + condaFile.text = '''\ + channels: + - hola + - ciao + '''.stripIndent(true) + + when: + def result = DockerHelper.condaFileFromPath(condaFile.toString(), null, new CondaOpts()) + then: + result.text == '''\ + channels: + - hola + - ciao + '''.stripIndent(true) + + when: + result = DockerHelper.condaFileFromPath(condaFile.toString(), ['ch1', 'ch2'], new CondaOpts()) + then: + result.text == '''\ + channels: + - hola + - ciao + - ch1 + - ch2 + '''.stripIndent(true) + + when: + result = DockerHelper.condaFileFromPath(condaFile.toString(), null, new CondaOpts(basePackages: 'foo=1.0 alpha=1 omega=2')) + then: + result.text == '''\ + channels: + - hola + - ciao + dependencies: + - foo=1.0 + - alpha=1 + - omega=2 + '''.stripIndent(true) + + + when: + result = DockerHelper.condaFileFromPath(condaFile.toString(), ['bioconda'], new CondaOpts(basePackages: 'alpha=1 omega=2')) + then: + result.text == '''\ + channels: + - hola + - ciao + - bioconda + dependencies: + - alpha=1 + - omega=2 + '''.stripIndent(true) + + cleanup: + if (condaFile) Files.delete(condaFile) + } + def 'should create dockerfile content from conda file' () { given: def CONDA_OPTS = new CondaOpts([basePackages: 'conda-forge::procps-ng']) From 5755132373625e4e971cc179eb3c6b52e9322ef1 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Sat, 26 Aug 2023 10:20:45 +0200 Subject: [PATCH 4/9] [release] Bump wave-utils@0.7.0 Signed-off-by: Paolo Di Tommaso --- wave-utils/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wave-utils/VERSION b/wave-utils/VERSION index b616048..faef31a 100644 --- a/wave-utils/VERSION +++ b/wave-utils/VERSION @@ -1 +1 @@ -0.6.2 +0.7.0 From 24c4a9d276ac17aa1542a246e8a7eb9733157f12 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Sat, 26 Aug 2023 16:44:08 +0200 Subject: [PATCH 5/9] Undeprecate Conda helpers Signed-off-by: Paolo Di Tommaso --- wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java b/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java index ff7231b..4031a45 100644 --- a/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java +++ b/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java @@ -251,7 +251,6 @@ static private Map spackBinding(SpackOpts opts) { return binding; } - @Deprecated static public String condaPackagesToDockerFile(String packages, List condaChannels, CondaOpts opts) { return condaPackagesTemplate0( "/templates/conda/dockerfile-conda-packages.txt", @@ -260,7 +259,6 @@ static public String condaPackagesToDockerFile(String packages, List con opts); } - @Deprecated static public String condaPackagesToSingularityFile(String packages, List condaChannels, CondaOpts opts) { return condaPackagesTemplate0( "/templates/conda/singularityfile-conda-packages.txt", From 3e73854ad6a8fe9226a817c77b1b3231c5970f0e Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Sat, 26 Aug 2023 16:44:39 +0200 Subject: [PATCH 6/9] Fix Conda file build for Singularity Signed-off-by: Paolo Di Tommaso --- .../templates/conda/singularityfile-conda-file.txt | 4 ++-- .../groovy/io/seqera/wave/util/DockerHelperTest.groovy | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/wave-utils/src/main/resources/templates/conda/singularityfile-conda-file.txt b/wave-utils/src/main/resources/templates/conda/singularityfile-conda-file.txt index d09c77f..0255a03 100644 --- a/wave-utils/src/main/resources/templates/conda/singularityfile-conda-file.txt +++ b/wave-utils/src/main/resources/templates/conda/singularityfile-conda-file.txt @@ -1,9 +1,9 @@ BootStrap: docker From: {{base_image}} %files - {{wave_context_dir}}/conda.yml /tmp/conda.yml + {{wave_context_dir}}/conda.yml /scratch/conda.yml %post - micromamba install -y -n base -f /tmp/conda.yml \ + micromamba install -y -n base -f /scratch/conda.yml \ {{base_packages}} && micromamba clean -a -y %environment diff --git a/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy b/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy index ab199d4..f2a4c68 100644 --- a/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy +++ b/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy @@ -617,9 +617,9 @@ class DockerHelperTest extends Specification { BootStrap: docker From: mambaorg/micromamba:1.4.9 %files - {{wave_context_dir}}/conda.yml /tmp/conda.yml + {{wave_context_dir}}/conda.yml /scratch/conda.yml %post - micromamba install -y -n base -f /tmp/conda.yml \\ + micromamba install -y -n base -f /scratch/conda.yml \\ && micromamba install -y -n base conda-forge::procps-ng \\ && micromamba clean -a -y %environment @@ -634,9 +634,9 @@ class DockerHelperTest extends Specification { BootStrap: docker From: mambaorg/micromamba:1.4.9 %files - {{wave_context_dir}}/conda.yml /tmp/conda.yml + {{wave_context_dir}}/conda.yml /scratch/conda.yml %post - micromamba install -y -n base -f /tmp/conda.yml \\ + micromamba install -y -n base -f /scratch/conda.yml \\ && micromamba clean -a -y %environment export PATH="$MAMBA_ROOT_PREFIX/bin:$PATH" From 90000a25dadb230c080bb6479d900c07975fb3d8 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Sat, 26 Aug 2023 16:45:23 +0200 Subject: [PATCH 7/9] [release] bump wave-utils@0.7.1 Signed-off-by: Paolo Di Tommaso --- wave-utils/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wave-utils/VERSION b/wave-utils/VERSION index faef31a..39e898a 100644 --- a/wave-utils/VERSION +++ b/wave-utils/VERSION @@ -1 +1 @@ -0.7.0 +0.7.1 From f15a5d02fd1244d930cff50ae664760267653f2a Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Sat, 2 Sep 2023 10:55:56 +0200 Subject: [PATCH 8/9] Fix Conda package names enclosed with quotes Signed-off-by: Paolo Di Tommaso --- .../io/seqera/wave/util/DockerHelper.java | 19 ++++++++- .../seqera/wave/util/DockerHelperTest.groovy | 41 ++++++++++++++++++- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java b/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java index 4031a45..5228931 100644 --- a/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java +++ b/wave-utils/src/main/java/io/seqera/wave/util/DockerHelper.java @@ -64,7 +64,21 @@ static public Path condaFileFromPackages(String packages, List condaChan static List condaPackagesToList(String packages) { if (packages == null || packages.isEmpty()) return null; - return Arrays.asList(packages.split(" ")); + return Arrays + .stream(packages.split(" ")) + .filter(it -> !StringUtils.isEmpty(it)) + .map(it -> trim0(it)).collect(Collectors.toList()); + } + + protected static String trim0(String value) { + if( value==null ) + return null; + value = value.trim(); + while( value.startsWith("'") && value.endsWith("'") ) + value = value.substring(1,value.length()-1); + while( value.startsWith("\"") && value.endsWith("\"") ) + value = value.substring(1,value.length()-1); + return value; } static String condaPackagesToCondaYaml(String packages, List channels, CondaOpts opts) { @@ -168,7 +182,8 @@ public static Path condaFileFromPath(String condaFile, List channels, Co static public List spackPackagesToList(String packages) { if( packages==null || packages.isEmpty() ) return null; - final List entries = Arrays.asList(packages.split(" ")); + final List entries = Arrays + .stream(packages.split(" ")).map(it -> trim0(it)).collect(Collectors.toList()); final List result = new ArrayList<>(); List current = new ArrayList<>(); for( String it : entries ) { diff --git a/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy b/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy index f2a4c68..087411e 100644 --- a/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy +++ b/wave-utils/src/test/groovy/io/seqera/wave/util/DockerHelperTest.groovy @@ -23,13 +23,47 @@ import spock.lang.Specification */ class DockerHelperTest extends Specification { + def 'should trim a string' () { + expect: + DockerHelper.trim0(STR) == EXPECTED + + where: + STR | EXPECTED + null | null + "foo" | "foo" + " foo " | "foo" + "'foo" | "'foo" + '"foo' | '"foo' + and: + "'foo'" | "foo" + "''foo''" | "foo" + " 'foo' " | "foo" + " ' foo ' " | " foo " + and: + '"foo"' | 'foo' + '""foo""' | 'foo' + ' "foo" ' | 'foo' + } + + def 'should convert conda packages to list' () { + expect: + DockerHelper.condaPackagesToList(STR) == EXPECTED + + where: + STR | EXPECTED + "foo" | ["foo"] + "foo bar" | ["foo", "bar"] + "foo 'bar'" | ["foo", "bar"] + "foo 'bar' " | ["foo", "bar"] + } + def 'should create conda yaml file' () { expect: - DockerHelper.condaPackagesToCondaYaml('foo=1.0 bar=2.0', null, new CondaOpts()) + DockerHelper.condaPackagesToCondaYaml("foo=1.0 'bar>=2.0'", null, new CondaOpts()) == '''\ dependencies: - foo=1.0 - - bar=2.0 + - bar>=2.0 '''.stripIndent(true) and: @@ -521,9 +555,12 @@ class DockerHelperTest extends Specification { PACKAGES | EXPECTED null | null 'alpha' | ['alpha'] + "'alpha@1.0'" | ['alpha@1.0'] + "alpha 'delta@1.0'" | ['alpha', 'delta@1.0'] 'alpha delta' | ['alpha', 'delta'] 'alpha delta gamma' | ['alpha', 'delta', 'gamma'] 'alpha 1aa' | ['alpha', '1aa'] + 'alpha 2bb ' | ['alpha', '2bb'] and: 'alpha x=1' | ['alpha x=1'] 'alpha x=1 delta' | ['alpha x=1', 'delta'] From ff047d30519d1c82cfe4f5780f2f82cd2d828205 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Sat, 2 Sep 2023 10:56:32 +0200 Subject: [PATCH 9/9] [release] bump wave-utils@0.7.2 Signed-off-by: Paolo Di Tommaso --- wave-utils/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wave-utils/VERSION b/wave-utils/VERSION index 39e898a..7486fdb 100644 --- a/wave-utils/VERSION +++ b/wave-utils/VERSION @@ -1 +1 @@ -0.7.1 +0.7.2