mirror of https://github.com/databricks/cli.git
Remove dependency on global state for the root command (#606)
## Changes This change is another step towards a CLI without globals. Also see #595. The flags for the root command are now encapsulated in struct types. ## Tests Unit tests pass.
This commit is contained in:
parent
cfff140815
commit
ec892aa11c
|
@ -47,7 +47,7 @@ var runCmd = &cobra.Command{
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if output != nil {
|
if output != nil {
|
||||||
switch root.OutputType() {
|
switch root.OutputType(cmd) {
|
||||||
case flags.OutputText:
|
case flags.OutputText:
|
||||||
resultString, err := output.String()
|
resultString, err := output.String()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -61,7 +61,7 @@ var runCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
cmd.OutOrStdout().Write(b)
|
cmd.OutOrStdout().Write(b)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown output type %s", root.OutputType())
|
return fmt.Errorf("unknown output type %s", root.OutputType(cmd))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -21,9 +21,9 @@ var workspaceClient int
|
||||||
var accountClient int
|
var accountClient int
|
||||||
var currentUser int
|
var currentUser int
|
||||||
|
|
||||||
func init() {
|
func initProfileFlag(cmd *cobra.Command) {
|
||||||
RootCmd.PersistentFlags().StringP("profile", "p", "", "~/.databrickscfg profile")
|
cmd.PersistentFlags().StringP("profile", "p", "", "~/.databrickscfg profile")
|
||||||
RootCmd.RegisterFlagCompletionFunc("profile", databrickscfg.ProfileCompletion)
|
cmd.RegisterFlagCompletionFunc("profile", databrickscfg.ProfileCompletion)
|
||||||
}
|
}
|
||||||
|
|
||||||
func MustAccountClient(cmd *cobra.Command, args []string) error {
|
func MustAccountClient(cmd *cobra.Command, args []string) error {
|
||||||
|
|
|
@ -118,8 +118,8 @@ func environmentCompletion(cmd *cobra.Command, args []string, toComplete string)
|
||||||
return maps.Keys(b.Config.Environments), cobra.ShellCompDirectiveDefault
|
return maps.Keys(b.Config.Environments), cobra.ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func initEnvironmentFlag(cmd *cobra.Command) {
|
||||||
// To operate in the context of a bundle, all commands must take an "environment" parameter.
|
// To operate in the context of a bundle, all commands must take an "environment" parameter.
|
||||||
RootCmd.PersistentFlags().StringP("environment", "e", "", "bundle environment to use (if applicable)")
|
cmd.PersistentFlags().StringP("environment", "e", "", "bundle environment to use (if applicable)")
|
||||||
RootCmd.RegisterFlagCompletionFunc("environment", environmentCompletion)
|
cmd.RegisterFlagCompletionFunc("environment", environmentCompletion)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/databricks/cli/bundle"
|
"github.com/databricks/cli/bundle"
|
||||||
"github.com/databricks/cli/bundle/config"
|
"github.com/databricks/cli/bundle/config"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,15 +28,18 @@ func setupDatabricksCfg(t *testing.T) {
|
||||||
t.Setenv(homeEnvVar, tempHomeDir)
|
t.Setenv(homeEnvVar, tempHomeDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setup(t *testing.T, host string) *bundle.Bundle {
|
func emptyCommand(t *testing.T) *cobra.Command {
|
||||||
|
ctx := context.Background()
|
||||||
|
cmd := &cobra.Command{}
|
||||||
|
cmd.SetContext(ctx)
|
||||||
|
initProfileFlag(cmd)
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func setup(t *testing.T, cmd *cobra.Command, host string) *bundle.Bundle {
|
||||||
setupDatabricksCfg(t)
|
setupDatabricksCfg(t)
|
||||||
|
|
||||||
ctx := context.Background()
|
err := configureBundle(cmd, []string{"validate"}, func() (*bundle.Bundle, error) {
|
||||||
RootCmd.SetContext(ctx)
|
|
||||||
_, err := initializeLogger(ctx)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
err = configureBundle(RootCmd, []string{"validate"}, func() (*bundle.Bundle, error) {
|
|
||||||
return &bundle.Bundle{
|
return &bundle.Bundle{
|
||||||
Config: config.Root{
|
Config: config.Root{
|
||||||
Bundle: config.Bundle{
|
Bundle: config.Bundle{
|
||||||
|
@ -48,46 +52,50 @@ func setup(t *testing.T, host string) *bundle.Bundle {
|
||||||
}, nil
|
}, nil
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
return bundle.Get(cmd.Context())
|
||||||
return bundle.Get(RootCmd.Context())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBundleConfigureDefault(t *testing.T) {
|
func TestBundleConfigureDefault(t *testing.T) {
|
||||||
b := setup(t, "https://x.com")
|
cmd := emptyCommand(t)
|
||||||
|
b := setup(t, cmd, "https://x.com")
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
b.WorkspaceClient()
|
b.WorkspaceClient()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBundleConfigureWithMultipleMatches(t *testing.T) {
|
func TestBundleConfigureWithMultipleMatches(t *testing.T) {
|
||||||
b := setup(t, "https://a.com")
|
cmd := emptyCommand(t)
|
||||||
|
b := setup(t, cmd, "https://a.com")
|
||||||
assert.Panics(t, func() {
|
assert.Panics(t, func() {
|
||||||
b.WorkspaceClient()
|
b.WorkspaceClient()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBundleConfigureWithNonExistentProfileFlag(t *testing.T) {
|
func TestBundleConfigureWithNonExistentProfileFlag(t *testing.T) {
|
||||||
RootCmd.Flag("profile").Value.Set("NOEXIST")
|
cmd := emptyCommand(t)
|
||||||
|
cmd.Flag("profile").Value.Set("NOEXIST")
|
||||||
|
|
||||||
b := setup(t, "https://x.com")
|
b := setup(t, cmd, "https://x.com")
|
||||||
assert.PanicsWithError(t, "no matching config profiles found", func() {
|
assert.PanicsWithError(t, "no matching config profiles found", func() {
|
||||||
b.WorkspaceClient()
|
b.WorkspaceClient()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBundleConfigureWithMismatchedProfile(t *testing.T) {
|
func TestBundleConfigureWithMismatchedProfile(t *testing.T) {
|
||||||
RootCmd.Flag("profile").Value.Set("PROFILE-1")
|
cmd := emptyCommand(t)
|
||||||
|
cmd.Flag("profile").Value.Set("PROFILE-1")
|
||||||
|
|
||||||
b := setup(t, "https://x.com")
|
b := setup(t, cmd, "https://x.com")
|
||||||
assert.PanicsWithError(t, "config host mismatch: profile uses host https://a.com, but CLI configured to use https://x.com", func() {
|
assert.PanicsWithError(t, "config host mismatch: profile uses host https://a.com, but CLI configured to use https://x.com", func() {
|
||||||
b.WorkspaceClient()
|
b.WorkspaceClient()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBundleConfigureWithCorrectProfile(t *testing.T) {
|
func TestBundleConfigureWithCorrectProfile(t *testing.T) {
|
||||||
RootCmd.Flag("profile").Value.Set("PROFILE-1")
|
cmd := emptyCommand(t)
|
||||||
|
cmd.Flag("profile").Value.Set("PROFILE-1")
|
||||||
|
|
||||||
b := setup(t, "https://a.com")
|
b := setup(t, cmd, "https://a.com")
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
b.WorkspaceClient()
|
b.WorkspaceClient()
|
||||||
})
|
})
|
||||||
|
@ -99,7 +107,8 @@ func TestBundleConfigureWithMismatchedProfileEnvVariable(t *testing.T) {
|
||||||
t.Setenv("DATABRICKS_CONFIG_PROFILE", "")
|
t.Setenv("DATABRICKS_CONFIG_PROFILE", "")
|
||||||
})
|
})
|
||||||
|
|
||||||
b := setup(t, "https://x.com")
|
cmd := emptyCommand(t)
|
||||||
|
b := setup(t, cmd, "https://x.com")
|
||||||
assert.PanicsWithError(t, "config host mismatch: profile uses host https://a.com, but CLI configured to use https://x.com", func() {
|
assert.PanicsWithError(t, "config host mismatch: profile uses host https://a.com, but CLI configured to use https://x.com", func() {
|
||||||
b.WorkspaceClient()
|
b.WorkspaceClient()
|
||||||
})
|
})
|
||||||
|
@ -110,9 +119,11 @@ func TestBundleConfigureWithProfileFlagAndEnvVariable(t *testing.T) {
|
||||||
t.Cleanup(func() {
|
t.Cleanup(func() {
|
||||||
t.Setenv("DATABRICKS_CONFIG_PROFILE", "")
|
t.Setenv("DATABRICKS_CONFIG_PROFILE", "")
|
||||||
})
|
})
|
||||||
RootCmd.Flag("profile").Value.Set("PROFILE-1")
|
|
||||||
|
|
||||||
b := setup(t, "https://a.com")
|
cmd := emptyCommand(t)
|
||||||
|
cmd.Flag("profile").Value.Set("PROFILE-1")
|
||||||
|
|
||||||
|
b := setup(t, cmd, "https://a.com")
|
||||||
assert.NotPanics(t, func() {
|
assert.NotPanics(t, func() {
|
||||||
b.WorkspaceClient()
|
b.WorkspaceClient()
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,32 +10,43 @@ import (
|
||||||
|
|
||||||
const envOutputFormat = "DATABRICKS_OUTPUT_FORMAT"
|
const envOutputFormat = "DATABRICKS_OUTPUT_FORMAT"
|
||||||
|
|
||||||
var outputType flags.Output = flags.OutputText
|
type outputFlag struct {
|
||||||
|
output flags.Output
|
||||||
|
}
|
||||||
|
|
||||||
|
func initOutputFlag(cmd *cobra.Command) *outputFlag {
|
||||||
|
f := outputFlag{
|
||||||
|
output: flags.OutputText,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Configure defaults from environment, if applicable.
|
// Configure defaults from environment, if applicable.
|
||||||
// If the provided value is invalid it is ignored.
|
// If the provided value is invalid it is ignored.
|
||||||
if v, ok := os.LookupEnv(envOutputFormat); ok {
|
if v, ok := os.LookupEnv(envOutputFormat); ok {
|
||||||
outputType.Set(v)
|
f.output.Set(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
RootCmd.PersistentFlags().VarP(&outputType, "output", "o", "output type: text or json")
|
cmd.PersistentFlags().VarP(&f.output, "output", "o", "output type: text or json")
|
||||||
|
return &f
|
||||||
}
|
}
|
||||||
|
|
||||||
func OutputType() flags.Output {
|
func OutputType(cmd *cobra.Command) flags.Output {
|
||||||
return outputType
|
f, ok := cmd.Flag("output").Value.(*flags.Output)
|
||||||
|
if !ok {
|
||||||
|
panic("output flag not defined")
|
||||||
|
}
|
||||||
|
|
||||||
|
return *f
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeIO(cmd *cobra.Command) error {
|
func (f *outputFlag) initializeIO(cmd *cobra.Command) error {
|
||||||
var template string
|
var template string
|
||||||
if cmd.Annotations != nil {
|
if cmd.Annotations != nil {
|
||||||
// rely on zeroval being an empty string
|
// rely on zeroval being an empty string
|
||||||
template = cmd.Annotations["template"]
|
template = cmd.Annotations["template"]
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdIO := cmdio.NewIO(outputType, cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr(), template)
|
cmdIO := cmdio.NewIO(f.output, cmd.InOrStdin(), cmd.OutOrStdout(), cmd.ErrOrStderr(), template)
|
||||||
ctx := cmdio.InContext(cmd.Context(), cmdIO)
|
ctx := cmdio.InContext(cmd.Context(), cmdIO)
|
||||||
cmd.SetContext(ctx)
|
cmd.SetContext(ctx)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/databricks/cli/libs/flags"
|
"github.com/databricks/cli/libs/flags"
|
||||||
"github.com/databricks/cli/libs/log"
|
"github.com/databricks/cli/libs/log"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/exp/slog"
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -66,12 +67,18 @@ func (l *friendlyHandler) Handle(ctx context.Context, rec slog.Record) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeLogHandler(opts slog.HandlerOptions) (slog.Handler, error) {
|
type logFlags struct {
|
||||||
switch logOutput {
|
file flags.LogFileFlag
|
||||||
|
level flags.LogLevelFlag
|
||||||
|
output flags.Output
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *logFlags) makeLogHandler(opts slog.HandlerOptions) (slog.Handler, error) {
|
||||||
|
switch f.output {
|
||||||
case flags.OutputJSON:
|
case flags.OutputJSON:
|
||||||
return opts.NewJSONHandler(logFile.Writer()), nil
|
return opts.NewJSONHandler(f.file.Writer()), nil
|
||||||
case flags.OutputText:
|
case flags.OutputText:
|
||||||
w := logFile.Writer()
|
w := f.file.Writer()
|
||||||
if cmdio.IsTTY(w) {
|
if cmdio.IsTTY(w) {
|
||||||
return &friendlyHandler{
|
return &friendlyHandler{
|
||||||
Handler: opts.NewTextHandler(w),
|
Handler: opts.NewTextHandler(w),
|
||||||
|
@ -81,13 +88,13 @@ func makeLogHandler(opts slog.HandlerOptions) (slog.Handler, error) {
|
||||||
return opts.NewTextHandler(w), nil
|
return opts.NewTextHandler(w), nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid log output mode: %s", logOutput)
|
return nil, fmt.Errorf("invalid log output mode: %s", f.output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeLogger(ctx context.Context) (context.Context, error) {
|
func (f *logFlags) initializeContext(ctx context.Context) (context.Context, error) {
|
||||||
opts := slog.HandlerOptions{}
|
opts := slog.HandlerOptions{}
|
||||||
opts.Level = logLevel.Level()
|
opts.Level = f.level.Level()
|
||||||
opts.AddSource = true
|
opts.AddSource = true
|
||||||
opts.ReplaceAttr = log.ReplaceAttrFunctions{
|
opts.ReplaceAttr = log.ReplaceAttrFunctions{
|
||||||
log.ReplaceLevelAttr,
|
log.ReplaceLevelAttr,
|
||||||
|
@ -95,12 +102,12 @@ func initializeLogger(ctx context.Context) (context.Context, error) {
|
||||||
}.ReplaceAttr
|
}.ReplaceAttr
|
||||||
|
|
||||||
// Open the underlying log file if the user configured an actual file to log to.
|
// Open the underlying log file if the user configured an actual file to log to.
|
||||||
err := logFile.Open()
|
err := f.file.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
handler, err := makeLogHandler(opts)
|
handler, err := f.makeLogHandler(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -109,27 +116,30 @@ func initializeLogger(ctx context.Context) (context.Context, error) {
|
||||||
return log.NewContext(ctx, slog.Default()), nil
|
return log.NewContext(ctx, slog.Default()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var logFile = flags.NewLogFileFlag()
|
func initLogFlags(cmd *cobra.Command) *logFlags {
|
||||||
var logLevel = flags.NewLogLevelFlag()
|
f := logFlags{
|
||||||
var logOutput = flags.OutputText
|
file: flags.NewLogFileFlag(),
|
||||||
|
level: flags.NewLogLevelFlag(),
|
||||||
|
output: flags.OutputText,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Configure defaults from environment, if applicable.
|
// Configure defaults from environment, if applicable.
|
||||||
// If the provided value is invalid it is ignored.
|
// If the provided value is invalid it is ignored.
|
||||||
if v, ok := os.LookupEnv(envLogFile); ok {
|
if v, ok := os.LookupEnv(envLogFile); ok {
|
||||||
logFile.Set(v)
|
f.file.Set(v)
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv(envLogLevel); ok {
|
if v, ok := os.LookupEnv(envLogLevel); ok {
|
||||||
logLevel.Set(v)
|
f.level.Set(v)
|
||||||
}
|
}
|
||||||
if v, ok := os.LookupEnv(envLogFormat); ok {
|
if v, ok := os.LookupEnv(envLogFormat); ok {
|
||||||
logOutput.Set(v)
|
f.output.Set(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
RootCmd.PersistentFlags().Var(&logFile, "log-file", "file to write logs to")
|
cmd.PersistentFlags().Var(&f.file, "log-file", "file to write logs to")
|
||||||
RootCmd.PersistentFlags().Var(&logLevel, "log-level", "log level")
|
cmd.PersistentFlags().Var(&f.level, "log-level", "log level")
|
||||||
RootCmd.PersistentFlags().Var(&logOutput, "log-format", "log output format (text or json)")
|
cmd.PersistentFlags().Var(&f.output, "log-format", "log output format (text or json)")
|
||||||
RootCmd.RegisterFlagCompletionFunc("log-file", logFile.Complete)
|
cmd.RegisterFlagCompletionFunc("log-file", f.file.Complete)
|
||||||
RootCmd.RegisterFlagCompletionFunc("log-level", logLevel.Complete)
|
cmd.RegisterFlagCompletionFunc("log-level", f.level.Complete)
|
||||||
RootCmd.RegisterFlagCompletionFunc("log-format", logOutput.Complete)
|
cmd.RegisterFlagCompletionFunc("log-format", f.output.Complete)
|
||||||
|
return &f
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,42 +7,55 @@ import (
|
||||||
|
|
||||||
"github.com/databricks/cli/libs/cmdio"
|
"github.com/databricks/cli/libs/cmdio"
|
||||||
"github.com/databricks/cli/libs/flags"
|
"github.com/databricks/cli/libs/flags"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
const envProgressFormat = "DATABRICKS_CLI_PROGRESS_FORMAT"
|
const envProgressFormat = "DATABRICKS_CLI_PROGRESS_FORMAT"
|
||||||
|
|
||||||
func resolveModeDefault(format flags.ProgressLogFormat) flags.ProgressLogFormat {
|
type progressLoggerFlag struct {
|
||||||
if (logLevel.String() == "disabled" || logFile.String() != "stderr") &&
|
flags.ProgressLogFormat
|
||||||
|
|
||||||
|
log *logFlags
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *progressLoggerFlag) resolveModeDefault(format flags.ProgressLogFormat) flags.ProgressLogFormat {
|
||||||
|
if (f.log.level.String() == "disabled" || f.log.file.String() != "stderr") &&
|
||||||
term.IsTerminal(int(os.Stderr.Fd())) {
|
term.IsTerminal(int(os.Stderr.Fd())) {
|
||||||
return flags.ModeInplace
|
return flags.ModeInplace
|
||||||
}
|
}
|
||||||
return flags.ModeAppend
|
return flags.ModeAppend
|
||||||
}
|
}
|
||||||
|
|
||||||
func initializeProgressLogger(ctx context.Context) (context.Context, error) {
|
func (f *progressLoggerFlag) initializeContext(ctx context.Context) (context.Context, error) {
|
||||||
if logLevel.String() != "disabled" && logFile.String() == "stderr" &&
|
if f.log.level.String() != "disabled" && f.log.file.String() == "stderr" &&
|
||||||
progressFormat == flags.ModeInplace {
|
f.ProgressLogFormat == flags.ModeInplace {
|
||||||
return nil, fmt.Errorf("inplace progress logging cannot be used when log-file is stderr")
|
return nil, fmt.Errorf("inplace progress logging cannot be used when log-file is stderr")
|
||||||
}
|
}
|
||||||
|
|
||||||
format := progressFormat
|
format := f.ProgressLogFormat
|
||||||
if format == flags.ModeDefault {
|
if format == flags.ModeDefault {
|
||||||
format = resolveModeDefault(format)
|
format = f.resolveModeDefault(format)
|
||||||
}
|
}
|
||||||
|
|
||||||
progressLogger := cmdio.NewLogger(format)
|
progressLogger := cmdio.NewLogger(format)
|
||||||
return cmdio.NewContext(ctx, progressLogger), nil
|
return cmdio.NewContext(ctx, progressLogger), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var progressFormat = flags.NewProgressLogFormat()
|
func initProgressLoggerFlag(cmd *cobra.Command, logFlags *logFlags) *progressLoggerFlag {
|
||||||
|
f := progressLoggerFlag{
|
||||||
|
ProgressLogFormat: flags.NewProgressLogFormat(),
|
||||||
|
|
||||||
|
log: logFlags,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
// Configure defaults from environment, if applicable.
|
// Configure defaults from environment, if applicable.
|
||||||
// If the provided value is invalid it is ignored.
|
// If the provided value is invalid it is ignored.
|
||||||
if v, ok := os.LookupEnv(envProgressFormat); ok {
|
if v, ok := os.LookupEnv(envProgressFormat); ok {
|
||||||
progressFormat.Set(v)
|
f.Set(v)
|
||||||
}
|
}
|
||||||
RootCmd.PersistentFlags().Var(&progressFormat, "progress-format", "format for progress logs (append, inplace, json)")
|
|
||||||
RootCmd.RegisterFlagCompletionFunc("progress-format", progressFormat.Complete)
|
cmd.PersistentFlags().Var(&f.ProgressLogFormat, "progress-format", "format for progress logs (append, inplace, json)")
|
||||||
|
cmd.RegisterFlagCompletionFunc("progress-format", f.ProgressLogFormat.Complete)
|
||||||
|
return &f
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,38 +6,62 @@ import (
|
||||||
|
|
||||||
"github.com/databricks/cli/libs/cmdio"
|
"github.com/databricks/cli/libs/cmdio"
|
||||||
"github.com/databricks/cli/libs/flags"
|
"github.com/databricks/cli/libs/flags"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type progressLoggerTest struct {
|
||||||
|
*cobra.Command
|
||||||
|
*logFlags
|
||||||
|
*progressLoggerFlag
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeProgressLoggerTest(t *testing.T) (
|
||||||
|
*progressLoggerTest,
|
||||||
|
*flags.LogLevelFlag,
|
||||||
|
*flags.LogFileFlag,
|
||||||
|
*flags.ProgressLogFormat,
|
||||||
|
) {
|
||||||
|
plt := &progressLoggerTest{
|
||||||
|
Command: &cobra.Command{},
|
||||||
|
}
|
||||||
|
plt.logFlags = initLogFlags(plt.Command)
|
||||||
|
plt.progressLoggerFlag = initProgressLoggerFlag(plt.Command, plt.logFlags)
|
||||||
|
return plt, &plt.logFlags.level, &plt.logFlags.file, &plt.progressLoggerFlag.ProgressLogFormat
|
||||||
|
}
|
||||||
|
|
||||||
func TestInitializeErrorOnIncompatibleConfig(t *testing.T) {
|
func TestInitializeErrorOnIncompatibleConfig(t *testing.T) {
|
||||||
|
plt, logLevel, logFile, progressFormat := initializeProgressLoggerTest(t)
|
||||||
logLevel.Set("info")
|
logLevel.Set("info")
|
||||||
logFile.Set("stderr")
|
logFile.Set("stderr")
|
||||||
progressFormat.Set("inplace")
|
progressFormat.Set("inplace")
|
||||||
_, err := initializeProgressLogger(context.Background())
|
_, err := plt.progressLoggerFlag.initializeContext(context.Background())
|
||||||
assert.ErrorContains(t, err, "inplace progress logging cannot be used when log-file is stderr")
|
assert.ErrorContains(t, err, "inplace progress logging cannot be used when log-file is stderr")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoErrorOnDisabledLogLevel(t *testing.T) {
|
func TestNoErrorOnDisabledLogLevel(t *testing.T) {
|
||||||
|
plt, logLevel, logFile, progressFormat := initializeProgressLoggerTest(t)
|
||||||
logLevel.Set("disabled")
|
logLevel.Set("disabled")
|
||||||
logFile.Set("stderr")
|
logFile.Set("stderr")
|
||||||
progressFormat.Set("inplace")
|
progressFormat.Set("inplace")
|
||||||
_, err := initializeProgressLogger(context.Background())
|
_, err := plt.progressLoggerFlag.initializeContext(context.Background())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNoErrorOnNonStderrLogFile(t *testing.T) {
|
func TestNoErrorOnNonStderrLogFile(t *testing.T) {
|
||||||
|
plt, logLevel, logFile, progressFormat := initializeProgressLoggerTest(t)
|
||||||
logLevel.Set("info")
|
logLevel.Set("info")
|
||||||
logFile.Set("stdout")
|
logFile.Set("stdout")
|
||||||
progressFormat.Set("inplace")
|
progressFormat.Set("inplace")
|
||||||
_, err := initializeProgressLogger(context.Background())
|
_, err := plt.progressLoggerFlag.initializeContext(context.Background())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDefaultLoggerModeResolution(t *testing.T) {
|
func TestDefaultLoggerModeResolution(t *testing.T) {
|
||||||
progressFormat = flags.NewProgressLogFormat()
|
plt, _, _, progressFormat := initializeProgressLoggerTest(t)
|
||||||
require.Equal(t, progressFormat, flags.ModeDefault)
|
require.Equal(t, *progressFormat, flags.ModeDefault)
|
||||||
ctx, err := initializeProgressLogger(context.Background())
|
ctx, err := plt.progressLoggerFlag.initializeContext(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
logger, ok := cmdio.FromContext(ctx)
|
logger, ok := cmdio.FromContext(ctx)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
|
|
|
@ -13,26 +13,34 @@ import (
|
||||||
"golang.org/x/exp/slog"
|
"golang.org/x/exp/slog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RootCmd represents the base command when called without any subcommands
|
func New() *cobra.Command {
|
||||||
var RootCmd = &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "databricks",
|
Use: "databricks",
|
||||||
Short: "Databricks CLI",
|
Short: "Databricks CLI",
|
||||||
Version: build.GetInfo().Version,
|
Version: build.GetInfo().Version,
|
||||||
|
|
||||||
// Cobra prints the usage string to stderr if a command returns an error.
|
// Cobra prints the usage string to stderr if a command returns an error.
|
||||||
// This usage string should only be displayed if an invalid combination of flags
|
// This usage string should only be displayed if an invalid combination of flags
|
||||||
// is specified and not when runtime errors occur (e.g. resource not found).
|
// is specified and not when runtime errors occur (e.g. resource not found).
|
||||||
// The usage string is include in [flagErrorFunc] for flag errors only.
|
// The usage string is include in [flagErrorFunc] for flag errors only.
|
||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
|
|
||||||
// Silence error printing by cobra. Errors are printed through cmdio.
|
// Silence error printing by cobra. Errors are printed through cmdio.
|
||||||
SilenceErrors: true,
|
SilenceErrors: true,
|
||||||
|
}
|
||||||
|
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
// Initialize flags
|
||||||
|
logFlags := initLogFlags(cmd)
|
||||||
|
progressLoggerFlag := initProgressLoggerFlag(cmd, logFlags)
|
||||||
|
outputFlag := initOutputFlag(cmd)
|
||||||
|
initProfileFlag(cmd)
|
||||||
|
initEnvironmentFlag(cmd)
|
||||||
|
|
||||||
|
cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
|
||||||
ctx := cmd.Context()
|
ctx := cmd.Context()
|
||||||
|
|
||||||
// Configure default logger.
|
// Configure default logger.
|
||||||
ctx, err := initializeLogger(ctx)
|
ctx, err := logFlags.initializeContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -43,7 +51,7 @@ var RootCmd = &cobra.Command{
|
||||||
slog.String("args", strings.Join(os.Args, ", ")))
|
slog.String("args", strings.Join(os.Args, ", ")))
|
||||||
|
|
||||||
// Configure progress logger
|
// Configure progress logger
|
||||||
ctx, err = initializeProgressLogger(ctx)
|
ctx, err = progressLoggerFlag.initializeContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -51,7 +59,7 @@ var RootCmd = &cobra.Command{
|
||||||
cmd.SetContext(ctx)
|
cmd.SetContext(ctx)
|
||||||
|
|
||||||
// Configure command IO
|
// Configure command IO
|
||||||
err = initializeIO(cmd)
|
err = outputFlag.initializeIO(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -63,7 +71,11 @@ var RootCmd = &cobra.Command{
|
||||||
ctx = withUpstreamInUserAgent(ctx)
|
ctx = withUpstreamInUserAgent(ctx)
|
||||||
cmd.SetContext(ctx)
|
cmd.SetContext(ctx)
|
||||||
return nil
|
return nil
|
||||||
},
|
}
|
||||||
|
|
||||||
|
cmd.SetFlagErrorFunc(flagErrorFunc)
|
||||||
|
cmd.SetVersionTemplate("Databricks CLI v{{.Version}}\n")
|
||||||
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap flag errors to include the usage string.
|
// Wrap flag errors to include the usage string.
|
||||||
|
@ -104,7 +116,5 @@ func Execute(cmd *cobra.Command) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
// Keep a global copy until all commands can be initialized.
|
||||||
RootCmd.SetFlagErrorFunc(flagErrorFunc)
|
var RootCmd = New()
|
||||||
RootCmd.SetVersionTemplate("Databricks CLI v{{.Version}}\n")
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue