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
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
76 func Run(args []string) (err error) {
77 rootfs := newFlagSet("dashman")
78 rootCmd := &ffcli.Command{
79 Name: "dashman",
80
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 {
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)
165 if len(name) > 0 {
166 s += " " + name
167 }
168 }
169
170
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
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
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
216 func vPrintln(msg string) {
217 if verbose {
218 fmt.Println(msg)
219 }
220 }
221
222
223 func vPrintf(str string, msg ...interface{}) {
224 if verbose {
225 fmt.Printf(str, msg...)
226 }
227 }
228
229
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