k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/hack/update-vendor.sh (about)

     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  
    17  set -o errexit
    18  set -o nounset
    19  set -o pipefail
    20  
    21  # Go tools really don't like it if you have a symlink in `pwd`.
    22  cd "$(pwd -P)"
    23  
    24  KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
    25  source "${KUBE_ROOT}/hack/lib/init.sh"
    26  
    27  # Get all the default Go environment.
    28  kube::golang::setup_env
    29  
    30  # Turn off workspaces until we are ready for them later
    31  export GOWORK=off
    32  # Explicitly opt into go modules
    33  export GO111MODULE=on
    34  # Explicitly set GOFLAGS to ignore vendor, since GOFLAGS=-mod=vendor breaks dependency resolution while rebuilding vendor
    35  export GOFLAGS=-mod=mod
    36  # Ensure sort order doesn't depend on locale
    37  export LANG=C
    38  export LC_ALL=C
    39  # Detect problematic GOPROXY settings that prevent lookup of dependencies
    40  if [[ "${GOPROXY:-}" == "off" ]]; then
    41    kube::log::error "Cannot run hack/update-vendor.sh with \$GOPROXY=off"
    42    exit 1
    43  fi
    44  
    45  kube::util::require-jq
    46  
    47  TMP_DIR="${TMP_DIR:-$(mktemp -d /tmp/update-vendor.XXXX)}"
    48  LOG_FILE="${LOG_FILE:-${TMP_DIR}/update-vendor.log}"
    49  kube::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.
    53  exec 11>&1            # Real stdout, use this explicitly
    54  exec 22>&2            # Real stderr, use this explicitly
    55  exec 1>"${LOG_FILE}"  # Automatic stdout
    56  exec 2>&1             # Automatic stderr
    57  set -x                # Trace this script to stderr
    58  go env                # For the log
    59  
    60  function 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  }
    67  trap 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)
    73  function 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  
   107  function 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  
   118  function 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  
   158  function 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  
   186  for 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    )
   197  done
   198  
   199  if [[ ! -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
   203  fi
   204  
   205  
   206  # Phase 2: ensure staging repo require/replace directives
   207  
   208  kube::log::status "go.mod: update staging references" >&11
   209  # Prune
   210  go mod edit -json \
   211      | jq -r '.Require[]? | select(.Version == "v0.0.0")                 | "-droprequire \(.Path)"' \
   212      | xargs -L 100 go mod edit -fmt
   213  go 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
   217  kube::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
   220  kube::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
   228  ensure_require_replace_directives_for_all_dependencies
   229  # resolves/expands references in the root go.mod (if needed)
   230  go mod tidy
   231  # pin expanded versions
   232  ensure_require_replace_directives_for_all_dependencies
   233  # group require/replace directives
   234  group_directives
   235  
   236  # Phase 4: copy root go.mod to staging dirs and rewrite
   237  
   238  kube::log::status "go.mod: propagate to staging modules" >&11
   239  for 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    )
   257  done
   258  
   259  
   260  # Phase 5: sort and tidy staging components
   261  
   262  kube::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.
   265  tidy_unordered="${TMP_DIR}/tidy_unordered.txt"
   266  kube::util::list_staging_repos \
   267      | xargs -I {} echo "k8s.io/{}" > "${tidy_unordered}"
   268  rm -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
   274  while 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    )
   318  done < "${tidy_unordered}"
   319  
   320  kube::log::status "go.mod: tidying" >&11
   321  for 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    )
   366  done
   367  echo "=== tidying root"
   368  go mod tidy
   369  
   370  # prune unused pinned non-local replace directives
   371  comm -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) |
   374  while read -r X; do echo "-dropreplace=${X}"; done |
   375  xargs -L 100 go mod edit -fmt
   376  
   377  # disallow transitive dependencies on k8s.io/kubernetes
   378  loopback_deps=()
   379  kube::util::read-array loopback_deps < <(go mod graph | grep ' k8s.io/kubernetes' || true)
   380  if (( "${#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
   384  fi
   385  
   386  # Phase 6: add generated comments to go.mod files
   387  kube::log::status "go.mod: adding generated comments" >&11
   388  add_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  "
   395  for 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    )
   400  done
   401  
   402  
   403  # Phase 7: update internal modules
   404  kube::log::status "vendor: updating internal modules" >&11
   405  hack/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  
   416  kube::log::status "vendor: updating vendor/LICENSES" >&11
   417  hack/update-vendor-licenses.sh
   418  
   419  kube::log::status "vendor: creating OWNERS file" >&11
   420  rm -f "vendor/OWNERS"
   421  cat <<__EOF__ > "vendor/OWNERS"
   422  # See the OWNERS docs at https://go.k8s.io/owners
   423  
   424  options:
   425    # make root approval non-recursive
   426    no_parent_owners: true
   427  approvers:
   428  - dep-approvers
   429  reviewers:
   430  - dep-reviewers
   431  __EOF__
   432  
   433  kube::log::status "NOTE: don't forget to handle vendor/* and LICENSE/* files that were added or removed" >&11