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  }