diff --git a/cmd/create.go b/cmd/create.go index 2476f47..f7e9d40 100644 --- a/cmd/create.go +++ b/cmd/create.go @@ -1,9 +1,9 @@ package cmd import ( - "fmt" - "github.com/spf13/cobra" + + log "github.com/sirupsen/logrus" ) func init() { @@ -23,5 +23,5 @@ var createCmd = &cobra.Command{ } func createJob(cmd *cobra.Command, args []string) { - fmt.Println("Not implemented yet.") + log.Println("Not implemented yet.") } diff --git a/cmd/fetch.go b/cmd/fetch.go index 2b3caf0..b90203d 100644 --- a/cmd/fetch.go +++ b/cmd/fetch.go @@ -2,10 +2,13 @@ package cmd import ( "fmt" - "os" + "github.com/spf13/pflag" + "github.com/paypal/gorealis" "github.com/paypal/gorealis/gen-go/apache/aurora" "github.com/spf13/cobra" + + log "github.com/sirupsen/logrus" ) func init() { @@ -20,30 +23,24 @@ func init() { taskConfigCmd.Flags().StringVarP(name, "name", "n", "", "Aurora Name") /* Fetch Leader */ - leaderCmd.Flags().String("zkPath", "/aurora/scheduler", "Zookeeper node path where leader election happens") - // Override usage template to hide global flags - leaderCmd.SetUsageTemplate(`Usage:{{if .Runnable}} -{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} -{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} - -Aliases: - {{.NameAndAliases}}{{end}}{{if .HasExample}} - -Examples: - {{.Example}}{{end}}{{if .HasAvailableSubCommands}} - - Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}} - {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}} - -Flags: - {{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}} - -Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}} -`) fetchCmd.AddCommand(leaderCmd) + // Hijack help function to hide unnecessary global flags + help := leaderCmd.HelpFunc() + leaderCmd.SetHelpFunc(func(cmd *cobra.Command, s []string){ + if cmd.HasInheritedFlags(){ + + cmd.InheritedFlags().VisitAll(func(f *pflag.Flag){ + if f.Name != "logLevel" { + f.Hidden = true + } + }) + } + help(cmd, s) + }) + // Fetch jobs fetchJobsCmd.Flags().StringVarP(role, "role", "r", "", "Aurora Role") fetchCmd.AddCommand(fetchJobsCmd) @@ -66,12 +63,16 @@ var taskConfigCmd = &cobra.Command{ var leaderCmd = &cobra.Command{ Use: "leader [zkNode0, zkNode1, ...zkNodeN]", - PersistentPreRun: func(cmd *cobra.Command, args []string) {}, //We don't need a realis client for this cmd + PersistentPreRun: func(cmd *cobra.Command, args []string) { + + }, //We don't need a realis client for this cmd PersistentPostRun: func(cmd *cobra.Command, args []string) {}, //We don't need a realis client for this cmd + PreRun: setConfig, + Args: cobra.MinimumNArgs(1), Short: "Fetch current Aurora leader given Zookeeper nodes. ", - Long: `Gets the current leading aurora scheduler instance using information from Zookeeper path. + Long: `Gets the current leading aurora scheduler instance using information from Zookeeper path. Pass Zookeeper nodes separated by a space as an argument to this command.`, - Run: fetchLeader, + Run: fetchLeader, } var fetchJobsCmd = &cobra.Command{ @@ -82,10 +83,10 @@ var fetchJobsCmd = &cobra.Command{ } var fetchStatusCmd = &cobra.Command{ - Use: "status", + Use: "status", Short: "Fetch the maintenance status of a node from Aurora", - Long: `This command will print the actual status of the mesos agent nodes in Aurora server`, - Run: fetchStatus, + Long: `This command will print the actual status of the mesos agent nodes in Aurora server`, + Run: fetchStatus, } func fetchTasks(cmd *cobra.Command, args []string) { @@ -107,69 +108,74 @@ func fetchTasks(cmd *cobra.Command, args []string) { tasks, err := client.GetTasksWithoutConfigs(taskQuery) if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } - for _, t := range tasks { - fmt.Println(t) + if toJson { + fmt.Println(toJSON(tasks)) + } else { + for _, t := range tasks { + fmt.Println(t) + } } } func fetchStatus(cmd *cobra.Command, args []string) { - fmt.Printf("Fetching maintenance status for %v \n", args) + log.Infof("Fetching maintenance status for %v \n", args) _, result, err := client.MaintenanceStatus(args...) if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } - for k := range result.Statuses { - fmt.Printf("Result: %s:%s\n", k.Host, k.Mode) + if toJson { + fmt.Println(toJSON(result.Statuses)) + } else { + for k := range result.Statuses { + fmt.Printf("Result: %s:%s\n", k.Host, k.Mode) + } } } func fetchLeader(cmd *cobra.Command, args []string) { - fmt.Printf("Fetching leader from %v \n", args) + log.Infof("Fetching leader from %v \n", args) if len(args) < 1 { - fmt.Println("At least one Zookeper node address must be passed in.") - os.Exit(1) + log.Fatalln("At least one Zookeeper node address must be passed in.") } url, err := realis.LeaderFromZKOpts(realis.ZKEndpoints(args...), realis.ZKPath(cmd.Flag("zkPath").Value.String())) if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } fmt.Println(url) } // TODO: Expand this to be able to filter by job name and environment. -func fetchJobs(cmd *cobra.Command, args []string) { - fmt.Printf("Fetching tasks under role: %s \n", *role) +func fetchJobs(cmd *cobra.Command, args []string) { + log.Infof("Fetching tasks under role: %s \n", *role) if *role == "" { - fmt.Println("Role must be specified.") - os.Exit(1) + log.Fatalln("Role must be specified.") } if *role == "*" { - fmt.Println("Warning: This is an expensive operation.") + log.Warnln("This is an expensive operation.") *role = "" } _, result, err := client.GetJobs(*role) if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } - for jobConfig, _ := range result.GetConfigs() { - fmt.Println(jobConfig) + if toJson { + fmt.Println(toJSON(result.GetConfigs())) + } else { + for jobConfig := range result.GetConfigs() { + fmt.Println(jobConfig) + } } - } diff --git a/cmd/force.go b/cmd/force.go index 13323a4..20b8ff3 100644 --- a/cmd/force.go +++ b/cmd/force.go @@ -2,10 +2,11 @@ package cmd import ( "fmt" - "os" "strconv" "github.com/spf13/cobra" + + log "github.com/sirupsen/logrus" ) func init() { @@ -38,10 +39,9 @@ func snapshot(cmd *cobra.Command, args []string) { fmt.Println("Forcing scheduler to write snapshot to Mesos replicated log") err := client.Snapshot() if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } else { - fmt.Println("Snapshot started successfully") + log.Println("Snapshot started successfully") } } @@ -57,10 +57,9 @@ func backup(cmd *cobra.Command, args []string) { fmt.Println("Forcing scheduler to write a Backup of latest Snapshot to file system") err := client.PerformBackup() if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } else { - fmt.Println("Backup started successfully") + log.Println("Backup started successfully") } } @@ -90,31 +89,29 @@ and the master responds with the latest state for each task, if possible. func explicitRecon(cmd *cobra.Command, args []string) { var batchSize *int32 - fmt.Println("Forcing scheduler to perform an explicit reconciliation with Mesos") + log.Println("Forcing scheduler to perform an explicit reconciliation with Mesos") switch len(args) { case 0: - fmt.Println("Using default batch size for explicit recon.") + log.Infoln("Using default batch size for explicit recon.") case 1: - fmt.Printf("Using %v as batch size for explicit recon.\n", args[0]) + log.Infof("Using %v as batch size for explicit recon.\n", args[0]) // Get batch size from args and convert it to the right format batchInt, err := strconv.Atoi(args[0]) if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } + batchInt32 := int32(batchInt) batchSize = &batchInt32 default: - fmt.Println("Provide 0 arguments to use default batch size or one argument to use a custom batch size.") - os.Exit(1) + log.Fatalln("Provide 0 arguments to use default batch size or one argument to use a custom batch size.") } err := client.ForceExplicitTaskReconciliation(batchSize) if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err.Error()) } else { fmt.Println("Explicit reconciliation started successfully") } @@ -129,11 +126,10 @@ var forceImplicitReconCmd = &cobra.Command{ func implicitRecon(cmd *cobra.Command, args []string) { - fmt.Println("Forcing scheduler to perform an implicit reconciliation with Mesos") + log.Println("Forcing scheduler to perform an implicit reconciliation with Mesos") err := client.PerformBackup() if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } else { fmt.Println("Implicit reconciliation started successfully") } diff --git a/cmd/kill.go b/cmd/kill.go index e8ea3da..6a16d84 100644 --- a/cmd/kill.go +++ b/cmd/kill.go @@ -2,20 +2,18 @@ package cmd import ( "fmt" - "log" - "os" "github.com/paypal/gorealis" "github.com/spf13/cobra" + + log "github.com/sirupsen/logrus" ) func init() { rootCmd.AddCommand(killCmd) - /* Sub-Commands */ - // Kill Job killCmd.AddCommand(killJobCmd) @@ -28,8 +26,6 @@ func init() { // Kill every task in the Aurora cluster killCmd.AddCommand(killEntireClusterCmd) - - } var killCmd = &cobra.Command{ @@ -51,7 +47,7 @@ var killEntireClusterCmd = &cobra.Command{ } func killJob(cmd *cobra.Command, args []string) { - log.Printf("Killing job [Env:%s Role:%s Name:%s]\n", *env, *role, *name) + log.Infof("Killing job [Env:%s Role:%s Name:%s]\n", *env, *role, *name) job := realis.NewJob(). Environment(*env). @@ -59,16 +55,18 @@ func killJob(cmd *cobra.Command, args []string) { Name(*name) resp, err := client.KillJob(job.JobKey()) if err != nil { - fmt.Println(err) - os.Exit(1) + log.Fatalln(err) } - if ok, err := monitor.Instances(job.JobKey(), 0, 5, 50); !ok || err != nil { - log.Println("Unable to kill all instances of job") - os.Exit(1) - } + if ok, err := monitor.Instances(job.JobKey(), 0, 5, 50); !ok || err != nil { + log.Fatalln("Unable to kill all instances of job") + } - fmt.Println(resp.String()) + if toJson { + fmt.Println(toJSON(resp.GetResult_())) + } else { + fmt.Println(resp.GetResult_()) + } } func killEntireCluster(cmd *cobra.Command, args []string) { diff --git a/cmd/root.go b/cmd/root.go index fbb2fa6..ede47b4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -1,14 +1,14 @@ package cmd import ( - "fmt" - "github.com/spf13/viper" - "os" - "strings" - "time" + "github.com/spf13/viper" + "strings" + "time" - "github.com/paypal/gorealis" - "github.com/spf13/cobra" + "github.com/paypal/gorealis" + "github.com/spf13/cobra" + + log "github.com/sirupsen/logrus" ) var username, password, zkAddr, schedAddr string @@ -19,14 +19,15 @@ var skipCertVerification bool var caCertsPath string var clientKey, clientCert string var configFile string +var toJson bool +var logLevel string -const australisVer = "v0.0.5" +const australisVer = "v0.0.6" var monitorInterval, monitorTimeout int func init() { - rootCmd.SetVersionTemplate(`{{printf "%s\n" .Version}}`) rootCmd.PersistentFlags().StringVarP(&zkAddr, "zookeeper", "z", "", "Zookeeper node(s) where Aurora stores information. (comma separated list)") @@ -38,7 +39,8 @@ func init() { rootCmd.PersistentFlags().StringVarP(&caCertsPath, "caCertsPath", "a", "", "Path where CA certificates can be found.") rootCmd.PersistentFlags().BoolVarP(&skipCertVerification, "skipCertVerification", "i", false, "Skip CA certificate hostname verification.") rootCmd.PersistentFlags().StringVar(&configFile, "config", "/etc/aurora/australis.yml", "Config file to use.") - + rootCmd.PersistentFlags().BoolVar(&toJson, "jsonOutput", false, "Print output in JSON format.") + rootCmd.PersistentFlags().StringVarP(&logLevel, "logLevel", "l", "info", "Set logging level [" + getLoggingLevels() + "].") } var rootCmd = &cobra.Command{ @@ -57,9 +59,23 @@ func Execute() { rootCmd.Execute() } +// TODO(rdelvalle): Move more from connect into this function +func setConfig(cmd *cobra.Command, args []string) { + lvl, err := log.ParseLevel(logLevel) + + if err != nil { + log.Fatalf("Log level %v is not valid\n", logLevel) + } + + log.SetLevel(lvl) +} + func connect(cmd *cobra.Command, args []string) { var err error + + setConfig(cmd, args) + zkAddrSlice := strings.Split(zkAddr, ",") viper.SetConfigFile(configFile) @@ -113,8 +129,7 @@ func connect(cmd *cobra.Command, args []string) { } else if schedAddr != "" { realisOptions = append(realisOptions, realis.SchedulerUrl(schedAddr)) } else { - fmt.Println("Zookeeper address or Scheduler URL must be provided.") - os.Exit(1) + log.Fatalln("Zookeeper address or Scheduler URL must be provided.") } // Client certificate configuration if available @@ -129,9 +144,7 @@ func connect(cmd *cobra.Command, args []string) { client, err = realis.NewRealisClient(realisOptions...) if err != nil { - fmt.Println(err) - os.Exit(1) + log.Fatal(err) } monitor = &realis.Monitor{Client: client} - } diff --git a/cmd/start.go b/cmd/start.go index 81b4751..849cb2b 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -2,10 +2,10 @@ package cmd import ( "fmt" - "os" - "github.com/paypal/gorealis/gen-go/apache/aurora" "github.com/spf13/cobra" + + log "github.com/sirupsen/logrus" ) func init() { @@ -38,31 +38,46 @@ expects a space separated list of hosts to place into maintenance mode.`, } func drain(cmd *cobra.Command, args []string) { - fmt.Println("Setting hosts to DRAINING") - fmt.Println(args) + log.Infoln("Setting hosts to DRAINING") + log.Infoln(args) _, result, err := client.DrainHosts(args...) if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } + log.Debugln(result) + // Monitor change to DRAINING and DRAINED mode hostResult, err := monitor.HostMaintenance( args, []aurora.MaintenanceMode{aurora.MaintenanceMode_DRAINED}, monitorInterval, monitorTimeout) + + transitioned := make([]string, 0,0) if err != nil { + nonTransitioned := make([]string, 0,0) + for host, ok := range hostResult { - if !ok { - fmt.Printf("Host %s did not transtion into desired mode(s)\n", host) + if ok { + transitioned = append(transitioned, host) + } else { + nonTransitioned = append(nonTransitioned, host) } } - fmt.Printf("error: %+v\n", err.Error()) - return + log.Printf("error: %+v\n", err) + if toJson { + fmt.Println(toJSON(nonTransitioned)) + } else { + fmt.Println("Did not enter DRAINED status: ", nonTransitioned) + } } - fmt.Println(result.String()) + if toJson { + fmt.Println(toJSON(transitioned)) + } else { + fmt.Println("Entered DRAINED status: ", transitioned) + } } diff --git a/cmd/stop.go b/cmd/stop.go index 71903c3..1d89068 100644 --- a/cmd/stop.go +++ b/cmd/stop.go @@ -2,10 +2,10 @@ package cmd import ( "fmt" - "os" - "github.com/paypal/gorealis/gen-go/apache/aurora" "github.com/spf13/cobra" + + log "github.com/sirupsen/logrus" ) func init() { @@ -45,42 +45,56 @@ var stopUpdateCmd = &cobra.Command{ } func endMaintenance(cmd *cobra.Command, args []string) { - fmt.Println("Setting hosts to NONE maintenance status.") - fmt.Println(args) + log.Println("Setting hosts to NONE maintenance status.") + log.Println(args) _, result, err := client.EndMaintenance(args...) if err != nil { - fmt.Printf("error: %+v\n", err.Error()) - os.Exit(1) + log.Fatalf("error: %+v\n", err) } + log.Debugln(result) + // Monitor change to NONE mode hostResult, err := monitor.HostMaintenance( args, []aurora.MaintenanceMode{aurora.MaintenanceMode_NONE}, monitorInterval, monitorTimeout) + + transitioned := make([]string, 0,0) if err != nil { + nonTransitioned := make([]string, 0,0) + for host, ok := range hostResult { - if !ok { - fmt.Printf("Host %s did not transtion into desired mode(s)\n", host) + if ok { + transitioned = append(transitioned, host) + } else { + nonTransitioned = append(nonTransitioned, host) } } - fmt.Printf("error: %+v\n", err.Error()) - return + log.Printf("error: %+v\n", err) + if toJson { + fmt.Println(toJSON(nonTransitioned)) + } else { + fmt.Println("Did not enter NONE status: ", nonTransitioned) + } } - fmt.Println(result.String()) + if toJson { + fmt.Println(toJSON(transitioned)) + } else { + fmt.Println("Entered NONE status: ", transitioned) + } } func stopUpdate(cmd *cobra.Command, args []string) { if len(args) != 1 { - fmt.Println("Only a single update ID must be provided.") - os.Exit(1) + log.Fatalln("Only a single update ID must be provided.") } - fmt.Printf("Stopping (aborting) update [%s/%s/%s] %s\n", *env, *role, *name, args[0]) + log.Infof("Stopping (aborting) update [%s/%s/%s] %s\n", *env, *role, *name, args[0]) resp, err := client.AbortJobUpdate(aurora.JobUpdateKey{ Job: &aurora.JobKey{Environment: *env, Role: *role, Name: *name}, @@ -89,9 +103,12 @@ func stopUpdate(cmd *cobra.Command, args []string) { "") if err != nil { - fmt.Println(err) - os.Exit(1) + log.Fatalln(err) } - fmt.Println(resp.String()) + if toJson{ + fmt.Println(toJSON(resp.GetResult_())) + } else { + fmt.Println(resp.GetDetails()) + } } diff --git a/cmd/util.go b/cmd/util.go new file mode 100644 index 0000000..781233c --- /dev/null +++ b/cmd/util.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "bytes" + "encoding/json" + + log "github.com/sirupsen/logrus" +) + +func toJSON(v interface{}) string { + + output, err := json.Marshal(v) + + if err != nil { + log.Fatalln("Unable to serialize statuses") + } + + return string(output) +} + + +func getLoggingLevels() string { + + var buffer bytes.Buffer + + for _, level := range log.AllLevels { + buffer.WriteString(level.String()) + buffer.WriteString(" ") + } + + buffer.Truncate(buffer.Len()-1) + + return buffer.String() + +} \ No newline at end of file