1
16
17 package sanity
18
19 import (
20 "fmt"
21 "os"
22 "regexp"
23 "strings"
24
25 "github.com/spf13/cobra"
26 "github.com/spf13/pflag"
27
28 "k8s.io/kubectl/pkg/util/templates"
29 )
30
31
32 type CmdCheck func(cmd *cobra.Command) []error
33
34
35 type GlobalCheck func() []error
36
37 var (
38
39 AllCmdChecks = []CmdCheck{
40 CheckLongDesc,
41 CheckExamples,
42 CheckFlags,
43 }
44
45
46 AllGlobalChecks = []GlobalCheck{
47 CheckGlobalVarFlags,
48 }
49 )
50
51
52 func RunGlobalChecks(globalChecks []GlobalCheck) []error {
53 fmt.Fprint(os.Stdout, "---+ RUNNING GLOBAL CHECKS\n")
54 errors := []error{}
55 for _, check := range globalChecks {
56 errors = append(errors, check()...)
57 }
58 return errors
59 }
60
61
62 func RunCmdChecks(cmd *cobra.Command, cmdChecks []CmdCheck, skipCmd []string) []error {
63 cmdPath := cmd.CommandPath()
64
65 for _, skipCmdPath := range skipCmd {
66 if cmdPath == skipCmdPath {
67 fmt.Fprintf(os.Stdout, "---+ skipping command %s\n", cmdPath)
68 return []error{}
69 }
70 }
71
72 errors := []error{}
73
74 if cmd.HasSubCommands() {
75 for _, subCmd := range cmd.Commands() {
76 errors = append(errors, RunCmdChecks(subCmd, cmdChecks, skipCmd)...)
77 }
78 }
79
80 fmt.Fprintf(os.Stdout, "---+ RUNNING COMMAND CHECKS on %q\n", cmdPath)
81
82 for _, check := range cmdChecks {
83 if err := check(cmd); len(err) > 0 {
84 errors = append(errors, err...)
85 }
86 }
87
88 return errors
89 }
90
91
92 func CheckLongDesc(cmd *cobra.Command) []error {
93 fmt.Fprint(os.Stdout, " ↳ checking long description\n")
94 cmdPath := cmd.CommandPath()
95 long := cmd.Long
96 if len(long) > 0 {
97 if strings.Trim(long, " \t\n") != long {
98 return []error{fmt.Errorf(`command %q: long description is not normalized, make sure you are calling templates.LongDesc (from pkg/cmd/templates) before assigning cmd.Long`, cmdPath)}
99 }
100 }
101 return nil
102 }
103
104
105 func CheckExamples(cmd *cobra.Command) []error {
106 fmt.Fprint(os.Stdout, " ↳ checking examples\n")
107 cmdPath := cmd.CommandPath()
108 examples := cmd.Example
109 errors := []error{}
110 if len(examples) > 0 {
111 for _, line := range strings.Split(examples, "\n") {
112 if !strings.HasPrefix(line, templates.Indentation) {
113 errors = append(errors, fmt.Errorf(`command %q: examples are not normalized, make sure you are calling templates.Examples (from pkg/cmd/templates) before assigning cmd.Example`, cmdPath))
114 }
115 if trimmed := strings.TrimSpace(line); strings.HasPrefix(trimmed, "//") {
116 errors = append(errors, fmt.Errorf(`command %q: we use # to start comments in examples instead of //`, cmdPath))
117 }
118 }
119 }
120 return errors
121 }
122
123
124 func CheckFlags(cmd *cobra.Command) []error {
125 allFlagsSlice := []*pflag.Flag{}
126
127 cmd.Flags().VisitAll(func(f *pflag.Flag) {
128 allFlagsSlice = append(allFlagsSlice, f)
129 })
130 cmd.PersistentFlags().VisitAll(func(f *pflag.Flag) {
131 allFlagsSlice = append(allFlagsSlice, f)
132 })
133
134 fmt.Fprintf(os.Stdout, " ↳ checking %d flags\n", len(allFlagsSlice))
135
136 errors := []error{}
137
138
139 regex, err := regexp.Compile(`^[a-z]+[a-z\-]*$`)
140 if err != nil {
141 errors = append(errors, fmt.Errorf("command %q: unable to compile regex to check flags", cmd.CommandPath()))
142 return errors
143 }
144 for _, flag := range allFlagsSlice {
145 name := flag.Name
146 if !regex.MatchString(name) {
147 errors = append(errors, fmt.Errorf("command %q: flag name %q is invalid, long form of flag names can only contain lowercase characters or dash (must match %v)", cmd.CommandPath(), name, regex))
148 }
149 }
150
151 return errors
152 }
153
154
155 func CheckGlobalVarFlags() []error {
156 fmt.Fprint(os.Stdout, " ↳ checking flags from global vars\n")
157 errors := []error{}
158 pflag.CommandLine.VisitAll(func(f *pflag.Flag) {
159 errors = append(errors, fmt.Errorf("flag %q is invalid, please don't register any flag under the global variable \"CommandLine\"", f.Name))
160 })
161 return errors
162 }
163
View as plain text