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