#!/bin/bash

# Copyright 2015 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.

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

readonly KNOWN_TOKENS_FILE="/srv/salt-overlay/salt/kube-apiserver/known_tokens.csv"
readonly BASIC_AUTH_FILE="/srv/salt-overlay/salt/kube-apiserver/basic_auth.csv"

# evaluate-manifest evalutes the source manifest with the environment variables.
function evaluate-manifest() {
  local src=$1
  local dst=$2
  cp ${src} ${dst}
  sed -i 's/\"/\\\"/g' ${dst} # eval will remove the double quotes if they are not escaped
  eval "echo \"$(< ${dst})\"" > ${dst}
}

# evaluate-manifests-dir evalutes the source manifests within $1 and put the result
# in $2.
function evaluate-manifests-dir() {
  local src=$1
  local dst=$2
  mkdir -p ${dst}

  for f in ${src}/*
  do
    evaluate-manifest $f ${dst}/${f##*/}
  done
}

function configure-kube-proxy() {
  echo "Configuring kube-proxy"
  mkdir -p /var/lib/kube-proxy
  evaluate-manifest ${MANIFESTS_DIR}/kubeproxy-config.yaml /var/lib/kube-proxy/kubeconfig
}

function configure-logging() {
  if [[ "${LOGGING_DESTINATION}" == "gcp" ]];then
    echo "Configuring fluentd-gcp"
    # fluentd-gcp
    evaluate-manifest ${MANIFESTS_DIR}/fluentd-gcp.yaml /etc/kubernetes/manifests/fluentd-gcp.yaml
  elif [[ "${LOGGING_DESTINATION}" == "elasticsearch" ]];then
    echo "Configuring fluentd-es"
    # fluentd-es
    evaluate-manifest ${MANIFESTS_DIR}/fluentd-es.yaml /etc/kubernetes/manifests/fluentd-es.yaml
  fi
}

function configure-admission-controls() {
  echo "Configuring admission controls"
  mkdir -p /etc/kubernetes/admission-controls
  cp -r ${SALT_DIR}/salt/kube-admission-controls/limit-range /etc/kubernetes/admission-controls/
}

function configure-etcd() {
  echo "Configuring etcd"
  touch /var/log/etcd.log
  evaluate-manifest ${MANIFESTS_DIR}/etcd.yaml /etc/kubernetes/manifests/etcd.yaml
}

function configure-etcd-events() {
  echo "Configuring etcd-events"
  touch /var/log/etcd-events.log
  evaluate-manifest ${MANIFESTS_DIR}/etcd-events.yaml /etc/kubernetes/manifests/etcd-events.yaml
}

function configure-addon-manager() {
  echo "Configuring addon-manager"
  evaluate-manifest ${MANIFESTS_DIR}/kube-addon-manager.yaml /etc/kubernetes/manifests/kube-addon-manager.yaml
}

function configure-kube-apiserver() {
  echo "Configuring kube-apiserver"
  
  # Wait for etcd to be up.
  wait-url-up http://127.0.0.1:2379/version

  touch /var/log/kube-apiserver.log

  # Copying known_tokens and basic_auth file.
  cp ${SALT_OVERLAY}/salt/kube-apiserver/*.csv /srv/kubernetes/
  evaluate-manifest ${MANIFESTS_DIR}/kube-apiserver.yaml /etc/kubernetes/manifests/kube-apiserver.yaml
}

function configure-kube-scheduler() {
  echo "Configuring kube-scheduler"
  touch /var/log/kube-scheduler.log
  evaluate-manifest ${MANIFESTS_DIR}/kube-scheduler.yaml /etc/kubernetes/manifests/kube-scheduler.yaml
}

function configure-kube-controller-manager() {
  # Wait for api server.
  wait-url-up http://127.0.0.1:8080/version
  echo "Configuring kube-controller-manager"
  touch /var/log/kube-controller-manager.log
  evaluate-manifest ${MANIFESTS_DIR}/kube-controller-manager.yaml /etc/kubernetes/manifests/kube-controller-manager.yaml
}

# Wait until $1 become reachable.
function wait-url-up() {
  until curl --silent $1
  do
    sleep 5
  done
}

# Configure addon yamls, and run salt/kube-addons/kube-addons.sh
function configure-master-addons() {
  echo "Configuring master addons"

  local addon_dir=/etc/kubernetes/addons
  mkdir -p ${addon_dir}

  # Copy namespace.yaml
  evaluate-manifest ${MANIFESTS_DIR}/addons/namespace.yaml ${addon_dir}/namespace.yaml

  if [[ "${ENABLE_L7_LOADBALANCING}" == "glbc" ]]; then
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/cluster-loadbalancing/glbc ${addon_dir}/cluster-loadbalancing/glbc
  fi

  if [[ "${ENABLE_CLUSTER_DNS}" == "true" ]]; then
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/dns ${addon_dir}/dns
    if [[ "${ENABLE_DNS_HORIZONTAL_AUTOSCALER}" == "true" ]]; then
      evaluate-manifests-dir ${MANIFESTS_DIR}/addons/dns-horizontal-autoscaler ${addon_dir}/dns-horizontal-autoscaler
    fi
  fi

  if [[ "${ENABLE_CLUSTER_UI}" == "true" ]]; then
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/dashboard ${addon_dir}/dashboard
  fi

  if [[ "${ENABLE_CLUSTER_LOGGING}" == "true" ]]; then
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/fluentd-elasticsearch  ${addon_dir}/fluentd-elasticsearch
  fi

  if [[ "${ENABLE_CLUSTER_MONITORING}" == "influxdb" ]]; then
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/cluster-monitoring/influxdb  ${addon_dir}/cluster-monitoring/influxdb
  elif [[ "${ENABLE_CLUSTER_MONITORING}" == "google" ]]; then
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/cluster-monitoring/google  ${addon_dir}/cluster-monitoring/google
  elif [[ "${ENABLE_CLUSTER_MONITORING}" == "standalone" ]]; then
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/cluster-monitoring/standalone  ${addon_dir}/cluster-monitoring/standalone
  elif [[ "${ENABLE_CLUSTER_MONITORING}" == "googleinfluxdb" ]]; then
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/cluster-monitoring/googleinfluxdb  ${addon_dir}/cluster-monitoring/googleinfluxdb
  fi

  # Note that, KUBE_ENABLE_INSECURE_REGISTRY is not supported yet.
  if [[ "${ENABLE_CLUSTER_REGISTRY}" == "true" ]]; then
    CLUSTER_REGISTRY_DISK_SIZE=$(convert-bytes-gce-kube "${CLUSTER_REGISTRY_DISK_SIZE}")
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/registry  ${addon_dir}/registry
  fi

  if [[ "${ENABLE_NODE_PROBLEM_DETECTOR}" == "true" ]]; then
    evaluate-manifests-dir ${MANIFESTS_DIR}/addons/node-problem-detector  ${addon_dir}/node-problem-detector
  fi
  if [[ "${ENABLE_DEFAULT_STORAGE_CLASS:-}" == "true" ]]; then
    setup-addon-manifests "addons" "storage-class/gce"
  fi

  evaluate-manifests-dir ${MANIFESTS_DIR}/addons/storage-class/gce ${addon_dir}/storage-class
}

function configure-master-components() {
  configure-admission-controls
  configure-etcd
  configure-etcd-events
  configure-kube-apiserver
  configure-kube-scheduler
  configure-kube-controller-manager
  configure-master-addons
  configure-addon-manager
}

# TODO(yifan): Merge this with mount-master-pd() in configure-vm.sh
# Pass ${save_format_and_mount} as an argument.
function mount-master-pd() {
  if [[ ! -e /dev/disk/by-id/google-master-pd ]]; then
    return
  fi
  device_info=$(ls -l /dev/disk/by-id/google-master-pd)
  relative_path=${device_info##* }
  device_path="/dev/disk/by-id/${relative_path}"

  # Format and mount the disk, create directories on it for all of the master's
  # persistent data, and link them to where they're used.
  echo "Mounting master-pd"
  mkdir -p /mnt/master-pd
  safe_format_and_mount=${SALT_DIR}/salt/helpers/safe_format_and_mount
  chmod +x ${safe_format_and_mount}
  ${safe_format_and_mount} -m "mkfs.ext4 -F" "${device_path}" /mnt/master-pd &>/var/log/master-pd-mount.log || \
    { echo "!!! master-pd mount failed, review /var/log/master-pd-mount.log !!!"; return 1; }
  # Contains all the data stored in etcd
  mkdir -m 700 -p /mnt/master-pd/var/etcd
  # Contains the dynamically generated apiserver auth certs and keys
  mkdir -p /mnt/master-pd/srv/kubernetes
  # Contains the cluster's initial config parameters and auth tokens
  mkdir -p /mnt/master-pd/srv/salt-overlay
  # Directory for kube-apiserver to store SSH key (if necessary)
  mkdir -p /mnt/master-pd/srv/sshproxy

  ln -s -f /mnt/master-pd/var/etcd /var/etcd
  ln -s -f /mnt/master-pd/srv/kubernetes /srv/kubernetes
  ln -s -f /mnt/master-pd/srv/sshproxy /srv/sshproxy
  ln -s -f /mnt/master-pd/srv/salt-overlay /srv/salt-overlay

  # This is a bit of a hack to get around the fact that salt has to run after the
  # PD and mounted directory are already set up. We can't give ownership of the
  # directory to etcd until the etcd user and group exist, but they don't exist
  # until salt runs if we don't create them here. We could alternatively make the
  # permissions on the directory more permissive, but this seems less bad.
  if ! id etcd &>/dev/null; then
    useradd -s /sbin/nologin -d /var/etcd etcd
  fi
  chown -R etcd /mnt/master-pd/var/etcd
  chgrp -R etcd /mnt/master-pd/var/etcd
}

# The job of this function is simple, but the basic regular expression syntax makes
# this difficult to read. What we want to do is convert from [0-9]+B, KB, KiB, MB, etc
# into [0-9]+, Ki, Mi, Gi, etc.
# This is done in two steps:
#   1. Convert from [0-9]+X?i?B into [0-9]X? (X denotes the prefix, ? means the field
#      is optional.
#   2. Attach an 'i' to the end of the string if we find a letter.
# The two step process is needed to handle the edge case in which we want to convert
# a raw byte count, as the result should be a simple number (e.g. 5B -> 5).
#
# TODO(yifan): Reuse the one defined in configure-vm.sh to remove duplication.
function convert-bytes-gce-kube() {
  local -r storage_space=$1
  echo "${storage_space}" | sed -e 's/^\([0-9]\+\)\([A-Z]\)\?i\?B$/\1\2/g' -e 's/\([A-Z]\)$/\1i/'
}

# TODO(yifan): Use create-salt-master-auth() in configure-vm.sh
function create-salt-master-auth() {
  if [[ ! -e /srv/kubernetes/ca.crt ]]; then
    if  [[ ! -z "${CA_CERT:-}" ]] && [[ ! -z "${MASTER_CERT:-}" ]] && [[ ! -z "${MASTER_KEY:-}" ]]; then
      mkdir -p /srv/kubernetes
      (umask 077;
        echo "${CA_CERT}" | base64 --decode > /srv/kubernetes/ca.crt;
        echo "${MASTER_CERT}" | base64 --decode > /srv/kubernetes/server.cert;
        echo "${MASTER_KEY}" | base64 --decode > /srv/kubernetes/server.key;
        # Kubecfg cert/key are optional and included for backwards compatibility.
        # TODO(roberthbailey): Remove these two lines once GKE no longer requires
        # fetching clients certs from the master VM.
        echo "${KUBECFG_CERT:-}" | base64 --decode > /srv/kubernetes/kubecfg.crt;
        echo "${KUBECFG_KEY:-}" | base64 --decode > /srv/kubernetes/kubecfg.key)
    fi
  fi
  if [ ! -e "${BASIC_AUTH_FILE}" ]; then
    mkdir -p /srv/salt-overlay/salt/kube-apiserver
    (umask 077;
      echo "${KUBE_PASSWORD},${KUBE_USER},admin" > "${BASIC_AUTH_FILE}")
  fi
  if [ ! -e "${KNOWN_TOKENS_FILE}" ]; then
    mkdir -p /srv/salt-overlay/salt/kube-apiserver
    (umask 077;
      echo "${KUBE_BEARER_TOKEN},admin,admin" > "${KNOWN_TOKENS_FILE}";
      echo "${KUBELET_TOKEN},kubelet,kubelet" >> "${KNOWN_TOKENS_FILE}";
      echo "${KUBE_PROXY_TOKEN},kube_proxy,kube_proxy" >> "${KNOWN_TOKENS_FILE}")

    # Generate tokens for other "service accounts".  Append to known_tokens.
    #
    # NB: If this list ever changes, this script actually has to
    # change to detect the existence of this file, kill any deleted
    # old tokens and add any new tokens (to handle the upgrade case).
    local -r service_accounts=("system:scheduler" "system:controller_manager" "system:logging" "system:monitoring")
    for account in "${service_accounts[@]}"; do
      token=$(dd if=/dev/urandom bs=128 count=1 2>/dev/null | base64 | tr -d "=+/" | dd bs=32 count=1 2>/dev/null)
      echo "${token},${account},${account}" >> "${KNOWN_TOKENS_FILE}"
    done
  fi
}

# $1 is the directory containing all of the docker images
function load-docker-images() {
  local success
  local restart_docker
  while true; do
    success=true
    restart_docker=false
    for image in "$1/"*; do
      timeout 30 docker load -i "${image}" &>/dev/null
      rc=$?
      if [[ "$rc" == 124 ]]; then
        restart_docker=true
      elif [[ "$rc" != 0 ]]; then
        success=false
      fi
    done
    if [[ "$success" == "true" ]]; then break; fi
    if [[ "$restart_docker" == "true" ]]; then systemctl restart docker; fi
    sleep 15
  done
}

function load-master-components-images() {
  echo "Loading images for master components"
  export RKT_BIN=/opt/rkt/rkt
  export DOCKER2ACI_BIN=/opt/docker2aci/docker2aci
  ${SALT_DIR}/install.sh ${KUBE_BIN_TAR}
  ${SALT_DIR}/salt/kube-master-addons/kube-master-addons.sh

  # Get the image tags.
  KUBE_APISERVER_DOCKER_TAG=$(cat ${KUBE_BIN_DIR}/kube-apiserver.docker_tag)
  KUBE_CONTROLLER_MANAGER_DOCKER_TAG=$(cat ${KUBE_BIN_DIR}/kube-controller-manager.docker_tag)
  KUBE_SCHEDULER_DOCKER_TAG=$(cat ${KUBE_BIN_DIR}/kube-scheduler.docker_tag)
}

##########
#  main  #
##########

KUBE_BIN_TAR=/opt/downloads/kubernetes-server-linux-amd64.tar.gz
KUBE_BIN_DIR=/opt/kubernetes/server/bin
SALT_DIR=/opt/kubernetes/saltbase
SALT_OVERLAY=/srv/salt-overlay
MANIFESTS_DIR=/opt/kube-manifests/kubernetes

# On CoreOS, the hosts is in /usr/share/baselayout/hosts
# So we need to manually populdate the hosts file here on gce.
echo "127.0.0.1 localhost" >> /etc/hosts
echo "::1 localhost" >> /etc/hosts

if [[ "${KUBERNETES_MASTER}" == "true" ]]; then
  mount-master-pd
  create-salt-master-auth
  load-master-components-images
  configure-master-components
else
  configure-kube-proxy
fi

if [[ "${ENABLE_NODE_LOGGING}" == "true" ]];then
  configure-logging
fi
  
echo "Finish configuration successfully!"
