Skip to content

Commit 6f4fead

Browse files
Generalize CLI token source into progressive command list
Replace the three explicit command fields (forceCmd, profileCmd, hostCmd) with a []cliCommand list and an activeCommandIndex. Feature flags are defined statically in cliFeatureFlags and stripped progressively to build commands for older CLI versions. Token() now iterates from activeCommandIndex, falling back on unknown flag errors and caching the working command index for subsequent calls. Adding future flags (e.g. --scopes) requires only a one-line addition to the cliFeatureFlags slice. Signed-off-by: Mihai Mitrea <mihai.mitrea@databricks.com>
1 parent 69a7c95 commit 6f4fead

File tree

3 files changed

+163
-117
lines changed

3 files changed

+163
-117
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
### Internal Changes
2424

25+
* Generalize CLI token source into a progressive command list for forward-compatible flag support.
2526
* Normalize internal token sources on `auth.TokenSource` for proper context propagation ([#1577](https://github.com/databricks/databricks-sdk-go/pull/1577)).
2627
* Fix `TestAzureGithubOIDCCredentials` hang caused by missing `HTTPTransport` stub: `EnsureResolved` now calls `resolveHostMetadata`, which makes a real network request when no transport is set ([#1550](https://github.com/databricks/databricks-sdk-go/pull/1550)).
2728
* Bump golang.org/x/crypto from 0.21.0 to 0.45.0 in /examples/slog ([#1566](https://github.com/databricks/databricks-sdk-go/pull/1566)).

config/cli_token_source.go

Lines changed: 66 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"path/filepath"
1111
"runtime"
1212
"strings"
13+
"sync/atomic"
1314
"time"
1415

1516
"github.com/databricks/databricks-sdk-go/logger"
@@ -31,50 +32,78 @@ type cliTokenResponse struct {
3132
Expiry string `json:"expiry"`
3233
}
3334

34-
// CliTokenSource fetches OAuth tokens by shelling out to the Databricks CLI.
35-
// Commands are tried in order: forceCmd -> profileCmd -> hostCmd, progressively
36-
// falling back to simpler invocations for older CLI versions.
37-
type CliTokenSource struct {
38-
// forceCmd uses --profile with --force-refresh to bypass the CLI's token cache.
39-
// Nil when cfg.Profile is empty (--force-refresh requires --profile support).
40-
forceCmd []string
35+
// cliFeatureFlag defines a CLI feature flag and the warning to log when
36+
// falling back because the CLI does not support it. Ordered newest-first:
37+
// commands are built by progressively stripping these flags for older CLIs.
38+
type cliFeatureFlag struct {
39+
flag string
40+
warningMessage string
41+
}
42+
43+
var cliFeatureFlags = []cliFeatureFlag{
44+
{"--force-refresh", "Databricks CLI does not support --force-refresh flag. The CLI's token cache may provide stale tokens. Please upgrade your CLI to the latest version."},
45+
}
4146

42-
// profileCmd uses --profile for token lookup. Nil when cfg.Profile is empty.
43-
profileCmd []string
47+
const profileFlagWarning = "Databricks CLI does not support --profile flag. Falling back to --host. Please upgrade your CLI to the latest version."
4448

45-
// hostCmd uses --host as a fallback for CLIs that predate --profile support.
46-
// Nil when cfg.Host is empty.
47-
hostCmd []string
49+
// cliCommand is a single CLI invocation with an associated warning message
50+
// that is logged when this command fails and we fall back to the next one.
51+
type cliCommand struct {
52+
args []string
53+
warningMessage string
54+
}
55+
56+
// CliTokenSource fetches OAuth tokens by shelling out to the Databricks CLI.
57+
// It holds a list of commands ordered from most feature-rich to simplest,
58+
// falling back progressively for older CLI versions that lack newer flags.
59+
type CliTokenSource struct {
60+
commands []cliCommand
61+
activeCommandIndex atomic.Int32
4862
}
4963

5064
func NewCliTokenSource(cfg *Config) (*CliTokenSource, error) {
5165
cliPath, err := findDatabricksCli(cfg.DatabricksCliPath)
5266
if err != nil {
5367
return nil, err
5468
}
55-
forceCmd, profileCmd, hostCmd := buildCliCommands(cliPath, cfg)
56-
return &CliTokenSource{forceCmd: forceCmd, profileCmd: profileCmd, hostCmd: hostCmd}, nil
69+
return &CliTokenSource{commands: buildCliCommands(cliPath, cfg)}, nil
5770
}
5871

59-
// buildCliCommands constructs the CLI commands for fetching an auth token.
60-
// When cfg.Profile is set, three commands are built: a --force-refresh variant,
61-
// a plain --profile variant, and (when host is available) a --host fallback.
62-
// When cfg.Profile is empty, only --host is returned — the CLI must support
63-
// --profile before --force-refresh can be used (monotonic feature assumption).
64-
func buildCliCommands(cliPath string, cfg *Config) ([]string, []string, []string) {
65-
var forceCmd, profileCmd, hostCmd []string
72+
// buildCliCommands constructs the list of CLI commands for fetching an auth
73+
// token, ordered from most feature-rich to simplest. When cfg.Profile is set,
74+
// commands include feature flags from [cliFeatureFlags] (stripped progressively)
75+
// plus a --host fallback when host is available. When cfg.Profile is empty,
76+
// only --host is returned — the CLI must support --profile before any feature
77+
// flags can be used (monotonic feature assumption).
78+
func buildCliCommands(cliPath string, cfg *Config) []cliCommand {
79+
var commands []cliCommand
6680
if cfg.Profile != "" {
67-
profileCmd = []string{cliPath, "auth", "token", "--profile", cfg.Profile}
68-
forceCmd = append(profileCmd, "--force-refresh")
81+
baseArgs := []string{cliPath, "auth", "token", "--profile", cfg.Profile}
82+
for i := 0; i <= len(cliFeatureFlags); i++ {
83+
args := append([]string{}, baseArgs...)
84+
for _, f := range cliFeatureFlags[i:] {
85+
args = append(args, f.flag)
86+
}
87+
warning := profileFlagWarning
88+
if i < len(cliFeatureFlags) {
89+
warning = cliFeatureFlags[i].warningMessage
90+
}
91+
commands = append(commands, cliCommand{args: args, warningMessage: warning})
92+
}
6993
}
70-
if cfg.Host != "" {
71-
hostCmd = buildHostCommand(cliPath, cfg)
94+
hostArgs := buildHostCommand(cliPath, cfg)
95+
if hostArgs != nil {
96+
commands = append(commands, cliCommand{args: hostArgs})
7297
}
73-
return forceCmd, profileCmd, hostCmd
98+
return commands
7499
}
75100

76101
// buildHostCommand constructs the legacy --host based CLI command.
102+
// Returns nil when cfg.Host is empty.
77103
func buildHostCommand(cliPath string, cfg *Config) []string {
104+
if cfg.Host == "" {
105+
return nil
106+
}
78107
cmd := []string{cliPath, "auth", "token", "--host", cfg.Host}
79108
switch cfg.HostType() {
80109
case AccountHost:
@@ -84,36 +113,24 @@ func buildHostCommand(cliPath string, cfg *Config) []string {
84113
}
85114

86115
// Token fetches an OAuth token by shelling out to the Databricks CLI.
87-
// Commands are tried in order: forceCmd -> profileCmd -> hostCmd, skipping nil
88-
// entries. Each command falls through to the next on "unknown flag" errors,
89-
// logging a warning about the unsupported feature.
116+
// Commands are tried from [activeCommandIndex] forward, falling back to simpler
117+
// commands when the CLI reports an unknown flag. Once a command succeeds,
118+
// [activeCommandIndex] is updated so subsequent calls skip commands known to fail.
90119
func (c *CliTokenSource) Token(ctx context.Context) (*oauth2.Token, error) {
91-
if c.forceCmd != nil {
92-
tok, err := c.execCliCommand(ctx, c.forceCmd)
93-
if err == nil {
94-
return tok, nil
95-
}
96-
if !isUnknownFlagError(err, "--force-refresh") && !isUnknownFlagError(err, "--profile") {
97-
return nil, err
98-
}
99-
logger.Warnf(ctx, "Databricks CLI does not support --force-refresh flag. The CLI's token cache may provide stale tokens. Please upgrade your CLI to the latest version.")
100-
}
101-
102-
if c.profileCmd != nil {
103-
tok, err := c.execCliCommand(ctx, c.profileCmd)
120+
for i := int(c.activeCommandIndex.Load()); i < len(c.commands); i++ {
121+
cmd := c.commands[i]
122+
tok, err := c.execCliCommand(ctx, cmd.args)
104123
if err == nil {
124+
c.activeCommandIndex.Store(int32(i))
105125
return tok, nil
106126
}
107-
if !isUnknownFlagError(err, "--profile") {
127+
lastCommand := i == len(c.commands)-1
128+
if lastCommand || !isUnknownFlagError(err, "") {
108129
return nil, err
109130
}
110-
logger.Warnf(ctx, "Databricks CLI does not support --profile flag. Falling back to --host. Please upgrade your CLI to the latest version.")
111-
}
112-
113-
if c.hostCmd == nil {
114-
return nil, fmt.Errorf("cannot get access token: no CLI commands available")
131+
logger.Warnf(ctx, cmd.warningMessage)
115132
}
116-
return c.execCliCommand(ctx, c.hostCmd)
133+
return nil, fmt.Errorf("cannot get access token: no CLI commands configured")
117134
}
118135

119136
func (c *CliTokenSource) execCliCommand(ctx context.Context, args []string) (*oauth2.Token, error) {

0 commit comments

Comments
 (0)