#!/usr/bin/env bash

# Copyright 2014 The Kubernetes Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Bring up a Kubernetes cluster.
# Usage:
#   wget -q -O - https://get.k8s.io | bash
# or
#   curl -fsSL https://get.k8s.io | bash
#
# Advanced options
#  Set KUBERNETES_PROVIDER to choose between different providers:
#  Google Compute Engine [default]
#   * export KUBERNETES_PROVIDER=gce; wget -q -O - https://get.k8s.io | bash
#
#  Set KUBERNETES_RELEASE to choose a specific release instead of the current
#    stable release, (e.g. 'v1.3.7').
#    See https://github.com/kubernetes/kubernetes/releases for release options.
#  Set KUBERNETES_RELEASE_URL to choose where to download binaries from.
#    (Defaults to https://dl.k8s.io/release).
#
#  Set KUBERNETES_SERVER_ARCH to choose the server (Kubernetes cluster)
#  architecture to download:
#    * amd64 [default]
#    * arm
#    * arm64
#    * ppc64le
#
#  Set KUBERNETES_NODE_PLATFORM to choose the platform for which to download
#  the node binaries. If none of KUBERNETES_NODE_PLATFORM and
#  KUBERNETES_NODE_ARCH is set, no node binaries will be downloaded. If only
#  one of the two is set, the other will be defaulted to the
#  KUBERNETES_SERVER_PLATFORM/ARCH.
#    * linux
#    * windows
#
#  Set KUBERNETES_NODE_ARCH to choose the node architecture to download the
#  node binaries. If none of KUBERNETES_NODE_PLATFORM and
#  KUBERNETES_NODE_ARCH is set, no node binaries will be downloaded. If only
#  one of the two is set, the other will be defaulted to the
#  KUBERNETES_SERVER_PLATFORM/ARCH.
#    * amd64 [default]
#    * arm
#    * arm64
#    * ppc64le
#
#  Set KUBERNETES_SKIP_DOWNLOAD to skip downloading a release.
#  Set KUBERNETES_SKIP_CONFIRM to skip the installation confirmation prompt.
#  Set KUBERNETES_SKIP_CREATE_CLUSTER to skip starting a cluster.
#  Set KUBERNETES_SKIP_RELEASE_VALIDATION to skip trying to validate the
#      Kubernetes release string. This implies that you know what you're doing
#      and have set KUBERNETES_RELEASE and KUBERNETES_RELEASE_URL properly.

set -o errexit
set -o nounset
set -o pipefail

# If KUBERNETES_RELEASE_URL is overridden but KUBERNETES_CI_RELEASE_URL is not then set KUBERNETES_CI_RELEASE_URL to KUBERNETES_RELEASE_URL.
KUBERNETES_CI_RELEASE_URL="${KUBERNETES_CI_RELEASE_URL:-${KUBERNETES_RELEASE_URL:-https://dl.k8s.io/ci}}"
KUBERNETES_RELEASE_URL="${KUBERNETES_RELEASE_URL:-https://dl.k8s.io}"

KUBE_RELEASE_VERSION_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)(-([a-zA-Z0-9]+)\\.(0|[1-9][0-9]*))?$"
#                       v1                .26               .0              -(rc            .0                .)?0              (  +014f      )?
KUBE_CI_VERSION_REGEX="^v(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)\\.(0|[1-9][0-9]*)-([a-zA-Z0-9]+\\.(0|[1-9][0-9]*)\\.)?(0|[1-9][0-9]*)(\\+[-0-9a-z]*)?$"

# Sets KUBE_VERSION variable if an explicit version number was provided (e.g. "v1.0.6",
# "v1.2.0-alpha.1.881+376438b69c7612", or "v1.2.0-881+376438b69c7612") or resolves the "published" version
# <path>/<version> (e.g. "release/stable",' "ci/latest-1") by reading from GCS.
#
# See the docs on getting builds for more information about version
# publication.
#
# Args:
#   $1 version string from command line
# Vars set:
#   KUBE_VERSION
function set_binary_version() {
  if [[ "${1}" =~ "/" ]]; then
    KUBE_VERSION=$(curl -fsSL --retry 5 "https://dl.k8s.io/${1}.txt")
  else
    KUBE_VERSION=${1}
  fi
  export KUBE_VERSION
}

# Use the script from inside the Kubernetes tarball to fetch the client and
# server binaries (if not included in kubernetes.tar.gz).
function download_kube_binaries {
  (
    cd kubernetes
    if [[ -x ./cluster/get-kube-binaries.sh ]]; then
      # Make sure to use the same download URL in get-kube-binaries.sh
      KUBERNETES_RELEASE_URL="${KUBERNETES_RELEASE_URL}" \
        ./cluster/get-kube-binaries.sh
    fi
  )
}

function create_cluster {
  if [[ -n "${KUBERNETES_SKIP_CREATE_CLUSTER-}" ]]; then
    exit 0
  fi
  echo "Creating a kubernetes on ${KUBERNETES_PROVIDER:-gce}..."
  (
    cd kubernetes
    ./cluster/kube-up.sh
    echo "Kubernetes binaries at ${PWD}/cluster/"
    if [[ ":$PATH:" != *":${PWD}/cluster:"* ]]; then
      echo "You may want to add this directory to your PATH in \$HOME/.profile"
    fi

    echo "Installation successful!"
  )
}

function valid-storage-scope {
  curl "${GCE_METADATA_INTERNAL}/service-accounts/default/scopes" -H "Metadata-Flavor: Google" -s | grep -E "auth/devstorage|auth/cloud-platform"
}

if [[ -n "${KUBERNETES_SKIP_DOWNLOAD-}" ]]; then
  create_cluster
  exit 0
fi

if [[ -d "./kubernetes" ]]; then
  if [[ -z "${KUBERNETES_SKIP_CONFIRM-}" ]]; then
    echo "'kubernetes' directory already exist. Should we skip download step and start to create cluster based on it? [Y]/n"
    read -r confirm
    if [[ ! "${confirm}" =~ ^[nN]$ ]]; then
      echo "Skipping download step."
      create_cluster
      exit 0
    fi
  fi
fi

# TODO: remove client checks once kubernetes.tar.gz no longer includes client
# binaries by default.
kernel=$(uname -s)
case "${kernel}" in
  Darwin)
    ;;
  Linux)
    ;;
  *)
    echo "Unknown, unsupported platform: ${kernel}." >&2
    echo "Supported platforms: Linux, Darwin." >&2
    echo "Bailing out." >&2
    exit 2
esac

machine=$(uname -m)
case "${machine}" in
  x86_64*|i?86_64*|amd64*)
    ;;
  aarch64*|arm64*)
    ;;
  ppc64le*)
    ;;
  arm*)
    ;;
  i?86*)
    ;;
  *)
    echo "Unknown, unsupported architecture (${machine})." >&2
    echo "Supported architectures x86_64, i686, arm, arm64, ppc64le." >&2
    echo "Bailing out." >&2
    exit 3
    ;;
esac

file=kubernetes.tar.gz
release=${KUBERNETES_RELEASE:-"release/stable"}

# Validate Kubernetes release version.
# Translate a published version <bucket>/<version> (e.g. "release/stable") to version number.
set_binary_version "${release}"
if [[ -z "${KUBERNETES_SKIP_RELEASE_VALIDATION-}" ]]; then
  if [[ ${KUBE_VERSION} =~ ${KUBE_RELEASE_VERSION_REGEX} ]]; then
    # Use KUBERNETES_RELEASE_URL for Releases and Pre-Releases
    # ie. 1.18.0 or 1.19.0-beta.0
    # shellcheck disable=SC2269 # this line is a noop but it helps with reading
    KUBERNETES_RELEASE_URL="${KUBERNETES_RELEASE_URL}"
  elif [[ ${KUBE_VERSION} =~ ${KUBE_CI_VERSION_REGEX} ]]; then
    # Override KUBERNETES_RELEASE_URL to point to the CI bucket;
    # this will be used by get-kube-binaries.sh.
    # ie. v1.19.0-beta.0.318+b618411f1edb98 and v1.19.0-318+b618411f1edb98
    KUBERNETES_RELEASE_URL="${KUBERNETES_CI_RELEASE_URL}"
  else
    echo "Version doesn't match regexp" >&2
    exit 1
  fi
fi
kubernetes_tar_url="${KUBERNETES_RELEASE_URL}/${KUBE_VERSION}/${file}"

need_download=true
if [[ -r "${PWD}/${file}" ]]; then
  downloaded_version=$(tar -xzOf "${PWD}/${file}" kubernetes/version 2>/dev/null || true)
  echo "Found preexisting ${file}, release ${downloaded_version}"
  if [[ "${downloaded_version}" == "${KUBE_VERSION}" ]]; then
    echo "Using preexisting kubernetes.tar.gz"
    need_download=false
  fi
fi

if "${need_download}"; then
  echo "Downloading kubernetes release ${KUBE_VERSION}"
  echo "  from ${kubernetes_tar_url}"
  echo "  to ${PWD}/${file}"
fi

if [[ -e "${PWD}/kubernetes" ]]; then
  # Let's try not to accidentally nuke something that isn't a kubernetes
  # release dir.
  if [[ ! -f "${PWD}/kubernetes/version" ]]; then
    echo "${PWD}/kubernetes exists but does not look like a Kubernetes release."
    echo "Aborting!"
    exit 5
  fi
  echo "Will also delete preexisting 'kubernetes' directory."
fi

if [[ -z "${KUBERNETES_SKIP_CONFIRM-}" ]]; then
  echo "Is this ok? [Y]/n"
  read -r confirm
  if [[ "${confirm}" =~ ^[nN]$ ]]; then
    echo "Aborting."
    exit 0
  fi
fi

if "${need_download}"; then
  if [[ $(which gsutil) ]] && [[ "$kubernetes_tar_url" =~ ^https://storage.googleapis.com/.* ]]; then
    gsutil cp "${kubernetes_tar_url//'https://storage.googleapis.com/'/gs://}" "${file}"
  elif [[ $(which curl) ]]; then
    # if the url belongs to GCS API we should use oauth2_token in the headers
    curl_headers=""
    if { [[ "${KUBERNETES_PROVIDER:-gce}" == "gce" ]] || [[ "${KUBERNETES_PROVIDER}" == "gke" ]] ; } &&
       [[ "$kubernetes_tar_url" =~ ^https://storage.googleapis.com.* ]] ; then
      curl_headers="Authorization: Bearer $(gcloud auth print-access-token)"
    fi
    curl ${curl_headers:+-H "${curl_headers}"} -fL --retry 3 --keepalive-time 2 "${kubernetes_tar_url}" -o "${file}"
  elif [[ $(which wget) ]]; then
    wget "${kubernetes_tar_url}"
  else
    echo "Couldn't find gsutil, curl, or wget.  Bailing out."
    exit 1
  fi
fi

echo "Unpacking kubernetes release ${KUBE_VERSION}"
rm -rf "${PWD}/kubernetes"
tar -xzf ${file}

download_kube_binaries
create_cluster
