From 799d2ccbb743d9f59b683618195e18d35703afe4 Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 7 May 2026 18:52:38 +0200 Subject: [PATCH 1/2] auth describe: show U2M token storage location and source Adds a "Token storage:" line to `databricks auth describe` output for profiles authenticated with `auth_type = databricks-cli` (U2M). Shows which backend the CLI uses for the token cache (plaintext file vs OS keyring) and where that choice came from (override flag, env var, config setting, or built-in default). Modeled on `gh auth status`, which surfaces "(keyring)" or the YAML hosts file path so users can tell at a glance where their tokens live. Mirrors the existing config-attribute "(from )" annotation style. Other auth types (PAT, M2M, OIDC, etc.) do not use the U2M cache and the line is omitted for them. Resolver: `ResolveStorageModeWithSource` now returns a typed `StorageSource` instead of an opaque bool, so callers can render the source directly without duplicating the precedence-to-label mapping. Co-authored-by: Isaac --- NEXT_CHANGELOG.md | 2 + .../u2m-plaintext-default/out.test.toml | 3 + .../describe/u2m-plaintext-default/output.txt | 12 ++ .../describe/u2m-plaintext-default/script | 14 +++ .../describe/u2m-plaintext-default/test.toml | 3 + .../describe/u2m-plaintext-env/out.test.toml | 3 + .../describe/u2m-plaintext-env/output.txt | 12 ++ .../auth/describe/u2m-plaintext-env/script | 15 +++ .../auth/describe/u2m-plaintext-env/test.toml | 3 + cmd/auth/describe.go | 105 ++++++++++++---- cmd/auth/describe_test.go | 114 ++++++++++++++++++ libs/auth/storage/cache.go | 4 +- libs/auth/storage/mode.go | 58 +++++++-- libs/auth/storage/mode_test.go | 62 ++++++---- 14 files changed, 352 insertions(+), 58 deletions(-) create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-default/out.test.toml create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-default/output.txt create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-default/script create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-default/test.toml create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-env/out.test.toml create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-env/output.txt create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-env/script create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-env/test.toml diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 409a875ad12..2e0b512efee 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -4,6 +4,8 @@ ### CLI +* `databricks auth describe` now reports where U2M (`databricks-cli`) tokens are stored: `plaintext` (`~/.databricks/token-cache.json`) or `secure` (OS keyring), and the source of the choice (env var, config setting, or default). + ### Bundles * Fixed `--force-pull` on `bundle summary` and `bundle open` so the flag bypasses the local state cache and reads state from the workspace. diff --git a/acceptance/cmd/auth/describe/u2m-plaintext-default/out.test.toml b/acceptance/cmd/auth/describe/u2m-plaintext-default/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-plaintext-default/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/describe/u2m-plaintext-default/output.txt b/acceptance/cmd/auth/describe/u2m-plaintext-default/output.txt new file mode 100644 index 00000000000..981244ff8d9 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-plaintext-default/output.txt @@ -0,0 +1,12 @@ + +>>> [CLI] auth describe --profile u2m-profile +Warn: [hostmetadata] failed to fetch host metadata for https://u2m-profile.databricks.test, will skip for 1m0s +Unable to authenticate: error getting token: cache: token not found +Token storage: plaintext, ~/.databricks/token-cache.json (from default) +----- +Current configuration: + ✓ host: https://u2m-profile.databricks.test (from ./home/.databrickscfg config file) + ✓ profile: u2m-profile (from --profile flag) + ✓ databricks_cli_path: [CLI] + ✓ auth_type: databricks-cli (from ./home/.databrickscfg config file) + ✓ rate_limit: [NUMID] (from DATABRICKS_RATE_LIMIT environment variable) diff --git a/acceptance/cmd/auth/describe/u2m-plaintext-default/script b/acceptance/cmd/auth/describe/u2m-plaintext-default/script new file mode 100644 index 00000000000..d0b1ce40020 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-plaintext-default/script @@ -0,0 +1,14 @@ +sethome "./home" + +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE +unset DATABRICKS_AUTH_STORAGE + +cat > "./home/.databrickscfg" <>> [CLI] auth describe --profile u2m-profile +Warn: [hostmetadata] failed to fetch host metadata for https://u2m-profile.databricks.test, will skip for 1m0s +Unable to authenticate: error getting token: cache: token not found +Token storage: plaintext, ~/.databricks/token-cache.json (from DATABRICKS_AUTH_STORAGE environment variable) +----- +Current configuration: + ✓ host: https://u2m-profile.databricks.test (from ./home/.databrickscfg config file) + ✓ profile: u2m-profile (from --profile flag) + ✓ databricks_cli_path: [CLI] + ✓ auth_type: databricks-cli (from ./home/.databrickscfg config file) + ✓ rate_limit: [NUMID] (from DATABRICKS_RATE_LIMIT environment variable) diff --git a/acceptance/cmd/auth/describe/u2m-plaintext-env/script b/acceptance/cmd/auth/describe/u2m-plaintext-env/script new file mode 100644 index 00000000000..21bfdf231f1 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-plaintext-env/script @@ -0,0 +1,15 @@ +sethome "./home" + +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE + +export DATABRICKS_AUTH_STORAGE=plaintext + +cat > "./home/.databrickscfg" < Date: Thu, 7 May 2026 19:08:34 +0200 Subject: [PATCH 2/2] auth describe: address review feedback on storage source labels - Drop the "--auth-storage flag" wording from StorageSourceOverride.String(): no CLI command currently exposes a storage-mode flag, so promising one in the label was misleading. Switch to a generic "command-line override". When a flag is added later, that PR can replace the label at the call site. - For StorageSourceConfig, surface the resolved config file path (DATABRICKS_CONFIG_FILE or /.databrickscfg) instead of hardcoding ".databrickscfg". Matches the SDK's existing config.Source style and no longer claims the wrong file when DATABRICKS_CONFIG_FILE is in use. Resolution stays in the describe command since that's the only caller that needs the user-facing path; the storage package keeps its resolver focused. - New acceptance tests cover the config-source path (u2m-plaintext-config) and the JSON output shape (u2m-json-output). Co-authored-by: Isaac --- .../describe/u2m-json-output/out.test.toml | 3 ++ .../auth/describe/u2m-json-output/output.txt | 8 +++++ .../cmd/auth/describe/u2m-json-output/script | 16 +++++++++ .../auth/describe/u2m-json-output/test.toml | 3 ++ .../u2m-plaintext-config/out.test.toml | 3 ++ .../describe/u2m-plaintext-config/output.txt | 12 +++++++ .../auth/describe/u2m-plaintext-config/script | 17 ++++++++++ .../describe/u2m-plaintext-config/test.toml | 3 ++ cmd/auth/describe.go | 33 ++++++++++++++++++- cmd/auth/describe_test.go | 27 +++++++++++++++ libs/auth/storage/mode.go | 13 ++++++-- libs/auth/storage/mode_test.go | 4 +-- 12 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 acceptance/cmd/auth/describe/u2m-json-output/out.test.toml create mode 100644 acceptance/cmd/auth/describe/u2m-json-output/output.txt create mode 100644 acceptance/cmd/auth/describe/u2m-json-output/script create mode 100644 acceptance/cmd/auth/describe/u2m-json-output/test.toml create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-config/out.test.toml create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-config/output.txt create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-config/script create mode 100644 acceptance/cmd/auth/describe/u2m-plaintext-config/test.toml diff --git a/acceptance/cmd/auth/describe/u2m-json-output/out.test.toml b/acceptance/cmd/auth/describe/u2m-json-output/out.test.toml new file mode 100644 index 00000000000..f784a183258 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-json-output/out.test.toml @@ -0,0 +1,3 @@ +Local = true +Cloud = false +EnvMatrix.DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/describe/u2m-json-output/output.txt b/acceptance/cmd/auth/describe/u2m-json-output/output.txt new file mode 100644 index 00000000000..7e2ac070cbc --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-json-output/output.txt @@ -0,0 +1,8 @@ + +>>> [CLI] auth describe --profile u2m-profile --output json +Warn: [hostmetadata] failed to fetch host metadata for https://u2m-profile.databricks.test, will skip for 1m0s +{ + "mode": "plaintext", + "location": "~/.databricks/token-cache.json", + "source": "default" +} diff --git a/acceptance/cmd/auth/describe/u2m-json-output/script b/acceptance/cmd/auth/describe/u2m-json-output/script new file mode 100644 index 00000000000..668d2374496 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-json-output/script @@ -0,0 +1,16 @@ +sethome "./home" + +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE +unset DATABRICKS_AUTH_STORAGE + +cat > "./home/.databrickscfg" <>> [CLI] auth describe --profile u2m-profile +Warn: [hostmetadata] failed to fetch host metadata for https://u2m-profile.databricks.test, will skip for 1m0s +Unable to authenticate: error getting token: cache: token not found +Token storage: plaintext, ~/.databricks/token-cache.json (from auth_storage in [__settings__] section of home/.databrickscfg) +----- +Current configuration: + ✓ host: https://u2m-profile.databricks.test (from ./home/.databrickscfg config file) + ✓ profile: u2m-profile (from --profile flag) + ✓ databricks_cli_path: [CLI] + ✓ auth_type: databricks-cli (from ./home/.databrickscfg config file) + ✓ rate_limit: [NUMID] (from DATABRICKS_RATE_LIMIT environment variable) diff --git a/acceptance/cmd/auth/describe/u2m-plaintext-config/script b/acceptance/cmd/auth/describe/u2m-plaintext-config/script new file mode 100644 index 00000000000..1cf6d3267d5 --- /dev/null +++ b/acceptance/cmd/auth/describe/u2m-plaintext-config/script @@ -0,0 +1,17 @@ +sethome "./home" + +unset DATABRICKS_HOST +unset DATABRICKS_TOKEN +unset DATABRICKS_CONFIG_PROFILE +unset DATABRICKS_AUTH_STORAGE + +cat > "./home/.databrickscfg" </.databrickscfg) so the output matches +// the SDK's config.Source style ("from config file") rather than +// hardcoding ".databrickscfg" when a custom path is in use. +func storageSourceLabel(ctx context.Context, source storage.StorageSource) string { + if source != storage.StorageSourceConfig { + return source.String() + } + return "auth_storage in [__settings__] section of " + resolvedConfigPath(ctx) +} + +// resolvedConfigPath returns the path the storage-mode resolver loaded from +// for [__settings__].auth_storage: DATABRICKS_CONFIG_FILE if set, otherwise +// /.databrickscfg. Falls back to "~/.databrickscfg" only when the home +// directory cannot be determined (rare; describe should not crash on this +// secondary metadata path). +func resolvedConfigPath(ctx context.Context) string { + if path := env.Get(ctx, "DATABRICKS_CONFIG_FILE"); path != "" { + return path + } + home, err := env.UserHomeDir(ctx) + if err != nil { + log.Debugf(ctx, "auth describe: resolve home dir: %v", err) + return "~/.databrickscfg" + } + return filepath.ToSlash(filepath.Join(home, ".databrickscfg")) +} + func getAuthDetails(cmd *cobra.Command, cfg *config.Config, showSensitive bool) config.AuthDetails { var opts []config.AuthDetailsOptions if showSensitive { diff --git a/cmd/auth/describe_test.go b/cmd/auth/describe_test.go index e202404b212..528decf1022 100644 --- a/cmd/auth/describe_test.go +++ b/cmd/auth/describe_test.go @@ -2,6 +2,7 @@ package auth import ( "errors" + "path/filepath" "testing" "github.com/databricks/cli/libs/auth/storage" @@ -270,6 +271,32 @@ func TestResolveTokenStorageInfo(t *testing.T) { } } +func TestStorageSourceLabel_ConfigUsesResolvedPath(t *testing.T) { + ctx := t.Context() + t.Setenv("DATABRICKS_CONFIG_FILE", "/custom/path/.databrickscfg") + got := storageSourceLabel(ctx, storage.StorageSourceConfig) + assert.Equal(t, "auth_storage in [__settings__] section of /custom/path/.databrickscfg", got) +} + +func TestStorageSourceLabel_ConfigDefaultsToHome(t *testing.T) { + ctx := t.Context() + home := t.TempDir() + t.Setenv("HOME", home) + t.Setenv("USERPROFILE", home) + t.Setenv("DATABRICKS_CONFIG_FILE", "") + got := storageSourceLabel(ctx, storage.StorageSourceConfig) + expected := "auth_storage in [__settings__] section of " + filepath.ToSlash(filepath.Join(home, ".databrickscfg")) + assert.Equal(t, expected, got) +} + +func TestStorageSourceLabel_NonConfigDelegatesToSource(t *testing.T) { + ctx := t.Context() + t.Setenv("DATABRICKS_CONFIG_FILE", "/should/not/appear") + assert.Equal(t, "default", storageSourceLabel(ctx, storage.StorageSourceDefault)) + assert.Equal(t, "DATABRICKS_AUTH_STORAGE environment variable", storageSourceLabel(ctx, storage.StorageSourceEnvVar)) + assert.Equal(t, "command-line override", storageSourceLabel(ctx, storage.StorageSourceOverride)) +} + func TestGetWorkspaceAuthStatus_U2M_PopulatesTokenStorage(t *testing.T) { ctx := t.Context() m := mocks.NewMockWorkspaceClient(t) diff --git a/libs/auth/storage/mode.go b/libs/auth/storage/mode.go index 5c1acca9604..6dd3c6e5dff 100644 --- a/libs/auth/storage/mode.go +++ b/libs/auth/storage/mode.go @@ -61,15 +61,22 @@ func (s StorageSource) Explicit() bool { // String returns a human-readable label for the source, matching the style // used by the SDK's config.Source.String() (e.g. "DATABRICKS_HOST environment -// variable", "--profile flag"). +// variable"). +// +// The label for StorageSourceConfig intentionally does not name a specific +// config file: callers that know the resolved path (e.g. auth describe) +// should append it themselves to match the SDK's "from config file" +// convention. The label for StorageSourceOverride is generic because no +// CLI command currently exposes a storage-mode flag; if one is added in +// the future, that command can replace the label at the call site. func (s StorageSource) String() string { switch s { case StorageSourceOverride: - return "--auth-storage flag" + return "command-line override" case StorageSourceEnvVar: return EnvVar + " environment variable" case StorageSourceConfig: - return "auth_storage in [__settings__] section of .databrickscfg" + return "auth_storage in [__settings__] section of config file" default: return "default" } diff --git a/libs/auth/storage/mode_test.go b/libs/auth/storage/mode_test.go index 32ff190faf2..4c6cf0e1614 100644 --- a/libs/auth/storage/mode_test.go +++ b/libs/auth/storage/mode_test.go @@ -205,7 +205,7 @@ func TestStorageSource_Explicit(t *testing.T) { func TestStorageSource_String(t *testing.T) { assert.Equal(t, "default", StorageSourceDefault.String()) - assert.Equal(t, "--auth-storage flag", StorageSourceOverride.String()) + assert.Equal(t, "command-line override", StorageSourceOverride.String()) assert.Equal(t, "DATABRICKS_AUTH_STORAGE environment variable", StorageSourceEnvVar.String()) - assert.Equal(t, "auth_storage in [__settings__] section of .databrickscfg", StorageSourceConfig.String()) + assert.Equal(t, "auth_storage in [__settings__] section of config file", StorageSourceConfig.String()) }