mirror of https://github.com/databricks/cli.git
Make bundle validation print text output by default (#1335)
## Changes It now shows human-readable warnings and validation status. ## Tests * Manual tests against many examples. * Errors still return immediately.
This commit is contained in:
parent
b4e2645942
commit
04cbc7171e
|
@ -2,15 +2,129 @@ package bundle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/databricks/cli/bundle"
|
"github.com/databricks/cli/bundle"
|
||||||
"github.com/databricks/cli/bundle/phases"
|
"github.com/databricks/cli/bundle/phases"
|
||||||
"github.com/databricks/cli/cmd/bundle/utils"
|
"github.com/databricks/cli/cmd/bundle/utils"
|
||||||
"github.com/databricks/cli/cmd/root"
|
"github.com/databricks/cli/cmd/root"
|
||||||
"github.com/databricks/cli/libs/log"
|
"github.com/databricks/cli/libs/diag"
|
||||||
|
"github.com/databricks/cli/libs/flags"
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var validateFuncMap = template.FuncMap{
|
||||||
|
"red": color.RedString,
|
||||||
|
"green": color.GreenString,
|
||||||
|
"blue": color.BlueString,
|
||||||
|
"yellow": color.YellowString,
|
||||||
|
"magenta": color.MagentaString,
|
||||||
|
"cyan": color.CyanString,
|
||||||
|
"bold": func(format string, a ...interface{}) string {
|
||||||
|
return color.New(color.Bold).Sprintf(format, a...)
|
||||||
|
},
|
||||||
|
"italic": func(format string, a ...interface{}) string {
|
||||||
|
return color.New(color.Italic).Sprintf(format, a...)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorTemplate = `{{ "Error" | red }}: {{ .Summary }}
|
||||||
|
{{ "at " }}{{ .Path.String | green }}
|
||||||
|
{{ "in " }}{{ .Location.String | cyan }}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
const warningTemplate = `{{ "Warning" | yellow }}: {{ .Summary }}
|
||||||
|
{{ "at " }}{{ .Path.String | green }}
|
||||||
|
{{ "in " }}{{ .Location.String | cyan }}
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
const summaryTemplate = `Name: {{ .Config.Bundle.Name | bold }}
|
||||||
|
Target: {{ .Config.Bundle.Target | bold }}
|
||||||
|
Workspace:
|
||||||
|
Host: {{ .Config.Workspace.Host | bold }}
|
||||||
|
User: {{ .Config.Workspace.CurrentUser.UserName | bold }}
|
||||||
|
Path: {{ .Config.Workspace.RootPath | bold }}
|
||||||
|
|
||||||
|
{{ .Trailer }}
|
||||||
|
`
|
||||||
|
|
||||||
|
func pluralize(n int, singular, plural string) string {
|
||||||
|
if n == 1 {
|
||||||
|
return fmt.Sprintf("%d %s", n, singular)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%d %s", n, plural)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTrailer(diags diag.Diagnostics) string {
|
||||||
|
parts := []string{}
|
||||||
|
if errors := len(diags.Filter(diag.Error)); errors > 0 {
|
||||||
|
parts = append(parts, color.RedString(pluralize(errors, "error", "errors")))
|
||||||
|
}
|
||||||
|
if warnings := len(diags.Filter(diag.Warning)); warnings > 0 {
|
||||||
|
parts = append(parts, color.YellowString(pluralize(warnings, "warning", "warnings")))
|
||||||
|
}
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return fmt.Sprintf("Found %s", strings.Join(parts, " and "))
|
||||||
|
} else {
|
||||||
|
return color.GreenString("Validation OK!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderTextOutput(cmd *cobra.Command, b *bundle.Bundle, diags diag.Diagnostics) error {
|
||||||
|
errorT := template.Must(template.New("error").Funcs(validateFuncMap).Parse(errorTemplate))
|
||||||
|
warningT := template.Must(template.New("warning").Funcs(validateFuncMap).Parse(warningTemplate))
|
||||||
|
|
||||||
|
// Print errors and warnings.
|
||||||
|
for _, d := range diags {
|
||||||
|
var t *template.Template
|
||||||
|
switch d.Severity {
|
||||||
|
case diag.Error:
|
||||||
|
t = errorT
|
||||||
|
case diag.Warning:
|
||||||
|
t = warningT
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make file relative to bundle root
|
||||||
|
if d.Location.File != "" {
|
||||||
|
out, _ := filepath.Rel(b.RootPath, d.Location.File)
|
||||||
|
d.Location.File = out
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render the diagnostic with the appropriate template.
|
||||||
|
err := t.Execute(cmd.OutOrStdout(), d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print validation summary.
|
||||||
|
t := template.Must(template.New("summary").Funcs(validateFuncMap).Parse(summaryTemplate))
|
||||||
|
err := t.Execute(cmd.OutOrStdout(), map[string]any{
|
||||||
|
"Config": b.Config,
|
||||||
|
"Trailer": buildTrailer(diags),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return diags.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderJsonOutput(cmd *cobra.Command, b *bundle.Bundle, diags diag.Diagnostics) error {
|
||||||
|
buf, err := json.MarshalIndent(b.Config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd.OutOrStdout().Write(buf)
|
||||||
|
return diags.Error()
|
||||||
|
}
|
||||||
|
|
||||||
func newValidateCommand() *cobra.Command {
|
func newValidateCommand() *cobra.Command {
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "validate",
|
Use: "validate",
|
||||||
|
@ -25,23 +139,19 @@ func newValidateCommand() *cobra.Command {
|
||||||
return diags.Error()
|
return diags.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
diags = bundle.Apply(ctx, b, phases.Initialize())
|
diags = diags.Extend(bundle.Apply(ctx, b, phases.Initialize()))
|
||||||
if err := diags.Error(); err != nil {
|
if err := diags.Error(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Until we change up the output of this command to be a text representation,
|
switch root.OutputType(cmd) {
|
||||||
// we'll just output all diagnostics as debug logs.
|
case flags.OutputText:
|
||||||
for _, diag := range diags {
|
return renderTextOutput(cmd, b, diags)
|
||||||
log.Debugf(cmd.Context(), "[%s]: %s", diag.Location, diag.Summary)
|
case flags.OutputJSON:
|
||||||
|
return renderJsonOutput(cmd, b, diags)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown output type %s", root.OutputType(cmd))
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := json.MarshalIndent(b.Config, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd.OutOrStdout().Write(buf)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
|
|
|
@ -101,3 +101,14 @@ func (ds Diagnostics) Error() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter returns a new list of diagnostics that match the specified severity.
|
||||||
|
func (ds Diagnostics) Filter(severity Severity) Diagnostics {
|
||||||
|
var out Diagnostics
|
||||||
|
for _, d := range ds {
|
||||||
|
if d.Severity == severity {
|
||||||
|
out = append(out, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue