Skip to content

Commit 628f509

Browse files
Refactor CliTokenSource to use an ordered attempt chain
Replace ad-hoc forceCmd/profileCmd/fallbackCmd fields with a generic ordered attempt model: each CliCommandAttempt describes its command, the error substrings that trigger fallback to the next attempt, and an optional log message. getToken() is now a simple loop over the attempt chain instead of nested if-else branches. This makes adding future CLI flag fallbacks straightforward without introducing more fields. DatabricksCliCredentialsProvider builds the attempt chain explicitly: - with profile: force+profile -> profile -> host - without profile: force+host -> host Old constructors delegate to the new model. Azure CLI callers use the 5-arg constructor which creates a single-attempt chain, so their behavior is unchanged. Signed-off-by: Mihai Mitrea <mihai.mitrea@databricks.com>
1 parent 048a903 commit 628f509

File tree

3 files changed

+249
-76
lines changed

3 files changed

+249
-76
lines changed

databricks-sdk-java/src/main/java/com/databricks/sdk/core/CliTokenSource.java

Lines changed: 126 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@
1515
import java.time.ZoneId;
1616
import java.time.format.DateTimeFormatter;
1717
import java.time.format.DateTimeParseException;
18+
import java.util.ArrayList;
1819
import java.util.Arrays;
20+
import java.util.Collections;
1921
import java.util.List;
22+
import java.util.stream.Collectors;
2023
import org.apache.commons.io.IOUtils;
2124
import org.slf4j.Logger;
2225
import org.slf4j.LoggerFactory;
@@ -25,21 +28,21 @@
2528
public class CliTokenSource implements TokenSource {
2629
private static final Logger LOG = LoggerFactory.getLogger(CliTokenSource.class);
2730

28-
private static final String UNKNOWN_PROFILE_FLAG = "unknown flag: --profile";
29-
private static final String UNKNOWN_FORCE_REFRESH_FLAG = "unknown flag: --force-refresh";
30-
31-
// forceCmd is tried before profileCmd when non-null. If the CLI rejects
32-
// --force-refresh or --profile, execution falls through to profileCmd.
33-
private List<String> forceCmd;
31+
/**
32+
* Describes a CLI command with the error substrings that allow falling through to the next
33+
* command in the chain and an optional log message emitted on fallback.
34+
*/
35+
static class CliCommand {
36+
private final List<String> cmd;
37+
private final List<String> fallbackTriggers;
38+
private final String fallbackMessage;
3439

35-
private List<String> profileCmd;
36-
private String tokenTypeField;
37-
private String accessTokenField;
38-
private String expiryField;
39-
private Environment env;
40-
// fallbackCmd is tried when profileCmd fails with "unknown flag: --profile",
41-
// indicating the CLI is too old to support --profile.
42-
private List<String> fallbackCmd;
40+
CliCommand(List<String> cmd, List<String> fallbackTriggers, String fallbackMessage) {
41+
this.cmd = cmd;
42+
this.fallbackTriggers = fallbackTriggers != null ? fallbackTriggers : Collections.emptyList();
43+
this.fallbackMessage = fallbackMessage;
44+
}
45+
}
4346

4447
/**
4548
* Internal exception that carries the clean stderr message but exposes full output for checks.
@@ -57,6 +60,13 @@ String getFullOutput() {
5760
}
5861
}
5962

63+
private final List<CliCommand> attempts;
64+
private final String tokenTypeField;
65+
private final String accessTokenField;
66+
private final String expiryField;
67+
private final Environment env;
68+
69+
/** Constructs a single-attempt source. Used by Azure CLI and simple callers. */
6070
public CliTokenSource(
6171
List<String> cmd,
6272
String tokenTypeField,
@@ -66,6 +76,7 @@ public CliTokenSource(
6676
this(cmd, tokenTypeField, accessTokenField, expiryField, env, null, null);
6777
}
6878

79+
/** Constructs a two-attempt source with --profile to --host fallback. */
6980
public CliTokenSource(
7081
List<String> cmd,
7182
String tokenTypeField,
@@ -76,6 +87,7 @@ public CliTokenSource(
7687
this(cmd, tokenTypeField, accessTokenField, expiryField, env, fallbackCmd, null);
7788
}
7889

90+
/** Constructs a source with optional force-refresh, profile, and host fallback chain. */
7991
public CliTokenSource(
8092
List<String> cmd,
8193
String tokenTypeField,
@@ -84,16 +96,89 @@ public CliTokenSource(
8496
Environment env,
8597
List<String> fallbackCmd,
8698
List<String> forceCmd) {
87-
super();
88-
this.profileCmd = OSUtils.get(env).getCliExecutableCommand(cmd);
99+
this(
100+
buildAttempts(forceCmd, cmd, fallbackCmd).stream()
101+
.map(
102+
a ->
103+
new CliCommand(
104+
OSUtils.get(env).getCliExecutableCommand(a.cmd),
105+
a.fallbackTriggers,
106+
a.fallbackMessage))
107+
.collect(Collectors.toList()),
108+
tokenTypeField,
109+
accessTokenField,
110+
expiryField,
111+
env,
112+
true);
113+
}
114+
115+
/** Creates a CliTokenSource from a pre-built attempt chain. */
116+
static CliTokenSource fromAttempts(
117+
List<CliCommand> attempts,
118+
String tokenTypeField,
119+
String accessTokenField,
120+
String expiryField,
121+
Environment env) {
122+
return new CliTokenSource(
123+
attempts.stream()
124+
.map(
125+
a ->
126+
new CliCommand(
127+
OSUtils.get(env).getCliExecutableCommand(a.cmd),
128+
a.fallbackTriggers,
129+
a.fallbackMessage))
130+
.collect(Collectors.toList()),
131+
tokenTypeField,
132+
accessTokenField,
133+
expiryField,
134+
env,
135+
true);
136+
}
137+
138+
private CliTokenSource(
139+
List<CliCommand> attempts,
140+
String tokenTypeField,
141+
String accessTokenField,
142+
String expiryField,
143+
Environment env,
144+
boolean alreadyResolved) {
145+
this.attempts = attempts;
89146
this.tokenTypeField = tokenTypeField;
90147
this.accessTokenField = accessTokenField;
91148
this.expiryField = expiryField;
92149
this.env = env;
93-
this.fallbackCmd =
94-
fallbackCmd != null ? OSUtils.get(env).getCliExecutableCommand(fallbackCmd) : null;
95-
this.forceCmd =
96-
forceCmd != null ? OSUtils.get(env).getCliExecutableCommand(forceCmd) : null;
150+
}
151+
152+
private static final String UNKNOWN_PROFILE_FLAG = "unknown flag: --profile";
153+
private static final String UNKNOWN_FORCE_REFRESH_FLAG = "unknown flag: --force-refresh";
154+
155+
private static List<CliCommand> buildAttempts(
156+
List<String> forceCmd, List<String> profileCmd, List<String> fallbackCmd) {
157+
List<CliCommand> attempts = new ArrayList<>();
158+
159+
if (forceCmd != null) {
160+
attempts.add(
161+
new CliCommand(
162+
forceCmd,
163+
Arrays.asList(UNKNOWN_FORCE_REFRESH_FLAG, UNKNOWN_PROFILE_FLAG),
164+
"Databricks CLI does not support --force-refresh flag. "
165+
+ "Falling back to regular token fetch. "
166+
+ "Please upgrade your CLI to the latest version."));
167+
}
168+
169+
if (fallbackCmd != null) {
170+
attempts.add(
171+
new CliCommand(
172+
profileCmd,
173+
Collections.singletonList(UNKNOWN_PROFILE_FLAG),
174+
"Databricks CLI does not support --profile flag. Falling back to --host. "
175+
+ "Please upgrade your CLI to the latest version."));
176+
attempts.add(new CliCommand(fallbackCmd, Collections.emptyList(), null));
177+
} else {
178+
attempts.add(new CliCommand(profileCmd, Collections.emptyList(), null));
179+
}
180+
181+
return attempts;
97182
}
98183

99184
/**
@@ -171,54 +256,39 @@ private Token execCliCommand(List<String> cmdToRun) throws IOException {
171256
}
172257
}
173258

174-
private String getErrorText(IOException e) {
259+
private static String getErrorText(IOException e) {
175260
return e instanceof CliCommandException
176261
? ((CliCommandException) e).getFullOutput()
177262
: e.getMessage();
178263
}
179264

180-
private boolean isUnknownFlagError(String errorText, String flag) {
181-
return errorText != null && errorText.contains(flag);
182-
}
183-
184-
private Token execProfileCmdWithFallback() {
185-
try {
186-
return execCliCommand(this.profileCmd);
187-
} catch (IOException e) {
188-
String textToCheck = getErrorText(e);
189-
if (fallbackCmd != null && isUnknownFlagError(textToCheck, UNKNOWN_PROFILE_FLAG)) {
190-
LOG.warn(
191-
"Databricks CLI does not support --profile flag. Falling back to --host. "
192-
+ "Please upgrade your CLI to the latest version.");
193-
try {
194-
return execCliCommand(this.fallbackCmd);
195-
} catch (IOException fallbackException) {
196-
throw new DatabricksException(fallbackException.getMessage(), fallbackException);
197-
}
198-
}
199-
throw new DatabricksException(e.getMessage(), e);
265+
private static boolean shouldFallback(CliCommand attempt, String errorText) {
266+
if (errorText == null) {
267+
return false;
200268
}
269+
return attempt.fallbackTriggers.stream().anyMatch(errorText::contains);
201270
}
202271

203272
@Override
204273
public Token getToken() {
205-
if (forceCmd == null) {
206-
return execProfileCmdWithFallback();
207-
}
274+
IOException lastException = null;
208275

209-
try {
210-
return execCliCommand(this.forceCmd);
211-
} catch (IOException e) {
212-
String textToCheck = getErrorText(e);
213-
if (isUnknownFlagError(textToCheck, UNKNOWN_FORCE_REFRESH_FLAG)
214-
|| isUnknownFlagError(textToCheck, UNKNOWN_PROFILE_FLAG)) {
215-
LOG.warn(
216-
"Databricks CLI does not support --force-refresh flag. "
217-
+ "Falling back to regular token fetch. "
218-
+ "Please upgrade your CLI to the latest version.");
219-
return execProfileCmdWithFallback();
276+
for (int i = 0; i < attempts.size(); i++) {
277+
CliCommand attempt = attempts.get(i);
278+
try {
279+
return execCliCommand(attempt.cmd);
280+
} catch (IOException e) {
281+
if (i + 1 < attempts.size() && shouldFallback(attempt, getErrorText(e))) {
282+
if (attempt.fallbackMessage != null) {
283+
LOG.warn(attempt.fallbackMessage);
284+
}
285+
lastException = e;
286+
continue;
287+
}
288+
throw new DatabricksException(e.getMessage(), e);
220289
}
221-
throw new DatabricksException(e.getMessage(), e);
222290
}
291+
292+
throw new DatabricksException(lastException.getMessage(), lastException);
223293
}
224294
}

databricks-sdk-java/src/main/java/com/databricks/sdk/core/DatabricksCliCredentialsProvider.java

Lines changed: 53 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,59 @@ List<String> buildHostArgs(String cliPath, DatabricksConfig config) {
6969
return cmd;
7070
}
7171

72+
private static final String UNKNOWN_PROFILE_FLAG = "unknown flag: --profile";
73+
private static final String UNKNOWN_FORCE_REFRESH_FLAG = "unknown flag: --force-refresh";
74+
7275
List<String> buildProfileArgs(String cliPath, DatabricksConfig config) {
7376
return new ArrayList<>(
7477
Arrays.asList(cliPath, "auth", "token", "--profile", config.getProfile()));
7578
}
7679

7780
private static List<String> withForceRefresh(List<String> cmd) {
78-
List<String> forceCmd = new ArrayList<>(cmd);
79-
forceCmd.add("--force-refresh");
80-
return forceCmd;
81+
List<String> result = new ArrayList<>(cmd);
82+
result.add("--force-refresh");
83+
return result;
84+
}
85+
86+
List<CliTokenSource.CliCommand> buildAttempts(
87+
String cliPath, DatabricksConfig config) {
88+
List<CliTokenSource.CliCommand> attempts = new ArrayList<>();
89+
90+
List<String> profileCmd;
91+
boolean hasHostFallback = false;
92+
93+
if (config.getProfile() != null) {
94+
profileCmd = buildProfileArgs(cliPath, config);
95+
hasHostFallback = config.getHost() != null;
96+
} else {
97+
profileCmd = buildHostArgs(cliPath, config);
98+
}
99+
100+
attempts.add(
101+
new CliTokenSource.CliCommand(
102+
withForceRefresh(profileCmd),
103+
Arrays.asList(UNKNOWN_FORCE_REFRESH_FLAG, UNKNOWN_PROFILE_FLAG),
104+
"Databricks CLI does not support --force-refresh flag. "
105+
+ "Falling back to regular token fetch. "
106+
+ "Please upgrade your CLI to the latest version."));
107+
108+
if (hasHostFallback) {
109+
attempts.add(
110+
new CliTokenSource.CliCommand(
111+
profileCmd,
112+
Collections.singletonList(UNKNOWN_PROFILE_FLAG),
113+
"Databricks CLI does not support --profile flag. Falling back to --host. "
114+
+ "Please upgrade your CLI to the latest version."));
115+
attempts.add(
116+
new CliTokenSource.CliCommand(
117+
buildHostArgs(cliPath, config), Collections.emptyList(), null));
118+
} else {
119+
attempts.add(
120+
new CliTokenSource.CliCommand(
121+
profileCmd, Collections.emptyList(), null));
122+
}
123+
124+
return attempts;
81125
}
82126

83127
private CliTokenSource getDatabricksCliTokenSource(DatabricksConfig config) {
@@ -90,23 +134,12 @@ private CliTokenSource getDatabricksCliTokenSource(DatabricksConfig config) {
90134
return null;
91135
}
92136

93-
List<String> profileCmd;
94-
List<String> fallbackCmd = null;
95-
List<String> forceCmd;
96-
97-
if (config.getProfile() != null) {
98-
profileCmd = buildProfileArgs(cliPath, config);
99-
forceCmd = withForceRefresh(profileCmd);
100-
if (config.getHost() != null) {
101-
fallbackCmd = buildHostArgs(cliPath, config);
102-
}
103-
} else {
104-
profileCmd = buildHostArgs(cliPath, config);
105-
forceCmd = withForceRefresh(profileCmd);
106-
}
107-
108-
return new CliTokenSource(
109-
profileCmd, "token_type", "access_token", "expiry", config.getEnv(), fallbackCmd, forceCmd);
137+
return CliTokenSource.fromAttempts(
138+
buildAttempts(cliPath, config),
139+
"token_type",
140+
"access_token",
141+
"expiry",
142+
config.getEnv());
110143
}
111144

112145
@Override

0 commit comments

Comments
 (0)