Skip to content

Add auth logout command with --profile and --force flags#4613

Open
mihaimitrea-db wants to merge 11 commits intomainfrom
mihaimitrea-db/stack/auth_logout
Open

Add auth logout command with --profile and --force flags#4613
mihaimitrea-db wants to merge 11 commits intomainfrom
mihaimitrea-db/stack/auth_logout

Conversation

@mihaimitrea-db
Copy link
Collaborator

@mihaimitrea-db mihaimitrea-db commented Feb 27, 2026

🥞 Stacked PR

Use this link to review incremental changes.


Implement databricks auth logout which clears cached OAuth tokens for a profile and, optionally, removes the profile from ~/.databrickscfg.

By default the command only clears tokens, requiring re-authentication on next use. Pass --delete to also remove the profile entry from the config file. This iteration supports explicit profile selection via --profile and a --force flag to skip the confirmation prompt. Interactive profile selection will be added in a follow-up.

Token cache cleanup is best-effort: the profile-keyed token is always removed, and the host-keyed token is removed only when no other profile references the same host.

Changes

  • Add databricks auth logout command (cmd/auth/logout.go) registered under the auth command group.
  • --profile selects the profile to log out of; --force skips the confirmation prompt.
  • --delete opts in to removing the profile section from ~/.databrickscfg. Without it, only cached OAuth tokens are cleared.
  • clearTokenCache removes the profile-keyed token and, if no other profile shares the same host, the host-keyed token. Host URLs are normalized (trailing slash stripped) to match token cache keys.
  • DeleteProfile in libs/databrickscfg/ops.go removes a named section from ~/.databrickscfg, creating a .bak backup first.

Why

There is currently no way to log out of a Databricks profile from the CLI. Users must manually edit ~/.databrickscfg and locate/delete token cache entries. This command provides a safe, single-step logout that handles token cleanup and optional profile removal.

Open issue from users requesting this feature: #1639

Tests

  • Unit tests for runLogout covering: successful logout, logout with --delete, non-interactive mode without --force, non-existent profile, shared-host token preservation, and logout with an empty token cache.
  • Unit tests for DeleteProfile covering: deleting an existing profile, profile not found, and custom config path.
  • Acceptance tests covering: profile ordering and comments preserved after deletion, deleting the last non-default profile, deleting the DEFAULT profile, token-only logout for unique and shared hosts, and error paths for non-existent profiles and missing --profile in non-interactive mode.
  • Manual testing by authenticating into multiple profiles and logging out.

@eng-dev-ecosystem-bot
Copy link
Collaborator

eng-dev-ecosystem-bot commented Feb 27, 2026

Commit: 333ea5a

Run: 22626695579

Env 🪲​BUG ❌​FAIL 🟨​KNOWN 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
🪲​ aws linux 1 7 1 1 7 266 782 11:02
🪲​ aws windows 1 7 1 7 269 780 8:00
🪲​ aws-ucws linux 1 2 7 7 363 697 10:50
🪲​ aws-ucws windows 1 2 7 7 365 695 8:29
🪲​ azure linux 2 8 1 1 9 261 780 146:38
🪲​ azure windows 2 5 1 2 1 9 264 778 114:36
🪲​ azure-ucws linux 2 7 1 3 9 359 693 130:24
🪲​ azure-ucws windows 2 8 1 2 9 361 691 147:53
🪲​ gcp linux 2 7 1 1 1 9 257 783 132:19
🪲​ gcp windows 2 8 1 1 9 259 781 145:47
27 interesting tests: 8 FAIL, 7 KNOWN, 7 SKIP, 3 flaky, 2 BUG
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
🟨​ TestAccept 🟨​K 🟨​K 💚​R 🔄​f 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K 🟨​K
🪲​ TestAccept/bundle/deployment/bind/alert 🙈​s 🙈​s 🙈​s 🙈​s 🪲​B 🪲​B 🪲​B 🪲​B 🪲​B 🪲​B
❌​ TestAccept/bundle/deployment/bind/alert/DATABRICKS_BUNDLE_ENGINE=direct ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/deployment/bind/alert/DATABRICKS_BUNDLE_ENGINE=terraform ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/generate/alert ✅​p ✅​p ✅​p ✅​p ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/generate/alert/DATABRICKS_BUNDLE_ENGINE=direct ✅​p ✅​p ✅​p ✅​p ❌​F ❌​F 🔄​f ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/generate/alert/DATABRICKS_BUNDLE_ENGINE=terraform ✅​p ✅​p ✅​p ✅​p ❌​F ❌​F ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/alerts/with_file ✅​p ✅​p ✅​p ✅​p ❌​F ✅​p ❌​F ❌​F ❌​F ❌​F
❌​ TestAccept/bundle/resources/alerts/with_file/DATABRICKS_BUNDLE_ENGINE=direct ✅​p ✅​p ✅​p ✅​p ❌​F 🔄​f ❌​F ❌​F 🔄​f ❌​F
❌​ TestAccept/bundle/resources/alerts/with_file/DATABRICKS_BUNDLE_ENGINE=terraform ✅​p ✅​p ✅​p ✅​p ❌​F 🔄​f ❌​F ❌​F ❌​F ❌​F
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/update_protected 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestAccept/ssh/connect-serverless-gpu 🙈​s 🙈​s 🔄​f 🔄​f 🙈​s 🙈​s 🔄​f 🔄​f 🙈​s 🙈​s
🔄​ TestAccept/ssh/connection 💚​R 💚​R 🔄​f 💚​R 💚​R 💚​R 🔄​f 🔄​f 💚​R 💚​R
🔄​ TestSecretsPutSecretBytesValue 🔄​f ✅​p 🙈​s 🙈​s ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p
🪲​ TestImportFileFormatSource 🪲​B 🪲​B 🪲​B 🪲​B 🪲​B 🪲​B 🪲​B 🪲​B 🪲​B 🪲​B
Top 24 slowest tests (at least 2 minutes):
duration env testname
6:07 azure windows TestAccept/bundle/resources/alerts/with_file/DATABRICKS_BUNDLE_ENGINE=terraform
5:45 gcp linux TestAccept/bundle/resources/alerts/with_file/DATABRICKS_BUNDLE_ENGINE=direct
4:17 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:58 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:49 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:28 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:15 azure windows TestAccept/bundle/resources/alerts/with_file/DATABRICKS_BUNDLE_ENGINE=direct
3:14 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:05 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:58 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:57 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:55 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:54 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:52 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:50 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:47 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:46 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:44 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:19 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:16 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:16 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:16 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:12 aws linux TestSecretsPutSecretBytesValue
2:05 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct

@mihaimitrea-db mihaimitrea-db self-assigned this Feb 27, 2026
@mihaimitrea-db mihaimitrea-db force-pushed the mihaimitrea-db/stack/auth_logout branch from 0b2d46f to 5f039b9 Compare March 2, 2026 11:42
@mihaimitrea-db mihaimitrea-db linked an issue Mar 2, 2026 that may be closed by this pull request
@mihaimitrea-db mihaimitrea-db marked this pull request as ready for review March 2, 2026 14:06
Copy link
Member

@simonfaltum simonfaltum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! Maybe add an extra test but can also be done in a follow up at a later stage

Copy link
Contributor

@pietern pietern left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend adding a few acceptance tests to test this e2e.

That would also capture some of the error messages.

defaultConfigPath := "~/.databrickscfg"
if runtime.GOOS == "windows" {
defaultConfigPath = "%USERPROFILE%\\.databrickscfg"
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it belongs (or should already exist) in libs/databrickscfg.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also honor $DATABRICKS_CONFIG_FILE.


cmd := &cobra.Command{
Use: "logout",
Short: "Log out of a Databricks profile",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging out implies (to me) only the token is removed. The long help says the profile itself is removed.

Either needs to be updated.


tokenCache, err := cache.NewFileTokenCache()
if err != nil {
log.Warnf(ctx, "Failed to open token cache: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When does this happen?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the file is corrupted

return err
}
if !approved {
return nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should log something here as well.

}

if err := tokenCache.Store(p.Name, nil); err != nil {
log.Warnf(ctx, "Failed to delete profile-keyed token for profile %q: %v", p.Name, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is storing a nil equal to removing?

Doesn't the token cache have a native remove function?

Copy link
Collaborator Author

@mihaimitrea-db mihaimitrea-db Mar 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, storing nil is equal to removing. The TokenCache interface defines the Store function as follows:

// Store stores the token with the given key, replacing any existing token.
// If t is nil, it deletes the token.
Store(key string, t *oauth2.Token) error

// (host/oidc/accounts/<account_id>).
func clearTokenCache(ctx context.Context, p profile.Profile, profiler profile.Profiler, tokenCache cache.TokenCache) {
if tokenCache == nil {
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the token cache doesn't exist, but the profile does, should the profile still be deleted?

@pietern
Copy link
Contributor

pietern commented Mar 3, 2026

An acceptance test will also confirm the before/after state of the .databrickscfg file.

I would like to see if comments, ordering, etc, are retained upon profile deletion.

@mihaimitrea-db mihaimitrea-db force-pushed the mihaimitrea-db/stack/auth_logout branch from b8fed18 to 0ca6e59 Compare March 3, 2026 08:56
@mihaimitrea-db mihaimitrea-db force-pushed the mihaimitrea-db/stack/auth_logout branch from 9cf58d8 to 66871b3 Compare March 3, 2026 12:30
@mihaimitrea-db mihaimitrea-db requested review from a team and lennartkats-db as code owners March 3, 2026 12:30
@mihaimitrea-db mihaimitrea-db force-pushed the mihaimitrea-db/stack/auth_logout branch from 66871b3 to 1764259 Compare March 3, 2026 12:34
@mihaimitrea-db mihaimitrea-db removed request for a team and lennartkats-db March 3, 2026 12:36
Implement the initial version of databricks auth logout which removes a profile from ~/.databrickscfg and clears associated OAuth tokens from the token cache.

This iteration supports explicit profile selection via --profile and a --force flag to skip the confirmation prompt. Interactive profile selection will be added in a follow-up.

Token cache cleanup is best-effort: the profile-keyed token is always removed, and the host-keyed token is removed only when no other profile references the same host.
Replace plain fmt.Sprintf confirmation prompt with a structured template using cmdio.RenderWithTemplate. The warning now uses color and bold formatting to clearly highlight the profile name, config path, and consequences before prompting for confirmation.
Resolve config path from the profiler instead of hardcoding fallbacks. Delete the profile before clearing the token cache so a config write failure does not leave tokens removed. Fix token cleanup for account and unified profiles by computing the correct OIDC cache key (host/oidc/accounts/<account_id>). Drop the nil profiler guard, add a success message on logout, and extract backupConfigFile in ops.go to remove duplication. Consolidate token cleanup tests into a table-driven test covering shared hosts, unique hosts, account, and unified profiles.
Merge shared-host token deletion verification into one main parametrized test by addding the hostBasedKey and isSharedKey parameters to each case. This replaces the TestLogoutTokenCacheCleanup test with an assertion: host-based keys are preserved when another profile shares the same host, and deleted otherwise.
Rewrite the test to use inline config seeds and explicit expected state. Add cases for deleting the last non-default profile, deleting a unified host profile with multiple keys, and deleting the DEFAULT section.
- Use profiler.GetPath() to resolve config path instead of hardcoding platform-specific defaults for the help text.
- Read DATABRICKS_CONFIG_FILE via env.Get(ctx, ...) instead of os.Getenv to respect context-level env overrides.
- Add abort message when user declines the confirmation prompt.
- Guard DeleteProfile against non-existent profiles to avoid creating unnecessary backup files.
- Add TestDeleteProfile_NotFound for the error path.
Cover four scenarios: profile ordering and comments are preserved after deletion, deleting the last non-default profile leaves an empty DEFAULT section, deleting the DEFAULT profile itself clears its keys and restores the default comment, and error paths for non-existent profiles and missing --profile in non-interactive mode.
By default, Authentication related commands. For more information regarding how
authentication for the Databricks CLI and SDKs work please refer to the documentation
linked below.

AWS: https://docs.databricks.com/dev-tools/auth/index.html
Azure: https://learn.microsoft.com/azure/databricks/dev-tools/auth
GCP: https://docs.gcp.databricks.com/dev-tools/auth/index.html

Usage:
  databricks auth [command]

Available Commands:
  describe    Describes the credentials and the source of those credentials, being used by the CLI to authenticate
  env         Get env
  login       Log into a Databricks workspace or account
  profiles    Lists profiles from ~/.databrickscfg
  token       Get authentication token

Flags:
      --account-id string              Databricks Account ID
      --experimental-is-unified-host   Flag to indicate if the host is a unified host
  -h, --help                           help for auth
      --host string                    Databricks Host
      --workspace-id string            Databricks Workspace ID

Global Flags:
      --debug            enable debug logging
  -o, --output type      output type: text or json (default text)
  -p, --profile string   ~/.databrickscfg profile
  -t, --target string    bundle target to use (if applicable)

Use "databricks auth [command] --help" for more information about a command. now only clears cached OAuth
tokens without removing the profile from ~/.databrickscfg. Pass
--delete to also remove the profile entry from the config file.
@mihaimitrea-db mihaimitrea-db force-pushed the mihaimitrea-db/stack/auth_logout branch from 1764259 to e8a6f57 Compare March 3, 2026 13:50
Existing acceptance tests that verify profile deletion now use --delete
since profile removal is opt-in. Two new acceptance tests verify token-only
logout: one for a unique host (both cache entries cleared) and one for a
shared host (host-keyed token preserved).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Add logout command

4 participants