Skip to content

Add text output mode for auth token and auth env#4725

Closed
simonfaltum wants to merge 11 commits intomainfrom
simonfaltum/auth-text-output
Closed

Add text output mode for auth token and auth env#4725
simonfaltum wants to merge 11 commits intomainfrom
simonfaltum/auth-text-output

Conversation

@simonfaltum
Copy link
Copy Markdown
Member

@simonfaltum simonfaltum commented Mar 12, 2026

Why

auth env had its own custom auth resolution logic (local --host/--profile flags, manual profile-to-host matching via ini file scanning). It didn't go through the standard CLI auth chain, so it couldn't resolve auth from bundle context, default profiles, or environment variables. It was effectively a standalone tool that only worked with explicit profile or host arguments.

The command should return the environment variables needed to authenticate as the exact same identity the CLI is currently authenticated as. This is what bundle run already does implicitly (via auth.Env on the resolved config), but there was no explicit command for it.

Additionally, both auth token and auth env always output JSON, ignoring the --output flag.

Changes

auth env: Refactored to use the CLI's standard auth resolution (root.MustAnyClient + cmdctx.ConfigUsed), same pattern as auth describe. Uses auth.Env(cfg) from libs/auth/env.go to generate env vars. This means it now works with bundle context, env var auth, default profiles, and all other standard auth paths.

Before: databricks auth env --host https://my-workspace.cloud.databricks.com with custom profile matching.
Now: databricks auth env returns the env vars for whatever identity the CLI resolved through its normal auth chain (profile flag, bundle config, env vars, default profile, etc.).

Breaking changes:

  • Removed command-specific --host and --profile flags (the inherited flags from the parent auth and root commands cover this)
  • JSON output is a flat map instead of {"env": {...}}
  • Only the primary env var per attribute is emitted (via auth.Env, consistent with bundle run)

Removed ~120 lines of dead code: canonicalHost, resolveSection, loadFromDatabricksCfg, collectEnvVars, ErrNoMatchingProfiles.

auth token: Respects --output text when explicitly set, outputting just the access token string suitable for piping.

Both commands: Respect --output text for KEY=VALUE lines (auth env) or plain token string (auth token). JSON remains the default for backward compatibility.

Test plan

  • Unit tests for both commands in text and JSON modes
  • Unit test for backward compatibility (no explicit --output keeps JSON)
  • Unit test for value quoting in auth env text mode
  • make checks passes

Both commands now respect --output text when explicitly set:
- auth token --output text: outputs just the access token string
- auth env --output text: outputs KEY=VALUE lines

JSON remains the default for backward compatibility.

Co-authored-by: Isaac
@eng-dev-ecosystem-bot
Copy link
Copy Markdown
Collaborator

eng-dev-ecosystem-bot commented Mar 12, 2026

Commit: 1a3c536

Run: 23462448752

Env 🟨​KNOWN 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
🟨​ aws linux 7 1 9 271 801 7:33
🟨​ aws windows 7 1 9 273 799 6:35
💚​ aws-ucws linux 8 9 369 716 7:15
🔄​ aws-ucws windows 3 7 9 369 714 6:22
💚​ azure linux 2 11 274 799 6:25
💚​ azure windows 2 11 276 797 4:01
💚​ azure-ucws linux 2 11 374 712 8:13
🔄​ azure-ucws windows 4 1 11 373 710 5:36
💚​ gcp linux 2 11 270 802 6:43
💚​ gcp windows 2 11 272 800 5:49
20 interesting tests: 9 SKIP, 7 KNOWN, 4 flaky
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 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ 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/basic 🙈​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/postgres_projects/update_display_name 🙈​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 ✅​p 🔄​f 🙈​s 🙈​s ✅​p 🔄​f 🙈​s 🙈​s
🔄​ TestAccept/ssh/connect-serverless-gpu/DATABRICKS_BUNDLE_ENGINE=direct ✅​p 🔄​f ✅​p 🔄​f
🔄​ TestAccept/ssh/connection 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R 🔄​f 💚​R 💚​R
🔄​ TestAccept/ssh/connection/DATABRICKS_BUNDLE_ENGINE=direct ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p 🔄​f ✅​p ✅​p
Top 25 slowest tests (at least 2 minutes):
duration env testname
5:53 aws-ucws linux TestAccept/ssh/connection/DATABRICKS_BUNDLE_ENGINE=direct
3:50 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:22 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:18 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:17 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:15 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:14 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:06 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:01 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:43 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:42 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:39 gcp linux TestAccept/ssh/connection/DATABRICKS_BUNDLE_ENGINE=direct
2:38 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:38 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:38 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:37 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:36 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:32 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:31 gcp linux TestSecretsPutSecretStringValue
2:15 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:14 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:12 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:09 aws linux TestAccept/ssh/connection/DATABRICKS_BUNDLE_ENGINE=direct
2:09 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:03 aws linux TestSecretsPutSecretStringValue

@simonfaltum simonfaltum marked this pull request as ready for review March 13, 2026 10:59
Copy link
Copy Markdown
Contributor

@shreyas-goenka shreyas-goenka left a comment

Choose a reason for hiding this comment

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

Note: This review was posted by Claude (AI assistant). Shreyas will do a separate, more thorough review pass.

Priority: MEDIUM — Env var inconsistency needs a decision

HIGH: DATABRICKS_OUTPUT_FORMAT env var silently ignored

Both auth token and auth env commands use cmd.Flag("output").Changed to detect text mode, which means the DATABRICKS_OUTPUT_FORMAT=text env var is ignored. This is inconsistent with the rest of the CLI where the env var is a first-class way to set output format. The guard exists because these commands have an inverted default (JSON by default, unlike other commands), so without it the default behavior would break. A decision is needed on whether the env var should be honored here.

MEDIUM: Shell quoting not fully .env-file compatible

The quoteEnvValue function uses POSIX shell single-quote escaping ('\''), but the PR description claims ".env loader" compatibility. Most .env parsers (Docker, python-dotenv) use different quoting rules. Only eval usage is truly compatible.

LOW: Doc comment typo

quoteEnvValue doc references '\" but code does '\''.

What looks good

  • Backward compatibility preserved (JSON remains default)
  • collectEnvVars extraction reduces duplication
  • Clean text output formatting

The comment referenced the '\" escape sequence, but the code
actually uses the POSIX '\'' sequence (end-quote, backslash-escaped
literal single quote, re-open quote).
…al characters

Values containing \n or \r were emitted unquoted, producing raw multi-line
shell output instead of a single safe KEY=VALUE pair. Add both characters
to shellQuotedSpecialChars so they trigger single-quoting.
Use root.MustAnyClient and auth.Env instead of custom profile/host
resolution logic. This makes auth env return the environment variables
for the exact identity the CLI is authenticated as, including bundle
context and all standard auth resolution paths.

Breaking changes:
- Removed command-specific --host and --profile flags (use the
  inherited flags from the parent/root commands)
- JSON output is a flat map instead of wrapped in {"env": ...}
- Only the primary env var per attribute is emitted (via auth.Env)
@renaudhartert-db
Copy link
Copy Markdown
Contributor

renaudhartert-db commented Mar 22, 2026

The command should return the environment variables needed to authenticate as the exact same identity the CLI is currently authenticated as.

To be honest, even that is arguable. For example, env variables in OIDC settings are not something that users truly control — some of these are not even part of the profile. It is one of these commands that have badly aged; it might be a good candidate for deprecation or at least refactor.

Comment on lines +97 to +98
_, _ = fmt.Fprintln(cmd.OutOrStdout(), t.AccessToken)
return nil
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What's the rationale behind swallowing the error, here and in other places?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure, I'll return the write errors instead. The pattern was carried over from the existing code but it's better to surface them so the CLI exits non-zero if stdout is broken.

}

func writeTokenOutput(cmd *cobra.Command, t *oauth2.Token) error {
// Output plain token when the user explicitly passes --output text.
Copy link
Copy Markdown
Contributor

@renaudhartert-db renaudhartert-db Mar 22, 2026

Choose a reason for hiding this comment

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

This comment does not bring much compared to reading the code. Rather, I think we should briefly explain why we discard implicit — backward compatibily?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair point. I'll replace this with a comment explaining the actual reason: auth token defaults to JSON (unlike most CLI commands), so we only switch to text when the user explicitly passes --output text to avoid breaking scripts that parse the JSON output.

Address review feedback: return write errors from fmt.Fprintln,
fmt.Fprintf, and Write calls instead of discarding them with _, _.
Also improve the comment in writeTokenOutput to explain why we
check cmd.Flag("output").Changed rather than just restating what
the code does.

Co-authored-by: Isaac
Copy link
Copy Markdown
Contributor

@renaudhartert-db renaudhartert-db left a comment

Choose a reason for hiding this comment

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

LGTM for the change related to auth token.

Though, I'd like to better understand the future of the auth env command and how we expect it to work with authentication types that rely on variables that are not owned by Databricks. This matters as ensuring proper implementation of this command has implication on the implementation of the auth library and the "profile type".

It might be worth to split this PR.

@simonfaltum
Copy link
Copy Markdown
Member Author

Split into #4903 and #4904

@simonfaltum simonfaltum closed this Apr 6, 2026
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.

4 participants