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
42 changes: 26 additions & 16 deletions cmd/auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,27 +311,17 @@ a new profile is created.
// 2. Configuring cluster and serverless;
// 3. Saving the profile.

// If discovery gave us an account_id but we still have no workspace_id,
// prompt the user to select a workspace. This applies to any host where
// .well-known/databricks-config returned an account_id.
shouldPromptWorkspace := authArguments.AccountID != "" &&
authArguments.WorkspaceID == "" &&
!skipWorkspace

if skipWorkspace && authArguments.WorkspaceID == "" {
authArguments.WorkspaceID = auth.WorkspaceIDNone
}

if shouldPromptWorkspace {
if shouldPromptWorkspace(authArguments, existingProfile, skipWorkspace) {
wsID, wsErr := promptForWorkspaceSelection(ctx, authArguments, persistentAuth)
if wsErr != nil {
log.Warnf(ctx, "Workspace selection failed: %v", wsErr)
} else if wsID == "" {
// User selected "Skip" from the prompt.
authArguments.WorkspaceID = auth.WorkspaceIDNone
} else {
} else if wsID != "" {
authArguments.WorkspaceID = wsID
}
// If wsID is empty, the user picked "Skip" — leave WorkspaceID empty.
// SaveToProfile omits the workspace_id key entirely for account-level
// profiles; MatchAccountProfiles treats absent workspace_id the same
// as the legacy "none" sentinel.
}

var clusterID, serverlessComputeID string
Expand Down Expand Up @@ -400,6 +390,26 @@ a new profile is created.
return cmd
}

// shouldPromptWorkspace reports whether the login flow should ask the user to
// pick a workspace. We prompt when we have an account_id but no workspace_id
// and the user did not pass --skip-workspace, with one exception: re-login
// into an existing profile that's already account-only for the SAME account
// (account_id matches and workspace_id is absent or the legacy "none"
// sentinel) honors the user's prior "skip" choice instead of re-prompting on
// every login. We require the account_id to match so reusing a profile name
// against a different account still gets the workspace prompt.
func shouldPromptWorkspace(authArguments *auth.AuthArguments, existingProfile *profile.Profile, skipWorkspace bool) bool {
if authArguments.AccountID == "" || authArguments.WorkspaceID != "" || skipWorkspace {
return false
}
if existingProfile != nil &&
existingProfile.AccountID == authArguments.AccountID &&
(existingProfile.WorkspaceID == "" || existingProfile.WorkspaceID == auth.WorkspaceIDNone) {
return false
}
return true
}

// Sets the host in the persistentAuth object based on the provided arguments and flags.
// Follows the following precedence:
// 1. [HOST] (first positional argument) or --host flag. Error if both are specified.
Expand Down
69 changes: 69 additions & 0 deletions cmd/auth/login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,75 @@ func TestSetHostAndAccountId_WorkspaceIDNoneSentinelInherited(t *testing.T) {
assert.Equal(t, auth.WorkspaceIDNone, args.WorkspaceID)
}

func TestShouldPromptWorkspace(t *testing.T) {
t.Setenv("DATABRICKS_CONFIG_FILE", "./testdata/.databrickscfg")
ctx, _ := cmdio.SetupTest(t.Context(), cmdio.TestOptions{})

legacyAccountProfile := loadTestProfile(t, ctx, "spog-skip-workspace")
newAccountProfile := loadTestProfile(t, ctx, "spog-skip-workspace-new")
workspaceProfile := loadTestProfile(t, ctx, "unified-workspace")

tests := []struct {
name string
authArguments auth.AuthArguments
existingProfile *profile.Profile
skipWorkspace bool
want bool
}{
{
name: "no profile, account_id set, no workspace_id",
authArguments: auth.AuthArguments{AccountID: "acc"},
want: true,
},
{
name: "re-login into legacy account-only profile (workspace_id = none)",
authArguments: auth.AuthArguments{AccountID: "spog-account"},
existingProfile: legacyAccountProfile,
want: false,
},
{
name: "re-login into new account-only profile (no workspace_id key)",
authArguments: auth.AuthArguments{AccountID: "spog-account"},
existingProfile: newAccountProfile,
want: false,
},
{
name: "re-login into workspace profile prompts when workspace_id missing from args",
authArguments: auth.AuthArguments{AccountID: "test-unified-account"},
existingProfile: workspaceProfile,
want: true,
},
{
name: "account-only profile for a different account still prompts",
authArguments: auth.AuthArguments{AccountID: "different-account"},
existingProfile: legacyAccountProfile,
want: true,
},
{
name: "skipWorkspace suppresses the prompt",
authArguments: auth.AuthArguments{AccountID: "acc"},
skipWorkspace: true,
want: false,
},
{
name: "no account_id means no prompt",
authArguments: auth.AuthArguments{},
want: false,
},
{
name: "workspace_id already known means no prompt",
authArguments: auth.AuthArguments{AccountID: "acc", WorkspaceID: "12345"},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := shouldPromptWorkspace(&tt.authArguments, tt.existingProfile, tt.skipWorkspace)
assert.Equal(t, tt.want, got)
})
}
}

func TestSetHostAndAccountId_URLParamsOverrideProfile(t *testing.T) {
t.Setenv("DATABRICKS_CONFIG_FILE", "./testdata/.databrickscfg")
ctx, _ := cmdio.SetupTest(t.Context(), cmdio.TestOptions{})
Expand Down
4 changes: 4 additions & 0 deletions cmd/auth/testdata/.databrickscfg
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ experimental_is_unified_host = true
host = https://spog.example.com
account_id = spog-account
workspace_id = none

[spog-skip-workspace-new]
host = https://spog.example.com
account_id = spog-account
Loading