...

Source file src/edge-infra.dev/cmd/edge/monitoring/dashman/cli/cli.go

Documentation: edge-infra.dev/cmd/edge/monitoring/dashman/cli

     1  package cli
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"flag"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"text/tabwriter"
    14  	"time"
    15  
    16  	"github.com/peterbourgon/ff/v3/ffcli"
    17  
    18  	"edge-infra.dev/pkg/lib/build/bazel"
    19  	"edge-infra.dev/pkg/lib/cli/commands"
    20  	dashmgr "edge-infra.dev/pkg/lib/gcp/monitoring/dashboardmanager"
    21  	"edge-infra.dev/pkg/lib/gcp/monitoring/monutil"
    22  )
    23  
    24  var (
    25  	Stderr  io.Writer = os.Stderr
    26  	Stdout  io.Writer = os.Stdout
    27  	Println           = log.Println
    28  	Printf            = log.Printf
    29  	Fatalf            = log.Fatalf
    30  	Errorf            = fmt.Errorf
    31  	Sprintf           = fmt.Sprintf
    32  )
    33  
    34  func isBoolFlag(f *flag.Flag) bool {
    35  	bf, ok := f.Value.(interface {
    36  		IsBoolFlag() bool
    37  	})
    38  	return ok && bf.IsBoolFlag()
    39  }
    40  
    41  func countFlags(fs *flag.FlagSet) (n int) {
    42  	fs.VisitAll(func(*flag.Flag) { n++ })
    43  	return n
    44  }
    45  
    46  func newFlagSet(name string) *flag.FlagSet {
    47  	onError := flag.ExitOnError
    48  	fs := flag.NewFlagSet(name, onError)
    49  	fs.SetOutput(Stderr)
    50  	return fs
    51  }
    52  
    53  var (
    54  	projectID string
    55  	verbose   = false
    56  	wd        string
    57  	tPath     string
    58  )
    59  
    60  var globalFlags = func() *flag.FlagSet {
    61  	fset := flag.NewFlagSet("global", flag.ExitOnError)
    62  	fset.StringVar(&projectID, "project", "", "GCP project ID for dashboard management.")
    63  	fset.BoolVar(&verbose, "verbose", false, "Enable verbose output of command")
    64  	return fset
    65  }()
    66  
    67  // withGlobalFlags adds the global flags to the FlagSet.
    68  func withGlobalFlags(fset *flag.FlagSet) *flag.FlagSet {
    69  	globalFlags.VisitAll(func(f *flag.Flag) {
    70  		fset.Var(f.Value, f.Name, f.Usage)
    71  	})
    72  	return fset
    73  }
    74  
    75  // Run runs the CLI. The args do not include the binary name.
    76  func Run(args []string) (err error) {
    77  	rootfs := newFlagSet("dashman")
    78  	rootCmd := &ffcli.Command{
    79  		Name: "dashman",
    80  		// ShortUsage: "dashman [flags] <subcommand> [command flags]",
    81  		ShortUsage: "dashman <subcommand> [command flags]",
    82  		ShortHelp:  "GCP Dashboard Manager Utility",
    83  		LongHelp: strings.TrimSpace(`
    84  For help on subcommands, add --help after: "dashman sync --help".
    85  This CLI is still under active development. Commands and flags may
    86  change in the future.
    87  `),
    88  		Subcommands: []*ffcli.Command{
    89  			addCmd,
    90  			deleteCmd,
    91  			getCmd,
    92  			syncCmd,
    93  			updateCmd,
    94  			commands.Version(),
    95  		},
    96  		FlagSet:   rootfs,
    97  		Exec:      func(context.Context, []string) error { return flag.ErrHelp },
    98  		UsageFunc: usageFunc,
    99  	}
   100  
   101  	wd, err = bazel.ResolveWd()
   102  	if err != nil {
   103  		return errors.New(Sprintf("failed to determine current working directory: %s", err.Error()))
   104  	}
   105  
   106  	err = os.Chdir(wd)
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	for _, c := range rootCmd.Subcommands {
   112  		c.UsageFunc = usageFunc
   113  	}
   114  
   115  	if err = rootCmd.Parse(args); err != nil {
   116  		if errors.Is(err, flag.ErrHelp) {
   117  			return nil
   118  		}
   119  		return err
   120  	}
   121  
   122  	dashmgr.Verbose = verbose
   123  	err = rootCmd.Run(context.Background())
   124  	if errors.Is(err, flag.ErrHelp) {
   125  		return nil
   126  	}
   127  	return err
   128  }
   129  
   130  func usageFunc(c *ffcli.Command) string {
   131  	var b strings.Builder
   132  
   133  	fmt.Fprintf(&b, "USAGE\n")
   134  	if c.ShortUsage != "" {
   135  		fmt.Fprintf(&b, "  %s\n", c.ShortUsage)
   136  	} else {
   137  		fmt.Fprintf(&b, "  %s\n", c.Name)
   138  	}
   139  	fmt.Fprintf(&b, "\n")
   140  
   141  	if c.LongHelp != "" {
   142  		fmt.Fprintf(&b, "%s\n\n", c.LongHelp)
   143  	}
   144  
   145  	if len(c.Subcommands) > 0 {
   146  		fmt.Fprintf(&b, "SUBCOMMANDS\n")
   147  		tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0)
   148  		for _, subcommand := range c.Subcommands {
   149  			fmt.Fprintf(tw, "  %s\t%s\n", subcommand.Name, subcommand.ShortHelp)
   150  		}
   151  		tw.Flush()
   152  		fmt.Fprintf(&b, "\n")
   153  	}
   154  
   155  	if countFlags(c.FlagSet) > 0 { //nolint
   156  		fmt.Fprintf(&b, "FLAGS\n")
   157  		tw := tabwriter.NewWriter(&b, 0, 2, 2, ' ', 0)
   158  		c.FlagSet.VisitAll(func(f *flag.Flag) {
   159  			var s string
   160  			name, usage := flag.UnquoteUsage(f)
   161  			if isBoolFlag(f) {
   162  				s = fmt.Sprintf("  --%s, --%s=false", f.Name, f.Name)
   163  			} else {
   164  				s = fmt.Sprintf("  --%s", f.Name) // Two spaces before --; see next two comments.
   165  				if len(name) > 0 {
   166  					s += " " + name
   167  				}
   168  			}
   169  			// Four spaces before the tab triggers good alignment
   170  			// for both 4- and 8-space tab stops.
   171  			s += "\n    \t"
   172  			s += strings.ReplaceAll(usage, "\n", "\n    \t")
   173  
   174  			if f.DefValue != "" {
   175  				s += fmt.Sprintf(" (default %s)", f.DefValue)
   176  			}
   177  
   178  			fmt.Fprintln(&b, s+"\n")
   179  		})
   180  		tw.Flush()
   181  		fmt.Fprintf(&b, "\n")
   182  	}
   183  	return strings.TrimSpace(b.String())
   184  }
   185  
   186  // checks the provided path for validity as file or folder source
   187  func checkPath(path string, folder bool) bool {
   188  	var err error
   189  
   190  	tPath, err = filepath.Abs(path)
   191  	if err != nil {
   192  		Printf("Error evaluating absolute path: %s\n", err.Error())
   193  		return false
   194  	}
   195  
   196  	if !monutil.FileExists(path) {
   197  		Println("Path specified is invalid")
   198  		return false
   199  	}
   200  
   201  	if !monutil.IsDirectory(path) && folder {
   202  		Println("Path specified appears to be a file but folder path is required")
   203  		return false
   204  	}
   205  	return true
   206  }
   207  
   208  // returns the date plus the number of days specified
   209  func getDate(numDays int) string {
   210  	currTime := time.Now()
   211  	newTime := currTime.AddDate(0, 0, numDays)
   212  	return newTime.Format("2006-01-02")
   213  }
   214  
   215  // enables verbose loging
   216  func vPrintln(msg string) {
   217  	if verbose {
   218  		fmt.Println(msg)
   219  	}
   220  }
   221  
   222  // enables verbose formatted loging
   223  func vPrintf(str string, msg ...interface{}) {
   224  	if verbose {
   225  		fmt.Printf(str, msg...)
   226  	}
   227  }
   228  
   229  // splits a string array and removes whitespace
   230  func strArray(str string) []string {
   231  	array := strings.Split(str, ",")
   232  	for i := 0; i < len(array); i++ {
   233  		array[i] = strings.TrimSpace(array[i])
   234  	}
   235  	return array
   236  }
   237  

View as plain text