From 154fb8a96790a3d473119004af85acb994b2ca91 Mon Sep 17 00:00:00 2001 From: Serge Smertin Date: Sat, 14 May 2022 19:55:55 +0200 Subject: [PATCH] added basic init command for `databricks.yml` --- cmd/init/init.go | 105 ++++++++++++++++++++ cmd/init/legacy-cli.go | 48 +++++++++ cmd/init/prompt.go | 83 ++++++++++++++++ cmd/init/templates/python-notebook/Hello.py | 4 + 4 files changed, 240 insertions(+) create mode 100644 cmd/init/init.go create mode 100644 cmd/init/legacy-cli.go create mode 100644 cmd/init/prompt.go create mode 100644 cmd/init/templates/python-notebook/Hello.py diff --git a/cmd/init/init.go b/cmd/init/init.go new file mode 100644 index 00000000..ca4b65de --- /dev/null +++ b/cmd/init/init.go @@ -0,0 +1,105 @@ +package init + +import ( + "embed" + "fmt" + "os" + "path" + + "github.com/databricks/bricks/cmd/root" + "github.com/databricks/bricks/project" + "github.com/ghodss/yaml" + "github.com/spf13/cobra" +) + +//go:embed templates +var templates embed.FS + +// initCmd represents the init command +var initCmd = &cobra.Command{ + Use: "init", + Short: "Project starter templates", + Long: `Generate project templates`, + + RunE: func(cmd *cobra.Command, args []string) error { + if project.IsDatabricksProject() { + return fmt.Errorf("this path is already a Databricks project") + } + profileChoice, err := getConnectionProfile() + if err != nil { + return err + } + wd, _ := os.Getwd() + q := Questions{ + Text{"name", "Project name", func(res Results) string { + return path.Base(wd) + }, func(ans Answer, prj *project.Project, res Results) { + prj.Name = ans.Value + }}, + *profileChoice, + Choice{"language", "Project language", Answers{ + {"Python", "Machine learning and data engineering focused projects", nil}, + {"Scala", "Data engineering focused projects with strong typing", nil}, + }}, + Choice{"isolation", "Deployment isolation", Answers{ + {"None", "Use shared Databricks workspace resources for all project team members", nil}, + {"Soft", "Prepend prefixes to each team member's deployment", func( + ans Answer, prj *project.Project, res Results) { + prj.Isolation = project.Soft + }}, + }}, + // Choice{"cloud", "Cloud", Answers{ + // {"AWS", "Amazon Web Services", nil}, + // {"Azure", "Microsoft Azure Cloud", nil}, + // {"GCP", "Google Cloud Platform", nil}, + // }}, + // Choice{"ci", "Continuous Integration", Answers{ + // {"None", "Do not create continuous integration configuration", nil}, + // {"GitHub Actions", "Create .github/workflows/push.yml configuration", nil}, + // {"Azure DevOps", "Create basic build and test pipelines", nil}, + // }}, + // Choice{"ide", "Integrated Development Environment", Answers{ + // {"None", "Do not create templates for IDE", nil}, + // {"VSCode", "Create .devcontainer and other useful things", nil}, + // {"PyCharm", "Create project conf and other things", nil}, + // }}, + } + res := Results{} + err = q.Ask(res) + if err != nil { + return err + } + var prj project.Project + for _, ans := range res { + if ans.Callback == nil { + continue + } + ans.Callback(ans, &prj, res) + } + raw, err := yaml.Marshal(prj) + if err != nil { + return err + } + newConfig, err := os.Create(fmt.Sprintf("%s/%s", wd, project.ConfigFile)) + if err != nil { + return err + } + _, err = newConfig.Write(raw) + if err != nil { + return err + } + d, err := templates.ReadDir(".") + if err != nil { + return err + } + for _, v := range d { + cmd.Printf("template found: %v", v.Name()) + } + cmd.Print("Config initialized!") + return err + }, +} + +func init() { + root.RootCmd.AddCommand(initCmd) +} diff --git a/cmd/init/legacy-cli.go b/cmd/init/legacy-cli.go new file mode 100644 index 00000000..a76ff180 --- /dev/null +++ b/cmd/init/legacy-cli.go @@ -0,0 +1,48 @@ +package init + +import ( + "fmt" + + "github.com/databricks/bricks/project" + "github.com/mitchellh/go-homedir" + "gopkg.in/ini.v1" +) + +func loadCliProfiles() (profiles []Answer, err error) { + file, err := homedir.Expand("~/.databrickscfg") + if err != nil { + return + } + gitConfig, err := ini.Load(file) + if err != nil { + return + } + for _, v := range gitConfig.Sections() { + host, err := v.GetKey("host") + if err != nil { + // invalid profile + continue + } + profiles = append(profiles, Answer{ + Value: v.Name(), + Details: fmt.Sprintf(`Connecting to "%s" workspace`, host), + Callback: func(ans Answer, prj *project.Project, _ Results) { + prj.Profile = ans.Value + }, + }) + } + return +} + +func getConnectionProfile() (*Choice, error) { + profiles, err := loadCliProfiles() + if err != nil { + return nil, err + } + // TODO: propmt for password and create ~/.databrickscfg + return &Choice{ + key: "profile", + Label: "Databricks CLI profile", + Answers: profiles, + }, err +} diff --git a/cmd/init/prompt.go b/cmd/init/prompt.go new file mode 100644 index 00000000..86c43eeb --- /dev/null +++ b/cmd/init/prompt.go @@ -0,0 +1,83 @@ +package init + +import ( + "fmt" + + "github.com/databricks/bricks/project" + "github.com/manifoldco/promptui" +) + +type Results map[string]Answer + +type Question interface { + Ask(res Results) (key string, ans Answer, err error) +} + +type Questions []Question + +func (qq Questions) Ask(res Results) error { + for _, v := range qq { + key, ans, err := v.Ask(res) + if err != nil { + return err + } + res[key] = ans + } + return nil +} + +type Text struct { + key string + Label string + Default func(res Results) string + Callback AnswerCallback +} + +func (t Text) Ask(res Results) (string, Answer, error) { + def := "" + if t.Default != nil { + def = t.Default(res) + } + v, err := (&promptui.Prompt{ + Label: t.Label, + Default: def, + }).Run() + return t.key, Answer{ + Value: v, + Callback: t.Callback, + }, err +} + +type Choice struct { + key string + Label string + Answers []Answer +} + +func (q Choice) Ask(res Results) (string, Answer, error) { + prompt := promptui.Select{ + Label: q.Label, + Items: q.Answers, + Templates: &promptui.SelectTemplates{ + Label: `{{ .Value }}`, + Details: `{{ .Details | green }}`, + Selected: fmt.Sprintf(`{{ "%s" | faint }}: {{ .Value | bold }}`, q.Label), + }, + } + i, _, err := prompt.Run() + return q.key, q.Answers[i], err +} + +type Answers []Answer + +type AnswerCallback func(ans Answer, prj *project.Project, res Results) + +type Answer struct { + Value string + Details string + Callback AnswerCallback +} + +func (a Answer) String() string { + return a.Value +} diff --git a/cmd/init/templates/python-notebook/Hello.py b/cmd/init/templates/python-notebook/Hello.py new file mode 100644 index 00000000..1910e099 --- /dev/null +++ b/cmd/init/templates/python-notebook/Hello.py @@ -0,0 +1,4 @@ +# Databricks notebook source + +# this was automatically generated +display(spark.tables()) \ No newline at end of file