Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions acceptance/cmd/auth/describe/u2m-json-output/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions acceptance/cmd/auth/describe/u2m-json-output/output.txt
Original file line number Diff line number Diff line change
@@ -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"
}
16 changes: 16 additions & 0 deletions acceptance/cmd/auth/describe/u2m-json-output/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
sethome "./home"

unset DATABRICKS_HOST
unset DATABRICKS_TOKEN
unset DATABRICKS_CONFIG_PROFILE
unset DATABRICKS_AUTH_STORAGE

cat > "./home/.databrickscfg" <<ENDCFG
[u2m-profile]
host = https://u2m-profile.databricks.test
auth_type = databricks-cli
ENDCFG

# Filter to just the new token_storage object so the assertion is focused
# and doesn't churn when other fields evolve.
trace $CLI auth describe --profile u2m-profile --output json | jq '.token_storage'
3 changes: 3 additions & 0 deletions acceptance/cmd/auth/describe/u2m-json-output/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Ignore = [
"home"
]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-config/output.txt
Original file line number Diff line number Diff line change
@@ -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 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)
17 changes: 17 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-config/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
sethome "./home"

unset DATABRICKS_HOST
unset DATABRICKS_TOKEN
unset DATABRICKS_CONFIG_PROFILE
unset DATABRICKS_AUTH_STORAGE

cat > "./home/.databrickscfg" <<ENDCFG
[__settings__]
auth_storage = plaintext

[u2m-profile]
host = https://u2m-profile.databricks.test
auth_type = databricks-cli
ENDCFG

trace $CLI auth describe --profile u2m-profile
3 changes: 3 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-config/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Ignore = [
"home"
]

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-default/output.txt
Original file line number Diff line number Diff line change
@@ -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)
14 changes: 14 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-default/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
sethome "./home"

unset DATABRICKS_HOST
unset DATABRICKS_TOKEN
unset DATABRICKS_CONFIG_PROFILE
unset DATABRICKS_AUTH_STORAGE

cat > "./home/.databrickscfg" <<ENDCFG
[u2m-profile]
host = https://u2m-profile.databricks.test
auth_type = databricks-cli
ENDCFG

trace $CLI auth describe --profile u2m-profile
3 changes: 3 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-default/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Ignore = [
"home"
]
3 changes: 3 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-env/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-env/output.txt
Original file line number Diff line number Diff line change
@@ -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 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)
15 changes: 15 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-env/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
sethome "./home"

unset DATABRICKS_HOST
unset DATABRICKS_TOKEN
unset DATABRICKS_CONFIG_PROFILE

export DATABRICKS_AUTH_STORAGE=plaintext

cat > "./home/.databrickscfg" <<ENDCFG
[u2m-profile]
host = https://u2m-profile.databricks.test
auth_type = databricks-cli
ENDCFG

trace $CLI auth describe --profile u2m-profile
3 changes: 3 additions & 0 deletions acceptance/cmd/auth/describe/u2m-plaintext-env/test.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Ignore = [
"home"
]
136 changes: 115 additions & 21 deletions cmd/auth/describe.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import (
"context"
"encoding/json"
"fmt"
"path/filepath"

"github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/libs/auth/storage"
"github.com/databricks/cli/libs/cmdctx"
"github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/env"
"github.com/databricks/cli/libs/flags"
"github.com/databricks/cli/libs/log"
"github.com/databricks/databricks-sdk-go/config"
"github.com/spf13/cobra"
)
Expand All @@ -21,10 +25,16 @@ var authTemplate = `{{"Host:" | bold}} {{.Status.Details.Host}}
{{"User:" | bold}} {{.Status.Username}}
{{- end}}
{{"Authenticated with:" | bold}} {{.Status.Details.AuthType}}
{{- if .Status.TokenStorage}}
{{"Token storage:" | bold}} {{.Status.TokenStorage.Mode}}, {{.Status.TokenStorage.Location}} {{ printf "(from %s)" .Status.TokenStorage.Source | italic}}
{{- end}}
-----
` + configurationTemplate

var errorTemplate = `Unable to authenticate: {{.Status.Error}}
{{- if .Status.TokenStorage}}
{{"Token storage:" | bold}} {{.Status.TokenStorage.Mode}}, {{.Status.TokenStorage.Location}} {{ printf "(from %s)" .Status.TokenStorage.Source | italic}}
{{- end}}
-----
` + configurationTemplate

Expand Down Expand Up @@ -80,10 +90,12 @@ func getAuthStatus(cmd *cobra.Command, args []string, showSensitive bool, fn try
cfg, isAccount, err := fn(cmd, args)
ctx := cmd.Context()
if err != nil {
details := getAuthDetails(cmd, cfg, showSensitive)
return &authStatus{ //nolint:nilerr // error is returned in the authStatus struct
Status: "error",
Error: err,
Details: getAuthDetails(cmd, cfg, showSensitive),
Status: "error",
Error: err,
Details: details,
TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType),
}, nil
}

Expand All @@ -93,18 +105,22 @@ func getAuthStatus(cmd *cobra.Command, args []string, showSensitive bool, fn try
// Doing a simple API call to check if the auth is valid
_, err := a.Workspaces.List(ctx)
if err != nil {
details := getAuthDetails(cmd, cfg, showSensitive)
return &authStatus{ //nolint:nilerr // error is returned in the authStatus struct
Status: "error",
Error: err,
Details: getAuthDetails(cmd, cfg, showSensitive),
Status: "error",
Error: err,
Details: details,
TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType),
}, nil
}

details := getAuthDetails(cmd, a.Config, showSensitive)
status := authStatus{
Status: "success",
Details: getAuthDetails(cmd, a.Config, showSensitive),
AccountID: a.Config.AccountID,
Username: a.Config.Username,
Status: "success",
Details: details,
AccountID: a.Config.AccountID,
Username: a.Config.Username,
TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType),
}

return &status, nil
Expand All @@ -113,17 +129,21 @@ func getAuthStatus(cmd *cobra.Command, args []string, showSensitive bool, fn try
w := cmdctx.WorkspaceClient(ctx)
me, err := w.CurrentUser.Me(ctx)
if err != nil {
details := getAuthDetails(cmd, cfg, showSensitive)
return &authStatus{ //nolint:nilerr // error is returned in the authStatus struct
Status: "error",
Error: err,
Details: getAuthDetails(cmd, cfg, showSensitive),
Status: "error",
Error: err,
Details: details,
TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType),
}, nil
}

details := getAuthDetails(cmd, w.Config, showSensitive)
status := authStatus{
Status: "success",
Details: getAuthDetails(cmd, w.Config, showSensitive),
Username: me.UserName,
Status: "success",
Details: details,
Username: me.UserName,
TokenStorage: resolveTokenStorageInfo(ctx, details.AuthType),
}

return &status, nil
Expand Down Expand Up @@ -153,11 +173,85 @@ func render(ctx context.Context, cmd *cobra.Command, status *authStatus, templat
}

type authStatus struct {
Status string `json:"status"`
Error error `json:"error,omitempty"`
Username string `json:"username,omitempty"`
AccountID string `json:"account_id,omitempty"`
Details config.AuthDetails `json:"details"`
Status string `json:"status"`
Error error `json:"error,omitempty"`
Username string `json:"username,omitempty"`
AccountID string `json:"account_id,omitempty"`
Details config.AuthDetails `json:"details"`
TokenStorage *tokenStorageInfo `json:"token_storage,omitempty"`
}

// tokenStorageInfo describes where the U2M (databricks-cli) token cache lives
// and which precedence level produced that choice. Populated only when the
// active auth type is "databricks-cli"; other auth types do not use the
// CLI's U2M token cache and the field is omitted.
type tokenStorageInfo struct {
Mode string `json:"mode"`
Location string `json:"location"`
Source string `json:"source"`
}

const (
plaintextLocation = "~/.databricks/token-cache.json"
secureLocation = "OS keyring (service: databricks-cli)"
)

// resolveTokenStorageInfo returns storage info for the U2M token cache, or
// nil if the active auth type does not use the cache. Resolver errors are
// logged at debug level and treated as "no info available" rather than
// failing describe; the user-visible auth status is more important than
// secondary metadata about where a token would have been stored.
func resolveTokenStorageInfo(ctx context.Context, authType string) *tokenStorageInfo {
if authType != authTypeDatabricksCLI {
return nil
}
mode, source, err := storage.ResolveStorageModeWithSource(ctx, "")
if err != nil {
log.Debugf(ctx, "auth describe: resolve storage mode: %v", err)
return nil
}
info := &tokenStorageInfo{
Mode: string(mode),
Source: storageSourceLabel(ctx, source),
}
switch mode {
case storage.StorageModePlaintext:
info.Location = plaintextLocation
case storage.StorageModeSecure:
info.Location = secureLocation
default:
return nil
}
return info
}

// storageSourceLabel returns a user-facing label for source. For
// StorageSourceConfig, it appends the resolved config file path
// (DATABRICKS_CONFIG_FILE or <home>/.databrickscfg) so the output matches
// the SDK's config.Source style ("from <path> 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
// <home>/.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 {
Expand Down
Loading
Loading