Skip to content

Commit a649259

Browse files
Introduce a CLI-owned Store interface for token storage (#5383)
## What is this about? The CLI's token storage is currently shaped by the SDK's TokenCache interface. That interface is internal to the SDK's user-to-machine (U2M) login flow and only carries a bare OAuth token keyed by a string, so there is no room to store anything alongside a token, and the CLI's storage is coupled to an SDK type that exists specifically for U2M login. This PR introduces a CLI-owned `Store` interface (`Put`, `Lookup`, `Delete`) over an explicit `Entry` envelope that wraps the token. A small adapter, `ToU2MTokenCache`, presents a `Store` to the SDK at the one place that still requires the SDK interface: passing a cache to `u2m.PersistentAuth`. The file and keyring backends now implement `Store`, and the OAuth helpers adapt it for the U2M paths. This is a structural change with no behavior change. The `Entry` only holds the token; the point is that it can now grow additional fields without changing the interface or depending on the SDK. The first such field will be a config checksum used to invalidate a cached token when its profile changed, which arrives with a later change that adds caching for machine-to-machine (M2M) and OIDC tokens. **Naming note for reviewers:** the new interface is named Store to distinguish the CLI's durable storage from the SDK's transient Cache. The concrete types and helpers in this package are intentionally left in the old vocabulary (fileTokenCache, keyringCache, and so on) in this PR to keep the diff focused on the structural change. A follow-up PR will take care of renaming. ## Testing Existing auth unit and acceptance tests pass; there are no output or behavior changes.
1 parent eb3fb12 commit a649259

17 files changed

Lines changed: 313 additions & 191 deletions

cmd/auth/in_memory_test.go

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,40 @@
11
package auth
22

33
import (
4-
"github.com/databricks/databricks-sdk-go/credentials/u2m/cache"
4+
"github.com/databricks/cli/libs/auth/storage"
55
"golang.org/x/oauth2"
66
)
77

88
type inMemoryTokenCache struct {
99
Tokens map[string]*oauth2.Token
1010
}
1111

12-
// Lookup implements TokenCache.
13-
// Returns a copy to match real (file-backed) cache behavior, where each
14-
// Lookup deserializes a fresh struct. Without the copy, callers that
12+
// Lookup returns a copy to match real (file-backed) cache behavior, where
13+
// each lookup deserializes a fresh struct. Without the copy, callers that
1514
// mutate the returned token (e.g. clearing RefreshToken) would corrupt
1615
// entries shared across test cases.
17-
func (i *inMemoryTokenCache) Lookup(key string) (*oauth2.Token, error) {
16+
func (i *inMemoryTokenCache) Lookup(key string) (storage.Entry, error) {
1817
token, ok := i.Tokens[key]
1918
if !ok {
20-
return nil, cache.ErrNotFound
19+
return storage.Entry{}, storage.ErrNotFound
2120
}
2221
cp := *token
23-
return &cp, nil
22+
return storage.Entry{Token: &cp}, nil
2423
}
2524

26-
// Store implements TokenCache.
27-
// Stores a copy to prevent callers from mutating cached entries after Store
28-
// returns (mirrors file-backed cache semantics).
29-
func (i *inMemoryTokenCache) Store(key string, t *oauth2.Token) error {
30-
if t == nil {
31-
delete(i.Tokens, key)
32-
} else {
33-
cp := *t
34-
i.Tokens[key] = &cp
35-
}
25+
// Put stores a copy to prevent callers from mutating cached entries after
26+
// put returns (mirrors file-backed cache semantics).
27+
func (i *inMemoryTokenCache) Put(key string, e storage.Entry) error {
28+
cp := *e.Token
29+
i.Tokens[key] = &cp
30+
return nil
31+
}
32+
33+
// Delete deletes the entry under key. Deleting a missing entry is not
34+
// an error.
35+
func (i *inMemoryTokenCache) Delete(key string) error {
36+
delete(i.Tokens, key)
3637
return nil
3738
}
3839

39-
var _ cache.TokenCache = (*inMemoryTokenCache)(nil)
40+
var _ storage.Store = (*inMemoryTokenCache)(nil)

cmd/auth/login.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"github.com/databricks/databricks-sdk-go/config"
2323
"github.com/databricks/databricks-sdk-go/config/experimental/auth/authconv"
2424
"github.com/databricks/databricks-sdk-go/credentials/u2m"
25-
"github.com/databricks/databricks-sdk-go/credentials/u2m/cache"
2625
"github.com/spf13/cobra"
2726
"golang.org/x/oauth2"
2827
)
@@ -300,7 +299,7 @@ a new profile is created.
300299
persistentAuthOpts := []u2m.PersistentAuthOption{
301300
u2m.WithOAuthArgument(oauthArgument),
302301
u2m.WithBrowser(getBrowserFunc(cmd)),
303-
u2m.WithTokenCache(storage.WrapForOAuthArgument(tokenCache, mode, oauthArgument)),
302+
u2m.WithTokenCache(storage.WrapForOAuthArgument(ctx, tokenCache, mode, oauthArgument)),
304303
}
305304
if len(scopesList) > 0 {
306305
persistentAuthOpts = append(persistentAuthOpts, u2m.WithScopes(scopesList))
@@ -629,7 +628,7 @@ type discoveryLoginInputs struct {
629628
scopes string
630629
existingProfile *profile.Profile
631630
browserFunc func(string) error
632-
tokenCache cache.TokenCache
631+
tokenCache storage.Store
633632
mode storage.StorageMode
634633
}
635634

@@ -651,7 +650,7 @@ func discoveryLogin(ctx context.Context, in discoveryLoginInputs) error {
651650
u2m.WithOAuthArgument(arg),
652651
u2m.WithBrowser(in.browserFunc),
653652
u2m.WithDiscoveryLogin(),
654-
u2m.WithTokenCache(storage.WrapForOAuthArgument(in.tokenCache, in.mode, arg)),
653+
u2m.WithTokenCache(storage.WrapForOAuthArgument(ctx, in.tokenCache, in.mode, arg)),
655654
}
656655
if len(scopesList) > 0 {
657656
opts = append(opts, u2m.WithScopes(scopesList))

cmd/auth/login_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ import (
1515
"time"
1616

1717
"github.com/databricks/cli/libs/auth"
18+
"github.com/databricks/cli/libs/auth/storage"
1819
"github.com/databricks/cli/libs/cmdio"
1920
"github.com/databricks/cli/libs/databrickscfg/profile"
2021
"github.com/databricks/cli/libs/env"
2122
"github.com/databricks/cli/libs/log"
2223
"github.com/databricks/databricks-sdk-go/credentials/u2m"
23-
"github.com/databricks/databricks-sdk-go/credentials/u2m/cache"
2424
"github.com/spf13/cobra"
2525
"github.com/stretchr/testify/assert"
2626
"github.com/stretchr/testify/require"
@@ -29,7 +29,7 @@ import (
2929

3030
// newTestTokenCache returns an in-memory token cache for tests so that
3131
// discoveryLogin and other login helpers don't touch ~/.databricks/token-cache.json.
32-
func newTestTokenCache() cache.TokenCache {
32+
func newTestTokenCache() storage.Store {
3333
return &inMemoryTokenCache{Tokens: map[string]*oauth2.Token{}}
3434
}
3535

cmd/auth/logout.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"github.com/databricks/cli/libs/databrickscfg/profile"
2929
"github.com/databricks/cli/libs/env"
3030
"github.com/databricks/databricks-sdk-go/config"
31-
"github.com/databricks/databricks-sdk-go/credentials/u2m/cache"
3231
"github.com/spf13/cobra"
3332
)
3433

@@ -158,7 +157,7 @@ type logoutArgs struct {
158157
autoApprove bool
159158
deleteProfile bool
160159
profiler profile.Profiler
161-
tokenCache cache.TokenCache
160+
tokenCache storage.Store
162161
configFilePath string
163162
}
164163

@@ -270,8 +269,8 @@ func getMatchingProfile(ctx context.Context, profileName string, profiler profil
270269
// remaining profile references the same key. For account and unified
271270
// profiles, the cache key includes the OIDC path
272271
// (host/oidc/accounts/<account_id>).
273-
func clearTokenCache(ctx context.Context, p profile.Profile, profiler profile.Profiler, tokenCache cache.TokenCache) error {
274-
if err := tokenCache.Store(p.Name, nil); err != nil {
272+
func clearTokenCache(ctx context.Context, p profile.Profile, profiler profile.Profiler, tokenCache storage.Store) error {
273+
if err := tokenCache.Delete(p.Name); err != nil {
275274
return fmt.Errorf("failed to delete profile-keyed token for profile %q: %w", p.Name, err)
276275
}
277276

@@ -291,7 +290,7 @@ func clearTokenCache(ctx context.Context, p profile.Profile, profiler profile.Pr
291290
}
292291

293292
if len(otherProfiles) == 0 {
294-
if err := tokenCache.Store(hostCacheKey, nil); err != nil {
293+
if err := tokenCache.Delete(hostCacheKey); err != nil {
295294
return fmt.Errorf("failed to delete host-keyed token for %q: %w", hostCacheKey, err)
296295
}
297296
}

cmd/auth/token.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,9 @@ type loadTokenArgs struct {
118118
// profiler is the profiler to use for reading the host and account ID from the .databrickscfg file.
119119
profiler profile.Profiler
120120

121-
// tokenCache is the underlying TokenCache used for OAuth tokens. The caller is
121+
// tokenCache is the underlying CLI cache used for OAuth tokens. The caller is
122122
// responsible for construction so that tests can substitute an in-memory cache.
123-
tokenCache cache.TokenCache
123+
tokenCache storage.Store
124124

125125
// mode is the resolved storage mode. When set to StorageModePlaintext,
126126
// login paths mirror freshly minted tokens under the legacy host-based
@@ -264,7 +264,7 @@ func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) {
264264
if err != nil {
265265
return nil, err
266266
}
267-
allArgs := append([]u2m.PersistentAuthOption{u2m.WithTokenCache(args.tokenCache)}, args.persistentAuthOpts...)
267+
allArgs := append([]u2m.PersistentAuthOption{u2m.WithTokenCache(storage.OAuthTokenCache(ctx, args.tokenCache, args.mode))}, args.persistentAuthOpts...)
268268
allArgs = append(allArgs, u2m.WithOAuthArgument(oauthArgument))
269269
persistentAuth, err := u2m.NewPersistentAuth(ctx, allArgs...)
270270
if err != nil {
@@ -318,7 +318,7 @@ func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) {
318318
//
319319
// Returns the resolved profile name and profile (if any). The host and related
320320
// fields on authArgs are updated in place when resolved via environment variables.
321-
func resolveNoArgsToken(ctx context.Context, profiler profile.Profiler, authArgs *auth.AuthArguments, tokenCache cache.TokenCache, mode storage.StorageMode) (string, *profile.Profile, error) {
321+
func resolveNoArgsToken(ctx context.Context, profiler profile.Profiler, authArgs *auth.AuthArguments, tokenCache storage.Store, mode storage.StorageMode) (string, *profile.Profile, error) {
322322
// Step 1: Try DATABRICKS_HOST env var (highest priority).
323323
if envHost := env.Get(ctx, "DATABRICKS_HOST"); envHost != "" {
324324
authArgs.Host = envHost
@@ -394,7 +394,7 @@ func resolveNoArgsToken(ctx context.Context, profiler profile.Profiler, authArgs
394394
// runInlineLogin runs a minimal interactive login flow: prompts for a profile
395395
// name and host, performs the OAuth challenge, saves the profile to
396396
// .databrickscfg, and returns the new profile name and profile.
397-
func runInlineLogin(ctx context.Context, profiler profile.Profiler, tokenCache cache.TokenCache, mode storage.StorageMode) (string, *profile.Profile, error) {
397+
func runInlineLogin(ctx context.Context, profiler profile.Profiler, tokenCache storage.Store, mode storage.StorageMode) (string, *profile.Profile, error) {
398398
profileName, err := promptForProfile(ctx, "DEFAULT")
399399
if err != nil {
400400
return "", nil, err
@@ -428,7 +428,7 @@ func runInlineLogin(ctx context.Context, profiler profile.Profiler, tokenCache c
428428
persistentAuthOpts := []u2m.PersistentAuthOption{
429429
u2m.WithOAuthArgument(oauthArgument),
430430
u2m.WithBrowser(func(url string) error { return browser.Open(ctx, url) }),
431-
u2m.WithTokenCache(storage.WrapForOAuthArgument(tokenCache, mode, oauthArgument)),
431+
u2m.WithTokenCache(storage.WrapForOAuthArgument(ctx, tokenCache, mode, oauthArgument)),
432432
}
433433
if len(scopesList) > 0 {
434434
persistentAuthOpts = append(persistentAuthOpts, u2m.WithScopes(scopesList))

0 commit comments

Comments
 (0)