1515import java .time .ZoneId ;
1616import java .time .format .DateTimeFormatter ;
1717import java .time .format .DateTimeParseException ;
18+ import java .util .ArrayList ;
1819import java .util .Arrays ;
20+ import java .util .Collections ;
1921import java .util .List ;
22+ import java .util .stream .Collectors ;
2023import org .apache .commons .io .IOUtils ;
2124import org .slf4j .Logger ;
2225import org .slf4j .LoggerFactory ;
2528public 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+ final List <String > cmd ;
37+ final List <String > fallbackTriggers ;
38+ 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,15 +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 = 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 ;
96182 }
97183
98184 /**
@@ -170,54 +256,39 @@ private Token execCliCommand(List<String> cmdToRun) throws IOException {
170256 }
171257 }
172258
173- private String getErrorText (IOException e ) {
259+ private static String getErrorText (IOException e ) {
174260 return e instanceof CliCommandException
175261 ? ((CliCommandException ) e ).getFullOutput ()
176262 : e .getMessage ();
177263 }
178264
179- private boolean isUnknownFlagError (String errorText , String flag ) {
180- return errorText != null && errorText .contains (flag );
181- }
182-
183- private Token execProfileCmdWithFallback () {
184- try {
185- return execCliCommand (this .profileCmd );
186- } catch (IOException e ) {
187- String textToCheck = getErrorText (e );
188- if (fallbackCmd != null && isUnknownFlagError (textToCheck , UNKNOWN_PROFILE_FLAG )) {
189- LOG .warn (
190- "Databricks CLI does not support --profile flag. Falling back to --host. "
191- + "Please upgrade your CLI to the latest version." );
192- try {
193- return execCliCommand (this .fallbackCmd );
194- } catch (IOException fallbackException ) {
195- throw new DatabricksException (fallbackException .getMessage (), fallbackException );
196- }
197- }
198- throw new DatabricksException (e .getMessage (), e );
265+ private static boolean shouldFallback (CliCommand attempt , String errorText ) {
266+ if (errorText == null ) {
267+ return false ;
199268 }
269+ return attempt .fallbackTriggers .stream ().anyMatch (errorText ::contains );
200270 }
201271
202272 @ Override
203273 public Token getToken () {
204- if (forceCmd == null ) {
205- return execProfileCmdWithFallback ();
206- }
274+ IOException lastException = null ;
207275
208- try {
209- return execCliCommand (this .forceCmd );
210- } catch (IOException e ) {
211- String textToCheck = getErrorText (e );
212- if (isUnknownFlagError (textToCheck , UNKNOWN_FORCE_REFRESH_FLAG )
213- || isUnknownFlagError (textToCheck , UNKNOWN_PROFILE_FLAG )) {
214- LOG .warn (
215- "Databricks CLI does not support --force-refresh flag. "
216- + "Falling back to regular token fetch. "
217- + "Please upgrade your CLI to the latest version." );
218- 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 );
219289 }
220- throw new DatabricksException (e .getMessage (), e );
221290 }
291+
292+ throw new DatabricksException (lastException .getMessage (), lastException );
222293 }
223294}
0 commit comments