diff --git a/app/src/main/java/io/seqera/wave/cli/App.java b/app/src/main/java/io/seqera/wave/cli/App.java index 716fbe1..dddc8fb 100644 --- a/app/src/main/java/io/seqera/wave/cli/App.java +++ b/app/src/main/java/io/seqera/wave/cli/App.java @@ -30,6 +30,7 @@ import java.nio.file.Files; import java.nio.file.NoSuchFileException; import java.nio.file.Path; +import java.time.Duration; import java.time.OffsetDateTime; import java.util.Arrays; import java.util.Base64; @@ -55,6 +56,7 @@ import io.seqera.wave.cli.model.ContainerSpecEx; import io.seqera.wave.cli.util.BuildInfo; import io.seqera.wave.cli.util.CliVersionProvider; +import io.seqera.wave.cli.util.DurationConverter; import io.seqera.wave.cli.util.YamlHelper; import io.seqera.wave.config.CondaOpts; import io.seqera.wave.config.SpackOpts; @@ -116,11 +118,8 @@ public class App implements Runnable { @Option(names = {"--platform"}, paramLabel = "''", description = "Platform to be used for the container build. One of: linux/amd64, linux/arm64.") private String platform; - @Option(names = {"--await"}, paramLabel = "false", description = "Await the container build to be available.") - private boolean await; - - @Option(names = {"--await-timeout"}, paramLabel = "false", description = "Timeout in minutes for awaiting the container build to be available, default is 15 minutes") - private Integer awaitTimeout; + @Option(names = {"--await"}, paramLabel = "false", arity = "0..1", description = "Await the container build to be available. you can provide a timeout like --await 10m or 2s, by default its 15 minutes.") + private Duration await; @Option(names = {"--context"}, paramLabel = "''", description = "Directory path where the build context is stored e.g. /some/context/path.") private String contextDir; @@ -200,6 +199,9 @@ public static void main(String[] args) { final App app = new App(); final CommandLine cli = new CommandLine(app); + //register duration converter + cli.registerConverter(Duration.class, new DurationConverter()); + // add examples in help cli .getCommandSpec() @@ -371,15 +373,12 @@ protected void validateArgs() { throw new IllegalCliArgumentException("Context path is not a directory - offending value: " + contextDir); } - if( dryRun && await ) + if( dryRun && await != null ) throw new IllegalCliArgumentException("Options --dry-run and --await conflicts each other"); if( !isEmpty(platform) && !VALID_PLATFORMS.contains(platform) ) throw new IllegalCliArgumentException(String.format("Unsupported container platform: '%s'", platform)); - if( !await && awaitTimeout != null) - throw new IllegalCliArgumentException("--awaitTimeout option is only allowed when --await option is used"); - } protected Client client() { @@ -435,8 +434,8 @@ public void run() { // submit it SubmitContainerTokenResponse resp = client.submit(request); // await build to be completed - if( await && resp.buildId!=null && !resp.cached ) - client.awaitCompletion(resp.buildId, awaitTimeout); + if( await != null && resp.buildId!=null && !resp.cached ) + client.awaitCompletion(resp.buildId, await); // print the wave container name System.out.println(dumpOutput(resp)); } diff --git a/app/src/main/java/io/seqera/wave/cli/Client.java b/app/src/main/java/io/seqera/wave/cli/Client.java index fd3f01c..f7220cb 100644 --- a/app/src/main/java/io/seqera/wave/cli/Client.java +++ b/app/src/main/java/io/seqera/wave/cli/Client.java @@ -197,12 +197,10 @@ protected URI imageToManifestUri(String image) { return URI.create(result); } - void awaitCompletion(String buildId, Integer timeout) { - timeout = timeout == null ? 15 : timeout; - final long maxAwait = Duration.ofMinutes(timeout).toMillis(); + void awaitCompletion(String buildId, Duration await) { final long startTime = Instant.now().toEpochMilli(); while (!isComplete(buildId)) { - if (System.currentTimeMillis() - startTime > maxAwait) { + if (System.currentTimeMillis() - startTime > await.toMillis()) { break; } } diff --git a/app/src/main/java/io/seqera/wave/cli/util/DurationConverter.java b/app/src/main/java/io/seqera/wave/cli/util/DurationConverter.java new file mode 100644 index 0000000..79ab74b --- /dev/null +++ b/app/src/main/java/io/seqera/wave/cli/util/DurationConverter.java @@ -0,0 +1,38 @@ +/* + * Wave, containers provisioning service + * Copyright (c) 2023-2024, Seqera Labs + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + + +package io.seqera.wave.cli.util; + +import picocli.CommandLine; + +import java.time.Duration; +/** + * Converter to convert cli argument to duration + * + * @author Munish Chouhan + */ +public class DurationConverter implements CommandLine.ITypeConverter { + @Override + public Duration convert(String value) { + if (value == null || value.trim().isEmpty()) { + return Duration.ofMinutes(15); + } + return Duration.parse("PT" + value.toUpperCase()); + } +} \ No newline at end of file diff --git a/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy b/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy index f0f1aa0..eeb5c9f 100644 --- a/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy +++ b/app/src/test/groovy/io/seqera/wave/cli/AppTest.groovy @@ -17,7 +17,10 @@ package io.seqera.wave.cli +import io.seqera.wave.cli.util.DurationConverter + import java.nio.file.Files +import java.time.Duration import java.time.Instant import io.seqera.wave.api.SubmitContainerTokenResponse @@ -192,7 +195,9 @@ class AppTest extends Specification { String[] args = ["-i", "ubuntu:latest","--dry-run", '--await'] when: - new CommandLine(app).parseArgs(args) + def cli = new CommandLine(app) + cli.registerConverter(Duration.class, new DurationConverter()) + cli.parseArgs(args) and: app.validateArgs() then: @@ -273,17 +278,54 @@ class AppTest extends Specification { app.@towerToken == 'xyz' } - def 'should not allow awaitTimeout without await'(){ + def 'should get the correct await duration in minutes'(){ given: def app = new App() - String[] args = ["-i", "ubuntu:latest", '--await-timeout', 10] + String[] args = ["-i", "ubuntu:latest", '--await', '10m'] when: - new CommandLine(app).parseArgs(args) + def cli = new CommandLine(app) + cli.registerConverter(Duration.class, new DurationConverter()) + cli.parseArgs(args) and: app.validateArgs() then: - def e = thrown(IllegalCliArgumentException) - e.message == '--awaitTimeout option is only allowed when --await option is used' + noExceptionThrown() + and: + app.@await == Duration.ofMinutes(10) + } + + def 'should get the correct await duration in seconds'(){ + given: + def app = new App() + String[] args = ["-i", "ubuntu:latest", '--await', '10s'] + + when: + def cli = new CommandLine(app) + cli.registerConverter(Duration.class, new DurationConverter()) + cli.parseArgs(args) + and: + app.validateArgs() + then: + noExceptionThrown() + and: + app.@await == Duration.ofSeconds(10) + } + + def 'should get the default await duration'(){ + given: + def app = new App() + String[] args = ["-i", "ubuntu:latest", '--await'] + + when: + def cli = new CommandLine(app) + cli.registerConverter(Duration.class, new DurationConverter()) + cli.parseArgs(args) + and: + app.validateArgs() + then: + noExceptionThrown() + and: + app.@await == Duration.ofMinutes(15) } }