#!/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 # Go tools really don't like it if you have a symlink in `pwd`. cd "$(pwd -P)" KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${KUBE_ROOT}/hack/lib/init.sh" # Get all the default Go environment. kube::golang::setup_env # Turn off workspaces until we are ready for them later export GOWORK=off # Explicitly opt into go modules export GO111MODULE=on # Explicitly set GOFLAGS to ignore vendor, since GOFLAGS=-mod=vendor breaks dependency resolution while rebuilding vendor export GOFLAGS=-mod=mod # Ensure sort order doesn't depend on locale export LANG=C export LC_ALL=C # Detect problematic GOPROXY settings that prevent lookup of dependencies if [[ "${GOPROXY:-}" == "off" ]]; then kube::log::error "Cannot run hack/update-vendor.sh with \$GOPROXY=off" exit 1 fi kube::util::require-jq TMP_DIR="${TMP_DIR:-$(mktemp -d /tmp/update-vendor.XXXX)}" LOG_FILE="${LOG_FILE:-${TMP_DIR}/update-vendor.log}" kube::log::status "logfile at ${LOG_FILE}" # Set up some FDs for this script to use, while capturing everything else to # the log. NOTHING ELSE should write to $LOG_FILE directly. exec 11>&1 # Real stdout, use this explicitly exec 22>&2 # Real stderr, use this explicitly exec 1>"${LOG_FILE}" # Automatic stdout exec 2>&1 # Automatic stderr set -x # Trace this script to stderr go env # For the log function finish { ret=$? if [[ ${ret} != 0 ]]; then echo "An error has occurred. Please see more details in ${LOG_FILE}" >&22 fi exit ${ret} } trap finish EXIT function print_go_mod_section() { local directive="$1" local file="$2" if [ -s "${file}" ]; then echo "${directive} (" cat "$file" echo ")" fi } function group_directives() { local local_tmp_dir local_tmp_dir=$(mktemp -d "${TMP_DIR}/group_replace.XXXX") local go_mod_require_direct="${local_tmp_dir}/go.mod.require_direct.tmp" local go_mod_require_indirect="${local_tmp_dir}/go.mod.require_indirect.tmp" local go_mod_replace="${local_tmp_dir}/go.mod.replace.tmp" local go_mod_other="${local_tmp_dir}/go.mod.other.tmp" # separate replace and non-replace directives awk " # print lines between 'require (' ... ')' lines /^require [(]/ { inrequire=1; next } inrequire && /^[)]/ { inrequire=0; next } inrequire && /\/\/ indirect/ { print > \"${go_mod_require_indirect}\"; next } inrequire { print > \"${go_mod_require_direct}\"; next } # print lines between 'replace (' ... ')' lines /^replace [(]/ { inreplace=1; next } inreplace && /^[)]/ { inreplace=0; next } inreplace { print > \"${go_mod_replace}\"; next } # print ungrouped replace directives with the replace directive trimmed /^replace [^(]/ { sub(/^replace /,\"\"); print > \"${go_mod_replace}\"; next } # print ungrouped require directives with the require directive trimmed /^require [^(].*\/\/ indirect/ { sub(/^require /,\"\"); print > \"${go_mod_require_indirect}\"; next } /^require [^(]/ { sub(/^require /,\"\"); print > \"${go_mod_require_direct}\"; next } # otherwise print to the other file { print > \"${go_mod_other}\" } " < go.mod { cat "${go_mod_other}"; print_go_mod_section "require" "${go_mod_require_direct}" print_go_mod_section "require" "${go_mod_require_indirect}" print_go_mod_section "replace" "${go_mod_replace}" } > go.mod go mod edit -fmt } function add_generated_comments() { local local_tmp_dir local_tmp_dir=$(mktemp -d "${TMP_DIR}/add_generated_comments.XXXX") local go_mod_nocomments="${local_tmp_dir}/go.mod.nocomments.tmp" # drop comments before the module directive awk " BEGIN { dropcomments=1 } /^module / { dropcomments=0 } dropcomments && /^\/\// { next } { print } " < go.mod > "${go_mod_nocomments}" # Add the specified comments local comments="${1}" { echo "${comments}" echo "" cat "${go_mod_nocomments}" } > go.mod # Format go mod edit -fmt } function add_staging_replace_directives() { local path_to_staging_k8s_io="$1" # Prune go mod edit -json \ | jq -r '.Require[]? | select(.Version == "v0.0.0") | "-droprequire \(.Path)"' \ | xargs -L 100 go mod edit -fmt go mod edit -json \ | jq -r '.Replace[]? | select(.New.Path | startswith("'"${path_to_staging_k8s_io}"'")) | "-dropreplace \(.Old.Path)"' \ | xargs -L 100 go mod edit -fmt # Re-add kube::util::list_staging_repos \ | while read -r X; do echo "-require k8s.io/${X}@v0.0.0"; done \ | xargs -L 100 go mod edit -fmt kube::util::list_staging_repos \ | while read -r X; do echo "-replace k8s.io/${X}=${path_to_staging_k8s_io}/${X}"; done \ | xargs -L 100 go mod edit -fmt } # === Capture go / godebug directives from root go.mod go_directive_value=$(grep '^go 1.' go.mod | awk '{print $2}' || true) if [[ -z "${go_directive_value}" ]]; then kube::log::error "root go.mod must have 'go 1.x.y' directive" >&22 2>&1 exit 1 fi godebug_directive_value=$(grep 'godebug default=go' go.mod | awk '{print $2}' || true) if [[ -z "${godebug_directive_value}" ]]; then kube::log::error "root go.mod must have 'godebug default=go1.x' directive" >&22 2>&1 exit 1 fi # === Ensure staging go.mod files exist for repo in $(kube::util::list_staging_repos); do ( cd "staging/src/k8s.io/${repo}" if [[ ! -f go.mod ]]; then kube::log::status "go.mod: initialize ${repo}" >&11 go mod init "k8s.io/${repo}" fi go mod edit -go "${go_directive_value}" -godebug "${godebug_directive_value}" ) done # === Ensure root and staging go.mod files refer to each other using v0.0.0 and local path replaces kube::log::status "go.mod: update staging module references" >&11 add_staging_replace_directives "./staging/src/k8s.io" for repo in $(kube::util::list_staging_repos); do ( cd "staging/src/k8s.io/${repo}" add_staging_replace_directives ".." ) done # === Ensure all root and staging modules are included in go.work kube::log::status "go.mod: go work use" >&11 ( cd "${KUBE_ROOT}" unset GOWORK unset GOFLAGS if [[ ! -f go.work ]]; then kube::log::status "go.work: initialize" >&11 go work init fi # Prune use directives go work edit -json \ | jq -r '.Use[]? | "-dropuse \(.DiskPath)"' \ | xargs -L 100 go work edit -fmt # Ensure go and godebug directives go work edit -go "${go_directive_value}" -godebug "${godebug_directive_value}" # Re-add use directives go work use . for repo in $(kube::util::list_staging_repos); do go work use "./staging/src/k8s.io/${repo}" done ) # === Propagate MVS across all root / staging modules (calculated by `go work`) back into root / staging modules kube::log::status "go.mod: go work sync" >&11 ( cd "${KUBE_ROOT}" unset GOWORK unset GOFLAGS go work sync ) # === Tidy kube::log::status "go.mod: tidy" >&11 for repo in $(kube::util::list_staging_repos); do ( echo "=== tidying k8s.io/${repo}" cd "staging/src/k8s.io/${repo}" go mod tidy -v group_directives ) done echo "=== tidying root" go mod tidy -v group_directives # === Prune unused replace directives, format modules kube::log::status "go.mod: prune" >&11 for repo in $(kube::util::list_staging_repos); do ( echo "=== pruning k8s.io/${repo}" cd "staging/src/k8s.io/${repo}" # drop all unused replace directives comm -23 \ <(go mod edit -json | jq -r '.Replace[] | .Old.Path' | sort) \ <(go list -m -json all | jq -r 'select(.Main | not) | .Path' | sort) | while read -r X; do echo "-dropreplace=${X}"; done | xargs -L 100 go mod edit -fmt group_directives ) done echo "=== pruning root" # drop unused replace directives other than to local paths comm -23 \ <(go mod edit -json | jq -r '.Replace[] | select(.New.Path | startswith("./") | not) | .Old.Path' | sort) \ <(go list -m -json all | jq -r 'select(.Main | not) | .Path' | sort) | while read -r X; do echo "-dropreplace=${X}"; done | xargs -L 100 go mod edit -fmt group_directives # === Add generated comments to go.mod files kube::log::status "go.mod: adding generated comments" >&11 add_generated_comments " // This is a generated file. Do not edit directly. // Ensure you've carefully read // https://git.k8s.io/community/contributors/devel/sig-architecture/vendor.md // Run hack/pin-dependency.sh to change pinned dependency versions. // Run hack/update-vendor.sh to update go.mod files and the vendor directory. " for repo in $(kube::util::list_staging_repos); do ( cd "staging/src/k8s.io/${repo}" add_generated_comments "// This is a generated file. Do not edit directly." ) done # === Update internal modules kube::log::status "vendor: updating internal modules" >&11 hack/update-internal-modules.sh # === Rebuild vendor directory ( kube::log::status "vendor: running 'go work vendor'" >&11 unset GOWORK unset GOFLAGS # rebuild go.work.sum rm -f go.work.sum go mod download # rebuild vendor go work vendor ) kube::log::status "vendor: updating vendor/LICENSES" >&11 hack/update-vendor-licenses.sh kube::log::status "vendor: creating OWNERS file" >&11 rm -f "vendor/OWNERS" cat <<__EOF__ > "vendor/OWNERS" # See the OWNERS docs at https://go.k8s.io/owners options: # make root approval non-recursive no_parent_owners: true approvers: - dep-approvers reviewers: - dep-reviewers __EOF__ # === Disallow transitive dependencies on k8s.io/kubernetes kube::log::status "go.mod: prevent staging --> k8s.io/kubernetes dep" >&11 for repo in $(kube::util::list_staging_repos); do ( echo "=== checking k8s.io/${repo}" cd "staging/src/k8s.io/${repo}" loopback_deps=() kube::util::read-array loopback_deps < <(go list all 2>/dev/null | grep k8s.io/kubernetes/ || true) if (( "${#loopback_deps[@]}" > 0 )); then kube::log::error "${#loopback_deps[@]} disallowed ${repo} -> k8s.io/kubernetes dependencies exist via the following imports: $(go mod why "${loopback_deps[@]}")" >&22 2>&1 exit 1 fi ) done kube::log::status "go.mod: prevent k8s.io/kubernetes --> * --> k8s.io/kubernetes dep" >&11 loopback_deps=() kube::util::read-array loopback_deps < <(go mod graph | grep ' k8s.io/kubernetes' || true) if (( "${#loopback_deps[@]}" > 0 )); then kube::log::error "${#loopback_deps[@]} disallowed transitive k8s.io/kubernetes dependencies exist via the following imports:" >&22 2>&1 kube::log::error "${loopback_deps[@]}" >&22 2>&1 exit 1 fi kube::log::status "NOTE: don't forget to handle vendor/* and LICENSE/* files that were added or removed" >&11