Skip to content

Commit

Permalink
Plat 1244/create data studio command (#479)
Browse files Browse the repository at this point in the history
* feat: data studios add command

* feat: data studios commands add, templates, start-as-new
  • Loading branch information
endre-seqera authored Jan 29, 2025
1 parent d60e842 commit b406210
Show file tree
Hide file tree
Showing 15 changed files with 961 additions and 55 deletions.
85 changes: 79 additions & 6 deletions conf/reflect-config.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@

package io.seqera.tower.cli.commands;

import io.seqera.tower.cli.commands.datastudios.AddCmd;
import io.seqera.tower.cli.commands.datastudios.ListCmd;
import io.seqera.tower.cli.commands.datastudios.StartAsNewCmd;
import io.seqera.tower.cli.commands.datastudios.StartCmd;
import io.seqera.tower.cli.commands.datastudios.TemplatesCmd;
import io.seqera.tower.cli.commands.datastudios.ViewCmd;
import picocli.CommandLine;

Expand All @@ -29,6 +32,9 @@
ViewCmd.class,
ListCmd.class,
StartCmd.class,
AddCmd.class,
TemplatesCmd.class,
StartAsNewCmd.class
}
)
public class DataStudiosCmd extends AbstractRootCmd {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,23 @@

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

import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.function.Supplier;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.commands.AbstractApiCmd;
import io.seqera.tower.cli.commands.enums.OutputType;
import io.seqera.tower.cli.exceptions.TowerException;
import io.seqera.tower.cli.utils.FilesHelper;
import io.seqera.tower.model.DataStudioConfiguration;
import io.seqera.tower.model.DataStudioDto;
import io.seqera.tower.model.DataStudioProgressStep;
import io.seqera.tower.model.DataStudioStatus;
import io.seqera.tower.model.DataStudioStatusInfo;

import static io.seqera.tower.cli.utils.ResponseHelper.waitStatus;
import static io.seqera.tower.model.DataStudioProgressStepStatus.ERRORED;
import static io.seqera.tower.model.DataStudioProgressStepStatus.IN_PROGRESS;

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

protected Integer onBeforeExit(int exitCode, String sessionId, Long workspaceId, DataStudioStatus targetStatus) {
boolean showProgress = app().output != OutputType.json;

try {
return waitStatus(
app().getOut(),
showProgress,
new ProgressStepMessageSupplier(sessionId, workspaceId),
targetStatus,
DataStudioStatus.values(),
() -> checkDataStudioStatus(sessionId, workspaceId),
DataStudioStatus.STOPPED, DataStudioStatus.ERRORED, DataStudioStatus.RUNNING
);
} catch (InterruptedException e) {
return exitCode;
}
}

protected DataStudioStatus checkDataStudioStatus(String sessionId, Long workspaceId) {
try {
DataStudioStatusInfo statusInfo = api().describeDataStudio(sessionId, workspaceId).getStatusInfo();
return statusInfo == null ? null : statusInfo.getStatus();
} catch (ApiException e) {
return null;
}
}

protected DataStudioConfiguration dataStudioConfigurationFrom(DataStudioConfigurationOptions configurationOptions, String condaEnvOverride) {
return dataStudioConfigurationFrom(null, configurationOptions, condaEnvOverride);
}
protected DataStudioConfiguration dataStudioConfigurationFrom(DataStudioDto baseStudio, DataStudioConfigurationOptions configurationOptions){
return dataStudioConfigurationFrom(baseStudio, configurationOptions, null);
}
protected DataStudioConfiguration dataStudioConfigurationFrom(DataStudioDto baseStudio, DataStudioConfigurationOptions configOptions, String condaEnvOverride) {
DataStudioConfiguration dataStudioConfiguration = baseStudio == null || baseStudio.getConfiguration() == null
? new DataStudioConfiguration()
: baseStudio.getConfiguration();

dataStudioConfiguration.setGpu(configOptions.gpu == null
? dataStudioConfiguration.getGpu()
: configOptions.gpu);
dataStudioConfiguration.setCpu(configOptions.cpu == null
? dataStudioConfiguration.getCpu()
: configOptions.cpu);
dataStudioConfiguration.setMemory(configOptions.memory == null
? dataStudioConfiguration.getMemory()
: configOptions.memory);
dataStudioConfiguration.setMountData(configOptions.mountData == null || configOptions.mountData.isEmpty()
? dataStudioConfiguration.getMountData()
: configOptions.mountData);

if (condaEnvOverride != null && !condaEnvOverride.isEmpty()) {
dataStudioConfiguration.setCondaEnvironment(condaEnvOverride);
}

return dataStudioConfiguration;
}

public class ProgressStepMessageSupplier implements Supplier<String> {

private final String sessionId;
Expand Down
138 changes: 138 additions & 0 deletions src/main/java/io/seqera/tower/cli/commands/datastudios/AddCmd.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
* Copyright 2021-2023, Seqera.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

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

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions;
import io.seqera.tower.cli.exceptions.TowerException;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.responses.datastudios.DataStudiosCreated;
import io.seqera.tower.cli.utils.FilesHelper;
import io.seqera.tower.model.DataStudioConfiguration;
import io.seqera.tower.model.DataStudioCreateRequest;
import io.seqera.tower.model.DataStudioCreateResponse;
import io.seqera.tower.model.DataStudioDto;
import io.seqera.tower.model.DataStudioStatus;
import picocli.CommandLine;

import java.io.IOException;
import java.nio.file.Path;

import static io.seqera.tower.cli.utils.ResponseHelper.waitStatus;

@CommandLine.Command(
name = "add",
description = "Add new data studio."
)
public class AddCmd extends AbstractStudiosCmd{

@CommandLine.Option(names = {"-n", "--name"}, description = "Data Studio name.", required = true)
public String name;

@CommandLine.Option(names = {"-d", "--description"}, description = "Data studio description.")
public String description;

@CommandLine.Mixin
public WorkspaceOptionalOptions workspace;

@CommandLine.Option(names = {"-t", "--template"}, description = "Data studio template container image. Available templates can be listed with 'studios templates' command", required = true)
public String template;

@CommandLine.Option(names = {"-c", "--compute-env"}, description = "Compute environment name.", required = true)
public String computeEnv;

@CommandLine.Mixin
public DataStudioConfigurationOptions dataStudioConfigOptions;

@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.")
public Path condaEnv;

@CommandLine.Option(names = {"-a", "--autoStart"}, description = "Create Data Studio and start it immediately, defaults to false", defaultValue = "false")
public Boolean autoStart;

@CommandLine.Option(names = {"--wait"}, description = "Wait until DataStudio is in RUNNING status. Valid options: ${COMPLETION-CANDIDATES}.")
public DataStudioStatus wait;

@Override
protected Response exec() throws ApiException {
Long wspId = workspaceId(workspace.workspace);

try {
DataStudioCreateRequest request = prepareRequest();
DataStudioCreateResponse response = api().createDataStudio(request, wspId, autoStart);
DataStudioDto dataStudioDto = response.getStudio();
assert dataStudioDto != null;
return new DataStudiosCreated(dataStudioDto.getSessionId(), wspId, workspaceRef(wspId), autoStart);
} catch (ApiException e) {
if (e.getCode() == 403) {
throw new TowerException(String.format("User not entitled to create studio at %s workspace", wspId));
}
throw e;
}
}

DataStudioCreateRequest prepareRequest() throws TowerException {
DataStudioCreateRequest request = new DataStudioCreateRequest();
request.setName(name);
if (description != null && !description.isEmpty()) {request.description(description);}
request.setDataStudioToolUrl(template);
request.setComputeEnvId(computeEnv);

String condaEnvString = null;
if (condaEnv != null) {
try {
condaEnvString = FilesHelper.readString(condaEnv);
} catch (IOException e) {
throw new TowerException(String.format("Cannot read conda environment file: %s. %s", condaEnv, e));
}
}


DataStudioConfiguration newConfig = dataStudioConfigurationFrom(dataStudioConfigOptions, condaEnvString);
request.setConfiguration(setDefaults(newConfig));
return request;
}

DataStudioConfiguration setDefaults(DataStudioConfiguration config) {
if (config.getGpu() == null) {
config.setGpu(0);
}
if (config.getCpu() == null) {
config.setCpu(2);
}
if (config.getMemory() == null) {
config.setMemory(8192);
}
return config;
}

@Override
protected Integer onBeforeExit(int exitCode, Response response) {

if (!autoStart) {
return exitCode;
}

if (exitCode != 0 || wait == null || response == null) {
return exitCode;
}

DataStudiosCreated createdResponse = ((DataStudiosCreated) response);
return onBeforeExit(exitCode, createdResponse.sessionId, createdResponse.workspaceId, wait);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@

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

import java.nio.file.Path;
import java.util.List;

import picocli.CommandLine;

public class DataStudioConfigurationOptions {

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

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

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

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

}
Loading

0 comments on commit b406210

Please sign in to comment.