mirror of https://github.com/databricks/cli.git
202 lines
5.2 KiB
Go
202 lines
5.2 KiB
Go
package template
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"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/telemetry"
|
|
"github.com/databricks/cli/libs/telemetry/protos"
|
|
)
|
|
|
|
const (
|
|
libraryDirName = "library"
|
|
templateDirName = "template"
|
|
schemaFileName = "databricks_template_schema.json"
|
|
)
|
|
|
|
type Writer interface {
|
|
// Configure the writer with:
|
|
// 1. The path to the config file (if any) that contains input values for the
|
|
// template.
|
|
// 2. The output directory where the template will be materialized.
|
|
Configure(ctx context.Context, configPath, outputDir string) error
|
|
|
|
// Materialize the template to the local file system.
|
|
Materialize(ctx context.Context, r Reader) error
|
|
|
|
// Log telemetry associated with the initialization of this template.
|
|
LogTelemetry(ctx context.Context)
|
|
}
|
|
|
|
type defaultWriter struct {
|
|
configPath string
|
|
outputFiler filer.Filer
|
|
templateName TemplateName
|
|
|
|
// Internal state
|
|
config *config
|
|
renderer *renderer
|
|
}
|
|
|
|
func constructOutputFiler(ctx context.Context, outputDir string) (filer.Filer, error) {
|
|
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 (writer *defaultWriter) Configure(ctx context.Context, configPath, outputDir string) error {
|
|
writer.configPath = configPath
|
|
|
|
outputFiler, err := constructOutputFiler(ctx, outputDir)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
writer.outputFiler = outputFiler
|
|
return nil
|
|
}
|
|
|
|
func (writer *defaultWriter) promptForInput(ctx context.Context, reader Reader) error {
|
|
readerFs, err := reader.FS(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if _, err := fs.Stat(readerFs, schemaFileName); errors.Is(err, fs.ErrNotExist) {
|
|
return fmt.Errorf("not a bundle template: expected to find a template schema file at %s", schemaFileName)
|
|
}
|
|
|
|
writer.config, err = newConfig(ctx, readerFs, schemaFileName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Read and assign config values from file
|
|
if writer.configPath != "" {
|
|
err = writer.config.assignValuesFromFile(writer.configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
helpers := loadHelpers(ctx)
|
|
writer.renderer, err = newRenderer(ctx, writer.config.values, helpers, readerFs, templateDirName, libraryDirName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Print welcome message
|
|
welcome := writer.config.schema.WelcomeMessage
|
|
if welcome != "" {
|
|
welcome, err = writer.renderer.executeTemplate(welcome)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmdio.LogString(ctx, welcome)
|
|
}
|
|
|
|
// Prompt user for any missing config values. Assign default values if
|
|
// terminal is not TTY
|
|
err = writer.config.promptOrAssignDefaultValues(writer.renderer)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return writer.config.validate()
|
|
}
|
|
|
|
func (writer *defaultWriter) printSuccessMessage(ctx context.Context) error {
|
|
success := writer.config.schema.SuccessMessage
|
|
if success == "" {
|
|
cmdio.LogString(ctx, "✨ Successfully initialized template")
|
|
return nil
|
|
}
|
|
|
|
success, err := writer.renderer.executeTemplate(success)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
cmdio.LogString(ctx, success)
|
|
return nil
|
|
}
|
|
|
|
func (writer *defaultWriter) Materialize(ctx context.Context, reader Reader) error {
|
|
err := writer.promptForInput(ctx, reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Walk the template file tree and compute in-memory representations of the
|
|
// output files.
|
|
err = writer.renderer.walk()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Flush the output files to disk.
|
|
err = writer.renderer.persistToDisk(ctx, writer.outputFiler)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return writer.printSuccessMessage(ctx)
|
|
}
|
|
|
|
func (writer *defaultWriter) LogTelemetry(ctx context.Context) {
|
|
// We don't log template enum args in the default template writer.
|
|
// This is because this writer can be used for customer templates, and we
|
|
// don't want to log PII.
|
|
event := telemetry.DatabricksCliLog{
|
|
BundleInitEvent: &protos.BundleInitEvent{
|
|
Uuid: bundleUuid,
|
|
TemplateName: string(writer.templateName),
|
|
},
|
|
}
|
|
|
|
telemetry.Log(ctx, event)
|
|
}
|
|
|
|
type writerWithFullTelemetry struct {
|
|
defaultWriter
|
|
}
|
|
|
|
func (writer *writerWithFullTelemetry) LogTelemetry(ctx context.Context) {
|
|
templateEnumArgs := []protos.BundleInitTemplateEnumArg{}
|
|
for k, v := range writer.config.enumValues() {
|
|
templateEnumArgs = append(templateEnumArgs, protos.BundleInitTemplateEnumArg{
|
|
Key: k,
|
|
Value: v,
|
|
})
|
|
}
|
|
|
|
event := telemetry.DatabricksCliLog{
|
|
BundleInitEvent: &protos.BundleInitEvent{
|
|
Uuid: bundleUuid,
|
|
TemplateName: string(writer.templateName),
|
|
TemplateEnumArgs: templateEnumArgs,
|
|
},
|
|
}
|
|
|
|
telemetry.Log(ctx, event)
|
|
}
|