diff --git a/cmd/clicheck/check_cli_conventions.go b/cmd/clicheck/check_cli_conventions.go new file mode 100644 index 00000000000..6c264b87b5d --- /dev/null +++ b/cmd/clicheck/check_cli_conventions.go @@ -0,0 +1,48 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "fmt" + "io/ioutil" + "os" + + "k8s.io/kubernetes/pkg/kubectl/cmd" + cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + cmdsanity "k8s.io/kubernetes/pkg/kubectl/cmd/util/sanity" +) + +var ( + skip = []string{} +) + +func main() { + errors := []error{} + + kubectl := cmd.NewKubectlCommand(cmdutil.NewFactory(nil), os.Stdin, ioutil.Discard, ioutil.Discard) + result := cmdsanity.CheckCmdTree(kubectl, cmdsanity.AllCmdChecks, []string{}) + errors = append(errors, result...) + + if len(errors) > 0 { + for i, err := range errors { + fmt.Fprintf(os.Stderr, "%d. %s\n\n", i+1, err) + } + os.Exit(1) + } + + fmt.Fprintln(os.Stdout, "Congrats, CLI looks good!") +} diff --git a/hack/.linted_packages b/hack/.linted_packages index 87545c341f2..2664e7a8417 100644 --- a/hack/.linted_packages +++ b/hack/.linted_packages @@ -1,6 +1,7 @@ cluster/addons/fluentd-elasticsearch/es-image cluster/images/etcd/attachlease cluster/images/etcd/rollback +cmd/clicheck cmd/gendocs cmd/genkubedocs cmd/genman diff --git a/hack/verify-cli-conventions.sh b/hack/verify-cli-conventions.sh new file mode 100755 index 00000000000..28a1a5c9be2 --- /dev/null +++ b/hack/verify-cli-conventions.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +# Copyright 2016 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -o errexit +set -o nounset +set -o pipefail + +KUBE_ROOT=$(dirname "${BASH_SOURCE}")/.. +source "${KUBE_ROOT}/hack/lib/init.sh" + +kube::golang::setup_env + +BINS=( + cmd/clicheck +) +make -C "${KUBE_ROOT}" WHAT="${BINS[*]}" + +clicheck=$(kube::util::find-binary "clicheck") + +if ! output=`$clicheck 2>&1` +then + echo "FAILURE: CLI is not following one or more required conventions:" + echo "$output" + exit 1 +else + echo "SUCCESS: CLI is following all tested conventions." +fi diff --git a/pkg/kubectl/cmd/util/sanity/cmd_sanity.go b/pkg/kubectl/cmd/util/sanity/cmd_sanity.go new file mode 100644 index 00000000000..1460d3228e8 --- /dev/null +++ b/pkg/kubectl/cmd/util/sanity/cmd_sanity.go @@ -0,0 +1,94 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package sanity + +import ( + "fmt" + "os" + "strings" + + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/kubectl/cmd/templates" +) + +type CmdCheck func(cmd *cobra.Command) []error + +var ( + AllCmdChecks = []CmdCheck{ + CheckLongDesc, + CheckExamples, + } +) + +func CheckCmdTree(cmd *cobra.Command, checks []CmdCheck, skip []string) []error { + cmdPath := cmd.CommandPath() + + for _, skipCmdPath := range skip { + if cmdPath == skipCmdPath { + fmt.Fprintf(os.Stdout, "-----+ skipping command %s\n", cmdPath) + return []error{} + } + } + + errors := []error{} + + if cmd.HasSubCommands() { + for _, subCmd := range cmd.Commands() { + errors = append(errors, CheckCmdTree(subCmd, checks, skip)...) + } + } + + fmt.Fprintf(os.Stdout, "-----+ checking command %s\n", cmdPath) + + for _, check := range checks { + if err := check(cmd); err != nil && len(err) > 0 { + errors = append(errors, err...) + } + } + + return errors +} + +func CheckLongDesc(cmd *cobra.Command) []error { + cmdPath := cmd.CommandPath() + long := cmd.Long + if len(long) > 0 { + if strings.Trim(long, " \t\n") != long { + 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)} + } + } + return nil +} + +func CheckExamples(cmd *cobra.Command) []error { + cmdPath := cmd.CommandPath() + examples := cmd.Example + errors := []error{} + if len(examples) > 0 { + for _, line := range strings.Split(examples, "\n") { + if !strings.HasPrefix(line, templates.Indentation) { + 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)) + } + 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)) + } + } + } + return errors +}