diff --git a/cmd/linkcheck/links.go b/cmd/linkcheck/links.go new file mode 100644 index 00000000000..7de7b9a4586 --- /dev/null +++ b/cmd/linkcheck/links.go @@ -0,0 +1,82 @@ +/* +Copyright 2015 The Kubernetes Authors All rights reserved. + +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" + "path" + "path/filepath" + "regexp" + "strings" + + flag "github.com/spf13/pflag" +) + +var ( + httpRE *regexp.Regexp + + rootDir = flag.String("root-dir", "", "Root directory containing documents to be processed.") + repoRoot = flag.String("repo-root", "", `Root directory of k8s repository.`) + fileSuffix = flag.String("file-suffix", "", "suffix of files to be checked") + prefix = flag.String("prefix", "", "Longest common prefix of the link URL, e.g., http://release.k8s.io/HEAD/ for links in pkg/api/types.go") +) + +func newWalkFunc(invalidLink *bool) filepath.WalkFunc { + return func(filePath string, info os.FileInfo, err error) error { + if !strings.HasSuffix(info.Name(), *fileSuffix) { + return nil + } + fileBytes, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + foundInvalid := false + matches := httpRE.FindAllSubmatch(fileBytes, -1) + for _, match := range matches { + //match[1] should look like docs/devel/api-conventions.md + if _, err := os.Stat(path.Join(*repoRoot, string(match[1]))); err != nil { + fmt.Fprintf(os.Stderr, "Link is not valid: %s\n", string(match[0])) + foundInvalid = true + } + } + if foundInvalid { + fmt.Fprintf(os.Stderr, "Found invalid links in %s\n", filePath) + *invalidLink = true + } + return nil + } +} + +func main() { + flag.Parse() + httpRE = regexp.MustCompile(*prefix + `(.*\.md)`) + + if *rootDir == "" || *repoRoot == "" || *prefix == "" { + flag.Usage() + os.Exit(2) + } + invalidLink := false + if err := filepath.Walk(*rootDir, newWalkFunc(&invalidLink)); err != nil { + fmt.Fprintf(os.Stderr, "Fail: %v.\n", err) + os.Exit(2) + } + if invalidLink { + os.Exit(1) + } +} diff --git a/hack/lib/golang.sh b/hack/lib/golang.sh index 5ee27ee917f..367ef9fa59f 100644 --- a/hack/lib/golang.sh +++ b/hack/lib/golang.sh @@ -34,6 +34,7 @@ kube::golang::server_targets() { cmd/kubelet cmd/hyperkube cmd/kubernetes + cmd/linkcheck plugin/cmd/kube-scheduler ) if [ -n "${KUBERNETES_CONTRIB:-}" ]; then diff --git a/hack/verify-linkcheck.sh b/hack/verify-linkcheck.sh new file mode 100755 index 00000000000..b758179becc --- /dev/null +++ b/hack/verify-linkcheck.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +# Copyright 2014 The Kubernetes Authors All rights reserved. +# +# 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 +linkcheck=$(kube::util::find-binary "linkcheck") + +if [[ ! -x "$linkcheck" ]]; then + { + echo "It looks as if you don't have a compiled linkcheck binary" + echo + echo "If you are running from a clone of the git repo, please run" + echo "'./hack/build-go.sh cmd/linkcheck'." + } >&2 + exit 1 +fi + +TYPEROOT="${KUBE_ROOT}/pkg/api/" +"${linkcheck}" "--root-dir=${TYPEROOT}" "--repo-root=${KUBE_ROOT}" "--file-suffix=types.go" "--prefix=http://releases.k8s.io/HEAD" && ret=0 || ret=$? +if [[ $ret -eq 1 ]]; then + echo "links in ${TYPEROOT} is out of date." + exit 1 +fi +if [[ $ret -gt 1 ]]; then + echo "Error running linkcheck" + exit 1 +fi diff --git a/hooks/pre-commit b/hooks/pre-commit index 48965e4d3b6..c1ae5ee9d99 100755 --- a/hooks/pre-commit +++ b/hooks/pre-commit @@ -97,6 +97,16 @@ else fi echo "${reset}" +echo -ne "Checking for links in API descriptions... " +if ! hack/verify-linkcheck.sh > /dev/null; then + echo "${red}ERROR!" + echo "Some links in pkg/api/.*types.go is outdated. They require manual fix." + exit_code=1 +else + echo "${green}OK" +fi +echo "${reset}" + echo -ne "Checking for docs that need updating... " if ! hack/verify-gendocs.sh > /dev/null; then echo "${red}ERROR!" diff --git a/shippable.yml b/shippable.yml index 824bb528513..7cfc58eac66 100644 --- a/shippable.yml +++ b/shippable.yml @@ -35,6 +35,7 @@ install: - PATH=$GOPATH/bin:$PATH ./hack/verify-generated-deep-copies.sh - PATH=$GOPATH/bin:./third_party/etcd:$PATH ./hack/verify-gendocs.sh - PATH=$GOPATH/bin:./third_party/etcd:$PATH ./hack/verify-swagger-spec.sh + - PATH=$GOPATH/bin:./third_party/etcd:$PATH ./hack/verify-linkcheck.sh script: - KUBE_RACE="-race" KUBE_COVER="y" KUBE_GOVERALLS_BIN="$HOME/gopath/bin/goveralls" KUBE_TIMEOUT='-timeout 300s' KUBE_COVERPROCS=8 KUBE_TEST_ETCD_PREFIXES="${KUBE_TEST_ETCD_PREFIXES}" KUBE_TEST_API_VERSIONS="${KUBE_TEST_API_VERSIONS}" ./hack/test-go.sh -- -p=2