2023-08-07 13:14:25 +00:00
|
|
|
package bundle
|
|
|
|
|
|
|
|
import (
|
2023-08-25 09:03:42 +00:00
|
|
|
"errors"
|
2023-11-28 09:04:06 +00:00
|
|
|
"fmt"
|
2023-08-07 13:14:25 +00:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2023-11-28 09:04:06 +00:00
|
|
|
"slices"
|
2023-08-07 13:14:25 +00:00
|
|
|
"strings"
|
|
|
|
|
2023-08-25 09:03:42 +00:00
|
|
|
"github.com/databricks/cli/cmd/root"
|
2023-09-05 13:57:01 +00:00
|
|
|
"github.com/databricks/cli/libs/cmdio"
|
2023-08-07 13:14:25 +00:00
|
|
|
"github.com/databricks/cli/libs/git"
|
|
|
|
"github.com/databricks/cli/libs/template"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
|
|
|
var gitUrlPrefixes = []string{
|
|
|
|
"https://",
|
|
|
|
"git@",
|
|
|
|
}
|
|
|
|
|
2023-11-28 09:04:06 +00:00
|
|
|
type nativeTemplate struct {
|
|
|
|
name string
|
|
|
|
gitUrl string
|
|
|
|
description string
|
|
|
|
aliases []string
|
|
|
|
}
|
|
|
|
|
2023-12-27 12:03:08 +00:00
|
|
|
const customTemplate = "custom..."
|
|
|
|
|
2023-11-28 09:04:06 +00:00
|
|
|
var nativeTemplates = []nativeTemplate{
|
|
|
|
{
|
|
|
|
name: "default-python",
|
2023-12-27 12:03:08 +00:00
|
|
|
description: "The default Python template for Notebooks / Delta Live Tables / Workflows",
|
2023-11-28 09:04:06 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "mlops-stacks",
|
|
|
|
gitUrl: "https://github.com/databricks/mlops-stacks",
|
2023-12-27 12:03:08 +00:00
|
|
|
description: "The Databricks MLOps Stacks template (github.com/databricks/mlops-stacks)",
|
2023-11-28 09:04:06 +00:00
|
|
|
aliases: []string{"mlops-stack"},
|
|
|
|
},
|
2023-12-27 12:03:08 +00:00
|
|
|
{
|
|
|
|
name: customTemplate,
|
|
|
|
description: "Bring your own template",
|
|
|
|
},
|
2023-11-28 09:04:06 +00:00
|
|
|
}
|
|
|
|
|
2023-12-27 12:03:08 +00:00
|
|
|
// Return template descriptions for command-line help
|
|
|
|
func nativeTemplateHelpDescriptions() string {
|
2023-11-28 09:04:06 +00:00
|
|
|
var lines []string
|
|
|
|
for _, template := range nativeTemplates {
|
2023-12-27 12:03:08 +00:00
|
|
|
if template.name != customTemplate {
|
|
|
|
lines = append(lines, fmt.Sprintf("- %s: %s", template.name, template.description))
|
|
|
|
}
|
2023-11-28 09:04:06 +00:00
|
|
|
}
|
|
|
|
return strings.Join(lines, "\n")
|
|
|
|
}
|
|
|
|
|
2023-12-27 12:03:08 +00:00
|
|
|
// Return template options for an interactive prompt
|
|
|
|
func nativeTemplateOptions() []cmdio.Tuple {
|
|
|
|
names := make([]cmdio.Tuple, 0, len(nativeTemplates))
|
2023-11-28 09:04:06 +00:00
|
|
|
for _, template := range nativeTemplates {
|
2023-12-27 12:03:08 +00:00
|
|
|
tuple := cmdio.Tuple{
|
|
|
|
Name: template.name,
|
|
|
|
Id: template.description,
|
|
|
|
}
|
|
|
|
names = append(names, tuple)
|
2023-11-28 09:04:06 +00:00
|
|
|
}
|
|
|
|
return names
|
|
|
|
}
|
|
|
|
|
2023-12-27 12:03:08 +00:00
|
|
|
func getNativeTemplateByDescription(description string) string {
|
|
|
|
for _, template := range nativeTemplates {
|
|
|
|
if template.description == description {
|
|
|
|
return template.name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2023-11-28 09:04:06 +00:00
|
|
|
func getUrlForNativeTemplate(name string) string {
|
|
|
|
for _, template := range nativeTemplates {
|
|
|
|
if template.name == name {
|
|
|
|
return template.gitUrl
|
|
|
|
}
|
|
|
|
if slices.Contains(template.aliases, name) {
|
|
|
|
return template.gitUrl
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ""
|
2023-10-16 08:36:01 +00:00
|
|
|
}
|
|
|
|
|
2023-08-07 13:14:25 +00:00
|
|
|
func isRepoUrl(url string) bool {
|
|
|
|
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 git@github.com:databricks/cli.git
|
|
|
|
// the name would be "cli.git"
|
|
|
|
func repoName(url string) string {
|
|
|
|
parts := strings.Split(strings.TrimRight(url, "/"), "/")
|
|
|
|
return parts[len(parts)-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
func newInitCommand() *cobra.Command {
|
|
|
|
cmd := &cobra.Command{
|
2023-08-25 09:03:42 +00:00
|
|
|
Use: "init [TEMPLATE_PATH]",
|
2023-10-19 07:08:36 +00:00
|
|
|
Short: "Initialize using a bundle template",
|
2023-08-25 09:03:42 +00:00
|
|
|
Args: cobra.MaximumNArgs(1),
|
2023-11-28 09:04:06 +00:00
|
|
|
Long: fmt.Sprintf(`Initialize using a bundle template.
|
2023-10-19 07:08:36 +00:00
|
|
|
|
|
|
|
TEMPLATE_PATH optionally specifies which template to use. It can be one of the following:
|
2023-11-28 09:04:06 +00:00
|
|
|
%s
|
2023-10-19 07:08:36 +00:00
|
|
|
- a local file system path with a template directory
|
|
|
|
- a Git repository URL, e.g. https://github.com/my/repository
|
|
|
|
|
2023-12-27 12:03:08 +00:00
|
|
|
See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more information on templates.`, nativeTemplateHelpDescriptions()),
|
2023-08-07 13:14:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var configFile string
|
2023-08-17 20:32:30 +00:00
|
|
|
var outputDir string
|
2023-08-18 09:29:48 +00:00
|
|
|
var templateDir string
|
2023-11-14 22:27:58 +00:00
|
|
|
var tag string
|
|
|
|
var branch string
|
2023-08-07 13:14:25 +00:00
|
|
|
cmd.Flags().StringVar(&configFile, "config-file", "", "File containing input parameters for template initialization.")
|
2023-10-19 07:08:36 +00:00
|
|
|
cmd.Flags().StringVar(&templateDir, "template-dir", "", "Directory path within a Git repository containing the template.")
|
2023-08-17 20:32:30 +00:00
|
|
|
cmd.Flags().StringVar(&outputDir, "output-dir", "", "Directory to write the initialized template to.")
|
2023-11-14 22:27:58 +00:00
|
|
|
cmd.Flags().StringVar(&branch, "tag", "", "Git tag to use for template initialization")
|
|
|
|
cmd.Flags().StringVar(&tag, "branch", "", "Git branch to use for template initialization")
|
2023-08-07 13:14:25 +00:00
|
|
|
|
2023-08-25 09:03:42 +00:00
|
|
|
cmd.PreRunE = root.MustWorkspaceClient
|
2023-08-07 13:14:25 +00:00
|
|
|
cmd.RunE = func(cmd *cobra.Command, args []string) error {
|
2023-11-14 22:27:58 +00:00
|
|
|
if tag != "" && branch != "" {
|
|
|
|
return errors.New("only one of --tag or --branch can be specified")
|
|
|
|
}
|
2023-10-19 07:08:36 +00:00
|
|
|
|
2023-11-14 22:27:58 +00:00
|
|
|
// Git ref to use for template initialization
|
|
|
|
ref := branch
|
|
|
|
if tag != "" {
|
|
|
|
ref = tag
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx := cmd.Context()
|
2023-08-25 09:03:42 +00:00
|
|
|
var templatePath string
|
|
|
|
if len(args) > 0 {
|
|
|
|
templatePath = args[0]
|
|
|
|
} else {
|
|
|
|
var err error
|
2023-12-18 15:01:59 +00:00
|
|
|
if !cmdio.IsPromptSupported(ctx) {
|
2023-08-25 09:03:42 +00:00
|
|
|
return errors.New("please specify a template")
|
|
|
|
}
|
2023-12-27 12:03:08 +00:00
|
|
|
description, err := cmdio.SelectOrdered(ctx, nativeTemplateOptions(), "Template to use")
|
2023-08-25 09:03:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-12-27 12:03:08 +00:00
|
|
|
templatePath = getNativeTemplateByDescription(description)
|
|
|
|
}
|
|
|
|
|
|
|
|
if templatePath == customTemplate {
|
|
|
|
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
|
2023-08-25 09:03:42 +00:00
|
|
|
}
|
2023-08-07 13:14:25 +00:00
|
|
|
|
2023-11-28 09:04:06 +00:00
|
|
|
// 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
|
2023-10-16 08:36:01 +00:00
|
|
|
}
|
|
|
|
|
2023-08-07 13:14:25 +00:00
|
|
|
if !isRepoUrl(templatePath) {
|
2023-10-19 07:08:36 +00:00
|
|
|
if templateDir != "" {
|
|
|
|
return errors.New("--template-dir can only be used with a Git repository URL")
|
|
|
|
}
|
2023-08-07 13:14:25 +00:00
|
|
|
// 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
|
2023-08-17 20:32:30 +00:00
|
|
|
return template.Materialize(ctx, configFile, templatePath, outputDir)
|
2023-08-07 13:14:25 +00:00
|
|
|
}
|
|
|
|
|
2023-09-11 10:22:05 +00:00
|
|
|
// 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)+"-*")
|
2023-08-07 13:14:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// TODO: Add automated test that the downloaded git repo is cleaned up.
|
2023-09-11 10:22:05 +00:00
|
|
|
// Clone the repository in the temporary directory
|
2023-11-14 22:27:58 +00:00
|
|
|
err = git.Clone(ctx, templatePath, ref, repoDir)
|
2023-08-07 13:14:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-09-11 10:22:05 +00:00
|
|
|
// Clean up downloaded repository once the template is materialized.
|
|
|
|
defer os.RemoveAll(repoDir)
|
2023-08-18 09:29:48 +00:00
|
|
|
return template.Materialize(ctx, configFile, filepath.Join(repoDir, templateDir), outputDir)
|
2023-08-07 13:14:25 +00:00
|
|
|
}
|
|
|
|
return cmd
|
|
|
|
}
|