diff --git a/cli/cli.go b/cli/cli.go index a82d5c19d..8a6f8e764 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -17,137 +17,54 @@ import ( "github.com/urfave/cli" ) -func failf(format string, args ...interface{}) { - log.Errorf(format, args...) - os.Exit(1) -} - -func before(c *cli.Context) error { - /* - return err will print app's help also, - use log.Fatal to avoid print help. - */ - - initHelpAndVersionFlags() - - // CI Mode check - if c.Bool(CIKey) { - // if CI mode indicated make sure we set the related env - // so all other tools we use will also get it - if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { - failf("Failed to set CI env, error: %s", err) - } - configs.IsCIMode = true - } - - if err := configs.InitPaths(); err != nil { - failf("Failed to initialize required paths, error: %s", err) - } - - // Pull Request Mode check - if c.Bool(PRKey) { - // if PR mode indicated make sure we set the related env - // so all other tools we use will also get it - if err := os.Setenv(configs.PRModeEnvKey, "true"); err != nil { - failf("Failed to set PR env, error: %s", err) - } - configs.IsPullRequestMode = true - } - - pullReqID := os.Getenv(configs.PullRequestIDEnvKey) - if pullReqID != "" { - configs.IsPullRequestMode = true - } - - IsPR := os.Getenv(configs.PRModeEnvKey) - if IsPR == "true" { - configs.IsPullRequestMode = true - } - - return nil -} - -func printVersion(c *cli.Context) { - log.Print(c.App.Version) -} - -func loggerParameters(arguments []string) (bool, string, bool) { - isRunCommand := false - outputFormat := "" - isDebug := false - - for i, argument := range arguments { - if argument == "run" { - isRunCommand = true - } - - if argument == "--"+OutputFormatKey { - if i+1 <= len(arguments) { - value := arguments[i+1] - - if !strings.HasPrefix(value, "--") { - outputFormat = value - } - } - } - - if argument == "--"+DebugModeKey { - if i+1 <= len(arguments) { - value := arguments[i+1] - - if strings.HasPrefix(value, "--") { - isDebug = true - } else { - value, err := strconv.ParseBool(value) - if err == nil { - isDebug = value - } - } - } - } - } - - return isRunCommand, outputFormat, isDebug -} - // Run ... func Run() { - isRunCommand, format, isDebug := loggerParameters(os.Args[1:]) - if !isDebug { - isDebug = os.Getenv(configs.DebugModeEnvKey) == "true" + // In the case of `--output-format=json` flag is set for the run command, all the logs are expected in JSON format. + // Because logs might be printed before processing the run command args, + // we need to manually parse the logger configuration. + isRunCommand, logFormat, isDebugMode := loggerParameters(os.Args[1:]) + + if !isDebugMode { + isDebugMode = os.Getenv(configs.DebugModeEnvKey) == "true" } loggerType := log.ConsoleLogger - if isRunCommand && format == "json" { - loggerType = log.JSONLogger + if isRunCommand && string(logFormat) != "" { + loggerType = logFormat } // Global logger needs to be initialised before using any log function opts := log.LoggerOpts{ LoggerType: loggerType, Producer: log.BitriseCLI, - DebugLogEnabled: isDebug, + DebugLogEnabled: isDebugMode, Writer: os.Stdout, TimeProvider: time.Now, } log.InitGlobalLogger(opts) - // Debug mode? - if isDebug { + if isDebugMode { // set for other tools, as an ENV if err := os.Setenv(configs.DebugModeEnvKey, "true"); err != nil { failf("Failed to set DEBUG env, error: %s", err) } + if err := os.Setenv("LOGLEVEL", "debug"); err != nil { + failf("Failed to set LOGLEVEL env, error: %s", err) + + } + configs.IsDebugMode = true } + // This is needed for the getPluginsList func in the cli.AppHelpTemplate + // and cli.AppHelpTemplate is evaluated before executing app.Before. if err := plugins.InitPaths(); err != nil { failf("Failed to initialize plugin path, error: %s", err) } - cli.VersionPrinter = printVersion + cli.VersionPrinter = func(c *cli.Context) { log.Print(c.App.Version) } cli.AppHelpTemplate = fmt.Sprintf(helpTemplate, getPluginsList()) app := cli.NewApp() @@ -195,3 +112,117 @@ func Run() { failf(err.Error()) } } + +func loggerParameters(arguments []string) (isRunCommand bool, outputFormat log.LoggerType, isDebug bool) { + for i, argument := range arguments { + if argument == "run" { + isRunCommand = true + } + + // syntax + // -flag + // --flag // double dashes are also permitted + // -flag=x + // -flag x // non-boolean flags only + // One or two dashes may be used; they are equivalent. + // https://pkg.go.dev/flag#hdr-Command_line_flag_syntax + if isFlag(OutputFormatKey, argument) { + var value string + components := strings.Split(argument, "=") + + // If the flag value was specified with an `=` mark then the second element in the array is the actual value. + // Otherwise, the value was specified as a separate item after the flag, and we need to take the next + // argument value. + if len(components) == 2 { + value = components[1] + } else if i+1 < len(arguments) { + value = arguments[i+1] + } + + switch value { + case string(log.JSONLogger): + outputFormat = log.JSONLogger + case string(log.ConsoleLogger): + outputFormat = log.ConsoleLogger + default: + // At this point we don't care about invalid values, + // the execution will fail when parsing the command's arguments. + } + } + + if isFlag(DebugModeKey, argument) { + components := strings.Split(argument, "=") + if len(components) == 2 { + value, err := strconv.ParseBool(components[1]) + if err == nil { + isDebug = value + } + } else { + components := strings.Split(argument, " ") + + // "-flag x" Command line flag syntax is not supported for boolean flags + // https://pkg.go.dev/flag#hdr-Command_line_flag_syntax + if len(components) == 1 { + isDebug = true + } + } + } + } + + return +} + +func isFlag(name, arg string) bool { + return arg == "--"+name || arg == "-"+name || + strings.HasPrefix(arg, "--"+name+"=") || strings.HasPrefix(arg, "-"+name+"=") +} + +func before(c *cli.Context) error { + /* + return err will print app's help also, + use log.Fatal to avoid print help. + */ + + initHelpAndVersionFlags() + + // CI Mode check + if c.Bool(CIKey) { + // if CI mode indicated make sure we set the related env + // so all other tools we use will also get it + if err := os.Setenv(configs.CIModeEnvKey, "true"); err != nil { + failf("Failed to set CI env, error: %s", err) + } + configs.IsCIMode = true + } + + if err := configs.InitPaths(); err != nil { + failf("Failed to initialize required paths, error: %s", err) + } + + // Pull Request Mode check + if c.Bool(PRKey) { + // if PR mode indicated make sure we set the related env + // so all other tools we use will also get it + if err := os.Setenv(configs.PRModeEnvKey, "true"); err != nil { + failf("Failed to set PR env, error: %s", err) + } + configs.IsPullRequestMode = true + } + + pullReqID := os.Getenv(configs.PullRequestIDEnvKey) + if pullReqID != "" { + configs.IsPullRequestMode = true + } + + IsPR := os.Getenv(configs.PRModeEnvKey) + if IsPR == "true" { + configs.IsPullRequestMode = true + } + + return nil +} + +func failf(format string, args ...interface{}) { + log.Errorf(format, args...) + os.Exit(1) +} diff --git a/cli/cli_test.go b/cli/cli_test.go new file mode 100644 index 000000000..5c763937a --- /dev/null +++ b/cli/cli_test.go @@ -0,0 +1,99 @@ +package cli + +import ( + "testing" + + "github.com/bitrise-io/bitrise/log" + "github.com/stretchr/testify/assert" +) + +func Test_loggerParameters(t *testing.T) { + tests := []struct { + name string + args []string + wantIsRunCommand bool + wantOutputFormat log.LoggerType + wantDebugMode bool + }{ + { + name: "Empty test", + args: []string{}, + wantIsRunCommand: false, + wantOutputFormat: "", + wantDebugMode: false, + }, + { + name: "Debug mode on with one dash syntax", + args: []string{"-debug"}, + wantDebugMode: true, + }, + { + name: "Debug mode on with two dash syntax", + args: []string{"--debug"}, + wantDebugMode: true, + }, + { + name: "Debug mode on with value syntax", + args: []string{"-debug=true"}, + wantDebugMode: true, + }, + { + name: "Debug mode off with value syntax", + args: []string{"--debug=true"}, + wantDebugMode: true, + }, + { + name: "Debug mode invalid syntax", + args: []string{"--debug true"}, + wantDebugMode: false, + }, + { + name: "Run command", + args: []string{"run"}, + wantIsRunCommand: true, + }, + { + name: "Output format json with one dash syntax", + args: []string{"-output-format", "json"}, + wantOutputFormat: "json", + }, + { + name: "Output format console with two dash syntax", + args: []string{"--output-format", "console"}, + wantOutputFormat: "console", + }, + { + name: "Output format json value with one dash syntax", + args: []string{"-output-format=json"}, + wantOutputFormat: "json", + }, + { + name: "Output format console value with two dash syntax", + args: []string{"--output-format=console"}, + wantOutputFormat: "console", + }, + { + name: "Output format invalid syntax", + args: []string{"-output-format", "--log-level"}, + wantOutputFormat: "", + }, + { + name: "Output format invalid value", + args: []string{"-output-format", "invalid"}, + wantOutputFormat: "", + }, + { + name: "Invalid flag", + args: []string{"-output-format-invalid=json"}, + wantOutputFormat: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + isRunCommand, outputFormat, debugMode := loggerParameters(tt.args) + assert.Equalf(t, tt.wantIsRunCommand, isRunCommand, "loggerParameters(%v)", tt.args) + assert.Equalf(t, tt.wantOutputFormat, outputFormat, "loggerParameters(%v)", tt.args) + assert.Equalf(t, tt.wantDebugMode, debugMode, "loggerParameters(%v)", tt.args) + }) + } +} diff --git a/cli/flags.go b/cli/flags.go index c6d0693f6..983bbf739 100644 --- a/cli/flags.go +++ b/cli/flags.go @@ -76,7 +76,7 @@ var ( // App flags flDebugMode = cli.BoolFlag{ Name: DebugModeKey, - Usage: "If true it enabled DEBUG mode. If no separate Log Level is specified this will also set the loglevel to debug.", + Usage: "If true it enables DEBUG mode.", EnvVar: configs.DebugModeEnvKey, } flTool = cli.BoolFlag{ diff --git a/configs/configs.go b/configs/configs.go index 93e9c0a97..01c89f988 100644 --- a/configs/configs.go +++ b/configs/configs.go @@ -50,8 +50,6 @@ const ( PullRequestIDEnvKey = "PULL_REQUEST_ID" // DebugModeEnvKey ... DebugModeEnvKey = "DEBUG" - // LogLevelEnvKey ... - LogLevelEnvKey = "LOGLEVEL" // IsSecretFilteringKey ... IsSecretFilteringKey = "BITRISE_SECRET_FILTERING" // IsSecretEnvsFilteringKey ... diff --git a/tools/tools.go b/tools/tools.go index 722fa13fa..cc0823dbe 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -160,13 +160,13 @@ func StepmanStepInfo(collection, stepID, stepVersion string) (stepmanModels.Step // StepmanRawStepList ... func StepmanRawStepList(collection string) (string, error) { - args := []string{"--loglevel", logLevel(), "step-list", "--collection", collection, "--format", "raw"} + args := []string{"step-list", "--collection", collection, "--format", "raw"} return command.RunCommandAndReturnCombinedStdoutAndStderr("stepman", args...) } // StepmanJSONStepList ... func StepmanJSONStepList(collection string) (string, error) { - args := []string{"--loglevel", logLevel(), "step-list", "--collection", collection, "--format", "json"} + args := []string{"step-list", "--collection", collection, "--format", "json"} var outBuffer bytes.Buffer var errBuffer bytes.Buffer @@ -183,31 +183,31 @@ func StepmanJSONStepList(collection string) (string, error) { // StepmanShare ... func StepmanShare() error { - args := []string{"--loglevel", logLevel(), "share", "--toolmode"} + args := []string{"share", "--toolmode"} return command.RunCommand("stepman", args...) } // StepmanShareAudit ... func StepmanShareAudit() error { - args := []string{"--loglevel", logLevel(), "share", "audit", "--toolmode"} + args := []string{"share", "audit", "--toolmode"} return command.RunCommand("stepman", args...) } // StepmanShareCreate ... func StepmanShareCreate(tag, git, stepID string) error { - args := []string{"--loglevel", logLevel(), "share", "create", "--tag", tag, "--git", git, "--stepid", stepID, "--toolmode"} + args := []string{"share", "create", "--tag", tag, "--git", git, "--stepid", stepID, "--toolmode"} return command.RunCommand("stepman", args...) } // StepmanShareFinish ... func StepmanShareFinish() error { - args := []string{"--loglevel", logLevel(), "share", "finish", "--toolmode"} + args := []string{"share", "finish", "--toolmode"} return command.RunCommand("stepman", args...) } // StepmanShareStart ... func StepmanShareStart(collection string) error { - args := []string{"--loglevel", logLevel(), "share", "start", "--collection", collection, "--toolmode"} + args := []string{"share", "start", "--collection", collection, "--toolmode"} return command.RunCommand("stepman", args...) } @@ -383,23 +383,15 @@ func MoveFile(oldpath, newpath string) error { // IsBuiltInFlagTypeKey returns true if the env key is a built-in flag type env key func IsBuiltInFlagTypeKey(env string) bool { - switch string(env) { + switch env { case configs.IsSecretFilteringKey, configs.IsSecretEnvsFilteringKey, configs.CIModeEnvKey, configs.PRModeEnvKey, configs.DebugModeEnvKey, - configs.LogLevelEnvKey, configs.PullRequestIDEnvKey: return true default: return false } } - -func logLevel() string { - if configs.IsDebugMode { - return "debug" - } - return "info" -}