...

Text file src/k8s.io/kubernetes/hack/update-vendor.sh

Documentation: k8s.io/kubernetes/hack

     1#!/usr/bin/env bash
     2
     3# Copyright 2019 The Kubernetes Authors.
     4#
     5# Licensed under the Apache License, Version 2.0 (the "License");
     6# you may not use this file except in compliance with the License.
     7# You may obtain a copy of the License at
     8#
     9#     http://www.apache.org/licenses/LICENSE-2.0
    10#
    11# Unless required by applicable law or agreed to in writing, software
    12# distributed under the License is distributed on an "AS IS" BASIS,
    13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14# See the License for the specific language governing permissions and
    15# limitations under the License.
    16
    17set -o errexit
    18set -o nounset
    19set -o pipefail
    20
    21# Go tools really don't like it if you have a symlink in `pwd`.
    22cd "$(pwd -P)"
    23
    24KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
    25source "${KUBE_ROOT}/hack/lib/init.sh"
    26
    27# Get all the default Go environment.
    28kube::golang::setup_env
    29
    30# Turn off workspaces until we are ready for them later
    31export GOWORK=off
    32# Explicitly opt into go modules
    33export GO111MODULE=on
    34# Explicitly set GOFLAGS to ignore vendor, since GOFLAGS=-mod=vendor breaks dependency resolution while rebuilding vendor
    35export GOFLAGS=-mod=mod
    36# Ensure sort order doesn't depend on locale
    37export LANG=C
    38export LC_ALL=C
    39# Detect problematic GOPROXY settings that prevent lookup of dependencies
    40if [[ "${GOPROXY:-}" == "off" ]]; then
    41  kube::log::error "Cannot run hack/update-vendor.sh with \$GOPROXY=off"
    42  exit 1
    43fi
    44
    45kube::util::require-jq
    46
    47TMP_DIR="${TMP_DIR:-$(mktemp -d /tmp/update-vendor.XXXX)}"
    48LOG_FILE="${LOG_FILE:-${TMP_DIR}/update-vendor.log}"
    49kube::log::status "logfile at ${LOG_FILE}"
    50
    51# Set up some FDs for this script to use, while capturing everything else to
    52# the log. NOTHING ELSE should write to $LOG_FILE directly.
    53exec 11>&1            # Real stdout, use this explicitly
    54exec 22>&2            # Real stderr, use this explicitly
    55exec 1>"${LOG_FILE}"  # Automatic stdout
    56exec 2>&1             # Automatic stderr
    57set -x                # Trace this script to stderr
    58go env                # For the log
    59
    60function finish {
    61  ret=$?
    62  if [[ ${ret} != 0 ]]; then
    63    echo "An error has occurred. Please see more details in ${LOG_FILE}" >&22
    64  fi
    65  exit ${ret}
    66}
    67trap finish EXIT
    68
    69# ensure_require_replace_directives_for_all_dependencies:
    70# - ensures all existing 'require' directives have an associated 'replace' directive pinning a version
    71# - adds explicit 'require' directives for all transitive dependencies
    72# - adds explicit 'replace' directives for all require directives (existing 'replace' directives take precedence)
    73function ensure_require_replace_directives_for_all_dependencies() {
    74  local local_tmp_dir
    75  local_tmp_dir=$(mktemp -d "${TMP_DIR}/pin_replace.XXXX")
    76
    77  # collect 'require' directives that actually specify a version
    78  local require_filter='(.Version != null) and (.Version != "v0.0.0") and (.Version != "v0.0.0-00010101000000-000000000000")'
    79  # collect 'replace' directives that unconditionally pin versions (old=new@version)
    80  local replace_filter='(.Old.Version == null) and (.New.Version != null)'
    81
    82  # Capture local require/replace directives before running any go commands that can modify the go.mod file
    83  local require_json="${local_tmp_dir}/require.json"
    84  local replace_json="${local_tmp_dir}/replace.json"
    85  go mod edit -json \
    86      | jq -r ".Require // [] | sort | .[] | select(${require_filter})" \
    87      > "${require_json}"
    88  go mod edit -json \
    89      | jq -r ".Replace // [] | sort | .[] | select(${replace_filter})" \
    90      > "${replace_json}"
    91
    92  # Propagate root replace/require directives into staging modules, in case we are downgrading, so they don't bump the root required version back up
    93  for repo in $(kube::util::list_staging_repos); do
    94    (
    95      cd "staging/src/k8s.io/${repo}"
    96      jq -r '"-require \(.Path)@\(.Version)"' < "${require_json}" \
    97          | xargs -L 100 go mod edit -fmt
    98      jq -r '"-replace \(.Old.Path)=\(.New.Path)@\(.New.Version)"' < "${replace_json}" \
    99          | xargs -L 100 go mod edit -fmt
   100    )
   101  done
   102
   103  # tidy to ensure require directives are added for indirect dependencies
   104  go mod tidy
   105}
   106
   107function print_go_mod_section() {
   108  local directive="$1"
   109  local file="$2"
   110
   111  if [ -s "${file}" ]; then
   112      echo "${directive} ("
   113      cat "$file"
   114      echo ")"
   115  fi
   116}
   117
   118function group_directives() {
   119  local local_tmp_dir
   120  local_tmp_dir=$(mktemp -d "${TMP_DIR}/group_replace.XXXX")
   121  local go_mod_require_direct="${local_tmp_dir}/go.mod.require_direct.tmp"
   122  local go_mod_require_indirect="${local_tmp_dir}/go.mod.require_indirect.tmp"
   123  local go_mod_replace="${local_tmp_dir}/go.mod.replace.tmp"
   124  local go_mod_other="${local_tmp_dir}/go.mod.other.tmp"
   125  # separate replace and non-replace directives
   126  awk "
   127     # print lines between 'require (' ... ')' lines
   128     /^require [(]/          { inrequire=1; next                            }
   129     inrequire && /^[)]/     { inrequire=0; next                            }
   130     inrequire && /\/\/ indirect/ { print > \"${go_mod_require_indirect}\"; next }
   131     inrequire               { print > \"${go_mod_require_direct}\";   next }
   132
   133     # print lines between 'replace (' ... ')' lines
   134     /^replace [(]/      { inreplace=1; next                   }
   135     inreplace && /^[)]/ { inreplace=0; next                   }
   136     inreplace           { print > \"${go_mod_replace}\"; next }
   137
   138     # print ungrouped replace directives with the replace directive trimmed
   139     /^replace [^(]/ { sub(/^replace /,\"\"); print > \"${go_mod_replace}\"; next }
   140
   141     # print ungrouped require directives with the require directive trimmed
   142     /^require [^(].*\/\/ indirect/ { sub(/^require /,\"\"); print > \"${go_mod_require_indirect}\"; next }
   143     /^require [^(]/ { sub(/^require /,\"\"); print > \"${go_mod_require_direct}\"; next }
   144
   145     # otherwise print to the other file
   146     { print > \"${go_mod_other}\" }
   147  " < go.mod
   148  {
   149    cat "${go_mod_other}";
   150    print_go_mod_section "require" "${go_mod_require_direct}"
   151    print_go_mod_section "require" "${go_mod_require_indirect}"
   152    print_go_mod_section "replace" "${go_mod_replace}"
   153  } > go.mod
   154
   155  go mod edit -fmt
   156}
   157
   158function add_generated_comments() {
   159  local local_tmp_dir
   160  local_tmp_dir=$(mktemp -d "${TMP_DIR}/add_generated_comments.XXXX")
   161  local go_mod_nocomments="${local_tmp_dir}/go.mod.nocomments.tmp"
   162
   163  # drop comments before the module directive
   164  awk "
   165     BEGIN           { dropcomments=1 }
   166     /^module /      { dropcomments=0 }
   167     dropcomments && /^\/\// { next }
   168     { print }
   169  " < go.mod > "${go_mod_nocomments}"
   170
   171  # Add the specified comments
   172  local comments="${1}"
   173  {
   174    echo "${comments}"
   175    echo ""
   176    cat "${go_mod_nocomments}"
   177   } > go.mod
   178
   179  # Format
   180  go mod edit -fmt
   181}
   182
   183
   184# Phase 1: ensure go.mod files for staging modules and main module
   185
   186for repo in $(kube::util::list_staging_repos); do
   187  (
   188    cd "staging/src/k8s.io/${repo}"
   189
   190    if [[ ! -f go.mod ]]; then
   191      kube::log::status "go.mod: initialize ${repo}" >&11
   192      rm -f Godeps/Godeps.json # remove before initializing, staging Godeps are not authoritative
   193      go mod init "k8s.io/${repo}"
   194      go mod edit -fmt
   195    fi
   196  )
   197done
   198
   199if [[ ! -f go.mod ]]; then
   200  kube::log::status "go.mod: initialize k8s.io/kubernetes" >&11
   201  go mod init "k8s.io/kubernetes"
   202  rm -f Godeps/Godeps.json # remove after initializing
   203fi
   204
   205
   206# Phase 2: ensure staging repo require/replace directives
   207
   208kube::log::status "go.mod: update staging references" >&11
   209# Prune
   210go mod edit -json \
   211    | jq -r '.Require[]? | select(.Version == "v0.0.0")                 | "-droprequire \(.Path)"' \
   212    | xargs -L 100 go mod edit -fmt
   213go mod edit -json \
   214    | jq -r '.Replace[]? | select(.New.Path | startswith("./staging/")) | "-dropreplace \(.Old.Path)"' \
   215    | xargs -L 100 go mod edit -fmt
   216# Re-add
   217kube::util::list_staging_repos \
   218    | while read -r X; do echo "-require k8s.io/${X}@v0.0.0"; done \
   219    | xargs -L 100 go mod edit -fmt
   220kube::util::list_staging_repos \
   221    | while read -r X; do echo "-replace k8s.io/${X}=./staging/src/k8s.io/${X}"; done \
   222    | xargs -L 100 go mod edit -fmt
   223
   224
   225# Phase 3: capture required (minimum) versions from all modules, and replaced (pinned) versions from the root module
   226
   227# pin referenced versions
   228ensure_require_replace_directives_for_all_dependencies
   229# resolves/expands references in the root go.mod (if needed)
   230go mod tidy
   231# pin expanded versions
   232ensure_require_replace_directives_for_all_dependencies
   233# group require/replace directives
   234group_directives
   235
   236# Phase 4: copy root go.mod to staging dirs and rewrite
   237
   238kube::log::status "go.mod: propagate to staging modules" >&11
   239for repo in $(kube::util::list_staging_repos); do
   240  (
   241    cd "staging/src/k8s.io/${repo}"
   242
   243    echo "=== propagating to ${repo}"
   244    # copy root go.mod, changing module name
   245    sed "s#module k8s.io/kubernetes#module k8s.io/${repo}#" \
   246        < "${KUBE_ROOT}/go.mod" \
   247        > "${KUBE_ROOT}/staging/src/k8s.io/${repo}/go.mod"
   248    # remove `require` directives for staging components (will get re-added as needed by `go list`)
   249    kube::util::list_staging_repos \
   250        | while read -r X; do echo "-droprequire k8s.io/${X}"; done \
   251        | xargs -L 100 go mod edit
   252    # rewrite `replace` directives for staging components to point to peer directories
   253    kube::util::list_staging_repos \
   254        | while read -r X; do echo "-replace k8s.io/${X}=../${X}"; done \
   255        | xargs -L 100 go mod edit
   256  )
   257done
   258
   259
   260# Phase 5: sort and tidy staging components
   261
   262kube::log::status "go.mod: sorting staging modules" >&11
   263# tidy staging repos in reverse dependency order.
   264# the content of dependencies' go.mod files affects what `go mod tidy` chooses to record in a go.mod file.
   265tidy_unordered="${TMP_DIR}/tidy_unordered.txt"
   266kube::util::list_staging_repos \
   267    | xargs -I {} echo "k8s.io/{}" > "${tidy_unordered}"
   268rm -f "${TMP_DIR}/tidy_deps.txt"
   269# SC2094 checks that you do not read and write to the same file in a pipeline.
   270# We do read from ${tidy_unordered} in the pipeline and mention it within the
   271# pipeline (but only ready it again) so we disable the lint to assure shellcheck
   272# that :this-is-fine:
   273# shellcheck disable=SC2094
   274while IFS= read -r repo; do
   275  # record existence of the repo to ensure modules with no peer relationships still get included in the order
   276  echo "${repo} ${repo}" >> "${TMP_DIR}/tidy_deps.txt"
   277
   278  (
   279    cd "${KUBE_ROOT}/staging/src/${repo}"
   280
   281    # save the original go.mod, since go list doesn't just add missing entries, it also removes specific required versions from it
   282    tmp_go_mod="${TMP_DIR}/tidy_${repo/\//_}_go.mod.original"
   283    tmp_go_deps="${TMP_DIR}/tidy_${repo/\//_}_deps.txt"
   284    cp go.mod "${tmp_go_mod}"
   285
   286    echo "=== sorting ${repo}"
   287    # 'go list' calculates direct imports and updates go.mod so that go list -m lists our module dependencies
   288    echo "=== computing imports for ${repo}"
   289    go list all
   290    # ignore errors related to importing `package main` packages, but catch
   291    # other errors (https://github.com/golang/go/issues/59186)
   292    errs=()
   293    kube::util::read-array errs < <(
   294        go list -e -tags=tools -json all | jq -r '.Error.Err | select( . != null )' \
   295            | grep -v "is a program, not an importable package"
   296    )
   297    if (( "${#errs[@]}" != 0 )); then
   298        for err in "${errs[@]}"; do
   299            echo "${err}" >&2
   300        done
   301        exit 1
   302    fi
   303
   304    # capture module dependencies
   305    go list -m -f '{{if not .Main}}{{.Path}}{{end}}' all > "${tmp_go_deps}"
   306
   307    # restore the original go.mod file
   308    cp "${tmp_go_mod}" go.mod
   309
   310    # list all module dependencies
   311    for dep in $(join "${tidy_unordered}" "${tmp_go_deps}"); do
   312      # record the relationship (put dep first, because we want to sort leaves first)
   313      echo "${dep} ${repo}" >> "${TMP_DIR}/tidy_deps.txt"
   314      # switch the required version to an explicit v0.0.0 (rather than an unknown v0.0.0-00010101000000-000000000000)
   315      go mod edit -require "${dep}@v0.0.0"
   316    done
   317  )
   318done < "${tidy_unordered}"
   319
   320kube::log::status "go.mod: tidying" >&11
   321for repo in $(tsort "${TMP_DIR}/tidy_deps.txt"); do
   322  (
   323    cd "${KUBE_ROOT}/staging/src/${repo}"
   324    echo "=== tidying ${repo}"
   325
   326    # prune replace directives that pin to the naturally selected version.
   327    # do this before tidying, since tidy removes unused modules that
   328    # don't provide any relevant packages, which forgets which version of the
   329    # unused transitive dependency we had a require directive for,
   330    # and prevents pruning the matching replace directive after tidying.
   331    go list -m -json all |
   332      jq -r 'select(.Replace != null) |
   333             select(.Path == .Replace.Path) |
   334             select(.Version == .Replace.Version) |
   335             "-dropreplace \(.Replace.Path)"' |
   336    xargs -L 100 go mod edit -fmt
   337
   338    go mod tidy -v
   339
   340    # disallow transitive dependencies on k8s.io/kubernetes
   341    loopback_deps=()
   342    kube::util::read-array loopback_deps < <(go list all 2>/dev/null | grep k8s.io/kubernetes/ || true)
   343    if (( "${#loopback_deps[@]}" > 0 )); then
   344      kube::log::error "${#loopback_deps[@]} disallowed ${repo} -> k8s.io/kubernetes dependencies exist via the following imports: $(go mod why "${loopback_deps[@]}")" >&22 2>&1
   345      exit 1
   346    fi
   347
   348    # prune unused pinned replace directives
   349    comm -23 \
   350      <(go mod edit -json | jq -r '.Replace[] | .Old.Path' | sort) \
   351      <(go list -m -json all | jq -r .Path | sort) |
   352    while read -r X; do echo "-dropreplace=${X}"; done |
   353    xargs -L 100 go mod edit -fmt
   354
   355    # prune replace directives that pin to the naturally selected version
   356    go list -m -json all |
   357      jq -r 'select(.Replace != null) |
   358             select(.Path == .Replace.Path) |
   359             select(.Version == .Replace.Version) |
   360             "-dropreplace \(.Replace.Path)"' |
   361    xargs -L 100 go mod edit -fmt
   362
   363    # group require/replace directives
   364    group_directives
   365  )
   366done
   367echo "=== tidying root"
   368go mod tidy
   369
   370# prune unused pinned non-local replace directives
   371comm -23 \
   372  <(go mod edit -json | jq -r '.Replace[] | select(.New.Path | startswith("./") | not) | .Old.Path' | sort) \
   373  <(go list -m -json all | jq -r .Path | sort) |
   374while read -r X; do echo "-dropreplace=${X}"; done |
   375xargs -L 100 go mod edit -fmt
   376
   377# disallow transitive dependencies on k8s.io/kubernetes
   378loopback_deps=()
   379kube::util::read-array loopback_deps < <(go mod graph | grep ' k8s.io/kubernetes' || true)
   380if (( "${#loopback_deps[@]}" > 0 )); then
   381  kube::log::error "${#loopback_deps[@]} disallowed transitive k8s.io/kubernetes dependencies exist via the following imports:" >&22 2>&1
   382  kube::log::error "${loopback_deps[@]}" >&22 2>&1
   383  exit 1
   384fi
   385
   386# Phase 6: add generated comments to go.mod files
   387kube::log::status "go.mod: adding generated comments" >&11
   388add_generated_comments "
   389// This is a generated file. Do not edit directly.
   390// Ensure you've carefully read
   391// https://git.k8s.io/community/contributors/devel/sig-architecture/vendor.md
   392// Run hack/pin-dependency.sh to change pinned dependency versions.
   393// Run hack/update-vendor.sh to update go.mod files and the vendor directory.
   394"
   395for repo in $(kube::util::list_staging_repos); do
   396  (
   397    cd "staging/src/k8s.io/${repo}"
   398    add_generated_comments "// This is a generated file. Do not edit directly."
   399  )
   400done
   401
   402
   403# Phase 7: update internal modules
   404kube::log::status "vendor: updating internal modules" >&11
   405hack/update-internal-modules.sh
   406
   407
   408# Phase 8: rebuild vendor directory
   409(
   410  kube::log::status "vendor: running 'go work vendor'" >&11
   411  unset GOWORK
   412  unset GOFLAGS
   413  go work vendor
   414)
   415
   416kube::log::status "vendor: updating vendor/LICENSES" >&11
   417hack/update-vendor-licenses.sh
   418
   419kube::log::status "vendor: creating OWNERS file" >&11
   420rm -f "vendor/OWNERS"
   421cat <<__EOF__ > "vendor/OWNERS"
   422# See the OWNERS docs at https://go.k8s.io/owners
   423
   424options:
   425  # make root approval non-recursive
   426  no_parent_owners: true
   427approvers:
   428- dep-approvers
   429reviewers:
   430- dep-reviewers
   431__EOF__
   432
   433kube::log::status "NOTE: don't forget to handle vendor/* and LICENSE/* files that were added or removed" >&11

View as plain text