diff --git a/assets/astra-db-java.jar b/assets/astra-db-java.jar new file mode 100644 index 00000000..e180e132 Binary files /dev/null and b/assets/astra-db-java.jar differ diff --git a/build.gradle.kts b/build.gradle.kts index a0dd4604..f4ae4e85 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -29,8 +29,9 @@ dependencies { implementation("org.slf4j:slf4j-nop:2.0.17") // for underlying api calls - implementation("com.datastax.astra:astra-db-java:2.3.1") + // implementation("com.datastax.astra:astra-db-java:2.3.1") implementation("com.datastax.astra:astra-sdk-devops:2.3.1") + implementation(files("./assets/astra-db-java.jar")) // unzip downloaded external programs (cqlsh, dsbulk, etc.) implementation("org.apache.commons:commons-compress:1.28.0") diff --git a/src/main/java/com/dtsx/astra/cli/commands/db/DbCmd.java b/src/main/java/com/dtsx/astra/cli/commands/db/DbCmd.java index b70dcedd..7758e707 100644 --- a/src/main/java/com/dtsx/astra/cli/commands/db/DbCmd.java +++ b/src/main/java/com/dtsx/astra/cli/commands/db/DbCmd.java @@ -1,5 +1,7 @@ package com.dtsx.astra.cli.commands.db; +import com.dtsx.astra.cli.commands.db.clone.DbCloneCmd; + import com.dtsx.astra.cli.commands.db.cdc.CdcCreateCmd; import com.dtsx.astra.cli.commands.db.cdc.CdcDeleteCmd; import com.dtsx.astra.cli.commands.db.cdc.CdcListCmd; @@ -38,6 +40,7 @@ DbCreateCmd.class, DbDeleteCmd.class, DbStatusCmd.class, + DbCloneCmd.class, CqlshCmd.class, DataAPICmd.class, DbDsbulkCmd.class, diff --git a/src/main/java/com/dtsx/astra/cli/commands/db/clone/AbstractDbCloneCmd.java b/src/main/java/com/dtsx/astra/cli/commands/db/clone/AbstractDbCloneCmd.java new file mode 100644 index 00000000..d7f673c2 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/commands/db/clone/AbstractDbCloneCmd.java @@ -0,0 +1,21 @@ +package com.dtsx.astra.cli.commands.db.clone; + +import com.dtsx.astra.cli.commands.db.AbstractPromptForDbCmd; +import com.dtsx.astra.cli.core.completions.caches.DbCompletionsCache; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; +import org.jetbrains.annotations.MustBeInvokedByOverriders; + +public abstract class AbstractDbCloneCmd extends AbstractPromptForDbCmd { + protected DbCloneGateway dbCloneGateway; + + @Override + @MustBeInvokedByOverriders + protected void prelude() { + super.prelude(); + dbCloneGateway = ctx.gateways().mkDbCloneGateway(profile().token(), profile().env(), new DbCompletionsCache(ctx, profileAndSource())); + + if (!ctx.properties().disableBetaWarnings()) { + ctx.log().warn("${cli.name} db clone commands are still in beta and may change without notice."); + } + } +} diff --git a/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneCmd.java b/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneCmd.java new file mode 100644 index 00000000..8348d455 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneCmd.java @@ -0,0 +1,19 @@ +package com.dtsx.astra.cli.commands.db.clone; + +import com.dtsx.astra.cli.core.mixins.HelpMixin; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; + +@Command( + name = "clone", + description = "Manage database cloning and snapshots", + subcommands = { + DbCloneStartCmd.class, + DbCloneStatusCmd.class, + DbCloneListSnapshotsCmd.class, + } +) +public class DbCloneCmd { + @Mixin + public HelpMixin help; +} diff --git a/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneListSnapshotsCmd.java b/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneListSnapshotsCmd.java new file mode 100644 index 00000000..2b29be94 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneListSnapshotsCmd.java @@ -0,0 +1,81 @@ +package com.dtsx.astra.cli.commands.db.clone; + +import com.dtsx.astra.cli.core.help.Example; +import com.dtsx.astra.cli.core.output.formats.OutputAll; +import com.dtsx.astra.cli.core.output.formats.OutputJson; +import com.dtsx.astra.cli.core.output.table.ShellTable; +import com.dtsx.astra.cli.operations.Operation; +import com.dtsx.astra.cli.operations.db.clone.DbCloneListSnapshotsOperation; +import com.dtsx.astra.cli.operations.db.clone.DbCloneListSnapshotsOperation.DbCloneListSnapshotsRequest; +import com.dtsx.astra.sdk.db.domain.DatabaseSnapshot; +import lombok.val; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import static com.dtsx.astra.cli.utils.CollectionUtils.sequencedMapOf; + +@Command( + name = "list-snapshots", + description = "List available snapshots for a database that can be used for cloning" +) +@Example( + comment = "List available snapshots for a database interactively", + command = "${cli.name} db clone list-snapshots" +) +@Example( + comment = "List available snapshots for a specific database", + command = "${cli.name} db clone list-snapshots my_db" +) +public class DbCloneListSnapshotsCmd extends AbstractDbCloneCmd> { + @Option( + names = { "-r", "--region" }, + description = "Filter snapshots by a specific region", + paramLabel = "REGION" + ) + public Optional $region = Optional.empty(); + + @Option( + names = { "--from" }, + description = "Start date for snapshot filtering (e.g. 2023-01-01)", + paramLabel = "START_DATE" + ) + public Optional $from = Optional.empty(); // TODO see if I can use instants or something + + @Option( + names = { "--to" }, + description = "End date for snapshot filtering (e.g. 2023-12-31)", + paramLabel = "END_DATE" + ) + public Optional $to = Optional.empty(); + + @Override + protected OutputJson executeJson(Supplier> result) { + return OutputJson.serializeValue(result); + } + + @Override + protected OutputAll execute(Supplier> result) { + val items = result.get() + .map(s -> sequencedMapOf( + "ID", s.getId(), + "Time", s.getTime() + )) + .toList(); + + return new ShellTable(items).withColumns("ID", "Time"); + } + + @Override + protected Operation> mkOperation() { + return new DbCloneListSnapshotsOperation(dbCloneGateway, new DbCloneListSnapshotsRequest($dbRef, $region, $from, $to)); + } + + @Override + protected String dbRefPrompt() { + return "Select the source database to list snapshots for"; + } +} diff --git a/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneStartCmd.java b/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneStartCmd.java new file mode 100644 index 00000000..69b5e662 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneStartCmd.java @@ -0,0 +1,158 @@ +package com.dtsx.astra.cli.commands.db.clone; + +import com.dtsx.astra.cli.core.CliConstants.$Db; +import com.dtsx.astra.cli.core.CliConstants.$Regions; +import com.dtsx.astra.cli.core.help.Example; +import com.dtsx.astra.cli.core.mixins.LongRunningOptionsMixin; +import com.dtsx.astra.cli.core.mixins.LongRunningOptionsMixin.WithSetTimeout; +import com.dtsx.astra.cli.core.models.DbRef; +import com.dtsx.astra.cli.core.output.Hint; +import com.dtsx.astra.cli.core.output.formats.OutputAll; +import com.dtsx.astra.cli.core.output.formats.OutputJson; +import com.dtsx.astra.cli.core.output.prompters.specific.DbRefPrompter; +import com.dtsx.astra.cli.core.output.prompters.specific.SnapshotPrompter; +import com.dtsx.astra.cli.operations.Operation; +import com.dtsx.astra.cli.operations.db.clone.DbCloneStartOperation; +import com.dtsx.astra.cli.operations.db.clone.DbCloneStartOperation.CloneCompleted; +import com.dtsx.astra.cli.operations.db.clone.DbCloneStartOperation.CloneStarted; +import com.dtsx.astra.cli.operations.db.clone.DbCloneStartOperation.DbCloneStartRequest; +import com.dtsx.astra.cli.operations.db.clone.DbCloneStartOperation.DbCloneStartResult; +import com.dtsx.astra.sdk.db.domain.DatabaseCloneStatus; +import lombok.val; +import org.jetbrains.annotations.MustBeInvokedByOverriders; +import picocli.CommandLine.Command; +import picocli.CommandLine.Mixin; +import picocli.CommandLine.Option; + +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import static com.dtsx.astra.cli.core.mixins.LongRunningOptionsMixin.LR_OPTS_TIMEOUT_NAME; +import static com.dtsx.astra.cli.utils.CollectionUtils.sequencedMapOf; + +@Command( + name = "start", + aliases = { "create" }, + description = "Start a clone operation from an existing database snapshot" +) +@Example( + comment = "Start and wait for a clone op interactively", + command = "${cli.name} db clone start" +) +@Example( // TODO probably use --from/--to or --source/--target or something + comment = "Start and wait for a clone op with explicit arguments", + command = "${cli.name} db clone start my_new_db -sd my_old_db -s snap123" +) +@Example( + comment = "Start a clone without waiting for it to become active", + command = "${cli.name} db clone start my_new_db -sd my_old_db -s snap123 --async" +) +@Example( + comment = "Start a clone from a source database in a specific region", + command = "${cli.name} db clone start my_new_db -sd my_old_db -s snap123 -sr us-east1" +) +public class DbCloneStartCmd extends AbstractDbCloneCmd implements WithSetTimeout { + @Option( + names = { "-sd", "--source-db" }, + description = "The source database to clone from", + paramLabel = $Db.LABEL + ) + public DbRef $sourceDbRef; + + @Option( + names = { "-s", "--snapshot-id" }, + description = "The snapshot ID to clone from", + paramLabel = "SNAPSHOT_ID" + ) + public String $snapshotId; + + @Option( + names = { "-sr", "--source-region" }, + description = "The region of the source database", + paramLabel = $Regions.LABEL + ) + public Optional $sourceRegion = Optional.empty(); + + @Mixin + protected LongRunningOptionsMixin lrMixin; + + @Option( + names = LR_OPTS_TIMEOUT_NAME, + description = "Timeout for the clone operation to complete", + defaultValue = "30m" + ) + public void setTimeout(Duration timeout) { + lrMixin.setTimeout(timeout); + } + + @Override + @MustBeInvokedByOverriders + protected void prelude() { + super.prelude(); + + if ($sourceDbRef == null) { + $sourceDbRef = DbRefPrompter.prompt(ctx, dbGateway, "Select the source database to clone from:", (b) -> b.fallbackIndex(0).fix(originalArgs(), "--source-db")); + } + + if ($snapshotId == null) { + $snapshotId = SnapshotPrompter.prompt(ctx, dbCloneGateway, $sourceDbRef, "Select the snapshot to clone from:", (b) -> b.fallbackIndex(0).fix(originalArgs(), "--snapshot-id")); + } + } + + @Override + protected OutputJson executeJson(Supplier result) { + return switch (result.get()) { + case CloneStarted(var status) -> OutputJson.serializeValue(mkData(status, null)); + case CloneCompleted(var status, var waitTime) -> OutputJson.serializeValue(mkData(status, waitTime)); + }; + } + + @Override + protected OutputAll execute(Supplier result) { + return switch (result.get()) { + case CloneStarted(var status) -> { + val msg = "Clone operation started with ID %s.".formatted( + ctx.highlight(status.getOperationId()) + ); + yield OutputAll.response(msg, mkData(status, null), List.of( + new Hint("Check clone status", originalArgs(), "--operation-id " + status.getOperationId()) + )); + } + case CloneCompleted(var status, var waitTime) -> { + val msg = "Clone operation completed after waiting %s seconds.".formatted( + ctx.highlight(waitTime.toSeconds()) + ); + yield OutputAll.response(msg, mkData(status, waitTime), List.of( + new Hint("Get database info", "${cli.name} db get %s".formatted($dbRef)) + )); + } + }; + } + + @Override + protected Operation mkOperation() { + return new DbCloneStartOperation(dbCloneGateway, new DbCloneStartRequest( + $dbRef, $sourceDbRef, $snapshotId, $sourceRegion, lrMixin.options(ctx) + )); + } + + @Override + protected String dbRefPrompt() { + return "Select the target database"; + } + + private LinkedHashMap mkData(DatabaseCloneStatus status, Duration waitedDuration) { + return sequencedMapOf( + "operationId", status.getOperationId(), + "status", status.getStatus(), + "phase", status.getPhase(), + "sourceDbId", status.getSourceDbId(), + "targetDbId", status.getTargetDbId(), + "snapshotId", status.getSnapshotId(), + "waitedSeconds", Optional.ofNullable(waitedDuration).map(Duration::getSeconds) // TODO check if data is all correct and complete + ); + } +} diff --git a/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneStatusCmd.java b/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneStatusCmd.java new file mode 100644 index 00000000..7d288e06 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/commands/db/clone/DbCloneStatusCmd.java @@ -0,0 +1,65 @@ +package com.dtsx.astra.cli.commands.db.clone; + +import com.dtsx.astra.cli.core.help.Example; +import com.dtsx.astra.cli.core.models.CloneOperationId; +import com.dtsx.astra.cli.core.output.formats.OutputAll; +import com.dtsx.astra.cli.core.output.formats.OutputJson; +import com.dtsx.astra.cli.core.output.table.ShellTable; +import com.dtsx.astra.cli.operations.Operation; +import com.dtsx.astra.cli.operations.db.clone.DbCloneStatusOperation; +import com.dtsx.astra.cli.operations.db.clone.DbCloneStatusOperation.DbCloneStatusRequest; +import com.dtsx.astra.sdk.db.domain.DatabaseCloneStatus; +import lombok.val; +import picocli.CommandLine.Command; +import picocli.CommandLine.Option; + +import java.util.function.Supplier; + +import static com.dtsx.astra.cli.utils.CollectionUtils.sequencedMapOf; + +@Command( + name = "status", + description = "Check the status of an ongoing or completed clone operation" +) +@Example( + comment = "Check the status of a specific clone operation", + command = "${cli.name} db clone status my_db -id 202d0d1b-14cf-43dc-89fe-efb23758ccef" +) +public class DbCloneStatusCmd extends AbstractDbCloneCmd { + @Option( + names = { "-id", "--operation-id" }, + description = "The ID of the clone operation", + paramLabel = "OPERATION_ID", + required = true + ) + public CloneOperationId $operationId; + + @Override + protected OutputJson executeJson(Supplier result) { + return OutputJson.serializeValue(result); + } + + @Override + protected OutputAll execute(Supplier result) { + val status = result.get(); + return ShellTable.forAttributes(sequencedMapOf( + "Operation ID", status.getOperationId(), + "Status", status.getStatus(), + "Source DB", status.getSourceDbId(), + "Target DB", status.getTargetDbId(), + "Snapshot ID", status.getSnapshotId(), + "Phase", status.getPhase(), + "Message", status.getMessage() + )); + } + + @Override + protected Operation mkOperation() { + return new DbCloneStatusOperation(dbCloneGateway, new DbCloneStatusRequest($dbRef, $operationId)); + } + + @Override + protected String dbRefPrompt() { + return "Select the target database for the clone operation"; + } +} diff --git a/src/main/java/com/dtsx/astra/cli/commands/token/AbstractTokenCmd.java b/src/main/java/com/dtsx/astra/cli/commands/token/AbstractTokenCmd.java index 3adb32a9..0df1f3a8 100644 --- a/src/main/java/com/dtsx/astra/cli/commands/token/AbstractTokenCmd.java +++ b/src/main/java/com/dtsx/astra/cli/commands/token/AbstractTokenCmd.java @@ -1,6 +1,7 @@ package com.dtsx.astra.cli.commands.token; import com.dtsx.astra.cli.commands.AbstractConnectedCmd; +import com.dtsx.astra.cli.core.completions.caches.RoleCompletionsCache; import com.dtsx.astra.cli.gateways.token.TokenGateway; import org.jetbrains.annotations.MustBeInvokedByOverriders; @@ -11,6 +12,6 @@ public abstract class AbstractTokenCmd extends AbstractConnectedCmd mkInstances(Ref profileAndSource) { + public DbCompletionsCache(CliContext ctx, Pair profileAndSource) { super(ctx, profileAndSource); } diff --git a/src/main/java/com/dtsx/astra/cli/core/models/CloneOperationId.java b/src/main/java/com/dtsx/astra/cli/core/models/CloneOperationId.java new file mode 100644 index 00000000..39a068e6 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/core/models/CloneOperationId.java @@ -0,0 +1,45 @@ +package com.dtsx.astra.cli.core.models; + +import com.dtsx.astra.cli.core.CliContext; +import com.dtsx.astra.cli.core.datatypes.Either; +import com.dtsx.astra.cli.core.output.Highlightable; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.*; + +import java.util.UUID; + +@EqualsAndHashCode +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class CloneOperationId implements Highlightable { + private final UUID id; + + public static Either parse(@NonNull String id) { + return ModelUtils.trimAndValidateBasics("Clone Operation ID", id).flatMap((trimmed) -> { + try { + val uuid = UUID.fromString(trimmed); + return Either.pure(mkUnsafe(uuid)); + } catch (IllegalArgumentException e) { + return Either.left("Clone Operation ID is not a valid UUID."); + } + }); + } + + public static CloneOperationId mkUnsafe(@NonNull UUID id) { + return new CloneOperationId(id); + } + + @JsonValue + public UUID unwrap() { + return id; + } + + @Override + public String highlight(CliContext ctx) { + return ctx.highlight(unwrap()); + } + + @Override + public String toString() { + return unwrap().toString(); + } +} diff --git a/src/main/java/com/dtsx/astra/cli/core/output/ExitCode.java b/src/main/java/com/dtsx/astra/cli/core/output/ExitCode.java index c87b0616..3d58c890 100644 --- a/src/main/java/com/dtsx/astra/cli/core/output/ExitCode.java +++ b/src/main/java/com/dtsx/astra/cli/core/output/ExitCode.java @@ -16,9 +16,9 @@ public enum ExitCode { ILLEGAL_OPERATION, INVALID_TOKEN, IO_ISSUE, - KEY_NOT_FOUND, KEYSPACE_ALREADY_EXISTS, KEYSPACE_NOT_FOUND, + KEY_NOT_FOUND, NO_ANSWER, OK, PARSE_ISSUE, @@ -31,6 +31,7 @@ public enum ExitCode { REGION_NOT_FOUND, RELEASE_NOT_FOUND, ROLE_NOT_FOUND, + SNAPSHOT_NOT_FOUND, STATUS_ISSUE, TABLE_ALREADY_EXISTS, TABLE_NOT_FOUND, diff --git a/src/main/java/com/dtsx/astra/cli/core/output/prompters/specific/SnapshotPrompter.java b/src/main/java/com/dtsx/astra/cli/core/output/prompters/specific/SnapshotPrompter.java new file mode 100644 index 00000000..e124cee8 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/core/output/prompters/specific/SnapshotPrompter.java @@ -0,0 +1,29 @@ +package com.dtsx.astra.cli.core.output.prompters.specific; + +import com.dtsx.astra.cli.core.CliContext; +import com.dtsx.astra.cli.core.models.DbRef; +import com.dtsx.astra.cli.core.output.prompters.builders.SelectorBuilder.NeedsClearAfterSelection; +import com.dtsx.astra.cli.core.output.prompters.builders.SelectorBuilder.NeedsFallback; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; +import com.dtsx.astra.sdk.db.domain.DatabaseSnapshot; + +import java.util.Optional; +import java.util.function.Function; + +import static com.dtsx.astra.cli.core.output.ExitCode.SNAPSHOT_NOT_FOUND; + +public class SnapshotPrompter { + public static String prompt(CliContext ctx, DbCloneGateway gateway, DbRef sourceDbRef, String prompt, Function, NeedsClearAfterSelection> fix) { + return SpecificPrompter.run(ctx, (b) -> b + .thing("snapshot") + .prompt(prompt) + .thingNotFoundCode(SNAPSHOT_NOT_FOUND) + .thingsSupplier(() -> gateway.findSnapshots(sourceDbRef, Optional.empty(), Optional.empty(), Optional.empty()).toList()) + .getThingIdentifier(DatabaseSnapshot::getId) + .getThingDisplayExtra((snapshot, _) -> snapshot.getTime()) + .fix(fix) + .mapSingleFound(DatabaseSnapshot::getId) + .mapMultipleFound(DatabaseSnapshot::getId) + ); + } +} diff --git a/src/main/java/com/dtsx/astra/cli/gateways/GatewayProvider.java b/src/main/java/com/dtsx/astra/cli/gateways/GatewayProvider.java index 1c5ea810..b0dfd915 100644 --- a/src/main/java/com/dtsx/astra/cli/gateways/GatewayProvider.java +++ b/src/main/java/com/dtsx/astra/cli/gateways/GatewayProvider.java @@ -4,6 +4,7 @@ import com.dtsx.astra.cli.core.models.AstraToken; import com.dtsx.astra.cli.gateways.db.DbGateway; import com.dtsx.astra.cli.gateways.db.cdc.CdcGateway; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; import com.dtsx.astra.cli.gateways.db.collection.CollectionGateway; import com.dtsx.astra.cli.gateways.db.keyspace.KeyspaceGateway; import com.dtsx.astra.cli.gateways.db.region.RegionGateway; @@ -22,6 +23,8 @@ public interface GatewayProvider { DbGateway mkDbGateway(AstraToken token, AstraEnvironment env, CompletionsCache dbCompletionsCache); + DbCloneGateway mkDbCloneGateway(AstraToken token, AstraEnvironment env, CompletionsCache dbCompletionsCache); + PcuGateway mkPcuGateway(AstraToken token, AstraEnvironment env, CompletionsCache pcuCompletionsCache); PcuAssociationsGateway mkPcuAssociationsGateway(AstraToken token, AstraEnvironment env); @@ -46,7 +49,7 @@ public interface GatewayProvider { TableGateway mkTableGateway(AstraToken token, AstraEnvironment env); - TokenGateway mkTokenGateway(AstraToken token, AstraEnvironment env); + TokenGateway mkTokenGateway(AstraToken token, AstraEnvironment env, CompletionsCache roleCompletionsCache); UserGateway mkUserGateway(AstraToken token, AstraEnvironment env, CompletionsCache userCompletionsCache); diff --git a/src/main/java/com/dtsx/astra/cli/gateways/GatewayProviderImpl.java b/src/main/java/com/dtsx/astra/cli/gateways/GatewayProviderImpl.java index 0aaf3ea5..2cc8ab26 100644 --- a/src/main/java/com/dtsx/astra/cli/gateways/GatewayProviderImpl.java +++ b/src/main/java/com/dtsx/astra/cli/gateways/GatewayProviderImpl.java @@ -2,13 +2,14 @@ import com.dtsx.astra.cli.core.CliContext; import com.dtsx.astra.cli.core.completions.CompletionsCache; -import com.dtsx.astra.cli.core.completions.caches.RoleCompletionsCache; import com.dtsx.astra.cli.core.models.AstraToken; import com.dtsx.astra.cli.gateways.db.DbGateway; import com.dtsx.astra.cli.gateways.db.DbGatewayCompletionsCacheWrapper; import com.dtsx.astra.cli.gateways.db.DbGatewayImpl; import com.dtsx.astra.cli.gateways.db.cdc.CdcGateway; import com.dtsx.astra.cli.gateways.db.cdc.CdcGatewayImpl; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGatewayImpl; import com.dtsx.astra.cli.gateways.db.collection.CollectionGateway; import com.dtsx.astra.cli.gateways.db.collection.CollectionGatewayImpl; import com.dtsx.astra.cli.gateways.db.keyspace.KeyspaceGateway; @@ -54,6 +55,11 @@ public DbGateway mkDbGateway(AstraToken token, AstraEnvironment env, Completions return new DbGatewayCompletionsCacheWrapper(new DbGatewayImpl(ctx(), apiProvider(token, env), token, env, GlobalInfoCache.INSTANCE), dbCompletionsCache); } + @Override + public DbCloneGateway mkDbCloneGateway(AstraToken token, AstraEnvironment env, CompletionsCache dbCompletionsCache) { + return new DbCloneGatewayImpl(ctx(), apiProvider(token, env), mkDbGateway(token, env, dbCompletionsCache)); + } + @Override public PcuGateway mkPcuGateway(AstraToken token, AstraEnvironment env, CompletionsCache pcuCompletionsCache) { return new PcuGatewayCompletionsCacheWrapper(new PcuGatewayImpl(ctx(), apiProvider(token, env)), pcuCompletionsCache); @@ -115,8 +121,8 @@ public TableGateway mkTableGateway(AstraToken token, AstraEnvironment env) { } @Override - public TokenGateway mkTokenGateway(AstraToken token, AstraEnvironment env) { - return new TokenGatewayImpl(ctx(), apiProvider(token, env), mkRoleGateway(token, env, new RoleCompletionsCache(ctx())), mkOrgGatewayStateless()); + public TokenGateway mkTokenGateway(AstraToken token, AstraEnvironment env, CompletionsCache roleCompletionsCache) { + return new TokenGatewayImpl(ctx(), apiProvider(token, env), mkRoleGateway(token, env, roleCompletionsCache), mkOrgGatewayStateless()); } @Override diff --git a/src/main/java/com/dtsx/astra/cli/gateways/db/clone/DbCloneGateway.java b/src/main/java/com/dtsx/astra/cli/gateways/db/clone/DbCloneGateway.java new file mode 100644 index 00000000..825f8fbb --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/gateways/db/clone/DbCloneGateway.java @@ -0,0 +1,21 @@ +package com.dtsx.astra.cli.gateways.db.clone; + +import com.dtsx.astra.cli.core.models.CloneOperationId; +import com.dtsx.astra.cli.core.models.DbRef; +import com.dtsx.astra.cli.gateways.SomeGateway; +import com.dtsx.astra.sdk.db.domain.DatabaseCloneStatus; +import com.dtsx.astra.sdk.db.domain.DatabaseSnapshot; + +import java.time.Duration; +import java.util.Optional; +import java.util.stream.Stream; + +public interface DbCloneGateway extends SomeGateway { + DatabaseCloneStatus cloneFrom(DbRef targetDbRef, DbRef sourceDbRef, String snapshotId, Optional sourceRegion); + + DatabaseCloneStatus findClone(DbRef targetDbRef, CloneOperationId operationId); + + Duration waitUntilClonePhase(DbRef targetDbRef, CloneOperationId operationId, String targetStatus, Duration timeout); + + Stream findSnapshots(DbRef sourceDbRef, Optional region, Optional from, Optional to); +} diff --git a/src/main/java/com/dtsx/astra/cli/gateways/db/clone/DbCloneGatewayImpl.java b/src/main/java/com/dtsx/astra/cli/gateways/db/clone/DbCloneGatewayImpl.java new file mode 100644 index 00000000..b69e6b56 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/gateways/db/clone/DbCloneGatewayImpl.java @@ -0,0 +1,61 @@ +package com.dtsx.astra.cli.gateways.db.clone; + +import com.dtsx.astra.cli.core.CliContext; +import com.dtsx.astra.cli.core.models.CloneOperationId; +import com.dtsx.astra.cli.core.models.DbRef; +import com.dtsx.astra.cli.gateways.APIProvider; +import com.dtsx.astra.cli.gateways.db.DbGateway; +import com.dtsx.astra.sdk.db.domain.DatabaseCloneStatus; +import com.dtsx.astra.sdk.db.domain.DatabaseSnapshot; +import lombok.RequiredArgsConstructor; +import lombok.val; + +import java.time.Duration; +import java.util.Optional; +import java.util.stream.Stream; + +import static com.dtsx.astra.cli.core.mixins.LongRunningOptionsMixin.awaitGenericStatus; + +@RequiredArgsConstructor +public class DbCloneGatewayImpl implements DbCloneGateway { + private final CliContext ctx; + private final APIProvider api; + private final DbGateway dbGateway; + + @Override + public DatabaseCloneStatus cloneFrom(DbRef targetDbRef, DbRef sourceDbRef, String snapshotId, Optional sourceRegion) { + val sourceId = dbGateway.findOne(sourceDbRef).getId(); + + return api.dbOpsClient(targetDbRef).clone().cloneFrom( + sourceId, + snapshotId, + sourceRegion.orElse(null) + ); + } + + @Override + public DatabaseCloneStatus findClone(DbRef targetDbRef, CloneOperationId operationId) { + return api.dbOpsClient(targetDbRef).clone().getCloneStatus(operationId.toString()); + } + + @Override + public Duration waitUntilClonePhase(DbRef targetDbRef, CloneOperationId operationId, String targetPhase, Duration timeout) { + return awaitGenericStatus( + ctx, + "database clone", + targetPhase.toUpperCase(), + () -> findClone(targetDbRef, operationId).getPhase().toUpperCase(), + s -> s, + timeout + ); + } + + @Override + public Stream findSnapshots(DbRef sourceDbRef, Optional region, Optional from, Optional to) { + return api.dbOpsClient(sourceDbRef).snapshots().find( + from.orElse(null), + to.orElse(null), + region.orElse(null) + ); + } +} diff --git a/src/main/java/com/dtsx/astra/cli/operations/db/clone/DbCloneListSnapshotsOperation.java b/src/main/java/com/dtsx/astra/cli/operations/db/clone/DbCloneListSnapshotsOperation.java new file mode 100644 index 00000000..00fef8f2 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/operations/db/clone/DbCloneListSnapshotsOperation.java @@ -0,0 +1,28 @@ +package com.dtsx.astra.cli.operations.db.clone; + +import com.dtsx.astra.cli.core.models.DbRef; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; +import com.dtsx.astra.cli.operations.Operation; +import com.dtsx.astra.sdk.db.domain.DatabaseSnapshot; +import lombok.RequiredArgsConstructor; + +import java.util.Optional; +import java.util.stream.Stream; + +@RequiredArgsConstructor +public class DbCloneListSnapshotsOperation implements Operation> { + private final DbCloneGateway dbCloneGateway; + private final DbCloneListSnapshotsRequest request; + + public record DbCloneListSnapshotsRequest( + DbRef sourceDbRef, + Optional region, + Optional from, + Optional to + ) {} + + @Override + public Stream execute() { + return dbCloneGateway.findSnapshots(request.sourceDbRef, request.region, request.from, request.to); + } +} diff --git a/src/main/java/com/dtsx/astra/cli/operations/db/clone/DbCloneStartOperation.java b/src/main/java/com/dtsx/astra/cli/operations/db/clone/DbCloneStartOperation.java new file mode 100644 index 00000000..1d510363 --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/operations/db/clone/DbCloneStartOperation.java @@ -0,0 +1,59 @@ +package com.dtsx.astra.cli.operations.db.clone; + +import com.dtsx.astra.cli.core.models.CloneOperationId; +import com.dtsx.astra.cli.core.models.DbRef; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; +import com.dtsx.astra.cli.operations.Operation; +import com.dtsx.astra.sdk.db.domain.DatabaseCloneStatus; +import lombok.RequiredArgsConstructor; +import lombok.val; + +import java.time.Duration; +import java.util.Optional; +import java.util.UUID; + +import static com.dtsx.astra.cli.core.mixins.LongRunningOptionsMixin.LongRunningOptions; +import static com.dtsx.astra.cli.operations.db.clone.DbCloneStartOperation.DbCloneStartResult; + +@RequiredArgsConstructor +public class DbCloneStartOperation implements Operation { + private final DbCloneGateway dbCloneGateway; + private final DbCloneStartRequest request; + + public record DbCloneStartRequest( + DbRef targetDbRef, + DbRef sourceDbRef, + String snapshotId, + Optional sourceRegion, + LongRunningOptions lrOptions + ) {} + + public sealed interface DbCloneStartResult {} + public record CloneStarted(DatabaseCloneStatus status) implements DbCloneStartResult {} + public record CloneCompleted(DatabaseCloneStatus status, Duration waitTime) implements DbCloneStartResult {} + + @Override + public DbCloneStartResult execute() { + val status = dbCloneGateway.cloneFrom( + request.targetDbRef, + request.sourceDbRef, + request.snapshotId, + request.sourceRegion + ); + + if (request.lrOptions.dontWait()) { + return new CloneStarted(status); + } + + val operationId = CloneOperationId.mkUnsafe(UUID.fromString(status.getOperationId())); + + val waitTime = dbCloneGateway.waitUntilClonePhase( + request.targetDbRef, + operationId, + "Done", + request.lrOptions.timeout() + ); + + return new CloneCompleted(dbCloneGateway.findClone(request.targetDbRef, operationId), waitTime); + } +} diff --git a/src/main/java/com/dtsx/astra/cli/operations/db/clone/DbCloneStatusOperation.java b/src/main/java/com/dtsx/astra/cli/operations/db/clone/DbCloneStatusOperation.java new file mode 100644 index 00000000..227b603c --- /dev/null +++ b/src/main/java/com/dtsx/astra/cli/operations/db/clone/DbCloneStatusOperation.java @@ -0,0 +1,21 @@ +package com.dtsx.astra.cli.operations.db.clone; + +import com.dtsx.astra.cli.core.models.CloneOperationId; +import com.dtsx.astra.cli.core.models.DbRef; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; +import com.dtsx.astra.cli.operations.Operation; +import com.dtsx.astra.sdk.db.domain.DatabaseCloneStatus; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class DbCloneStatusOperation implements Operation { + private final DbCloneGateway dbCloneGateway; + private final DbCloneStatusRequest request; + + public record DbCloneStatusRequest(DbRef targetDbRef, CloneOperationId operationId) {} + + @Override + public DatabaseCloneStatus execute() { + return dbCloneGateway.findClone(request.targetDbRef, request.operationId); + } +} diff --git a/src/test/java/com/dtsx/astra/cli/snapshot/BaseCmdSnapshotTest.java b/src/test/java/com/dtsx/astra/cli/snapshot/BaseCmdSnapshotTest.java index 5902a6fa..881e5dff 100644 --- a/src/test/java/com/dtsx/astra/cli/snapshot/BaseCmdSnapshotTest.java +++ b/src/test/java/com/dtsx/astra/cli/snapshot/BaseCmdSnapshotTest.java @@ -156,6 +156,7 @@ protected final UnverifiedCmdOutput run(String cmd, OutputType outputType, Funct put("${RegionName}", Regions.NAME.unwrap()); put("${CollectionName}", Collections.Ref.name()); put("${TableName}", Tables.Ref.name()); + put("${CloneOperationId}", Clone.OperationId.toString()); }}; private String[] buildCmdParts(String cmd, OutputType outputType) { diff --git a/src/test/java/com/dtsx/astra/cli/snapshot/commands/db/clone/DbCloneListSnapshotsCmdSnapshotTest.java b/src/test/java/com/dtsx/astra/cli/snapshot/commands/db/clone/DbCloneListSnapshotsCmdSnapshotTest.java new file mode 100644 index 00000000..fddf2f64 --- /dev/null +++ b/src/test/java/com/dtsx/astra/cli/snapshot/commands/db/clone/DbCloneListSnapshotsCmdSnapshotTest.java @@ -0,0 +1,32 @@ +package com.dtsx.astra.cli.snapshot.commands.db.clone; + +import com.dtsx.astra.cli.core.output.formats.OutputType; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; +import com.dtsx.astra.cli.snapshot.BaseCmdSnapshotTest; +import com.dtsx.astra.cli.snapshot.SnapshotTestOptions.SnapshotTestOptionsModifier; +import com.dtsx.astra.cli.snapshot.annotations.TestForAllOutputs; +import com.dtsx.astra.cli.testlib.Fixtures.Clone; +import com.dtsx.astra.cli.testlib.Fixtures.Databases; + +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; + +public class DbCloneListSnapshotsCmdSnapshotTest extends BaseCmdSnapshotTest { + private SnapshotTestOptionsModifier opts() { + return (o) -> o + .gateway(DbCloneGateway.class, (mock) -> { + doAnswer(inv -> Clone.Snapshots.stream()).when(mock).findSnapshots(any(), any(), any(), any()); + }); + } + + @TestForAllOutputs + public void db_clone_list_snapshots(OutputType outputType) { + verifyRun("db clone list-snapshots ${DatabaseName}", outputType, o -> o.use(opts()) + .verify((mocks) -> { + verify(mocks.dbCloneGateway()).findSnapshots(Databases.NameRef, Optional.empty(), Optional.empty(), Optional.empty()); + })); + } +} diff --git a/src/test/java/com/dtsx/astra/cli/snapshot/commands/db/clone/DbCloneStartCmdSnapshotTest.java b/src/test/java/com/dtsx/astra/cli/snapshot/commands/db/clone/DbCloneStartCmdSnapshotTest.java new file mode 100644 index 00000000..bcc5f0e4 --- /dev/null +++ b/src/test/java/com/dtsx/astra/cli/snapshot/commands/db/clone/DbCloneStartCmdSnapshotTest.java @@ -0,0 +1,45 @@ +package com.dtsx.astra.cli.snapshot.commands.db.clone; + +import com.dtsx.astra.cli.core.models.DbRef; +import com.dtsx.astra.cli.core.output.formats.OutputType; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; +import com.dtsx.astra.cli.snapshot.BaseCmdSnapshotTest; +import com.dtsx.astra.cli.snapshot.SnapshotTestOptions.SnapshotTestOptionsModifier; +import com.dtsx.astra.cli.snapshot.annotations.TestForAllOutputs; +import com.dtsx.astra.cli.testlib.Fixtures.Clone; +import com.dtsx.astra.cli.testlib.Fixtures.Databases; + +import java.time.Duration; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +public class DbCloneStartCmdSnapshotTest extends BaseCmdSnapshotTest { + private SnapshotTestOptionsModifier opts() { + return (o) -> o + .gateway(DbCloneGateway.class, (mock) -> { + doReturn(Clone.Status).when(mock).cloneFrom(any(), any(), any(), any()); + when(mock.waitUntilClonePhase(any(), any(), any(), any())).thenReturn(Duration.ofMillis(6789)); + when(mock.findClone(any(), any())).thenReturn(Clone.StatusCompleted); + }); + } + + @TestForAllOutputs + public void db_clone_start(OutputType outputType) { + verifyRun("db clone start new_db -sd ${DatabaseName} -s snap-789", outputType, o -> o.use(opts()) + .verify((mocks) -> { + verify(mocks.dbCloneGateway()).cloneFrom(DbRef.fromNameUnsafe("new_db"), Databases.NameRef, "snap-789", Optional.empty()); + verify(mocks.dbCloneGateway()).waitUntilClonePhase(DbRef.fromNameUnsafe("new_db"), Clone.OperationId, "Done", Duration.ofMinutes(30)); + })); + } + + @TestForAllOutputs + public void db_clone_start_async(OutputType outputType) { + verifyRun("db clone start new_db -sd ${DatabaseName} -s snap-789 --async", outputType, o -> o.use(opts()) + .verify((mocks) -> { + verify(mocks.dbCloneGateway()).cloneFrom(DbRef.fromNameUnsafe("new_db"), Databases.NameRef, "snap-789", Optional.empty()); + verify(mocks.dbCloneGateway(), never()).waitUntilClonePhase(any(), any(), any(), any()); + })); + } +} diff --git a/src/test/java/com/dtsx/astra/cli/snapshot/commands/db/clone/DbCloneStatusCmdSnapshotTest.java b/src/test/java/com/dtsx/astra/cli/snapshot/commands/db/clone/DbCloneStatusCmdSnapshotTest.java new file mode 100644 index 00000000..abf62447 --- /dev/null +++ b/src/test/java/com/dtsx/astra/cli/snapshot/commands/db/clone/DbCloneStatusCmdSnapshotTest.java @@ -0,0 +1,30 @@ +package com.dtsx.astra.cli.snapshot.commands.db.clone; + +import com.dtsx.astra.cli.core.output.formats.OutputType; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; +import com.dtsx.astra.cli.snapshot.BaseCmdSnapshotTest; +import com.dtsx.astra.cli.snapshot.SnapshotTestOptions.SnapshotTestOptionsModifier; +import com.dtsx.astra.cli.snapshot.annotations.TestForAllOutputs; +import com.dtsx.astra.cli.testlib.Fixtures.Clone; +import com.dtsx.astra.cli.testlib.Fixtures.Databases; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.verify; + +public class DbCloneStatusCmdSnapshotTest extends BaseCmdSnapshotTest { + private SnapshotTestOptionsModifier opts() { + return (o) -> o + .gateway(DbCloneGateway.class, (mock) -> { + doReturn(Clone.Status).when(mock).findClone(any(), any()); + }); + } + + @TestForAllOutputs + public void db_clone_status(OutputType outputType) { + verifyRun("db clone status ${DatabaseName} -id ${CloneOperationId}", outputType, o -> o.use(opts()) + .verify((mocks) -> { + verify(mocks.dbCloneGateway()).findClone(Databases.NameRef, Clone.OperationId); + })); + } +} diff --git a/src/test/java/com/dtsx/astra/cli/testlib/Fixtures.java b/src/test/java/com/dtsx/astra/cli/testlib/Fixtures.java index 861b49cc..ebbbed00 100644 --- a/src/test/java/com/dtsx/astra/cli/testlib/Fixtures.java +++ b/src/test/java/com/dtsx/astra/cli/testlib/Fixtures.java @@ -10,6 +10,8 @@ import com.dtsx.astra.cli.core.models.*; import com.dtsx.astra.cli.utils.JsonUtils; import com.dtsx.astra.sdk.db.domain.Database; +import com.dtsx.astra.sdk.db.domain.DatabaseCloneStatus; +import com.dtsx.astra.sdk.db.domain.DatabaseSnapshot; import com.dtsx.astra.sdk.db.domain.Datacenter; import com.dtsx.astra.sdk.org.domain.*; import com.dtsx.astra.sdk.pcu.domain.PCUType; @@ -94,6 +96,16 @@ public static class Regions { public static final RegionRef NAME = RegionRef.mkUnsafe(ONE.getRegion()); } + public static class Clone { + public static final DatabaseCloneStatus Status = load("DatabaseCloneStatus.json", new TypeReference<>() {}); + + public static final DatabaseCloneStatus StatusCompleted = load("DatabaseCloneStatusCompleted.json", new TypeReference<>() {}); + + public static final CloneOperationId OperationId = CloneOperationId.mkUnsafe(UUID.fromString(Status.getOperationId())); + + public static final List Snapshots = load("DatabaseSnapshots.json", new TypeReference<>() {}); + } + public static final CreateTokenResponse CreateTokenResponse = load("CreateTokenResponse.json", new TypeReference<>() {}); public static final FindEmbeddingProvidersResult FindEmbeddingProvidersResult = load("FindEmbeddingProvidersResult.json", new TypeReference<>() {}); diff --git a/src/test/java/com/dtsx/astra/cli/testlib/doubles/GatewayProviderMock.java b/src/test/java/com/dtsx/astra/cli/testlib/doubles/GatewayProviderMock.java index e0d1f631..e5b8939a 100644 --- a/src/test/java/com/dtsx/astra/cli/testlib/doubles/GatewayProviderMock.java +++ b/src/test/java/com/dtsx/astra/cli/testlib/doubles/GatewayProviderMock.java @@ -6,6 +6,7 @@ import com.dtsx.astra.cli.gateways.SomeGateway; import com.dtsx.astra.cli.gateways.db.DbGateway; import com.dtsx.astra.cli.gateways.db.cdc.CdcGateway; +import com.dtsx.astra.cli.gateways.db.clone.DbCloneGateway; import com.dtsx.astra.cli.gateways.db.collection.CollectionGateway; import com.dtsx.astra.cli.gateways.db.keyspace.KeyspaceGateway; import com.dtsx.astra.cli.gateways.db.region.RegionGateway; @@ -52,9 +53,14 @@ public class GatewayProviderMock implements GatewayProvider { RoleGateway.class, TableGateway.class, TokenGateway.class, - UserGateway.class + UserGateway.class, + DbCloneGateway.class ); + public DbCloneGateway dbCloneGateway() { + return returnIfEnabled(DbCloneGateway.class); + } + public DbGateway dbGateway() { return returnIfEnabled(DbGateway.class); } @@ -124,6 +130,11 @@ public DbGateway mkDbGateway(AstraToken token, AstraEnvironment env, Completions return dbGateway(); } + @Override + public DbCloneGateway mkDbCloneGateway(AstraToken token, AstraEnvironment env, CompletionsCache dbCompletionsCache) { + return dbCloneGateway(); + } + @Override public PcuGateway mkPcuGateway(AstraToken token, AstraEnvironment env, CompletionsCache pcuCompletionsCache) { return pcuGateway(); @@ -185,7 +196,7 @@ public TableGateway mkTableGateway(AstraToken token, AstraEnvironment env) { } @Override - public TokenGateway mkTokenGateway(AstraToken token, AstraEnvironment env) { + public TokenGateway mkTokenGateway(AstraToken token, AstraEnvironment env, CompletionsCache roleCompletionsCache) { return tokenGateway(); } diff --git a/src/test/resources/fixtures/serialized/DatabaseCloneStatus.json b/src/test/resources/fixtures/serialized/DatabaseCloneStatus.json new file mode 100644 index 00000000..bb39928c --- /dev/null +++ b/src/test/resources/fixtures/serialized/DatabaseCloneStatus.json @@ -0,0 +1,9 @@ +{ + "operationId": "c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f", + "status": "RUNNING", + "sourceDbId": "123e4567-e89b-12d3-a456-426614174000", + "targetDbId": "987e6543-e21b-34d5-c678-426614174011", + "snapshotId": "78901234-7890-1234-5678-789012345678", + "phase": "COPYING_DATA", + "message": "Clone is in progress" +} diff --git a/src/test/resources/fixtures/serialized/DatabaseCloneStatusCompleted.json b/src/test/resources/fixtures/serialized/DatabaseCloneStatusCompleted.json new file mode 100644 index 00000000..7ceaa92d --- /dev/null +++ b/src/test/resources/fixtures/serialized/DatabaseCloneStatusCompleted.json @@ -0,0 +1,9 @@ +{ + "operationId": "c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f", + "status": "DONE", + "sourceDbId": "123e4567-e89b-12d3-a456-426614174000", + "targetDbId": "987e6543-e21b-34d5-c678-426614174011", + "snapshotId": "78901234-7890-1234-5678-789012345678", + "phase": "DONE", + "message": "Clone completed successfully" +} diff --git a/src/test/resources/fixtures/serialized/DatabaseSnapshots.json b/src/test/resources/fixtures/serialized/DatabaseSnapshots.json new file mode 100644 index 00000000..1b42a905 --- /dev/null +++ b/src/test/resources/fixtures/serialized/DatabaseSnapshots.json @@ -0,0 +1,10 @@ +[ + { + "id": "11111111-1111-1111-1111-111111111111", + "time": "2023-10-20T10:00:00Z" + }, + { + "id": "22222222-2222-2222-2222-222222222222", + "time": "2023-10-21T10:00:00Z" + } +] diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneListSnapshotsCmd/db_clone_list_snapshots.csv.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneListSnapshotsCmd/db_clone_list_snapshots.csv.approved.txt new file mode 100644 index 00000000..46f40a71 --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneListSnapshotsCmd/db_clone_list_snapshots.csv.approved.txt @@ -0,0 +1,9 @@ +---- meta ---- +command: astra db clone list-snapshots two_regions_db -o csv +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: code,message,id,time +stdout: OK,,11111111-1111-1111-1111-111111111111,2023-10-20T10:00:00Z +stdout: OK,,22222222-2222-2222-2222-222222222222,2023-10-21T10:00:00Z +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneListSnapshotsCmd/db_clone_list_snapshots.human.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneListSnapshotsCmd/db_clone_list_snapshots.human.approved.txt new file mode 100644 index 00000000..1581139f --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneListSnapshotsCmd/db_clone_list_snapshots.human.approved.txt @@ -0,0 +1,12 @@ +---- meta ---- +command: astra db clone list-snapshots two_regions_db +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: ┌──────────────────────────────────────┬──────────────────────┐ +stdout: │ ID │ Time │ +stdout: ├──────────────────────────────────────┼──────────────────────┤ +stdout: │ 11111111-1111-1111-1111-111111111111 │ 2023-10-20T10:00:00Z │ +stdout: │ 22222222-2222-2222-2222-222222222222 │ 2023-10-21T10:00:00Z │ +stdout: └──────────────────────────────────────┴──────────────────────┘ +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneListSnapshotsCmd/db_clone_list_snapshots.json.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneListSnapshotsCmd/db_clone_list_snapshots.json.approved.txt new file mode 100644 index 00000000..4ec175ca --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneListSnapshotsCmd/db_clone_list_snapshots.json.approved.txt @@ -0,0 +1,16 @@ +---- meta ---- +command: astra db clone list-snapshots two_regions_db -o json +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: { +stdout: "code" : "OK", +stdout: "data" : [ { +stdout: "id" : "11111111-1111-1111-1111-111111111111", +stdout: "time" : "2023-10-20T10:00:00Z" +stdout: }, { +stdout: "id" : "22222222-2222-2222-2222-222222222222", +stdout: "time" : "2023-10-21T10:00:00Z" +stdout: } ] +stdout: } +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start.csv.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start.csv.approved.txt new file mode 100644 index 00000000..b90503ef --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start.csv.approved.txt @@ -0,0 +1,8 @@ +---- meta ---- +command: astra db clone start new_db -sd two_regions_db -s snap-789 -o csv +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: code,message,operation_id,status,phase,source_db_id,target_db_id,snapshot_id,waited_seconds +stdout: OK,Clone operation completed after waiting 6 seconds.,c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f,DONE,DONE,123e4567-e89b-12d3-a456-426614174000,987e6543-e21b-34d5-c678-426614174011,78901234-7890-1234-5678-789012345678,6 +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start.human.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start.human.approved.txt new file mode 100644 index 00000000..12b82b87 --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start.human.approved.txt @@ -0,0 +1,10 @@ +---- meta ---- +command: astra db clone start new_db -sd two_regions_db -s snap-789 +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: Clone operation completed after waiting 6 seconds. +stdout: +stdout: # Get database info +stdout: $ astra db get new_db +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start.json.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start.json.approved.txt new file mode 100644 index 00000000..636e162c --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start.json.approved.txt @@ -0,0 +1,18 @@ +---- meta ---- +command: astra db clone start new_db -sd two_regions_db -s snap-789 -o json +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: { +stdout: "code" : "OK", +stdout: "data" : { +stdout: "operationId" : "c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f", +stdout: "status" : "DONE", +stdout: "phase" : "DONE", +stdout: "sourceDbId" : "123e4567-e89b-12d3-a456-426614174000", +stdout: "targetDbId" : "987e6543-e21b-34d5-c678-426614174011", +stdout: "snapshotId" : "78901234-7890-1234-5678-789012345678", +stdout: "waitedSeconds" : 6 +stdout: } +stdout: } +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start_async.csv.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start_async.csv.approved.txt new file mode 100644 index 00000000..2e1c8a95 --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start_async.csv.approved.txt @@ -0,0 +1,8 @@ +---- meta ---- +command: astra db clone start new_db -sd two_regions_db -s snap-789 --async -o csv +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: code,message,operation_id,status,phase,source_db_id,target_db_id,snapshot_id,waited_seconds +stdout: OK,Clone operation started with ID 'c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f'.,c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f,RUNNING,COPYING_DATA,123e4567-e89b-12d3-a456-426614174000,987e6543-e21b-34d5-c678-426614174011,78901234-7890-1234-5678-789012345678, +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start_async.human.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start_async.human.approved.txt new file mode 100644 index 00000000..0fa93404 --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start_async.human.approved.txt @@ -0,0 +1,10 @@ +---- meta ---- +command: astra db clone start new_db -sd two_regions_db -s snap-789 --async +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: Clone operation started with ID 'c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f'. +stdout: +stdout: # Check clone status +stdout: $ astra db clone start new_db -sd two_regions_db -s snap-789 --async --operation-id c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start_async.json.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start_async.json.approved.txt new file mode 100644 index 00000000..3e851a29 --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneStartCmd/db_clone_start_async.json.approved.txt @@ -0,0 +1,18 @@ +---- meta ---- +command: astra db clone start new_db -sd two_regions_db -s snap-789 --async -o json +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: { +stdout: "code" : "OK", +stdout: "data" : { +stdout: "operationId" : "c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f", +stdout: "status" : "RUNNING", +stdout: "phase" : "COPYING_DATA", +stdout: "sourceDbId" : "123e4567-e89b-12d3-a456-426614174000", +stdout: "targetDbId" : "987e6543-e21b-34d5-c678-426614174011", +stdout: "snapshotId" : "78901234-7890-1234-5678-789012345678", +stdout: "waitedSeconds" : null +stdout: } +stdout: } +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneStatusCmd/db_clone_status.csv.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneStatusCmd/db_clone_status.csv.approved.txt new file mode 100644 index 00000000..7d460e0c --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneStatusCmd/db_clone_status.csv.approved.txt @@ -0,0 +1,14 @@ +---- meta ---- +command: astra db clone status two_regions_db -id c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f -o csv +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: code,message,attribute,value +stdout: OK,,Operation ID,c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f +stdout: OK,,Status,RUNNING +stdout: OK,,Source DB,123e4567-e89b-12d3-a456-426614174000 +stdout: OK,,Target DB,987e6543-e21b-34d5-c678-426614174011 +stdout: OK,,Snapshot ID,78901234-7890-1234-5678-789012345678 +stdout: OK,,Phase,COPYING_DATA +stdout: OK,,Message,Clone is in progress +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneStatusCmd/db_clone_status.human.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneStatusCmd/db_clone_status.human.approved.txt new file mode 100644 index 00000000..61f36721 --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneStatusCmd/db_clone_status.human.approved.txt @@ -0,0 +1,17 @@ +---- meta ---- +command: astra db clone status two_regions_db -id c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: ┌──────────────┬──────────────────────────────────────┐ +stdout: │ Attribute │ Value │ +stdout: ├──────────────┼──────────────────────────────────────┤ +stdout: │ Operation ID │ c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f │ +stdout: │ Status │ RUNNING │ +stdout: │ Source DB │ 123e4567-e89b-12d3-a456-426614174000 │ +stdout: │ Target DB │ 987e6543-e21b-34d5-c678-426614174011 │ +stdout: │ Snapshot ID │ 78901234-7890-1234-5678-789012345678 │ +stdout: │ Phase │ COPYING_DATA │ +stdout: │ Message │ Clone is in progress │ +stdout: └──────────────┴──────────────────────────────────────┘ +---- end ---- diff --git a/src/test/resources/snapshots/_db/_clone/DbCloneStatusCmd/db_clone_status.json.approved.txt b/src/test/resources/snapshots/_db/_clone/DbCloneStatusCmd/db_clone_status.json.approved.txt new file mode 100644 index 00000000..092001bf --- /dev/null +++ b/src/test/resources/snapshots/_db/_clone/DbCloneStatusCmd/db_clone_status.json.approved.txt @@ -0,0 +1,18 @@ +---- meta ---- +command: astra db clone status two_regions_db -id c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f -o json +exit_code: 0 +---- output ---- +stderr: [WARNING] astra db clone commands are still in beta and may change without notice. +stdout: { +stdout: "code" : "OK", +stdout: "data" : { +stdout: "operationId" : "c4d5e6f7-a8b9-4c5d-6e7f-8a9b0c1d2e3f", +stdout: "sourceDbId" : "123e4567-e89b-12d3-a456-426614174000", +stdout: "targetDbId" : "987e6543-e21b-34d5-c678-426614174011", +stdout: "phase" : "COPYING_DATA", +stdout: "status" : "RUNNING", +stdout: "message" : "Clone is in progress", +stdout: "snapshotId" : "78901234-7890-1234-5678-789012345678" +stdout: } +stdout: } +---- end ----