Tune template

This commit is contained in:
Lennart Kats 2023-09-21 21:13:37 +02:00
parent b3b00fd226
commit ee0c77ffb8
No known key found for this signature in database
GPG Key ID: 1EB8B57673197023
9 changed files with 75 additions and 27 deletions

View File

@ -40,20 +40,28 @@ func repoName(url string) string {
func newInitCommand() *cobra.Command { func newInitCommand() *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "init [TEMPLATE_PATH]", Use: "init [TEMPLATE_PATH]",
Short: "Initialize Template", Short: "Initialize a new bundle from a template",
Args: cobra.MaximumNArgs(1), Long: `Initialize a new bundle from a template.
TEMPLATE_PATH optionally specifies which template to use. It can be one of the following:
- 'default-python' for the default Python template
- a local file system path with a template directory
- a Git repository URL, e.g. https://github.com/my/repository
See https://docs.databricks.com//dev-tools/bundles/templates.html for more information on templates.`,
} }
var configFile string var configFile string
var outputDir string var outputDir string
var templateDir string var templateDir string
cmd.Flags().StringVar(&configFile, "config-file", "", "File containing input parameters for template initialization.") cmd.Flags().StringVar(&configFile, "config-file", "", "File containing input parameters for template initialization.")
cmd.Flags().StringVar(&templateDir, "template-dir", "", "Directory within repository that holds the template specification.") cmd.Flags().StringVar(&templateDir, "template-dir", "", "Directory path within a Git repository containing the template.")
cmd.Flags().StringVar(&outputDir, "output-dir", "", "Directory to write the initialized template to.") cmd.Flags().StringVar(&outputDir, "output-dir", "", "Directory to write the initialized template to.")
cmd.PreRunE = root.MustWorkspaceClient cmd.PreRunE = root.MustWorkspaceClient
cmd.RunE = func(cmd *cobra.Command, args []string) error { cmd.RunE = func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context() ctx := cmd.Context()
var templatePath string var templatePath string
if len(args) > 0 { if len(args) > 0 {
templatePath = args[0] templatePath = args[0]
@ -69,6 +77,9 @@ func newInitCommand() *cobra.Command {
} }
if !isRepoUrl(templatePath) { if !isRepoUrl(templatePath) {
if templateDir != "" {
return errors.New("--template-dir can only be used with a Git repository URL")
}
// skip downloading the repo because input arg is not a URL. We assume // skip downloading the repo because input arg is not a URL. We assume
// it's a path on the local file system in that case // it's a path on the local file system in that case
return template.Materialize(ctx, configFile, templatePath, outputDir) return template.Materialize(ctx, configFile, templatePath, outputDir)

View File

@ -20,6 +20,8 @@ type Schema struct {
// The values are the schema for the type of the field // The values are the schema for the type of the field
Properties map[string]*Schema `json:"properties,omitempty"` Properties map[string]*Schema `json:"properties,omitempty"`
SuccessMessage string `json:"successMessage,omitempty"`
// The schema for all values of an array // The schema for all values of an array
Items *Schema `json:"items,omitempty"` Items *Schema `json:"items,omitempty"`

View File

@ -65,7 +65,7 @@ func (c *config) assignValuesFromFile(path string) error {
} }
// Assigns default values from schema to input config map // Assigns default values from schema to input config map
func (c *config) assignDefaultValues() error { func (c *config) assignDefaultValues(r *renderer) error {
for name, property := range c.schema.Properties { for name, property := range c.schema.Properties {
// Config already has a value assigned // Config already has a value assigned
if _, ok := c.values[name]; ok { if _, ok := c.values[name]; ok {
@ -75,13 +75,21 @@ func (c *config) assignDefaultValues() error {
if property.Default == nil { if property.Default == nil {
continue continue
} }
defaultVal, err := jsonschema.ToString(property.Default, property.Type)
if err != nil {
return err
}
defaultVal, err = r.executeTemplate(defaultVal)
if err != nil {
return err
}
c.values[name] = property.Default c.values[name] = property.Default
} }
return nil return nil
} }
// Prompts user for values for properties that do not have a value set yet // Prompts user for values for properties that do not have a value set yet
func (c *config) promptForValues() error { func (c *config) promptForValues(r *renderer) error {
for _, p := range c.schema.OrderedProperties() { for _, p := range c.schema.OrderedProperties() {
name := p.Name name := p.Name
property := p.Schema property := p.Schema
@ -99,6 +107,15 @@ func (c *config) promptForValues() error {
if err != nil { if err != nil {
return err return err
} }
defaultVal, err = r.executeTemplate(defaultVal)
if err != nil {
return err
}
}
description, err := r.executeTemplate(property.Description)
if err != nil {
return err
} }
// Get user input by running the prompt // Get user input by running the prompt
@ -109,12 +126,12 @@ func (c *config) promptForValues() error {
if err != nil { if err != nil {
return err return err
} }
userInput, err = cmdio.AskSelect(c.ctx, property.Description, enums) userInput, err = cmdio.AskSelect(c.ctx, description, enums)
if err != nil { if err != nil {
return err return err
} }
} else { } else {
userInput, err = cmdio.Ask(c.ctx, property.Description, defaultVal) userInput, err = cmdio.Ask(c.ctx, description, defaultVal)
if err != nil { if err != nil {
return err return err
} }
@ -132,11 +149,11 @@ func (c *config) promptForValues() error {
// Prompt user for any missing config values. Assign default values if // Prompt user for any missing config values. Assign default values if
// terminal is not TTY // terminal is not TTY
func (c *config) promptOrAssignDefaultValues() error { func (c *config) promptOrAssignDefaultValues(r *renderer) error {
if cmdio.IsOutTTY(c.ctx) && cmdio.IsInTTY(c.ctx) { if cmdio.IsOutTTY(c.ctx) && cmdio.IsInTTY(c.ctx) {
return c.promptForValues() return c.promptForValues(r)
} }
return c.assignDefaultValues() return c.assignDefaultValues(r)
} }
// Validates the configuration. If passes, the configuration is ready to be used // Validates the configuration. If passes, the configuration is ready to be used

View File

@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
"os"
"regexp" "regexp"
"text/template" "text/template"
@ -65,7 +66,7 @@ func loadHelpers(ctx context.Context) template.FuncMap {
// Get smallest node type (follows Terraform's GetSmallestNodeType) // Get smallest node type (follows Terraform's GetSmallestNodeType)
"smallest_node_type": func() (string, error) { "smallest_node_type": func() (string, error) {
if w.Config.Host == "" { if w.Config.Host == "" {
return "", errors.New("cannot determine target workspace, please first setup a configuration profile using 'databricks auth login'") return "", errors.New("cannot determine target workspace, please first setup a configuration profile using 'databricks configure'")
} }
if w.Config.IsAzure() { if w.Config.IsAzure() {
return "Standard_D3_v2", nil return "Standard_D3_v2", nil
@ -74,9 +75,12 @@ func loadHelpers(ctx context.Context) template.FuncMap {
} }
return "i3.xlarge", nil return "i3.xlarge", nil
}, },
"path_separator": func() string {
return string(os.PathSeparator)
},
"workspace_host": func() (string, error) { "workspace_host": func() (string, error) {
if w.Config.Host == "" { if w.Config.Host == "" {
return "", errors.New("cannot determine target workspace, please first setup a configuration profile using 'databricks auth login'") return "", errors.New("cannot determine target workspace, please first setup a configuration profile using 'databricks configure'")
} }
return w.Config.Host, nil return w.Config.Host, nil
}, },

View File

@ -56,23 +56,23 @@ func Materialize(ctx context.Context, configFilePath, templateRoot, outputDir st
} }
} }
// Prompt user for any missing config values. Assign default values if r, err := newRenderer(ctx, config.values, helpers, templatePath, libraryPath, outputDir)
// terminal is not TTY
err = config.promptOrAssignDefaultValues()
if err != nil { if err != nil {
return err return err
} }
// Prompt user for any missing config values. Assign default values if
// terminal is not TTY
err = config.promptOrAssignDefaultValues(r)
if err != nil {
return err
}
err = config.validate() err = config.validate()
if err != nil { if err != nil {
return err return err
} }
// Walk and render the template, since input configuration is complete // Walk and render the template, since input configuration is complete
r, err := newRenderer(ctx, config.values, helpers, templatePath, libraryPath, outputDir)
if err != nil {
return err
}
err = r.walk() err = r.walk()
if err != nil { if err != nil {
return err return err
@ -82,7 +82,17 @@ func Materialize(ctx context.Context, configFilePath, templateRoot, outputDir st
if err != nil { if err != nil {
return err return err
} }
cmdio.LogString(ctx, "✨ Successfully initialized template")
success := config.schema.SuccessMessage
if success == "" {
cmdio.LogString(ctx, "✨ Successfully initialized template")
} else {
success, err = r.executeTemplate(success)
if err != nil {
return err
}
cmdio.LogString(ctx, success)
}
return nil return nil
} }

View File

@ -311,7 +311,7 @@ func (r *renderer) persistToDisk() error {
path := file.DstPath().absPath() path := file.DstPath().absPath()
_, err := os.Stat(path) _, err := os.Stat(path)
if err == nil { if err == nil {
return fmt.Errorf("failed to persist to disk, conflict with existing file: %s", path) return fmt.Errorf("failed initialize template, one or more files already exist: %s", path)
} }
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("error while verifying file %s does not already exist: %w", path, err) return fmt.Errorf("error while verifying file %s does not already exist: %w", path, err)

View File

@ -10,22 +10,24 @@
"type": "string", "type": "string",
"default": "yes", "default": "yes",
"enum": ["yes", "no"], "enum": ["yes", "no"],
"description": "Include a stub (sample) notebook in 'my_project/src'", "description": "Include a stub (sample) notebook in '{{.project_name}}{{path_separator}}src'",
"order": 2 "order": 2
}, },
"include_dlt": { "include_dlt": {
"type": "string", "type": "string",
"default": "yes", "default": "yes",
"enum": ["yes", "no"], "enum": ["yes", "no"],
"description": "Include a stub (sample) DLT pipeline in 'my_project/src'", "description": "Include a stub (sample) Delta Live Tables pipeline in '{{.project_name}}{{path_separator}}src'",
"order": 3 "order": 3
}, },
"include_python": { "include_python": {
"type": "string", "type": "string",
"default": "yes", "default": "yes",
"enum": ["yes", "no"], "enum": ["yes", "no"],
"description": "Include a stub (sample) Python package 'my_project/src'", "comment": "If the selected they don't want a notebook we provide an extra hint that the stub Python package can be used to author their DLT pipeline",
"description": "Include a stub (sample) Python package in '{{.project_name}}{{path_separator}}src'",
"order": 4 "order": 4
} }
} },
"successMessage": "\n✨ Your new project has been created under '{{.project_name}}{{path_separator}}'!\n\nPlease refer to the README.md of your project for further instructions on getting started.\nOr read the documentation on Databricks Asset Bundles at https://docs.databricks.com/dev-tools/bundles/index.html."
} }

View File

@ -9,6 +9,8 @@
"python.testing.unittestEnabled": false, "python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true, "python.testing.pytestEnabled": true,
"files.exclude": { "files.exclude": {
"**/*.egg-info": true "**/*.egg-info": true,
"**/__pycache__": true,
".pytest_cache": true,
}, },
} }

View File

@ -30,7 +30,7 @@ The '{{.project_name}}' project was generated by using the default-python templa
5. To run a job or pipeline, use the "run" comand: 5. To run a job or pipeline, use the "run" comand:
``` ```
$ databricks bundle run {{.project_name}}_job $ databricks bundle run
``` ```
6. Optionally, install developer tools such as the Databricks extension for Visual Studio Code from 6. Optionally, install developer tools such as the Databricks extension for Visual Studio Code from