-
Notifications
You must be signed in to change notification settings - Fork 62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor bundle init
#2074
base: main
Are you sure you want to change the base?
Refactor bundle init
#2074
Changes from 10 commits
070ae2f
daa2b91
a743139
d238dd8
2965c30
ed24f28
af89580
9f83fe0
00266cb
2b1c5cb
8607e08
39dfe05
7105777
4755db4
0a68fb3
8202aaf
4743095
57a7519
38d47e6
ad28ce1
f394606
2a0dbde
a2a3ae7
30d683a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,175 +1,15 @@ | ||
package bundle | ||
|
||
import ( | ||
"context" | ||
"errors" | ||
"fmt" | ||
"io/fs" | ||
"os" | ||
"path/filepath" | ||
"slices" | ||
"strings" | ||
|
||
"github.com/databricks/cli/cmd/root" | ||
"github.com/databricks/cli/libs/cmdio" | ||
"github.com/databricks/cli/libs/dbr" | ||
"github.com/databricks/cli/libs/filer" | ||
"github.com/databricks/cli/libs/git" | ||
"github.com/databricks/cli/libs/template" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var gitUrlPrefixes = []string{ | ||
"https://", | ||
"git@", | ||
} | ||
|
||
type nativeTemplate struct { | ||
name string | ||
gitUrl string | ||
description string | ||
aliases []string | ||
hidden bool | ||
} | ||
|
||
const customTemplate = "custom..." | ||
|
||
var nativeTemplates = []nativeTemplate{ | ||
{ | ||
name: "default-python", | ||
description: "The default Python template for Notebooks / Delta Live Tables / Workflows", | ||
}, | ||
{ | ||
name: "default-sql", | ||
description: "The default SQL template for .sql files that run with Databricks SQL", | ||
}, | ||
{ | ||
name: "dbt-sql", | ||
description: "The dbt SQL template (databricks.com/blog/delivering-cost-effective-data-real-time-dbt-and-databricks)", | ||
}, | ||
{ | ||
name: "mlops-stacks", | ||
gitUrl: "https://github.com/databricks/mlops-stacks", | ||
description: "The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)", | ||
aliases: []string{"mlops-stack"}, | ||
}, | ||
{ | ||
name: "default-pydabs", | ||
gitUrl: "https://databricks.github.io/workflows-authoring-toolkit/pydabs-template.git", | ||
hidden: true, | ||
description: "The default PyDABs template", | ||
}, | ||
{ | ||
name: customTemplate, | ||
description: "Bring your own template", | ||
}, | ||
} | ||
|
||
// Return template descriptions for command-line help | ||
func nativeTemplateHelpDescriptions() string { | ||
var lines []string | ||
for _, template := range nativeTemplates { | ||
if template.name != customTemplate && !template.hidden { | ||
lines = append(lines, fmt.Sprintf("- %s: %s", template.name, template.description)) | ||
} | ||
} | ||
return strings.Join(lines, "\n") | ||
} | ||
|
||
// Return template options for an interactive prompt | ||
func nativeTemplateOptions() []cmdio.Tuple { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved to |
||
names := make([]cmdio.Tuple, 0, len(nativeTemplates)) | ||
for _, template := range nativeTemplates { | ||
if template.hidden { | ||
continue | ||
} | ||
tuple := cmdio.Tuple{ | ||
Name: template.name, | ||
Id: template.description, | ||
} | ||
names = append(names, tuple) | ||
} | ||
return names | ||
} | ||
|
||
func getNativeTemplateByDescription(description string) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved inline to |
||
for _, template := range nativeTemplates { | ||
if template.description == description { | ||
return template.name | ||
} | ||
} | ||
return "" | ||
} | ||
|
||
func getUrlForNativeTemplate(name string) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replaced by |
||
for _, template := range nativeTemplates { | ||
if template.name == name { | ||
return template.gitUrl | ||
} | ||
if slices.Contains(template.aliases, name) { | ||
return template.gitUrl | ||
} | ||
} | ||
return "" | ||
} | ||
|
||
func getFsForNativeTemplate(name string) (fs.FS, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replaced by |
||
builtin, err := template.Builtin() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// If this is a built-in template, the return value will be non-nil. | ||
var templateFS fs.FS | ||
for _, entry := range builtin { | ||
if entry.Name == name { | ||
templateFS = entry.FS | ||
break | ||
} | ||
} | ||
|
||
return templateFS, nil | ||
} | ||
|
||
func isRepoUrl(url string) bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved to |
||
result := false | ||
for _, prefix := range gitUrlPrefixes { | ||
if strings.HasPrefix(url, prefix) { | ||
result = true | ||
break | ||
} | ||
} | ||
return result | ||
} | ||
|
||
// Computes the repo name from the repo URL. Treats the last non empty word | ||
// when splitting at '/' as the repo name. For example: for url [email protected]:databricks/cli.git | ||
// the name would be "cli.git" | ||
func repoName(url string) string { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved to |
||
parts := strings.Split(strings.TrimRight(url, "/"), "/") | ||
return parts[len(parts)-1] | ||
} | ||
|
||
func constructOutputFiler(ctx context.Context, outputDir string) (filer.Filer, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. moved to |
||
outputDir, err := filepath.Abs(outputDir) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// If the CLI is running on DBR and we're writing to the workspace file system, | ||
// use the extension-aware workspace filesystem filer to instantiate the template. | ||
// | ||
// It is not possible to write notebooks through the workspace filesystem's FUSE mount. | ||
// Therefore this is the only way we can initialize templates that contain notebooks | ||
// when running the CLI on DBR and initializing a template to the workspace. | ||
// | ||
if strings.HasPrefix(outputDir, "/Workspace/") && dbr.RunsOnRuntime(ctx) { | ||
return filer.NewWorkspaceFilesExtensionsClient(root.WorkspaceClient(ctx), outputDir) | ||
} | ||
|
||
return filer.NewLocalClient(outputDir) | ||
} | ||
|
||
func newInitCommand() *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "init [TEMPLATE_PATH]", | ||
|
@@ -182,7 +22,7 @@ TEMPLATE_PATH optionally specifies which template to use. It can be one of the f | |
- 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/en/dev-tools/bundles/templates.html for more information on templates.`, nativeTemplateHelpDescriptions()), | ||
See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more information on templates.`, template.HelpDescriptions()), | ||
} | ||
|
||
var configFile string | ||
|
@@ -198,92 +38,32 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf | |
|
||
cmd.PreRunE = root.MustWorkspaceClient | ||
cmd.RunE = func(cmd *cobra.Command, args []string) error { | ||
if tag != "" && branch != "" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Moved over to |
||
return errors.New("only one of --tag or --branch can be specified") | ||
} | ||
|
||
// Git ref to use for template initialization | ||
ref := branch | ||
if tag != "" { | ||
ref = tag | ||
} | ||
|
||
ctx := cmd.Context() | ||
var templatePath string | ||
var templatePathOrUrl string | ||
if len(args) > 0 { | ||
templatePath = args[0] | ||
} else { | ||
var err error | ||
if !cmdio.IsPromptSupported(ctx) { | ||
return errors.New("please specify a template") | ||
} | ||
description, err := cmdio.SelectOrdered(ctx, nativeTemplateOptions(), "Template to use") | ||
if err != nil { | ||
return err | ||
} | ||
templatePath = getNativeTemplateByDescription(description) | ||
templatePathOrUrl = args[0] | ||
} | ||
|
||
outputFiler, err := constructOutputFiler(ctx, outputDir) | ||
if err != nil { | ||
return err | ||
r := template.Resolver{ | ||
TemplatePathOrUrl: templatePathOrUrl, | ||
ConfigFile: configFile, | ||
OutputDir: outputDir, | ||
TemplateDir: templateDir, | ||
Tag: tag, | ||
Branch: branch, | ||
} | ||
|
||
if templatePath == customTemplate { | ||
ctx := cmd.Context() | ||
tmpl, err := r.Resolve(ctx) | ||
if errors.Is(err, template.ErrCustomSelected) { | ||
cmdio.LogString(ctx, "Please specify a path or Git repository to use a custom template.") | ||
cmdio.LogString(ctx, "See https://docs.databricks.com/en/dev-tools/bundles/templates.html to learn more about custom templates.") | ||
return nil | ||
} | ||
|
||
// Expand templatePath to a git URL if it's an alias for a known native template | ||
// and we know it's git URL. | ||
if gitUrl := getUrlForNativeTemplate(templatePath); gitUrl != "" { | ||
templatePath = gitUrl | ||
} | ||
|
||
if !isRepoUrl(templatePath) { | ||
if templateDir != "" { | ||
return errors.New("--template-dir can only be used with a Git repository URL") | ||
} | ||
|
||
templateFS, err := getFsForNativeTemplate(templatePath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// If this is not a built-in template, then it must be a local file system path. | ||
if templateFS == nil { | ||
templateFS = os.DirFS(templatePath) | ||
} | ||
|
||
// 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 | ||
return template.Materialize(ctx, configFile, templateFS, outputFiler) | ||
} | ||
|
||
// Create a temporary directory with the name of the repository. The '*' | ||
// character is replaced by a random string in the generated temporary directory. | ||
repoDir, err := os.MkdirTemp("", repoName(templatePath)+"-*") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// start the spinner | ||
promptSpinner := cmdio.Spinner(ctx) | ||
promptSpinner <- "Downloading the template\n" | ||
|
||
// TODO: Add automated test that the downloaded git repo is cleaned up. | ||
// Clone the repository in the temporary directory | ||
err = git.Clone(ctx, templatePath, ref, repoDir) | ||
close(promptSpinner) | ||
if err != nil { | ||
return err | ||
} | ||
defer tmpl.Reader.Close() | ||
|
||
// Clean up downloaded repository once the template is materialized. | ||
defer os.RemoveAll(repoDir) | ||
templateFS := os.DirFS(filepath.Join(repoDir, templateDir)) | ||
return template.Materialize(ctx, configFile, templateFS, outputFiler) | ||
return tmpl.Writer.Materialize(ctx, tmpl.Reader) | ||
} | ||
return cmd | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved to
template.HelpDescriptions()