From 503eaad7855bd3a0a0be2671ec0d2d6e65f3a0e6 Mon Sep 17 00:00:00 2001 From: Jordi Deu-Pons Date: Mon, 5 Dec 2022 16:02:19 +0100 Subject: [PATCH] Support shared pipelines (#279) * Allow to choose pipeline visibility when listing pipelines * Allow to launch shared pipelines * Add visibility column at list * Add missing license header * Fix tests * Search for multiple pipelines at launch command --- build.gradle | 2 +- conf/resource-config.json | 10 ++++- .../tower/cli/commands/AbstractApiCmd.java | 14 +++++++ .../seqera/tower/cli/commands/LaunchCmd.java | 37 ++++++++++++++----- .../pipelines/AbstractPipelinesCmd.java | 2 +- .../cli/commands/pipelines/ExportCmd.java | 4 +- .../tower/cli/commands/pipelines/ListCmd.java | 5 ++- .../pipelines/PipelineVisibility.java | 29 +++++++++++++++ .../cli/commands/pipelines/UpdateCmd.java | 3 +- .../tower/cli/commands/pipelines/ViewCmd.java | 3 +- .../responses/pipelines/PipelinesList.java | 4 +- .../responses/pipelines/PipelinesView.java | 2 +- .../seqera/tower/cli/utils/ModelHelper.java | 2 +- .../io/seqera/tower/cli/LaunchCmdTest.java | 20 ---------- 14 files changed, 95 insertions(+), 42 deletions(-) create mode 100644 src/main/java/io/seqera/tower/cli/commands/pipelines/PipelineVisibility.java diff --git a/build.gradle b/build.gradle index 5dad4016..0f800a71 100644 --- a/build.gradle +++ b/build.gradle @@ -23,7 +23,7 @@ dependencies { implementation 'org.slf4j:slf4j-api:1.7.36' implementation 'ch.qos.logback:logback-core:1.2.11' implementation 'ch.qos.logback:logback-classic:1.2.11' - implementation 'io.seqera.tower:tower-java-sdk:1.4.2' + implementation 'io.seqera.tower:tower-java-sdk:1.4.3' implementation 'info.picocli:picocli:4.6.3' annotationProcessor 'info.picocli:picocli-codegen:4.6.3' diff --git a/conf/resource-config.json b/conf/resource-config.json index 3d2ad351..ed08bc22 100644 --- a/conf/resource-config.json +++ b/conf/resource-config.json @@ -17,11 +17,17 @@ "bundles":[ { "name":"org.glassfish.jersey.client.internal.localization", - "locales":["und"] + "locales":[ + "", + "und" + ] }, { "name":"org.glassfish.jersey.internal.localization", - "locales":["und"] + "locales":[ + "", + "und" + ] }, { "name":"org.glassfish.jersey.media.multipart.internal.localization" diff --git a/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java b/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java index c82d38ed..ca84b2a6 100644 --- a/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java @@ -30,6 +30,7 @@ import io.seqera.tower.model.ListComputeEnvsResponseEntry; import io.seqera.tower.model.ListWorkspacesAndOrgResponse; import io.seqera.tower.model.OrgAndWorkspaceDbDto; +import io.seqera.tower.model.PipelineDbDto; import io.seqera.tower.model.User; import io.seqera.tower.model.WorkflowQueryAttribute; import org.glassfish.jersey.client.ClientConfig; @@ -381,6 +382,19 @@ protected String workspaceRef(Long workspaceId) throws ApiException { return buildWorkspaceRef(orgName(workspaceId), workspaceName(workspaceId)); } + protected Long sourceWorkspaceId(Long currentWorkspace, PipelineDbDto pipeline) { + if (pipeline == null) + return null; + + if (pipeline.getWorkspaceId() == null) + return null; + + if (pipeline.getWorkspaceId().equals(currentWorkspace)) + return null; + + return pipeline.getWorkspaceId(); + } + @Override public Integer call() { try { diff --git a/src/main/java/io/seqera/tower/cli/commands/LaunchCmd.java b/src/main/java/io/seqera/tower/cli/commands/LaunchCmd.java index 881a8f16..7960a46b 100644 --- a/src/main/java/io/seqera/tower/cli/commands/LaunchCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/LaunchCmd.java @@ -20,6 +20,7 @@ import io.seqera.tower.model.ComputeEnvResponseDto; import io.seqera.tower.model.Launch; import io.seqera.tower.model.ListPipelinesResponse; +import io.seqera.tower.model.PipelineDbDto; import io.seqera.tower.model.SubmitWorkflowLaunchRequest; import io.seqera.tower.model.SubmitWorkflowLaunchResponse; import io.seqera.tower.model.WorkflowLaunchRequest; @@ -104,7 +105,7 @@ protected Response runNextflowPipeline(Long wspId) throws ApiException, IOExcept .workDir(ce.getConfig().getWorkDir()) .preRunScript(ce.getConfig().getPreRunScript()) .postRunScript(ce.getConfig().getPostRunScript()) - ), wspId); + ), wspId, null); } private WorkflowLaunchRequest updateLaunchRequest(WorkflowLaunchRequest base) throws IOException { @@ -130,28 +131,46 @@ private WorkflowLaunchRequest updateLaunchRequest(WorkflowLaunchRequest base) th } protected Response runTowerPipeline(Long wspId) throws ApiException, IOException { - ListPipelinesResponse pipelines = api().listPipelines(Collections.emptyList(), wspId, 2, 0, pipeline, null); + ListPipelinesResponse pipelines = api().listPipelines(Collections.emptyList(), wspId, 50, 0, pipeline, "all"); if (pipelines.getTotalSize() == 0) { throw new InvalidResponseException(String.format("Pipeline '%s' not found on this workspace.", pipeline)); } - if (pipelines.getTotalSize() > 1) { - throw new InvalidResponseException(String.format("Multiple pipelines match '%s'", pipeline)); + PipelineDbDto pipe = null; + for (PipelineDbDto p : pipelines.getPipelines()) { + if (pipeline.equals(p.getName())) { + pipe = p; + break; + } } - Long pipelineId = pipelines.getPipelines().get(0).getPipelineId(); - Launch launch = api().describePipelineLaunch(pipelineId, wspId).getLaunch(); + if (pipe == null) { + throw new InvalidResponseException(String.format("Pipeline '%s' not found", pipe)); + } + + Long sourceWorkspaceId = sourceWorkspaceId(wspId, pipe); + + Launch launch = api().describePipelineLaunch(pipe.getPipelineId(), wspId, sourceWorkspaceId).getLaunch(); WorkflowLaunchRequest launchRequest = createLaunchRequest(launch); if (computeEnv != null) { launchRequest.computeEnvId(computeEnvByRef(wspId, computeEnv).getId()); } - return submitWorkflow(updateLaunchRequest(launchRequest), wspId); + if (launchRequest.getComputeEnvId() == null) { + launchRequest.computeEnvId(primaryComputeEnv(wspId).getId()); + } + + if (launchRequest.getWorkDir() == null) { + ComputeEnvResponseDto ce = api().describeComputeEnv(launchRequest.getComputeEnvId(), wspId, NO_CE_ATTRIBUTES).getComputeEnv(); + launchRequest.workDir(ce.getConfig().getWorkDir()); + } + + return submitWorkflow(updateLaunchRequest(launchRequest), wspId, sourceWorkspaceId); } - protected Response submitWorkflow(WorkflowLaunchRequest launch, Long wspId) throws ApiException { - SubmitWorkflowLaunchResponse response = api().createWorkflowLaunch(new SubmitWorkflowLaunchRequest().launch(launch), wspId, null, null); + protected Response submitWorkflow(WorkflowLaunchRequest launch, Long wspId, Long sourceWorkspaceId) throws ApiException { + SubmitWorkflowLaunchResponse response = api().createWorkflowLaunch(new SubmitWorkflowLaunchRequest().launch(launch), wspId, null, sourceWorkspaceId); String workflowId = response.getWorkflowId(); return new RunSubmited(workflowId, wspId, baseWorkspaceUrl(wspId), workspaceRef(wspId)); } diff --git a/src/main/java/io/seqera/tower/cli/commands/pipelines/AbstractPipelinesCmd.java b/src/main/java/io/seqera/tower/cli/commands/pipelines/AbstractPipelinesCmd.java index 66d66e56..0d1c82af 100644 --- a/src/main/java/io/seqera/tower/cli/commands/pipelines/AbstractPipelinesCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/pipelines/AbstractPipelinesCmd.java @@ -29,7 +29,7 @@ public AbstractPipelinesCmd() { protected PipelineDbDto pipelineByName(Long workspaceId, String name) throws ApiException { - ListPipelinesResponse list = api().listPipelines(Collections.emptyList(), workspaceId, null, null, name, null); + ListPipelinesResponse list = api().listPipelines(Collections.emptyList(), workspaceId, null, null, name, "all"); if (list.getPipelines().isEmpty()) { throw new PipelineNotFoundException(name, workspaceRef(workspaceId)); diff --git a/src/main/java/io/seqera/tower/cli/commands/pipelines/ExportCmd.java b/src/main/java/io/seqera/tower/cli/commands/pipelines/ExportCmd.java index 731bcd88..0545e230 100644 --- a/src/main/java/io/seqera/tower/cli/commands/pipelines/ExportCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/pipelines/ExportCmd.java @@ -44,8 +44,8 @@ public class ExportCmd extends AbstractPipelinesCmd { protected Response exec() throws ApiException { Long wspId = workspaceId(workspace.workspace); PipelineDbDto pipeline = fetchPipeline(pipelineRefOptions, wspId); - - Launch launch = api().describePipelineLaunch(pipeline.getPipelineId(), wspId).getLaunch(); + Long sourceWorkspaceId = sourceWorkspaceId(wspId, pipeline); + Launch launch = api().describePipelineLaunch(pipeline.getPipelineId(), wspId, sourceWorkspaceId).getLaunch(); WorkflowLaunchRequest workflowLaunchRequest = ModelHelper.createLaunchRequest(launch); diff --git a/src/main/java/io/seqera/tower/cli/commands/pipelines/ListCmd.java b/src/main/java/io/seqera/tower/cli/commands/pipelines/ListCmd.java index 8ee72860..933f9883 100644 --- a/src/main/java/io/seqera/tower/cli/commands/pipelines/ListCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/pipelines/ListCmd.java @@ -36,6 +36,9 @@ public class ListCmd extends AbstractPipelinesCmd { @CommandLine.Option(names = {"-f", "--filter"}, description = "Show only pipelines that contain the given word.") public String filter; + @CommandLine.Option(names = {"--visibility"}, description = "Show pipelines: ${COMPLETION-CANDIDATES} [default: private].", defaultValue = "private") + public PipelineVisibility visibility; + @CommandLine.Mixin PaginationOptions paginationOptions; @@ -48,7 +51,7 @@ protected Response exec() throws ApiException, IOException { ListPipelinesResponse response = new ListPipelinesResponse(); try { - response = api().listPipelines(Collections.emptyList(), wspId, max, offset, filter, null); + response = api().listPipelines(Collections.emptyList(), wspId, max, offset, filter, visibility.toString()); } catch (ApiException apiException) { if (apiException.getCode() == 404){ diff --git a/src/main/java/io/seqera/tower/cli/commands/pipelines/PipelineVisibility.java b/src/main/java/io/seqera/tower/cli/commands/pipelines/PipelineVisibility.java new file mode 100644 index 00000000..f1d668b6 --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/commands/pipelines/PipelineVisibility.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021, Seqera Labs. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Source Code Form is "Incompatible With Secondary Licenses", as + * defined by the Mozilla Public License, v. 2.0. + */ + +package io.seqera.tower.cli.commands.pipelines; + +public enum PipelineVisibility { + ALL("all"), + PRIVATE("private"), + SHARED("shared"); + + private final String value; + + PipelineVisibility(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } +} diff --git a/src/main/java/io/seqera/tower/cli/commands/pipelines/UpdateCmd.java b/src/main/java/io/seqera/tower/cli/commands/pipelines/UpdateCmd.java index 7249c0db..7df80d4f 100644 --- a/src/main/java/io/seqera/tower/cli/commands/pipelines/UpdateCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/pipelines/UpdateCmd.java @@ -67,7 +67,8 @@ protected Response exec() throws ApiException, IOException { id = pipe.getPipelineId(); } - Launch launch = api().describePipelineLaunch(id, wspId).getLaunch(); + Long sourceWorkspaceId = sourceWorkspaceId(wspId, pipe); + Launch launch = api().describePipelineLaunch(id, wspId, sourceWorkspaceId).getLaunch(); // Retrieve the provided computeEnv or use the primary if not provided String ceId = opts.computeEnv != null ? computeEnvByRef(wspId, opts.computeEnv).getId() : launch.getComputeEnv().getId(); diff --git a/src/main/java/io/seqera/tower/cli/commands/pipelines/ViewCmd.java b/src/main/java/io/seqera/tower/cli/commands/pipelines/ViewCmd.java index 8df07e29..a61b309c 100644 --- a/src/main/java/io/seqera/tower/cli/commands/pipelines/ViewCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/pipelines/ViewCmd.java @@ -36,8 +36,9 @@ public class ViewCmd extends AbstractPipelinesCmd { protected Response exec() throws ApiException { Long wspId = workspaceId(workspace.workspace); PipelineDbDto pipeline = fetchPipeline(pipelineRefOptions, wspId); + Long sourceWorkspaceId = sourceWorkspaceId(wspId, pipeline); - Launch launch = api().describePipelineLaunch(pipeline.getPipelineId(), wspId).getLaunch(); + Launch launch = api().describePipelineLaunch(pipeline.getPipelineId(), wspId, sourceWorkspaceId).getLaunch(); return new PipelinesView(workspaceRef(wspId), pipeline, launch, baseWorkspaceUrl(wspId)); } diff --git a/src/main/java/io/seqera/tower/cli/responses/pipelines/PipelinesList.java b/src/main/java/io/seqera/tower/cli/responses/pipelines/PipelinesList.java index 9796c9f4..c69c0438 100644 --- a/src/main/java/io/seqera/tower/cli/responses/pipelines/PipelinesList.java +++ b/src/main/java/io/seqera/tower/cli/responses/pipelines/PipelinesList.java @@ -45,13 +45,13 @@ public void toString(PrintWriter out) { return; } - TableList table = new TableList(out, 3, "ID", "Name", "Repository", "Description").sortBy(0); + TableList table = new TableList(out, 4, "ID", "Name", "Repository", "Visibility").sortBy(0); table.setPrefix(" "); pipelines.forEach(pipe -> table.addRow( formatPipelineId(pipe.getPipelineId(), baseWorkspaceUrl), pipe.getName(), pipe.getRepository(), - pipe.getDescription() + pipe.getVisibility() )); table.print(); out.println(""); diff --git a/src/main/java/io/seqera/tower/cli/responses/pipelines/PipelinesView.java b/src/main/java/io/seqera/tower/cli/responses/pipelines/PipelinesView.java index b1e75afe..aabc66a9 100644 --- a/src/main/java/io/seqera/tower/cli/responses/pipelines/PipelinesView.java +++ b/src/main/java/io/seqera/tower/cli/responses/pipelines/PipelinesView.java @@ -58,7 +58,7 @@ public void toString(PrintWriter out) { table.addRow("Name", info.getName()); table.addRow("Description", info.getDescription()); table.addRow("Repository", info.getRepository()); - table.addRow("Compute env.", launch.getComputeEnv().getName()); + table.addRow("Compute env.", launch.getComputeEnv() == null ? "(not defined)" : launch.getComputeEnv().getName()); table.print(); out.println(String.format("%n Configuration:%n%n%s%n", configJson.replaceAll("(?m)^", " "))); diff --git a/src/main/java/io/seqera/tower/cli/utils/ModelHelper.java b/src/main/java/io/seqera/tower/cli/utils/ModelHelper.java index ddf65a6a..cc12a674 100644 --- a/src/main/java/io/seqera/tower/cli/utils/ModelHelper.java +++ b/src/main/java/io/seqera/tower/cli/utils/ModelHelper.java @@ -25,7 +25,7 @@ private ModelHelper() { public static WorkflowLaunchRequest createLaunchRequest(Launch launch) { return new WorkflowLaunchRequest() .id(launch.getId()) - .computeEnvId(launch.getComputeEnv().getId()) + .computeEnvId(launch.getComputeEnv() != null ? launch.getComputeEnv().getId() : null) .pipeline(launch.getPipeline()) .workDir(launch.getWorkDir()) .revision(launch.getRevision()) diff --git a/src/test/java/io/seqera/tower/cli/LaunchCmdTest.java b/src/test/java/io/seqera/tower/cli/LaunchCmdTest.java index 646b5272..6e62f24e 100644 --- a/src/test/java/io/seqera/tower/cli/LaunchCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/LaunchCmdTest.java @@ -72,26 +72,6 @@ void testPipelineNotfound(MockServerClient mock) { assertEquals(1, out.exitCode); } - @Test - void testMultiplePipelinesFound(MockServerClient mock) { - - mock.reset(); - - // Create server expectation - mock.when( - request().withMethod("GET").withPath("/pipelines"), exactly(1) - ).respond( - response().withStatusCode(200).withBody(loadResource("pipelines_multiple")).withContentType(MediaType.APPLICATION_JSON) - ); - - // Run the command - ExecOut out = exec(mock, "launch", "hello"); - - // Assert results - assertEquals(errorMessage(out.app, new InvalidResponseException("Multiple pipelines match 'hello'")), out.stdErr); - assertEquals(1, out.exitCode); - } - @ParameterizedTest @EnumSource(OutputType.class) void testSubmitLaunchpadPipeline(OutputType format, MockServerClient mock) {