#!/bin/bash

# update docker'version for kubernetes node
# 2017-04-19

function command_exists ()
{
  command -v "$@" > /dev/null 2>&1
}

WORKDIR=/tmp/update-docker
AVAILABLE_DOCKER_VERSION=("1.10.3" "1.12.3" "1.13.1")
DOCKER_PACKAGE_URL_PREFIX="http://domeos-binpack.bjcnc.scs.sohucs.com/docker/"
K8S_INSTALL_PATH="/usr/sbin/domeos/k8s"
OLD_DOCKER_GRAPH="/var/lib/docker"
DOCKER_GRAPH="/data/docker"
HOST_NAME=`hostname -s`

function help ()
{
  echo "
Usage:
  update_docker.sh [options]
  update_docker.sh [command]

Available Commands:
  help   show the help information about update_docker.sh.

Options:
  --new-version         (REQUIRED) the new docker version, supported version: ${AVAILABLE_DOCKER_VERSION[@]}
  --api-server          (REQUIRED) kube-apiserver address ('http://ip:port' or 'https://ip:port')
  --kube-node-name      The name of node that now run the shell script in the kubernetes
  --old-docker-graph    The old graph path of docker (default $OLD_DOCKER_GRAPH)
  --docker-graph        The graph path of docker (default $DOCKER_GRAPH)
  --need-migrate-pods   Whether you need to migrate the pods on the current node (true or false, default true)
  --need-change-storage Whether you need to change the config of storage and move the docker storage directory (true or false, default true)

Usage Example:
  sudo bash update_docker.sh --new-version 1.12.3 --api-server http://10.10.10.10:8080 --kube-node-name test-10-10 --need-change-storage false
"
}

if [[ "$1" =~ "help" ]] || [ -z "$1" ]; then
  help
  exit 1
fi

# STEP 01: Pre-check arguments and environment
echo -e "\033[36m[INFO] STEP 01: Pre-check arguments and environment...\033[0m"
OPTS=$(getopt -o : --long new-version:,api-server:,kube-node-name:,need-migrate-pods:,need-change-storage:,docker-graph:,old-docker-graph: -- "$@")
if [ $? != 0 ]; then
  echo -e "\033[31m[ERROR] update_docker.sh argument is illegal\033[0m"
  exit 1
fi
eval set -- "$OPTS"
new_version=
api_server=
kube_node_name=
docker_graph=
need_migrate_pods="true"
need_change_storage="true"
while true ; do
  case "$1" in
    --new-version) new_version=$2; shift 2;;
    --api-server) api_server=$2; shift 2;;
    --kube-node-name) kube_node_name=$2; shift 2;;
    --old-docker-graph) old_docker_graph=$2; shift 2;;
    --docker-graph) docker_graph=$2; shift 2;;
    --need-migrate-pods) need_migrate_pods=$2; shift 2;;
    --need-change-storage) need_change_storage=$2; shift 2;;
    --) shift; break;;
  esac
done
if [ -z "$api_server" ]; then
  echo -e "\033[31m[ERROR] --api-server is absent\033[0m"
  exit 1
else
  echo "--api-server: $api_server"
fi
if [ -z "$kube_node_name" ]; then
  echo -e "\033[36m[INFO] --kube-node-name is absent, default '$HOST_NAME'\033[0m"
  kube_node_name=$HOST_NAME
else
  echo "--kube-node-name: $kube_node_name"
fi
if [ -z "$new_version" ]; then
  echo -e "\033[31m[ERROR] --new-version is absent\033[0m"
  exit 1
else
  available="false"
  for i in ${AVAILABLE_DOCKER_VERSION[@]} ; do
    if [ "$i" == "$new_version" ]; then
      available="true"
      break
    fi
  done
  if [ "$available" == "true" ]; then
    echo "--new-version: $new_version"
  else
    echo -e "\033[31m[ERROR] this shell script does not provide Docker $new_version package, you have to update docker's version by yourself.\033[0m"
    exit 1
  fi
fi
if [ "$need_migrate_pods" != "true" ]; then
  need_migrate_pods="false"
  echo "--need-migrate-pods: $need_migrate_pods"
else
  echo -e "\033[36m[INFO] --need-migrate-pods is absent, default '$need_migrate_pods'\033[0m"
fi
if [ "$need_change_storage" != "true" ]; then
  need_change_storage="false"
  echo "--need-change-storage: $need_change_storage"
  old_docker_graph=$OLD_DOCKER_GRAPH
  docker_graph=$DOCKER_GRAPH
else
  echo -e "\033[36m[INFO] --need-change-storage is absent, default '$need_change_storage'\033[0m"
  if [ -z "$old_docker_graph" ]; then
    echo -e "\033[36m[INFO] --docker-graph is absent, default '$OLD_DOCKER_GRAPH'\033[0m"
    old_docker_graph=$OLD_DOCKER_GRAPH
  else
    if [ "${old_docker_graph:0-1}" = "/" ]; then
      old_docker_graph=${old_docker_graph:0:0-1}
    fi
    echo "--old-docker-graph: $old_docker_graph"
  fi
  if [ -z "$docker_graph" ]; then
    echo -e "\033[36m[INFO] --docker-graph is absent, default '$DOCKER_GRAPH'\033[0m"
    docker_graph=$DOCKER_GRAPH
  else
    if [ "${docker_graph:0-1}" = "/" ]; then
      docker_graph=${docker_graph:0:0-1}
    fi
    echo "--docker-graph: $docker_graph"
  fi
  if [ ! -d "$old_docker_graph" ]; then
    echo -e "\033[31m[ERROR] the old docker graph dir doesn't exist, please check: $old_docker_graph\033[0m"
    exit 1
  fi
  if [ -d "$docker_graph" ]; then
    echo -e "\033[31m[ERROR] the new docker graph dir exists, please check: $docker_graph\033[0m"
    exit 1
  fi
fi
if ! command_exists docker ; then
  echo -e "\033[31m[ERROR] docker: command not found.\033[0m"
  exit 1
fi
current_version=`docker --version | awk '{print $3}' | cut -d ',' -f 1`
echo -e "current docker version: $current_version"
if [ "$new_version" = "$current_version" ]; then
  echo -e "\033[36m[WARN] the current version and target version are the same.\033[0m"
fi
kubectlpath=$K8S_INSTALL_PATH/current/kubectl
if [ ! -f "$kubectlpath" ]; then
  echo -e "\033[31m[ERROR] $kubectlpath not exist, failed to drain kubelet node $kube_node_name\033[0m"
  exit 1
fi

# STEP 02: Download docker's rpm package
echo -e "\033[36m[INFO] STEP 02: Download docker's rpm package...\033[0m"
set -e
mkdir -p "$WORKDIR/docker"
/bin/rm -f $WORKDIR/docker/*.rpm
curl -o "$WORKDIR/docker.tgz" "$DOCKER_PACKAGE_URL_PREFIX$new_version/docker.tgz"
tar zxf "$WORKDIR/docker.tgz" -C "$WORKDIR/docker"
set +e

#STEP 03:Drain kubelet node
echo -e "\033[36m[INFO] STEP 03: Drain kubelet node $kube_node_name\033[0m"
if [ "$need_migrate_pods" = "true" ]; then
  $kubectlpath -s $api_server drain $kube_node_name --ignore-daemonsets --delete-local-data --force
  if [ $? -eq 0 ]; then
    echo -e "\033[36m[INFO] Drain kubelet node $kube_node_name successful...\033[0m"
  else
    echo -e "\033[31m[ERROR] Failed to drain kubelet node $kube_node_name\033[0m"
    exit 1
  fi
else
  echo -e "\033[36m[INFO] STEP 03: Skip...\033[0m"
fi

# STEP 04: Unload docker
echo -e "\033[36m[INFO] STEP 04: Unload docker...\033[0m"
echo -e "\033[36m[INFO] stopping docker service...\033[0m"
set -e
systemctl stop docker
[ -f /etc/sysconfig/docker ] && /bin/cp -f /etc/sysconfig/docker "$WORKDIR/docker.config-$current_version.bak"
/bin/cp -f /usr/lib/systemd/system/docker.service "$WORKDIR/docker.service-$current_version.bak"
set +e
echo -e "\033[36m[INFO] removing the following rpm package:\033[0m"
rpm -qa | grep docker
rpm -qa | grep container-selinux
rpm -qa | grep docker | xargs rpm -e
rpm -qa | grep container-selinux | xargs rpm -e

# STEP 05: Install new docker
echo -e "\033[36m[INFO] STEP 05: Install new docker...\033[0m"
set -e
yum localinstall $WORKDIR/docker/docker-engine-*.rpm -y
[ -f /etc/sysconfig/docker ] && /bin/cp -f /etc/sysconfig/docker "$WORKDIR/docker.config-$new_version.bak"
[ -f "$WORKDIR/docker.config-$current_version.bak" ] && /bin/cp "$WORKDIR/docker.config-$current_version.bak" /etc/sysconfig/docker
/bin/cp -f /usr/lib/systemd/system/docker.service $WORKDIR/docker.service-$new_version.bak
sed -i '/\[Service\]/, $d' /usr/lib/systemd/system/docker.service
echo "[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/docker
ExecStart=/usr/bin/docker daemon \$DOCKER_OPTS \\
\$DOCKER_STORAGE_OPTIONS \\
\$DOCKER_NETWORK_OPTIONS \\
\$ADD_REGISTRY \\
\$BLOCK_REGISTRY \\
\$INSECURE_REGISTRY
ExecReload=/bin/kill -s HUP \$MAINPID

MountFlags=slave
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=0
Delegate=yes

[Install]
WantedBy=multi-user.target
" >> /usr/lib/systemd/system/docker.service
if [ "$need_change_storage" = "true" ]; then
  echo -e "\033[36m[INFO] changing storage config, and moving docker graph data, from $old_docker_graph to $docker_graph...\033[0m"
  [ -f /etc/sysconfig/docker ] && sed -i '/DOCKER_STORAGE_OPTIONS/, 1d' /etc/sysconfig/docker
  docker_storage_options="DOCKER_STORAGE_OPTIONS=\"--graph $docker_graph\""
  echo $docker_storage_options >> /etc/sysconfig/docker
  mkdir -p $docker_graph
  [ ! -z "$old_docker_graph" ] && /bin/mv $old_docker_graph/* $docker_graph
fi
set +e

# STEP 06: Restart docker service
echo -e "\033[36m[INFO] STEP 06: Restart docker service...\033[0m"
systemctl daemon-reload
systemctl start docker
_r=$?
sleep 5
systemctl status -l docker
if [ "$_r" = "0" ]; then
  VERSION=`docker --version | awk '{print $3}' | cut -d ',' -f 1`
  echo -e "update docker success, new docker version: $VERSION"
else
  echo -e "start docker failed, please check the environment and manual rollback, the backup file is stored in $WORKDIR"
  exit 1
fi

#STEP 07: Uncordon kubelet node
echo -e "\033[36m[INFO] STEP 07: Uncordon kubelet node $kube_node_name...\033[0m" 
if [ "$need_migrate_pods" = "true" ]; then
  $kubectlpath -s $api_server uncordon $kube_node_name
  if [ $? -eq 0 ]; then
    echo -e "\033[36m Uncordon kubelet node $kube_node_name successful...\033[0m"  
  else
    echo -e "\033[31m Failed to uncordon kubelet node $kube_node_name\033[0m"
    exit 1
  fi
else
  echo -e "\033[36m[INFO] STEP 07: Skip...\033[0m"
fi
