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

     1  #!/usr/bin/env bash
     2  
     3  # Copyright 2014 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  function kube::util::sourced_variable {
    18    # Call this function to tell shellcheck that a variable is supposed to
    19    # be used from other calling context. This helps quiet an "unused
    20    # variable" warning from shellcheck and also document your code.
    21    true
    22  }
    23  
    24  kube::util::sortable_date() {
    25    date "+%Y%m%d-%H%M%S"
    26  }
    27  
    28  # arguments: target, item1, item2, item3, ...
    29  # returns 0 if target is in the given items, 1 otherwise.
    30  kube::util::array_contains() {
    31    local search="$1"
    32    local element
    33    shift
    34    for element; do
    35      if [[ "${element}" == "${search}" ]]; then
    36        return 0
    37       fi
    38    done
    39    return 1
    40  }
    41  
    42  kube::util::wait_for_url() {
    43    local url=$1
    44    local prefix=${2:-}
    45    local wait=${3:-1}
    46    local times=${4:-30}
    47    local maxtime=${5:-1}
    48  
    49    command -v curl >/dev/null || {
    50      kube::log::usage "curl must be installed"
    51      exit 1
    52    }
    53  
    54    local i
    55    for i in $(seq 1 "${times}"); do
    56      local out
    57      if out=$(curl --max-time "${maxtime}" -gkfs "${@:6}" "${url}" 2>/dev/null); then
    58        kube::log::status "On try ${i}, ${prefix}: ${out}"
    59        return 0
    60      fi
    61      sleep "${wait}"
    62    done
    63    kube::log::error "Timed out waiting for ${prefix} to answer at ${url}; tried ${times} waiting ${wait} between each"
    64    return 1
    65  }
    66  
    67  kube::util::wait_for_url_with_bearer_token() {
    68    local url=$1
    69    local token=$2
    70    local prefix=${3:-}
    71    local wait=${4:-1}
    72    local times=${5:-30}
    73    local maxtime=${6:-1}
    74  
    75    kube::util::wait_for_url "${url}" "${prefix}" "${wait}" "${times}" "${maxtime}" -H "Authorization: Bearer ${token}"
    76  }
    77  
    78  # Example:  kube::util::wait_for_success 120 5 "kubectl get nodes|grep localhost"
    79  # arguments: wait time, sleep time, shell command
    80  # returns 0 if the shell command get output, 1 otherwise.
    81  kube::util::wait_for_success(){
    82    local wait_time="$1"
    83    local sleep_time="$2"
    84    local cmd="$3"
    85    while [ "$wait_time" -gt 0 ]; do
    86      if eval "$cmd"; then
    87        return 0
    88      else
    89        sleep "$sleep_time"
    90        wait_time=$((wait_time-sleep_time))
    91      fi
    92    done
    93    return 1
    94  }
    95  
    96  # Example:  kube::util::trap_add 'echo "in trap DEBUG"' DEBUG
    97  # See: http://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal
    98  kube::util::trap_add() {
    99    local trap_add_cmd
   100    trap_add_cmd=$1
   101    shift
   102  
   103    for trap_add_name in "$@"; do
   104      local existing_cmd
   105      local new_cmd
   106  
   107      # Grab the currently defined trap commands for this trap
   108      existing_cmd=$(trap -p "${trap_add_name}" |  awk -F"'" '{print $2}')
   109  
   110      if [[ -z "${existing_cmd}" ]]; then
   111        new_cmd="${trap_add_cmd}"
   112      else
   113        new_cmd="${trap_add_cmd};${existing_cmd}"
   114      fi
   115  
   116      # Assign the test. Disable the shellcheck warning telling that trap
   117      # commands should be single quoted to avoid evaluating them at this
   118      # point instead evaluating them at run time. The logic of adding new
   119      # commands to a single trap requires them to be evaluated right away.
   120      # shellcheck disable=SC2064
   121      trap "${new_cmd}" "${trap_add_name}"
   122    done
   123  }
   124  
   125  # Opposite of kube::util::ensure-temp-dir()
   126  kube::util::cleanup-temp-dir() {
   127    rm -rf "${KUBE_TEMP}"
   128  }
   129  
   130  # Create a temp dir that'll be deleted at the end of this bash session.
   131  #
   132  # Vars set:
   133  #   KUBE_TEMP
   134  kube::util::ensure-temp-dir() {
   135    if [[ -z ${KUBE_TEMP-} ]]; then
   136      KUBE_TEMP=$(mktemp -d 2>/dev/null || mktemp -d -t kubernetes.XXXXXX)
   137      kube::util::trap_add kube::util::cleanup-temp-dir EXIT
   138    fi
   139  }
   140  
   141  kube::util::host_os() {
   142    local host_os
   143    case "$(uname -s)" in
   144      Darwin)
   145        host_os=darwin
   146        ;;
   147      Linux)
   148        host_os=linux
   149        ;;
   150      *)
   151        kube::log::error "Unsupported host OS.  Must be Linux or Mac OS X."
   152        exit 1
   153        ;;
   154    esac
   155    echo "${host_os}"
   156  }
   157  
   158  kube::util::host_arch() {
   159    local host_arch
   160    case "$(uname -m)" in
   161      x86_64*)
   162        host_arch=amd64
   163        ;;
   164      i?86_64*)
   165        host_arch=amd64
   166        ;;
   167      amd64*)
   168        host_arch=amd64
   169        ;;
   170      aarch64*)
   171        host_arch=arm64
   172        ;;
   173      arm64*)
   174        host_arch=arm64
   175        ;;
   176      arm*)
   177        host_arch=arm
   178        ;;
   179      i?86*)
   180        host_arch=x86
   181        ;;
   182      s390x*)
   183        host_arch=s390x
   184        ;;
   185      ppc64le*)
   186        host_arch=ppc64le
   187        ;;
   188      *)
   189        kube::log::error "Unsupported host arch. Must be x86_64, 386, arm, arm64, s390x or ppc64le."
   190        exit 1
   191        ;;
   192    esac
   193    echo "${host_arch}"
   194  }
   195  
   196  # This figures out the host platform without relying on golang.  We need this as
   197  # we don't want a golang install to be a prerequisite to building yet we need
   198  # this info to figure out where the final binaries are placed.
   199  kube::util::host_platform() {
   200    echo "$(kube::util::host_os)/$(kube::util::host_arch)"
   201  }
   202  
   203  # looks for $1 in well-known output locations for the platform ($2)
   204  # $KUBE_ROOT must be set
   205  kube::util::find-binary-for-platform() {
   206    local -r lookfor="$1"
   207    local -r platform="$2"
   208    local locations=(
   209      "${KUBE_ROOT}/_output/bin/${lookfor}"
   210      "${KUBE_ROOT}/_output/dockerized/bin/${platform}/${lookfor}"
   211      "${KUBE_ROOT}/_output/local/bin/${platform}/${lookfor}"
   212      "${KUBE_ROOT}/platforms/${platform}/${lookfor}"
   213    )
   214  
   215    # if we're looking for the host platform, add local non-platform-qualified search paths
   216    if [[ "${platform}" = "$(kube::util::host_platform)" ]]; then
   217      locations+=(
   218        "${KUBE_ROOT}/_output/local/go/bin/${lookfor}"
   219        "${KUBE_ROOT}/_output/dockerized/go/bin/${lookfor}"
   220      );
   221    fi
   222  
   223    # looks for $1 in the $PATH
   224    if which "${lookfor}" >/dev/null; then
   225      local -r local_bin="$(which "${lookfor}")"
   226      locations+=( "${local_bin}"  );
   227    fi
   228  
   229    # List most recently-updated location.
   230    local -r bin=$( (ls -t "${locations[@]}" 2>/dev/null || true) | head -1 )
   231  
   232    if [[ -z "${bin}" ]]; then
   233      kube::log::error "Failed to find binary ${lookfor} for platform ${platform}"
   234      return 1
   235    fi
   236  
   237    echo -n "${bin}"
   238  }
   239  
   240  # looks for $1 in well-known output locations for the host platform
   241  # $KUBE_ROOT must be set
   242  kube::util::find-binary() {
   243    kube::util::find-binary-for-platform "$1" "$(kube::util::host_platform)"
   244  }
   245  
   246  # Takes a group/version and returns the path to its location on disk, sans
   247  # "pkg". E.g.:
   248  # * default behavior: extensions/v1beta1 -> apis/extensions/v1beta1
   249  # * default behavior for only a group: experimental -> apis/experimental
   250  # * Special handling for empty group: v1 -> api/v1, unversioned -> api/unversioned
   251  # * Special handling for groups suffixed with ".k8s.io": foo.k8s.io/v1 -> apis/foo/v1
   252  # * Very special handling for when both group and version are "": / -> api
   253  #
   254  # $KUBE_ROOT must be set.
   255  kube::util::group-version-to-pkg-path() {
   256    local group_version="$1"
   257  
   258    # Make a list of all know APIs by listing their dirs.
   259    local apidirs=()
   260    kube::util::read-array apidirs < <(
   261        cd "${KUBE_ROOT}/staging/src/k8s.io/api" || return 1 # make shellcheck happy
   262        find . -name types.go -exec dirname {} \; \
   263          | sed "s|\./||g" \
   264          | LC_ALL=C sort -u)
   265  
   266    # Compare each API dir against the requested GV, and if we find it, no
   267    # special handling needed.
   268    for api in "${apidirs[@]}"; do
   269      # Change "foo.bar.k8s.io/v1" -> "foo/v1" notation.
   270      local simple_gv="${group_version/.*k8s.io/}"
   271      if [[ "${api}" = "${simple_gv}" ]]; then
   272        echo "staging/src/k8s.io/api/${simple_gv}"
   273        return
   274      fi
   275    done
   276  
   277    # "v1" is the API GroupVersion
   278    if [[ "${group_version}" == "v1" ]]; then
   279      echo "staging/src/k8s.io/api/core/v1"
   280      return
   281    fi
   282  
   283    # Special cases first.
   284    # TODO(lavalamp): Simplify this by moving pkg/api/v1 and splitting pkg/api,
   285    # moving the results to pkg/apis/api.
   286    case "${group_version}" in
   287      # both group and version are "", this occurs when we generate deep copies for internal objects of the legacy v1 API.
   288      __internal)
   289        echo "pkg/apis/core"
   290        ;;
   291      meta/v1)
   292        echo "staging/src/k8s.io/apimachinery/pkg/apis/meta/v1"
   293        ;;
   294      meta/v1beta1)
   295        echo "staging/src/k8s.io/apimachinery/pkg/apis/meta/v1beta1"
   296        ;;
   297      internal.apiserver.k8s.io/v1alpha1)
   298        echo "staging/src/k8s.io/api/apiserverinternal/v1alpha1"
   299        ;;
   300      *.k8s.io)
   301        echo "pkg/apis/${group_version%.*k8s.io}"
   302        ;;
   303      *.k8s.io/*)
   304        echo "pkg/apis/${group_version/.*k8s.io/}"
   305        ;;
   306      *)
   307        echo "pkg/apis/${group_version%__internal}"
   308        ;;
   309    esac
   310  }
   311  
   312  # Takes a group/version and returns the swagger-spec file name.
   313  # default behavior: extensions/v1beta1 -> extensions_v1beta1
   314  # special case for v1: v1 -> v1
   315  kube::util::gv-to-swagger-name() {
   316    local group_version="$1"
   317    case "${group_version}" in
   318      v1)
   319        echo "v1"
   320        ;;
   321      *)
   322        echo "${group_version%/*}_${group_version#*/}"
   323        ;;
   324    esac
   325  }
   326  
   327  # Returns the name of the upstream remote repository name for the local git
   328  # repo, e.g. "upstream" or "origin".
   329  kube::util::git_upstream_remote_name() {
   330    git remote -v | grep fetch |\
   331      grep -E 'github.com[/:]kubernetes/kubernetes|k8s.io/kubernetes' |\
   332      head -n 1 | awk '{print $1}'
   333  }
   334  
   335  # Exits script if working directory is dirty. If it's run interactively in the terminal
   336  # the user can commit changes in a second terminal. This script will wait.
   337  kube::util::ensure_clean_working_dir() {
   338    while ! git diff HEAD --exit-code &>/dev/null; do
   339      echo -e "\nUnexpected dirty working directory:\n"
   340      if tty -s; then
   341          git status -s
   342      else
   343          git diff -a # be more verbose in log files without tty
   344          exit 1
   345      fi | sed 's/^/  /'
   346      echo -e "\nCommit your changes in another terminal and then continue here by pressing enter."
   347      read -r
   348    done 1>&2
   349  }
   350  
   351  # Find the base commit using:
   352  # $PULL_BASE_SHA if set (from Prow)
   353  # current ref from the remote upstream branch
   354  kube::util::base_ref() {
   355    local -r git_branch=$1
   356  
   357    if [[ -n ${PULL_BASE_SHA:-} ]]; then
   358      echo "${PULL_BASE_SHA}"
   359      return
   360    fi
   361  
   362    full_branch="$(kube::util::git_upstream_remote_name)/${git_branch}"
   363  
   364    # make sure the branch is valid, otherwise the check will pass erroneously.
   365    if ! git describe "${full_branch}" >/dev/null; then
   366      # abort!
   367      exit 1
   368    fi
   369  
   370    echo "${full_branch}"
   371  }
   372  
   373  # Checks whether there are any files matching pattern $2 changed between the
   374  # current branch and upstream branch named by $1.
   375  # Returns 1 (false) if there are no changes
   376  #         0 (true) if there are changes detected.
   377  kube::util::has_changes() {
   378    local -r git_branch=$1
   379    local -r pattern=$2
   380    local -r not_pattern=${3:-totallyimpossiblepattern}
   381  
   382    local base_ref
   383    base_ref=$(kube::util::base_ref "${git_branch}")
   384    echo "Checking for '${pattern}' changes against '${base_ref}'"
   385  
   386    # notice this uses ... to find the first shared ancestor
   387    if git diff --name-only "${base_ref}...HEAD" | grep -v -E "${not_pattern}" | grep "${pattern}" > /dev/null; then
   388      return 0
   389    fi
   390    # also check for pending changes
   391    if git status --porcelain | grep -v -E "${not_pattern}" | grep "${pattern}" > /dev/null; then
   392      echo "Detected '${pattern}' uncommitted changes."
   393      return 0
   394    fi
   395    echo "No '${pattern}' changes detected."
   396    return 1
   397  }
   398  
   399  kube::util::download_file() {
   400    local -r url=$1
   401    local -r destination_file=$2
   402  
   403    rm "${destination_file}" 2&> /dev/null || true
   404  
   405    for i in $(seq 5)
   406    do
   407      if ! curl -fsSL --retry 3 --keepalive-time 2 "${url}" -o "${destination_file}"; then
   408        echo "Downloading ${url} failed. $((5-i)) retries left."
   409        sleep 1
   410      else
   411        echo "Downloading ${url} succeed"
   412        return 0
   413      fi
   414    done
   415    return 1
   416  }
   417  
   418  # Test whether openssl is installed.
   419  # Sets:
   420  #  OPENSSL_BIN: The path to the openssl binary to use
   421  function kube::util::test_openssl_installed {
   422      if ! openssl version >& /dev/null; then
   423        echo "Failed to run openssl. Please ensure openssl is installed"
   424        exit 1
   425      fi
   426  
   427      OPENSSL_BIN=$(command -v openssl)
   428  }
   429  
   430  # Query the API server for client certificate authentication capabilities
   431  function kube::util::test_client_certificate_authentication_enabled {
   432    local output
   433    kube::util::test_openssl_installed
   434  
   435    output=$(echo \
   436      | "${OPENSSL_BIN}" s_client -connect "127.0.0.1:${SECURE_API_PORT}" 2> /dev/null \
   437      | grep -A3 'Acceptable client certificate CA names')
   438  
   439    if [[ "${output}" != *"/CN=127.0.0.1"* ]] && [[ "${output}" != *"CN = 127.0.0.1"* ]]; then
   440      echo "API server not configured for client certificate authentication"
   441      echo "Output of from acceptable client certificate check: ${output}"
   442      exit 1
   443    fi
   444  }
   445  
   446  # creates a client CA, args are sudo, dest-dir, ca-id, purpose
   447  # purpose is dropped in after "key encipherment", you usually want
   448  # '"client auth"'
   449  # '"server auth"'
   450  # '"client auth","server auth"'
   451  function kube::util::create_signing_certkey {
   452      local sudo=$1
   453      local dest_dir=$2
   454      local id=$3
   455      local purpose=$4
   456      # Create client ca
   457      ${sudo} /usr/bin/env bash -e <<EOF
   458      rm -f "${dest_dir}/${id}-ca.crt" "${dest_dir}/${id}-ca.key"
   459      ${OPENSSL_BIN} req -x509 -sha256 -new -nodes -days 365 -newkey rsa:2048 -keyout "${dest_dir}/${id}-ca.key" -out "${dest_dir}/${id}-ca.crt" -subj "/C=xx/ST=x/L=x/O=x/OU=x/CN=ca/emailAddress=x/"
   460      echo '{"signing":{"default":{"expiry":"43800h","usages":["signing","key encipherment",${purpose}]}}}' > "${dest_dir}/${id}-ca-config.json"
   461  EOF
   462  }
   463  
   464  # signs a client certificate: args are sudo, dest-dir, CA, filename (roughly), username, groups...
   465  function kube::util::create_client_certkey {
   466      local sudo=$1
   467      local dest_dir=$2
   468      local ca=$3
   469      local id=$4
   470      local cn=${5:-$4}
   471      local groups=""
   472      local SEP=""
   473      shift 5
   474      while [ -n "${1:-}" ]; do
   475          groups+="${SEP}{\"O\":\"$1\"}"
   476          SEP=","
   477          shift 1
   478      done
   479      ${sudo} /usr/bin/env bash -e <<EOF
   480      cd ${dest_dir}
   481      echo '{"CN":"${cn}","names":[${groups}],"hosts":[""],"key":{"algo":"rsa","size":2048}}' | ${CFSSL_BIN} gencert -ca=${ca}.crt -ca-key=${ca}.key -config=${ca}-config.json - | ${CFSSLJSON_BIN} -bare client-${id}
   482      mv "client-${id}-key.pem" "client-${id}.key"
   483      mv "client-${id}.pem" "client-${id}.crt"
   484      rm -f "client-${id}.csr"
   485  EOF
   486  }
   487  
   488  # signs a serving certificate: args are sudo, dest-dir, ca, filename (roughly), subject, hosts...
   489  function kube::util::create_serving_certkey {
   490      local sudo=$1
   491      local dest_dir=$2
   492      local ca=$3
   493      local id=$4
   494      local cn=${5:-$4}
   495      local hosts=""
   496      local SEP=""
   497      shift 5
   498      while [ -n "${1:-}" ]; do
   499          hosts+="${SEP}\"$1\""
   500          SEP=","
   501          shift 1
   502      done
   503      ${sudo} /usr/bin/env bash -e <<EOF
   504      cd ${dest_dir}
   505      echo '{"CN":"${cn}","hosts":[${hosts}],"key":{"algo":"rsa","size":2048}}' | ${CFSSL_BIN} gencert -ca=${ca}.crt -ca-key=${ca}.key -config=${ca}-config.json - | ${CFSSLJSON_BIN} -bare serving-${id}
   506      mv "serving-${id}-key.pem" "serving-${id}.key"
   507      mv "serving-${id}.pem" "serving-${id}.crt"
   508      rm -f "serving-${id}.csr"
   509  EOF
   510  }
   511  
   512  # creates a self-contained kubeconfig: args are sudo, dest-dir, ca file, host, port, client id, token(optional)
   513  function kube::util::write_client_kubeconfig {
   514      local sudo=$1
   515      local dest_dir=$2
   516      local ca_file=$3
   517      local api_host=$4
   518      local api_port=$5
   519      local client_id=$6
   520      local token=${7:-}
   521      cat <<EOF | ${sudo} tee "${dest_dir}"/"${client_id}".kubeconfig > /dev/null
   522  apiVersion: v1
   523  kind: Config
   524  clusters:
   525    - cluster:
   526        certificate-authority: ${ca_file}
   527        server: https://${api_host}:${api_port}/
   528      name: local-up-cluster
   529  users:
   530    - user:
   531        token: ${token}
   532        client-certificate: ${dest_dir}/client-${client_id}.crt
   533        client-key: ${dest_dir}/client-${client_id}.key
   534      name: local-up-cluster
   535  contexts:
   536    - context:
   537        cluster: local-up-cluster
   538        user: local-up-cluster
   539      name: local-up-cluster
   540  current-context: local-up-cluster
   541  EOF
   542  
   543      # flatten the kubeconfig files to make them self contained
   544      username=$(whoami)
   545      ${sudo} /usr/bin/env bash -e <<EOF
   546      $(kube::util::find-binary kubectl) --kubeconfig="${dest_dir}/${client_id}.kubeconfig" config view --minify --flatten > "/tmp/${client_id}.kubeconfig"
   547      mv -f "/tmp/${client_id}.kubeconfig" "${dest_dir}/${client_id}.kubeconfig"
   548      chown ${username} "${dest_dir}/${client_id}.kubeconfig"
   549  EOF
   550  }
   551  
   552  # list_staging_repos outputs a sorted list of repos in staging/src/k8s.io
   553  # each entry will just be the $repo portion of staging/src/k8s.io/$repo/...
   554  # $KUBE_ROOT must be set.
   555  function kube::util::list_staging_repos() {
   556    (
   557      cd "${KUBE_ROOT}/staging/src/k8s.io" && \
   558      find . -mindepth 1 -maxdepth 1 -type d | cut -c 3- | sort
   559    )
   560  }
   561  
   562  
   563  # Determines if docker can be run, failures may simply require that the user be added to the docker group.
   564  function kube::util::ensure_docker_daemon_connectivity {
   565    DOCKER_OPTS=${DOCKER_OPTS:-""}
   566    IFS=" " read -ra docker_opts <<< "${DOCKER_OPTS}"
   567    if ! docker "${docker_opts[@]:+"${docker_opts[@]}"}" info > /dev/null 2>&1 ; then
   568      cat <<'EOF' >&2
   569  Can't connect to 'docker' daemon.  please fix and retry.
   570  
   571  Possible causes:
   572    - Docker Daemon not started
   573      - Linux: confirm via your init system
   574      - macOS w/ Docker for Mac: Check the menu bar and start the Docker application
   575    - DOCKER_HOST hasn't been set or is set incorrectly
   576      - Linux: domain socket is used, DOCKER_* should be unset. In Bash run `unset ${!DOCKER_*}`
   577      - macOS w/ Docker for Mac: domain socket is used, DOCKER_* should be unset. In Bash run `unset ${!DOCKER_*}`
   578    - Other things to check:
   579      - Linux: User isn't in 'docker' group.  Add and relogin.
   580        - Something like 'sudo usermod -a -G docker ${USER}'
   581        - RHEL7 bug and workaround: https://bugzilla.redhat.com/show_bug.cgi?id=1119282#c8
   582  EOF
   583      return 1
   584    fi
   585  }
   586  
   587  # Wait for background jobs to finish. Return with
   588  # an error status if any of the jobs failed.
   589  kube::util::wait-for-jobs() {
   590    local fail=0
   591    local job
   592    for job in $(jobs -p); do
   593      wait "${job}" || fail=$((fail + 1))
   594    done
   595    return ${fail}
   596  }
   597  
   598  # kube::util::join <delim> <list...>
   599  # Concatenates the list elements with the delimiter passed as first parameter
   600  #
   601  # Ex: kube::util::join , a b c
   602  #  -> a,b,c
   603  function kube::util::join {
   604    local IFS="$1"
   605    shift
   606    echo "$*"
   607  }
   608  
   609  # Downloads cfssl/cfssljson into $1 directory if they do not already exist in PATH
   610  #
   611  # Assumed vars:
   612  #   $1 (cfssl directory) (optional)
   613  #
   614  # Sets:
   615  #  CFSSL_BIN: The path of the installed cfssl binary
   616  #  CFSSLJSON_BIN: The path of the installed cfssljson binary
   617  #
   618  # shellcheck disable=SC2120 # optional parameters
   619  function kube::util::ensure-cfssl {
   620    if command -v cfssl &>/dev/null && command -v cfssljson &>/dev/null; then
   621      CFSSL_BIN=$(command -v cfssl)
   622      CFSSLJSON_BIN=$(command -v cfssljson)
   623      return 0
   624    fi
   625  
   626    host_arch=$(kube::util::host_arch)
   627  
   628    if [[ "${host_arch}" != "amd64" ]]; then
   629      echo "Cannot download cfssl on non-amd64 hosts and cfssl does not appear to be installed."
   630      echo "Please install cfssl and cfssljson and verify they are in \$PATH."
   631      echo "Hint: export PATH=\$PATH:\$GOPATH/bin; go install github.com/cloudflare/cfssl/cmd/...@latest"
   632      exit 1
   633    fi
   634  
   635    # Create a temp dir for cfssl if no directory was given
   636    local cfssldir=${1:-}
   637    if [[ -z "${cfssldir}" ]]; then
   638      kube::util::ensure-temp-dir
   639      cfssldir="${KUBE_TEMP}/cfssl"
   640    fi
   641  
   642    mkdir -p "${cfssldir}"
   643    pushd "${cfssldir}" > /dev/null || return 1
   644  
   645      echo "Unable to successfully run 'cfssl' from ${PATH}; downloading instead..."
   646      kernel=$(uname -s)
   647      case "${kernel}" in
   648        Linux)
   649          curl --retry 10 -L -o cfssl https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl_1.5.0_linux_amd64
   650          curl --retry 10 -L -o cfssljson https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssljson_1.5.0_linux_amd64
   651          ;;
   652        Darwin)
   653          curl --retry 10 -L -o cfssl https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssl_1.5.0_darwin_amd64
   654          curl --retry 10 -L -o cfssljson https://github.com/cloudflare/cfssl/releases/download/v1.5.0/cfssljson_1.5.0_darwin_amd64
   655          ;;
   656        *)
   657          echo "Unknown, unsupported platform: ${kernel}." >&2
   658          echo "Supported platforms: Linux, Darwin." >&2
   659          exit 2
   660      esac
   661  
   662      chmod +x cfssl || true
   663      chmod +x cfssljson || true
   664  
   665      CFSSL_BIN="${cfssldir}/cfssl"
   666      CFSSLJSON_BIN="${cfssldir}/cfssljson"
   667      if [[ ! -x ${CFSSL_BIN} || ! -x ${CFSSLJSON_BIN} ]]; then
   668        echo "Failed to download 'cfssl'. Please install cfssl and cfssljson and verify they are in \$PATH."
   669        echo "Hint: export PATH=\$PATH:\$GOPATH/bin; go install github.com/cloudflare/cfssl/cmd/...@latest"
   670        exit 1
   671      fi
   672    popd > /dev/null || return 1
   673  }
   674  
   675  # kube::util::ensure-docker-buildx
   676  # Check if we have "docker buildx" commands available
   677  #
   678  function kube::util::ensure-docker-buildx {
   679    # podman returns 0 on `docker buildx version`, docker on `docker buildx`. One of them must succeed.
   680    if docker buildx version >/dev/null 2>&1 || docker buildx >/dev/null 2>&1; then
   681      return 0
   682    else
   683      echo "ERROR: docker buildx not available. Docker 19.03 or higher is required with experimental features enabled"
   684      exit 1
   685    fi
   686  }
   687  
   688  # kube::util::ensure-bash-version
   689  # Check if we are using a supported bash version
   690  #
   691  function kube::util::ensure-bash-version {
   692    # shellcheck disable=SC2004
   693    if ((${BASH_VERSINFO[0]}<4)) || ( ((${BASH_VERSINFO[0]}==4)) && ((${BASH_VERSINFO[1]}<2)) ); then
   694      echo "ERROR: This script requires a minimum bash version of 4.2, but got version of ${BASH_VERSINFO[0]}.${BASH_VERSINFO[1]}"
   695      if [ "$(uname)" = 'Darwin' ]; then
   696        echo "On macOS with homebrew 'brew install bash' is sufficient."
   697      fi
   698      exit 1
   699    fi
   700  }
   701  
   702  # kube::util::ensure-gnu-sed
   703  # Determines which sed binary is gnu-sed on linux/darwin
   704  #
   705  # Sets:
   706  #  SED: The name of the gnu-sed binary
   707  #
   708  function kube::util::ensure-gnu-sed {
   709    # NOTE: the echo below is a workaround to ensure sed is executed before the grep.
   710    # see: https://github.com/kubernetes/kubernetes/issues/87251
   711    sed_help="$(LANG=C sed --help 2>&1 || true)"
   712    if echo "${sed_help}" | grep -q "GNU\|BusyBox"; then
   713      SED="sed"
   714    elif command -v gsed &>/dev/null; then
   715      SED="gsed"
   716    else
   717      kube::log::error "Failed to find GNU sed as sed or gsed. If you are on Mac: brew install gnu-sed." >&2
   718      return 1
   719    fi
   720    kube::util::sourced_variable "${SED}"
   721  }
   722  
   723  # kube::util::ensure-gnu-date
   724  # Determines which date binary is gnu-date on linux/darwin
   725  #
   726  # Sets:
   727  #  DATE: The name of the gnu-date binary
   728  #
   729  function kube::util::ensure-gnu-date {
   730    # NOTE: the echo below is a workaround to ensure date is executed before the grep.
   731    # see: https://github.com/kubernetes/kubernetes/issues/87251
   732    date_help="$(LANG=C date --help 2>&1 || true)"
   733    if echo "${date_help}" | grep -q "GNU\|BusyBox"; then
   734      DATE="date"
   735    elif command -v gdate &>/dev/null; then
   736      DATE="gdate"
   737    else
   738      kube::log::error "Failed to find GNU date as date or gdate. If you are on Mac: brew install coreutils." >&2
   739      return 1
   740    fi
   741    kube::util::sourced_variable "${DATE}"
   742  }
   743  
   744  # kube::util::check-file-in-alphabetical-order <file>
   745  # Check that the file is in alphabetical order
   746  #
   747  function kube::util::check-file-in-alphabetical-order {
   748    local failure_file="$1"
   749    if ! diff -u "${failure_file}" <(LC_ALL=C sort "${failure_file}"); then
   750      {
   751        echo
   752        echo "${failure_file} is not in alphabetical order. Please sort it:"
   753        echo
   754        echo "  LC_ALL=C sort -o ${failure_file} ${failure_file}"
   755        echo
   756      } >&2
   757      false
   758    fi
   759  }
   760  
   761  # kube::util::require-jq
   762  # Checks whether jq is installed.
   763  function kube::util::require-jq {
   764    if ! command -v jq &>/dev/null; then
   765      kube::log::error  "jq not found. Please install."
   766      return 1
   767    fi
   768  }
   769  
   770  # outputs md5 hash of $1, works on macOS and Linux
   771  function kube::util::md5() {
   772    if which md5 >/dev/null 2>&1; then
   773      md5 -q "$1"
   774    else
   775      md5sum "$1" | awk '{ print $1 }'
   776    fi
   777  }
   778  
   779  # kube::util::read-array
   780  # Reads in stdin and adds it line by line to the array provided. This can be
   781  # used instead of "mapfile -t", and is bash 3 compatible.  If the named array
   782  # exists and is an array, it will be overwritten.  Otherwise it will be unset
   783  # and recreated.
   784  #
   785  # Assumed vars:
   786  #   $1 (name of array to create/modify)
   787  #
   788  # Example usage:
   789  #   kube::util::read-array files < <(ls -1)
   790  #
   791  # When in doubt:
   792  #  $ W=abc         # a string
   793  #  $ X=(a b c)     # an array
   794  #  $ declare -A Y  # an associative array
   795  #  $ unset Z       # not set at all
   796  #  $ declare -p W X Y Z
   797  #  declare -- W="abc"
   798  #  declare -a X=([0]="a" [1]="b" [2]="c")
   799  #  declare -A Y
   800  #  bash: line 26: declare: Z: not found
   801  #  $ kube::util::read-array W < <(echo -ne "1 1\n2 2\n3 3\n")
   802  #  bash: W is defined but isn't an array
   803  #  $ kube::util::read-array X < <(echo -ne "1 1\n2 2\n3 3\n")
   804  #  $ kube::util::read-array Y < <(echo -ne "1 1\n2 2\n3 3\n")
   805  #  bash: Y is defined but isn't an array
   806  #  $ kube::util::read-array Z < <(echo -ne "1 1\n2 2\n3 3\n")
   807  #  $ declare -p W X Y Z
   808  #  declare -- W="abc"
   809  #  declare -a X=([0]="1 1" [1]="2 2" [2]="3 3")
   810  #  declare -A Y
   811  #  declare -a Z=([0]="1 1" [1]="2 2" [2]="3 3")
   812  function kube::util::read-array {
   813    if [[ -z "$1" ]]; then
   814      echo "usage: ${FUNCNAME[0]} <varname>" >&2
   815      return 1
   816    fi
   817    if [[ -n $(declare -p "$1" 2>/dev/null) ]]; then
   818      if ! declare -p "$1" 2>/dev/null | grep -q '^declare -a'; then
   819        echo "${FUNCNAME[0]}: $1 is defined but isn't an array" >&2
   820        return 2
   821      fi
   822    fi
   823    # shellcheck disable=SC2034 # this variable _is_ used
   824    local __read_array_i=0
   825    while IFS= read -r "$1[__read_array_i++]"; do :; done
   826    if ! eval "[[ \${$1[--__read_array_i]} ]]"; then
   827      unset "$1[__read_array_i]" # ensures last element isn't empty
   828    fi
   829  }
   830  
   831  # Some useful colors.
   832  if [[ -z "${color_start-}" ]]; then
   833    declare -r color_start="\033["
   834    declare -r color_red="${color_start}0;31m"
   835    declare -r color_yellow="${color_start}0;33m"
   836    declare -r color_green="${color_start}0;32m"
   837    declare -r color_blue="${color_start}1;34m"
   838    declare -r color_cyan="${color_start}1;36m"
   839    declare -r color_norm="${color_start}0m"
   840  
   841    kube::util::sourced_variable "${color_start}"
   842    kube::util::sourced_variable "${color_red}"
   843    kube::util::sourced_variable "${color_yellow}"
   844    kube::util::sourced_variable "${color_green}"
   845    kube::util::sourced_variable "${color_blue}"
   846    kube::util::sourced_variable "${color_cyan}"
   847    kube::util::sourced_variable "${color_norm}"
   848  fi
   849  
   850  # ex: ts=2 sw=2 et filetype=sh