From 807421a27d2929dff337e9a47b54963964ebfe37 Mon Sep 17 00:00:00 2001 From: Mihai Mitrea Date: Tue, 17 Mar 2026 09:40:45 +0000 Subject: [PATCH 1/3] Add --force-refresh flag to `auth token` command Wire a new --force-refresh CLI flag that delegates to the SDK's ForceRefreshToken() method, bypassing the cached token validity check. The default path through Token() is unchanged. Note: this will not compile until the SDK ships ForceRefreshToken(). --- NEXT_CHANGELOG.md | 1 + .../out.test.toml | 5 ++ .../output.txt | 4 ++ .../script | 4 ++ .../test.toml | 8 +++ .../force-refresh-no-cache/out.test.toml | 5 ++ .../token/force-refresh-no-cache/output.txt | 3 + .../auth/token/force-refresh-no-cache/script | 3 + .../token/force-refresh-success/out.test.toml | 5 ++ .../token/force-refresh-success/output.txt | 3 + .../auth/token/force-refresh-success/script | 4 ++ acceptance/cmd/auth/token/script.prepare | 31 ++++++++++ cmd/auth/token.go | 22 +++++-- cmd/auth/token_test.go | 61 +++++++++++++++++++ 14 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/out.test.toml create mode 100644 acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/output.txt create mode 100644 acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/script create mode 100644 acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/test.toml create mode 100644 acceptance/cmd/auth/token/force-refresh-no-cache/out.test.toml create mode 100644 acceptance/cmd/auth/token/force-refresh-no-cache/output.txt create mode 100644 acceptance/cmd/auth/token/force-refresh-no-cache/script create mode 100644 acceptance/cmd/auth/token/force-refresh-success/out.test.toml create mode 100644 acceptance/cmd/auth/token/force-refresh-success/output.txt create mode 100644 acceptance/cmd/auth/token/force-refresh-success/script create mode 100644 acceptance/cmd/auth/token/script.prepare diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index bdf1483d09..93f8240dd6 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,6 +7,7 @@ - Add `bundle.engine` config setting to select the deployment engine (`terraform` or `direct`). The `DATABRICKS_BUNDLE_ENGINE` environment variable takes precedence over this setting. When the configured engine doesn't match existing deployment state, a warning is issued and the existing engine is used ([#4749](https://github.com/databricks/cli/pull/4749)). ### CLI +* Add `--force-refresh` flag to `databricks auth token` to force a token refresh even when the cached token is still valid ([#4564](https://github.com/databricks/cli/issues/4564)). ### Bundles * engine/direct: Fix permanent drift on experiment name field ([#4627](https://github.com/databricks/cli/pull/4627)) diff --git a/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/out.test.toml b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/output.txt b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/output.txt new file mode 100644 index 0000000000..f1a1b8cadb --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/output.txt @@ -0,0 +1,4 @@ +Error: A new access token could not be retrieved because the refresh token is invalid. To reauthenticate, run the following command: + $ databricks auth login --profile test-profile + +Exit code: 1 diff --git a/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/script b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/script new file mode 100644 index 0000000000..b5f5c297bc --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/script @@ -0,0 +1,4 @@ +setup_test_profile +setup_test_token_cache + +errcode $CLI auth token --profile test-profile --force-refresh diff --git a/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/test.toml b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/test.toml new file mode 100644 index 0000000000..f93632544f --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/test.toml @@ -0,0 +1,8 @@ +Ignore = [ + "home" +] + +[[Server]] +Pattern = "POST /oidc/v1/token" +Response.StatusCode = 401 +Response.Body = '{"error": "invalid_request", "error_description": "Refresh token is invalid"}' diff --git a/acceptance/cmd/auth/token/force-refresh-no-cache/out.test.toml b/acceptance/cmd/auth/token/force-refresh-no-cache/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-no-cache/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/token/force-refresh-no-cache/output.txt b/acceptance/cmd/auth/token/force-refresh-no-cache/output.txt new file mode 100644 index 0000000000..a5fb246734 --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-no-cache/output.txt @@ -0,0 +1,3 @@ +Error: cache: databricks OAuth is not configured for this host. Try logging in again with `databricks auth login --profile test-profile` before retrying. If this fails, please report this issue to the Databricks CLI maintainers at https://github.com/databricks/cli/issues/new + +Exit code: 1 diff --git a/acceptance/cmd/auth/token/force-refresh-no-cache/script b/acceptance/cmd/auth/token/force-refresh-no-cache/script new file mode 100644 index 0000000000..e561647987 --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-no-cache/script @@ -0,0 +1,3 @@ +setup_test_profile + +errcode $CLI auth token --profile test-profile --force-refresh diff --git a/acceptance/cmd/auth/token/force-refresh-success/out.test.toml b/acceptance/cmd/auth/token/force-refresh-success/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-success/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/token/force-refresh-success/output.txt b/acceptance/cmd/auth/token/force-refresh-success/output.txt new file mode 100644 index 0000000000..0b965964ac --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-success/output.txt @@ -0,0 +1,3 @@ + +>>> [CLI] auth token --profile test-profile --force-refresh +"oauth-token" diff --git a/acceptance/cmd/auth/token/force-refresh-success/script b/acceptance/cmd/auth/token/force-refresh-success/script new file mode 100644 index 0000000000..e3e1167dd0 --- /dev/null +++ b/acceptance/cmd/auth/token/force-refresh-success/script @@ -0,0 +1,4 @@ +setup_test_profile +setup_test_token_cache + +trace $CLI auth token --profile test-profile --force-refresh | jq .access_token diff --git a/acceptance/cmd/auth/token/script.prepare b/acceptance/cmd/auth/token/script.prepare new file mode 100644 index 0000000000..afdf008034 --- /dev/null +++ b/acceptance/cmd/auth/token/script.prepare @@ -0,0 +1,31 @@ +setup_test_profile() { + export DATABRICKS_HOST_ORIG="$DATABRICKS_HOST" + + sethome "./home" + unset DATABRICKS_HOST + unset DATABRICKS_TOKEN + unset DATABRICKS_CONFIG_PROFILE + + cat > "./home/.databrickscfg" < "./home/.databricks/token-cache.json" < Date: Wed, 18 Mar 2026 17:22:14 +0100 Subject: [PATCH 2/3] Apply suggestion from @mihaimitrea-db --- NEXT_CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 93f8240dd6..b682604da9 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -7,7 +7,7 @@ - Add `bundle.engine` config setting to select the deployment engine (`terraform` or `direct`). The `DATABRICKS_BUNDLE_ENGINE` environment variable takes precedence over this setting. When the configured engine doesn't match existing deployment state, a warning is issued and the existing engine is used ([#4749](https://github.com/databricks/cli/pull/4749)). ### CLI -* Add `--force-refresh` flag to `databricks auth token` to force a token refresh even when the cached token is still valid ([#4564](https://github.com/databricks/cli/issues/4564)). +* Add `--force-refresh` flag to `databricks auth token` to force a token refresh even when the cached token is still valid ([#4767](https://github.com/databricks/cli/pull/4767)). ### Bundles * engine/direct: Fix permanent drift on experiment name field ([#4627](https://github.com/databricks/cli/pull/4627)) From 7335554e965984941bcd527511ee51972bc8c1e5 Mon Sep 17 00:00:00 2001 From: Mihai Mitrea Date: Thu, 19 Mar 2026 16:47:17 +0000 Subject: [PATCH 3/3] Address review feedback on --force-refresh PR - Add failOnCallTransport to assert no network call when reusing cached token - Remove inherited Ignore from force-refresh-invalid-refresh-token test.toml - Add explanatory comment in force-refresh-success script - Tighten --force-refresh description in Long help text - Rename resp -> got in validateToken callbacks for consistency - Fix inMemoryTokenCache to copy tokens on Lookup/Store, matching file-backed cache semantics and preventing cross-test corruption --- .../test.toml | 4 ---- .../auth/token/force-refresh-success/script | 2 ++ cmd/auth/in_memory_test.go | 12 ++++++++++-- cmd/auth/token.go | 6 +++--- cmd/auth/token_test.go | 18 +++++++++++++----- go.mod | 2 ++ go.sum | 2 -- 7 files changed, 30 insertions(+), 16 deletions(-) diff --git a/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/test.toml b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/test.toml index f93632544f..95067d76b7 100644 --- a/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/test.toml +++ b/acceptance/cmd/auth/token/force-refresh-invalid-refresh-token/test.toml @@ -1,7 +1,3 @@ -Ignore = [ - "home" -] - [[Server]] Pattern = "POST /oidc/v1/token" Response.StatusCode = 401 diff --git a/acceptance/cmd/auth/token/force-refresh-success/script b/acceptance/cmd/auth/token/force-refresh-success/script index e3e1167dd0..6f4dda4b57 100644 --- a/acceptance/cmd/auth/token/force-refresh-success/script +++ b/acceptance/cmd/auth/token/force-refresh-success/script @@ -1,4 +1,6 @@ setup_test_profile setup_test_token_cache +# The cached token is "cached-access-token". The mock OIDC server returns +# "oauth-token", so this confirms --force-refresh fetched a fresh token. trace $CLI auth token --profile test-profile --force-refresh | jq .access_token diff --git a/cmd/auth/in_memory_test.go b/cmd/auth/in_memory_test.go index 8c9f50015b..aea197a3c5 100644 --- a/cmd/auth/in_memory_test.go +++ b/cmd/auth/in_memory_test.go @@ -10,20 +10,28 @@ type inMemoryTokenCache struct { } // Lookup implements TokenCache. +// Returns a copy to match real (file-backed) cache behavior, where each +// Lookup deserializes a fresh struct. Without the copy, callers that +// mutate the returned token (e.g. clearing RefreshToken) would corrupt +// entries shared across test cases. func (i *inMemoryTokenCache) Lookup(key string) (*oauth2.Token, error) { token, ok := i.Tokens[key] if !ok { return nil, cache.ErrNotFound } - return token, nil + cp := *token + return &cp, nil } // Store implements TokenCache. +// Stores a copy to prevent callers from mutating cached entries after Store +// returns (mirrors file-backed cache semantics). func (i *inMemoryTokenCache) Store(key string, t *oauth2.Token) error { if t == nil { delete(i.Tokens, key) } else { - i.Tokens[key] = t + cp := *t + i.Tokens[key] = &cp } return nil } diff --git a/cmd/auth/token.go b/cmd/auth/token.go index 982994773f..c0a1c962ca 100644 --- a/cmd/auth/token.go +++ b/cmd/auth/token.go @@ -56,9 +56,9 @@ func newTokenCommand(authArguments *auth.AuthArguments) *cobra.Command { Short: "Get authentication token", Long: `Get authentication token from the local cache in ~/.databricks/token-cache.json. Refresh the access token if it is expired or close to expiry. Use --force-refresh -to always refresh regardless of the token's remaining lifetime. Note: This command -only works with U2M authentication (using the 'databricks auth login' command). -M2M authentication using a client ID and secret is not supported.`, +to bypass expiry checks. Note: This command only works with U2M authentication +(using the 'databricks auth login' command). M2M authentication using a client ID +and secret is not supported.`, } var tokenTimeout time.Duration diff --git a/cmd/auth/token_test.go b/cmd/auth/token_test.go index 442cbd45aa..f36909c9b2 100644 --- a/cmd/auth/token_test.go +++ b/cmd/auth/token_test.go @@ -2,6 +2,7 @@ package auth import ( "context" + "errors" "net/http" "testing" "time" @@ -16,6 +17,12 @@ import ( "golang.org/x/oauth2" ) +type failOnCallTransport struct{} + +func (failOnCallTransport) RoundTrip(*http.Request) (*http.Response, error) { + return nil, errors.New("unexpected HTTP call") +} + var refreshFailureTokenResponse = fixtures.HTTPFixture{ MatchAny: true, Status: 401, @@ -192,9 +199,9 @@ func TestToken_loadToken(t *testing.T) { }, }, } - validateToken := func(resp *oauth2.Token) { - assert.Equal(t, "new-access-token", resp.AccessToken) - assert.Equal(t, "Bearer", resp.TokenType) + validateToken := func(got *oauth2.Token) { + assert.Equal(t, "new-access-token", got.AccessToken) + assert.Equal(t, "Bearer", got.TokenType) } cases := []struct { @@ -719,10 +726,11 @@ func TestToken_loadToken(t *testing.T) { persistentAuthOpts: []u2m.PersistentAuthOption{ u2m.WithTokenCache(tokenCache), u2m.WithOAuthEndpointSupplier(&MockApiClient{}), + u2m.WithHttpClient(&http.Client{Transport: failOnCallTransport{}}), }, }, - validateToken: func(resp *oauth2.Token) { - assert.Equal(t, "cached-access-token", resp.AccessToken) + validateToken: func(got *oauth2.Token) { + assert.Equal(t, "cached-access-token", got.AccessToken) }, }, { diff --git a/go.mod b/go.mod index 97cab5b6f4..8194f9706d 100644 --- a/go.mod +++ b/go.mod @@ -107,3 +107,5 @@ require ( google.golang.org/grpc v1.78.0 // indirect google.golang.org/protobuf v1.36.11 // indirect ) + +replace github.com/databricks/databricks-sdk-go => ../databricks-sdk-go diff --git a/go.sum b/go.sum index f72c2e937d..b087aa249e 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,6 @@ github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= -github.com/databricks/databricks-sdk-go v0.120.0 h1:XLEoLeVUB/MFygyklLiB2HtQTeaULnfr1RyGtYcl2gQ= -github.com/databricks/databricks-sdk-go v0.120.0/go.mod h1:hWoHnHbNLjPKiTm5K/7bcIv3J3Pkgo5x9pPzh8K3RVE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=