package configure import ( "bytes" "errors" "fmt" "os" "path/filepath" "github.com/databricks/bricks/cmd/prompt" "github.com/databricks/bricks/cmd/root" "github.com/databricks/bricks/project" "github.com/spf13/cobra" "gopkg.in/ini.v1" ) type Configs struct { Host string `ini:"host"` Token string `ini:"token,omitempty"` Profile string `ini:"-"` } var noInteractive, tokenMode bool func (cfg *Configs) loadNonInteractive(cmd *cobra.Command) error { host, err := cmd.Flags().GetString("host") if err != nil || host == "" { return fmt.Errorf("use --host to specify host in non interactive mode: %w", err) } cfg.Host = host if !tokenMode { return nil } n, err := fmt.Scanf("%s\n", &cfg.Token) if err != nil { return err } if n != 1 { return fmt.Errorf("exactly 1 argument required") } return nil } func (cfg *Configs) loadInteractive(cmd *cobra.Command) error { res := prompt.Results{} questions := prompt.Questions{} host, err := cmd.Flags().GetString("host") if err != nil || host == "" { questions = append(questions, prompt.Text{ Key: "host", Label: "Databricks Host", Default: func(res prompt.Results) string { return cfg.Host }, Callback: func(ans prompt.Answer, config *project.Config, res prompt.Results) { cfg.Host = ans.Value }, }) } else { cfg.Host = host } if tokenMode { questions = append(questions, prompt.Text{ Key: "token", Label: "Databricks Token", Default: func(res prompt.Results) string { return cfg.Token }, Callback: func(ans prompt.Answer, config *project.Config, res prompt.Results) { cfg.Token = ans.Value }, }) } err = questions.Ask(res) if err != nil { return err } for _, answer := range res { if answer.Callback != nil { answer.Callback(answer, nil, res) } } return nil } var configureCmd = &cobra.Command{ Use: "configure", Short: "Configure authentication", RunE: func(cmd *cobra.Command, args []string) error { profile, err := cmd.Flags().GetString("profile") if err != nil { return fmt.Errorf("read --profile flag: %w", err) } path := os.Getenv("DATABRICKS_CONFIG_FILE") if path == "" { path, err = os.UserHomeDir() if err != nil { return fmt.Errorf("homedir: %w", err) } } if filepath.Base(path) == ".databrickscfg" { path = filepath.Dir(path) } err = os.MkdirAll(path, os.ModeDir|os.ModePerm) if err != nil { return fmt.Errorf("create config dir: %w", err) } cfgPath := filepath.Join(path, ".databrickscfg") _, err = os.Stat(cfgPath) if errors.Is(err, os.ErrNotExist) { file, err := os.Create(cfgPath) if err != nil { return fmt.Errorf("create config file: %w", err) } file.Close() } else if err != nil { return fmt.Errorf("open config file: %w", err) } ini_cfg, err := ini.Load(cfgPath) if err != nil { return fmt.Errorf("load config file: %w", err) } cfg := &Configs{"", "", profile} err = ini_cfg.Section(profile).MapTo(cfg) if err != nil { return fmt.Errorf("unmarshal loaded config: %w", err) } if noInteractive { err = cfg.loadNonInteractive(cmd) } else { err = cfg.loadInteractive(cmd) } if err != nil { return fmt.Errorf("reading configs: %w", err) } err = ini_cfg.Section(profile).ReflectFrom(cfg) if err != nil { return fmt.Errorf("marshall config: %w", err) } var buffer bytes.Buffer if ini_cfg.Section("DEFAULT").Body() != "" { //This configuration makes the ini library write the DEFAULT header explicitly. //DEFAULT section might be empty ini.DefaultHeader = true } _, err = ini_cfg.WriteTo(&buffer) if err != nil { return fmt.Errorf("write config to buffer: %w", err) } err = os.WriteFile(cfgPath, buffer.Bytes(), os.ModePerm) if err != nil { return fmt.Errorf("write congfig to file: %w", err) } return nil }, } func init() { root.RootCmd.AddCommand(configureCmd) configureCmd.Flags().BoolVarP(&tokenMode, "token", "t", false, "Configure using Databricks Personal Access Token") configureCmd.Flags().BoolVar(&noInteractive, "no-interactive", false, "Don't show interactive prompts for inputs. Read directly from stdin.") configureCmd.Flags().String("host", "", "Host to connect to.") configureCmd.Flags().String("profile", "DEFAULT", "CLI connection profile to use.") }