Skip to content

Commit 688d7e1

Browse files
feat: add --wait flag to compute-envs delete (#593)
* test: add failing tests for compute-envs delete --wait Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Rob Syme <rob.syme@gmail.com> * feat: add --wait flag to compute-envs delete command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Rob Syme <rob.syme@gmail.com> * feat: add --wait flag to compute-envs delete command Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Rob Syme <rob.syme@gmail.com> * feat: reuse wait logic from RequestHelper There is common logic to wait for given resource status, this commit leverages that instead of custom logic for CE deletion. * chore: remove unused class fields --------- Signed-off-by: Rob Syme <rob.syme@gmail.com> Co-authored-by: Stefano Boriero <stefano.boriero@seqera.io>
1 parent bfdd6f8 commit 688d7e1

3 files changed

Lines changed: 97 additions & 4 deletions

File tree

src/main/java/io/seqera/tower/cli/commands/computeenvs/DeleteCmd.java

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,21 @@
1717
package io.seqera.tower.cli.commands.computeenvs;
1818

1919
import io.seqera.tower.ApiException;
20+
import io.seqera.tower.cli.commands.enums.OutputType;
2021
import io.seqera.tower.cli.commands.global.WorkspaceOptionalOptions;
2122
import io.seqera.tower.cli.exceptions.ComputeEnvNotFoundException;
2223
import io.seqera.tower.cli.responses.Response;
2324
import io.seqera.tower.cli.responses.computeenvs.ComputeEnvDeleted;
2425
import io.seqera.tower.model.ComputeEnvResponseDto;
26+
import io.seqera.tower.model.ComputeEnvStatus;
2527
import picocli.CommandLine;
2628
import picocli.CommandLine.Command;
2729

30+
import java.util.Collections;
31+
import java.util.concurrent.TimeUnit;
32+
33+
import static io.seqera.tower.cli.utils.ResponseHelper.waitStatus;
34+
2835
@Command(
2936
name = "delete",
3037
description = "Delete a compute environment."
@@ -37,6 +44,9 @@ public class DeleteCmd extends AbstractComputeEnvCmd {
3744
@CommandLine.Mixin
3845
public WorkspaceOptionalOptions workspace;
3946

47+
@CommandLine.Option(names = {"--wait"}, description = "Wait until the compute environment is fully deleted.")
48+
public boolean wait;
49+
4050
@Override
4151
protected Response exec() throws ApiException {
4252
Long wspId = workspaceId(workspace.workspace);
@@ -51,7 +61,7 @@ protected Response exec() throws ApiException {
5161

5262
try {
5363
computeEnvsApi().deleteComputeEnv(id, wspId, null);
54-
return new ComputeEnvDeleted(id, workspaceRef(wspId));
64+
return new ComputeEnvDeleted(id, workspaceRef(wspId), wspId);
5565
} catch (ApiException e) {
5666
if (e.getCode() == 403) {
5767
// Customize the forbidden message
@@ -60,4 +70,36 @@ protected Response exec() throws ApiException {
6070
throw e;
6171
}
6272
}
63-
}
73+
74+
@Override
75+
protected Integer onBeforeExit(int exitCode, Response response) {
76+
if (exitCode != 0 || !wait || response == null) {
77+
return exitCode;
78+
}
79+
80+
ComputeEnvDeleted computeEnv = (ComputeEnvDeleted) response;
81+
boolean showProgress = app().output != OutputType.json;
82+
83+
try {
84+
return waitStatus(
85+
app().getOut(),
86+
showProgress,
87+
ComputeEnvStatus.DELETED,
88+
ComputeEnvStatus.values(),
89+
() -> checkComputeEnvStatus(computeEnv.id, computeEnv.workspaceId),
90+
ComputeEnvStatus.ERRORED, ComputeEnvStatus.INVALID
91+
);
92+
} catch (InterruptedException e) {
93+
Thread.currentThread().interrupt();
94+
return exitCode;
95+
}
96+
}
97+
98+
private ComputeEnvStatus checkComputeEnvStatus(String computeEnvId, Long workspaceId) {
99+
try {
100+
return computeEnvsApi().describeComputeEnv(computeEnvId, workspaceId, Collections.emptyList()).getComputeEnv().getStatus();
101+
} catch (ApiException | NullPointerException e) {
102+
return null;
103+
}
104+
}
105+
}

src/main/java/io/seqera/tower/cli/responses/computeenvs/ComputeEnvDeleted.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ public class ComputeEnvDeleted extends Response {
2222

2323
public final String id;
2424
public final String workspaceRef;
25+
public final Long workspaceId;
2526

26-
public ComputeEnvDeleted(String id, String workspaceRef) {
27+
public ComputeEnvDeleted(String id, String workspaceRef, Long workspaceId) {
2728
this.id = id;
29+
this.workspaceId = workspaceId;
2830
this.workspaceRef = workspaceRef;
2931
}
3032

src/test/java/io/seqera/tower/cli/computeenvs/ComputeEnvsCmdTest.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ void testDelete(OutputType format, MockServerClient mock) {
7676
);
7777

7878
ExecOut out = exec(format, mock, "compute-envs", "delete", "-i", "vYOK4vn7spw7bHHWBDXZ2");
79-
assertOutput(format, out, new ComputeEnvDeleted("vYOK4vn7spw7bHHWBDXZ2", USER_WORKSPACE_NAME));
79+
assertOutput(format, out, new ComputeEnvDeleted("vYOK4vn7spw7bHHWBDXZ2", USER_WORKSPACE_NAME, null));
8080
}
8181

8282
@Test
@@ -712,4 +712,53 @@ void testUpdateInvalidName(OutputType format, MockServerClient mock) {
712712
assertEquals("", out.stdOut);
713713
assertEquals(1, out.exitCode);
714714
}
715+
716+
@Test
717+
void testDeleteWaitHappyPath(MockServerClient mock) {
718+
// DELETE returns 204
719+
mock.when(
720+
request().withMethod("DELETE").withPath("/compute-envs/vYOK4vn7spw7bHHWBDXZ2"), exactly(1)
721+
).respond(
722+
response().withStatusCode(204)
723+
);
724+
725+
// First DESCRIBE returns DELETING
726+
mock.when(
727+
request().withMethod("GET").withPath("/compute-envs/vYOK4vn7spw7bHHWBDXZ2"), exactly(1)
728+
).respond(
729+
response().withStatusCode(200).withBody("{\"computeEnv\":{\"id\":\"vYOK4vn7spw7bHHWBDXZ2\",\"name\":\"demo\",\"platform\":\"aws-batch\",\"status\":\"DELETING\"}}").withContentType(MediaType.APPLICATION_JSON)
730+
);
731+
732+
// Second DESCRIBE returns 404 (CE has been deleted)
733+
mock.when(
734+
request().withMethod("GET").withPath("/compute-envs/vYOK4vn7spw7bHHWBDXZ2"), exactly(1)
735+
).respond(
736+
response().withStatusCode(200).withBody("{\"computeEnv\":{\"id\":\"vYOK4vn7spw7bHHWBDXZ2\",\"name\":\"demo\",\"platform\":\"aws-batch\",\"status\":\"DELETED\"}}").withContentType(MediaType.APPLICATION_JSON)
737+
);
738+
739+
ExecOut out = exec(mock, "compute-envs", "delete", "-i", "vYOK4vn7spw7bHHWBDXZ2", "--wait");
740+
741+
assertEquals(0, out.exitCode);
742+
}
743+
744+
@Test
745+
void testDeleteWaitErrored(MockServerClient mock) {
746+
// DELETE returns 204
747+
mock.when(
748+
request().withMethod("DELETE").withPath("/compute-envs/vYOK4vn7spw7bHHWBDXZ2"), exactly(1)
749+
).respond(
750+
response().withStatusCode(204)
751+
);
752+
753+
// DESCRIBE returns ERRORED
754+
mock.when(
755+
request().withMethod("GET").withPath("/compute-envs/vYOK4vn7spw7bHHWBDXZ2"), exactly(1)
756+
).respond(
757+
response().withStatusCode(200).withBody("{\"computeEnv\":{\"id\":\"vYOK4vn7spw7bHHWBDXZ2\",\"name\":\"demo\",\"platform\":\"aws-batch\",\"status\":\"ERRORED\"}}").withContentType(MediaType.APPLICATION_JSON)
758+
);
759+
760+
ExecOut out = exec(mock, "compute-envs", "delete", "-i", "vYOK4vn7spw7bHHWBDXZ2", "--wait");
761+
762+
assertEquals(1, out.exitCode);
763+
}
715764
}

0 commit comments

Comments
 (0)