Skip to content

Commit b406210

Browse files
authored
Plat 1244/create data studio command (#479)
* feat: data studios add command * feat: data studios commands add, templates, start-as-new
1 parent d60e842 commit b406210

15 files changed

+961
-55
lines changed

conf/reflect-config.json

+79-6
Large diffs are not rendered by default.

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;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2021-2023, Seqera.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package io.seqera.tower.cli.commands.datastudios;
19+
20+
import io.seqera.tower.ApiException;
21+
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;
31+
import picocli.CommandLine;
32+
33+
import java.io.IOException;
34+
import java.nio.file.Path;
35+
36+
import static io.seqera.tower.cli.utils.ResponseHelper.waitStatus;
37+
38+
@CommandLine.Command(
39+
name = "add",
40+
description = "Add new data studio."
41+
)
42+
public class AddCmd extends AbstractStudiosCmd{
43+
44+
@CommandLine.Option(names = {"-n", "--name"}, description = "Data Studio name.", required = true)
45+
public String name;
46+
47+
@CommandLine.Option(names = {"-d", "--description"}, description = "Data studio description.")
48+
public String description;
49+
50+
@CommandLine.Mixin
51+
public WorkspaceOptionalOptions workspace;
52+
53+
@CommandLine.Option(names = {"-t", "--template"}, description = "Data studio template container image. Available templates can be listed with 'studios templates' command", required = true)
54+
public String template;
55+
56+
@CommandLine.Option(names = {"-c", "--compute-env"}, description = "Compute environment name.", required = true)
57+
public String computeEnv;
58+
59+
@CommandLine.Mixin
60+
public DataStudioConfigurationOptions dataStudioConfigOptions;
61+
62+
@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.")
63+
public Path condaEnv;
64+
65+
@CommandLine.Option(names = {"-a", "--autoStart"}, description = "Create Data Studio and start it immediately, defaults to false", defaultValue = "false")
66+
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+
}
138+
}

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,22 @@
1717

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

20+
import java.nio.file.Path;
2021
import java.util.List;
2122

2223
import picocli.CommandLine;
2324

2425
public class DataStudioConfigurationOptions {
2526

26-
@CommandLine.Option(names = {"-g", "--gpu"}, description = "Optional configuration override for 'gpu' setting (Integer representing number of cores)")
27+
@CommandLine.Option(names = {"--gpu"}, description = "Optional configuration override for 'gpu' setting (Integer representing number of cores)")
2728
public Integer gpu;
2829

29-
@CommandLine.Option(names = {"-c", "--cpu"}, description = "Optional configuration override for 'cpu' setting (Integer representing number of cores)")
30+
@CommandLine.Option(names = {"--cpu"}, description = "Optional configuration override for 'cpu' setting (Integer representing number of cores)")
3031
public Integer cpu;
3132

32-
@CommandLine.Option(names = {"-m", "--memory"}, description = "Optional configuration override for 'memory' setting (Integer representing memory in MBs)")
33+
@CommandLine.Option(names = {"--memory"}, description = "Optional configuration override for 'memory' setting (Integer representing memory in MBs)")
3334
public Integer memory;
3435

3536
@CommandLine.Option(names = {"--mount-data"}, description = "Optional configuration override for 'mountData' setting (comma separate list of datalinkIds)", split = ",")
3637
public List<String> mountData;
37-
3838
}

0 commit comments

Comments
 (0)