#!/bin/bash

# Copyright 2021 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 pipefail
set -o nounset

# Check that required binaries are installed
command -v make >/dev/null 2>&1 || { echo >&2 "make is required but it's not installed.  Aborting."; exit 1; }
command -v docker >/dev/null 2>&1 || { echo >&2 "docker is required but it's not installed.  Aborting."; exit 1; }

if [[ "${REPO_ROOT:-}" = "" ]]; then
    echo "Error: \$REPO_ROOT is unset."
    echo "Set \$REPO_ROOT in the sourcing script, e.g.:"
    echo 'REPO_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )"'
    exit 1
fi

OUTPUT="${OUTPUT:-${REPO_ROOT}/_output}"

# Parameters

# Parameters required to be set by caller for dev environment creation only:
#   AUTHENTICATOR_IMAGE
#   ADMIN_ARN

# Parameters with sane defaults: 
AUTHENTICATOR_BIN="${AUTHENTICATOR_BIN:-${OUTPUT}/bin/aws-iam-authenticator}"
REGION="${REGION:-us-west-2}"
APISERVER_URL="${APISERVER_URL:-https://127.0.0.1:6443}"
CLUSTER_NAME="${CLUSTER_NAME:-authenticator-dev-cluster}"
NETWORK_NAME="${NETWORK_NAME:-authenticator-dev}"
NETWORK_SUBNET="${NETWORK_SUBNET:-172.30.0.0/16}"
AUTHENTICATOR_IP="${AUTHENTICATOR_IP:-172.30.0.10}"
AUTHENTICATOR_PORT="${AUTHENTICATOR_PORT:-21362}"
KIND_BIN="${KIND_BIN:-${OUTPUT}/bin/kind}"

# Not configurable:
authenticator_healthz_port=21363

# Location of templates, config files, mounts
authenticator_config_template="${REPO_ROOT}/hack/dev/authenticator.yaml"
authenticator_access_entry_template="${REPO_ROOT}/hack/dev/access-entries.json"
authenticator_dynamicfile_mode_config_template="${REPO_ROOT}/hack/dev/authenticator_with_dynamicfile_mode.yaml"
kind_config_template="${REPO_ROOT}/hack/dev/env.yaml"
kind_config_host_dir="${OUTPUT}/dev/kind"
kind_config="${kind_config_host_dir}/env.yaml"
authenticator_config_host_dir="${OUTPUT}/dev/authenticator/config"
authenticator_config="${authenticator_config_host_dir}/authenticator.yaml"
authenticator_dynamicfile_mode_config="${authenticator_config_host_dir}/authenticator_dynamicfile_mode.yaml"
authenticator_export_host_dir="${OUTPUT}/dev/authenticator/export"
authenticator_state_host_dir="${OUTPUT}/dev/authenticator/state"
authenticator_dynamicfile_host_path="${OUTPUT}/dev/authenticator/access-entry"
authenticator_access_entry_host_file="${authenticator_dynamicfile_host_path}/access-entries.json"
authenticator_dynamicfile_dest_path="/var/authenticator/access-entry"
authenticator_access_entry_dest_file="${authenticator_dynamicfile_dest_path}/access-entries.json"
authenticator_backend_mode_dest_file="${authenticator_dynamicfile_dest_path}/backend-modes.json"
authenticator_config_dest_dir="/etc/authenticator"
authenticator_export_dest_dir="/var/authenticator/export"
authenticator_state_dest_dir="/var/authenticator/state"
apiserver_config_dest_dir="/etc/kubernetes/authenticator"
# Kubeconfig used when authenticator loads its mapping configuration from the API server
authenticator_kubeconfig="${authenticator_config_dest_dir}/authenticator-kubeconfig.yaml"
# Kubeconfig passed to the apiserver so it can kind its authentication webhook
authenticator_generate_kubeconfig="${authenticator_export_dest_dir}/apiserver-auth-webhook-kubeconfig.yaml"
apiserver_auth_webhook_kubeconfig="${apiserver_config_dest_dir}/apiserver-auth-webhook-kubeconfig.yaml"
host_path_webhook_kubeconfig="${authenticator_export_host_dir}/apiserver-auth-webhook-kubeconfig.yaml"
# Kubectl kubeconfig template
kubectl_kubeconfig_template="${REPO_ROOT}/hack/dev/kubeconfig.yaml"
client_dir="${OUTPUT}/dev/client"
# Generated kubeconfig for testing
kubectl_kubeconfig="${client_dir}/kubeconfig.yaml"
# Admin kubeconfig generated by kind
kind_kubeconfig="${client_dir}/kind-kubeconfig.yaml"

function install_kind() {
    if ! [[ -f "${KIND_BIN}" ]]; then
        if [[ "$OSTYPE" == "darwin"* ]]; then
            # for Intel Macs
            [ $(uname -m) = x86_64 ]&& curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-darwin-amd64
            # for M1 / ARM Macs
            [ $(uname -m) = arm64 ] && curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-darwin-arm64
        else
            curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.17.0/kind-linux-amd64
        fi
        chmod +x ./kind
        mv ./kind "${KIND_BIN}"
    fi
    command -v "${KIND_BIN}" >/dev/null 2>&1 || { echo >&2 "kind is required but it's not installed.  Aborting."; exit 1; }
}

function create_network() {
    if [[ -n $(docker network ls | grep authenticator-dev) ]]; then
        echo "${NETWORK_NAME} exists"
    else
        echo "Creating network ${NETWORK_NAME}"
        docker network create "${NETWORK_NAME}" \
            --driver bridge \
            --subnet "${NETWORK_SUBNET}" \
            --opt "com.docker.network.bridge.enable_ip_masquerade=true"
    fi
}

function delete_network() {
    if [[ -n $(docker network ls | grep authenticator-dev) ]]; then
        echo "Deleting network ${NETWORK_NAME}."
        docker network rm "${NETWORK_NAME}"
    else
        echo "Network ${NETWORK_NAME} doesn't exist, doing nothing."
    fi
}

function write_authenticator_config() {
    mkdir -p "${authenticator_config_host_dir}"
    sed -e "s|{{ADMIN_ARN}}|${ADMIN_ARN}|g" \
        -e "s|{{AUTHENTICATOR_STATE_DIR}}|${authenticator_state_dest_dir}|g" \
        -e "s|{{AUTHENTICATOR_GENERATE_KUBECONFIG}}|${authenticator_generate_kubeconfig}|g" \
        -e "s|{{AUTHENTICATOR_KUBECONFIG}}|${authenticator_kubeconfig}|g" \
        -e "s|{{AUTHENTICATOR_PORT}}|${AUTHENTICATOR_PORT}|g" \
        -e "s|{{AUTHENTICATOR_IP}}|${AUTHENTICATOR_IP}|g" \
        -e "s|{{CLUSTER_NAME}}|${CLUSTER_NAME}|g" \
        "${authenticator_config_template}" > "${authenticator_config}"
}

function write_authenticator_with_dynamicfile_mode_config() {
    mkdir -p "${authenticator_config_host_dir}"
    mkdir -p "${authenticator_dynamicfile_host_path}"
    sed -e "s|{{ADMIN_ARN}}|${ADMIN_ARN}|g" \
        -e "s|{{AUTHENTICATOR_STATE_DIR}}|${authenticator_state_dest_dir}|g" \
        -e "s|{{AUTHENTICATOR_GENERATE_KUBECONFIG}}|${authenticator_generate_kubeconfig}|g" \
        -e "s|{{AUTHENTICATOR_KUBECONFIG}}|${authenticator_kubeconfig}|g" \
        -e "s|{{AUTHENTICATOR_PORT}}|${AUTHENTICATOR_PORT}|g" \
        -e "s|{{AUTHENTICATOR_IP}}|${AUTHENTICATOR_IP}|g" \
        -e "s|{{CLUSTER_NAME}}|${CLUSTER_NAME}|g" \
        -e "s|{{AUTHENTICATOR_DYNAMICFILE_PATH}}|${authenticator_access_entry_dest_file}|g" \
        -e "s|{{BACKENDMODE_PATH}}|${authenticator_backend_mode_dest_file}|g" \
        "${authenticator_dynamicfile_mode_config_template}" > "${authenticator_dynamicfile_mode_config}"
    cat "${authenticator_dynamicfile_mode_config}"
    cp "${authenticator_access_entry_template}" "${authenticator_access_entry_host_file}"
}

function write_kind_config() {
    mkdir -p "${kind_config_host_dir}"
    sed -e "s|{{AUTHENTICATOR_EXPORT_HOST_DIR}}|${authenticator_export_host_dir}|g" \
        -e "s|{{APISERVER_CONFIG_DEST_DIR}}|${apiserver_config_dest_dir}|g" \
        -e "s|{{APISERVER_AUTH_WEBHOOK_KUBECONFIG}}|${apiserver_auth_webhook_kubeconfig}|g" \
        -e "s|{{CLUSTER_NAME}}|${CLUSTER_NAME}|g" \
        "${kind_config_template}" > "${kind_config}"
}

function start_authenticator() {
    mkdir -p "${authenticator_state_host_dir}"
    mkdir -p "${authenticator_export_host_dir}"
    chmod -R 777 "${authenticator_state_host_dir}"
    chmod -R 777 "${authenticator_export_host_dir}"
    docker run \
        --detach \
        --ip "${AUTHENTICATOR_IP}" \
        --mount "type=bind,src=${authenticator_config_host_dir},dst=${authenticator_config_dest_dir}" \
        --mount "type=bind,src=${authenticator_state_host_dir},dst=${authenticator_state_dest_dir}" \
        --mount "type=bind,src=${authenticator_export_host_dir},dst=${authenticator_export_dest_dir}" \
        --name aws-iam-authenticator \
        --network "${NETWORK_NAME}" \
        --publish ${authenticator_healthz_port}:${authenticator_healthz_port} \
        --publish ${AUTHENTICATOR_PORT}:${AUTHENTICATOR_PORT} \
        --rm \
        "${AUTHENTICATOR_IMAGE}" \
        server \
        --config "${authenticator_config_dest_dir}/authenticator.yaml"
}

function start_authenticator_with_dynamicfile() {
    mkdir -p "${authenticator_state_host_dir}"
    mkdir -p "${authenticator_export_host_dir}"
    chmod -R 777 "${authenticator_state_host_dir}"
    chmod -R 777 "${authenticator_export_host_dir}"
    chmod -R 777 "${authenticator_dynamicfile_host_path}"
    chmod 777 "${authenticator_access_entry_host_file}"

    docker run \
        --detach \
        --ip "${AUTHENTICATOR_IP}" \
        --mount "type=bind,src=${authenticator_config_host_dir},dst=${authenticator_config_dest_dir}" \
        --mount "type=bind,src=${authenticator_state_host_dir},dst=${authenticator_state_dest_dir}" \
        --mount "type=bind,src=${authenticator_export_host_dir},dst=${authenticator_export_dest_dir}" \
        --mount "type=bind,src=${authenticator_dynamicfile_host_path},dst=${authenticator_dynamicfile_dest_path}" \
        --name aws-iam-authenticator \
        --network "${NETWORK_NAME}" \
        --publish ${authenticator_healthz_port}:${authenticator_healthz_port} \
        --publish ${AUTHENTICATOR_PORT}:${AUTHENTICATOR_PORT} \
        --env AWS_REGION="us-west-2" \
        "${AUTHENTICATOR_IMAGE}" \
        server \
        --config "${authenticator_config_dest_dir}/authenticator_dynamicfile_mode.yaml"

}

function kill_authenticator() {
    docker kill aws-iam-authenticator || true
}

# We are going to talk to the authenticator over the network we set up for kind nodes.
# We can think of the authenticator as running on a separate node.
function replace_authenticator_ip() {
    mkdir -p "${authenticator_export_host_dir}"
    sed -i -e "s|localhost|${AUTHENTICATOR_IP}|g" \
        "${host_path_webhook_kubeconfig}"
}

function extract_certificate_authority_data() {
    mkdir -p "${client_dir}"
    python3 "${REPO_ROOT}/hack/dev/extract_ca_data.py" \
         --kubeconfig "${kind_kubeconfig}" \
         --cluster "kind-${CLUSTER_NAME}"
}

function write_kubectl_kubeconfig() {
    mkdir -p "${client_dir}"
    sed -e "s|{{CERTIFICATE_AUTHORITY_DATA}}|${certificate_authority_data}|g" \
        -e "s|{{APISERVER_URL}}|${APISERVER_URL}|g" \
        -e "s|{{CLUSTER_NAME}}|${CLUSTER_NAME}|g" \
        -e "s|{{REGION}}|${REGION}|g" \
        -e "s|{{AUTHENTICATOR_BIN}}|${AUTHENTICATOR_BIN}|g" \
        "${kubectl_kubeconfig_template}" > "${kubectl_kubeconfig}"
}

function create_kind_cluster() {
    export KIND_EXPERIMENTAL_DOCKER_NETWORK="${NETWORK_NAME}"
    "${KIND_BIN}" create cluster \
        --config "${kind_config_host_dir}/env.yaml" \
        --kubeconfig "${kind_kubeconfig}"
}

function delete_kind_cluster() {
    "${KIND_BIN}" delete cluster --name "${CLUSTER_NAME}"
}
