From daafa3a4cb93c9c81863da34b7d1ca14e50cd430 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Sat, 22 Jun 2019 23:09:10 +0300 Subject: [PATCH 1/6] hack: include a verify-whitespace script and fix whitespace issues --- LICENSE | 2 +- OWNERS | 2 +- SECURITY_CONTACTS | 2 +- code-of-conduct.md | 2 +- hack/utils.sh | 24 ++++++++++++ hack/verify-whitespace.sh | 48 +++++++++++++++++++++++ scripts/publish-manager.sh | 2 +- third_party/forked/loadbalancer/README.md | 2 +- 8 files changed, 78 insertions(+), 6 deletions(-) create mode 100644 hack/utils.sh create mode 100755 hack/verify-whitespace.sh diff --git a/LICENSE b/LICENSE index f49a4e1..261eeb9 100644 --- a/LICENSE +++ b/LICENSE @@ -198,4 +198,4 @@ 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. \ No newline at end of file + limitations under the License. diff --git a/OWNERS b/OWNERS index b334d3f..41da9ae 100644 --- a/OWNERS +++ b/OWNERS @@ -8,4 +8,4 @@ approvers: reviewers: - cluster-api-maintainers - - cluster-api-provider-docker-maintainers \ No newline at end of file + - cluster-api-provider-docker-maintainers diff --git a/SECURITY_CONTACTS b/SECURITY_CONTACTS index 3dd08fd..7da23c4 100644 --- a/SECURITY_CONTACTS +++ b/SECURITY_CONTACTS @@ -13,4 +13,4 @@ detiber justinsb luxas -timothysc \ No newline at end of file +timothysc diff --git a/code-of-conduct.md b/code-of-conduct.md index 3cd88b2..0d15c00 100644 --- a/code-of-conduct.md +++ b/code-of-conduct.md @@ -1,3 +1,3 @@ # Kubernetes Community Code of Conduct -Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) \ No newline at end of file +Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) diff --git a/hack/utils.sh b/hack/utils.sh new file mode 100644 index 0000000..105d171 --- /dev/null +++ b/hack/utils.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +# Copyright 2019 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. + +# get_root_path returns the root path of the project source tree +get_root_path() { + echo "$(git rev-parse --show-toplevel)" +} + +# cd_root_path cds to the root path of the project source tree +cd_root_path() { + cd "$(get_root_path)" || exit +} diff --git a/hack/verify-whitespace.sh b/hack/verify-whitespace.sh new file mode 100755 index 0000000..5701e3a --- /dev/null +++ b/hack/verify-whitespace.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Copyright 2019 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 nounset +set -o pipefail + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +# cd to the root path +cd_root_path + +echo "Verifying trailing whitespace..." +TRAILING="$(grep -rnI '[[:blank:]]$' . | grep -v -e .git)" + +ERR="0" +if [[ ! -z "$TRAILING" ]]; then + echo "Found trailing whitespace in the follow files:" + echo "${TRAILING}" + ERR="1" +fi + +echo -e "Verifying new lines at end of files..." +FILES="$(git ls-files | grep -I -v -e vendor)" +while read -r LINE; do + grep -qI . "${LINE}" || continue # skip binary files + c="$(tail -c 1 "${LINE}")" + if [[ "$c" != "" ]]; then + echo "${LINE}: no newline at the end of file" + ERR=1 + fi +done <<< "${FILES}" + +if [[ "$ERR" == "1" ]]; then + echo "Found whitespace errors!" + exit 1 +fi diff --git a/scripts/publish-manager.sh b/scripts/publish-manager.sh index 0ee9bf6..1e7fcff 100755 --- a/scripts/publish-manager.sh +++ b/scripts/publish-manager.sh @@ -22,4 +22,4 @@ TAG=${TAG:-latest} IMAGE="gcr.io/${REGISTRY}/capd-manager:${TAG}" docker build --file Dockerfile -t "${IMAGE}" . -gcloud docker -- push "${IMAGE}" \ No newline at end of file +gcloud docker -- push "${IMAGE}" diff --git a/third_party/forked/loadbalancer/README.md b/third_party/forked/loadbalancer/README.md index 76d0aa4..5cbbd71 100644 --- a/third_party/forked/loadbalancer/README.md +++ b/third_party/forked/loadbalancer/README.md @@ -1 +1 @@ -Forked from Kind v0.3.0 release branch to expose some generic functionality. \ No newline at end of file +Forked from Kind v0.3.0 release branch to expose some generic functionality. From a481e7c8a3abebee4cc1d344ac0e3bf1d45f15c1 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Sat, 22 Jun 2019 23:42:11 +0300 Subject: [PATCH 2/6] hack: include a verify-golint script and fix linting issues --- actuators/actuators.go | 6 +++- actuators/machine.go | 8 +++++- execer/client.go | 6 ++++ hack/verify-golint.sh | 51 +++++++++++++++++++++++++++++++++ kind/actions/cluster_actions.go | 8 +++++- kind/actions/kind.go | 2 ++ kind/kubeadm/config.go | 11 +++++-- 7 files changed, 86 insertions(+), 6 deletions(-) create mode 100755 hack/verify-golint.sh diff --git a/actuators/actuators.go b/actuators/actuators.go index c9ec400..4cfc862 100644 --- a/actuators/actuators.go +++ b/actuators/actuators.go @@ -39,12 +39,15 @@ func getRole(machine *clusterv1.Machine) string { return setValue } +// Cluster defines a cluster actuator object type Cluster struct{} +// NewClusterActuator returns a new cluster actuator object func NewClusterActuator() *Cluster { return &Cluster{} } +// Reconcile setups an external load balancer for the cluster if needed func (c *Cluster) Reconcile(cluster *clusterv1.Cluster) error { elb, err := getExternalLoadBalancerNode(cluster.Name) if err != nil { @@ -72,11 +75,12 @@ func getExternalLoadBalancerNode(clusterName string) (*nodes.Node, error) { return nil, nil } if len(elb) > 1 { - return nil, errors.New("Too many external load balancers.") + return nil, errors.New("too many external load balancers") } return &elb[0], nil } +// Delete can be used to delete a cluster func (c *Cluster) Delete(cluster *clusterv1.Cluster) error { fmt.Println("Cluster delete is not implemented.") return nil diff --git a/actuators/machine.go b/actuators/machine.go index a3e2cea..3eaba1c 100644 --- a/actuators/machine.go +++ b/actuators/machine.go @@ -41,11 +41,13 @@ const ( clusterAPIControlPlaneSetLabel = "controlplane" ) +// Machine defines a machine actuator type type Machine struct { Core corev1.CoreV1Interface ClusterAPI v1alpha1.ClusterV1alpha1Interface } +// NewMachineActuator returns a new machine actuator object func NewMachineActuator(clusterapi v1alpha1.ClusterV1alpha1Interface, core corev1.CoreV1Interface) *Machine { return &Machine{ Core: core, @@ -53,7 +55,8 @@ func NewMachineActuator(clusterapi v1alpha1.ClusterV1alpha1Interface, core corev } } -// Have to print all the errors because cluster-api swallows them +// Create creates a machine for a given cluster +// Note: have to print all the errors because cluster-api swallows them func (m *Machine) Create(ctx context.Context, c *clusterv1.Cluster, machine *clusterv1.Machine) error { old := machine.DeepCopy() fmt.Printf("Creating a machine for cluster %q\n", c.Name) @@ -175,11 +178,13 @@ func (m *Machine) Delete(ctx context.Context, cluster *clusterv1.Cluster, machin return nil } +// Update updates a machine func (m *Machine) Update(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) error { fmt.Println("Update machine is not implemented yet.") return nil } +// Exists returns true if a machine exists in the cluster func (m *Machine) Exists(ctx context.Context, cluster *clusterv1.Cluster, machine *clusterv1.Machine) (bool, error) { if machine.Spec.ProviderID != nil { return true, nil @@ -236,6 +241,7 @@ func providerID(name string) string { return fmt.Sprintf("docker://%s", name) } +// CAPIroleToKindRole converts a CAPI role to kind role // TODO there is a better way to do this. func CAPIroleToKindRole(CAPIRole string) string { if CAPIRole == clusterAPIControlPlaneSetLabel { diff --git a/execer/client.go b/execer/client.go index dff17cb..77e88b0 100644 --- a/execer/client.go +++ b/execer/client.go @@ -35,6 +35,7 @@ type Client struct { ExtraEnv []string } +// NewClient returns a new client object func NewClient(command string) *Client { return &Client{ Stdout: os.Stdout, @@ -44,6 +45,9 @@ func NewClient(command string) *Client { } } +// PipeToCommand pipes a standard input stream to a client command, starts the command and waits +// until the client command has finished writing its standard output and standard error to the respective +// client buffers. func (c *Client) PipeToCommand(stdin io.Reader, args ...string) error { cmd := exec.Command(c.Command, args...) cmd.Env = append(os.Environ(), c.ExtraEnv...) @@ -83,6 +87,7 @@ func (c *Client) PipeToCommand(stdin io.Reader, args ...string) error { return nil } +// RunCommandReturnOutput runs a client command and returns its output func (c *Client) RunCommandReturnOutput(args ...string) (string, error) { cmd := exec.Command(c.Command, args...) cmd.Env = append(os.Environ(), c.ExtraEnv...) @@ -126,6 +131,7 @@ func (c *Client) RunCommandReturnOutput(args ...string) (string, error) { } +// RunCommand runs a client command func (c *Client) RunCommand(args ...string) error { cmd := exec.Command(c.Command, args...) cmd.Env = append(os.Environ(), c.ExtraEnv...) diff --git a/hack/verify-golint.sh b/hack/verify-golint.sh new file mode 100755 index 0000000..a0c10a5 --- /dev/null +++ b/hack/verify-golint.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Copyright 2019 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. + +# CI script to run go lint over our code +set -o errexit +set -o nounset +set -o pipefail + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" + +# cd to the root path +REPO_PATH=$(get_root_path) + +# create a temporary directory +TMP_DIR=$(mktemp -d) + +# cleanup +exitHandler() ( + echo "Cleaning up..." + rm -rf "${TMP_DIR}" +) +trap exitHandler EXIT + +# pull the source code and build the binary +cd "${TMP_DIR}" +URL="http://github.com/golang/lint" +echo "Cloning ${URL} in ${TMP_DIR}..." +git clone --quiet --depth=1 "${URL}" . +echo "Building golint..." +export GO111MODULE=on +go build -o ./golint/golint ./golint + +# run the binary +cd "${REPO_PATH}" +echo "Running golint..." +git ls-files | grep "\.go" | \ + grep -v "\\/vendor\\/" | \ + xargs -L1 "${TMP_DIR}/golint/golint" -set_exit_status diff --git a/kind/actions/cluster_actions.go b/kind/actions/cluster_actions.go index 31e84ba..a0f1e9d 100644 --- a/kind/actions/cluster_actions.go +++ b/kind/actions/cluster_actions.go @@ -117,6 +117,7 @@ func ConfigureLoadBalancer(clusterName string) error { return errors.WithStack(docker.Kill("SIGHUP", loadBalancerNode.Name())) } +// KubeadmConfig writes the kubeadm config to a node func KubeadmConfig(node *nodes.Node, clusterName, lbip string) error { // get installed kubernetes version from the node image kubeVersion, err := node.KubeVersion() @@ -124,7 +125,7 @@ func KubeadmConfig(node *nodes.Node, clusterName, lbip string) error { return errors.Wrap(err, "failed to get kubernetes version from node") } - kubeadmConfig, err := kubeadm.InitConifguration(kubeVersion, clusterName, fmt.Sprintf("%s:%d", lbip, 6443)) + kubeadmConfig, err := kubeadm.InitConfiguration(kubeVersion, clusterName, fmt.Sprintf("%s:%d", lbip, 6443)) if err != nil { return errors.Wrap(err, "failed to generate kubeadm config content") @@ -137,6 +138,7 @@ func KubeadmConfig(node *nodes.Node, clusterName, lbip string) error { return nil } +// KubeadmInit execute kubeadm init on the boostrap control-plane node of a cluster func KubeadmInit(clusterName string) error { allNodes, err := nodes.List(fmt.Sprintf("label=%s=%s", constants.ClusterLabelKey, clusterName)) if err != nil { @@ -177,6 +179,7 @@ func KubeadmInit(clusterName string) error { return nil } +// InstallCNI installs a CNI plugin from a node func InstallCNI(node *nodes.Node) error { // read the manifest from the node var raw bytes.Buffer @@ -211,6 +214,7 @@ func InstallCNI(node *nodes.Node) error { return nil } +// KubeadmJoin executes kubeadm join on a node func KubeadmJoin(clusterName string, node *nodes.Node) error { allNodes, err := nodes.List(fmt.Sprintf("label=%s=%s", constants.ClusterLabelKey, clusterName)) if err != nil { @@ -243,6 +247,7 @@ func KubeadmJoin(clusterName string, node *nodes.Node) error { return nil } +// SetNodeProviderRef patches a node with docker://node-name as the providerID func SetNodeProviderRef(clusterName, nodeName string) error { allNodes, err := nodes.List(fmt.Sprintf("label=%s=%s", constants.ClusterLabelKey, clusterName)) if err != nil { @@ -274,6 +279,7 @@ func SetNodeProviderRef(clusterName, nodeName string) error { return nil } +// GetNodeRefUID returns the node reference UID func GetNodeRefUID(clusterName, nodeName string) (string, error) { // k get nodes my-cluster-worker -o custom-columns=UID:.metadata.uid --no-headers allNodes, err := nodes.List(fmt.Sprintf("label=%s=%s", constants.ClusterLabelKey, clusterName)) diff --git a/kind/actions/kind.go b/kind/actions/kind.go index 19b4603..3eb689a 100644 --- a/kind/actions/kind.go +++ b/kind/actions/kind.go @@ -179,6 +179,7 @@ func DeleteControlPlane(clusterName, nodeName string) error { return ConfigureLoadBalancer(clusterName) } +// DeleteWorker removes a worker node from a cluster func DeleteWorker(clusterName, nodeName string) error { nodeList, err := nodes.List( fmt.Sprintf("label=%s=%s", constants.ClusterLabelKey, clusterName), @@ -202,6 +203,7 @@ func DeleteWorker(clusterName, nodeName string) error { return nodes.Delete(node) } +// ListControlPlanes returns the list of control-plane nodes for a cluster func ListControlPlanes(clusterName string) ([]nodes.Node, error) { return nodes.List( fmt.Sprintf("label=%s=%s", constants.ClusterLabelKey, clusterName), diff --git a/kind/kubeadm/config.go b/kind/kubeadm/config.go index 0422d16..1656462 100644 --- a/kind/kubeadm/config.go +++ b/kind/kubeadm/config.go @@ -27,12 +27,17 @@ import ( ) const ( - KnownTokenID = "abcdef" + // KnownTokenID is the kubeadm token ID + KnownTokenID = "abcdef" + // KnownTokenSecret is the kubeadm token secret KnownTokenSecret = "0123456789abcdef" - Token = KnownTokenID + "." + KnownTokenSecret + // Token is the kubeadm token formed by combining the token ID and secret + Token = KnownTokenID + "." + KnownTokenSecret ) -func InitConifguration(version, name, controlPlaneEndpoint string) ([]byte, error) { +// InitConfiguration accepts a set of paramenters like Kubernetes version and cluster name, +// and marshals the kubeadm configuration types in a `---` separated JSON document. +func InitConfiguration(version, name, controlPlaneEndpoint string) ([]byte, error) { configuration := &kubeadmv1beta1.InitConfiguration{ TypeMeta: metav1.TypeMeta{ Kind: "InitConfiguration", From 98392c7646a3c189e8bdf03ee4bf00cf04bdf5b2 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Sat, 22 Jun 2019 23:43:09 +0300 Subject: [PATCH 3/6] hack: include a verify-spelling script and fix spelling issues --- hack/verify-spelling.sh | 61 +++++++++++++++++++++++++++++++++ kind/actions/cluster_actions.go | 2 +- 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100755 hack/verify-spelling.sh diff --git a/hack/verify-spelling.sh b/hack/verify-spelling.sh new file mode 100755 index 0000000..6dd53d0 --- /dev/null +++ b/hack/verify-spelling.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Copyright 2019 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 + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +# cd to the root path +cd_root_path + +# create a temporary directory +TMP_DIR=$(mktemp -d) + +# cleanup +exitHandler() ( + echo "Cleaning up..." + rm -rf "${TMP_DIR}" +) +trap exitHandler EXIT + +# pull misspell +export GO111MODULE=on +URL="https://github.com/client9/misspell" +echo "Cloning ${URL} in ${TMP_DIR}..." +git clone --quiet --depth=1 "${URL}" "${TMP_DIR}" +pushd "${TMP_DIR}" > /dev/null +go mod init +popd > /dev/null + +# build misspell +BIN_PATH="${TMP_DIR}/cmd/misspell" +pushd "${BIN_PATH}" > /dev/null +echo "Building misspell..." +go build > /dev/null +popd > /dev/null + +# check spelling +RES=0 +ERROR_LOG="${TMP_DIR}/errors.log" +echo "Checking spelling..." +git ls-files | grep -v -e vendor | xargs "${BIN_PATH}/misspell" > "${ERROR_LOG}" +if [[ -s "${ERROR_LOG}" ]]; then + sed 's/^/error: /' "${ERROR_LOG}" # add 'error' to each line to highlight in e2e status + echo "Found spelling errors!" + RES=1 +fi +exit "${RES}" diff --git a/kind/actions/cluster_actions.go b/kind/actions/cluster_actions.go index a0f1e9d..ebd4e68 100644 --- a/kind/actions/cluster_actions.go +++ b/kind/actions/cluster_actions.go @@ -353,7 +353,7 @@ func KubeadmReset(clusterName, nodeName string) error { fmt.Sprintf("name=^%s$", nodeName), ) if len(nodeList) < 1 { - return errors.Errorf("could nto find node %q", nodeName) + return errors.Errorf("could not find node %q", nodeName) } node := nodeList[0] From 64c3eaf050788326c62f0c77e017c4f95d83c756 Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Tue, 25 Jun 2019 02:03:09 +0300 Subject: [PATCH 4/6] hack: include a verify-gofmt script and fix gofmt issues --- actuators/actuators.go | 2 +- hack/update-gofmt.sh | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100755 hack/update-gofmt.sh diff --git a/actuators/actuators.go b/actuators/actuators.go index 4cfc862..b7a2c30 100644 --- a/actuators/actuators.go +++ b/actuators/actuators.go @@ -20,10 +20,10 @@ import ( "fmt" "io/ioutil" - "sigs.k8s.io/cluster-api-provider-docker/kind/actions" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/cluster-api-provider-docker/kind/actions" clusterv1 "sigs.k8s.io/cluster-api/pkg/apis/cluster/v1alpha1" "sigs.k8s.io/kind/pkg/cluster/constants" "sigs.k8s.io/kind/pkg/cluster/nodes" diff --git a/hack/update-gofmt.sh b/hack/update-gofmt.sh new file mode 100755 index 0000000..ed120eb --- /dev/null +++ b/hack/update-gofmt.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Copyright 2019 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. + +# script to run gofmt over our code (not vendor) +set -o errexit +set -o nounset +set -o pipefail + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +# cd to the root path +cd_root_path + +# update go fmt +go fmt ./... From 7feae074ccc7c4227b8518512088e764c919f16a Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Sat, 22 Jun 2019 23:48:50 +0300 Subject: [PATCH 5/6] hack: include more update and verify scripts update: - deps: calls go mod tidy verify: - build: calls build for ./cmd - deps: verifies if the current go.* files are valid - gotest: calls go tests on _test files if present - govet: calls go vet - shellcheck: executes shellcheck on all .sh files --- hack/update-all.sh | 25 +++++++++++ hack/update-deps.sh | 27 ++++++++++++ hack/verify-all.sh | 88 +++++++++++++++++++++++++++++++++++++++ hack/verify-build.sh | 26 ++++++++++++ hack/verify-deps.sh | 50 ++++++++++++++++++++++ hack/verify-gofmt.sh | 31 ++++++++++++++ hack/verify-gotest.sh | 27 ++++++++++++ hack/verify-govet.sh | 28 +++++++++++++ hack/verify-shellcheck.sh | 54 ++++++++++++++++++++++++ 9 files changed, 356 insertions(+) create mode 100755 hack/update-all.sh create mode 100755 hack/update-deps.sh create mode 100755 hack/verify-all.sh create mode 100755 hack/verify-build.sh create mode 100755 hack/verify-deps.sh create mode 100755 hack/verify-gofmt.sh create mode 100755 hack/verify-gotest.sh create mode 100755 hack/verify-govet.sh create mode 100755 hack/verify-shellcheck.sh diff --git a/hack/update-all.sh b/hack/update-all.sh new file mode 100755 index 0000000..8839e01 --- /dev/null +++ b/hack/update-all.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Copyright 2019 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 + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +REPO_PATH=$(get_root_path) + +"${REPO_PATH}"/hack/update-deps.sh +"${REPO_PATH}"/hack/update-gofmt.sh diff --git a/hack/update-deps.sh b/hack/update-deps.sh new file mode 100755 index 0000000..e4137c9 --- /dev/null +++ b/hack/update-deps.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Copyright 2019 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 nounset +set -o errexit +set -o pipefail + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +# cd to the root path +cd_root_path + +export GO111MODULE="on" +go mod tidy diff --git a/hack/verify-all.sh b/hack/verify-all.sh new file mode 100755 index 0000000..1d227dc --- /dev/null +++ b/hack/verify-all.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +# Copyright 2019 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 + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" + +# set REPO_PATH +REPO_PATH=$(get_root_path) +cd "${REPO_PATH}" + +# exit code, if a script fails we'll set this to 1 +res=0 + +# run all verify scripts, optionally skipping any of them + +if [[ "${VERIFY_WHITESPACE:-true}" == "true" ]]; then + echo "[*] Verifying whitespace..." + hack/verify-whitespace.sh || res=1 + cd "${REPO_PATH}" +fi + +if [[ "${VERIFY_SPELLING:-true}" == "true" ]]; then + echo "[*] Verifying spelling..." + hack/verify-spelling.sh || res=1 + cd "${REPO_PATH}" +fi + +if [[ "${VERIFY_GOFMT:-true}" == "true" ]]; then + echo "[*] Verifying gofmt..." + hack/verify-gofmt.sh || res=1 + cd "${REPO_PATH}" +fi + +if [[ "${VERIFY_GOLINT:-true}" == "true" ]]; then + echo "[*] Verifying golint..." + hack/verify-golint.sh || res=1 + cd "${REPO_PATH}" +fi + +if [[ "${VERIFY_GOVET:-true}" == "true" ]]; then + echo "[*] Verifying govet..." + hack/verify-govet.sh || res=1 + cd "${REPO_PATH}" +fi + +if [[ "${VERIFY_DEPS:-true}" == "true" ]]; then + echo "[*] Verifying deps..." + hack/verify-deps.sh || res=1 + cd "${REPO_PATH}" +fi + +if [[ "${VERIFY_GOTEST:-true}" == "true" ]]; then + echo "[*] Verifying gotest..." + hack/verify-gotest.sh || res=1 + cd "${REPO_PATH}" +fi + +if [[ "${VERIFY_BUILD:-true}" == "true" ]]; then + echo "[*] Verifying build..." + hack/verify-build.sh || res=1 + cd "${REPO_PATH}" +fi + +# exit based on verify scripts +if [[ "${res}" = 0 ]]; then + echo "" + echo "All verify checks passed, congrats!" +else + echo "" + echo "One or more verify checks failed! See output above..." +fi +exit "${res}" diff --git a/hack/verify-build.sh b/hack/verify-build.sh new file mode 100755 index 0000000..8284b60 --- /dev/null +++ b/hack/verify-build.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# Copyright 2019 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 + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" + +# check if the code builds +cd_root_path +export GO111MODULE=on +go build ./cmd/... diff --git a/hack/verify-deps.sh b/hack/verify-deps.sh new file mode 100755 index 0000000..cacc490 --- /dev/null +++ b/hack/verify-deps.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# Copyright 2019 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 nounset +set -o pipefail + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +# cd to the root path +cd_root_path + +# cleanup on exit +cleanup() { + echo "Cleaning up..." + mv go.mod.old go.mod + mv go.sum.old go.sum +} +trap cleanup EXIT + +echo "Verifying..." +# temporary copy the go mod and sum files +cp go.mod go.mod.old || exit +cp go.sum go.sum.old || exit + +# run update-deps.sh +export GO111MODULE="on" +./hack/update-deps.sh + +# compare the old and new files +DIFF0=$(diff -u go.mod go.mod.old) +DIFF1=$(diff -u go.sum go.sum.old) + +if [[ -n "${DIFF0}" ]] || [[ -n "${DIFF1}" ]]; then + echo "${DIFF0}" + echo "${DIFF1}" + echo "Check failed. Please run ./hack/update-deps.sh" + exit 1 +fi diff --git a/hack/verify-gofmt.sh b/hack/verify-gofmt.sh new file mode 100755 index 0000000..c49b850 --- /dev/null +++ b/hack/verify-gofmt.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Copyright 2019 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 + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +# cd to the root path +cd_root_path + +# check for gofmt diffs +diff=$(git ls-files | grep "\.go" | grep -v "\/vendor" | xargs gofmt -s -d 2>&1) +if [[ -n "${diff}" ]]; then + echo "${diff}" + echo + echo "Check failed. Please run hack/update-gofmt.sh" +fi diff --git a/hack/verify-gotest.sh b/hack/verify-gotest.sh new file mode 100755 index 0000000..7f078d6 --- /dev/null +++ b/hack/verify-gotest.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Copyright 2019 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 + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +# cd to the root path +cd_root_path + +# run go test +export GO111MODULE=on +go test ./... diff --git a/hack/verify-govet.sh b/hack/verify-govet.sh new file mode 100755 index 0000000..ccfe41f --- /dev/null +++ b/hack/verify-govet.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Copyright 2019 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. + +# CI script to run go vet over our code +set -o errexit +set -o nounset +set -o pipefail + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +# cd to the root path +cd_root_path + +# run go vet +export GO111MODULE=on +go vet ./... diff --git a/hack/verify-shellcheck.sh b/hack/verify-shellcheck.sh new file mode 100755 index 0000000..2cd3d19 --- /dev/null +++ b/hack/verify-shellcheck.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Copyright 2019 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 + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +ROOT_PATH=$(get_root_path) + +# create a temporary directory +TMP_DIR=$(mktemp -d) + +# cleanup on exit +cleanup() { + echo "Cleaning up..." + rm -rf "${TMP_DIR}" +} +trap cleanup EXIT + +# install shellcheck (Linux-x64 only!) +cd "${TMP_DIR}" || exit +VERSION="shellcheck-v0.6.0" +DOWNLOAD_FILE="${VERSION}.linux.x86_64.tar.xz" +wget https://storage.googleapis.com/shellcheck/"${DOWNLOAD_FILE}" +tar xf "${DOWNLOAD_FILE}" +cd "${VERSION}" || exit + +echo "Running shellcheck..." +cd "${ROOT_PATH}" || exit +OUT="${TMP_DIR}/out.log" +FILES=$(find . -name "*.sh") +while read -r file; do + "${TMP_DIR}/${VERSION}/shellcheck" "$file" >> "${OUT}" 2>&1 +done <<< "$FILES" + +if [[ -s "${OUT}" ]]; then + echo "Found errors:" + cat "${OUT}" + exit 1 +fi From 73d04919cabee62ecbf7a047f4b2710a55ff90bd Mon Sep 17 00:00:00 2001 From: "Lubomir I. Ivanov" Date: Tue, 25 Jun 2019 06:58:58 +0300 Subject: [PATCH 6/6] hack: include boiler plate verification tooling Currently supports .go, .py and .sh files. --- hack/verify-all.sh | 6 ++ hack/verify-boilerplate.go | 174 ++++++++++++++++++++++++++++++++ hack/verify-boilerplate.sh | 25 +++++ hack/verify-boilerplate_test.go | 113 +++++++++++++++++++++ 4 files changed, 318 insertions(+) create mode 100644 hack/verify-boilerplate.go create mode 100755 hack/verify-boilerplate.sh create mode 100644 hack/verify-boilerplate_test.go diff --git a/hack/verify-all.sh b/hack/verify-all.sh index 1d227dc..1bd5a11 100755 --- a/hack/verify-all.sh +++ b/hack/verify-all.sh @@ -41,6 +41,12 @@ if [[ "${VERIFY_SPELLING:-true}" == "true" ]]; then cd "${REPO_PATH}" fi +if [[ "${VERIFY_BOILERPLATE:-true}" == "true" ]]; then + echo "[*] Verifying boilerplate..." + hack/verify-boilerplate.sh || res=1 + cd "${REPO_PATH}" +fi + if [[ "${VERIFY_GOFMT:-true}" == "true" ]]; then echo "[*] Verifying gofmt..." hack/verify-gofmt.sh || res=1 diff --git a/hack/verify-boilerplate.go b/hack/verify-boilerplate.go new file mode 100644 index 0000000..a49cd59 --- /dev/null +++ b/hack/verify-boilerplate.go @@ -0,0 +1,174 @@ +/* +Copyright 2019 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 ( + "errors" + "fmt" + "io/ioutil" + "os" + "regexp" + "strings" +) + +const ( + yearPlaceholder = "YEAR" + boilerPlateStart = "Copyright " + boilerPlateEnd = "limitations under the License." +) + +var ( + supportedExt = []string{".go", ".py", ".sh"} + yearRegexp = regexp.MustCompile("(20)[0-9][0-9]") + boilerPlate = []string{ + boilerPlateStart + yearPlaceholder + " 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", + boilerPlateEnd, + } +) + +// trimLeadingComment strips a single line comment characters such as # or // +// at the exact beginning of a line, but also the first possible space character after it. +func trimLeadingComment(line, c string) string { + if strings.Index(line, c) == 0 { + x := len(c) + if len(line) == x { + return "" + } + if line[x] == byte(' ') { + return line[x+1:] + } + return line[x:] + } + return line +} + +// verifyFileExtension verifies if the file extensions is supported +func isSupportedFileExtension(filePath string) bool { + // check if the file has an extension + idx := strings.LastIndex(filePath, ".") + if idx == -1 { + return false + } + + // check if the file has a supported extension + ext := filePath[idx : idx+len(filePath)-idx] + for _, e := range supportedExt { + if e == ext { + return true + } + } + return false +} + +// verifyBoilerplate verifies if a string contains the boilerplate +func verifyBoilerplate(contents string) error { + idx := 0 + foundBoilerplateStart := false + lines := strings.Split(contents, "\n") + for _, line := range lines { + // handle leading comments + line = trimLeadingComment(line, "//") + line = trimLeadingComment(line, "#") + + // find the start of the boilerplate + bpLine := boilerPlate[idx] + if strings.Contains(line, boilerPlateStart) { + foundBoilerplateStart = true + + // validate the year of the copyright + yearWords := strings.Split(line, " ") + expectedLen := len(strings.Split(boilerPlate[0], " ")) + if len(yearWords) != expectedLen { + return fmt.Errorf("copyright line should contain exactly %d words", expectedLen) + } + if !yearRegexp.MatchString(yearWords[1]) { + return fmt.Errorf("cannot parse the year in the copyright line") + } + bpLine = strings.ReplaceAll(bpLine, yearPlaceholder, yearWords[1]) + } + + // match line by line + if foundBoilerplateStart { + if line != bpLine { + return fmt.Errorf("boilerplate line %d does not match\nexpected: %q\ngot: %q", idx+1, bpLine, line) + } + idx++ + // exit after the last line is found + if strings.Index(line, boilerPlateEnd) == 0 { + break + } + } + } + + if !foundBoilerplateStart { + return fmt.Errorf("the file is missing a boilerplate") + } + if idx < len(boilerPlate) { + return errors.New("boilerplate has missing lines") + } + return nil +} + +// verifyFile verifies if a file contains the boilerplate +func verifyFile(filePath string) error { + if len(filePath) == 0 { + return errors.New("empty file name") + } + + if !isSupportedFileExtension(filePath) { + fmt.Printf("skipping %q: unsupported file type\n", filePath) + return nil + } + + // read the file + b, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + + return verifyBoilerplate(string(b)) +} + +func main() { + if len(os.Args) < 2 { + fmt.Println("usage: " + + "go run verify-boilerplate.go ...") + os.Exit(1) + } + + hasErr := false + for _, filePath := range os.Args[1:] { + if err := verifyFile(filePath); err != nil { + fmt.Printf("error validating %q: %v\n", filePath, err) + hasErr = true + } + } + if hasErr { + os.Exit(1) + } +} diff --git a/hack/verify-boilerplate.sh b/hack/verify-boilerplate.sh new file mode 100755 index 0000000..eeab227 --- /dev/null +++ b/hack/verify-boilerplate.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# Copyright 2019 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 + +# shellcheck source=/dev/null +source "$(dirname "$0")/utils.sh" +# cd to the root path +cd_root_path + +git ls-files | xargs go run ./hack/verify-boilerplate.go diff --git a/hack/verify-boilerplate_test.go b/hack/verify-boilerplate_test.go new file mode 100644 index 0000000..c87b21c --- /dev/null +++ b/hack/verify-boilerplate_test.go @@ -0,0 +1,113 @@ +/* +Copyright 2019 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 ( + "testing" +) + +func TestVerifyBoilerPlate(t *testing.T) { + testcases := []struct { + name string + bp string + expectedError bool + }{ + { + name: "valid: boilerplate is valid", + bp: `\/* +Copyright 2019 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. + */`, + expectedError: false, + }, + { + name: "invalid: missing lines", + bp: ` +Copyright 2019 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +`, + expectedError: true, + }, + { + name: "invalid: bad year", + bp: "Copyright 1019 The Kubernetes Authors.", + expectedError: true, + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + if err := verifyBoilerplate(tc.bp); err != nil != tc.expectedError { + t.Errorf("expected error: %v, got: %v, error: %v", tc.expectedError, err != nil, err) + } + }) + } +} + +func TestTrimLeadingComment(t *testing.T) { + testcases := []struct { + name string + comment string + line string + expectedResult string + }{ + { + name: "trim leading comment", + comment: "#", + line: "# test", + expectedResult: "test", + }, + { + name: "empty line", + comment: "#", + line: "#", + expectedResult: "", + }, + { + name: "trim leading comment and space", + comment: "//", + line: "// test", + expectedResult: "test", + }, + { + name: "no comment", + comment: "//", + line: "test", + expectedResult: "test", + }, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + if res := trimLeadingComment(tc.line, tc.comment); res != tc.expectedResult { + t.Errorf("expected: %q, got: %q", tc.expectedResult, res) + } + }) + } +}