...

Text file src/k8s.io/kubernetes/hack/lib/util.sh

Documentation: k8s.io/kubernetes/hack/lib

     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
    17function 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
    24kube::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.
    30kube::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
    42kube::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
    67kube::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.
    81kube::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
    98kube::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()
   126kube::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
   134kube::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
   141kube::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
   158kube::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.
   199kube::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
   205kube::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
   242kube::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.
   255kube::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
   315kube::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".
   329kube::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.
   337kube::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
   354kube::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.
   377kube::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
   399kube::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
   421function 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
   431function 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"'
   451function 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"
   461EOF
   462}
   463
   464# signs a client certificate: args are sudo, dest-dir, CA, filename (roughly), username, groups...
   465function 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"
   485EOF
   486}
   487
   488# signs a serving certificate: args are sudo, dest-dir, ca, filename (roughly), subject, hosts...
   489function 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"
   509EOF
   510}
   511
   512# creates a self-contained kubeconfig: args are sudo, dest-dir, ca file, host, port, client id, token(optional)
   513function 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
   522apiVersion: v1
   523kind: Config
   524clusters:
   525  - cluster:
   526      certificate-authority: ${ca_file}
   527      server: https://${api_host}:${api_port}/
   528    name: local-up-cluster
   529users:
   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
   535contexts:
   536  - context:
   537      cluster: local-up-cluster
   538      user: local-up-cluster
   539    name: local-up-cluster
   540current-context: local-up-cluster
   541EOF
   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"
   549EOF
   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.
   555function 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.
   564function 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
   569Can't connect to 'docker' daemon.  please fix and retry.
   570
   571Possible 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
   582EOF
   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.
   589kube::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
   603function 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
   619function 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#
   678function 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#
   691function 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#
   708function 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#
   729function 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#
   747function 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.
   763function 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
   771function 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")
   812function 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.
   832if [[ -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}"
   848fi
   849
   850# ex: ts=2 sw=2 et filetype=sh

View as plain text