From 501a39c3c3bef785fc420ee09ecb50d5d3757d26 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 Mar 2026 22:32:53 +0100 Subject: [PATCH 01/16] Add shared resolvePositionalArg for auth commands --- cmd/auth/resolve.go | 37 ++++++++++++++++ cmd/auth/resolve_test.go | 96 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 cmd/auth/resolve.go create mode 100644 cmd/auth/resolve_test.go diff --git a/cmd/auth/resolve.go b/cmd/auth/resolve.go new file mode 100644 index 0000000000..4fb35ce80f --- /dev/null +++ b/cmd/auth/resolve.go @@ -0,0 +1,37 @@ +package auth + +import ( + "context" + "fmt" + "strings" + + "github.com/databricks/cli/libs/databrickscfg/profile" +) + +// looksLikeHost returns true if the argument looks like a host URL rather than +// a profile name. Profile names are short identifiers (e.g., "logfood", +// "DEFAULT"), while host URLs contain dots or start with "http". +func looksLikeHost(arg string) bool { + return strings.Contains(arg, ".") || strings.HasPrefix(arg, "http") +} + +// resolvePositionalArg resolves a positional argument to either a profile name +// or a host. It tries the argument as a profile name first. If no profile +// matches and the argument looks like a host URL, it returns it as a host. If +// no profile matches and the argument does not look like a host, it returns an +// error. +func resolvePositionalArg(ctx context.Context, arg string, profiler profile.Profiler) (profileName string, host string, err error) { + candidateProfile, err := loadProfileByName(ctx, arg, profiler) + if err != nil { + return "", "", err + } + if candidateProfile != nil { + return arg, "", nil + } + + if looksLikeHost(arg) { + return "", arg, nil + } + + return "", "", fmt.Errorf("no profile named %q found", arg) +} diff --git a/cmd/auth/resolve_test.go b/cmd/auth/resolve_test.go new file mode 100644 index 0000000000..b6135d4689 --- /dev/null +++ b/cmd/auth/resolve_test.go @@ -0,0 +1,96 @@ +package auth + +import ( + "testing" + + "github.com/databricks/cli/libs/cmdio" + "github.com/databricks/cli/libs/databrickscfg/profile" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestResolvePositionalArgMatchesProfile(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{ + {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, + }, + } + + profileName, host, err := resolvePositionalArg(ctx, "logfood", profiler) + require.NoError(t, err) + assert.Equal(t, "logfood", profileName) + assert.Empty(t, host) +} + +func TestResolvePositionalArgFallsBackToHost(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{ + {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, + }, + } + + profileName, host, err := resolvePositionalArg(ctx, "https://other.cloud.databricks.com", profiler) + require.NoError(t, err) + assert.Empty(t, profileName) + assert.Equal(t, "https://other.cloud.databricks.com", host) +} + +func TestResolvePositionalArgFallsBackToHostWithDot(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{}, + } + + profileName, host, err := resolvePositionalArg(ctx, "my-workspace.cloud.databricks.com", profiler) + require.NoError(t, err) + assert.Empty(t, profileName) + assert.Equal(t, "my-workspace.cloud.databricks.com", host) +} + +func TestResolvePositionalArgErrorsForNonHostNonProfile(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{ + {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, + }, + } + + _, _, err := resolvePositionalArg(ctx, "e2-logfood", profiler) + assert.ErrorContains(t, err, `no profile named "e2-logfood" found`) +} + +func TestResolvePositionalArgHttpPrefix(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{}, + } + + profileName, host, err := resolvePositionalArg(ctx, "http://localhost:8080", profiler) + require.NoError(t, err) + assert.Empty(t, profileName) + assert.Equal(t, "http://localhost:8080", host) +} + +func TestResolvePositionalArgEmptyProfiles(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{} + + _, _, err := resolvePositionalArg(ctx, "myprofile", profiler) + assert.ErrorContains(t, err, `no profile named "myprofile" found`) +} + +func TestResolvePositionalArgProfileWithDotInName(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{ + {Name: "default.dev", Host: "https://dev.cloud.databricks.com"}, + }, + } + + profileName, host, err := resolvePositionalArg(ctx, "default.dev", profiler) + require.NoError(t, err) + assert.Equal(t, "default.dev", profileName) + assert.Empty(t, host) +} From 6f0541cf6d55e6ba115de902d0415fd03176df33 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 Mar 2026 22:44:17 +0100 Subject: [PATCH 02/16] Refactor resolve tests to table-driven, tighten looksLikeHost check Co-authored-by: Isaac --- cmd/auth/resolve.go | 2 +- cmd/auth/resolve_test.go | 152 +++++++++++++++++++-------------------- 2 files changed, 75 insertions(+), 79 deletions(-) diff --git a/cmd/auth/resolve.go b/cmd/auth/resolve.go index 4fb35ce80f..cf3a97646b 100644 --- a/cmd/auth/resolve.go +++ b/cmd/auth/resolve.go @@ -12,7 +12,7 @@ import ( // a profile name. Profile names are short identifiers (e.g., "logfood", // "DEFAULT"), while host URLs contain dots or start with "http". func looksLikeHost(arg string) bool { - return strings.Contains(arg, ".") || strings.HasPrefix(arg, "http") + return strings.Contains(arg, ".") || strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") } // resolvePositionalArg resolves a positional argument to either a profile name diff --git a/cmd/auth/resolve_test.go b/cmd/auth/resolve_test.go index b6135d4689..7a4a4e5441 100644 --- a/cmd/auth/resolve_test.go +++ b/cmd/auth/resolve_test.go @@ -9,88 +9,84 @@ import ( "github.com/stretchr/testify/require" ) -func TestResolvePositionalArgMatchesProfile(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{ - {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, +func TestResolvePositionalArg(t *testing.T) { + cases := []struct { + name string + profiles profile.Profiles + arg string + wantProfile string + wantHost string + wantErr string + }{ + { + name: "matches profile", + profiles: profile.Profiles{ + {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, + }, + arg: "logfood", + wantProfile: "logfood", + wantHost: "", }, - } - - profileName, host, err := resolvePositionalArg(ctx, "logfood", profiler) - require.NoError(t, err) - assert.Equal(t, "logfood", profileName) - assert.Empty(t, host) -} - -func TestResolvePositionalArgFallsBackToHost(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{ - {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, + { + name: "falls back to https host", + profiles: profile.Profiles{ + {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, + }, + arg: "https://other.cloud.databricks.com", + wantProfile: "", + wantHost: "https://other.cloud.databricks.com", }, - } - - profileName, host, err := resolvePositionalArg(ctx, "https://other.cloud.databricks.com", profiler) - require.NoError(t, err) - assert.Empty(t, profileName) - assert.Equal(t, "https://other.cloud.databricks.com", host) -} - -func TestResolvePositionalArgFallsBackToHostWithDot(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{}, - } - - profileName, host, err := resolvePositionalArg(ctx, "my-workspace.cloud.databricks.com", profiler) - require.NoError(t, err) - assert.Empty(t, profileName) - assert.Equal(t, "my-workspace.cloud.databricks.com", host) -} - -func TestResolvePositionalArgErrorsForNonHostNonProfile(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{ - {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, + { + name: "falls back to host with dot", + profiles: profile.Profiles{}, + arg: "my-workspace.cloud.databricks.com", + wantProfile: "", + wantHost: "my-workspace.cloud.databricks.com", }, - } - - _, _, err := resolvePositionalArg(ctx, "e2-logfood", profiler) - assert.ErrorContains(t, err, `no profile named "e2-logfood" found`) -} - -func TestResolvePositionalArgHttpPrefix(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{}, - } - - profileName, host, err := resolvePositionalArg(ctx, "http://localhost:8080", profiler) - require.NoError(t, err) - assert.Empty(t, profileName) - assert.Equal(t, "http://localhost:8080", host) -} - -func TestResolvePositionalArgEmptyProfiles(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{} - - _, _, err := resolvePositionalArg(ctx, "myprofile", profiler) - assert.ErrorContains(t, err, `no profile named "myprofile" found`) -} - -func TestResolvePositionalArgProfileWithDotInName(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{ - {Name: "default.dev", Host: "https://dev.cloud.databricks.com"}, + { + name: "errors for non-host non-profile", + profiles: profile.Profiles{ + {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, + }, + arg: "e2-logfood", + wantErr: `no profile named "e2-logfood" found`, + }, + { + name: "http prefix", + profiles: profile.Profiles{}, + arg: "http://localhost:8080", + wantProfile: "", + wantHost: "http://localhost:8080", + }, + { + name: "empty profiles error", + profiles: profile.Profiles{}, + arg: "myprofile", + wantErr: `no profile named "myprofile" found`, + }, + { + name: "profile with dot in name", + profiles: profile.Profiles{ + {Name: "default.dev", Host: "https://dev.cloud.databricks.com"}, + }, + arg: "default.dev", + wantProfile: "default.dev", + wantHost: "", }, } - profileName, host, err := resolvePositionalArg(ctx, "default.dev", profiler) - require.NoError(t, err) - assert.Equal(t, "default.dev", profileName) - assert.Empty(t, host) + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{Profiles: tc.profiles} + profileName, host, err := resolvePositionalArg(ctx, tc.arg, profiler) + if tc.wantErr != "" { + assert.ErrorContains(t, err, tc.wantErr) + return + } + require.NoError(t, err) + assert.Equal(t, tc.wantProfile, profileName) + assert.Equal(t, tc.wantHost, host) + }) + } } From a56e29e89aeb1194734c796652674b6e92e15d68 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 Mar 2026 22:47:20 +0100 Subject: [PATCH 03/16] auth login: treat positional arg as profile name first Co-authored-by: Isaac --- cmd/auth/login.go | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/cmd/auth/login.go b/cmd/auth/login.go index fd03f02c35..536f887403 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -94,7 +94,7 @@ func newLoginCommand(authArguments *auth.AuthArguments) *cobra.Command { defaultConfigPath = "%USERPROFILE%\\.databrickscfg" } cmd := &cobra.Command{ - Use: "login [HOST]", + Use: "login [PROFILE_OR_HOST]", Short: "Log into a Databricks workspace or account", Long: fmt.Sprintf(`Log into a Databricks workspace or account. @@ -109,9 +109,9 @@ information, see: If no host is provided, the CLI opens login.databricks.com where you can authenticate and select a workspace. -The host can be provided as a positional argument, via --host, or from an -existing profile. The host URL may include query parameters to set the -workspace and account ID: +The positional argument is resolved as a profile name first. If no profile with +that name exists and the argument looks like a URL, it is used as a host. The +host URL may include query parameters to set the workspace and account ID: databricks auth login --host "https://?o=&account_id=" @@ -149,6 +149,24 @@ a new profile is created. return errors.New("please either configure serverless or cluster, not both") } + // Resolve positional argument as profile or host. + if len(args) > 0 && authArguments.Host != "" { + return errors.New("please only provide a host as an argument or a flag, not both") + } + if profileName == "" && len(args) == 1 { + resolvedProfile, resolvedHost, err := resolvePositionalArg(ctx, args[0], profile.DefaultProfiler) + if err != nil { + return err + } + if resolvedProfile != "" { + profileName = resolvedProfile + args = nil + } else { + authArguments.Host = resolvedHost + args = nil + } + } + // If the user has not specified a profile name, prompt for one. if profileName == "" { var err error From 487244ebd0ef058e35de00204ddfa78920dbd9e7 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 Mar 2026 22:48:06 +0100 Subject: [PATCH 04/16] auth token: use resolvePositionalArg for better error messages Co-authored-by: Isaac --- cmd/auth/token.go | 13 ++++++++----- cmd/auth/token_test.go | 15 +++++++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/cmd/auth/token.go b/cmd/auth/token.go index 50549b4ac1..66d3f601fc 100644 --- a/cmd/auth/token.go +++ b/cmd/auth/token.go @@ -156,15 +156,18 @@ func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) { args.profileName = env.Get(ctx, "DATABRICKS_CONFIG_PROFILE") } - // If no --profile flag, try resolving the positional arg as a profile name. - // If it matches, use it. If not, fall through to host treatment. + // If no --profile flag, resolve the positional arg as a profile name first, + // then as a host. Error if it matches neither. if args.profileName == "" && len(args.args) == 1 { - candidateProfile, err := loadProfileByName(ctx, args.args[0], args.profiler) + resolvedProfile, resolvedHost, err := resolvePositionalArg(ctx, args.args[0], args.profiler) if err != nil { return nil, err } - if candidateProfile != nil { - args.profileName = args.args[0] + if resolvedProfile != "" { + args.profileName = resolvedProfile + args.args = nil + } else { + args.authArguments.Host = resolvedHost args.args = nil } } diff --git a/cmd/auth/token_test.go b/cmd/auth/token_test.go index e9dc74eca0..bbde7bbbee 100644 --- a/cmd/auth/token_test.go +++ b/cmd/auth/token_test.go @@ -385,7 +385,7 @@ func TestToken_loadToken(t *testing.T) { args: loadTokenArgs{ authArguments: &auth.AuthArguments{}, profileName: "", - args: []string{"nonexistent"}, + args: []string{"nonexistent.cloud.databricks.com"}, tokenTimeout: 1 * time.Hour, profiler: profiler, persistentAuthOpts: []u2m.PersistentAuthOption{ @@ -394,9 +394,20 @@ func TestToken_loadToken(t *testing.T) { }, }, wantErr: "cache: databricks OAuth is not configured for this host. " + - "Try logging in again with `databricks auth login --host https://nonexistent` before retrying. " + + "Try logging in again with `databricks auth login --host https://nonexistent.cloud.databricks.com` before retrying. " + "If this fails, please report this issue to the Databricks CLI maintainers at https://github.com/databricks/cli/issues/new", }, + { + name: "errors with clear message for non-host non-profile positional arg", + args: loadTokenArgs{ + authArguments: &auth.AuthArguments{}, + profileName: "", + args: []string{"e2-logfood"}, + tokenTimeout: 1 * time.Hour, + profiler: profiler, + }, + wantErr: `no profile named "e2-logfood" found`, + }, { name: "scheme-less account host ambiguity detected correctly", args: loadTokenArgs{ From 9037a5cd3fa62c3d195537d440db601c701d2c58 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 Mar 2026 22:50:40 +0100 Subject: [PATCH 05/16] auth logout: use shared resolvePositionalArg, remove local --profile Co-authored-by: Isaac --- cmd/auth/logout.go | 74 ++++++++++------------------------------- cmd/auth/logout_test.go | 40 +++++++++------------- cmd/auth/resolve.go | 42 +++++++++++++++++++++++ 3 files changed, 74 insertions(+), 82 deletions(-) diff --git a/cmd/auth/logout.go b/cmd/auth/logout.go index 864ac34033..254e451e6f 100644 --- a/cmd/auth/logout.go +++ b/cmd/auth/logout.go @@ -28,7 +28,7 @@ You will need to run {{ "databricks auth login" | bold }} to re-authenticate. func newLogoutCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "logout [PROFILE]", + Use: "logout [PROFILE_OR_HOST]", Short: "Log out of a Databricks profile", Args: cobra.MaximumNArgs(1), Hidden: true, @@ -66,26 +66,37 @@ the profile is an error. } var force bool - var profileName string var deleteProfile bool cmd.Flags().BoolVar(&force, "force", false, "Skip confirmation prompt") - cmd.Flags().StringVar(&profileName, "profile", "", "The profile to log out of") cmd.Flags().BoolVar(&deleteProfile, "delete", false, "Delete the profile from the config file") cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() profiler := profile.DefaultProfiler + profileName := "" + profileFlag := cmd.Flag("profile") + if profileFlag != nil { + profileName = profileFlag.Value.String() + } + // Resolve the positional argument to a profile name. - if profileName != "" && len(args) == 1 { + if profileFlag != nil && profileFlag.Changed && len(args) == 1 { return errors.New("providing both --profile and a positional argument is not supported") } if profileName == "" && len(args) == 1 { - resolved, err := resolveLogoutArg(ctx, args[0], profiler) + resolvedProfile, resolvedHost, err := resolvePositionalArg(ctx, args[0], profiler) if err != nil { return err } - profileName = resolved + if resolvedProfile != "" { + profileName = resolvedProfile + } else { + profileName, err = resolveHostToProfile(ctx, resolvedHost, profiler) + if err != nil { + return err + } + } } if profileName == "" { @@ -290,54 +301,3 @@ func hostCacheKeyAndMatchFn(p profile.Profile) (string, profile.ProfileMatchFunc return host, profile.WithHost(host) } -// resolveLogoutArg resolves a positional argument to a profile name. It first -// tries to match the argument as a profile name, then as a host URL. If the -// host matches multiple profiles in a non-interactive context, it returns an -// error listing the matching profile names. -func resolveLogoutArg(ctx context.Context, arg string, profiler profile.Profiler) (string, error) { - // Try as profile name first. - candidateProfile, err := loadProfileByName(ctx, arg, profiler) - if err != nil { - return "", err - } - if candidateProfile != nil { - return arg, nil - } - - // Try as host URL. - canonicalHost := (&config.Config{Host: arg}).CanonicalHostName() - hostProfiles, err := profiler.LoadProfiles(ctx, profile.WithHost(canonicalHost)) - if err != nil { - return "", err - } - - switch len(hostProfiles) { - case 1: - return hostProfiles[0].Name, nil - case 0: - allProfiles, err := profiler.LoadProfiles(ctx, profile.MatchAllProfiles) - if err != nil { - return "", fmt.Errorf("no profile found matching %q", arg) - } - names := strings.Join(allProfiles.Names(), ", ") - return "", fmt.Errorf("no profile found matching %q. Available profiles: %s", arg, names) - default: - // Multiple profiles match the host. - if cmdio.IsPromptSupported(ctx) { - selected, err := profile.SelectProfile(ctx, profile.SelectConfig{ - Label: fmt.Sprintf("Multiple profiles found for %q. Select one to log out of", arg), - Profiles: hostProfiles, - StartInSearchMode: len(hostProfiles) > 5, - ActiveTemplate: `▸ {{.PaddedName | bold}}{{if .AccountID}} (account: {{.AccountID}}){{else}} ({{.Host}}){{end}}`, - InactiveTemplate: ` {{.PaddedName}}{{if .AccountID}} (account: {{.AccountID | faint}}){{else}} ({{.Host | faint}}){{end}}`, - SelectedTemplate: `{{ "Selected profile" | faint }}: {{ .Name | bold }}`, - }) - if err != nil { - return "", err - } - return selected, nil - } - names := strings.Join(hostProfiles.Names(), ", ") - return "", fmt.Errorf("multiple profiles found matching host %q: %s. Please specify the profile name directly", arg, names) - } -} diff --git a/cmd/auth/logout_test.go b/cmd/auth/logout_test.go index 7468a0779b..467a2e709c 100644 --- a/cmd/auth/logout_test.go +++ b/cmd/auth/logout_test.go @@ -8,6 +8,7 @@ import ( "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/databrickscfg" "github.com/databricks/cli/libs/databrickscfg/profile" + "github.com/spf13/cobra" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/oauth2" @@ -262,7 +263,7 @@ func TestLogoutNoTokensWithDelete(t *testing.T) { assert.Empty(t, profiles) } -func TestLogoutResolveArgMatchesProfileName(t *testing.T) { +func TestResolveHostToProfileMatchesOneProfile(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) profiler := profile.InMemoryProfiler{ Profiles: profile.Profiles{ @@ -271,26 +272,12 @@ func TestLogoutResolveArgMatchesProfileName(t *testing.T) { }, } - resolved, err := resolveLogoutArg(ctx, "dev", profiler) + resolved, err := resolveHostToProfile(ctx, "https://dev.cloud.databricks.com", profiler) require.NoError(t, err) assert.Equal(t, "dev", resolved) } -func TestLogoutResolveArgMatchesHostWithOneProfile(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{ - {Name: "dev", Host: "https://dev.cloud.databricks.com", AuthType: "databricks-cli"}, - {Name: "staging", Host: "https://staging.cloud.databricks.com", AuthType: "databricks-cli"}, - }, - } - - resolved, err := resolveLogoutArg(ctx, "https://dev.cloud.databricks.com", profiler) - require.NoError(t, err) - assert.Equal(t, "dev", resolved) -} - -func TestLogoutResolveArgMatchesHostWithMultipleProfiles(t *testing.T) { +func TestResolveHostToProfileMatchesMultipleProfiles(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) profiler := profile.InMemoryProfiler{ Profiles: profile.Profiles{ @@ -299,13 +286,13 @@ func TestLogoutResolveArgMatchesHostWithMultipleProfiles(t *testing.T) { }, } - _, err := resolveLogoutArg(ctx, "https://shared.cloud.databricks.com", profiler) + _, err := resolveHostToProfile(ctx, "https://shared.cloud.databricks.com", profiler) assert.ErrorContains(t, err, "multiple profiles found matching host") assert.ErrorContains(t, err, "dev1") assert.ErrorContains(t, err, "dev2") } -func TestLogoutResolveArgMatchesNothing(t *testing.T) { +func TestResolveHostToProfileMatchesNothing(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) profiler := profile.InMemoryProfiler{ Profiles: profile.Profiles{ @@ -314,13 +301,13 @@ func TestLogoutResolveArgMatchesNothing(t *testing.T) { }, } - _, err := resolveLogoutArg(ctx, "https://unknown.cloud.databricks.com", profiler) - assert.ErrorContains(t, err, `no profile found matching "https://unknown.cloud.databricks.com"`) + _, err := resolveHostToProfile(ctx, "https://unknown.cloud.databricks.com", profiler) + assert.ErrorContains(t, err, `no profile found matching host "https://unknown.cloud.databricks.com"`) assert.ErrorContains(t, err, "dev") assert.ErrorContains(t, err, "staging") } -func TestLogoutResolveArgCanonicalizesHost(t *testing.T) { +func TestResolveHostToProfileCanonicalizesHost(t *testing.T) { profiler := profile.InMemoryProfiler{ Profiles: profile.Profiles{ {Name: "dev", Host: "https://dev.cloud.databricks.com", AuthType: "databricks-cli"}, @@ -339,7 +326,7 @@ func TestLogoutResolveArgCanonicalizesHost(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) - resolved, err := resolveLogoutArg(ctx, tc.arg, profiler) + resolved, err := resolveHostToProfile(ctx, tc.arg, profiler) require.NoError(t, err) assert.Equal(t, "dev", resolved) }) @@ -347,9 +334,12 @@ func TestLogoutResolveArgCanonicalizesHost(t *testing.T) { } func TestLogoutProfileFlagAndPositionalArgConflict(t *testing.T) { + parent := &cobra.Command{Use: "root"} + parent.PersistentFlags().StringP("profile", "p", "", "~/.databrickscfg profile") cmd := newLogoutCommand() - cmd.SetArgs([]string{"myprofile", "--profile", "other"}) - err := cmd.Execute() + parent.AddCommand(cmd) + parent.SetArgs([]string{"logout", "myprofile", "--profile", "other"}) + err := parent.Execute() assert.ErrorContains(t, err, "providing both --profile and a positional argument is not supported") } diff --git a/cmd/auth/resolve.go b/cmd/auth/resolve.go index cf3a97646b..a6fc867dea 100644 --- a/cmd/auth/resolve.go +++ b/cmd/auth/resolve.go @@ -5,7 +5,9 @@ import ( "fmt" "strings" + "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/databrickscfg/profile" + "github.com/databricks/databricks-sdk-go/config" ) // looksLikeHost returns true if the argument looks like a host URL rather than @@ -35,3 +37,43 @@ func resolvePositionalArg(ctx context.Context, arg string, profiler profile.Prof return "", "", fmt.Errorf("no profile named %q found", arg) } + +// resolveHostToProfile resolves a host URL to a profile name. If multiple +// profiles match the host, it prompts the user to select one (or errors in +// non-interactive mode). If no profiles match, it returns an error. +func resolveHostToProfile(ctx context.Context, host string, profiler profile.Profiler) (string, error) { + canonicalHost := (&config.Config{Host: host}).CanonicalHostName() + hostProfiles, err := profiler.LoadProfiles(ctx, profile.WithHost(canonicalHost)) + if err != nil { + return "", err + } + + switch len(hostProfiles) { + case 1: + return hostProfiles[0].Name, nil + case 0: + allProfiles, err := profiler.LoadProfiles(ctx, profile.MatchAllProfiles) + if err != nil { + return "", fmt.Errorf("no profile found matching host %q", host) + } + names := strings.Join(allProfiles.Names(), ", ") + return "", fmt.Errorf("no profile found matching host %q. Available profiles: %s", host, names) + default: + if cmdio.IsPromptSupported(ctx) { + selected, err := profile.SelectProfile(ctx, profile.SelectConfig{ + Label: fmt.Sprintf("Multiple profiles found for %q. Select one", host), + Profiles: hostProfiles, + StartInSearchMode: len(hostProfiles) > 5, + ActiveTemplate: "▸ {{.PaddedName | bold}}{{if .AccountID}} (account: {{.AccountID}}){{else}} ({{.Host}}){{end}}", + InactiveTemplate: " {{.PaddedName}}{{if .AccountID}} (account: {{.AccountID | faint}}){{else}} ({{.Host | faint}}){{end}}", + SelectedTemplate: `{{ "Selected profile" | faint }}: {{ .Name | bold }}`, + }) + if err != nil { + return "", err + } + return selected, nil + } + names := strings.Join(hostProfiles.Names(), ", ") + return "", fmt.Errorf("multiple profiles found matching host %q: %s. Please specify the profile name directly", host, names) + } +} From 5d92ceb70f38acf137be21ffb00ec65dcad2eb60 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 Mar 2026 22:55:27 +0100 Subject: [PATCH 06/16] Reword login error message, co-locate resolveHostToProfile tests Co-authored-by: Isaac --- cmd/auth/login.go | 2 +- cmd/auth/logout_test.go | 70 ---------------------------------------- cmd/auth/resolve_test.go | 70 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 71 deletions(-) diff --git a/cmd/auth/login.go b/cmd/auth/login.go index 536f887403..273a89ea96 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -151,7 +151,7 @@ a new profile is created. // Resolve positional argument as profile or host. if len(args) > 0 && authArguments.Host != "" { - return errors.New("please only provide a host as an argument or a flag, not both") + return errors.New("please only provide a positional argument or --host, not both") } if profileName == "" && len(args) == 1 { resolvedProfile, resolvedHost, err := resolvePositionalArg(ctx, args[0], profile.DefaultProfiler) diff --git a/cmd/auth/logout_test.go b/cmd/auth/logout_test.go index 467a2e709c..2c0aa5abf9 100644 --- a/cmd/auth/logout_test.go +++ b/cmd/auth/logout_test.go @@ -263,76 +263,6 @@ func TestLogoutNoTokensWithDelete(t *testing.T) { assert.Empty(t, profiles) } -func TestResolveHostToProfileMatchesOneProfile(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{ - {Name: "dev", Host: "https://dev.cloud.databricks.com", AuthType: "databricks-cli"}, - {Name: "staging", Host: "https://staging.cloud.databricks.com", AuthType: "databricks-cli"}, - }, - } - - resolved, err := resolveHostToProfile(ctx, "https://dev.cloud.databricks.com", profiler) - require.NoError(t, err) - assert.Equal(t, "dev", resolved) -} - -func TestResolveHostToProfileMatchesMultipleProfiles(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{ - {Name: "dev1", Host: "https://shared.cloud.databricks.com", AuthType: "databricks-cli"}, - {Name: "dev2", Host: "https://shared.cloud.databricks.com", AuthType: "databricks-cli"}, - }, - } - - _, err := resolveHostToProfile(ctx, "https://shared.cloud.databricks.com", profiler) - assert.ErrorContains(t, err, "multiple profiles found matching host") - assert.ErrorContains(t, err, "dev1") - assert.ErrorContains(t, err, "dev2") -} - -func TestResolveHostToProfileMatchesNothing(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{ - {Name: "dev", Host: "https://dev.cloud.databricks.com", AuthType: "databricks-cli"}, - {Name: "staging", Host: "https://staging.cloud.databricks.com", AuthType: "databricks-cli"}, - }, - } - - _, err := resolveHostToProfile(ctx, "https://unknown.cloud.databricks.com", profiler) - assert.ErrorContains(t, err, `no profile found matching host "https://unknown.cloud.databricks.com"`) - assert.ErrorContains(t, err, "dev") - assert.ErrorContains(t, err, "staging") -} - -func TestResolveHostToProfileCanonicalizesHost(t *testing.T) { - profiler := profile.InMemoryProfiler{ - Profiles: profile.Profiles{ - {Name: "dev", Host: "https://dev.cloud.databricks.com", AuthType: "databricks-cli"}, - }, - } - - cases := []struct { - name string - arg string - }{ - {name: "canonical URL", arg: "https://dev.cloud.databricks.com"}, - {name: "trailing slash", arg: "https://dev.cloud.databricks.com/"}, - {name: "no scheme", arg: "dev.cloud.databricks.com"}, - } - - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - ctx := cmdio.MockDiscard(t.Context()) - resolved, err := resolveHostToProfile(ctx, tc.arg, profiler) - require.NoError(t, err) - assert.Equal(t, "dev", resolved) - }) - } -} - func TestLogoutProfileFlagAndPositionalArgConflict(t *testing.T) { parent := &cobra.Command{Use: "root"} parent.PersistentFlags().StringP("profile", "p", "", "~/.databrickscfg profile") diff --git a/cmd/auth/resolve_test.go b/cmd/auth/resolve_test.go index 7a4a4e5441..d47d1d2ab1 100644 --- a/cmd/auth/resolve_test.go +++ b/cmd/auth/resolve_test.go @@ -90,3 +90,73 @@ func TestResolvePositionalArg(t *testing.T) { }) } } + +func TestResolveHostToProfileMatchesOneProfile(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{ + {Name: "dev", Host: "https://dev.cloud.databricks.com", AuthType: "databricks-cli"}, + {Name: "staging", Host: "https://staging.cloud.databricks.com", AuthType: "databricks-cli"}, + }, + } + + resolved, err := resolveHostToProfile(ctx, "https://dev.cloud.databricks.com", profiler) + require.NoError(t, err) + assert.Equal(t, "dev", resolved) +} + +func TestResolveHostToProfileMatchesMultipleProfiles(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{ + {Name: "dev1", Host: "https://shared.cloud.databricks.com", AuthType: "databricks-cli"}, + {Name: "dev2", Host: "https://shared.cloud.databricks.com", AuthType: "databricks-cli"}, + }, + } + + _, err := resolveHostToProfile(ctx, "https://shared.cloud.databricks.com", profiler) + assert.ErrorContains(t, err, "multiple profiles found matching host") + assert.ErrorContains(t, err, "dev1") + assert.ErrorContains(t, err, "dev2") +} + +func TestResolveHostToProfileMatchesNothing(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{ + {Name: "dev", Host: "https://dev.cloud.databricks.com", AuthType: "databricks-cli"}, + {Name: "staging", Host: "https://staging.cloud.databricks.com", AuthType: "databricks-cli"}, + }, + } + + _, err := resolveHostToProfile(ctx, "https://unknown.cloud.databricks.com", profiler) + assert.ErrorContains(t, err, `no profile found matching host "https://unknown.cloud.databricks.com"`) + assert.ErrorContains(t, err, "dev") + assert.ErrorContains(t, err, "staging") +} + +func TestResolveHostToProfileCanonicalizesHost(t *testing.T) { + profiler := profile.InMemoryProfiler{ + Profiles: profile.Profiles{ + {Name: "dev", Host: "https://dev.cloud.databricks.com", AuthType: "databricks-cli"}, + }, + } + + cases := []struct { + name string + arg string + }{ + {name: "canonical URL", arg: "https://dev.cloud.databricks.com"}, + {name: "trailing slash", arg: "https://dev.cloud.databricks.com/"}, + {name: "no scheme", arg: "dev.cloud.databricks.com"}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + resolved, err := resolveHostToProfile(ctx, tc.arg, profiler) + require.NoError(t, err) + assert.Equal(t, "dev", resolved) + }) + } +} From bd2673cf545211c54a8707642d18b9a84bd67434 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 Mar 2026 22:58:33 +0100 Subject: [PATCH 07/16] Fix whitespace and lint in logout.go and resolve.go Co-authored-by: Isaac --- cmd/auth/logout.go | 1 - cmd/auth/resolve.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cmd/auth/logout.go b/cmd/auth/logout.go index 254e451e6f..a6fcb551d4 100644 --- a/cmd/auth/logout.go +++ b/cmd/auth/logout.go @@ -300,4 +300,3 @@ func hostCacheKeyAndMatchFn(p profile.Profile) (string, profile.ProfileMatchFunc return host, profile.WithHost(host) } - diff --git a/cmd/auth/resolve.go b/cmd/auth/resolve.go index a6fc867dea..95ca3ea3ef 100644 --- a/cmd/auth/resolve.go +++ b/cmd/auth/resolve.go @@ -22,7 +22,7 @@ func looksLikeHost(arg string) bool { // matches and the argument looks like a host URL, it returns it as a host. If // no profile matches and the argument does not look like a host, it returns an // error. -func resolvePositionalArg(ctx context.Context, arg string, profiler profile.Profiler) (profileName string, host string, err error) { +func resolvePositionalArg(ctx context.Context, arg string, profiler profile.Profiler) (profileName, host string, err error) { candidateProfile, err := loadProfileByName(ctx, arg, profiler) if err != nil { return "", "", err From 1bf93ffd11ee8e7a85aa37c0533dfe6dc74e2e86 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 25 Mar 2026 23:47:02 +0100 Subject: [PATCH 08/16] Fix review findings for auth positional profile resolution - Fix token command skipping resolver when DATABRICKS_CONFIG_PROFILE is set by moving positional arg resolution before the env var read - Add test for login's --host + positional argument conflict guard - Align token command's Use string to PROFILE_OR_HOST for consistency - Add host:port detection (e.g., localhost:8080) to looksLikeHost - Improve resolveHostToProfile prompt label to "Select one to use" Co-authored-by: Isaac --- cmd/auth/login_test.go | 11 +++++++++++ cmd/auth/resolve.go | 14 ++++++++++++-- cmd/auth/resolve_test.go | 7 +++++++ cmd/auth/token.go | 23 +++++++++++++---------- cmd/auth/token_test.go | 14 ++++++++++++++ 5 files changed, 57 insertions(+), 12 deletions(-) diff --git a/cmd/auth/login_test.go b/cmd/auth/login_test.go index 9b55ec5fe6..e442973bc7 100644 --- a/cmd/auth/login_test.go +++ b/cmd/auth/login_test.go @@ -992,3 +992,14 @@ auth_type = databricks-cli assert.Equal(t, "fresh-account", savedProfile.AccountID, "account_id should be saved from introspection") assert.Equal(t, "222222", savedProfile.WorkspaceID, "workspace_id should be updated to fresh introspection value") } + +func TestLoginRejectsHostFlagWithPositionalArg(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + authArgs := &auth.AuthArguments{Host: "https://example.com"} + cmd := newLoginCommand(authArgs) + cmd.Flags().String("profile", "", "") + cmd.SetContext(ctx) + cmd.SetArgs([]string{"myprofile"}) + err := cmd.Execute() + assert.ErrorContains(t, err, "please only provide a positional argument or --host, not both") +} diff --git a/cmd/auth/resolve.go b/cmd/auth/resolve.go index 95ca3ea3ef..7c0e477323 100644 --- a/cmd/auth/resolve.go +++ b/cmd/auth/resolve.go @@ -3,6 +3,7 @@ package auth import ( "context" "fmt" + "strconv" "strings" "github.com/databricks/cli/libs/cmdio" @@ -14,7 +15,16 @@ import ( // a profile name. Profile names are short identifiers (e.g., "logfood", // "DEFAULT"), while host URLs contain dots or start with "http". func looksLikeHost(arg string) bool { - return strings.Contains(arg, ".") || strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") + if strings.Contains(arg, ".") || strings.HasPrefix(arg, "http://") || strings.HasPrefix(arg, "https://") { + return true + } + // Match host:port pattern without dots or scheme (e.g., localhost:8080). + if i := strings.LastIndex(arg, ":"); i > 0 { + if _, err := strconv.Atoi(arg[i+1:]); err == nil { + return true + } + } + return false } // resolvePositionalArg resolves a positional argument to either a profile name @@ -61,7 +71,7 @@ func resolveHostToProfile(ctx context.Context, host string, profiler profile.Pro default: if cmdio.IsPromptSupported(ctx) { selected, err := profile.SelectProfile(ctx, profile.SelectConfig{ - Label: fmt.Sprintf("Multiple profiles found for %q. Select one", host), + Label: fmt.Sprintf("Multiple profiles found for %q. Select one to use", host), Profiles: hostProfiles, StartInSearchMode: len(hostProfiles) > 5, ActiveTemplate: "▸ {{.PaddedName | bold}}{{if .AccountID}} (account: {{.AccountID}}){{else}} ({{.Host}}){{end}}", diff --git a/cmd/auth/resolve_test.go b/cmd/auth/resolve_test.go index d47d1d2ab1..1180b22038 100644 --- a/cmd/auth/resolve_test.go +++ b/cmd/auth/resolve_test.go @@ -58,6 +58,13 @@ func TestResolvePositionalArg(t *testing.T) { wantProfile: "", wantHost: "http://localhost:8080", }, + { + name: "host:port without dots or scheme", + profiles: profile.Profiles{}, + arg: "localhost:8080", + wantProfile: "", + wantHost: "localhost:8080", + }, { name: "empty profiles error", profiles: profile.Profiles{}, diff --git a/cmd/auth/token.go b/cmd/auth/token.go index 66d3f601fc..f45fabee42 100644 --- a/cmd/auth/token.go +++ b/cmd/auth/token.go @@ -54,7 +54,7 @@ func applyUnifiedHostFlags(p *profile.Profile, args *auth.AuthArguments) { func newTokenCommand(authArguments *auth.AuthArguments) *cobra.Command { cmd := &cobra.Command{ - Use: "token [HOST_OR_PROFILE]", + Use: "token [PROFILE_OR_HOST]", Short: "Get authentication token", Long: `Get authentication token from the local cache in ~/.databricks/token-cache.json. Refresh the access token if it is expired or close to expiry. Use --force-refresh @@ -149,15 +149,10 @@ func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) { return nil, errors.New("providing both a profile and host is not supported") } - // When no explicit --profile flag is provided, check the env var. This - // handles the case where downstream tools (like the Terraform provider) - // pass --host but not --profile, while DATABRICKS_CONFIG_PROFILE is set. - if args.profileName == "" { - args.profileName = env.Get(ctx, "DATABRICKS_CONFIG_PROFILE") - } - - // If no --profile flag, resolve the positional arg as a profile name first, - // then as a host. Error if it matches neither. + // Resolve the positional arg as a profile name first, then as a host. + // Error if it matches neither. This runs before the DATABRICKS_CONFIG_PROFILE + // env var check so that an explicit positional argument always goes through + // profile-first resolution. if args.profileName == "" && len(args.args) == 1 { resolvedProfile, resolvedHost, err := resolvePositionalArg(ctx, args.args[0], args.profiler) if err != nil { @@ -172,6 +167,14 @@ func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) { } } + // When no explicit --profile flag or positional arg is provided, check the + // env var. This handles the case where downstream tools (like the Terraform + // provider) pass --host but not --profile, while DATABRICKS_CONFIG_PROFILE + // is set. + if args.profileName == "" { + args.profileName = env.Get(ctx, "DATABRICKS_CONFIG_PROFILE") + } + existingProfile, err := loadProfileByName(ctx, args.profileName, args.profiler) if err != nil { return nil, err diff --git a/cmd/auth/token_test.go b/cmd/auth/token_test.go index bbde7bbbee..d53cb87b94 100644 --- a/cmd/auth/token_test.go +++ b/cmd/auth/token_test.go @@ -707,6 +707,20 @@ func TestToken_loadToken(t *testing.T) { }, validateToken: validateToken, }, + { + name: "DATABRICKS_CONFIG_PROFILE with positional typo runs resolver first", + setupCtx: func(ctx context.Context) context.Context { + return env.Set(ctx, "DATABRICKS_CONFIG_PROFILE", "active") + }, + args: loadTokenArgs{ + authArguments: &auth.AuthArguments{}, + profileName: "", + args: []string{"e2-logfood"}, + tokenTimeout: 1 * time.Hour, + profiler: profiler, + }, + wantErr: `no profile named "e2-logfood" found`, + }, { name: "host flag with profile env var disambiguates multi-profile", setupCtx: func(ctx context.Context) context.Context { From 01e11679ff8522eb23ca3754766962be62f4315c Mon Sep 17 00:00:00 2001 From: simon Date: Thu, 26 Mar 2026 00:01:37 +0100 Subject: [PATCH 09/16] Add NEXT_CHANGELOG entry for auth positional profile resolution Co-authored-by: Isaac --- NEXT_CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 1f24915016..059bffb366 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -5,6 +5,7 @@ ### Notable Changes ### CLI +* Auth commands now resolve positional arguments as profile names first, with host fallback ([#4840](https://github.com/databricks/cli/pull/4840)) ### Bundles From b6db124428249ae49c60734eff8b1518b380ddb5 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 7 Apr 2026 12:44:59 +0200 Subject: [PATCH 10/16] Use [PROFILE] in usage strings, keep host fallback silent The positional argument is primarily a profile name. Host URL support is backwards compatibility, not the intended path forward, so we don't advertise it in --help output. Co-authored-by: Isaac --- NEXT_CHANGELOG.md | 2 +- cmd/auth/login.go | 30 ++++++++++++++++++++++++------ cmd/auth/logout.go | 2 +- cmd/auth/token.go | 2 +- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 059bffb366..8461163194 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -5,7 +5,7 @@ ### Notable Changes ### CLI -* Auth commands now resolve positional arguments as profile names first, with host fallback ([#4840](https://github.com/databricks/cli/pull/4840)) +* Auth commands now accept a profile name as a positional argument ([#4840](https://github.com/databricks/cli/pull/4840)) ### Bundles diff --git a/cmd/auth/login.go b/cmd/auth/login.go index 273a89ea96..b5f312e70f 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -94,7 +94,7 @@ func newLoginCommand(authArguments *auth.AuthArguments) *cobra.Command { defaultConfigPath = "%USERPROFILE%\\.databrickscfg" } cmd := &cobra.Command{ - Use: "login [PROFILE_OR_HOST]", + Use: "login [PROFILE]", Short: "Log into a Databricks workspace or account", Long: fmt.Sprintf(`Log into a Databricks workspace or account. @@ -110,16 +110,34 @@ If no host is provided, the CLI opens login.databricks.com where you can authenticate and select a workspace. The positional argument is resolved as a profile name first. If no profile with -that name exists and the argument looks like a URL, it is used as a host. The -host URL may include query parameters to set the workspace and account ID: +that name exists and the argument looks like a URL, it is used as a host. If no +host is provided (via --host, the positional argument, or from an existing +profile), the CLI will open login.databricks.com where you can authenticate and +select a workspace. The workspace URL will be discovered automatically. + +The host URL may include query parameters to set the workspace and account ID: databricks auth login --host "https://?o=&account_id=" Note: URLs containing "?" must be quoted to prevent shell interpretation. -If a profile with the given name already exists, it is updated. Otherwise -a new profile is created. -`, defaultConfigPath), +1. If a profile with the specified name exists and specifies a host, you'll + be logged into the host specified by the profile. The profile will be updated + to use "databricks-cli" as the auth type if that was not the case before. + +2. If a profile with the specified name exists but does not specify a host, + you'll be prompted to specify a host. The profile will be updated to use the + specified host. The auth type will be updated to "databricks-cli" if that was + not the case before. + +3. If a profile with the specified name exists and specifies a host, but you + specify a different host using --host, the profile will + be updated to use the newly specified host. The auth type will be updated to + "databricks-cli" if that was not the case before. + +4. If a profile with the specified name does not exist, a new profile will be + created with the specified host. The auth type will be set to "databricks-cli". +`, defaultConfigPath, defaultConfigPath), } var loginTimeout time.Duration diff --git a/cmd/auth/logout.go b/cmd/auth/logout.go index a6fcb551d4..ab1cd3e9fa 100644 --- a/cmd/auth/logout.go +++ b/cmd/auth/logout.go @@ -28,7 +28,7 @@ You will need to run {{ "databricks auth login" | bold }} to re-authenticate. func newLogoutCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "logout [PROFILE_OR_HOST]", + Use: "logout [PROFILE]", Short: "Log out of a Databricks profile", Args: cobra.MaximumNArgs(1), Hidden: true, diff --git a/cmd/auth/token.go b/cmd/auth/token.go index f45fabee42..72f6168bb7 100644 --- a/cmd/auth/token.go +++ b/cmd/auth/token.go @@ -54,7 +54,7 @@ func applyUnifiedHostFlags(p *profile.Profile, args *auth.AuthArguments) { func newTokenCommand(authArguments *auth.AuthArguments) *cobra.Command { cmd := &cobra.Command{ - Use: "token [PROFILE_OR_HOST]", + Use: "token [PROFILE]", Short: "Get authentication token", Long: `Get authentication token from the local cache in ~/.databricks/token-cache.json. Refresh the access token if it is expired or close to expiry. Use --force-refresh From dc1b3bcd698eac6b9542a043dfd3028208ba9309 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 7 Apr 2026 17:58:46 +0200 Subject: [PATCH 11/16] Address review feedback: sentinel errors, nil guard removal, profile+arg conflict - Use errNoProfileFound sentinel error instead of formatted string for resolvePositionalArg, enabling errors.Is checks in tests - Remove unnecessary nil guard on global --profile flag in logout and token commands (profile is always registered as a persistent root flag) - Add positional arg + --profile conflict check in login command Co-authored-by: Isaac --- cmd/auth/login.go | 3 +++ cmd/auth/logout.go | 7 ++----- cmd/auth/resolve.go | 5 ++++- cmd/auth/resolve_test.go | 11 ++++++----- cmd/auth/token.go | 6 +----- cmd/auth/token_test.go | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cmd/auth/login.go b/cmd/auth/login.go index b5f312e70f..4ee49889f7 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -168,6 +168,9 @@ Note: URLs containing "?" must be quoted to prevent shell interpretation. } // Resolve positional argument as profile or host. + if len(args) > 0 && cmd.Flag("profile").Changed { + return errors.New("please only provide a positional argument or --profile, not both") + } if len(args) > 0 && authArguments.Host != "" { return errors.New("please only provide a positional argument or --host, not both") } diff --git a/cmd/auth/logout.go b/cmd/auth/logout.go index ab1cd3e9fa..8aaa25d227 100644 --- a/cmd/auth/logout.go +++ b/cmd/auth/logout.go @@ -74,14 +74,11 @@ the profile is an error. ctx := cmd.Context() profiler := profile.DefaultProfiler - profileName := "" profileFlag := cmd.Flag("profile") - if profileFlag != nil { - profileName = profileFlag.Value.String() - } + profileName := profileFlag.Value.String() // Resolve the positional argument to a profile name. - if profileFlag != nil && profileFlag.Changed && len(args) == 1 { + if profileFlag.Changed && len(args) == 1 { return errors.New("providing both --profile and a positional argument is not supported") } if profileName == "" && len(args) == 1 { diff --git a/cmd/auth/resolve.go b/cmd/auth/resolve.go index 7c0e477323..e6cc199c38 100644 --- a/cmd/auth/resolve.go +++ b/cmd/auth/resolve.go @@ -2,6 +2,7 @@ package auth import ( "context" + "errors" "fmt" "strconv" "strings" @@ -11,6 +12,8 @@ import ( "github.com/databricks/databricks-sdk-go/config" ) +var errNoProfileFound = errors.New("no matching profile found") + // looksLikeHost returns true if the argument looks like a host URL rather than // a profile name. Profile names are short identifiers (e.g., "logfood", // "DEFAULT"), while host URLs contain dots or start with "http". @@ -45,7 +48,7 @@ func resolvePositionalArg(ctx context.Context, arg string, profiler profile.Prof return "", arg, nil } - return "", "", fmt.Errorf("no profile named %q found", arg) + return "", "", fmt.Errorf("%w: %q", errNoProfileFound, arg) } // resolveHostToProfile resolves a host URL to a profile name. If multiple diff --git a/cmd/auth/resolve_test.go b/cmd/auth/resolve_test.go index 1180b22038..77094a0a01 100644 --- a/cmd/auth/resolve_test.go +++ b/cmd/auth/resolve_test.go @@ -1,6 +1,7 @@ package auth import ( + "errors" "testing" "github.com/databricks/cli/libs/cmdio" @@ -16,7 +17,7 @@ func TestResolvePositionalArg(t *testing.T) { arg string wantProfile string wantHost string - wantErr string + wantErr error }{ { name: "matches profile", @@ -49,7 +50,7 @@ func TestResolvePositionalArg(t *testing.T) { {Name: "logfood", Host: "https://logfood.cloud.databricks.com"}, }, arg: "e2-logfood", - wantErr: `no profile named "e2-logfood" found`, + wantErr: errNoProfileFound, }, { name: "http prefix", @@ -69,7 +70,7 @@ func TestResolvePositionalArg(t *testing.T) { name: "empty profiles error", profiles: profile.Profiles{}, arg: "myprofile", - wantErr: `no profile named "myprofile" found`, + wantErr: errNoProfileFound, }, { name: "profile with dot in name", @@ -87,8 +88,8 @@ func TestResolvePositionalArg(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) profiler := profile.InMemoryProfiler{Profiles: tc.profiles} profileName, host, err := resolvePositionalArg(ctx, tc.arg, profiler) - if tc.wantErr != "" { - assert.ErrorContains(t, err, tc.wantErr) + if tc.wantErr != nil { + assert.True(t, errors.Is(err, tc.wantErr), "expected %v, got %v", tc.wantErr, err) return } require.NoError(t, err) diff --git a/cmd/auth/token.go b/cmd/auth/token.go index 72f6168bb7..afa46ba274 100644 --- a/cmd/auth/token.go +++ b/cmd/auth/token.go @@ -75,11 +75,7 @@ and secret is not supported.`, cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() - profileName := "" - profileFlag := cmd.Flag("profile") - if profileFlag != nil { - profileName = profileFlag.Value.String() - } + profileName := cmd.Flag("profile").Value.String() t, err := loadToken(ctx, loadTokenArgs{ authArguments: authArguments, diff --git a/cmd/auth/token_test.go b/cmd/auth/token_test.go index d53cb87b94..ba4ca4ec0e 100644 --- a/cmd/auth/token_test.go +++ b/cmd/auth/token_test.go @@ -406,7 +406,7 @@ func TestToken_loadToken(t *testing.T) { tokenTimeout: 1 * time.Hour, profiler: profiler, }, - wantErr: `no profile named "e2-logfood" found`, + wantErr: `no matching profile found: "e2-logfood"`, }, { name: "scheme-less account host ambiguity detected correctly", @@ -719,7 +719,7 @@ func TestToken_loadToken(t *testing.T) { tokenTimeout: 1 * time.Hour, profiler: profiler, }, - wantErr: `no profile named "e2-logfood" found`, + wantErr: `no matching profile found: "e2-logfood"`, }, { name: "host flag with profile env var disambiguates multi-profile", From 8f180cdab8c33212b25d963f962042fab53886c2 Mon Sep 17 00:00:00 2001 From: simon Date: Tue, 7 Apr 2026 19:13:23 +0200 Subject: [PATCH 12/16] Fix lint (assert.ErrorIs) and remove incorrect login profile+arg conflict The testifylint linter requires assert.ErrorIs over assert.True(errors.Is). The blanket rejection of positional arg + --profile in login was wrong: `databricks auth login https://host --profile myprofile` is valid (host as positional arg with explicit profile). The profile-first resolution already skips when profileName is set, so no extra guard is needed. Co-authored-by: Isaac --- cmd/auth/login.go | 3 --- cmd/auth/resolve_test.go | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/cmd/auth/login.go b/cmd/auth/login.go index 4ee49889f7..b5f312e70f 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -168,9 +168,6 @@ Note: URLs containing "?" must be quoted to prevent shell interpretation. } // Resolve positional argument as profile or host. - if len(args) > 0 && cmd.Flag("profile").Changed { - return errors.New("please only provide a positional argument or --profile, not both") - } if len(args) > 0 && authArguments.Host != "" { return errors.New("please only provide a positional argument or --host, not both") } diff --git a/cmd/auth/resolve_test.go b/cmd/auth/resolve_test.go index 77094a0a01..8f4fcbbc11 100644 --- a/cmd/auth/resolve_test.go +++ b/cmd/auth/resolve_test.go @@ -1,7 +1,6 @@ package auth import ( - "errors" "testing" "github.com/databricks/cli/libs/cmdio" @@ -89,7 +88,7 @@ func TestResolvePositionalArg(t *testing.T) { profiler := profile.InMemoryProfiler{Profiles: tc.profiles} profileName, host, err := resolvePositionalArg(ctx, tc.arg, profiler) if tc.wantErr != nil { - assert.True(t, errors.Is(err, tc.wantErr), "expected %v, got %v", tc.wantErr, err) + assert.ErrorIs(t, err, tc.wantErr) return } require.NoError(t, err) From 83bf8b9fb2133331498312d1637fcb1f24e1f63c Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 8 Apr 2026 11:18:01 +0200 Subject: [PATCH 13/16] Error when positional arg is combined with --host or --profile The positional argument is a shorthand that resolves to either a profile or a host. Combining it with explicit flags is ambiguous, so we now error with a user-friendly message that echoes the argument and suggests using the flags directly. This is consistent across login, logout, and token. Co-authored-by: Isaac --- .../login/host-arg-overrides-profile/output.txt | 4 ++-- .../auth/login/host-arg-overrides-profile/script | 8 ++++---- cmd/auth/login.go | 10 ++++++---- cmd/auth/login_test.go | 15 +++++++++++++-- cmd/auth/logout.go | 7 ++++--- cmd/auth/logout_test.go | 2 +- cmd/auth/token.go | 9 +++++---- cmd/auth/token_test.go | 2 +- 8 files changed, 36 insertions(+), 21 deletions(-) diff --git a/acceptance/cmd/auth/login/host-arg-overrides-profile/output.txt b/acceptance/cmd/auth/login/host-arg-overrides-profile/output.txt index 79371465c4..7351ad225b 100644 --- a/acceptance/cmd/auth/login/host-arg-overrides-profile/output.txt +++ b/acceptance/cmd/auth/login/host-arg-overrides-profile/output.txt @@ -3,8 +3,8 @@ [override-test] host = https://old-host.cloud.databricks.com -=== Login with positional host argument (should override profile) ->>> [CLI] auth login [DATABRICKS_URL] --profile override-test +=== Login with --host flag (should override profile) +>>> [CLI] auth login --host [DATABRICKS_URL] --profile override-test Profile override-test was successfully saved === Profile after login (host should be updated) diff --git a/acceptance/cmd/auth/login/host-arg-overrides-profile/script b/acceptance/cmd/auth/login/host-arg-overrides-profile/script index 8d3cc68ae4..6ab9de4d44 100644 --- a/acceptance/cmd/auth/login/host-arg-overrides-profile/script +++ b/acceptance/cmd/auth/login/host-arg-overrides-profile/script @@ -13,10 +13,10 @@ cat "./home/.databrickscfg" # and follows the redirect back to localhost. export BROWSER="browser.py" -# Login with profile but provide a different host as positional argument -# The positional argument should override the profile's host -title "Login with positional host argument (should override profile)" -trace $CLI auth login $DATABRICKS_HOST --profile override-test +# Login with profile but provide a different host via --host flag. +# The --host flag should override the profile's stored host. +title "Login with --host flag (should override profile)" +trace $CLI auth login --host $DATABRICKS_HOST --profile override-test title "Profile after login (host should be updated)\n" cat "./home/.databrickscfg" diff --git a/cmd/auth/login.go b/cmd/auth/login.go index b5f312e70f..b0e060202b 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -167,11 +167,13 @@ Note: URLs containing "?" must be quoted to prevent shell interpretation. return errors.New("please either configure serverless or cluster, not both") } - // Resolve positional argument as profile or host. - if len(args) > 0 && authArguments.Host != "" { - return errors.New("please only provide a positional argument or --host, not both") + // The positional argument is a shorthand that resolves to either a + // profile or a host. It cannot be combined with explicit flags. + // Use "databricks auth login --host X --profile Y" instead. + if len(args) > 0 && (authArguments.Host != "" || profileName != "") { + return fmt.Errorf("argument %q cannot be combined with --host or --profile. Use the --host and --profile flags instead", args[0]) } - if profileName == "" && len(args) == 1 { + if len(args) == 1 { resolvedProfile, resolvedHost, err := resolvePositionalArg(ctx, args[0], profile.DefaultProfiler) if err != nil { return err diff --git a/cmd/auth/login_test.go b/cmd/auth/login_test.go index e442973bc7..81924f027a 100644 --- a/cmd/auth/login_test.go +++ b/cmd/auth/login_test.go @@ -993,7 +993,7 @@ auth_type = databricks-cli assert.Equal(t, "222222", savedProfile.WorkspaceID, "workspace_id should be updated to fresh introspection value") } -func TestLoginRejectsHostFlagWithPositionalArg(t *testing.T) { +func TestLoginRejectsPositionalArgWithHostFlag(t *testing.T) { ctx := cmdio.MockDiscard(t.Context()) authArgs := &auth.AuthArguments{Host: "https://example.com"} cmd := newLoginCommand(authArgs) @@ -1001,5 +1001,16 @@ func TestLoginRejectsHostFlagWithPositionalArg(t *testing.T) { cmd.SetContext(ctx) cmd.SetArgs([]string{"myprofile"}) err := cmd.Execute() - assert.ErrorContains(t, err, "please only provide a positional argument or --host, not both") + assert.ErrorContains(t, err, `argument "myprofile" cannot be combined with --host or --profile`) +} + +func TestLoginRejectsPositionalArgWithProfileFlag(t *testing.T) { + ctx := cmdio.MockDiscard(t.Context()) + authArgs := &auth.AuthArguments{} + cmd := newLoginCommand(authArgs) + cmd.Flags().String("profile", "", "") + cmd.SetContext(ctx) + cmd.SetArgs([]string{"--profile", "myprofile", "https://example.com"}) + err := cmd.Execute() + assert.ErrorContains(t, err, `argument "https://example.com" cannot be combined with --host or --profile`) } diff --git a/cmd/auth/logout.go b/cmd/auth/logout.go index 8aaa25d227..6d11481f37 100644 --- a/cmd/auth/logout.go +++ b/cmd/auth/logout.go @@ -77,11 +77,12 @@ the profile is an error. profileFlag := cmd.Flag("profile") profileName := profileFlag.Value.String() - // Resolve the positional argument to a profile name. + // The positional argument is a shorthand that resolves to either a + // profile or a host. It cannot be combined with explicit flags. if profileFlag.Changed && len(args) == 1 { - return errors.New("providing both --profile and a positional argument is not supported") + return fmt.Errorf("argument %q cannot be combined with --profile. Use the --profile flag instead", args[0]) } - if profileName == "" && len(args) == 1 { + if len(args) == 1 { resolvedProfile, resolvedHost, err := resolvePositionalArg(ctx, args[0], profiler) if err != nil { return err diff --git a/cmd/auth/logout_test.go b/cmd/auth/logout_test.go index 2c0aa5abf9..50c152deb5 100644 --- a/cmd/auth/logout_test.go +++ b/cmd/auth/logout_test.go @@ -270,7 +270,7 @@ func TestLogoutProfileFlagAndPositionalArgConflict(t *testing.T) { parent.AddCommand(cmd) parent.SetArgs([]string{"logout", "myprofile", "--profile", "other"}) err := parent.Execute() - assert.ErrorContains(t, err, "providing both --profile and a positional argument is not supported") + assert.ErrorContains(t, err, `argument "myprofile" cannot be combined with --profile`) } func TestLogoutDeleteClearsDefaultProfile(t *testing.T) { diff --git a/cmd/auth/token.go b/cmd/auth/token.go index afa46ba274..592caf444a 100644 --- a/cmd/auth/token.go +++ b/cmd/auth/token.go @@ -140,16 +140,17 @@ type loadTokenArgs struct { // the provided profiler if not explicitly provided. If the token cannot be refreshed, a helpful error message // is printed to the user with steps to reauthenticate. func loadToken(ctx context.Context, args loadTokenArgs) (*oauth2.Token, error) { - // If a profile is provided we read the host from the .databrickscfg file - if args.profileName != "" && len(args.args) > 0 { - return nil, errors.New("providing both a profile and host is not supported") + // The positional argument is a shorthand that resolves to either a + // profile or a host. It cannot be combined with explicit flags. + if len(args.args) > 0 && (args.authArguments.Host != "" || args.profileName != "") { + return nil, fmt.Errorf("argument %q cannot be combined with --host or --profile. Use the --host and --profile flags instead", args.args[0]) } // Resolve the positional arg as a profile name first, then as a host. // Error if it matches neither. This runs before the DATABRICKS_CONFIG_PROFILE // env var check so that an explicit positional argument always goes through // profile-first resolution. - if args.profileName == "" && len(args.args) == 1 { + if len(args.args) == 1 { resolvedProfile, resolvedHost, err := resolvePositionalArg(ctx, args.args[0], args.profiler) if err != nil { return nil, err diff --git a/cmd/auth/token_test.go b/cmd/auth/token_test.go index ba4ca4ec0e..ec7fe2004a 100644 --- a/cmd/auth/token_test.go +++ b/cmd/auth/token_test.go @@ -547,7 +547,7 @@ func TestToken_loadToken(t *testing.T) { u2m.WithOAuthEndpointSupplier(&MockApiClient{}), }, }, - wantErr: "providing both a profile and host is not supported", + wantErr: `argument "workspace-a" cannot be combined with --host or --profile. Use the --host and --profile flags instead`, }, { name: "no args, profiles exist, non-interactive — error with profile hint", From 6599d8bab75975ffe5bc18790077600049a2b3d4 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 8 Apr 2026 11:22:07 +0200 Subject: [PATCH 14/16] Align login help text with #4906 and document profile-first resolution Co-authored-by: Isaac --- cmd/auth/login.go | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/cmd/auth/login.go b/cmd/auth/login.go index b0e060202b..3efcac990b 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -94,7 +94,7 @@ func newLoginCommand(authArguments *auth.AuthArguments) *cobra.Command { defaultConfigPath = "%USERPROFILE%\\.databrickscfg" } cmd := &cobra.Command{ - Use: "login [PROFILE]", + Use: "login [PROFILE_OR_HOST]", Short: "Log into a Databricks workspace or account", Long: fmt.Sprintf(`Log into a Databricks workspace or account. @@ -109,11 +109,10 @@ information, see: If no host is provided, the CLI opens login.databricks.com where you can authenticate and select a workspace. -The positional argument is resolved as a profile name first. If no profile with -that name exists and the argument looks like a URL, it is used as a host. If no -host is provided (via --host, the positional argument, or from an existing -profile), the CLI will open login.databricks.com where you can authenticate and -select a workspace. The workspace URL will be discovered automatically. +The positional argument is resolved as a profile name first. If no profile +with that name exists and the argument looks like a URL, it is used as a +host. The positional argument cannot be combined with --host or --profile; +use the flags directly to specify both. The host URL may include query parameters to set the workspace and account ID: @@ -121,23 +120,9 @@ The host URL may include query parameters to set the workspace and account ID: Note: URLs containing "?" must be quoted to prevent shell interpretation. -1. If a profile with the specified name exists and specifies a host, you'll - be logged into the host specified by the profile. The profile will be updated - to use "databricks-cli" as the auth type if that was not the case before. - -2. If a profile with the specified name exists but does not specify a host, - you'll be prompted to specify a host. The profile will be updated to use the - specified host. The auth type will be updated to "databricks-cli" if that was - not the case before. - -3. If a profile with the specified name exists and specifies a host, but you - specify a different host using --host, the profile will - be updated to use the newly specified host. The auth type will be updated to - "databricks-cli" if that was not the case before. - -4. If a profile with the specified name does not exist, a new profile will be - created with the specified host. The auth type will be set to "databricks-cli". -`, defaultConfigPath, defaultConfigPath), +If a profile with the given name already exists, it is updated. Otherwise +a new profile is created. +`, defaultConfigPath), } var loginTimeout time.Duration From f4bf11791743c7335979d108c60efe985e6cc8a9 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 8 Apr 2026 11:22:18 +0200 Subject: [PATCH 15/16] Use [PROFILE] in login usage string We silently support host URLs as positional args but don't want to advertise it in the usage line. Co-authored-by: Isaac --- cmd/auth/login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/auth/login.go b/cmd/auth/login.go index 3efcac990b..eaf42a3c92 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -94,7 +94,7 @@ func newLoginCommand(authArguments *auth.AuthArguments) *cobra.Command { defaultConfigPath = "%USERPROFILE%\\.databrickscfg" } cmd := &cobra.Command{ - Use: "login [PROFILE_OR_HOST]", + Use: "login [PROFILE]", Short: "Log into a Databricks workspace or account", Long: fmt.Sprintf(`Log into a Databricks workspace or account. From f5bc0c33f4c7808fe8c252d41c1d5834277dd6b3 Mon Sep 17 00:00:00 2001 From: simon Date: Wed, 8 Apr 2026 17:13:03 +0200 Subject: [PATCH 16/16] Fix host-arg-overrides-profile test to expect conflict error The profileHostConflictCheck PreRunE hook now rejects login when --profile and --host specify different hosts. Update the test to expect the conflict error instead of a successful override. Co-authored-by: Isaac --- .../host-arg-overrides-profile/out.databrickscfg | 7 ------- .../login/host-arg-overrides-profile/output.txt | 13 +++---------- .../auth/login/host-arg-overrides-profile/script | 16 +++------------- 3 files changed, 6 insertions(+), 30 deletions(-) delete mode 100644 acceptance/cmd/auth/login/host-arg-overrides-profile/out.databrickscfg diff --git a/acceptance/cmd/auth/login/host-arg-overrides-profile/out.databrickscfg b/acceptance/cmd/auth/login/host-arg-overrides-profile/out.databrickscfg deleted file mode 100644 index b72d6351e2..0000000000 --- a/acceptance/cmd/auth/login/host-arg-overrides-profile/out.databrickscfg +++ /dev/null @@ -1,7 +0,0 @@ -; The profile defined in the DEFAULT section is to be used as a fallback when no profile is explicitly specified. -[DEFAULT] - -[override-test] -host = [DATABRICKS_URL] -workspace_id = [NUMID] -auth_type = databricks-cli diff --git a/acceptance/cmd/auth/login/host-arg-overrides-profile/output.txt b/acceptance/cmd/auth/login/host-arg-overrides-profile/output.txt index 7351ad225b..d6d8c1e3be 100644 --- a/acceptance/cmd/auth/login/host-arg-overrides-profile/output.txt +++ b/acceptance/cmd/auth/login/host-arg-overrides-profile/output.txt @@ -3,15 +3,8 @@ [override-test] host = https://old-host.cloud.databricks.com -=== Login with --host flag (should override profile) +=== Login with --host flag (should error on conflict) >>> [CLI] auth login --host [DATABRICKS_URL] --profile override-test -Profile override-test was successfully saved +Error: --profile "override-test" has host "https://old-host.cloud.databricks.com", which conflicts with --host "[DATABRICKS_URL]". Use --profile only to select a profile -=== Profile after login (host should be updated) -; The profile defined in the DEFAULT section is to be used as a fallback when no profile is explicitly specified. -[DEFAULT] - -[override-test] -host = [DATABRICKS_URL] -workspace_id = [NUMID] -auth_type = databricks-cli +Exit code: 1 diff --git a/acceptance/cmd/auth/login/host-arg-overrides-profile/script b/acceptance/cmd/auth/login/host-arg-overrides-profile/script index 6ab9de4d44..107cc6dbeb 100644 --- a/acceptance/cmd/auth/login/host-arg-overrides-profile/script +++ b/acceptance/cmd/auth/login/host-arg-overrides-profile/script @@ -9,17 +9,7 @@ EOF title "Initial profile with old host\n" cat "./home/.databrickscfg" -# Use a fake browser that performs a GET on the authorization URL -# and follows the redirect back to localhost. -export BROWSER="browser.py" - -# Login with profile but provide a different host via --host flag. -# The --host flag should override the profile's stored host. -title "Login with --host flag (should override profile)" +# Login with --profile and --host where the profile has a different host. +# This should error because the profile's host conflicts with --host. +title "Login with --host flag (should error on conflict)" trace $CLI auth login --host $DATABRICKS_HOST --profile override-test - -title "Profile after login (host should be updated)\n" -cat "./home/.databrickscfg" - -# Track the .databrickscfg file that was created to surface changes. -mv "./home/.databrickscfg" "./out.databrickscfg"