diff --git a/internal/command/root.go b/internal/command/root.go index a319aac..369bca9 100644 --- a/internal/command/root.go +++ b/internal/command/root.go @@ -8,8 +8,12 @@ The Apache Software Foundation (http://www.apache.org/). package command import ( + "io" + "log/slog" + "github.com/spf13/cobra" + "github.com/score-spec/score-compose/internal/logging" "github.com/score-spec/score-compose/internal/version" ) @@ -22,6 +26,22 @@ This tool produces a docker-compose configuration file from the SCORE specificat Complete documentation is available at https://score.dev`, // don't print the errors - we print these ourselves in main() SilenceErrors: true, + + // This function always runs for all subcommands + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if q, _ := cmd.Flags().GetBool("quiet"); q { + slog.SetDefault(slog.New(&logging.SimpleHandler{Level: slog.LevelError, Writer: io.Discard})) + } else if v, _ := cmd.Flags().GetCount("verbose"); v == 0 { + slog.SetDefault(slog.New(&logging.SimpleHandler{Level: slog.LevelInfo, Writer: cmd.ErrOrStderr()})) + } else if v == 1 { + slog.SetDefault(slog.New(&logging.SimpleHandler{Level: slog.LevelDebug, Writer: cmd.ErrOrStderr()})) + } else if v == 2 { + slog.SetDefault(slog.New(slog.NewTextHandler(cmd.ErrOrStderr(), &slog.HandlerOptions{ + Level: slog.LevelDebug, AddSource: true, + }))) + } + return nil + }, } ) @@ -29,6 +49,8 @@ func init() { rootCmd.Version = version.BuildVersionString() rootCmd.SetVersionTemplate(`{{with .Name}}{{printf "%s " .}}{{end}}{{printf "%s" .Version}} `) + rootCmd.PersistentFlags().Bool("quiet", false, "Mute any logging output") + rootCmd.PersistentFlags().CountP("verbose", "v", "Increase log verbosity and detail by specifying this flag one or more times") } func Execute() error { diff --git a/internal/command/root_test.go b/internal/command/root_test.go index 540f3f0..ae8f387 100644 --- a/internal/command/root_test.go +++ b/internal/command/root_test.go @@ -24,8 +24,10 @@ Available Commands: run Translate the SCORE file to docker-compose configuration Flags: - -h, --help help for score-compose - -v, --version version for score-compose + -h, --help help for score-compose + --quiet Mute any logging output + -v, --verbose count Increase log verbosity and detail by specifying this flag one or more times + --version version for score-compose Use "score-compose [command] --help" for more information about a command. `, stdout) @@ -58,6 +60,10 @@ Available Commands: Flags: -h, --help help for completion +Global Flags: + --quiet Mute any logging output + -v, --verbose count Increase log verbosity and detail by specifying this flag one or more times + Use "score-compose completion [command] --help" for more information about a command. `, stdout) assert.Equal(t, "", stderr) @@ -98,6 +104,10 @@ Usage: Flags: -h, --help help for bash --no-descriptions disable completion descriptions + +Global Flags: + --quiet Mute any logging output + -v, --verbose count Increase log verbosity and detail by specifying this flag one or more times `, stdout) assert.Equal(t, "", stderr) } @@ -130,6 +140,10 @@ Usage: Flags: -h, --help help for fish --no-descriptions disable completion descriptions + +Global Flags: + --quiet Mute any logging output + -v, --verbose count Increase log verbosity and detail by specifying this flag one or more times `, stdout) assert.Equal(t, "", stderr) } @@ -173,6 +187,10 @@ Usage: Flags: -h, --help help for zsh --no-descriptions disable completion descriptions + +Global Flags: + --quiet Mute any logging output + -v, --verbose count Increase log verbosity and detail by specifying this flag one or more times `, stdout) assert.Equal(t, "", stderr) } @@ -202,6 +220,10 @@ Usage: Flags: -h, --help help for powershell --no-descriptions disable completion descriptions + +Global Flags: + --quiet Mute any logging output + -v, --verbose count Increase log verbosity and detail by specifying this flag one or more times `, stdout) assert.Equal(t, "", stderr) } diff --git a/internal/command/run.go b/internal/command/run.go index 6500999..aecd3d4 100644 --- a/internal/command/run.go +++ b/internal/command/run.go @@ -22,12 +22,11 @@ import ( "github.com/tidwall/sjson" "gopkg.in/yaml.v3" - "github.com/score-spec/score-compose/internal/compose" - "github.com/score-spec/score-compose/internal/logging" - loader "github.com/score-spec/score-go/loader" schema "github.com/score-spec/score-go/schema" score "github.com/score-spec/score-go/types" + + "github.com/score-spec/score-compose/internal/compose" ) const ( @@ -45,7 +44,6 @@ var ( overrideParams []string skipValidation bool - verbose bool ) func init() { @@ -58,13 +56,13 @@ func init() { runCmd.Flags().StringArrayVarP(&overrideParams, "property", "p", nil, "Overrides selected property value") runCmd.Flags().BoolVar(&skipValidation, "skip-validation", false, "DEPRECATED: Disables Score file schema validation") - runCmd.Flags().BoolVar(&verbose, "verbose", false, "Enable diagnostic messages (written to STDERR)") rootCmd.AddCommand(runCmd) } var runCmd = &cobra.Command{ Use: "run [--file=score.yaml] [--output=compose.yaml]", + Args: cobra.NoArgs, Short: "Translate the SCORE file to docker-compose configuration", RunE: run, // don't print the errors - we print these ourselves in main() @@ -75,12 +73,6 @@ func run(cmd *cobra.Command, args []string) error { // Silence usage message if args are parsed correctly cmd.SilenceUsage = true - logLevel := slog.LevelWarn - if verbose { - logLevel = slog.LevelDebug - } - slog.SetDefault(slog.New(&logging.SimpleHandler{Level: logLevel, Writer: cmd.ErrOrStderr()})) - // Open source file // slog.Info(fmt.Sprintf("Reading Score file '%s'", scoreFile)) @@ -102,10 +94,10 @@ func run(cmd *cobra.Command, args []string) error { // Apply overrides from file (optional) // if overridesFile != "" { - slog.Info(fmt.Sprintf("Loading Score overrides file '%s'", overridesFile)) if ovr, err := os.Open(overridesFile); err == nil { defer ovr.Close() + slog.Info(fmt.Sprintf("Loading Score overrides file '%s'", overridesFile)) var ovrMap map[string]interface{} if err = loader.ParseYAML(&ovrMap, ovr); err != nil { return err diff --git a/internal/command/run_test.go b/internal/command/run_test.go index 056f055..d2966b0 100644 --- a/internal/command/run_test.go +++ b/internal/command/run_test.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" "github.com/spf13/cobra" @@ -64,7 +65,10 @@ Flags: --overrides string Overrides SCORE file (default "./overrides.score.yaml") -p, --property stringArray Overrides selected property value --skip-validation DEPRECATED: Disables Score file schema validation - --verbose Enable diagnostic messages (written to STDERR) + +Global Flags: + --quiet Mute any logging output + -v, --verbose count Increase log verbosity and detail by specifying this flag one or more times `, stdout) assert.Equal(t, "", stderr) @@ -219,7 +223,7 @@ func TestExample_invalid_spec(t *testing.T) { stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"run", "--file", filepath.Join(td, "score.yaml"), "--output", filepath.Join(td, "compose.yaml")}) assert.EqualError(t, err, "validating workload spec: jsonschema: '' does not validate with https://score.dev/schemas/score#/required: missing properties: 'apiVersion', 'metadata', 'containers'") assert.Equal(t, "", stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) } func TestVolumeSubPathNotSupported(t *testing.T) { @@ -239,7 +243,7 @@ containers: stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"run", "--file", filepath.Join(td, "score.yaml"), "--output", filepath.Join(td, "compose.yaml")}) assert.EqualError(t, err, "building docker-compose configuration: containers.container-one1.volumes[0].path: can't mount named volume with sub path '/sub/path': not supported") assert.Equal(t, "", stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) } func TestFilesNotSupported(t *testing.T) { @@ -258,7 +262,7 @@ containers: stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"run", "--file", filepath.Join(td, "score.yaml"), "--output", filepath.Join(td, "compose.yaml")}) assert.EqualError(t, err, "building docker-compose configuration: containers.container-one1.files: not supported") assert.Equal(t, "", stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) } func TestInvalidWorkloadName(t *testing.T) { @@ -274,7 +278,7 @@ containers: stdout, stderr, err := executeAndResetCommand(context.Background(), rootCmd, []string{"run", "--file", filepath.Join(td, "score.yaml")}) assert.EqualError(t, err, "validating workload spec: jsonschema: '/metadata/name' does not validate with https://score.dev/schemas/score#/properties/metadata/properties/name/pattern: does not match pattern '^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$'") assert.Equal(t, "", stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) } func TestRunExample01(t *testing.T) { @@ -293,7 +297,7 @@ func TestRunExample01(t *testing.T) { ` assert.Equal(t, expectedOutput, stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose.yaml")) require.NoError(t, err) assert.Equal(t, expectedOutput, string(rawComposeContent)) @@ -333,7 +337,7 @@ func TestRunExample02(t *testing.T) { ` assert.Equal(t, expectedOutput, stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose.yaml")) require.NoError(t, err) assert.Equal(t, expectedOutput, string(rawComposeContent)) @@ -376,12 +380,7 @@ func TestRunExample03(t *testing.T) { ` assert.Equal(t, expectedOutput, stdout) - for _, l := range []string{ - "WARN: resources.db: 'postgres.default' is not directly supported in score-compose, references will be converted to environment variables\n", - "WARN: resources.service-b: 'service.default' is not directly supported in score-compose, references will be converted to environment variables\n", - } { - assert.Contains(t, stderr, l) - } + assert.NotEqual(t, "", strings.TrimSpace(stderr)) rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose-a.yaml")) require.NoError(t, err) assert.Equal(t, expectedOutput, string(rawComposeContent)) @@ -404,7 +403,7 @@ func TestRunExample03(t *testing.T) { ` assert.Equal(t, expectedOutput, stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose-b.yaml")) require.NoError(t, err) assert.Equal(t, expectedOutput, string(rawComposeContent)) @@ -451,7 +450,7 @@ func TestRunExample04(t *testing.T) { ` assert.Equal(t, expectedOutput, stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose.yaml")) require.NoError(t, err) assert.Equal(t, expectedOutput, string(rawComposeContent)) @@ -501,7 +500,7 @@ containers: ` assert.Equal(t, expectedOutput, stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose.yaml")) require.NoError(t, err) assert.Equal(t, expectedOutput, string(rawComposeContent)) @@ -536,7 +535,7 @@ containers: ` assert.Equal(t, expectedOutput, stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose.yaml")) require.NoError(t, err) assert.Equal(t, expectedOutput, string(rawComposeContent)) @@ -565,7 +564,7 @@ containers: ` assert.Equal(t, expectedOutput, stdout) - assert.Equal(t, "", stderr) + assert.NotEqual(t, "", strings.TrimSpace(stderr)) rawComposeContent, err := os.ReadFile(filepath.Join(td, "compose.yaml")) require.NoError(t, err) assert.Equal(t, expectedOutput, string(rawComposeContent))