Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
fd648cc
feat: add pipeline version API client to the base abstract cmd
JaimeSeqLabs Feb 17, 2026
86d0181
feat: utility classes update
JaimeSeqLabs Feb 17, 2026
7403f26
feat: pipeline versions list cmd
JaimeSeqLabs Feb 17, 2026
ff8a78c
fix: missing license header
JaimeSeqLabs Feb 17, 2026
ed4aef5
feat: unit test for pipeline versions cmd
JaimeSeqLabs Feb 17, 2026
935d716
chore: move error handler into base class
JaimeSeqLabs Feb 18, 2026
649fb06
feat: pipeline versions view cmd
JaimeSeqLabs Feb 18, 2026
2557c3b
feat: pipeline version update cmd
JaimeSeqLabs Feb 18, 2026
67c0a77
feat: include commands in the root versioning class
JaimeSeqLabs Feb 18, 2026
ee94e58
feat: unit tests for versions view and update cmds
JaimeSeqLabs Feb 18, 2026
4de3e48
fix: remove unsetting default flag use case, update version by name
JaimeSeqLabs Feb 18, 2026
627b72a
chore: move version resolution to base api cmd
JaimeSeqLabs Feb 18, 2026
4080fc6
feat: versioning support for 'pipelines add' cmd, refactor error hand…
JaimeSeqLabs Feb 19, 2026
f8c6d46
feat: versioning support for 'pipelines view' cmd, refactor error han…
JaimeSeqLabs Feb 19, 2026
aebd939
feat: versioning support for 'pipelines export' cmd
JaimeSeqLabs Feb 19, 2026
a1534f0
feat: versioning support for 'pipelines launch' cmd
JaimeSeqLabs Feb 19, 2026
6acd7b9
feat: versioning support for 'pipelines update' cmd, detect when draf…
JaimeSeqLabs Feb 19, 2026
840a7ce
feat: updated unit tests
JaimeSeqLabs Feb 19, 2026
d7c9857
feat: updated reflection files
JaimeSeqLabs Feb 19, 2026
845818e
feat: include versioning data in 'pipelines export' cmd ouput
JaimeSeqLabs Feb 19, 2026
510a6a8
feat: include versioning data in 'pipelines view' cmd output
JaimeSeqLabs Feb 19, 2026
838be74
feat: update tests with versioning data output
JaimeSeqLabs Feb 19, 2026
8cdca25
refactor: move pipeline labels subcommands to separate package
JaimeSeqLabs Feb 19, 2026
63358de
fix: reflection files
JaimeSeqLabs Feb 19, 2026
56d36d8
merge: resolve conflicts with master (pipelineSchemaId)
JaimeSeqLabs Feb 19, 2026
5104931
fix: ensure retro-compatibility, auto name and promote if the changes…
JaimeSeqLabs Feb 23, 2026
03ab185
refactor: clarify filter parameter
JaimeSeqLabs Feb 23, 2026
12ec5c0
refactor: add version ID to the version list output
JaimeSeqLabs Feb 23, 2026
bf0a97a
test: cleanup test comments [ci skip]
JaimeSeqLabs Feb 23, 2026
ebe2d3e
Merge remote-tracking branch 'origin/master' into PLAT-3660-pipeline-…
JaimeSeqLabs Feb 24, 2026
c6f499f
refactor: remove confusing http return code handling
JaimeSeqLabs Feb 25, 2026
c1f25a3
refactor: rename versions update cmd to versions manage
JaimeSeqLabs Feb 25, 2026
b07f992
fix: license header
JaimeSeqLabs Feb 25, 2026
178d579
chore: update filter flag description with correct keywords, add extr…
JaimeSeqLabs Feb 25, 2026
9d5e302
chore: remove unnecessary pipeline null checks
JaimeSeqLabs Feb 25, 2026
b5a7677
chore: remove unnecessary feature check
JaimeSeqLabs Feb 26, 2026
a20c9e0
chore: remove references to frontend files
JaimeSeqLabs Feb 26, 2026
857db4c
chore: change access modifier for version name generator method
JaimeSeqLabs Feb 26, 2026
dfeb3e2
fix: missing response class
JaimeSeqLabs Feb 26, 2026
14ce21c
fix: remove unused test case
JaimeSeqLabs Feb 26, 2026
6dd228c
chore: remove unnecessary feature check
JaimeSeqLabs Feb 26, 2026
246f1bb
refactor: simplify default pipeline version fetch
JaimeSeqLabs Feb 26, 2026
8e30f7e
refactor: remove pipeline versions view cmd, info already available w…
JaimeSeqLabs Feb 26, 2026
e736ee4
refactor: remove pipeline versions view cmd, info already available w…
JaimeSeqLabs Feb 26, 2026
dff3ecf
chore: reflection files cleanup
JaimeSeqLabs Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 62 additions & 6 deletions conf/reflect-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -1469,12 +1469,6 @@
"allDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelines.LabelsCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelines.LaunchOptions",
"allDeclaredFields":true,
Expand Down Expand Up @@ -1511,6 +1505,48 @@
"allDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelines.labels.LabelsCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelines.versions.ListCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelines.versions.UpdateCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelines.versions.UpdateCmd$UpdateOptions",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelines.versions.VersionRefOptions",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelines.versions.VersionRefOptions$VersionRef",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelines.versions.VersionsCmd",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.pipelineschemas.AbstractPipelineSchemasCmd",
"allDeclaredFields":true,
Expand Down Expand Up @@ -2187,12 +2223,30 @@
"allDeclaredMethods":true,
"allDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.pipelines.PipelinesUpdated",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.pipelines.PipelinesView",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.pipelines.versions.ListPipelineVersionsCmdResponse",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.pipelines.versions.ManagePipelineVersionCmdResponse",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.pipelineschemas.PipelineSchemasAdded",
"allDeclaredFields":true,
Expand Down Expand Up @@ -4027,6 +4081,7 @@
},
{
"name":"io.seqera.tower.model.ListPipelineVersionsResponse",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"addVersionsItem","parameterTypes":["io.seqera.tower.model.PipelineDbDto"] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getTotalSize","parameterTypes":[] }, {"name":"getVersions","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"setTotalSize","parameterTypes":["java.lang.Long"] }, {"name":"setVersions","parameterTypes":["java.util.List"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }, {"name":"totalSize","parameterTypes":["java.lang.Long"] }, {"name":"versions","parameterTypes":["java.util.List"] }]
Expand Down Expand Up @@ -4352,6 +4407,7 @@
},
{
"name":"io.seqera.tower.model.PipelineVersionManageRequest",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"equals","parameterTypes":["java.lang.Object"] }, {"name":"getIsDefault","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }, {"name":"hashCode","parameterTypes":[] }, {"name":"isDefault","parameterTypes":["java.lang.Boolean"] }, {"name":"name","parameterTypes":["java.lang.String"] }, {"name":"setIsDefault","parameterTypes":["java.lang.Boolean"] }, {"name":"setName","parameterTypes":["java.lang.String"] }, {"name":"toIndentedString","parameterTypes":["java.lang.Object"] }, {"name":"toString","parameterTypes":[] }]
Expand Down
4 changes: 2 additions & 2 deletions conf/resource-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,10 @@
"locales":["und"]
}, {
"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"
}]
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import io.seqera.tower.api.OrgsApi;
import io.seqera.tower.api.PipelineSchemasApi;
import io.seqera.tower.api.PipelineSecretsApi;
import io.seqera.tower.api.PipelineVersionsApi;
import io.seqera.tower.api.PipelinesApi;
import io.seqera.tower.api.PlatformsApi;
import io.seqera.tower.api.ServiceInfoApi;
Expand All @@ -43,6 +44,7 @@
import io.seqera.tower.cli.Tower;
import io.seqera.tower.cli.commands.labels.Label;
import io.seqera.tower.cli.commands.labels.LabelsFinder;
import io.seqera.tower.cli.commands.pipelines.versions.VersionRefOptions;
import io.seqera.tower.cli.exceptions.ComputeEnvNotFoundException;
import io.seqera.tower.cli.exceptions.InvalidWorkspaceParameterException;
import io.seqera.tower.cli.exceptions.MissingTowerAccessTokenException;
Expand All @@ -59,10 +61,12 @@
import io.seqera.tower.model.Credentials;
import io.seqera.tower.model.DataStudioQueryAttribute;
import io.seqera.tower.model.ListComputeEnvsResponseEntry;
import io.seqera.tower.model.ListPipelineVersionsResponse;
import io.seqera.tower.model.ListWorkspacesAndOrgResponse;
import io.seqera.tower.model.OrgAndWorkspaceDto;
import io.seqera.tower.model.PipelineDbDto;
import io.seqera.tower.model.PipelineQueryAttribute;
import io.seqera.tower.model.PipelineVersionFullInfoDto;
import io.seqera.tower.model.UserResponseDto;
import io.seqera.tower.model.WorkflowQueryAttribute;
import org.glassfish.jersey.CommonProperties;
Expand All @@ -82,6 +86,7 @@
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.function.Predicate;
import java.util.logging.Logger;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -115,6 +120,7 @@ public abstract class AbstractApiCmd extends AbstractCmd {
private PipelinesApi pipelinesApi;
private PipelineSchemasApi pipelineSchemasApi;
private PipelineSecretsApi pipelineSecretsApi;
private PipelineVersionsApi pipelineVersionsApi;
private PlatformsApi platformsApi;
private ServiceInfoApi serviceInfoApi;
private StudiosApi studiosApi;
Expand Down Expand Up @@ -234,6 +240,10 @@ protected PipelinesApi pipelinesApi() throws ApiException {
return pipelinesApi == null ? new PipelinesApi(apiClient()) : pipelinesApi;
}

protected PipelineVersionsApi pipelineVersionsApi() throws ApiException {
return pipelineVersionsApi == null ? new PipelineVersionsApi(apiClient()) : pipelineVersionsApi;
}

protected PlatformsApi platformsApi() throws ApiException {
return platformsApi == null ? new PlatformsApi(apiClient()) : platformsApi;
}
Expand Down Expand Up @@ -579,6 +589,39 @@ protected String workspaceRef(Long workspaceId) throws ApiException {
return buildWorkspaceRef(orgName(workspaceId), workspaceName(workspaceId));
}

protected PipelineVersionFullInfoDto findPipelineVersionByRef(Long pipelineId, Long wspId, VersionRefOptions.VersionRef ref) throws ApiException {
String search = ref.versionName;
Boolean isPublished = ref.versionName != null ? true : null;
Predicate<PipelineVersionFullInfoDto> matcher = ref.versionId != null
? v -> ref.versionId.equals(v.getId())
: v -> ref.versionName.equals(v.getName());

ListPipelineVersionsResponse response = pipelineVersionsApi()
.listPipelineVersions(pipelineId, wspId, null, null, search, isPublished);

if (response.getVersions() == null) {
throw new TowerException("No versions available for the pipeline, check if Pipeline versioning feature is enabled for the workspace");
}

return response.getVersions().stream()
.map(PipelineDbDto::getVersion)
.filter(Objects::nonNull)
.filter(matcher)
.findFirst()
.orElse(null);
}

protected String resolvePipelineVersionId(Long pipelineId, Long wspId, VersionRefOptions.VersionRef versionRef) throws ApiException {
if (versionRef == null) return null;
if (versionRef.versionId != null) return versionRef.versionId;

PipelineVersionFullInfoDto version = findPipelineVersionByRef(pipelineId, wspId, versionRef);
if (version == null) {
throw new TowerException(String.format("Pipeline version '%s' not found", versionRef.versionName));
}
return version.getId();
}

protected Long sourceWorkspaceId(Long currentWorkspace, PipelineDbDto pipeline) {
if (pipeline == null)
return null;
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/io/seqera/tower/cli/commands/LaunchCmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.seqera.tower.cli.commands.enums.OutputType;
import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions;
import io.seqera.tower.cli.commands.labels.Label;
import io.seqera.tower.cli.commands.pipelines.versions.VersionRefOptions;
import io.seqera.tower.cli.exceptions.InvalidResponseException;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.responses.runs.RunSubmited;
Expand Down Expand Up @@ -82,6 +83,10 @@ public class LaunchCmd extends AbstractRootCmd {
@Option(names = {"--commit-id"}, description = "Specific Git commit hash to pin the pipeline execution to.")
String commitId;

// Explicit "0..1" for clarity — contrasts with the required "1" in VersionRefOptions. @Mixin won't work here as it would lose mutual exclusivity.
@ArgGroup(multiplicity = "0..1")
VersionRefOptions.VersionRef versionRef;

@Option(names = {"--wait"}, description = "Wait until workflow reaches specified status: ${COMPLETION-CANDIDATES}")
public WorkflowStatus wait;

Expand Down Expand Up @@ -177,8 +182,9 @@ protected Response runTowerPipeline(Long wspId) throws ApiException, IOException
}

Long sourceWorkspaceId = sourceWorkspaceId(wspId, pipe);
String versionId = resolvePipelineVersionId(pipe.getPipelineId(), wspId, versionRef);

LaunchDbDto launch = pipelinesApi().describePipelineLaunch(pipe.getPipelineId(), wspId, sourceWorkspaceId, null).getLaunch();
LaunchDbDto launch = pipelinesApi().describePipelineLaunch(pipe.getPipelineId(), wspId, sourceWorkspaceId, versionId).getLaunch();

WorkflowLaunchRequest launchRequest = createLaunchRequest(launch);
if (computeEnv != null) {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/io/seqera/tower/cli/commands/PipelinesCmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,11 @@
import io.seqera.tower.cli.commands.pipelines.DeleteCmd;
import io.seqera.tower.cli.commands.pipelines.ExportCmd;
import io.seqera.tower.cli.commands.pipelines.ImportCmd;
import io.seqera.tower.cli.commands.pipelines.LabelsCmd;
import io.seqera.tower.cli.commands.pipelines.labels.LabelsCmd;
import io.seqera.tower.cli.commands.pipelines.ListCmd;
import io.seqera.tower.cli.commands.pipelines.UpdateCmd;
import io.seqera.tower.cli.commands.pipelines.ViewCmd;
import io.seqera.tower.cli.commands.pipelines.versions.VersionsCmd;
import picocli.CommandLine.Command;


Expand All @@ -39,6 +40,7 @@
ExportCmd.class,
ImportCmd.class,
LabelsCmd.class,
VersionsCmd.class
}
)
public class PipelinesCmd extends AbstractRootCmd {
Expand Down
79 changes: 48 additions & 31 deletions src/main/java/io/seqera/tower/cli/commands/pipelines/AddCmd.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@
import io.seqera.tower.ApiException;
import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions;
import io.seqera.tower.cli.commands.labels.LabelsOptionalOptions;
import io.seqera.tower.cli.commands.pipelines.labels.PipelinesLabelsManager;
import io.seqera.tower.cli.exceptions.TowerException;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.responses.pipelines.PipelinesAdded;
import io.seqera.tower.cli.utils.FilesHelper;
import io.seqera.tower.cli.utils.ResponseHelper;
import io.seqera.tower.model.ComputeEnvResponseDto;
import io.seqera.tower.model.CreatePipelineRequest;
import io.seqera.tower.model.CreatePipelineResponse;
import io.seqera.tower.model.CreatePipelineVersionRequest;
import io.seqera.tower.model.Visibility;
import io.seqera.tower.model.WorkflowLaunchRequest;
import picocli.CommandLine;
Expand Down Expand Up @@ -55,6 +59,9 @@ public class AddCmd extends AbstractPipelinesCmd {
@Parameters(index = "0", paramLabel = "PIPELINE_URL", description = "Nextflow pipeline URL", arity = "1")
public String pipeline;

@Option(names = {"--version-name"}, description = "Initial pipeline version name.")
public String versionName;

@Mixin
public LabelsOptionalOptions labels;

Expand All @@ -77,7 +84,7 @@ protected Response exec() throws ApiException, IOException {
// Retrieve the provided computeEnv or use the primary if not provided
ComputeEnvResponseDto ce = opts.computeEnv != null ? computeEnvByRef(wspId, opts.computeEnv) : null;

// By default use primary compute environment at private workspaces
// By default, use primary compute environment at private workspaces
if (ce == null && visibility == Visibility.PRIVATE) {
ce = primaryComputeEnv(wspId);
if (ce == null) {
Expand All @@ -90,36 +97,46 @@ protected Response exec() throws ApiException, IOException {
String preRunScriptValue = opts.preRunScript == null && ce != null ? ce.getConfig().getPreRunScript() : FilesHelper.readString(opts.preRunScript);
String postRunScriptValue = opts.postRunScript == null && ce != null ? ce.getConfig().getPostRunScript() : FilesHelper.readString(opts.postRunScript);

CreatePipelineResponse response = pipelinesApi().createPipeline(
new CreatePipelineRequest()
.name(name)
.description(description)
.launch(new WorkflowLaunchRequest()
.computeEnvId(ce != null ? ce.getId() : null)
.pipeline(pipeline)
.revision(opts.revision)
.commitId(opts.commitId)
.workDir(workDirValue)
.configProfiles(opts.profile)
.paramsText(FilesHelper.readString(opts.paramsFile))

// Advanced options
.configText(FilesHelper.readString(opts.config))
.preRunScript(preRunScriptValue)
.postRunScript(postRunScriptValue)
.pullLatest(opts.pullLatest)
.stubRun(opts.stubRun)
.mainScript(opts.mainScript)
.entryName(opts.entryName)
.schemaName(opts.schemaName)
.pipelineSchemaId(pipelineSchemaId)
.userSecrets(removeEmptyValues(opts.userSecrets))
.workspaceSecrets(removeEmptyValues(opts.workspaceSecrets))
)
, wspId
);

attachLabels(wspId,response.getPipeline().getPipelineId());
CreatePipelineResponse response;
try {
response = pipelinesApi().createPipeline(
new CreatePipelineRequest()
.name(name)
.description(description)
.version(versionName != null ? new CreatePipelineVersionRequest().name(versionName) : null)
.launch(new WorkflowLaunchRequest()
.computeEnvId(ce != null ? ce.getId() : null)
.pipeline(pipeline)
.revision(opts.revision)
.commitId(opts.commitId)
.workDir(workDirValue)
.configProfiles(opts.profile)
.paramsText(FilesHelper.readString(opts.paramsFile))

// Advanced options
.configText(FilesHelper.readString(opts.config))
.preRunScript(preRunScriptValue)
.postRunScript(postRunScriptValue)
.pullLatest(opts.pullLatest)
.stubRun(opts.stubRun)
.mainScript(opts.mainScript)
.entryName(opts.entryName)
.schemaName(opts.schemaName)
.pipelineSchemaId(pipelineSchemaId)
.userSecrets(removeEmptyValues(opts.userSecrets))
.workspaceSecrets(removeEmptyValues(opts.workspaceSecrets))
)
, wspId
);
} catch (ApiException e) {
throw new TowerException(String.format("Unable to add pipeline '%s': %s", name, ResponseHelper.decodeMessage(e)));
}

try {
attachLabels(wspId, response.getPipeline().getPipelineId());
} catch (ApiException e) {
throw new TowerException(String.format("Pipeline '%s' was created but failed to add labels: %s", name, ResponseHelper.decodeMessage(e)));
}

return new PipelinesAdded(workspaceRef(wspId), response.getPipeline().getName());
}
Expand Down
Loading