diff --git a/acceptance/cmd/auth/login/with-scopes/out.databrickscfg b/acceptance/cmd/auth/login/with-scopes/out.databrickscfg new file mode 100644 index 0000000000..7aac4e9365 --- /dev/null +++ b/acceptance/cmd/auth/login/with-scopes/out.databrickscfg @@ -0,0 +1,7 @@ +; The profile defined in the DEFAULT section is to be used as a fallback when no profile is explicitly specified. +[DEFAULT] + +[scoped-test] +host = [DATABRICKS_URL] +scopes = jobs,pipelines,clusters +auth_type = databricks-cli diff --git a/acceptance/cmd/auth/login/with-scopes/out.test.toml b/acceptance/cmd/auth/login/with-scopes/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/cmd/auth/login/with-scopes/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/cmd/auth/login/with-scopes/output.txt b/acceptance/cmd/auth/login/with-scopes/output.txt new file mode 100644 index 0000000000..1909fadecc --- /dev/null +++ b/acceptance/cmd/auth/login/with-scopes/output.txt @@ -0,0 +1,3 @@ + +>>> [CLI] auth login --host [DATABRICKS_URL] --profile scoped-test --scopes jobs,pipelines,clusters +Profile scoped-test was successfully saved diff --git a/acceptance/cmd/auth/login/with-scopes/script b/acceptance/cmd/auth/login/with-scopes/script new file mode 100644 index 0000000000..f2a4be783b --- /dev/null +++ b/acceptance/cmd/auth/login/with-scopes/script @@ -0,0 +1,10 @@ +sethome "./home" + +# Use a fake browser that performs a GET on the authorization URL +# and follows the redirect back to localhost. +export BROWSER="browser.py" + +trace $CLI auth login --host $DATABRICKS_HOST --profile scoped-test --scopes "jobs,pipelines,clusters" + +# Track the .databrickscfg file that was created to surface changes. +mv "./home/.databrickscfg" "./out.databrickscfg" diff --git a/acceptance/cmd/auth/login/with-scopes/test.toml b/acceptance/cmd/auth/login/with-scopes/test.toml new file mode 100644 index 0000000000..36c0e7e237 --- /dev/null +++ b/acceptance/cmd/auth/login/with-scopes/test.toml @@ -0,0 +1,3 @@ +Ignore = [ + "home" +] diff --git a/cmd/auth/login.go b/cmd/auth/login.go index 2416e9effa..70aeef9707 100644 --- a/cmd/auth/login.go +++ b/cmd/auth/login.go @@ -101,12 +101,15 @@ depends on the existing profiles you have set in your configuration file var loginTimeout time.Duration var configureCluster bool var configureServerless bool + var scopes string cmd.Flags().DurationVar(&loginTimeout, "timeout", defaultTimeout, "Timeout for completing login challenge in the browser") cmd.Flags().BoolVar(&configureCluster, "configure-cluster", false, "Prompts to configure cluster") cmd.Flags().BoolVar(&configureServerless, "configure-serverless", false, "Prompts to configure serverless") + cmd.Flags().StringVar(&scopes, "scopes", "", + "Comma-separated list of OAuth scopes to request (defaults to 'all-apis')") cmd.RunE = func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() @@ -149,14 +152,24 @@ depends on the existing profiles you have set in your configuration file return err } + var scopesList []string + if scopes != "" { + for _, s := range strings.Split(scopes, ",") { + scopesList = append(scopesList, strings.TrimSpace(s)) + } + } + oauthArgument, err := authArguments.ToOAuthArgument() if err != nil { return err } persistentAuthOpts := []u2m.PersistentAuthOption{ u2m.WithOAuthArgument(oauthArgument), + u2m.WithBrowser(getBrowserFunc(cmd)), + } + if len(scopesList) > 0 { + persistentAuthOpts = append(persistentAuthOpts, u2m.WithScopes(scopesList)) } - persistentAuthOpts = append(persistentAuthOpts, u2m.WithBrowser(getBrowserFunc(cmd))) persistentAuth, err := u2m.NewPersistentAuth(ctx, persistentAuthOpts...) if err != nil { return err @@ -222,6 +235,7 @@ depends on the existing profiles you have set in your configuration file ClusterID: clusterID, ConfigFile: os.Getenv("DATABRICKS_CONFIG_FILE"), ServerlessComputeID: serverlessComputeID, + Scopes: scopesList, }) if err != nil { return err diff --git a/libs/databrickscfg/ops_test.go b/libs/databrickscfg/ops_test.go index dd8484fb7b..ed81da2d10 100644 --- a/libs/databrickscfg/ops_test.go +++ b/libs/databrickscfg/ops_test.go @@ -225,3 +225,27 @@ func TestSaveToProfile_ClearingPreviousProfile(t *testing.T) { assert.Equal(t, "https://foo", raw["host"]) assert.Equal(t, "databricks-cli", raw["auth_type"]) } + +func TestSaveToProfile_WithScopes(t *testing.T) { + ctx := context.Background() + path := filepath.Join(t.TempDir(), "databrickscfg") + + err := SaveToProfile(ctx, &config.Config{ + ConfigFile: path, + Profile: "scoped", + Host: "https://myworkspace.cloud.databricks.com", + AuthType: "databricks-cli", + Scopes: []string{"jobs", "pipelines", "clusters"}, + }) + require.NoError(t, err) + + file, err := loadOrCreateConfigFile(path) + require.NoError(t, err) + section, err := file.GetSection("scoped") + require.NoError(t, err) + raw := section.KeysHash() + assert.Len(t, raw, 3) + assert.Equal(t, "https://myworkspace.cloud.databricks.com", raw["host"]) + assert.Equal(t, "databricks-cli", raw["auth_type"]) + assert.Equal(t, "jobs,pipelines,clusters", raw["scopes"]) +}