diff --git a/conf/jni-config.json b/conf/jni-config.json index dc4ab7eb..b565f742 100644 --- a/conf/jni-config.json +++ b/conf/jni-config.json @@ -9,24 +9,15 @@ }, { "name":"java.lang.ClassLoader", - "methods":[ - {"name":"getPlatformClassLoader","parameterTypes":[] }, - {"name":"loadClass","parameterTypes":["java.lang.String"] } - ] + "methods":[{"name":"getPlatformClassLoader","parameterTypes":[] }, {"name":"loadClass","parameterTypes":["java.lang.String"] }] }, { "name":"java.lang.String", - "methods":[ - {"name":"lastIndexOf","parameterTypes":["int"] }, - {"name":"substring","parameterTypes":["int"] } - ] + "methods":[{"name":"lastIndexOf","parameterTypes":["int"] }, {"name":"substring","parameterTypes":["int"] }] }, { "name":"java.lang.System", - "methods":[ - {"name":"getProperty","parameterTypes":["java.lang.String"] }, - {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] } - ] + "methods":[{"name":"getProperty","parameterTypes":["java.lang.String"] }, {"name":"setProperty","parameterTypes":["java.lang.String","java.lang.String"] }] }, { "name":"jdk.internal.loader.ClassLoaders$PlatformClassLoader" diff --git a/conf/reflect-config.json b/conf/reflect-config.json index 7dacaa41..0ba67222 100644 --- a/conf/reflect-config.json +++ b/conf/reflect-config.json @@ -1663,6 +1663,12 @@ "queryAllDeclaredMethods":true, "queryAllDeclaredConstructors":true }, +{ + "name":"io.seqera.tower.cli.responses.computeenvs.ComputeEnvUpdated", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true +}, { "name":"io.seqera.tower.cli.responses.computeenvs.ComputeEnvExport", "allDeclaredFields":true, @@ -2181,7 +2187,8 @@ "name":"io.seqera.tower.model.AwsBatchConfig", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"getCliPath","parameterTypes":[] }, {"name":"getComputeJobRole","parameterTypes":[] }, {"name":"getComputeQueue","parameterTypes":[] }, {"name":"getDiscriminator","parameterTypes":[] }, {"name":"getDragenQueue","parameterTypes":[] }, {"name":"getEnvironment","parameterTypes":[] }, {"name":"getExecutionRole","parameterTypes":[] }, {"name":"getForge","parameterTypes":[] }, {"name":"getForgedResources","parameterTypes":[] }, {"name":"getFusion2Enabled","parameterTypes":[] }, {"name":"getHeadJobCpus","parameterTypes":[] }, {"name":"getHeadJobMemoryMb","parameterTypes":[] }, {"name":"getHeadJobRole","parameterTypes":[] }, {"name":"getHeadQueue","parameterTypes":[] }, {"name":"getLogGroup","parameterTypes":[] }, {"name":"getNvnmeStorageEnabled","parameterTypes":[] }, {"name":"getPostRunScript","parameterTypes":[] }, {"name":"getPreRunScript","parameterTypes":[] }, {"name":"getRegion","parameterTypes":[] }, {"name":"getVolumes","parameterTypes":[] }, {"name":"getWaveEnabled","parameterTypes":[] }, {"name":"getWorkDir","parameterTypes":[] }, {"name":"setCliPath","parameterTypes":["java.lang.String"] }, {"name":"setComputeJobRole","parameterTypes":["java.lang.String"] }, {"name":"setComputeQueue","parameterTypes":["java.lang.String"] }, {"name":"setDragenQueue","parameterTypes":["java.lang.String"] }, {"name":"setEnvironment","parameterTypes":["java.util.List"] }, {"name":"setExecutionRole","parameterTypes":["java.lang.String"] }, {"name":"setForge","parameterTypes":["io.seqera.tower.model.ForgeConfig"] }, {"name":"setForgedResources","parameterTypes":["java.util.List"] }, {"name":"setFusion2Enabled","parameterTypes":["java.lang.Boolean"] }, {"name":"setHeadJobCpus","parameterTypes":["java.lang.Integer"] }, {"name":"setHeadJobMemoryMb","parameterTypes":["java.lang.Integer"] }, {"name":"setHeadJobRole","parameterTypes":["java.lang.String"] }, {"name":"setHeadQueue","parameterTypes":["java.lang.String"] }, {"name":"setLogGroup","parameterTypes":["java.lang.String"] }, {"name":"setNvnmeStorageEnabled","parameterTypes":["java.lang.Boolean"] }, {"name":"setPostRunScript","parameterTypes":["java.lang.String"] }, {"name":"setPreRunScript","parameterTypes":["java.lang.String"] }, {"name":"setRegion","parameterTypes":["java.lang.String"] }, {"name":"setVolumes","parameterTypes":["java.util.List"] }, {"name":"setWaveEnabled","parameterTypes":["java.lang.Boolean"] }, {"name":"setWorkDir","parameterTypes":["java.lang.String"] }] }, { "name":"io.seqera.tower.model.AwsSecurityKeys", @@ -2299,7 +2306,8 @@ { "name":"io.seqera.tower.model.ComputeEnvStatus", "allDeclaredFields":true, - "allDeclaredMethods":true + "allDeclaredMethods":true, + "methods":[{"name":"fromValue","parameterTypes":["java.lang.String"] }] }, { "name":"io.seqera.tower.model.ComputePlatformDto", @@ -2556,7 +2564,8 @@ "name":"io.seqera.tower.model.DescribeComputeEnvResponse", "allDeclaredFields":true, "allDeclaredMethods":true, - "allDeclaredConstructors":true + "allDeclaredConstructors":true, + "methods":[{"name":"","parameterTypes":[] }, {"name":"setComputeEnv","parameterTypes":["io.seqera.tower.model.ComputeEnvResponseDto"] }] }, { "name":"io.seqera.tower.model.DescribeCredentialsResponse", @@ -3054,11 +3063,6 @@ "allDeclaredMethods":true, "allDeclaredConstructors":true }, -{ - "name":"io.seqera.tower.model.PipelineOptimizationStatus", - "allDeclaredFields":true, - "queryAllDeclaredMethods":true -}, { "name":"io.seqera.tower.model.PipelineSecret", "allDeclaredFields":true, @@ -3176,6 +3180,13 @@ "allDeclaredMethods":true, "allDeclaredConstructors":true }, +{ + "name":"io.seqera.tower.model.UpdateComputeEnvRequest", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true, + "queryAllDeclaredConstructors":true, + "methods":[{"name":"getName","parameterTypes":[] }] +}, { "name":"io.seqera.tower.model.UpdateCredentialsRequest", "allDeclaredFields":true, diff --git a/conf/resource-config.json b/conf/resource-config.json index 3d2ad351..d7a04dc3 100644 --- a/conf/resource-config.json +++ b/conf/resource-config.json @@ -1,30 +1,29 @@ { "resources":{ - "includes":[ - { - "pattern":"\\QMETA-INF/build-info.properties\\E" - }, - { - "pattern":"\\QMETA-INF/services/org.glassfish.hk2.extension.ServiceLocatorGenerator\\E" - }, - { - "pattern":"\\QMETA-INF/services/org.glassfish.jersey.internal.inject.InjectionManagerFactory\\E" - }, - { - "pattern":"\\QMETA-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable\\E" - } - ]}, - "bundles":[ - { - "name":"org.glassfish.jersey.client.internal.localization", - "locales":["und"] - }, - { - "name":"org.glassfish.jersey.internal.localization", - "locales":["und"] - }, - { - "name":"org.glassfish.jersey.media.multipart.internal.localization" - } - ] + "includes":[{ + "pattern":"\\QMETA-INF/build-info.properties\\E" + }, { + "pattern":"\\QMETA-INF/services/org.glassfish.hk2.extension.ServiceLocatorGenerator\\E" + }, { + "pattern":"\\QMETA-INF/services/org.glassfish.jersey.internal.inject.InjectionManagerFactory\\E" + }, { + "pattern":"\\QMETA-INF/services/org.glassfish.jersey.internal.spi.AutoDiscoverable\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt67b/nfkc.nrm\\E" + }, { + "pattern":"java.base:\\Qjdk/internal/icu/impl/data/icudt67b/uprops.icu\\E" + }, { + "pattern":"java.base:\\Qsun/net/idn/uidna.spp\\E" + }, { + "pattern":"java.base:\\Qsun/text/resources/LineBreakIteratorData\\E" + }]}, + "bundles":[{ + "name":"org.glassfish.jersey.client.internal.localization", + "locales":["", "und"] + }, { + "name":"org.glassfish.jersey.internal.localization", + "locales":["", "und"] + }, { + "name":"org.glassfish.jersey.media.multipart.internal.localization" + }] } diff --git a/conf/serialization-config.json b/conf/serialization-config.json index 0d4f101c..f3d7e06e 100644 --- a/conf/serialization-config.json +++ b/conf/serialization-config.json @@ -1,2 +1,8 @@ -[ -] +{ + "types":[ + ], + "lambdaCapturingTypes":[ + ], + "proxies":[ + ] +} diff --git a/src/main/java/io/seqera/tower/cli/commands/ComputeEnvsCmd.java b/src/main/java/io/seqera/tower/cli/commands/ComputeEnvsCmd.java index 734dfb9a..3e9c0032 100644 --- a/src/main/java/io/seqera/tower/cli/commands/ComputeEnvsCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/ComputeEnvsCmd.java @@ -17,6 +17,7 @@ import io.seqera.tower.cli.commands.computeenvs.ImportCmd; import io.seqera.tower.cli.commands.computeenvs.ListCmd; import io.seqera.tower.cli.commands.computeenvs.PrimaryCmd; +import io.seqera.tower.cli.commands.computeenvs.UpdateCmd; import io.seqera.tower.cli.commands.computeenvs.ViewCmd; import picocli.CommandLine.Command; @@ -26,6 +27,7 @@ description = "Manage workspace compute environments.", subcommands = { AddCmd.class, + UpdateCmd.class, DeleteCmd.class, ViewCmd.class, ListCmd.class, diff --git a/src/main/java/io/seqera/tower/cli/commands/actions/UpdateCmd.java b/src/main/java/io/seqera/tower/cli/commands/actions/UpdateCmd.java index 764b7ff4..acfb4bc2 100644 --- a/src/main/java/io/seqera/tower/cli/commands/actions/UpdateCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/actions/UpdateCmd.java @@ -14,6 +14,7 @@ import io.seqera.tower.ApiException; import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions; import io.seqera.tower.cli.commands.pipelines.LaunchOptions; +import io.seqera.tower.cli.exceptions.InvalidResponseException; import io.seqera.tower.cli.exceptions.TowerException; import io.seqera.tower.cli.responses.Response; import io.seqera.tower.cli.responses.actions.ActionUpdate; @@ -38,6 +39,9 @@ public class UpdateCmd extends AbstractActionsCmd { @CommandLine.Option(names = {"-s", "--status"}, description = "Action status (pause or active).") public String status; + @CommandLine.Option(names = {"--new-name"}, description = "Action new name.") + public String newName; + @CommandLine.Mixin public WorkspaceOptionalOptions workspace; @@ -50,6 +54,15 @@ protected Response exec() throws ApiException, IOException { ActionResponseDto action = fetchDescribeActionResponse(actionRefOptions, wspId).getAction(); String actionName = action.getName(); + // Validate new action name if any + if (newName != null) { + try { + api().validateActionName(wspId, newName); + } catch (ApiException ex) { + throw new InvalidResponseException(String.format("Action name '%s' is not valid", newName)); + } + } + // Retrieve the provided computeEnv or use the primary if not provided String ceId = opts.computeEnv != null ? computeEnvByRef(wspId, opts.computeEnv).getId() : action.getLaunch().getComputeEnv().getId(); @@ -78,6 +91,7 @@ protected Response exec() throws ApiException, IOException { .entryName(opts.entryName); UpdateActionRequest request = new UpdateActionRequest(); + request.setName(newName != null ? newName : actionName); request.setLaunch(workflowLaunchRequest); try { diff --git a/src/main/java/io/seqera/tower/cli/commands/computeenvs/UpdateCmd.java b/src/main/java/io/seqera/tower/cli/commands/computeenvs/UpdateCmd.java new file mode 100644 index 00000000..0f85819c --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/commands/computeenvs/UpdateCmd.java @@ -0,0 +1,83 @@ +/* + * 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.computeenvs; + +import io.seqera.tower.ApiException; +import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions; +import io.seqera.tower.cli.exceptions.ComputeEnvNotFoundException; +import io.seqera.tower.cli.exceptions.InvalidResponseException; +import io.seqera.tower.cli.responses.Response; +import io.seqera.tower.cli.responses.computeenvs.ComputeEnvUpdated; +import io.seqera.tower.model.ComputeEnvResponseDto; +import io.seqera.tower.model.UpdateComputeEnvRequest; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +import java.io.IOException; + +@Command( + name = "update", + description = "Update compute environments." +) +public class UpdateCmd extends AbstractComputeEnvCmd { + + @Mixin + public ComputeEnvRefOptions computeEnvRefOptions; + + @Mixin + public WorkspaceOptionalOptions workspace; + + @Option(names = {"--new-name"}, description = "Compute environment new name.") + public String newName; + + @Override + protected Response exec() throws ApiException, IOException { + Long wspId = workspaceId(workspace.workspace); + + if (newName != null) { + try { + api().validateComputeEnvName(wspId, newName); + } catch (ApiException ex) { + throw new InvalidResponseException(String.format("Compute environment name '%s' is not valid", newName)); + } + } + ComputeEnvResponseDto ce = describeCE(computeEnvRefOptions, wspId); + + + UpdateComputeEnvRequest req = new UpdateComputeEnvRequest() + .name(newName != null ? newName : ce.getName()); + + api().updateComputeEnv(ce.getId(), req, wspId); + + + return new ComputeEnvUpdated(workspaceRef(wspId), ce.getName()); + + } + + private ComputeEnvResponseDto describeCE(ComputeEnvRefOptions computeEnvRefOptions, Long wspId) throws ComputeEnvNotFoundException, ApiException { + try { + return fetchComputeEnv(computeEnvRefOptions, wspId); + + } catch (ApiException e) { + if (e.getCode() == 403) { + String ref = computeEnvRefOptions.computeEnv.computeEnvId != null + ? computeEnvRefOptions.computeEnv.computeEnvId + : computeEnvRefOptions.computeEnv.computeEnvName; + // Customize the forbidden message + throw new ComputeEnvNotFoundException(ref, workspaceRef(wspId)); + } + + throw e; + } + } +} diff --git a/src/main/java/io/seqera/tower/cli/commands/organizations/UpdateCmd.java b/src/main/java/io/seqera/tower/cli/commands/organizations/UpdateCmd.java index 8afff2a3..ceb59956 100644 --- a/src/main/java/io/seqera/tower/cli/commands/organizations/UpdateCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/organizations/UpdateCmd.java @@ -30,6 +30,9 @@ public class UpdateCmd extends AbstractOrganizationsCmd { @CommandLine.Mixin OrganizationRefOptions organizationRefOptions; + @CommandLine.Option(names = {"--new-name"}, description = "Organization new name.") + public String newName; + @CommandLine.Option(names = {"-f", "--full-name"}, description = "Organization full name.") public String fullName; @@ -43,6 +46,7 @@ protected Response exec() throws ApiException, IOException { OrganizationDbDto organization = response.getOrganization(); UpdateOrganizationRequest request = new UpdateOrganizationRequest(); + request.setName(newName != null ? newName : organization.getName()); request.setFullName(fullName != null ? fullName : organization.getFullName()); request.setDescription(opts.description != null ? opts.description : organization.getDescription()); request.setLocation(opts.location != null ? opts.location : organization.getLocation()); 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 7df80d4f..95b8b941 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 @@ -13,6 +13,7 @@ import io.seqera.tower.ApiException; import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions; +import io.seqera.tower.cli.exceptions.InvalidResponseException; import io.seqera.tower.cli.responses.Response; import io.seqera.tower.cli.responses.pipelines.PipelinesUpdated; import io.seqera.tower.cli.utils.FilesHelper; @@ -47,6 +48,9 @@ public class UpdateCmd extends AbstractPipelinesCmd { @Option(names = {"-d", "--description"}, description = "Pipeline description.") public String description; + @Option(names = {"--new-name"}, description = "Pipeline new name.") + public String newName; + @Mixin public LaunchOptions opts; @@ -55,7 +59,9 @@ public class UpdateCmd extends AbstractPipelinesCmd { @Override protected Response exec() throws ApiException, IOException { + Long wspId = workspaceId(workspace.workspace); + Long orgId = wspId != null ? orgId(wspId) : null; PipelineDbDto pipe; Long id; @@ -67,39 +73,46 @@ protected Response exec() throws ApiException, IOException { id = pipe.getPipelineId(); } + if (newName != null) { + try { + api().validatePipelineName(wspId, orgId, newName); + } catch (ApiException ex) { + throw new InvalidResponseException(String.format("Pipeline name '%s' is not valid", newName)); + } + } + 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(); - UpdatePipelineResponse response = api().updatePipeline( - pipe.getPipelineId(), - new UpdatePipelineRequest() - .description(coalesce(description, pipe.getDescription())) - .launch(new WorkflowLaunchRequest() - .computeEnvId(ceId) - .pipeline(coalesce(pipeline, launch.getPipeline())) - .revision(coalesce(opts.revision, launch.getRevision())) - .workDir(coalesce(opts.workDir, launch.getWorkDir())) - .configProfiles(coalesce(opts.profile, launch.getConfigProfiles())) - .paramsText(coalesce(FilesHelper.readString(opts.paramsFile), launch.getParamsText())) - - // Advanced options - .configText(coalesce(FilesHelper.readString(opts.config), launch.getConfigText())) - .preRunScript(coalesce(FilesHelper.readString(opts.preRunScript), launch.getPreRunScript())) - .postRunScript(coalesce(FilesHelper.readString(opts.postRunScript), launch.getPostRunScript())) - .pullLatest(coalesce(opts.pullLatest, launch.getPullLatest())) - .stubRun(coalesce(opts.stubRun, launch.getStubRun())) - .mainScript(coalesce(opts.mainScript, launch.getMainScript())) - .entryName(coalesce(opts.entryName, launch.getEntryName())) - .schemaName(coalesce(opts.schemaName, launch.getSchemaName())) - .userSecrets(coalesce(removeEmptyValues(opts.userSecrets), launch.getUserSecrets())) - .workspaceSecrets(coalesce(removeEmptyValues(opts.workspaceSecrets), launch.getWorkspaceSecrets())) - ) - , wspId - ); - - return new PipelinesUpdated(workspaceRef(wspId), response.getPipeline().getName()); + UpdatePipelineRequest updateReq = new UpdatePipelineRequest() + .name(coalesce(newName, pipe.getName())) + .description(coalesce(description, pipe.getDescription())) + .launch(new WorkflowLaunchRequest() + .computeEnvId(ceId) + .pipeline(coalesce(pipeline, launch.getPipeline())) + .revision(coalesce(opts.revision, launch.getRevision())) + .workDir(coalesce(opts.workDir, launch.getWorkDir())) + .configProfiles(coalesce(opts.profile, launch.getConfigProfiles())) + .paramsText(coalesce(FilesHelper.readString(opts.paramsFile), launch.getParamsText())) + + // Advanced options + .configText(coalesce(FilesHelper.readString(opts.config), launch.getConfigText())) + .preRunScript(coalesce(FilesHelper.readString(opts.preRunScript), launch.getPreRunScript())) + .postRunScript(coalesce(FilesHelper.readString(opts.postRunScript), launch.getPostRunScript())) + .pullLatest(coalesce(opts.pullLatest, launch.getPullLatest())) + .stubRun(coalesce(opts.stubRun, launch.getStubRun())) + .mainScript(coalesce(opts.mainScript, launch.getMainScript())) + .entryName(coalesce(opts.entryName, launch.getEntryName())) + .schemaName(coalesce(opts.schemaName, launch.getSchemaName())) + .userSecrets(coalesce(removeEmptyValues(opts.userSecrets), launch.getUserSecrets())) + .workspaceSecrets(coalesce(removeEmptyValues(opts.workspaceSecrets), launch.getWorkspaceSecrets())) + ); + + api().updatePipeline(pipe.getPipelineId(), updateReq, wspId); + + return new PipelinesUpdated(workspaceRef(wspId), pipe.getName()); } } diff --git a/src/main/java/io/seqera/tower/cli/commands/workspaces/UpdateCmd.java b/src/main/java/io/seqera/tower/cli/commands/workspaces/UpdateCmd.java index c0cff5f8..5614312a 100644 --- a/src/main/java/io/seqera/tower/cli/commands/workspaces/UpdateCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/workspaces/UpdateCmd.java @@ -24,6 +24,8 @@ import picocli.CommandLine.Option; import java.io.IOException; +import java.util.List; +import java.util.Objects; @Command( name = "update", @@ -34,6 +36,9 @@ public class UpdateCmd extends AbstractWorkspaceCmd { @CommandLine.Option(names = {"-i", "--id"}, description = "Workspace ID to delete.", required = true) public Long workspaceId; + @Option(names = {"--new-name"}, description = "The workspace new name.") + public String workspaceNewName; + @Option(names = {"-f", "--fullName"}, description = "The workspace full name.") public String workspaceFullName; @@ -42,7 +47,13 @@ public class UpdateCmd extends AbstractWorkspaceCmd { @Override protected Response exec() throws ApiException, IOException { - if (workspaceFullName == null && description == null) { + + boolean updates = + workspaceFullName != null + || workspaceNewName != null + || description != null; + + if (!updates) { throw new ShowUsageException(getSpec(), "Required at least one option to update"); } @@ -53,6 +64,10 @@ protected Response exec() throws ApiException, IOException { .fullName(response.getWorkspace().getFullName()) .description(response.getWorkspace().getDescription()); + if (workspaceNewName != null) { + request.setName(workspaceNewName); + } + if (workspaceFullName != null) { request.setFullName(workspaceFullName); } @@ -62,7 +77,7 @@ protected Response exec() throws ApiException, IOException { } request.setVisibility(Visibility.PRIVATE); - response = api().updateWorkspace(ws.getOrgId(), ws.getWorkspaceId(), request); + api().updateWorkspace(ws.getOrgId(), ws.getWorkspaceId(), request); return new WorkspaceUpdated(response.getWorkspace().getName(), ws.getOrgName(), response.getWorkspace().getVisibility()); } diff --git a/src/main/java/io/seqera/tower/cli/responses/computeenvs/ComputeEnvUpdated.java b/src/main/java/io/seqera/tower/cli/responses/computeenvs/ComputeEnvUpdated.java new file mode 100644 index 00000000..e44c3caf --- /dev/null +++ b/src/main/java/io/seqera/tower/cli/responses/computeenvs/ComputeEnvUpdated.java @@ -0,0 +1,31 @@ +/* + * 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.responses.computeenvs; + +import io.seqera.tower.cli.responses.Response; + +public class ComputeEnvUpdated extends Response { + + public final String wspRef; + public final String ceName; + + public ComputeEnvUpdated(String wspRef, String ceName) { + this.wspRef = wspRef; + this.ceName = ceName; + } + + @Override + public String toString() { + return ansi(String.format("%n @|yellow Compute environment '%s' updated at %s workspace|@%n", ceName, wspRef)); + } + +} diff --git a/src/test/java/io/seqera/tower/cli/actions/ActionsCmdTest.java b/src/test/java/io/seqera/tower/cli/actions/ActionsCmdTest.java index 88cf0c4b..7e8c3197 100644 --- a/src/test/java/io/seqera/tower/cli/actions/ActionsCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/actions/ActionsCmdTest.java @@ -14,7 +14,6 @@ import com.fasterxml.jackson.core.JsonProcessingException; import io.seqera.tower.cli.BaseCmdTest; import io.seqera.tower.cli.commands.enums.OutputType; -import io.seqera.tower.cli.commands.labels.LabelsSubcmdOptions; import io.seqera.tower.cli.exceptions.ActionNotFoundException; import io.seqera.tower.cli.exceptions.TowerException; import io.seqera.tower.cli.responses.actions.ActionAdd; @@ -22,13 +21,13 @@ import io.seqera.tower.cli.responses.actions.ActionsDelete; import io.seqera.tower.cli.responses.actions.ActionsList; import io.seqera.tower.cli.responses.actions.ActionsView; -import io.seqera.tower.cli.responses.labels.ManageLabels; import io.seqera.tower.model.ActionResponseDto; import io.seqera.tower.model.ListActionsResponseActionInfo; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.mockserver.client.MockServerClient; +import org.mockserver.matchers.MatchType; import org.mockserver.model.MediaType; import java.io.IOException; @@ -43,6 +42,7 @@ import static org.mockserver.matchers.Times.exactly; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import static org.mockserver.model.JsonBody.json; class ActionsCmdTest extends BaseCmdTest { @@ -478,6 +478,106 @@ void testUpdateWithError(MockServerClient mock) { assertEquals(errorMessage(out.app, new TowerException(String.format("Unable to update action '%s' for workspace '%s'", "hello", USER_WORKSPACE_NAME))), out.stdErr); } + @ParameterizedTest + @EnumSource(OutputType.class) + void testUpdateName(OutputType format, MockServerClient mock) { + mock.reset(); + + mock.when( + request().withMethod("GET").withPath("/actions"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("actions/actions_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/actions/57byWxhmUDLLWIF4J97XEP"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("actions/action_view")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/compute-envs").withQueryStringParameter("status", "AVAILABLE"), exactly(1) + ).respond( + response().withStatusCode(200).withBody("{\"computeEnvs\":[{\"id\":\"vYOK4vn7spw7bHHWBDXZ2\",\"name\":\"demo\",\"platform\":\"aws-batch\",\"status\":\"AVAILABLE\",\"message\":null,\"lastUsed\":null,\"primary\":null,\"workspaceName\":null,\"visibility\":null}]}").withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/compute-envs/vYOK4vn7spw7bHHWBDXZ2"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("compute_env_demo")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("PUT").withPath("/actions/57byWxhmUDLLWIF4J97XEP") + .withBody(json("{ \"name\": \"hello_world\" }", MatchType.ONLY_MATCHING_FIELDS)), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + mock.when( + request().withMethod("GET").withPath("/actions/validate") + .withQueryStringParameter("name", "hello_world"), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(format, mock, "actions", "update", "-n", "hello", "--new-name", "hello_world"); + assertOutput(format, out, new ActionUpdate("hello", USER_WORKSPACE_NAME, "57byWxhmUDLLWIF4J97XEP")); + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testUpdateInvalidName(OutputType format, MockServerClient mock) { + mock.reset(); + + mock.when( + request().withMethod("GET").withPath("/actions"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("actions/actions_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/actions/57byWxhmUDLLWIF4J97XEP"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("actions/action_view")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/compute-envs").withQueryStringParameter("status", "AVAILABLE"), exactly(1) + ).respond( + response().withStatusCode(200).withBody("{\"computeEnvs\":[{\"id\":\"vYOK4vn7spw7bHHWBDXZ2\",\"name\":\"demo\",\"platform\":\"aws-batch\",\"status\":\"AVAILABLE\",\"message\":null,\"lastUsed\":null,\"primary\":null,\"workspaceName\":null,\"visibility\":null}]}").withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/compute-envs/vYOK4vn7spw7bHHWBDXZ2"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("compute_env_demo")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("PUT").withPath("/actions/57byWxhmUDLLWIF4J97XEP"), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + mock.when( + request().withMethod("GET").withPath("/actions/validate") + .withQueryStringParameter("name", "#hello"), + exactly(1) + ).respond( + response().withStatusCode(400) + ); + + ExecOut out = exec(format, mock, "actions", "update", "-n", "hello", "--new-name", "#hello"); + + assertEquals("", out.stdOut); + assertEquals(1, out.exitCode); + assertEquals(errorMessage(out.app, new TowerException(String.format("Action name '%s' is not valid", "#hello"))), out.stdErr); + } + @ParameterizedTest @EnumSource(OutputType.class) void testPause(OutputType format, MockServerClient mock) throws IOException { diff --git a/src/test/java/io/seqera/tower/cli/computeenvs/ComputeEnvsCmdTest.java b/src/test/java/io/seqera/tower/cli/computeenvs/ComputeEnvsCmdTest.java index 6bd3770c..182a7c0f 100644 --- a/src/test/java/io/seqera/tower/cli/computeenvs/ComputeEnvsCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/computeenvs/ComputeEnvsCmdTest.java @@ -20,11 +20,13 @@ import io.seqera.tower.cli.BaseCmdTest; import io.seqera.tower.cli.commands.enums.OutputType; import io.seqera.tower.cli.exceptions.ComputeEnvNotFoundException; +import io.seqera.tower.cli.exceptions.InvalidResponseException; import io.seqera.tower.cli.exceptions.TowerException; import io.seqera.tower.cli.responses.computeenvs.ComputeEnvAdded; import io.seqera.tower.cli.responses.computeenvs.ComputeEnvDeleted; import io.seqera.tower.cli.responses.computeenvs.ComputeEnvExport; import io.seqera.tower.cli.responses.computeenvs.ComputeEnvList; +import io.seqera.tower.cli.responses.computeenvs.ComputeEnvUpdated; import io.seqera.tower.cli.responses.computeenvs.ComputeEnvView; import io.seqera.tower.cli.responses.computeenvs.ComputeEnvsPrimaryGet; import io.seqera.tower.cli.responses.computeenvs.ComputeEnvsPrimarySet; @@ -439,4 +441,87 @@ void testPrimarySetAzure(OutputType format, MockServerClient mock) throws JsonPr ComputeEnvResponseDto ce = parseJson("{\"id\":\"lkasjdlkfwerjbEcrycwrSSe\",\"name\":\"demo\",\"platform\":\"azure-batch\"}", ComputeEnvResponseDto.class); assertOutput(format, out, new ComputeEnvsPrimarySet("[organization2 / workspace2]", ce)); } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testUpdateName(OutputType format, MockServerClient mock) { + + mock.when( + request().withMethod("GET").withPath("/compute-envs/validate") + .withQueryStringParameter("name", "testCE") + .withQueryStringParameter("workspaceId", "75887156211590"), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + mock.when( + request().withMethod("GET").withPath("/compute-envs"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("compute_envs_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/compute-envs/vYOK4vn7spw7bHHWBDXZ2"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("compute_env_demo")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("PUT").withPath("/compute-envs/vYOK4vn7spw7bHHWBDXZ2"), exactly(1) + ).respond( + response().withStatusCode(204) + ); + + mock.when( + request().withMethod("GET").withPath("/user-info"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("user")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/user/1264/workspaces"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("workspaces/workspaces_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + ExecOut out = exec(format, mock, "compute-envs", "update", "-n", "demo", "-w", "75887156211590", "--new-name", "testCE"); + + assertEquals("", out.stdErr); + assertOutput(format, out, new ComputeEnvUpdated("[organization2 / workspace2]", "demo")); + assertEquals(0, out.exitCode); + + } + + @ParameterizedTest + @EnumSource(OutputType.class) + void testUpdateInvalidName(OutputType format, MockServerClient mock) { + + mock.when( + request().withMethod("GET").withPath("/compute-envs/validate") + .withQueryStringParameter("name", "testCE") + .withQueryStringParameter("workspaceId", "75887156211590"), + exactly(1) + ).respond( + response().withStatusCode(400) + ); + + mock.when( + request().withMethod("GET").withPath("/user-info"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("user")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/user/1264/workspaces"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("workspaces/workspaces_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + ExecOut out = exec(format, mock, "compute-envs", "update", "-n", "demo", "-w", "75887156211590", "--new-name", "testCE"); + + assertEquals(errorMessage(out.app, new InvalidResponseException("Compute environment name 'testCE' is not valid")), out.stdErr); + assertEquals("", out.stdOut); + assertEquals(1, out.exitCode); + } } diff --git a/src/test/java/io/seqera/tower/cli/organizations/OrganizationsCmdTest.java b/src/test/java/io/seqera/tower/cli/organizations/OrganizationsCmdTest.java index a9725892..1d8a28ed 100644 --- a/src/test/java/io/seqera/tower/cli/organizations/OrganizationsCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/organizations/OrganizationsCmdTest.java @@ -27,6 +27,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.mockserver.client.MockServerClient; +import org.mockserver.model.JsonBody; import org.mockserver.model.MediaType; import java.util.Arrays; @@ -38,6 +39,7 @@ import static org.mockserver.matchers.Times.exactly; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import static org.mockserver.model.JsonBody.json; class OrganizationsCmdTest extends BaseCmdTest { @@ -295,6 +297,40 @@ void testUpdate(OutputType format, MockServerClient mock) { assertOutput(format, out, new OrganizationsUpdated(27736513644467L, "organization1")); } + @ParameterizedTest + @EnumSource(OutputType.class) + void testUpdateName(OutputType format, MockServerClient mock) { + mock.reset(); + mock.when( + request().withMethod("GET").withPath("/user-info"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("user")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/user/1264/workspaces"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("workspaces/workspaces_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/orgs/27736513644467"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("organizations/organizations_view")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("PUT").withPath("/orgs/27736513644467") + .withBody(json("{\"name\":\"organization2\",\"fullName\":\"sample organization\"}")), + exactly(1) + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(format, mock, "organizations", "update", "-n", "organization1", "--new-name", "organization2", "-f", "sample organization"); + assertOutput(format, out, new OrganizationsUpdated(27736513644467L, "organization1")); + } + @Test void testUpdateError(MockServerClient mock) { mock.reset(); diff --git a/src/test/java/io/seqera/tower/cli/pipelines/PipelinesCmdTest.java b/src/test/java/io/seqera/tower/cli/pipelines/PipelinesCmdTest.java index 782e2863..75472e97 100644 --- a/src/test/java/io/seqera/tower/cli/pipelines/PipelinesCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/pipelines/PipelinesCmdTest.java @@ -19,6 +19,7 @@ import io.seqera.tower.cli.BaseCmdTest; import io.seqera.tower.cli.commands.enums.OutputType; import io.seqera.tower.cli.commands.labels.LabelsSubcmdOptions; +import io.seqera.tower.cli.exceptions.InvalidResponseException; import io.seqera.tower.cli.exceptions.MultiplePipelinesFoundException; import io.seqera.tower.cli.exceptions.NoComputeEnvironmentException; import io.seqera.tower.cli.exceptions.PipelineNotFoundException; @@ -42,6 +43,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.mockserver.client.MockServerClient; +import org.mockserver.matchers.Times; +import org.mockserver.model.JsonBody; import org.mockserver.model.MediaType; import java.io.IOException; @@ -57,6 +60,7 @@ import static org.mockserver.matchers.Times.exactly; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import static org.mockserver.model.JsonBody.json; class PipelinesCmdTest extends BaseCmdTest { @@ -76,7 +80,8 @@ void testUpdate(MockServerClient mock) { ); mock.when( - request().withMethod("PUT").withPath("/pipelines/217997727159863").withBody("{\"description\":\"Sleep one minute and exit\",\"launch\":{\"computeEnvId\":\"vYOK4vn7spw7bHHWBDXZ2\",\"pipeline\":\"https://github.com/pditommaso/nf-sleep\",\"workDir\":\"s3://nextflow-ci/jordeu\",\"paramsText\":\"timeout: 60\\n\",\"pullLatest\":false,\"stubRun\":false}}"), exactly(1) + request().withMethod("PUT").withPath("/pipelines/217997727159863") + .withBody(json("{\"description\":\"Sleep one minute and exit\",\"name\":\"sleep_one_minute\",\"launch\":{\"computeEnvId\":\"vYOK4vn7spw7bHHWBDXZ2\",\"pipeline\":\"https://github.com/pditommaso/nf-sleep\",\"workDir\":\"s3://nextflow-ci/jordeu\",\"paramsText\":\"timeout: 60\\n\",\"pullLatest\":false,\"stubRun\":false}}")), exactly(1) ).respond( response().withStatusCode(200).withBody("{\"pipeline\":{\"pipelineId\":217997727159863,\"name\":\"sleep_one_minute\",\"description\":\"Sleep one minute and exit\",\"icon\":null,\"repository\":\"https://github.com/pditommaso/nf-sleep\",\"userId\":4,\"userName\":\"jordi\",\"userFirstName\":null,\"userLastName\":null,\"orgId\":null,\"orgName\":null,\"workspaceId\":null,\"workspaceName\":null,\"visibility\":null}}").withContentType(MediaType.APPLICATION_JSON) ); @@ -117,7 +122,8 @@ void testUpdateComputeEnv(MockServerClient mock) { ); mock.when( - request().withMethod("PUT").withPath("/pipelines/217997727159863").withBody("{\"launch\":{\"computeEnvId\":\"isnEDBLvHDAIteOEF44ow\",\"pipeline\":\"https://github.com/pditommaso/nf-sleep\",\"workDir\":\"s3://nextflow-ci/jordeu\",\"paramsText\":\"timeout: 60\\n\",\"pullLatest\":false,\"stubRun\":false}}"), exactly(1) + request().withMethod("PUT").withPath("/pipelines/217997727159863") + .withBody(json("{\"name\":\"sleep_one_minute\",\"launch\":{\"computeEnvId\":\"isnEDBLvHDAIteOEF44ow\",\"pipeline\":\"https://github.com/pditommaso/nf-sleep\",\"workDir\":\"s3://nextflow-ci/jordeu\",\"paramsText\":\"timeout: 60\\n\",\"pullLatest\":false,\"stubRun\":false}}")), exactly(1) ).respond( response().withStatusCode(200).withBody("{\"pipeline\":{\"pipelineId\":217997727159863,\"name\":\"sleep_one_minute\",\"description\":\"Sleep one minute and exit\",\"icon\":null,\"repository\":\"https://github.com/pditommaso/nf-sleep\",\"userId\":4,\"userName\":\"jordi\",\"userFirstName\":null,\"userLastName\":null,\"orgId\":null,\"orgName\":null,\"workspaceId\":null,\"workspaceName\":null,\"visibility\":null}}").withContentType(MediaType.APPLICATION_JSON) ); @@ -128,6 +134,85 @@ void testUpdateComputeEnv(MockServerClient mock) { assertEquals(new PipelinesUpdated(USER_WORKSPACE_NAME, "sleep_one_minute").toString(), out.stdOut); } + @Test + void testUpdatePipelineName(MockServerClient mock) { + + mock.reset(); + + mock.when( + request().withMethod("GET").withPath("/pipelines").withQueryStringParameter("search", "sleep_one_minute") + ).respond( + response().withStatusCode(200).withBody("{\"pipelines\":[{\"pipelineId\":217997727159863,\"name\":\"sleep_one_minute\",\"description\":null,\"icon\":null,\"repository\":\"https://github.com/pditommaso/nf-sleep\",\"userId\":4,\"userName\":\"jordi\",\"userFirstName\":null,\"userLastName\":null,\"orgId\":null,\"orgName\":null,\"workspaceId\":null,\"workspaceName\":null,\"visibility\":null}],\"totalSize\":1}").withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/pipelines/217997727159863/launch") + ).respond( + response().withStatusCode(200).withBody(loadResource("pipelines_update")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("PUT").withPath("/pipelines/217997727159863") + .withBody(json("{\"name\": \"sleepOneMinute\", \"launch\":{\"computeEnvId\":\"vYOK4vn7spw7bHHWBDXZ2\",\"pipeline\":\"https://github.com/pditommaso/nf-sleep\",\"workDir\":\"s3://nextflow-ci/jordeu\",\"paramsText\":\"timeout: 60\\n\",\"pullLatest\":false,\"stubRun\":false}}")), + exactly(1) + ).respond( + response().withStatusCode(200) + .withBody("{\"pipeline\":{\"pipelineId\":217997727159863,\"name\":\"sleepOneMinute\",\"description\":\"Sleep one minute and exit\",\"icon\":null,\"repository\":\"https://github.com/pditommaso/nf-sleep\",\"userId\":4,\"userName\":\"jordi\",\"userFirstName\":null,\"userLastName\":null,\"orgId\":null,\"orgName\":null,\"workspaceId\":null,\"workspaceName\":null,\"visibility\":null}}").withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/pipelines/validate").withQueryStringParameter("name", "sleepOneMinute") + ).respond( + response().withStatusCode(204) + ); + + ExecOut out = exec(mock, "pipelines", "update", "-n", "sleep_one_minute", "--new-name", "sleepOneMinute"); + + assertEquals("", out.stdErr); + assertEquals(new PipelinesUpdated(USER_WORKSPACE_NAME, "sleep_one_minute").toString(), out.stdOut); + + } + + @Test + void testUpdatePipelineInvalidName(MockServerClient mock) { + + mock.reset(); + + mock.when( + request().withMethod("GET").withPath("/pipelines").withQueryStringParameter("search", "sleep_one_minute") + ).respond( + response().withStatusCode(200).withBody("{\"pipelines\":[{\"pipelineId\":217997727159863,\"name\":\"sleep_one_minute\",\"description\":null,\"icon\":null,\"repository\":\"https://github.com/pditommaso/nf-sleep\",\"userId\":4,\"userName\":\"jordi\",\"userFirstName\":null,\"userLastName\":null,\"orgId\":null,\"orgName\":null,\"workspaceId\":null,\"workspaceName\":null,\"visibility\":null}],\"totalSize\":1}").withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/pipelines/217997727159863/launch") + ).respond( + response().withStatusCode(200).withBody(loadResource("pipelines_update")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("PUT").withPath("/pipelines/217997727159863") + .withBody(json("{\"name\": \"sleepOneMinute\", \"launch\":{\"computeEnvId\":\"vYOK4vn7spw7bHHWBDXZ2\",\"pipeline\":\"https://github.com/pditommaso/nf-sleep\",\"workDir\":\"s3://nextflow-ci/jordeu\",\"paramsText\":\"timeout: 60\\n\",\"pullLatest\":false,\"stubRun\":false}}")), + exactly(1) + ).respond( + response().withStatusCode(200) + .withBody("{\"pipeline\":{\"pipelineId\":217997727159863,\"name\":\"sleepOneMinute\",\"description\":\"Sleep one minute and exit\",\"icon\":null,\"repository\":\"https://github.com/pditommaso/nf-sleep\",\"userId\":4,\"userName\":\"jordi\",\"userFirstName\":null,\"userLastName\":null,\"orgId\":null,\"orgName\":null,\"workspaceId\":null,\"workspaceName\":null,\"visibility\":null}}").withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/pipelines/validate").withQueryStringParameter("name", "#sleep") + ).respond( + response().withStatusCode(400) + .withBody(json("{\"message\": \"Pipeline name must contain a minimum of 2 and a maximum of 99 alphanumeric characters separated by dashes or underscores\"}")) + ); + + ExecOut out = exec(mock, "pipelines", "update", "-n", "sleep_one_minute", "--new-name", "#sleep"); + + assertEquals(errorMessage(out.app, new InvalidResponseException("Pipeline name '#sleep' is not valid")), out.stdErr); + assertEquals("", out.stdOut); + assertEquals(1, out.exitCode); + } + @Test void testAdd(MockServerClient mock) throws IOException { diff --git a/src/test/java/io/seqera/tower/cli/workspaces/WorkspacesCmdTest.java b/src/test/java/io/seqera/tower/cli/workspaces/WorkspacesCmdTest.java index 7e30ffa9..608ebb7d 100644 --- a/src/test/java/io/seqera/tower/cli/workspaces/WorkspacesCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/workspaces/WorkspacesCmdTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; import org.mockserver.client.MockServerClient; +import org.mockserver.model.JsonBody; import org.mockserver.model.MediaType; import java.util.Arrays; @@ -40,6 +41,7 @@ import static org.mockserver.matchers.Times.exactly; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import static org.mockserver.model.JsonBody.json; class WorkspacesCmdTest extends BaseCmdTest { @@ -318,6 +320,44 @@ void testUpdateById(OutputType format, MockServerClient mock) { assertOutput(format, out, new WorkspaceUpdated("workspace1", "organization1", Visibility.PRIVATE)); } + @ParameterizedTest + @EnumSource(OutputType.class) + void testUpdateNameById(OutputType format, MockServerClient mock) { + + mock.when( + request().withMethod("GET").withPath("/user-info"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("user")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/user/1264/workspaces"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("workspaces/workspaces_list")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("GET").withPath("/orgs/27736513644467/workspaces/75887156211589"), exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("workspaces/workspaces_view")).withContentType(MediaType.APPLICATION_JSON) + ); + + mock.when( + request().withMethod("PUT").withPath("/orgs/27736513644467/workspaces/75887156211589") + .withBody(json("{\"name\":\"workspace2\",\"fullName\":\"wsp-new\",\"description\":\"workspace description\",\"visibility\":\"PRIVATE\"}")), + exactly(1) + ).respond( + response().withStatusCode(200).withBody(loadResource("workspaces/workspaces_update_response")).withContentType(MediaType.APPLICATION_JSON) + ); + + ExecOut out = exec(format, mock, "workspaces", "update", "-i", "75887156211589", + "--new-name", "workspace2", + "-f", "wsp-new", + "-d", "workspace description" + ); + assertOutput(format, out, new WorkspaceUpdated("workspace1", "organization1", Visibility.PRIVATE)); + } + @Test void testUpdateWorkspaceNotFound(MockServerClient mock) { mock.when( diff --git a/src/test/resources/runcmd/compute_env_demo.json b/src/test/resources/runcmd/compute_env_demo.json index 80ac0a92..cd63560e 100644 --- a/src/test/resources/runcmd/compute_env_demo.json +++ b/src/test/resources/runcmd/compute_env_demo.json @@ -72,7 +72,7 @@ }, "dateCreated": "2021-09-08T06:00:06Z", "lastUpdated": "2021-09-08T06:00:59Z", - "lastUsed": null, + "lastUsed": "2021-10-06T08:08:53Z", "deleted": null, "status": "AVAILABLE", "message": null, diff --git a/src/test/resources/runcmd/compute_envs_list.json b/src/test/resources/runcmd/compute_envs_list.json index 26facba4..3f432b86 100644 --- a/src/test/resources/runcmd/compute_envs_list.json +++ b/src/test/resources/runcmd/compute_envs_list.json @@ -21,6 +21,17 @@ "primary": true, "workspaceName": "wsp-jfernandez", "visibility": "PRIVATE" + }, + { + "id": "vYOK4vn7spw7bHHWBDXZ2", + "name": "demo", + "platform": "aws-batch", + "status": "AVAILABLE", + "message": null, + "lastUsed": "2021-10-06T08:08:53Z", + "primary": true, + "workspaceName": "wsp-jfernandez", + "visibility": "PRIVATE" } ] } diff --git a/src/test/resources/runcmd/workspaces/workspaces_update_response.json b/src/test/resources/runcmd/workspaces/workspaces_update_response.json index 85a2e4a5..afc7826b 100644 --- a/src/test/resources/runcmd/workspaces/workspaces_update_response.json +++ b/src/test/resources/runcmd/workspaces/workspaces_update_response.json @@ -1,7 +1,7 @@ { "workspace": { "id": 75887156211589, - "name": "workspace1", + "name": "workspace2", "fullName": "workspace-1", "description": "workspace description", "visibility": "PRIVATE",