More cli sanity verifications

pull/6/head
Fabiano Franz 2017-05-03 12:59:52 -03:00
parent 151846db80
commit 3bb04c67cc
4 changed files with 84 additions and 18 deletions

View File

@ -31,16 +31,23 @@ var (
) )
func main() { func main() {
errors := []error{} var errorCount int
kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard)
result := cmdsanity.CheckCmdTree(kubectl, cmdsanity.AllCmdChecks, []string{}) errors := cmdsanity.RunCmdChecks(kubectl, cmdsanity.AllCmdChecks, []string{})
errors = append(errors, result...) for _, err := range errors {
errorCount++
fmt.Fprintf(os.Stderr, " %d. %s\n", errorCount, err)
}
if len(errors) > 0 { errors = cmdsanity.RunGlobalChecks(cmdsanity.AllGlobalChecks)
for i, err := range errors { for _, err := range errors {
fmt.Fprintf(os.Stderr, "%d. %s\n\n", i+1, err) errorCount++
} fmt.Fprintf(os.Stderr, " %d. %s\n", errorCount, err)
}
if errorCount > 0 {
fmt.Fprintf(os.Stdout, "Found %d errors.\n", errorCount)
os.Exit(1) os.Exit(1)
} }

View File

@ -32,9 +32,12 @@ clicheck=$(kube::util::find-binary "clicheck")
if ! output=`$clicheck 2>&1` if ! output=`$clicheck 2>&1`
then then
echo "FAILURE: CLI is not following one or more required conventions:"
echo "$output" echo "$output"
echo
echo "FAILURE: CLI is not following one or more required conventions."
exit 1 exit 1
else else
echo "$output"
echo
echo "SUCCESS: CLI is following all tested conventions." echo "SUCCESS: CLI is following all tested conventions."
fi fi

View File

@ -15,6 +15,7 @@ go_library(
deps = [ deps = [
"//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/templates:go_default_library",
"//vendor/github.com/spf13/cobra:go_default_library", "//vendor/github.com/spf13/cobra:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
], ],
) )

View File

@ -19,27 +19,43 @@ package sanity
import ( import (
"fmt" "fmt"
"os" "os"
"regexp"
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates" "k8s.io/kubernetes/pkg/kubectl/cmd/templates"
) )
type CmdCheck func(cmd *cobra.Command) []error type CmdCheck func(cmd *cobra.Command) []error
type GlobalCheck func() []error
var ( var (
AllCmdChecks = []CmdCheck{ AllCmdChecks = []CmdCheck{
CheckLongDesc, CheckLongDesc,
CheckExamples, CheckExamples,
CheckFlags,
}
AllGlobalChecks = []GlobalCheck{
CheckGlobalVarFlags,
} }
) )
func CheckCmdTree(cmd *cobra.Command, checks []CmdCheck, skip []string) []error { func RunGlobalChecks(globalChecks []GlobalCheck) []error {
fmt.Fprint(os.Stdout, "---+ RUNNING GLOBAL CHECKS\n")
errors := []error{}
for _, check := range globalChecks {
errors = append(errors, check()...)
}
return errors
}
func RunCmdChecks(cmd *cobra.Command, cmdChecks []CmdCheck, skipCmd []string) []error {
cmdPath := cmd.CommandPath() cmdPath := cmd.CommandPath()
for _, skipCmdPath := range skip { for _, skipCmdPath := range skipCmd {
if cmdPath == skipCmdPath { if cmdPath == skipCmdPath {
fmt.Fprintf(os.Stdout, "-----+ skipping command %s\n", cmdPath) fmt.Fprintf(os.Stdout, "---+ skipping command %s\n", cmdPath)
return []error{} return []error{}
} }
} }
@ -48,13 +64,13 @@ func CheckCmdTree(cmd *cobra.Command, checks []CmdCheck, skip []string) []error
if cmd.HasSubCommands() { if cmd.HasSubCommands() {
for _, subCmd := range cmd.Commands() { for _, subCmd := range cmd.Commands() {
errors = append(errors, CheckCmdTree(subCmd, checks, skip)...) errors = append(errors, RunCmdChecks(subCmd, cmdChecks, skipCmd)...)
} }
} }
fmt.Fprintf(os.Stdout, "-----+ checking command %s\n", cmdPath) fmt.Fprintf(os.Stdout, "---+ RUNNING COMMAND CHECKS on %q\n", cmdPath)
for _, check := range checks { for _, check := range cmdChecks {
if err := check(cmd); err != nil && len(err) > 0 { if err := check(cmd); err != nil && len(err) > 0 {
errors = append(errors, err...) errors = append(errors, err...)
} }
@ -64,26 +80,26 @@ func CheckCmdTree(cmd *cobra.Command, checks []CmdCheck, skip []string) []error
} }
func CheckLongDesc(cmd *cobra.Command) []error { func CheckLongDesc(cmd *cobra.Command) []error {
fmt.Fprint(os.Stdout, " ↳ checking long description\n")
cmdPath := cmd.CommandPath() cmdPath := cmd.CommandPath()
long := cmd.Long long := cmd.Long
if len(long) > 0 { if len(long) > 0 {
if strings.Trim(long, " \t\n") != long { if strings.Trim(long, " \t\n") != long {
return []error{fmt.Errorf(`command %q: long description is not normalized 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)}
make sure you are calling templates.LongDesc (from pkg/cmd/templates) before assigning cmd.Long`, cmdPath)}
} }
} }
return nil return nil
} }
func CheckExamples(cmd *cobra.Command) []error { func CheckExamples(cmd *cobra.Command) []error {
fmt.Fprint(os.Stdout, " ↳ checking examples\n")
cmdPath := cmd.CommandPath() cmdPath := cmd.CommandPath()
examples := cmd.Example examples := cmd.Example
errors := []error{} errors := []error{}
if len(examples) > 0 { if len(examples) > 0 {
for _, line := range strings.Split(examples, "\n") { for _, line := range strings.Split(examples, "\n") {
if !strings.HasPrefix(line, templates.Indentation) { if !strings.HasPrefix(line, templates.Indentation) {
errors = append(errors, fmt.Errorf(`command %q: examples are not normalized 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))
make sure you are calling templates.Examples (from pkg/cmd/templates) before assigning cmd.Example`, cmdPath))
} }
if trimmed := strings.TrimSpace(line); strings.HasPrefix(trimmed, "//") { if trimmed := strings.TrimSpace(line); strings.HasPrefix(trimmed, "//") {
errors = append(errors, fmt.Errorf(`command %q: we use # to start comments in examples instead of //`, cmdPath)) errors = append(errors, fmt.Errorf(`command %q: we use # to start comments in examples instead of //`, cmdPath))
@ -92,3 +108,42 @@ func CheckExamples(cmd *cobra.Command) []error {
} }
return errors return errors
} }
func CheckFlags(cmd *cobra.Command) []error {
allFlagsSlice := []*pflag.Flag{}
cmd.Flags().VisitAll(func(f *pflag.Flag) {
allFlagsSlice = append(allFlagsSlice, f)
})
cmd.PersistentFlags().VisitAll(func(f *pflag.Flag) {
allFlagsSlice = append(allFlagsSlice, f)
})
fmt.Fprintf(os.Stdout, " ↳ checking %d flags\n", len(allFlagsSlice))
errors := []error{}
// check flags long names
regex, err := regexp.Compile(`^[a-z]+[a-z\-]*$`)
if err != nil {
errors = append(errors, fmt.Errorf("command %q: unable to compile regex to check flags", cmd.CommandPath()))
return errors
}
for _, flag := range allFlagsSlice {
name := flag.Name
if !regex.MatchString(name) {
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))
}
}
return errors
}
func CheckGlobalVarFlags() []error {
fmt.Fprint(os.Stdout, " ↳ checking flags from global vars\n")
errors := []error{}
pflag.CommandLine.VisitAll(func(f *pflag.Flag) {
errors = append(errors, fmt.Errorf("flag %q is invalid, please don't register any flag under the global variable \"CommandLine\"", f.Name))
})
return errors
}