Skip to content

Commit 980dfda

Browse files
authored
Add ability to run against local devops api instances (#308)
* a * b * c * little more work * unhide --local-endpoint for config create * auto use fake token for local env * update tests * remove some fqdns
1 parent e6fee6e commit 980dfda

24 files changed

Lines changed: 454 additions & 127 deletions

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
ASTRA_TOKEN=your_astra_token_here
33

44
# Optional: Environment name, defaults to 'prod' if not set.
5-
# Possible values: prod, dev, test
5+
# Possible values: prod, dev, test, local
66
ASTRA_ENV=prod
77

88
# Required: Full Astra DB URL, example:
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
a18d6edf7a50ce022ca1b6624a9e0331f444aaf17914547e3c69c3c2f567b286

build.gradle.kts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import java.net.URLClassLoader
2+
import java.util.zip.ZipFile
3+
import java.security.MessageDigest
24

35
buildscript {
46
repositories {
@@ -401,3 +403,53 @@ tasks.register<Jar>("fatJar") {
401403
configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
402404
})
403405
}
406+
407+
// -----------------------------------------------------------------
408+
// AstraEnvironment classpath patch
409+
//
410+
// src/main/java/com/dtsx/astra/sdk/utils/AstraEnvironment.java shadows
411+
// the upstream enum to add LOCAL support. The SHA below guards against
412+
// the upstream class changing without us noticing.
413+
//
414+
// If the build fails with a SHA mismatch:
415+
// 1. Check what changed in astra-sdk-devops
416+
// 2. Update src/main/java/com/dtsx/astra/sdk/utils/AstraEnvironment.java
417+
// 3. Run ./gradlew updateAstraEnvironmentPatchSha
418+
// 4. Commit both files
419+
// -----------------------------------------------------------------
420+
421+
fun astraEnvClassSha(): String {
422+
val jar = configurations.runtimeClasspath.get().first { it.name.contains("astra-sdk-devops") }
423+
val bytes = ZipFile(jar).use { zip ->
424+
zip.getInputStream(zip.getEntry("com/dtsx/astra/sdk/utils/AstraEnvironment.class")).readBytes()
425+
}
426+
return MessageDigest.getInstance("SHA-256")
427+
.digest(bytes).joinToString("") { "%02x".format(it) }
428+
}
429+
430+
tasks.register("verifyAstraEnvironmentPatch") {
431+
inputs.files(configurations.runtimeClasspath)
432+
inputs.file("assets/AstraEnvironment.class.sha256")
433+
doLast {
434+
val expected = file("assets/AstraEnvironment.class.sha256").readText().trim()
435+
val actual = astraEnvClassSha()
436+
if (actual != expected)
437+
throw GradleException(
438+
"Upstream AstraEnvironment has changed (SHA mismatch).\n" +
439+
"Update the patch then run: ./gradlew updateAstraEnvironmentPatchSha\n\n" +
440+
"Expected: $expected\nActual: $actual"
441+
)
442+
}
443+
}
444+
445+
tasks.register("updateAstraEnvironmentPatchSha") {
446+
group = "build"
447+
description = "Regenerates the AstraEnvironment patch SHA after an upstream change."
448+
doLast {
449+
val sha = astraEnvClassSha()
450+
file("assets/AstraEnvironment.class.sha256").writeText(sha)
451+
logger.lifecycle("SHA updated: $sha")
452+
}
453+
}
454+
455+
tasks.compileJava { dependsOn("verifyAstraEnvironmentPatch") }

src/main/java/com/dtsx/astra/cli/commands/AbstractConnectedCmd.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ record DefaultFile(ProfileName profile) implements ProfileSource {}
4444
protected void prelude() {
4545
super.prelude();
4646
$connOpts = mergeConnectionOptions();
47+
48+
if (!ctx.properties().disableBetaWarnings() && profile().env().isLocal()) {
49+
ctx.log().warn("Local environments are still in beta and may change without notice.");
50+
}
4751
}
4852

4953
private ConnectionOptions mergeConnectionOptions() {
@@ -85,7 +89,8 @@ private ProfileSource profileSource() {
8589
}
8690

8791
if ($connOpts.$creds != null) {
88-
return new FromArgs($connOpts.$creds.$token, $connOpts.$creds.$env.orElse(AstraEnvironment.PROD));
92+
val env = AstraEnvironment.resolve($connOpts.$creds.$env, $connOpts.$creds.$localEndpoint);
93+
return new FromArgs($connOpts.$creds.$token, env);
8994
}
9095

9196
val defaultFilePath = AstraConfig.resolveDefaultAstraConfigFile(ctx);

src/main/java/com/dtsx/astra/cli/commands/ConnectionOptions.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import com.dtsx.astra.cli.core.config.ProfileName;
1010
import com.dtsx.astra.cli.core.models.AstraToken;
1111
import com.dtsx.astra.cli.core.properties.CliProperties.ConstEnvVars;
12-
import com.dtsx.astra.sdk.utils.AstraEnvironment;
12+
1313
import lombok.NoArgsConstructor;
1414
import lombok.val;
1515
import org.jetbrains.annotations.Nullable;
@@ -46,19 +46,28 @@ public static class CredsSpec {
4646
@Option(
4747
names = { $Env.LONG },
4848
completionCandidates = AstraEnvCompletion.class,
49-
description = "Astra environment the token belongs to: prod (default), dev, or test. Leave unset unless you were issued a non-prod token.",
49+
description = "Astra environment the token belongs to: prod (default), dev, test, or local. Leave unset unless you were issued a non-prod token.",
5050
paramLabel = $Env.LABEL
5151
)
52-
public Optional<AstraEnvironment> $env;
52+
public Optional<String> $env;
5353

54-
public CredsSpec(AstraToken $token, Optional<AstraEnvironment> $env) {
54+
@Option(
55+
names = { "--local-endpoint" },
56+
description = "The endpoint URL for local Astra environments (required when --env local)",
57+
paramLabel = "URL",
58+
hidden = true
59+
)
60+
public Optional<String> $localEndpoint;
61+
62+
public CredsSpec(AstraToken $token, Optional<String> $env, Optional<String> $localEndpoint) {
5563
this.$token = $token;
5664
this.$env = $env;
65+
this.$localEndpoint = $localEndpoint;
5766
}
5867

59-
@SuppressWarnings("unused") // it may be marked as unused but used by picocli
68+
@SuppressWarnings("unused") // it may be marked as unused but is used by picocli
6069
public CredsSpec() {
61-
this(null, Optional.empty());
70+
this(null, Optional.empty(), Optional.empty());
6271
}
6372
}
6473

@@ -121,9 +130,13 @@ public ConnectionOptions merge(ConnectionOptions other) {
121130

122131
val env = (other.$creds != null && other.$creds.$env != null && other.$creds.$env.isPresent())
123132
? other.$creds.$env
124-
: (this.$creds != null ? this.$creds.$env : Optional.<AstraEnvironment>empty());
133+
: (this.$creds != null ? this.$creds.$env : Optional.<String>empty());
134+
135+
val localEndpoint = (other.$creds != null && other.$creds.$localEndpoint != null && other.$creds.$localEndpoint.isPresent())
136+
? other.$creds.$localEndpoint
137+
: (this.$creds != null ? this.$creds.$localEndpoint : Optional.<String>empty());
125138

126-
return new CredsSpec(token, env);
139+
return new CredsSpec(token, env, localEndpoint);
127140
}
128141
return null;
129142
}

src/main/java/com/dtsx/astra/cli/commands/SetupCmd.java

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import com.dtsx.astra.cli.core.datatypes.NEList;
99
import com.dtsx.astra.cli.core.exceptions.AstraCliException;
1010
import com.dtsx.astra.cli.core.exceptions.internal.cli.ExecutionCancelledException;
11+
import com.dtsx.astra.cli.core.exceptions.internal.cli.OptionValidationException;
1112
import com.dtsx.astra.cli.core.exceptions.internal.misc.InvalidTokenException;
1213
import com.dtsx.astra.cli.core.help.Example;
1314
import com.dtsx.astra.cli.core.models.AstraToken;
@@ -37,6 +38,7 @@
3738
import static com.dtsx.astra.cli.core.output.ExitCode.UNSUPPORTED_EXECUTION;
3839
import static com.dtsx.astra.cli.utils.StringUtils.NL;
3940
import static com.dtsx.astra.cli.utils.StringUtils.trimIndent;
41+
import static com.dtsx.astra.sdk.utils.AstraEnvironment.PROD;
4042

4143
@Command(
4244
name = "setup",
@@ -60,11 +62,11 @@ public class SetupCmd extends AbstractCmd<SetupResult> {
6062

6163
@Option(
6264
names = { $Env.LONG, $Env.SHORT },
63-
description = "Astra environment the token belongs to: prod (default), dev, or test. Leave unset unless you were issued a non-prod token.",
65+
description = "Astra environment the token belongs to: prod (default), dev, test, or local. Leave unset unless you were issued a non-prod token.",
6466
completionCandidates = AstraEnvCompletion.class,
6567
paramLabel = $Env.LABEL
6668
)
67-
public Optional<AstraEnvironment> $env;
69+
public Optional<String> $env;
6870

6971
@Option(
7072
names = { "--name" },
@@ -129,10 +131,10 @@ private OutputHuman handleSameProfileAlreadyExists(SameProfileAlreadyExists resu
129131

130132
private <T> T throwInvalidToken(Optional<AstraEnvironment> hint) {
131133
if (hint.isPresent()) {
132-
val currentEnvName = $env.orElse(AstraEnvironment.PROD).name().toLowerCase();
134+
val currentEnvName = $env.orElse(PROD.name()).toLowerCase();
133135
val validEnvName = hint.get().name().toLowerCase();
134136

135-
val fixAction = hint.get() == AstraEnvironment.PROD
137+
val fixAction = hint.get() == PROD
136138
? "drop @'!--env!@ (prod is the default) or pass @'!--env prod!@"
137139
: "pass @'!--env " + validEnvName + "!@";
138140

@@ -176,13 +178,32 @@ private OutputHuman showDocsLink() {
176178

177179
@Override
178180
protected Operation<SetupResult> mkOperation() {
181+
// TODO not sure if this should be here
182+
if ($env.isPresent() && $env.get().equalsIgnoreCase("LOCAL")) {
183+
throw new AstraCliException(UNSUPPORTED_EXECUTION, """
184+
@|bold,red Error: LOCAL environments cannot be configured via interactive setup.|@
185+
186+
Please use @'!${cli.name} config create!@ with @'!--env local!@ and @'!--local-endpoint!@ to create a LOCAL profile.
187+
""", List.of(
188+
new Hint("Create a LOCAL profile", "${cli.name} config create <name> --token <token> --env local --local-endpoint <url>")
189+
));
190+
}
191+
192+
val resolvedEnv = $env.map(envStr -> {
193+
try {
194+
return AstraEnvironment.valueOf(envStr);
195+
} catch (IllegalArgumentException e) {
196+
throw new OptionValidationException("env", "Invalid environment: '" + envStr + "'. Expected one of: " + String.join(", ", AstraEnvironment.allValuesLower()));
197+
}
198+
});
199+
179200
return new SetupOperation(
180201
ctx,
181202
ctx.gateways()::mkOrgGateway,
182203
ctx.gateways().mkOrgGatewayStateless(),
183204
new SetupRequest(
184205
$token,
185-
$env,
206+
resolvedEnv,
186207
$name,
187208
this::assertShouldSetup,
188209
this::promptForNextActionIfExistingUser,
@@ -213,7 +234,7 @@ private String mkArgsAddendum() {
213234
$name.map(n -> "Profile '" + n + "'").orElse("The profile"),
214235
Stream.concat(
215236
$token.map(t -> " with token " + t).stream(),
216-
$env.map(e -> " in env '" + e.name().toLowerCase() + "'").stream()
237+
$env.map(e -> " in env '" + e.toLowerCase() + "'").stream()
217238
).collect(Collectors.joining(""))
218239
);
219240
}
@@ -316,12 +337,12 @@ private AstraEnvironment promptForEnv(AstraEnvironment defaultEnv) {
316337
.defaultOption(defaultEnv)
317338
.mapper(e -> e.name().toLowerCase())
318339
.fallbackFlag("--env")
319-
.fix(originalArgs(), "--env <prod|test|dev>")
340+
.fix(originalArgs(), "--env <" + String.join("|", AstraEnvironment.allValuesLower()) + ">")
320341
.dontClearAfterSelection();
321342
}
322343

323344
private ProfileName promptForName(String defaultName, AstraEnvironment env) {
324-
val envAddendum = (env != AstraEnvironment.PROD)
345+
val envAddendum = (env != PROD)
325346
? " " + ctx.highlight(env.name().toLowerCase())
326347
: "";
327348

src/main/java/com/dtsx/astra/cli/commands/config/ConfigCreateCmd.java

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import static com.dtsx.astra.cli.utils.CollectionUtils.sequencedMapOf;
2929
import static com.dtsx.astra.cli.utils.StringUtils.NL;
3030
import static com.dtsx.astra.cli.utils.StringUtils.trimIndent;
31+
import static com.dtsx.astra.sdk.utils.AstraEnvironment.PROD;
3132

3233
@Command(
3334
name = "create",
@@ -67,19 +68,25 @@ public class ConfigCreateCmd extends AbstractConfigCmd<ConfigCreateResult> {
6768
@Option(
6869
names = { $Token.LONG, $Token.SHORT },
6970
description = "Astra token (@|code AstraCS:...|@) or @|code @<file>|@ to read from file",
70-
paramLabel = $Token.LABEL,
71-
required = true
71+
paramLabel = $Token.LABEL
7272
)
73-
public AstraToken $token;
73+
public Optional<AstraToken> $token;
7474

7575
@Option(
7676
names = { $Env.LONG, $Env.SHORT },
77-
description = "Astra environment the token belongs to: prod (default), dev, or test. Leave unset unless you were issued a non-prod token.",
77+
description = "Astra environment the token belongs to: prod (default), dev, test, or local. Leave unset unless you were issued a non-prod token.",
7878
completionCandidates = AstraEnvCompletion.class,
7979
defaultValue = $Env.DEFAULT,
8080
paramLabel = $Env.LABEL
8181
)
82-
public AstraEnvironment $env;
82+
public Optional<String> $env;
83+
84+
@Option(
85+
names = { "--local-endpoint" },
86+
description = "The endpoint URL for local Astra environments (required when --env local)",
87+
paramLabel = "URL"
88+
)
89+
public Optional<String> $localEndpoint;
8390

8491
@Option(
8592
names = { "-d", "--default" },
@@ -94,6 +101,15 @@ public class ConfigCreateCmd extends AbstractConfigCmd<ConfigCreateResult> {
94101
)
95102
private Optional<Boolean> $overwrite;
96103

104+
@Option(
105+
names = { "--validate" },
106+
description = "Validate the token by making a request to the Astra",
107+
defaultValue = "true",
108+
fallbackValue = "true",
109+
negatable = true
110+
)
111+
public boolean $validate;
112+
97113
@Override
98114
public final OutputAll execute(Supplier<ConfigCreateResult> resultSupplier) {
99115
val result = resultSupplier.get();
@@ -103,6 +119,8 @@ public final OutputAll execute(Supplier<ConfigCreateResult> resultSupplier) {
103119
case ProfileIllegallyExists(var profileName) -> throwProfileAlreadyExists(profileName);
104120
case ViolatedFailIfExists() -> throwAttemptedToSetDefault();
105121
case InvalidToken(var hint) -> throwInvalidToken(hint);
122+
case MissingToken _ -> throwMissingToken();
123+
case NameRequiredIfNotValidated _ -> throwNameRequiredIfNotValidated();
106124
};
107125
}
108126

@@ -168,10 +186,10 @@ private <T> T throwAttemptedToSetDefault() {
168186

169187
private <T> T throwInvalidToken(Optional<AstraEnvironment> hint) {
170188
if (hint.isPresent()) {
171-
val currentEnvName = $env.name().toLowerCase();
189+
val currentEnvName = $env.orElse(PROD.name()).toLowerCase();
172190
val validEnvName = hint.get().name().toLowerCase();
173191

174-
val fixAction = hint.get() == AstraEnvironment.PROD
192+
val fixAction = hint.get() == PROD
175193
? "drop @'!--env!@ (prod is the default) or pass @'!--env prod!@"
176194
: "pass @'!--env " + validEnvName + "!@";
177195

@@ -191,14 +209,30 @@ private <T> T throwInvalidToken(Optional<AstraEnvironment> hint) {
191209
""");
192210
}
193211

212+
private <T> T throwMissingToken() {
213+
throw new AstraCliException(VALIDATION_ISSUE, """
214+
@|bold,red An explicit astra token must be provided if '--env' is not 'local'|@
215+
""");
216+
}
217+
218+
private <T> T throwNameRequiredIfNotValidated() {
219+
throw new AstraCliException(VALIDATION_ISSUE, """
220+
@|bold,red An explicit profile name must be provided if --validate=false.|@
221+
""");
222+
}
223+
194224
@Override
195225
public Operation<ConfigCreateResult> mkOperation() {
196-
return new ConfigCreateOperation(ctx, config(true), ctx.gateways().mkOrgGateway($token, $env), ctx.gateways().mkOrgGatewayStateless(), new CreateConfigRequest(
226+
val env = AstraEnvironment.resolve($env, $localEndpoint);
227+
228+
return new ConfigCreateOperation(ctx, config(true), t -> ctx.gateways().mkOrgGateway(t, env), ctx.gateways().mkOrgGatewayStateless(), new CreateConfigRequest(
197229
$profileName,
198230
$token,
199-
$env,
231+
env,
200232
$overwrite,
201233
$setDefault,
234+
$validate,
235+
$localEndpoint,
202236
this::assertCanOverwriteProfile
203237
));
204238
}

src/main/java/com/dtsx/astra/cli/core/completions/impls/AstraEnvCompletion.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import com.dtsx.astra.cli.core.completions.StaticCompletion;
44
import com.dtsx.astra.sdk.utils.AstraEnvironment;
55

6-
import java.util.Arrays;
6+
import java.util.List;
77

88
public class AstraEnvCompletion extends StaticCompletion {
99
public AstraEnvCompletion() {
10-
super(Arrays.stream(AstraEnvironment.values()).map(Enum::name).map(String::toLowerCase).toList());
10+
super(List.of(AstraEnvironment.allValuesLower()));
1111
}
1212
}

0 commit comments

Comments
 (0)