Skip to content

Commit 3336496

Browse files
committed
feat: data studios commands add, templates, start-as-new
1 parent 7c627b0 commit 3336496

15 files changed

+903
-66
lines changed

conf/reflect-config.json

+72
Original file line numberDiff line numberDiff line change
@@ -1124,6 +1124,12 @@
11241124
"allDeclaredFields":true,
11251125
"queryAllDeclaredMethods":true
11261126
},
1127+
{
1128+
"name":"io.seqera.tower.cli.commands.datastudios.AddCmd",
1129+
"allDeclaredFields":true,
1130+
"queryAllDeclaredMethods":true,
1131+
"methods":[{"name":"<init>","parameterTypes":[] }]
1132+
},
11271133
{
11281134
"name":"io.seqera.tower.cli.commands.datastudios.DataStudioConfigurationOptions",
11291135
"allDeclaredFields":true,
@@ -1148,12 +1154,24 @@
11481154
"queryAllDeclaredMethods":true,
11491155
"methods":[{"name":"<init>","parameterTypes":[] }]
11501156
},
1157+
{
1158+
"name":"io.seqera.tower.cli.commands.datastudios.StartAsNewCmd",
1159+
"allDeclaredFields":true,
1160+
"queryAllDeclaredMethods":true,
1161+
"methods":[{"name":"<init>","parameterTypes":[] }]
1162+
},
11511163
{
11521164
"name":"io.seqera.tower.cli.commands.datastudios.StartCmd",
11531165
"allDeclaredFields":true,
11541166
"queryAllDeclaredMethods":true,
11551167
"methods":[{"name":"<init>","parameterTypes":[] }]
11561168
},
1169+
{
1170+
"name":"io.seqera.tower.cli.commands.datastudios.TemplatesCmd",
1171+
"allDeclaredFields":true,
1172+
"queryAllDeclaredMethods":true,
1173+
"methods":[{"name":"<init>","parameterTypes":[] }]
1174+
},
11571175
{
11581176
"name":"io.seqera.tower.cli.commands.datastudios.ViewCmd",
11591177
"allDeclaredFields":true,
@@ -1865,6 +1883,18 @@
18651883
"queryAllDeclaredMethods":true,
18661884
"queryAllDeclaredConstructors":true
18671885
},
1886+
{
1887+
"name":"io.seqera.tower.cli.responses.datastudios.DataStudiosCreated",
1888+
"allDeclaredFields":true,
1889+
"queryAllDeclaredMethods":true,
1890+
"queryAllDeclaredConstructors":true
1891+
},
1892+
{
1893+
"name":"io.seqera.tower.cli.responses.datastudios.DataStudiosTemplatesList",
1894+
"allDeclaredFields":true,
1895+
"queryAllDeclaredMethods":true,
1896+
"queryAllDeclaredConstructors":true
1897+
},
18681898
{
18691899
"name":"io.seqera.tower.cli.responses.labels.DeleteLabelsResponse",
18701900
"allDeclaredFields":true,
@@ -2633,6 +2663,12 @@
26332663
"queryAllDeclaredConstructors":true,
26342664
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"fromValue","parameterTypes":["java.lang.String"] }, {"name":"setDataLinks","parameterTypes":["java.util.List"] }, {"name":"setTotalSize","parameterTypes":["java.lang.Long"] }]
26352665
},
2666+
{
2667+
"name":"io.seqera.tower.model.DataStudioCheckpointDto",
2668+
"allDeclaredFields":true,
2669+
"queryAllDeclaredMethods":true,
2670+
"queryAllDeclaredConstructors":true
2671+
},
26362672
{
26372673
"name":"io.seqera.tower.model.DataStudioComputeEnvDto",
26382674
"allDeclaredFields":true,
@@ -2647,6 +2683,20 @@
26472683
"queryAllDeclaredConstructors":true,
26482684
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getCondaEnvironment","parameterTypes":[] }, {"name":"getCpu","parameterTypes":[] }, {"name":"getGpu","parameterTypes":[] }, {"name":"getMemory","parameterTypes":[] }, {"name":"getMountData","parameterTypes":[] }, {"name":"setCondaEnvironment","parameterTypes":["java.lang.String"] }, {"name":"setCpu","parameterTypes":["java.lang.Integer"] }, {"name":"setGpu","parameterTypes":["java.lang.Integer"] }, {"name":"setMemory","parameterTypes":["java.lang.Integer"] }, {"name":"setMountData","parameterTypes":["java.util.List"] }]
26492685
},
2686+
{
2687+
"name":"io.seqera.tower.model.DataStudioCreateRequest",
2688+
"allDeclaredFields":true,
2689+
"queryAllDeclaredMethods":true,
2690+
"queryAllDeclaredConstructors":true,
2691+
"methods":[{"name":"getComputeEnvId","parameterTypes":[] }, {"name":"getConfiguration","parameterTypes":[] }, {"name":"getDataStudioToolUrl","parameterTypes":[] }, {"name":"getDescription","parameterTypes":[] }, {"name":"getInitialCheckpointId","parameterTypes":[] }, {"name":"getName","parameterTypes":[] }]
2692+
},
2693+
{
2694+
"name":"io.seqera.tower.model.DataStudioCreateResponse",
2695+
"allDeclaredFields":true,
2696+
"queryAllDeclaredMethods":true,
2697+
"queryAllDeclaredConstructors":true,
2698+
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setStudio","parameterTypes":["io.seqera.tower.model.DataStudioDto"] }]
2699+
},
26502700
{
26512701
"name":"io.seqera.tower.model.DataStudioDto",
26522702
"allDeclaredFields":true,
@@ -2661,6 +2711,13 @@
26612711
"queryAllDeclaredConstructors":true,
26622712
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getCheckpointId","parameterTypes":[] }, {"name":"getCheckpointName","parameterTypes":[] }, {"name":"getSessionId","parameterTypes":[] }, {"name":"getStudioName","parameterTypes":[] }, {"name":"setCheckpointId","parameterTypes":["java.lang.Long"] }, {"name":"setCheckpointName","parameterTypes":["java.lang.String"] }, {"name":"setSessionId","parameterTypes":["java.lang.String"] }, {"name":"setStudioName","parameterTypes":["java.lang.String"] }]
26632713
},
2714+
{
2715+
"name":"io.seqera.tower.model.DataStudioListCheckpointsResponse",
2716+
"allDeclaredFields":true,
2717+
"queryAllDeclaredMethods":true,
2718+
"queryAllDeclaredConstructors":true,
2719+
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setCheckpoints","parameterTypes":["java.util.List"] }, {"name":"setTotalSize","parameterTypes":["java.lang.Long"] }]
2720+
},
26642721
{
26652722
"name":"io.seqera.tower.model.DataStudioListResponse",
26662723
"allDeclaredFields":true,
@@ -2715,6 +2772,13 @@
27152772
"queryAllDeclaredConstructors":true,
27162773
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"getIcon","parameterTypes":[] }, {"name":"getRepository","parameterTypes":[] }, {"name":"setIcon","parameterTypes":["java.lang.String"] }, {"name":"setRepository","parameterTypes":["java.lang.String"] }]
27172774
},
2775+
{
2776+
"name":"io.seqera.tower.model.DataStudioTemplatesListResponse",
2777+
"allDeclaredFields":true,
2778+
"queryAllDeclaredMethods":true,
2779+
"queryAllDeclaredConstructors":true,
2780+
"methods":[{"name":"<init>","parameterTypes":[] }, {"name":"setTemplates","parameterTypes":["java.util.List"] }, {"name":"setTotalSize","parameterTypes":["java.lang.Long"] }]
2781+
},
27182782
{
27192783
"name":"io.seqera.tower.model.Dataset",
27202784
"allDeclaredFields":true,
@@ -3580,6 +3644,10 @@
35803644
"queryAllDeclaredMethods":true,
35813645
"queryAllDeclaredConstructors":true
35823646
},
3647+
{
3648+
"name":"java.net.UnknownHostException",
3649+
"methods":[{"name":"<init>","parameterTypes":["java.lang.String"] }]
3650+
},
35833651
{
35843652
"name":"java.nio.file.Path"
35853653
},
@@ -4145,6 +4213,10 @@
41454213
"allDeclaredFields":true,
41464214
"methods":[{"name":"objectFieldOffset","parameterTypes":["java.lang.reflect.Field"] }, {"name":"putBoolean","parameterTypes":["java.lang.Object","long","boolean"] }]
41474215
},
4216+
{
4217+
"name":"sun.net.www.protocol.http.ntlm.NTLMAuthentication",
4218+
"methods":[{"name":"<init>","parameterTypes":["boolean","java.lang.String","int","java.net.PasswordAuthentication"] }, {"name":"<init>","parameterTypes":["boolean","java.net.URL","java.net.PasswordAuthentication"] }, {"name":"isTrustedSite","parameterTypes":["java.net.URL"] }, {"name":"supportsTransparentAuth","parameterTypes":[] }]
4219+
},
41484220
{
41494221
"name":"sun.security.pkcs.SignerInfo[]"
41504222
},

src/main/java/io/seqera/tower/cli/commands/DataStudiosCmd.java

+6
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717

1818
package io.seqera.tower.cli.commands;
1919

20+
import io.seqera.tower.cli.commands.datastudios.AddCmd;
2021
import io.seqera.tower.cli.commands.datastudios.ListCmd;
22+
import io.seqera.tower.cli.commands.datastudios.StartAsNewCmd;
2123
import io.seqera.tower.cli.commands.datastudios.StartCmd;
24+
import io.seqera.tower.cli.commands.datastudios.TemplatesCmd;
2225
import io.seqera.tower.cli.commands.datastudios.ViewCmd;
2326
import picocli.CommandLine;
2427

@@ -29,6 +32,9 @@
2932
ViewCmd.class,
3033
ListCmd.class,
3134
StartCmd.class,
35+
AddCmd.class,
36+
TemplatesCmd.class,
37+
StartAsNewCmd.class
3238
}
3339
)
3440
public class DataStudiosCmd extends AbstractRootCmd {

src/main/java/io/seqera/tower/cli/commands/datastudios/AbstractStudiosCmd.java

+67
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,23 @@
1717

1818
package io.seqera.tower.cli.commands.datastudios;
1919

20+
import java.io.IOException;
21+
import java.nio.file.Path;
2022
import java.util.Optional;
2123
import java.util.function.Supplier;
2224

2325
import io.seqera.tower.ApiException;
2426
import io.seqera.tower.cli.commands.AbstractApiCmd;
27+
import io.seqera.tower.cli.commands.enums.OutputType;
28+
import io.seqera.tower.cli.exceptions.TowerException;
29+
import io.seqera.tower.cli.utils.FilesHelper;
30+
import io.seqera.tower.model.DataStudioConfiguration;
2531
import io.seqera.tower.model.DataStudioDto;
2632
import io.seqera.tower.model.DataStudioProgressStep;
33+
import io.seqera.tower.model.DataStudioStatus;
34+
import io.seqera.tower.model.DataStudioStatusInfo;
2735

36+
import static io.seqera.tower.cli.utils.ResponseHelper.waitStatus;
2837
import static io.seqera.tower.model.DataStudioProgressStepStatus.ERRORED;
2938
import static io.seqera.tower.model.DataStudioProgressStepStatus.IN_PROGRESS;
3039

@@ -34,6 +43,64 @@ protected DataStudioDto fetchDataStudio(DataStudioRefOptions dataStudioRefOption
3443
return api().describeDataStudio(dataStudioRefOptions.dataStudio.sessionId, wspId);
3544
}
3645

46+
protected Integer onBeforeExit(int exitCode, String sessionId, Long workspaceId, DataStudioStatus targetStatus) {
47+
boolean showProgress = app().output != OutputType.json;
48+
49+
try {
50+
return waitStatus(
51+
app().getOut(),
52+
showProgress,
53+
new ProgressStepMessageSupplier(sessionId, workspaceId),
54+
targetStatus,
55+
DataStudioStatus.values(),
56+
() -> checkDataStudioStatus(sessionId, workspaceId),
57+
DataStudioStatus.STOPPED, DataStudioStatus.ERRORED, DataStudioStatus.RUNNING
58+
);
59+
} catch (InterruptedException e) {
60+
return exitCode;
61+
}
62+
}
63+
64+
protected DataStudioStatus checkDataStudioStatus(String sessionId, Long workspaceId) {
65+
try {
66+
DataStudioStatusInfo statusInfo = api().describeDataStudio(sessionId, workspaceId).getStatusInfo();
67+
return statusInfo == null ? null : statusInfo.getStatus();
68+
} catch (ApiException e) {
69+
return null;
70+
}
71+
}
72+
73+
protected DataStudioConfiguration dataStudioConfigurationFrom(DataStudioConfigurationOptions configurationOptions, String condaEnvOverride) {
74+
return dataStudioConfigurationFrom(null, configurationOptions, condaEnvOverride);
75+
}
76+
protected DataStudioConfiguration dataStudioConfigurationFrom(DataStudioDto baseStudio, DataStudioConfigurationOptions configurationOptions){
77+
return dataStudioConfigurationFrom(baseStudio, configurationOptions, null);
78+
}
79+
protected DataStudioConfiguration dataStudioConfigurationFrom(DataStudioDto baseStudio, DataStudioConfigurationOptions configOptions, String condaEnvOverride) {
80+
DataStudioConfiguration dataStudioConfiguration = baseStudio == null || baseStudio.getConfiguration() == null
81+
? new DataStudioConfiguration()
82+
: baseStudio.getConfiguration();
83+
84+
dataStudioConfiguration.setGpu(configOptions.gpu == null
85+
? dataStudioConfiguration.getGpu()
86+
: configOptions.gpu);
87+
dataStudioConfiguration.setCpu(configOptions.cpu == null
88+
? dataStudioConfiguration.getCpu()
89+
: configOptions.cpu);
90+
dataStudioConfiguration.setMemory(configOptions.memory == null
91+
? dataStudioConfiguration.getMemory()
92+
: configOptions.memory);
93+
dataStudioConfiguration.setMountData(configOptions.mountData == null || configOptions.mountData.isEmpty()
94+
? dataStudioConfiguration.getMountData()
95+
: configOptions.mountData);
96+
97+
if (condaEnvOverride != null && !condaEnvOverride.isEmpty()) {
98+
dataStudioConfiguration.setCondaEnvironment(condaEnvOverride);
99+
}
100+
101+
return dataStudioConfiguration;
102+
}
103+
37104
public class ProgressStepMessageSupplier implements Supplier<String> {
38105

39106
private final String sessionId;

src/main/java/io/seqera/tower/cli/commands/datastudios/AddCmd.java

+87-17
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,24 @@
1717

1818
package io.seqera.tower.cli.commands.datastudios;
1919

20+
import io.seqera.tower.ApiException;
2021
import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions;
22+
import io.seqera.tower.cli.exceptions.TowerException;
23+
import io.seqera.tower.cli.responses.Response;
24+
import io.seqera.tower.cli.responses.datastudios.DataStudiosCreated;
25+
import io.seqera.tower.cli.utils.FilesHelper;
26+
import io.seqera.tower.model.DataStudioConfiguration;
27+
import io.seqera.tower.model.DataStudioCreateRequest;
28+
import io.seqera.tower.model.DataStudioCreateResponse;
29+
import io.seqera.tower.model.DataStudioDto;
30+
import io.seqera.tower.model.DataStudioStatus;
2131
import picocli.CommandLine;
2232

33+
import java.io.IOException;
2334
import java.nio.file.Path;
2435

36+
import static io.seqera.tower.cli.utils.ResponseHelper.waitStatus;
37+
2538
@CommandLine.Command(
2639
name = "add",
2740
description = "Add new data studio."
@@ -37,32 +50,89 @@ public class AddCmd extends AbstractStudiosCmd{
3750
@CommandLine.Mixin
3851
public WorkspaceOptionalOptions workspace;
3952

40-
// completion candidates?
41-
// preprocessor ?
4253
@CommandLine.Option(names = {"-t", "--template"}, description = "Data studio template container image. Available templates can be listed with 'studios templates' command", required = true)
4354
public String template;
4455

45-
// add template endpoint
46-
47-
4856
@CommandLine.Option(names = {"-c", "--compute-env"}, description = "Compute environment name.", required = true)
4957
public String computeEnv;
5058

51-
@CommandLine.Option(names = {"--gpu"}, description = "The number of GPUs allocated to the data studio, defaults to 0", defaultValue = "0")
52-
public String gpu;
53-
54-
@CommandLine.Option(names = {"--cpu"}, description = "The number of CPUs allocated to the data studio, defaults to 2", defaultValue = "2")
55-
public String cpu;
56-
57-
@CommandLine.Option(names = {"--memory"}, description = "The maximum memory allocated to the data studio, defaults to 8192", defaultValue = "8192")
58-
public String memory;
59-
60-
//mounted data ????
59+
@CommandLine.Mixin
60+
public DataStudioConfigurationOptions dataStudioConfigOptions;
6161

62-
// start as new - how to model it???
6362
@CommandLine.Option(names = {"--conda-env-yml", "--conda-env-yaml"}, description = "Path to a YAML env file with Conda packages to be installed in the Data Studio environment.")
6463
public Path condaEnv;
6564

66-
@CommandLine.Option(names = {"-a", "--autoStart"}, description = "Create Data Studio and start it immediately, defaults to true", defaultValue = "true")
65+
@CommandLine.Option(names = {"-a", "--autoStart"}, description = "Create Data Studio and start it immediately, defaults to false", defaultValue = "false")
6766
public Boolean autoStart;
67+
68+
@CommandLine.Option(names = {"--wait"}, description = "Wait until DataStudio is in RUNNING status. Valid options: ${COMPLETION-CANDIDATES}.")
69+
public DataStudioStatus wait;
70+
71+
@Override
72+
protected Response exec() throws ApiException {
73+
Long wspId = workspaceId(workspace.workspace);
74+
75+
try {
76+
DataStudioCreateRequest request = prepareRequest();
77+
DataStudioCreateResponse response = api().createDataStudio(request, wspId, autoStart);
78+
DataStudioDto dataStudioDto = response.getStudio();
79+
assert dataStudioDto != null;
80+
return new DataStudiosCreated(dataStudioDto.getSessionId(), wspId, workspaceRef(wspId), autoStart);
81+
} catch (ApiException e) {
82+
if (e.getCode() == 403) {
83+
throw new TowerException(String.format("User not entitled to create studio at %s workspace", wspId));
84+
}
85+
throw e;
86+
}
87+
}
88+
89+
DataStudioCreateRequest prepareRequest() throws TowerException {
90+
DataStudioCreateRequest request = new DataStudioCreateRequest();
91+
request.setName(name);
92+
if (description != null && !description.isEmpty()) {request.description(description);}
93+
request.setDataStudioToolUrl(template);
94+
request.setComputeEnvId(computeEnv);
95+
96+
String condaEnvString = null;
97+
if (condaEnv != null) {
98+
try {
99+
condaEnvString = FilesHelper.readString(condaEnv);
100+
} catch (IOException e) {
101+
throw new TowerException(String.format("Cannot read conda environment file: %s. %s", condaEnv, e));
102+
}
103+
}
104+
105+
106+
DataStudioConfiguration newConfig = dataStudioConfigurationFrom(dataStudioConfigOptions, condaEnvString);
107+
request.setConfiguration(setDefaults(newConfig));
108+
return request;
109+
}
110+
111+
DataStudioConfiguration setDefaults(DataStudioConfiguration config) {
112+
if (config.getGpu() == null) {
113+
config.setGpu(0);
114+
}
115+
if (config.getCpu() == null) {
116+
config.setCpu(2);
117+
}
118+
if (config.getMemory() == null) {
119+
config.setMemory(8192);
120+
}
121+
return config;
122+
}
123+
124+
@Override
125+
protected Integer onBeforeExit(int exitCode, Response response) {
126+
127+
if (!autoStart) {
128+
return exitCode;
129+
}
130+
131+
if (exitCode != 0 || wait == null || response == null) {
132+
return exitCode;
133+
}
134+
135+
DataStudiosCreated createdResponse = ((DataStudiosCreated) response);
136+
return onBeforeExit(exitCode, createdResponse.sessionId, createdResponse.workspaceId, wait);
137+
}
68138
}

0 commit comments

Comments
 (0)