Skip to content

Commit d58d93a

Browse files
authored
auth: rename legacy storage mode to plaintext, make it the default (#5088)
## Why The CLI's storage-mode resolver had three values: `legacy` (default, file cache + host-key dual-write), `secure` (OS keyring), and `plaintext` (file cache, no dual-write, intended placeholder for a future no-mirror mode). The `plaintext` path duplicated `legacy` minus the host-key entry that older Go SDKs (v0.61-v0.103) still rely on, so its "no dual-write" property bought users nothing. Two modes is the right surface: `plaintext` for the file cache and `secure` for the OS keyring. While in there, also fixes the host-key dual-write code path so it actually goes through the SDK on every cache write (including refresh), the way `DualWritingTokenCache`'s docstring already claimed. ## Changes **Mode rename** - `plaintext` takes today's `legacy` semantics (file cache + host-key dual-write) and becomes the resolver default. - `secure` is unchanged. - `legacy` is removed from the user-visible surface. `DATABRICKS_AUTH_STORAGE=legacy` is now rejected with the standard "unknown storage mode" error listing `plaintext` and `secure`. The keyword was undocumented and users on the default were unaffected. **Wrap-once refactor** - New `storage.WrapForOAuthArgument(cache, mode, arg)` returns `NewDualWritingTokenCache(...)` for plaintext, the cache unchanged otherwise. Applied at the three login `NewPersistentAuth` call sites (login main flow, `discoveryLogin`, `runInlineLogin`). - Deletes `dualWriteLegacyHostKey`/`mirrorTokenUnderHostKey` and the post-Challenge call sites. The mirror now happens inside the SDK's own Store call via the wrapper, removing one redundant Lookup and one redundant primary-key Store per login. - `DualWritingTokenCache.Store` now treats the host-key mirror as best-effort: a failure on the second Store is silently dropped. Previously the wrapper returned the error, but it was always called by a helper that swallowed it; pulling the wrapper into the SDK Store path made that error fatal, which would block primary login over a non-essential backward-compat shim. **Acceptance fixtures** - `legacy-env-default/` -> `plaintext-env-default/`. Scripts that set `DATABRICKS_AUTH_STORAGE=legacy` now set `=plaintext`. Error-message outputs regenerated. ## Test plan - [x] `make checks` clean - [x] `make test` passes (5305 unit, 2514 acceptance) - [x] `make lint` 0 issues - [x] Storage-mode acceptance: invalid env, invalid config, env-overrides-config, plaintext-env-default with the new error-message format. - [x] `TestWrapForOAuthArgument`: end-to-end Store across plaintext / secure / unknown — primary key always written, host-key mirror only in plaintext. - [x] `TestDualWritingCacheStoreHostKeyFailureIsBestEffort`: host-key write error does not propagate; primary write persists.
1 parent 1c514fc commit d58d93a

17 files changed

Lines changed: 137 additions & 133 deletions

File tree

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
out.requests.txt

acceptance/bundle/run_as/job_default/test.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
Badness = "run_as is still set even though it's not in bundle and not in reset request"
2+
Ignore = [".databricks/.gitignore"]
23

34
Local = false
45
Cloud = true

acceptance/cmd/auth/storage-modes/env-overrides-config/script

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export DATABRICKS_AUTH_STORAGE=legacy
1+
export DATABRICKS_AUTH_STORAGE=plaintext
22

33
cat > "./home/.databrickscfg" <<ENDCFG
44
[__settings__]
@@ -26,7 +26,7 @@ ENDCACHE
2626
title "Token cache keys before logout\n"
2727
jq -S '.tokens | keys' "./home/.databricks/token-cache.json"
2828

29-
# Env var = legacy must beat [__settings__] auth_storage = secure.
29+
# Env var = plaintext must beat [__settings__] auth_storage = secure.
3030
# Proof: logout clears the file-backed token-cache.json. If the env override
3131
# were ignored, the CLI would try the keyring and leave the file cache alone.
3232
trace $CLI auth logout --profile dev --auto-approve
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

22
>>> [CLI] auth token --profile nonexistent
3-
Error: auth_storage: unknown storage mode "bogus" (want legacy, secure, or plaintext)
3+
Error: auth_storage: unknown storage mode "bogus" (want plaintext or secure)
44

55
Exit code: 1
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

22
>>> [CLI] auth token --profile nonexistent
3-
Error: DATABRICKS_AUTH_STORAGE: unknown storage mode "bogus" (want legacy, secure, or plaintext)
3+
Error: DATABRICKS_AUTH_STORAGE: unknown storage mode "bogus" (want plaintext or secure)
44

55
Exit code: 1

acceptance/cmd/auth/storage-modes/legacy-env-default/out.test.toml renamed to acceptance/cmd/auth/storage-modes/plaintext-env-default/out.test.toml

File renamed without changes.

acceptance/cmd/auth/storage-modes/legacy-env-default/output.txt renamed to acceptance/cmd/auth/storage-modes/plaintext-env-default/output.txt

File renamed without changes.

acceptance/cmd/auth/storage-modes/legacy-env-default/script renamed to acceptance/cmd/auth/storage-modes/plaintext-env-default/script

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export DATABRICKS_AUTH_STORAGE=legacy
1+
export DATABRICKS_AUTH_STORAGE=plaintext
22

33
cat > "./home/.databrickscfg" <<ENDCFG
44
[dev]

cmd/auth/login.go

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,23 +60,6 @@ func discoveryErr(msg string, err error) error {
6060
return fmt.Errorf("%s%s", msg, discoveryFallbackTip)
6161
}
6262

63-
// dualWriteLegacyHostKey mirrors the freshly minted token under the legacy
64-
// host-based cache key so users alternating between CLI and SDK find it.
65-
// Skipped for secure mode to avoid multiplying keyring entries.
66-
func dualWriteLegacyHostKey(ctx context.Context, tokenCache cache.TokenCache, arg u2m.OAuthArgument, mode storage.StorageMode) {
67-
if mode != storage.StorageModeLegacy {
68-
return
69-
}
70-
t, err := tokenCache.Lookup(arg.GetCacheKey())
71-
if err != nil || t == nil {
72-
return
73-
}
74-
dual := storage.NewDualWritingTokenCache(tokenCache, arg)
75-
if err := dual.Store(arg.GetCacheKey(), t); err != nil {
76-
log.Debugf(ctx, "token cache dual-write failed: %v", err)
77-
}
78-
}
79-
8063
type discoveryPersistentAuth interface {
8164
Challenge() error
8265
Token() (*oauth2.Token, error)
@@ -256,7 +239,7 @@ a new profile is created.
256239
persistentAuthOpts := []u2m.PersistentAuthOption{
257240
u2m.WithOAuthArgument(oauthArgument),
258241
u2m.WithBrowser(getBrowserFunc(cmd)),
259-
u2m.WithTokenCache(tokenCache),
242+
u2m.WithTokenCache(storage.WrapForOAuthArgument(tokenCache, mode, oauthArgument)),
260243
}
261244
if len(scopesList) > 0 {
262245
persistentAuthOpts = append(persistentAuthOpts, u2m.WithScopes(scopesList))
@@ -273,7 +256,6 @@ a new profile is created.
273256
if err = persistentAuth.Challenge(); err != nil {
274257
return err
275258
}
276-
dualWriteLegacyHostKey(ctx, tokenCache, oauthArgument, mode)
277259
// At this point, an OAuth token has been successfully minted and stored
278260
// in the CLI cache. The rest of the command focuses on:
279261
// 1. Workspace selection for SPOG hosts (best-effort);
@@ -593,7 +575,7 @@ func discoveryLogin(ctx context.Context, in discoveryLoginInputs) error {
593575
u2m.WithOAuthArgument(arg),
594576
u2m.WithBrowser(in.browserFunc),
595577
u2m.WithDiscoveryLogin(),
596-
u2m.WithTokenCache(in.tokenCache),
578+
u2m.WithTokenCache(storage.WrapForOAuthArgument(in.tokenCache, in.mode, arg)),
597579
}
598580
if len(scopesList) > 0 {
599581
opts = append(opts, u2m.WithScopes(scopesList))
@@ -613,7 +595,6 @@ func discoveryLogin(ctx context.Context, in discoveryLoginInputs) error {
613595
if err := persistentAuth.Challenge(); err != nil {
614596
return discoveryErr("login via login.databricks.com failed", err)
615597
}
616-
dualWriteLegacyHostKey(ctx, in.tokenCache, arg, in.mode)
617598

618599
discoveredHost := arg.GetDiscoveredHost()
619600
if discoveredHost == "" {

cmd/auth/login_test.go

Lines changed: 0 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"time"
1616

1717
"github.com/databricks/cli/libs/auth"
18-
"github.com/databricks/cli/libs/auth/storage"
1918
"github.com/databricks/cli/libs/cmdio"
2019
"github.com/databricks/cli/libs/databrickscfg/profile"
2120
"github.com/databricks/cli/libs/env"
@@ -1104,73 +1103,3 @@ func TestLoginRejectsPositionalArgWithProfileFlag(t *testing.T) {
11041103
err := cmd.Execute()
11051104
assert.ErrorContains(t, err, `argument "https://example.com" cannot be combined with --host or --profile`)
11061105
}
1107-
1108-
func TestDualWriteLegacyHostKey(t *testing.T) {
1109-
const (
1110-
profileName = "dual-profile"
1111-
host = "https://dual-host.example.com"
1112-
)
1113-
tok := &oauth2.Token{AccessToken: "abc", RefreshToken: "r"}
1114-
1115-
cacheWithToken := func() *inMemoryTokenCache {
1116-
return &inMemoryTokenCache{Tokens: map[string]*oauth2.Token{profileName: tok}}
1117-
}
1118-
emptyCache := func() *inMemoryTokenCache {
1119-
return &inMemoryTokenCache{Tokens: map[string]*oauth2.Token{}}
1120-
}
1121-
newArg := func(t *testing.T) *u2m.BasicDiscoveryOAuthArgument {
1122-
arg, err := u2m.NewBasicDiscoveryOAuthArgument(profileName)
1123-
require.NoError(t, err)
1124-
arg.SetDiscoveredHost(host)
1125-
return arg
1126-
}
1127-
1128-
cases := []struct {
1129-
name string
1130-
mode storage.StorageMode
1131-
cache func() *inMemoryTokenCache
1132-
wantHostKey bool
1133-
}{
1134-
{
1135-
name: "legacy mirrors cached token under host key",
1136-
mode: storage.StorageModeLegacy,
1137-
cache: cacheWithToken,
1138-
wantHostKey: true,
1139-
},
1140-
{
1141-
name: "legacy is a no-op when cache has no entry",
1142-
mode: storage.StorageModeLegacy,
1143-
cache: emptyCache,
1144-
},
1145-
{
1146-
name: "secure skips dual-write",
1147-
mode: storage.StorageModeSecure,
1148-
cache: cacheWithToken,
1149-
},
1150-
{
1151-
name: "plaintext skips dual-write",
1152-
mode: storage.StorageModePlaintext,
1153-
cache: cacheWithToken,
1154-
},
1155-
{
1156-
name: "unknown mode skips dual-write",
1157-
mode: storage.StorageModeUnknown,
1158-
cache: cacheWithToken,
1159-
},
1160-
}
1161-
1162-
for _, tc := range cases {
1163-
t.Run(tc.name, func(t *testing.T) {
1164-
c := tc.cache()
1165-
dualWriteLegacyHostKey(t.Context(), c, newArg(t), tc.mode)
1166-
1167-
got, err := c.Lookup(host)
1168-
if tc.wantHostKey {
1169-
require.NoError(t, err)
1170-
assert.Equal(t, tok, got)
1171-
} else {
1172-
assert.ErrorIs(t, err, cache.ErrNotFound)
1173-
}
1174-
})
1175-
}
1176-
}

0 commit comments

Comments
 (0)