github.com/wtsi-ssg/wrstat@v1.1.4-0.20221008232152-3030622a8cf8/cmd/root.go (about) 1 /******************************************************************************* 2 * Copyright (c) 2021 Genome Research Ltd. 3 * 4 * Author: Sendu Bala <sb10@sanger.ac.uk> 5 * 6 * Permission is hereby granted, free of charge, to any person obtaining 7 * a copy of this software and associated documentation files (the 8 * "Software"), to deal in the Software without restriction, including 9 * without limitation the rights to use, copy, modify, merge, publish, 10 * distribute, sublicense, and/or sell copies of the Software, and to 11 * permit persons to whom the Software is furnished to do so, subject to 12 * the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included 15 * in all copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 ******************************************************************************/ 25 26 // package cmd is the cobra file that enables subcommands and handles 27 // command-line args. 28 29 package cmd 30 31 import ( 32 "bytes" 33 "fmt" 34 "os" 35 "path/filepath" 36 "time" 37 38 "github.com/VertebrateResequencing/wr/jobqueue" 39 "github.com/inconshreveable/log15" 40 "github.com/spf13/cobra" 41 "github.com/wtsi-ssg/wrstat/scheduler" 42 ) 43 44 const userOnlyPerm = 0700 45 46 // appLogger is used for logging events in our commands. 47 var appLogger = log15.New() 48 49 // these variables are accessible by all subcommands. 50 var deployment string 51 var sudo bool 52 53 const connectTimeout = 10 * time.Second 54 55 // RootCmd represents the base command when called without any subcommands. 56 var RootCmd = &cobra.Command{ 57 Use: "wrstat", 58 Short: "wrstat gets stats on all files in a filesystem directory tree.", 59 Long: `wrstat gets stats on all files in a filesystem directory tree. 60 61 It uses wr to queue getting the stats for subsets of the tree, so enabling the 62 work to be done in parallel and potentially distributed over many nodes. 63 64 Before doing anything else, the wr manager must be running. If the manager can 65 run commands on multiple nodes, be sure to set wr's ManagerHost config option to 66 the host you started the manager on. Or run commands from the same node that you 67 started the manager on. 68 69 If you need root to have permission to see all deseired files, either start wr 70 manager as root, or start it as a user that can sudo without a password when 71 running wrstat, and supply the --sudo option to wrstat sub commands. 72 73 For raw stats on a directory and all its sub contents: 74 $ wrstat walk -o [/output/location] -d [dependency_group] [/location/of/interest] 75 76 Combine all the above output files: 77 $ wrstat combine [/output/location] 78 79 Or more easily work on multiple locations of interest at once by doing the 80 above 2 steps on each location and moving the final results to a final location: 81 $ wrstat multi -w [/working/directory] -f [/final/output/dir] [/a /b /c]`, 82 } 83 84 // Execute adds all child commands to the root command and sets flags 85 // appropriately. This is called by main.main(). It only needs to happen once to 86 // the rootCmd. 87 func Execute() { 88 if err := RootCmd.Execute(); err != nil { 89 die(err.Error()) 90 } 91 } 92 93 func init() { 94 // set up logging to stderr 95 appLogger.SetHandler(log15.LvlFilterHandler(log15.LvlInfo, log15.StderrHandler)) 96 97 // global flags 98 RootCmd.PersistentFlags().StringVar(&deployment, 99 "deployment", 100 "production", 101 "the deployment your wr manager was started with") 102 103 RootCmd.PersistentFlags().BoolVar(&sudo, 104 "sudo", 105 false, 106 "created jobs will run with sudo") 107 } 108 109 // hideGlobalFlags can be used for sub-commands that don't need deployment and 110 // sudo options. 111 func hideGlobalFlags(from *cobra.Command, command *cobra.Command, strings []string) { 112 if err := RootCmd.Flags().MarkHidden("deployment"); err != nil { 113 die("err: %s", err) 114 } 115 116 if err := RootCmd.Flags().MarkHidden("sudo"); err != nil { 117 die("err: %s", err) 118 } 119 120 from.Parent().HelpFunc()(command, strings) 121 } 122 123 // logToFile logs to the given file. 124 func logToFile(path string) { 125 fh, err := log15.FileHandler(path, log15.LogfmtFormat()) 126 if err != nil { 127 warn("Could not log to file [%s]: %s", path, err) 128 129 return 130 } 131 132 appLogger.SetHandler(fh) 133 } 134 135 // setCLIFormat logs plain text log messages to STDERR. 136 func setCLIFormat() { 137 appLogger.SetHandler(log15.StreamHandler(os.Stderr, cliFormat())) 138 } 139 140 // cliFormat returns a log15.Format that only prints the plain log msg. 141 func cliFormat() log15.Format { //nolint:ireturn 142 return log15.FormatFunc(func(r *log15.Record) []byte { 143 b := &bytes.Buffer{} 144 fmt.Fprintf(b, "%s\n", r.Msg) 145 146 return b.Bytes() 147 }) 148 } 149 150 // cliPrint outputs the message to STDOUT. 151 func cliPrint(msg string, a ...interface{}) { 152 fmt.Fprintf(os.Stdout, msg, a...) 153 } 154 155 // info is a convenience to log a message at the Info level. 156 func info(msg string, a ...interface{}) { 157 appLogger.Info(fmt.Sprintf(msg, a...)) 158 } 159 160 // warn is a convenience to log a message at the Warn level. 161 func warn(msg string, a ...interface{}) { 162 appLogger.Warn(fmt.Sprintf(msg, a...)) 163 } 164 165 // die is a convenience to log a message at the Error level and exit non zero. 166 func die(msg string, a ...interface{}) { 167 appLogger.Error(fmt.Sprintf(msg, a...)) 168 os.Exit(1) 169 } 170 171 // newScheduler returns a new Scheduler, exiting on error. It also returns a 172 // function you should defer. 173 // 174 // If you provide a non-blank queue, that queue will be used when scheduling. 175 func newScheduler(cwd, queue string) (*scheduler.Scheduler, func()) { 176 s, err := scheduler.New(deployment, cwd, queue, connectTimeout, appLogger, sudo) 177 178 if err != nil { 179 die("%s", err) 180 } 181 182 return s, func() { 183 err = s.Disconnect() 184 if err != nil { 185 warn("failed to disconnect from wr manager: %s", err) 186 } 187 } 188 } 189 190 // repGrp returns a rep_grp that can be used for a wrstat job we will create. 191 func repGrp(cmd, dir, unique string) string { 192 if dir == "" { 193 return fmt.Sprintf("wrstat-%s-%s-%s", cmd, dateStamp(), unique) 194 } 195 196 return fmt.Sprintf("wrstat-%s-%s-%s-%s", cmd, filepath.Base(dir), dateStamp(), unique) 197 } 198 199 // dateStamp returns today's date in the form YYYYMMDD. 200 func dateStamp() string { 201 t := time.Now() 202 203 return t.Format("20060102") 204 } 205 206 // addJobsToQueue adds the jobs to wr's queue. 207 func addJobsToQueue(s *scheduler.Scheduler, jobs []*jobqueue.Job) { 208 if err := s.SubmitJobs(jobs); err != nil { 209 die("failed to add jobs to wr's queue: %s", err) 210 } 211 }