| ... | ... |
@@ -70,6 +70,7 @@ verify: build |
| 70 | 70 |
hack/verify-generated-clientsets.sh |
| 71 | 71 |
hack/verify-generated-completions.sh |
| 72 | 72 |
hack/verify-generated-docs.sh |
| 73 |
+ hack/verify-cli-conventions.sh |
|
| 73 | 74 |
PROTO_OPTIONAL=1 hack/verify-generated-protobuf.sh |
| 74 | 75 |
hack/verify-generated-swagger-descriptions.sh |
| 75 | 76 |
hack/verify-generated-swagger-spec.sh |
| 76 | 77 |
new file mode 100755 |
| ... | ... |
@@ -0,0 +1,29 @@ |
| 0 |
+#!/bin/bash |
|
| 1 |
+source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
|
|
| 2 |
+ |
|
| 3 |
+echo "===== Verifying CLI Conventions =====" |
|
| 4 |
+ |
|
| 5 |
+# ensure we have the latest compiled binaries |
|
| 6 |
+"${OS_ROOT}/hack/build-go.sh" tools/clicheck
|
|
| 7 |
+ |
|
| 8 |
+# Find binary |
|
| 9 |
+clicheck="$(os::build::find-binary clicheck)" |
|
| 10 |
+ |
|
| 11 |
+if [[ -z "$clicheck" ]]; then |
|
| 12 |
+ {
|
|
| 13 |
+ echo "It looks as if you don't have a compiled clicheck binary" |
|
| 14 |
+ echo |
|
| 15 |
+ echo "If you are running from a clone of the git repo, please run" |
|
| 16 |
+ echo "'./hack/build-go.sh tools/clicheck'." |
|
| 17 |
+ } >&2 |
|
| 18 |
+ exit 1 |
|
| 19 |
+fi |
|
| 20 |
+ |
|
| 21 |
+if ! output=`$clicheck 2>&1` |
|
| 22 |
+then |
|
| 23 |
+ echo "FAILURE: CLI is not following one or more required conventions:" |
|
| 24 |
+ echo "$output" |
|
| 25 |
+ exit 1 |
|
| 26 |
+else |
|
| 27 |
+ echo "SUCCESS: CLI is following all tested conventions." |
|
| 28 |
+fi |
| 0 | 29 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,78 @@ |
| 0 |
+package sanity |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ "strings" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/openshift/origin/pkg/cmd/templates" |
|
| 8 |
+ "github.com/spf13/cobra" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+type CmdCheck func(cmd *cobra.Command) []error |
|
| 12 |
+ |
|
| 13 |
+var ( |
|
| 14 |
+ AllCmdChecks = []CmdCheck{
|
|
| 15 |
+ CheckLongDesc, |
|
| 16 |
+ CheckExamples, |
|
| 17 |
+ } |
|
| 18 |
+) |
|
| 19 |
+ |
|
| 20 |
+func CheckCmdTree(cmd *cobra.Command, checks []CmdCheck, skip []string) []error {
|
|
| 21 |
+ cmdPath := cmd.CommandPath() |
|
| 22 |
+ |
|
| 23 |
+ for _, skipCmdPath := range skip {
|
|
| 24 |
+ if cmdPath == skipCmdPath {
|
|
| 25 |
+ fmt.Fprintf(os.Stdout, "-----+ skipping command %s\n", cmdPath) |
|
| 26 |
+ return []error{}
|
|
| 27 |
+ } |
|
| 28 |
+ } |
|
| 29 |
+ |
|
| 30 |
+ errors := []error{}
|
|
| 31 |
+ |
|
| 32 |
+ if cmd.HasSubCommands() {
|
|
| 33 |
+ for _, subCmd := range cmd.Commands() {
|
|
| 34 |
+ errors = append(errors, CheckCmdTree(subCmd, checks, skip)...) |
|
| 35 |
+ } |
|
| 36 |
+ } |
|
| 37 |
+ |
|
| 38 |
+ fmt.Fprintf(os.Stdout, "-----+ checking command %s\n", cmdPath) |
|
| 39 |
+ |
|
| 40 |
+ for _, check := range checks {
|
|
| 41 |
+ if err := check(cmd); err != nil && len(err) > 0 {
|
|
| 42 |
+ errors = append(errors, err...) |
|
| 43 |
+ } |
|
| 44 |
+ } |
|
| 45 |
+ |
|
| 46 |
+ return errors |
|
| 47 |
+} |
|
| 48 |
+ |
|
| 49 |
+func CheckLongDesc(cmd *cobra.Command) []error {
|
|
| 50 |
+ cmdPath := cmd.CommandPath() |
|
| 51 |
+ long := cmd.Long |
|
| 52 |
+ if len(long) > 0 {
|
|
| 53 |
+ if strings.Trim(long, " \t\n") != long {
|
|
| 54 |
+ return []error{fmt.Errorf(`command %q: long description is not normalized
|
|
| 55 |
+ ↳ make sure you are calling templates.LongDesc (from pkg/cmd/templates) before assigning cmd.Long`, cmdPath)} |
|
| 56 |
+ } |
|
| 57 |
+ } |
|
| 58 |
+ return nil |
|
| 59 |
+} |
|
| 60 |
+ |
|
| 61 |
+func CheckExamples(cmd *cobra.Command) []error {
|
|
| 62 |
+ cmdPath := cmd.CommandPath() |
|
| 63 |
+ examples := cmd.Example |
|
| 64 |
+ errors := []error{}
|
|
| 65 |
+ if len(examples) > 0 {
|
|
| 66 |
+ for _, line := range strings.Split(examples, "\n") {
|
|
| 67 |
+ if !strings.HasPrefix(line, templates.Indentation) {
|
|
| 68 |
+ errors = append(errors, fmt.Errorf(`command %q: examples are not normalized |
|
| 69 |
+ ↳ make sure you are calling templates.Examples (from pkg/cmd/templates) before assigning cmd.Example`, cmdPath)) |
|
| 70 |
+ } |
|
| 71 |
+ if trimmed := strings.TrimSpace(line); strings.HasPrefix(trimmed, "//") {
|
|
| 72 |
+ errors = append(errors, fmt.Errorf(`command %q: we use # to start comments in examples instead of //`, cmdPath)) |
|
| 73 |
+ } |
|
| 74 |
+ } |
|
| 75 |
+ } |
|
| 76 |
+ return errors |
|
| 77 |
+} |
| 0 | 78 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,37 @@ |
| 0 |
+package main |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "os" |
|
| 5 |
+ |
|
| 6 |
+ "github.com/openshift/origin/pkg/cmd/openshift" |
|
| 7 |
+ cmdsanity "github.com/openshift/origin/pkg/cmd/util/sanity" |
|
| 8 |
+) |
|
| 9 |
+ |
|
| 10 |
+var ( |
|
| 11 |
+ skip = []string{
|
|
| 12 |
+ "openshift kube", // TODO enable when we upstream all these conventions |
|
| 13 |
+ "openshift start kubernetes", // TODO enable when we upstream all these conventions |
|
| 14 |
+ "openshift cli create quota", // TODO has examples starting with '//', enable when we upstream all these conventions |
|
| 15 |
+ "openshift cli adm", // already checked in 'openshift admin' |
|
| 16 |
+ "openshift ex", // we will only care about experimental when they get promoted |
|
| 17 |
+ "openshift cli types", |
|
| 18 |
+ } |
|
| 19 |
+) |
|
| 20 |
+ |
|
| 21 |
+func main() {
|
|
| 22 |
+ errors := []error{}
|
|
| 23 |
+ |
|
| 24 |
+ oc := openshift.NewCommandOpenShift("openshift")
|
|
| 25 |
+ result := cmdsanity.CheckCmdTree(oc, cmdsanity.AllCmdChecks, skip) |
|
| 26 |
+ errors = append(errors, result...) |
|
| 27 |
+ |
|
| 28 |
+ if len(errors) > 0 {
|
|
| 29 |
+ for i, err := range errors {
|
|
| 30 |
+ fmt.Fprintf(os.Stderr, "%d. %s\n\n", i+1, err) |
|
| 31 |
+ } |
|
| 32 |
+ os.Exit(1) |
|
| 33 |
+ } |
|
| 34 |
+ |
|
| 35 |
+ fmt.Fprintln(os.Stdout, "Congrats, CLI looks good!") |
|
| 36 |
+} |