The kubeControllerManager endpoints were being cleared on each iteration of the loop over control nodes leading to only the last control node being added. Instead the array has been cleared outside of the loop.
546 lines
23 KiB
Bash
Executable File
546 lines
23 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
set -o errexit
|
|
set -o pipefail
|
|
|
|
# shellcheck disable=SC2155
|
|
export PROJECT_DIR=$(git rev-parse --show-toplevel)
|
|
|
|
# shellcheck disable=SC2155
|
|
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt
|
|
|
|
# shellcheck disable=SC1091
|
|
source "${PROJECT_DIR}/.config.env"
|
|
|
|
show_help() {
|
|
cat << EOF
|
|
Usage: $(basename "$0") <options>
|
|
-h, --help Display help
|
|
--verify Verify .config.env settings
|
|
EOF
|
|
}
|
|
|
|
main() {
|
|
local verify=
|
|
parse_command_line "$@"
|
|
if [[ "${verify}" == 1 ]]; then
|
|
verify_start
|
|
verify_binaries
|
|
verify_master_count
|
|
verify_ansible_hosts
|
|
verify_metallb
|
|
verify_kubevip
|
|
verify_cluster_service_cidrs
|
|
verify_addressing
|
|
verify_age
|
|
verify_git_repository
|
|
verify_cloudflare
|
|
verify_success
|
|
else
|
|
# generate sops configuration file
|
|
envsubst < "${PROJECT_DIR}/tmpl/.sops.yaml" \
|
|
> "${PROJECT_DIR}/.sops.yaml"
|
|
# generate cluster settings
|
|
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/flux/cluster-settings.yaml" \
|
|
> "${PROJECT_DIR}/kubernetes/flux/vars/cluster-settings.yaml"
|
|
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/flux/cluster.yaml" \
|
|
> "${PROJECT_DIR}/kubernetes/flux/config/cluster.yaml"
|
|
# generate secrets
|
|
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/cluster-secrets.sops.yaml" \
|
|
> "${PROJECT_DIR}/kubernetes/flux/vars/cluster-secrets.sops.yaml"
|
|
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/flux/vars/cluster-secrets.sops.yaml"
|
|
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/cert-manager-secret.sops.yaml" \
|
|
> "${PROJECT_DIR}/kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml"
|
|
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml"
|
|
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/cloudflared-secret.sops.yaml" \
|
|
> "${PROJECT_DIR}/kubernetes/apps/networking/cloudflared/app/secret.sops.yaml"
|
|
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/networking/cloudflared/app/secret.sops.yaml"
|
|
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/external-dns-secret.sops.yaml" \
|
|
> "${PROJECT_DIR}/kubernetes/apps/networking/external-dns/app/secret.sops.yaml"
|
|
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/networking/external-dns/app/secret.sops.yaml"
|
|
# generate ansible settings
|
|
envsubst < "${PROJECT_DIR}/tmpl/ansible/supplemental.yml" \
|
|
> "${PROJECT_DIR}/ansible/inventory/group_vars/kubernetes/supplemental.yml"
|
|
# generate ansible hosts file and secrets
|
|
generate_ansible_hosts
|
|
generate_ansible_host_secrets
|
|
setup_github_webhook
|
|
setup_weave_gitops
|
|
setup_grafana_admin
|
|
setup_prometheus_endpoints
|
|
success
|
|
fi
|
|
}
|
|
|
|
parse_command_line() {
|
|
while :; do
|
|
case "${1:-}" in
|
|
-h|--help)
|
|
show_help
|
|
exit
|
|
;;
|
|
--verify)
|
|
verify=1
|
|
;;
|
|
*)
|
|
break
|
|
;;
|
|
esac
|
|
|
|
shift
|
|
done
|
|
if [[ -z "$verify" ]]; then
|
|
verify=0
|
|
fi
|
|
}
|
|
|
|
_has_binary() {
|
|
command -v "${1}" >/dev/null 2>&1 || {
|
|
_log "ERROR(${FUNCNAME[0]})" "${1} is not installed or not found in \$PATH"
|
|
exit 1
|
|
}
|
|
_log "INFO(${FUNCNAME[0]})" "Found CLI tool ${1} and it is in \$PATH"
|
|
}
|
|
|
|
_has_optional_envar() {
|
|
local option="${1}"
|
|
# shellcheck disable=SC2015
|
|
[[ "${!option}" == "" ]] && {
|
|
_log "WARN" "Unset optional variable ${option}"
|
|
} || {
|
|
_log "INFO(${FUNCNAME[0]})" "Found variable '${option}' with value '${!option}'"
|
|
}
|
|
}
|
|
|
|
_has_envar() {
|
|
local option="${1}"
|
|
local secret="${2:-false}"
|
|
local value=
|
|
# shellcheck disable=SC2015
|
|
if [[ "${!option}" == "" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "Unset variable ${option}"
|
|
exit 1
|
|
fi
|
|
value="${!option}"
|
|
if [[ $secret == "true" ]]; then
|
|
value="***"
|
|
fi
|
|
_log "INFO(${FUNCNAME[0]})" "Found variable '${option}' with value '${value}'"
|
|
}
|
|
|
|
_has_valid_ip() {
|
|
local ip="${1}"
|
|
local variable_name="${2}"
|
|
if ! ipcalc "${ip}" | awk 'BEGIN{FS=":"; is_invalid=0} /^INVALID/ {is_invalid=1; print $1} END{exit is_invalid}' >/dev/null 2>&1; then
|
|
_log "INFO(${FUNCNAME[0]})" "Variable '${variable_name}' has an invalid IP address '${ip}'"
|
|
exit 1
|
|
else
|
|
_log "INFO(${FUNCNAME[0]})" "Variable '${variable_name}' has a valid IP address '${ip}'"
|
|
fi
|
|
}
|
|
|
|
verify_addressing() {
|
|
local found_kube_vip="false"
|
|
local found_k8s_gateway="false"
|
|
local found_ingress="false"
|
|
# Verify the metallb min and metallb ceiling are in the same network
|
|
metallb_subnet_min=$(echo "${BOOTSTRAP_METALLB_LB_RANGE}" | cut -d- -f1 | cut -d. -f1,2,3)
|
|
metallb_subnet_ceil=$(echo "${BOOTSTRAP_METALLB_LB_RANGE}" | cut -d- -f2 | cut -d. -f1,2,3)
|
|
if [[ "${metallb_subnet_min}" != "${metallb_subnet_ceil}" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The provided MetalLB IP range '${BOOTSTRAP_METALLB_LB_RANGE}' is not in the same subnet"
|
|
exit 1
|
|
fi
|
|
# Verify the node IP addresses are on the same network as the metallb range
|
|
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
|
node_subnet=$(echo "${!var}" | cut -d. -f1,2,3)
|
|
if [[ "${node_subnet}" != "${metallb_subnet_min}" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The subnet for node '${!var}' is not in the same subnet as the provided metallb range '${BOOTSTRAP_METALLB_LB_RANGE}'"
|
|
exit 1
|
|
fi
|
|
done
|
|
# Verify the kube-vip IP is in the same network as the metallb range
|
|
kubevip_subnet=$(echo "${BOOTSTRAP_KUBE_VIP_ADDR}" | cut -d. -f1,2,3)
|
|
if [[ "${kubevip_subnet}" != "${metallb_subnet_min}" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The subnet for kupe-vip '${BOOTSTRAP_KUBE_VIP_ADDR}' is not the same subnet as the provided metallb range '${BOOTSTRAP_METALLB_LB_RANGE}'"
|
|
exit 1
|
|
fi
|
|
# Depending on the IP address, verify if it should be in the metallb range or not
|
|
metallb_octet_min=$(echo "${BOOTSTRAP_METALLB_LB_RANGE}" | cut -d- -f1 | cut -d. -f4)
|
|
metallb_octet_ceil=$(echo "${BOOTSTRAP_METALLB_LB_RANGE}" | cut -d- -f2 | cut -d. -f4)
|
|
for (( octet=metallb_octet_min; octet<=metallb_octet_ceil; octet++ )); do
|
|
addr="${metallb_subnet_min}.${octet}"
|
|
if [[ "${addr}" == "${BOOTSTRAP_KUBE_VIP_ADDR}" ]]; then
|
|
found_kube_vip="true"
|
|
fi
|
|
if [[ "${addr}" == "${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}" ]]; then
|
|
found_k8s_gateway="true"
|
|
fi
|
|
if [[ "${addr}" == "${BOOTSTRAP_METALLB_INGRESS_ADDR}" ]]; then
|
|
found_ingress="true"
|
|
fi
|
|
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
|
if [[ "${!var}" == "${addr}" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The IP for node '${!var}' should NOT be in the provided metallb range '${BOOTSTRAP_METALLB_LB_RANGE}'"
|
|
exit 1
|
|
fi
|
|
done
|
|
done
|
|
if [[ "${found_kube_vip}" == "true" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The IP for kube-vip '${BOOTSTRAP_KUBE_VIP_ADDR}' should NOT be in the provided metallb range '${BOOTSTRAP_METALLB_LB_RANGE}'"
|
|
exit 1
|
|
fi
|
|
if [[ "${found_k8s_gateway}" == "false" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The IP for k8s_gateway '${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}' should be in the provided metallb range '${BOOTSTRAP_METALLB_LB_RANGE}'"
|
|
exit 1
|
|
fi
|
|
if [[ "${found_ingress}" == "false" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The IP for ingress '${BOOTSTRAP_METALLB_INGRESS_ADDR}' should be in the provided metallb range '${BOOTSTRAP_METALLB_LB_RANGE}'"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
verify_age() {
|
|
_has_envar "BOOTSTRAP_AGE_PUBLIC_KEY"
|
|
_has_envar "SOPS_AGE_KEY_FILE"
|
|
if [[ ! "$BOOTSTRAP_AGE_PUBLIC_KEY" =~ ^age.* ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "BOOTSTRAP_AGE_PUBLIC_KEY does not start with age"
|
|
exit 1
|
|
else
|
|
_log "INFO(${FUNCNAME[0]})" "Age public key is in the correct format"
|
|
fi
|
|
if [[ ! -f ~/.config/sops/age/keys.txt ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "Unable to find Age file keys.txt in ~/.config/sops/age"
|
|
exit 1
|
|
else
|
|
_log "INFO(${FUNCNAME[0]})" "Found Age public key '${BOOTSTRAP_AGE_PUBLIC_KEY}'"
|
|
fi
|
|
}
|
|
|
|
verify_binaries() {
|
|
_has_binary "age"
|
|
_has_binary "ansible"
|
|
_has_binary "envsubst"
|
|
_has_binary "flux"
|
|
_has_binary "git"
|
|
_has_binary "gitops"
|
|
_has_binary "ipcalc"
|
|
_has_binary "jq"
|
|
_has_binary "pip3"
|
|
_has_binary "sops"
|
|
_has_binary "ssh"
|
|
_has_binary "task"
|
|
_has_binary "yq"
|
|
if ! [[ "$(sops --version)" =~ 3\.[0-9]+\.[0-9]+ ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "Incompatible sops version, make sure you are using the latest release of github.com/mozilla/sops"
|
|
exit 1
|
|
fi
|
|
if ! [[ "$(yq --version)" =~ 4\.[0-9]+\.[0-9]+ ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "Incompatible yq version, make sure you are using the latest release of github.com/mikefarah/yq"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
verify_kubevip() {
|
|
_has_envar "BOOTSTRAP_KUBE_VIP_ADDR"
|
|
_has_valid_ip "${BOOTSTRAP_KUBE_VIP_ADDR}" "BOOTSTRAP_KUBE_VIP_ADDR"
|
|
}
|
|
|
|
verify_metallb() {
|
|
_has_envar "BOOTSTRAP_METALLB_LB_RANGE"
|
|
_has_envar "BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR"
|
|
_has_envar "BOOTSTRAP_METALLB_INGRESS_ADDR"
|
|
_has_valid_ip "$(echo "${BOOTSTRAP_METALLB_LB_RANGE}" | cut -d- -f1)" "BOOTSTRAP_METALLB_LB_RANGE"
|
|
_has_valid_ip "$(echo "${BOOTSTRAP_METALLB_LB_RANGE}" | cut -d- -f2)" "BOOTSTRAP_METALLB_LB_RANGE"
|
|
_has_valid_ip "${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}" "BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR"
|
|
_has_valid_ip "${BOOTSTRAP_METALLB_INGRESS_ADDR}" "BOOTSTRAP_METALLB_INGRESS_ADDR"
|
|
}
|
|
|
|
verify_cluster_service_cidrs() {
|
|
#TODO: Validate input is a CIDR range
|
|
_has_envar "BOOTSTRAP_CLUSTER_CIDR"
|
|
_has_envar "BOOTSTRAP_SERVICE_CIDR"
|
|
}
|
|
|
|
verify_git_repository() {
|
|
_has_envar "BOOTSTRAP_GIT_REPOSITORY"
|
|
export GIT_TERMINAL_PROMPT=0
|
|
pushd "$(mktemp -d)" >/dev/null 2>&1
|
|
[ "$(git ls-remote "${BOOTSTRAP_GIT_REPOSITORY}" 2> /dev/null)" ] || {
|
|
_log "ERROR(${FUNCNAME[0]})" "Unable to find the remote Git repository '${BOOTSTRAP_GIT_REPOSITORY}'"
|
|
exit 1
|
|
}
|
|
popd >/dev/null 2>&1
|
|
export GIT_TERMINAL_PROMPT=1
|
|
}
|
|
|
|
verify_master_count() {
|
|
local master_node_count=
|
|
master_node_count=0
|
|
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
|
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
|
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
|
if [[ "${!node_control}" == "true" ]]; then
|
|
master_node_count=$((master_node_count+1))
|
|
fi
|
|
done
|
|
# Check for existance of master nodes and that their count is not an even number
|
|
if [[ ${master_node_count} -eq 0 || $((master_node_count%2)) -eq 0 ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "You must have 1, 3 or an odd number of master nodes >=3 inorder for etcd to have quorum"
|
|
exit 1
|
|
fi
|
|
_log "INFO(${FUNCNAME[0]})" "Verified control plane node count"
|
|
}
|
|
|
|
verify_cloudflare() {
|
|
local errors=
|
|
local account_zone=
|
|
_has_envar "BOOTSTRAP_CLOUDFLARE_APITOKEN" "true"
|
|
_has_envar "BOOTSTRAP_CLOUDFLARE_DOMAIN" "true"
|
|
_has_envar "BOOTSTRAP_ACME_EMAIL" "true"
|
|
# Try to retrieve zone information from Cloudflare's API
|
|
account_zone=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=${BOOTSTRAP_CLOUDFLARE_DOMAIN}&status=active" \
|
|
-H "Authorization: Bearer ${BOOTSTRAP_CLOUDFLARE_APITOKEN}" \
|
|
-H "Content-Type: application/json"
|
|
)
|
|
if [[ "$(echo "${account_zone}" | jq ".success")" == "true" ]]; then
|
|
_log "INFO(${FUNCNAME[0]})" "Verified Cloudflare Account and Zone information"
|
|
else
|
|
errors=$(echo "${account_zone}" | jq -c ".errors")
|
|
_log "ERROR(${FUNCNAME[0]})" "Unable to get Cloudflare Account and Zone information ${errors}"
|
|
exit 1
|
|
fi
|
|
local cloudflare_tunnel=
|
|
_has_envar "BOOTSTRAP_CLOUDFLARE_ACCOUNT_TAG" "true"
|
|
_has_envar "BOOTSTRAP_CLOUDFLARE_TUNNEL_SECRET" "true"
|
|
_has_envar "BOOTSTRAP_CLOUDFLARE_TUNNEL_ID" "true"
|
|
# Try to retrieve the Cloudflare Tunnel information from Cloudflare's API
|
|
cloudflare_tunnel=$(curl -s -X GET "https://api.cloudflare.com/client/v4/accounts/${BOOTSTRAP_CLOUDFLARE_ACCOUNT_TAG}/cfd_tunnel/${BOOTSTRAP_CLOUDFLARE_TUNNEL_ID}" \
|
|
-H "Authorization: Bearer ${BOOTSTRAP_CLOUDFLARE_APITOKEN}" \
|
|
-H "Content-Type: application/json"
|
|
)
|
|
if [[ "$(echo "${cloudflare_tunnel}" | jq ".success")" == "true" ]]; then
|
|
_log "INFO(${FUNCNAME[0]})" "Verified Cloudflare Tunnel information"
|
|
else
|
|
errors=$(echo "${cloudflare_tunnel}" | jq -c ".errors")
|
|
_log "ERROR(${FUNCNAME[0]})" "Unable to get Cloudflare Tunnel information ${errors}"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
verify_ansible_hosts() {
|
|
local node_id=
|
|
local node_addr=
|
|
local node_username=
|
|
local node_password=
|
|
local node_control=
|
|
local node_hostname=
|
|
local default_control_node_prefix=
|
|
local default_worker_node_prefix=
|
|
default_control_node_prefix="BOOTSTRAP_ANSIBLE_DEFAULT_CONTROL_NODE_HOSTNAME_PREFIX"
|
|
default_worker_node_prefix="BOOTSTRAP_ANSIBLE_DEFAULT_NODE_HOSTNAME_PREFIX"
|
|
_has_optional_envar "${default_control_node_prefix}"
|
|
_has_optional_envar "${default_worker_node_prefix}"
|
|
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
|
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
|
node_addr="BOOTSTRAP_ANSIBLE_HOST_ADDR_${node_id}"
|
|
node_username="BOOTSTRAP_ANSIBLE_SSH_USERNAME_${node_id}"
|
|
node_password="BOOTSTRAP_ANSIBLE_SUDO_PASSWORD_${node_id}"
|
|
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
|
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
|
_has_envar "${node_addr}"
|
|
_has_envar "${node_username}"
|
|
_has_envar "${node_password}" "true"
|
|
_has_envar "${node_control}"
|
|
_has_optional_envar "${node_hostname}"
|
|
if [[ "${!node_addr}" == "${BOOTSTRAP_KUBE_VIP_ADDR}" && "${BOOTSTRAP_KUBE_VIP_ENABLED}" == "true" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The kube-vip IP '${BOOTSTRAP_KUBE_VIP_ADDR}' should not be the same as the IP for node '${!node_addr}'"
|
|
exit 1
|
|
fi
|
|
if [[ "${!node_addr}" == "${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The k8s-gateway load balancer IP '${BOOTSTRAP_METALLB_K8S_GATEWAY_ADDR}' should not be the same as the IP for node '${!node_addr}'"
|
|
exit 1
|
|
fi
|
|
if [[ "${!node_addr}" == "${BOOTSTRAP_METALLB_INGRESS_ADDR}" ]]; then
|
|
_log "ERROR(${FUNCNAME[0]})" "The ingress load balancer IP '${BOOTSTRAP_METALLB_INGRESS_ADDR}' should not be the same as the IP for node '${!node_addr}'"
|
|
exit 1
|
|
fi
|
|
if ssh -q -o BatchMode=yes -o ConnectTimeout=5 "${!node_username}"@"${!var}" "true"; then
|
|
_log "INFO(${FUNCNAME[0]})" "SSH into host '${!var}' with username '${!node_username}' was successfull"
|
|
else
|
|
_log "ERROR(${FUNCNAME[0]})" "SSH into host '${!var}' with username '${!node_username}'was NOT successful, did you copy over your SSH key?"
|
|
exit 1
|
|
fi
|
|
done
|
|
}
|
|
|
|
verify_start() {
|
|
_log "INFO(${FUNCNAME[0]})" "Starting verification of .config.env, please wait..."
|
|
}
|
|
|
|
verify_success() {
|
|
_log "INFO(${FUNCNAME[0]})" "All checks passed!"
|
|
_log "INFO(${FUNCNAME[0]})" "Run the script without --verify to template all the files out"
|
|
exit 0
|
|
}
|
|
|
|
generate_ansible_host_secrets() {
|
|
local node_id=
|
|
local node_username=
|
|
local node_password=
|
|
local node_hostname=
|
|
local node_control=
|
|
default_control_node_prefix=${BOOTSTRAP_ANSIBLE_DEFAULT_CONTROL_NODE_HOSTNAME_PREFIX:-k8s-}
|
|
default_worker_node_prefix=${BOOTSTRAP_ANSIBLE_DEFAULT_NODE_HOSTNAME_PREFIX:-k8s-}
|
|
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
|
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
|
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
|
if [[ "${!node_control}" == "true" ]]; then
|
|
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
|
host_key="${!node_hostname:-${default_control_node_prefix}}"
|
|
if [ "${host_key}" == "${default_control_node_prefix}" ]; then
|
|
node_hostname=${default_control_node_prefix}${node_id}
|
|
else
|
|
node_hostname=${!node_hostname}
|
|
fi
|
|
else
|
|
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
|
host_key="${!node_hostname:-${default_worker_node_prefix}}"
|
|
if [ "${host_key}" == "${default_worker_node_prefix}" ]; then
|
|
node_hostname=${default_worker_node_prefix}${node_id}
|
|
else
|
|
node_hostname=${!node_hostname}
|
|
fi
|
|
fi
|
|
{
|
|
node_username="BOOTSTRAP_ANSIBLE_SSH_USERNAME_${node_id}"
|
|
node_password="BOOTSTRAP_ANSIBLE_SUDO_PASSWORD_${node_id}"
|
|
printf "kind: Secret\n"
|
|
printf "ansible_user: %s\n" "${!node_username}"
|
|
printf "ansible_become_pass: %s\n" "${!node_password}"
|
|
} > "${PROJECT_DIR}/ansible/inventory/host_vars/${node_hostname}.sops.yml"
|
|
sops --encrypt --in-place "${PROJECT_DIR}/ansible/inventory/host_vars/${node_hostname}.sops.yml"
|
|
done
|
|
}
|
|
|
|
generate_ansible_hosts() {
|
|
local worker_node_count=
|
|
default_control_node_prefix=${BOOTSTRAP_ANSIBLE_DEFAULT_CONTROL_NODE_HOSTNAME_PREFIX:-k8s-}
|
|
default_worker_node_prefix=${BOOTSTRAP_ANSIBLE_DEFAULT_NODE_HOSTNAME_PREFIX:-k8s-}
|
|
{
|
|
printf -- "---\n"
|
|
printf "kubernetes:\n"
|
|
printf " children:\n"
|
|
printf " master:\n"
|
|
printf " hosts:\n"
|
|
master_node_count=0
|
|
worker_node_count=0
|
|
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
|
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
|
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
|
if [[ "${!node_control}" == "true" ]]; then
|
|
master_node_count=$((master_node_count+1))
|
|
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
|
host_key="${!node_hostname:-${default_control_node_prefix}}"
|
|
if [ "${host_key}" == "${default_control_node_prefix}" ]; then
|
|
node_hostname=${default_control_node_prefix}${node_id}
|
|
else
|
|
node_hostname=${!node_hostname}
|
|
fi
|
|
printf " %s:\n" "${node_hostname}"
|
|
printf " ansible_host: %s\n" "${!var}"
|
|
else
|
|
worker_node_count=$((worker_node_count+1))
|
|
fi
|
|
done
|
|
if [[ ${worker_node_count} -gt 0 ]]; then
|
|
printf " worker:\n"
|
|
printf " hosts:\n"
|
|
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
|
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
|
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
|
if [[ "${!node_control}" == "false" ]]; then
|
|
node_hostname="BOOTSTRAP_ANSIBLE_HOSTNAME_${node_id}"
|
|
host_key="${!node_hostname:-${default_worker_node_prefix}}"
|
|
if [ "${host_key}" == "${default_worker_node_prefix}" ]; then
|
|
node_hostname=${default_worker_node_prefix}${node_id}
|
|
else
|
|
node_hostname=${!node_hostname}
|
|
fi
|
|
printf " %s:\n" "${node_hostname}"
|
|
printf " ansible_host: %s\n" "${!var}"
|
|
fi
|
|
done
|
|
fi
|
|
} > "${PROJECT_DIR}/ansible/inventory/hosts.yml"
|
|
}
|
|
|
|
setup_prometheus_endpoints() {
|
|
yq --inplace 'del(.spec.values.kubeControllerManager.endpoints[])' \
|
|
"${PROJECT_DIR}/kubernetes/apps/monitoring/kube-prometheus-stack/app/helmrelease.yaml"
|
|
for var in "${!BOOTSTRAP_ANSIBLE_HOST_ADDR_@}"; do
|
|
node_id=$(echo "${var}" | awk -F"_" '{print $5}')
|
|
node_control="BOOTSTRAP_ANSIBLE_CONTROL_NODE_${node_id}"
|
|
if [[ "${!node_control}" == "true" ]]; then
|
|
node_addr="BOOTSTRAP_ANSIBLE_HOST_ADDR_${node_id}"
|
|
_log "INFO(${FUNCNAME[0]})" "Setting up Prometheus endpoint for '${!node_addr}'"
|
|
yq --inplace ".spec.values.kubeControllerManager.endpoints += \"${!node_addr}\"" \
|
|
"${PROJECT_DIR}/kubernetes/apps/monitoring/kube-prometheus-stack/app/helmrelease.yaml"
|
|
fi
|
|
done
|
|
}
|
|
|
|
setup_github_webhook() {
|
|
_has_envar "BOOTSTRAP_FLUX_GITHUB_WEBHOOK_SECRET"
|
|
WEBHOOK_SECRET="${BOOTSTRAP_FLUX_GITHUB_WEBHOOK_SECRET}"
|
|
if [[ "${WEBHOOK_SECRET}" == "generated" ]]; then
|
|
WEBHOOK_SECRET="$(openssl rand -hex 12)"
|
|
fi
|
|
export BOOTSTRAP_FLUX_GITHUB_WEBHOOK_SECRET="${WEBHOOK_SECRET}"
|
|
_log "INFO(${FUNCNAME[0]})" "Using GitHub Token '${WEBHOOK_SECRET}' for Flux"
|
|
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/github-webhook-token-secret.sops.yaml" \
|
|
> "${PROJECT_DIR}/kubernetes/apps/flux-system/addons/webhooks/github/secret.sops.yaml"
|
|
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/flux-system/addons/webhooks/github/secret.sops.yaml"
|
|
}
|
|
|
|
setup_weave_gitops() {
|
|
_has_envar "BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD"
|
|
WEAVE_GITOPS_ADMIN_PASSWORD="${BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD}"
|
|
if [[ "${WEAVE_GITOPS_ADMIN_PASSWORD}" == "generated" ]]; then
|
|
WEAVE_GITOPS_ADMIN_PASSWORD="$(openssl rand -hex 12)"
|
|
fi
|
|
export BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD="${WEAVE_GITOPS_ADMIN_PASSWORD}"
|
|
_log "INFO(${FUNCNAME[0]})" "Using admin password '${WEAVE_GITOPS_ADMIN_PASSWORD}' for Weave Gitops"
|
|
# Convert password to bcrypt hash
|
|
# shellcheck disable=SC2155
|
|
export BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD="$(echo -n "${BOOTSTRAP_WEAVE_GITOPS_ADMIN_PASSWORD}" | gitops get bcrypt-hash)"
|
|
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/weave-gitops-secret.sops.yaml" \
|
|
> "${PROJECT_DIR}/kubernetes/apps/flux-system/weave-gitops/app/secret.sops.yaml"
|
|
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/flux-system/weave-gitops/app/secret.sops.yaml"
|
|
}
|
|
|
|
setup_grafana_admin() {
|
|
_has_envar "BOOTSTRAP_GRAFANA_ADMIN_PASSWORD"
|
|
GRAFANA_ADMIN_PASSWORD="${BOOTSTRAP_GRAFANA_ADMIN_PASSWORD}"
|
|
if [[ "${GRAFANA_ADMIN_PASSWORD}" == "generated" ]]; then
|
|
GRAFANA_ADMIN_PASSWORD="$(openssl rand -hex 12)"
|
|
fi
|
|
export BOOTSTRAP_GRAFANA_ADMIN_PASSWORD="${GRAFANA_ADMIN_PASSWORD}"
|
|
_log "INFO(${FUNCNAME[0]})" "Using password '${GRAFANA_ADMIN_PASSWORD}' for Grafana"
|
|
envsubst < "${PROJECT_DIR}/tmpl/kubernetes/grafana-admin-secret.sops.yaml" \
|
|
> "${PROJECT_DIR}/kubernetes/apps/monitoring/grafana/app/secret.sops.yaml"
|
|
sops --encrypt --in-place "${PROJECT_DIR}/kubernetes/apps/monitoring/grafana/app/secret.sops.yaml"
|
|
}
|
|
|
|
success() {
|
|
_log "INFO(${FUNCNAME[0]})" "All files have been templated, proceed to the next steps outlined in the README"
|
|
exit 0
|
|
}
|
|
|
|
_log() {
|
|
local type="${1}"
|
|
local msg="${2}"
|
|
printf 'timestamp="%s" type="%s" message="%s"\n' "$(date)" "${type}" "${msg}"
|
|
}
|
|
|
|
main "$@"
|