diff --git a/cmd/bundle/init.go b/cmd/bundle/init.go index 307e367d6..755a09b9f 100644 --- a/cmd/bundle/init.go +++ b/cmd/bundle/init.go @@ -36,6 +36,7 @@ See https://docs.databricks.com/en/dev-tools/bundles/templates.html for more inf cmd.Flags().StringVar(&branch, "tag", "", "Git tag to use for template initialization") cmd.Flags().StringVar(&tag, "branch", "", "Git branch to use for template initialization") + cmd.PreRunE = root.MustWorkspaceClient cmd.RunE = func(cmd *cobra.Command, args []string) error { r := template.Resolver{ TemplatePathOrUrl: args[0], diff --git a/libs/template/reader_test.go b/libs/template/reader_test.go index 74e70a5e1..f1e037fca 100644 --- a/libs/template/reader_test.go +++ b/libs/template/reader_test.go @@ -32,10 +32,10 @@ func TestBuiltInReader(t *testing.T) { // message defined. fd, err := fs.Open("databricks_template_schema.json") require.NoError(t, err) - defer fd.Close() b, err := io.ReadAll(fd) require.NoError(t, err) assert.Contains(t, string(b), "welcome_message") + assert.NoError(t, fd.Close()) } r := &builtinReader{name: "doesnotexist"} @@ -78,10 +78,10 @@ func TestGitUrlReader(t *testing.T) { // Assert the fs returned is rooted at the templateDir. fd, err := fs.Open("somefile") require.NoError(t, err) - defer fd.Close() b, err := io.ReadAll(fd) require.NoError(t, err) assert.Equal(t, "somecontent", string(b)) + assert.NoError(t, fd.Close()) // Assert the FS is cached. cloneFunc should not be called again. _, err = r.FS(ctx) @@ -106,10 +106,10 @@ func TestLocalReader(t *testing.T) { // Assert the fs returned is rooted at correct location. fd, err := fs.Open("somefile") require.NoError(t, err) - defer fd.Close() b, err := io.ReadAll(fd) require.NoError(t, err) assert.Equal(t, "somecontent", string(b)) + assert.NoError(t, fd.Close()) // Assert close does not error assert.NoError(t, r.Close()) diff --git a/libs/template/resolve.go b/libs/template/resolve.go index 7d3b68bff..88299b808 100644 --- a/libs/template/resolve.go +++ b/libs/template/resolve.go @@ -8,12 +8,24 @@ import ( ) type Resolver struct { + // One of the following three: + // 1. Path to a local template directory. + // 2. URL to a Git repository containing a template. + // 3. Name of a built-in template. TemplatePathOrUrl string - ConfigFile string - OutputDir string - TemplateDir string - Tag string - Branch string + + // Path to a JSON file containing the configuration values to be used for + // template initialization. + ConfigFile string + + // Directory to write the initialized template to. + OutputDir string + + // Directory path within a Git repository containing the template. + TemplateDir string + + Tag string + Branch string } var ErrCustomSelected = errors.New("custom template selected") @@ -32,23 +44,32 @@ func (r Resolver) Resolve(ctx context.Context) (*Template, error) { ref = r.Tag } - var tmpl *Template + var err error + var templateName TemplateName + if r.TemplatePathOrUrl == "" { // Prompt the user to select a template // if a template path or URL is not provided. - tmplId, err := SelectTemplate(ctx) + templateName, err = SelectTemplate(ctx) if err != nil { return nil, err } + } - if tmplId == Custom { - return nil, ErrCustomSelected - } + templateName = TemplateName(r.TemplatePathOrUrl) - tmpl = Get(tmplId) - } else { - // Based on the provided template path or URL, - // configure a reader for the template. + // User should not directly select "custom" and instead should provide the + // file path or the Git URL for the template directly. + // Custom is just for internal representation purposes. + if templateName == Custom { + return nil, ErrCustomSelected + } + + tmpl := Get(templateName) + + // If the user directory provided a template path or URL that is not a built-in template, + // then configure a reader for the template. + if tmpl == nil { tmpl = Get(Custom) if isRepoUrl(r.TemplatePathOrUrl) { tmpl.Reader = &gitReader{ @@ -62,9 +83,10 @@ func (r Resolver) Resolve(ctx context.Context) (*Template, error) { path: r.TemplatePathOrUrl, } } + } - err := tmpl.Writer.Configure(ctx, r.ConfigFile, r.OutputDir) + err = tmpl.Writer.Configure(ctx, r.ConfigFile, r.OutputDir) if err != nil { return nil, err } diff --git a/libs/template/template.go b/libs/template/template.go index 75b913d87..46bdef57a 100644 --- a/libs/template/template.go +++ b/libs/template/template.go @@ -96,7 +96,6 @@ func options() []cmdio.Tuple { return names } -// TODO CONTINUE defining the methods that the init command will finally rely on. func SelectTemplate(ctx context.Context) (TemplateName, error) { if !cmdio.IsPromptSupported(ctx) { return "", fmt.Errorf("please specify a template") diff --git a/libs/template/writer.go b/libs/template/writer.go index b0ec1ad46..f0b7ae6de 100644 --- a/libs/template/writer.go +++ b/libs/template/writer.go @@ -14,28 +14,7 @@ import ( "github.com/databricks/cli/libs/filer" ) -// TODO: Retain coverage for the missing schema test case -// func TestMaterializeForNonTemplateDirectory(t *testing.T) { -// tmpDir := t.TempDir() -// w, err := databricks.NewWorkspaceClient(&databricks.Config{}) -// require.NoError(t, err) -// ctx := root.SetWorkspaceClient(context.Background(), w) - -// tmpl := TemplateX{ -// TemplateOpts: TemplateOpts{ -// ConfigFilePath: "", -// TemplateFS: os.DirFS(tmpDir), -// OutputFiler: nil, -// }, -// } - -// // Try to materialize a non-template directory. -// err = tmpl.Materialize(ctx) -// assert.EqualError(t, err, fmt.Sprintf("not a bundle template: expected to find a template schema file at %s", schemaFileName)) -// } - -// TODO: Add tests for these writers, mocking the cmdio library -// at the same time. +// TODO: Add some golden tests for these. const ( libraryDirName = "library" templateDirName = "template" @@ -77,7 +56,7 @@ func constructOutputFiler(ctx context.Context, outputDir string) (filer.Filer, e return filer.NewLocalClient(outputDir) } -func (tmpl *defaultWriter) Configure(ctx context.Context, configPath string, outputDir string) error { +func (tmpl *defaultWriter) Configure(ctx context.Context, configPath, outputDir string) error { tmpl.configPath = configPath outputFiler, err := constructOutputFiler(ctx, outputDir) diff --git a/libs/template/writer_test.go b/libs/template/writer_test.go new file mode 100644 index 000000000..7c19a316d --- /dev/null +++ b/libs/template/writer_test.go @@ -0,0 +1,50 @@ +package template + +import ( + "context" + "testing" + + "github.com/databricks/cli/cmd/root" + "github.com/databricks/cli/libs/dbr" + "github.com/databricks/cli/libs/filer" + "github.com/databricks/databricks-sdk-go" + workspaceConfig "github.com/databricks/databricks-sdk-go/config" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestDefaultWriterConfigure(t *testing.T) { + // Test on local file system. + w1 := &defaultWriter{} + err := w1.Configure(context.Background(), "/foo/bar", "/out/abc") + assert.NoError(t, err) + + assert.Equal(t, "/foo/bar", w1.configPath) + assert.IsType(t, &filer.LocalClient{}, w1.outputFiler) + + // Test on DBR + ctx := dbr.MockRuntime(context.Background(), true) + ctx = root.SetWorkspaceClient(ctx, &databricks.WorkspaceClient{ + Config: &workspaceConfig.Config{Host: "https://myhost.com"}, + }) + w2 := &defaultWriter{} + err = w2.Configure(ctx, "/foo/bar", "/Workspace/out/abc") + assert.NoError(t, err) + + assert.Equal(t, "/foo/bar", w2.configPath) + assert.IsType(t, &filer.WorkspaceFilesClient{}, w2.outputFiler) +} + +func TestMaterializeForNonTemplateDirectory(t *testing.T) { + tmpDir1 := t.TempDir() + tmpDir2 := t.TempDir() + ctx := context.Background() + + w := &defaultWriter{} + err := w.Configure(ctx, "/foo/bar", tmpDir1) + require.NoError(t, err) + + // Try to materialize a non-template directory. + err = w.Materialize(ctx, &localReader{path: tmpDir2}) + assert.EqualError(t, err, "not a bundle template: expected to find a template schema file at databricks_template_schema.json") +}