Created
April 30, 2021 22:28
-
-
Save kenthua/fbb1a5a30c0da431c0e029966a51381c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
set -CeE | |
set -o pipefail | |
if [[ "${BASH_VERSINFO:-0}" -lt 4 ]]; then | |
cat << EOF >&2 | |
WARNING: bash ${BASH_VERSION} does not support several modern safety features. | |
This script was written with the latest POSIX standard in mind, and was only | |
tested with modern shell standards. This script may not perform correctly in | |
this environment. | |
EOF | |
sleep 1 | |
else | |
set -u | |
fi | |
### These are hooks for Cloud Build to be able to use debug/staging images | |
### when necessary. Don't set these environment variables unless you're testing | |
### in CI/CD. | |
_CI_ASM_IMAGE_LOCATION="${_CI_ASM_IMAGE_LOCATION:=}" | |
_CI_ASM_IMAGE_TAG="${_CI_ASM_IMAGE_TAG:=}" | |
_CI_ASM_PKG_LOCATION="${_CI_ASM_PKG_LOCATION:=}" | |
_CI_CLOUDRUN_IMAGE_HUB="${_CI_CLOUDRUN_IMAGE_HUB:=}" | |
_CI_CLOUDRUN_IMAGE_TAG="${_CI_CLOUDRUN_IMAGE_TAG:=}" | |
_CI_REVISION_PREFIX="${_CI_REVISION_PREFIX:=}" | |
_CI_NO_VALIDATE="${_CI_NO_VALIDATE:=0}" | |
_CI_NO_REVISION="${_CI_NO_REVISION:=0}" | |
_CI_ISTIOCTL_REL_PATH="${_CI_ISTIOCTL_REL_PATH:=}" | |
_CI_TRUSTED_GCP_PROJECTS="${_CI_TRUSTED_GCP_PROJECTS:=}" | |
_CI_ENVIRON_PROJECT_NUMBER="${_CI_ENVIRON_PROJECT_NUMBER:=}" | |
_CI_CRC_VERSION="${_CI_CRC_VERSION:=0}" | |
_CI_I_AM_A_TEST_ROBOT="${_CI_I_AM_A_TEST_ROBOT:=0}" | |
### Internal variables ### | |
MAJOR="${MAJOR:=1}"; readonly MAJOR; | |
MINOR="${MINOR:=9}"; readonly MINOR; | |
POINT="${POINT:=3}"; readonly POINT; | |
REV="${REV:=2}"; readonly REV; | |
CONFIG_VER="${CONFIG_VER:="1-unstable"}"; readonly CONFIG_VER; | |
K8S_MINOR=0 | |
### File related constants ### | |
VALIDATION_FIX_FILE_NAME="istiod-service.yaml" | |
ASM_VERSION_FILE=".asm_version" | |
ISTIO_FOLDER_NAME="" | |
ISTIOCTL_REL_PATH="" | |
BASE_REL_PATH="" | |
PACKAGE_DIRECTORY="" | |
VALIDATION_FIX_SERVICE="" | |
OPTIONS_DIRECTORY="" | |
OPERATOR_MANIFEST="" | |
BETA_CRD_MANIFEST="" | |
MANAGED_MANIFEST="" | |
MANAGED_WEBHOOKS="" | |
CONNECT_RBAC="" | |
EXPOSE_ISTIOD_SERVICE="" | |
CANONICAL_CONTROLLER_MANIFEST="" | |
SCRIPT_NAME="${0##*/}" | |
GCLOUD_USER_OR_SA="${GCLOUD_USER_OR_SA:=}" | |
PROJECT_NUMBER="" | |
GKE_CLUSTER_URI="" | |
GCE_NETWORK_NAME="" | |
KPT_URL="" | |
KUBECONFIG="" | |
APATH="" | |
AKUBECTL="" | |
AKPT="" | |
AGCLOUD="" | |
ABS_OVERLAYS="" | |
RELEASE="" | |
REVISION_LABEL="" | |
RELEASE_LINE="" | |
PREVIOUS_RELEASE_LINE="" | |
KPT_BRANCH="" | |
NAMESPACE_EXISTS=0 | |
### Option variables ### | |
PROJECT_ID="${PROJECT_ID:=}" | |
CLUSTER_NAME="${CLUSTER_NAME:=}" | |
CLUSTER_LOCATION="${CLUSTER_LOCATION:=}" | |
MODE="${MODE:=}" | |
CA="${CA:=}" | |
CUSTOM_OVERLAY="" | |
OPTIONAL_OVERLAY="" | |
ENABLE_ALL="${ENABLE_ALL:=0}" | |
ENABLE_CLUSTER_ROLES="${ENABLE_CLUSTER_ROLES:=0}" | |
ENABLE_CLUSTER_LABELS="${ENABLE_CLUSTER_LABELS:=0}" | |
ENABLE_GCP_APIS="${ENABLE_GCP_APIS:=0}" | |
ENABLE_GCP_IAM_ROLES="${ENABLE_GCP_IAM_ROLES:=0}" | |
ENABLE_GCP_COMPONENTS="${ENABLE_GCP_COMPONENTS:=0}" | |
ENABLE_REGISTRATION="${ENABLE_REGISTRATION:=0}" | |
ENABLE_NAMESPACE_CREATION="${ENABLE_NAMESPACE_CREATION:=0}" | |
DISABLE_CANONICAL_SERVICE="${DISABLE_CANONICAL_SERVICE:=0}" | |
PRINT_CONFIG="${PRINT_CONFIG:=0}" | |
SERVICE_ACCOUNT="${SERVICE_ACCOUNT:=}" | |
KEY_FILE="${KEY_FILE:=}" | |
OUTPUT_DIR="${OUTPUT_DIR:=}" | |
CA_CERT="${CA_CERT:=}" | |
CA_KEY="${CA_KEY:=}" | |
CA_ROOT="${CA_ROOT:=}" | |
CA_CHAIN="${CA_CHAIN:=}" | |
CA_NAME="${CA_NAME:=}" | |
DRY_RUN="${DRY_RUN:=0}" | |
ONLY_VALIDATE="${ONLY_VALIDATE:=0}" | |
ONLY_ENABLE="${ONLY_ENABLE:=0}" | |
VERBOSE="${VERBOSE:=0}" | |
MANAGED="${MANAGED:=0}" | |
MANAGED_SERVICE_ACCOUNT="" | |
PRINT_HELP=0 | |
PRINT_VERSION=0 | |
CUSTOM_CA=0 | |
USE_HUB_WIP=0 | |
USE_VM=0 | |
ENVIRON_PROJECT_ID="" | |
HUB_MEMBERSHIP_ID="" | |
CUSTOM_REVISION=0 | |
WI_ENABLED=0 | |
HAS_TS=0 | |
main() { | |
init | |
parse_args "${@}" | |
# make sure to redirect stdout as soon as possible if we're dumping the config | |
if [[ "${PRINT_CONFIG}" -eq 1 ]]; then | |
exec 3>&1 | |
exec 1>&2 | |
fi | |
validate_args | |
set_up_local_workspace | |
configure_kubectl | |
validate_cli_dependencies | |
if is_sa; then | |
auth_service_account | |
fi | |
if should_validate || can_modify_at_all; then | |
local_iam_user > /dev/null | |
validate_gcp_resources | |
fi | |
if needs_asm && ! necessary_files_exist; then | |
download_asm | |
fi | |
if needs_asm; then | |
organize_kpt_files | |
fi | |
if should_validate && ! is_managed; then | |
validate_dependencies | |
fi | |
if can_modify_gcp_apis; then | |
enable_gcloud_apis | |
elif should_validate; then | |
exit_if_apis_not_enabled | |
fi | |
if can_register_cluster; then | |
register_cluster | |
elif should_validate && [[ "${USE_HUB_WIP}" -eq 1 ]]; then | |
exit_if_cluster_unregistered | |
fi | |
# Managed must be able to set IAM permissions on a generated user, so the flow | |
# is a bit different | |
if is_managed; then | |
if can_modify_gcp_components; then | |
enable_workload_identity | |
enable_stackdriver_kubernetes | |
else | |
exit_if_no_workload_identity | |
exit_if_stackdriver_not_enabled | |
fi | |
if can_modify_gcp_iam_roles; then | |
bind_user_to_iam_policy "roles/meshconfig.admin" "$(local_iam_user)" | |
fi | |
if can_modify_at_all; then | |
MANAGED_SERVICE_JSON="$(init_meshconfig_managed)" | |
MANAGED_SERVICE_ACCOUNT="$(echo "${MANAGED_SERVICE_JSON}" | jq -r .serviceAccount)" | |
if [[ -z "${MANAGED_SERVICE_JSON}" || "${MANAGED_SERVICE_ACCOUNT}" == "null" ]]; then | |
fatal "Couldn't initialize meshconfig, do you have the required permission resourcemanager.projects.setIamPolicy?" | |
fi | |
unset MANAGED_SERVICE_JSON | |
bind_user_to_iam_policy "$(required_iam_roles_mcp_sa)" "$(iam_user)" | |
fi | |
else | |
if can_modify_gcp_iam_roles; then | |
bind_user_to_iam_policy "$(required_iam_roles)" "$(iam_user)" | |
elif should_validate; then | |
exit_if_out_of_iam_policy | |
fi | |
fi | |
get_project_number | |
if can_modify_cluster_labels; then | |
add_cluster_labels | |
elif should_validate; then | |
exit_if_cluster_unlabeled | |
fi | |
if ! is_managed; then | |
if can_modify_gcp_components; then | |
enable_workload_identity | |
init_meshconfig | |
if [[ "${CA}" = "gcp_cas" ]]; then | |
# This sets up IAM privileges for the project to be able to access GCP CAS. | |
# If modify_gcp_component permissions are not granted, it is assumed that the | |
# user has taken care of this, else Istio setup will fail | |
init_gcp_cas | |
fi | |
enable_stackdriver_kubernetes | |
if should_enable_service_mesh_feature; then | |
enable_service_mesh_feature | |
fi | |
elif should_validate; then | |
warn "There is no way to validate that the meshconfig API has been initialized." | |
warn "This needs to happen once per GCP project. If the API has not been initialized" | |
warn "for ${PROJECT_ID}, please run ${SCRIPT_NAME} with the --enable_gcp_components" | |
warn "flag. Otherwise, installation will succeed but Anthos Service Mesh" | |
warn_pause "will not function correctly." | |
exit_if_no_workload_identity | |
exit_if_stackdriver_not_enabled | |
if should_enable_service_mesh_feature; then | |
exit_if_service_mesh_feature_not_enabled | |
fi | |
fi | |
fi | |
if can_modify_cluster_roles; then | |
bind_user_to_cluster_admin | |
elif should_validate; then | |
exit_if_not_cluster_admin | |
fi | |
if can_create_namespace; then | |
create_istio_namespace | |
elif should_validate; then | |
exit_if_istio_namespace_not_exists | |
fi | |
if [[ "${ONLY_VALIDATE}" -eq 1 ]]; then | |
info "Successfully validated all requirements to install ASM in this environment." | |
return 0 | |
fi | |
if only_enable; then | |
info "Successfully performed specified --enable actions." | |
return 0 | |
fi | |
if [[ "${PRINT_CONFIG}" -eq 1 && "${USE_HUB_WIP}" -eq 1 ]]; then | |
populate_environ_info | |
fi | |
if can_modify_at_all && [[ "${MANAGED}" -eq 1 ]]; then | |
start_managed_control_plane | |
configure_package | |
install_managed_components | |
outro | |
return 0 | |
fi | |
if [[ "${USE_VM}" -eq 1 ]]; then | |
register_gce_identity_provider | |
fi | |
configure_package | |
handle_multi_yaml_bug | |
if [[ "${PRINT_CONFIG}" -eq 1 ]]; then | |
print_config >&3 | |
return 0 | |
fi | |
if [[ "${CUSTOM_CA}" -eq 1 ]]; then | |
install_secrets | |
fi | |
install_asm | |
info "Successfully installed ASM." | |
return 0 | |
} | |
init() { | |
# BSD-style readlink apparently doesn't have the same -f toggle on readlink | |
case "$(uname)" in | |
Linux ) APATH="readlink";; | |
Darwin) APATH="stat";; | |
*);; | |
esac | |
if hash ts 2>/dev/null; then | |
HAS_TS=1 | |
fi | |
if [[ "${POINT}" == "alpha" ]]; then | |
RELEASE="${MAJOR}.${MINOR}-alpha.${REV}" | |
REVISION_LABEL="${_CI_REVISION_PREFIX}asm-${MAJOR}${MINOR}${POINT}" | |
KPT_BRANCH="${_CI_ASM_KPT_BRANCH:=master}" | |
elif [[ "$(version_message)" =~ ^[0-9]+\.[0-9]+\.[0-9]+-asm\.[0-9]+\+config[0-9]+$ ]]; then | |
RELEASE="${MAJOR}.${MINOR}.${POINT}-asm.${REV}" | |
REVISION_LABEL="${_CI_REVISION_PREFIX}asm-${MAJOR}${MINOR}${POINT}-${REV}" | |
KPT_BRANCH="${_CI_ASM_KPT_BRANCH:=$(version_message)}" | |
else | |
RELEASE="${MAJOR}.${MINOR}.${POINT}-asm.${REV}" | |
REVISION_LABEL="${_CI_REVISION_PREFIX}asm-${MAJOR}${MINOR}${POINT}-${REV}" | |
KPT_BRANCH="${_CI_ASM_KPT_BRANCH:=release-${MAJOR}.${MINOR}-asm}" | |
fi | |
RELEASE_LINE="${MAJOR}.${MINOR}." | |
PREVIOUS_RELEASE_LINE="${MAJOR}.$(( MINOR - 1 ))." | |
readonly RELEASE; readonly RELEASE_LINE; readonly PREVIOUS_RELEASE_LINE; readonly KPT_BRANCH; | |
ISTIO_FOLDER_NAME="istio-${RELEASE}"; readonly ISTIO_FOLDER_NAME; | |
ISTIOCTL_REL_PATH="${ISTIO_FOLDER_NAME}/bin/istioctl"; readonly ISTIOCTL_REL_PATH; | |
BASE_REL_PATH="${ISTIO_FOLDER_NAME}/manifests/charts/base/files/gen-istio-cluster.yaml"; readonly BASE_REL_PATH; | |
PACKAGE_DIRECTORY="asm/istio"; readonly PACKAGE_DIRECTORY; | |
VALIDATION_FIX_SERVICE="${PACKAGE_DIRECTORY}/${VALIDATION_FIX_FILE_NAME}"; readonly VALIDATION_FIX_SERVICE; | |
OPTIONS_DIRECTORY="${PACKAGE_DIRECTORY}/options"; readonly OPTIONS_DIRECTORY; | |
OPERATOR_MANIFEST="${PACKAGE_DIRECTORY}/istio-operator.yaml"; readonly OPERATOR_MANIFEST; | |
BETA_CRD_MANIFEST="${OPTIONS_DIRECTORY}/v1beta1-crds.yaml"; readonly BETA_CRD_MANIFEST; | |
MANAGED_MANIFEST="${OPTIONS_DIRECTORY}/managed-control-plane.yaml"; readonly MANAGED_MANIFEST; | |
MANAGED_WEBHOOKS="${OPTIONS_DIRECTORY}/managed-control-plane-webhooks.yaml"; readonly MANAGED_WEBHOOKS; | |
CONNECT_RBAC="asm/third-party/hub-connect-impersonate-rbac.yaml"; readonly CONNECT_RBAC; | |
EXPOSE_ISTIOD_SERVICE="${PACKAGE_DIRECTORY}/expansion/expose-istiod.yaml"; readonly EXPOSE_ISTIOD_SERVICE; | |
CANONICAL_CONTROLLER_MANIFEST="asm/canonical-service/controller.yaml"; readonly CANONICAL_CONTROLLER_MANIFEST; | |
AKUBECTL="$(which kubectl || true)" | |
AGCLOUD="$(which gcloud || true)" | |
AKPT="$(which kpt || true)" | |
} | |
### Convenience functions ### | |
####### | |
# run takes a list of arguments that represents a command | |
# If DRY_RUN or VERBOSE is enabled, it will print the command, and if DRY_RUN is | |
# not enabled it runs the command. | |
####### | |
run() { | |
if [[ "${DRY_RUN}" -eq 1 ]]; then | |
warn "Would have executed: ${*}" | |
return | |
elif [[ "${VERBOSE}" -eq 0 ]]; then | |
"${@}" 2>/dev/null | |
return "$?" | |
fi | |
info "Running: '${*}'" | |
info "-------------" | |
local RETVAL | |
{ "${@}"; RETVAL="$?"; } || true | |
return $RETVAL | |
} | |
apath() { | |
"${APATH}" "${@}" | |
} | |
gcloud() { | |
run "${AGCLOUD}" "${@}" | |
} | |
kubectl() { | |
run "${AKUBECTL}" "${@}" | |
} | |
kpt() { | |
run "${AKPT}" "${@}" | |
} | |
istioctl() { | |
run "$(istioctl_path)" "${@}" | |
} | |
istioctl_path() { | |
if [[ -n "${_CI_ISTIOCTL_REL_PATH}" && -f "${_CI_ISTIOCTL_REL_PATH}" ]]; then | |
echo "${_CI_ISTIOCTL_REL_PATH}" | |
else | |
echo "./${ISTIOCTL_REL_PATH}" | |
fi | |
} | |
####### | |
# retry takes an integer N as the first argument, and a list of arguments | |
# representing a command afterwards. It will retry the given command up to N | |
# times before returning 1. If the command is kubectl, it will try to | |
# re-get credentials in case something caused the k8s IP to change. | |
####### | |
retry() { | |
local MAX_TRIES; MAX_TRIES="${1}"; | |
shift 1 | |
for i in $(seq 0 "${MAX_TRIES}"); do | |
if [[ "${i}" -eq "${MAX_TRIES}" ]]; then | |
break | |
fi | |
{ "${@}" && return 0; } || true | |
warn "Failed, retrying...($((i+1)) of ${MAX_TRIES})" | |
sleep $((2 * i + 2)) | |
done | |
false | |
} | |
fail_if_empty() { | |
local VAL | |
VAL="$("${@}")" | |
if [[ -z "${VAL}" ]]; then | |
false | |
return | |
fi | |
echo "${VAL}" | |
} | |
find_missing_strings() { | |
local NEEDLES; NEEDLES="${1}"; | |
local HAYSTACK; HAYSTACK="${2}"; | |
local NOTFOUND; NOTFOUND=""; | |
while read -r needle; do | |
EXITCODE=0 | |
grep -q "${needle}" <<EOF || EXITCODE=$? | |
${HAYSTACK} | |
EOF | |
if [[ "${EXITCODE}" -ne 0 ]]; then | |
NOTFOUND="${needle},${NOTFOUND}" | |
fi | |
done <<EOF | |
${NEEDLES} | |
EOF | |
if [[ -n "${NOTFOUND}" ]]; then NOTFOUND="$(strip_trailing_commas "${NOTFOUND}")"; fi | |
echo "${NOTFOUND}" | |
} | |
strip_trailing_commas() { | |
# shellcheck disable=SC2001 | |
echo "${1}" | sed 's/,*$//g' | |
} | |
configure_kubectl(){ | |
info "Fetching/writing GCP credentials to kubeconfig file..." | |
retry 2 gcloud container clusters get-credentials "${CLUSTER_NAME}" \ | |
--project="${PROJECT_ID}" \ | |
--zone="${CLUSTER_LOCATION}" | |
if ! hash nc 2>/dev/null; then | |
warn "nc not found, skipping k8s connection verification" | |
warn "(Installation will continue normally.)" | |
return | |
fi | |
info "Verifying connectivity (10s)..." | |
local ADDR | |
ADDR="$(kubectl config view --minify=true -ojson | jq .clusters[0].cluster.server -r)" | |
local RETVAL; RETVAL=0; | |
run nc -zvw 10 "${ADDR:8:${#ADDR}}" 443 || RETVAL=$? | |
if [[ "${RETVAL}" -ne 0 ]]; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
Couldn't connect to ${CLUSTER_NAME}. | |
If this is a private cluster, verify that the correct firewall rules are applied. | |
https://cloud.google.com/service-mesh/docs/gke-install-overview#requirements | |
EOF | |
fi | |
info "kubeconfig set to ${PROJECT_ID}/${CLUSTER_LOCATION}/${CLUSTER_NAME}..." | |
} | |
warn() { | |
info "[WARNING]: ${1}" >&2 | |
} | |
warn_pause() { | |
warn "${1}" | |
sleep 2 | |
} | |
error() { | |
info "[ERROR]: ${1}" >&2 | |
} | |
info() { | |
if [[ "${VERBOSE}" -eq 1 && "${HAS_TS}" -eq 1 ]]; then | |
echo "${SCRIPT_NAME}: ${1}" | TZ=utc ts '%Y-%m-%dT%.T' >&2 | |
else | |
echo "${SCRIPT_NAME}: ${1}" >&2 | |
fi | |
} | |
fatal() { | |
error "${1}" | |
exit 2 | |
} | |
fatal_with_usage() { | |
error "${1}" | |
usage_short >&2 | |
exit 2 | |
} | |
starline() { | |
echo "*****************************" | |
} | |
is_managed() { | |
if [[ "${MANAGED}" -ne 1 ]]; then false; fi | |
} | |
is_sa() { | |
if [[ -z "${SERVICE_ACCOUNT}" ]]; then false; fi | |
} | |
should_validate() { | |
if [[ "${PRINT_CONFIG}" -eq 1 || "${_CI_NO_VALIDATE}" -eq 1 ]] || only_enable; then false; fi | |
} | |
only_enable() { | |
if [[ "${ONLY_ENABLE}" -eq 0 ]]; then false; fi | |
} | |
can_modify_at_all() { | |
if [[ "${ONLY_VALIDATE}" -eq 1 || "${PRINT_CONFIG}" -eq 1 ]]; then false; fi | |
} | |
can_modify_cluster_roles() { | |
if ! can_modify_at_all; then false; return; fi | |
if is_managed || [[ "${ENABLE_ALL}" -eq 1 || "${ENABLE_CLUSTER_ROLES}" -eq 1 ]]; then | |
true | |
else | |
false | |
fi | |
} | |
can_modify_cluster_labels() { | |
if ! can_modify_at_all; then false; return; fi | |
if [[ "${ENABLE_ALL}" -eq 1 || "${ENABLE_CLUSTER_LABELS}" -eq 1 ]]; then | |
true | |
else | |
false | |
fi | |
} | |
can_modify_gcp_apis() { | |
if ! can_modify_at_all; then false; return; fi | |
if [[ "${ENABLE_ALL}" -eq 1 || "${ENABLE_GCP_APIS}" -eq 1 ]]; then | |
true | |
else | |
false | |
fi | |
} | |
can_modify_gcp_iam_roles() { | |
if ! can_modify_at_all; then false; return; fi | |
if is_managed || [[ "${ENABLE_ALL}" -eq 1 || "${ENABLE_GCP_IAM_ROLES}" -eq 1 ]]; then | |
true | |
else | |
false | |
fi | |
} | |
can_modify_gcp_components() { | |
if ! can_modify_at_all; then false; return; fi | |
if is_managed || [[ "${ENABLE_ALL}" -eq 1 || "${ENABLE_GCP_COMPONENTS}" -eq 1 ]]; then | |
true | |
else | |
false | |
fi | |
} | |
can_register_cluster() { | |
if ! can_modify_at_all; then false; return; fi | |
if [[ "${ENABLE_ALL}" -eq 1 && ("${USE_VM}" -eq 1 || "${USE_HUB_WIP}" -eq 1) ]] \ | |
|| [[ "${ENABLE_REGISTRATION}" -eq 1 ]]; then | |
true | |
else | |
false | |
fi | |
} | |
can_create_namespace() { | |
if ! can_modify_at_all; then false; return; fi | |
if [[ "${ENABLE_ALL}" -eq 1 || "${ENABLE_NAMESPACE_CREATION}" -eq 1 ]]; then | |
true | |
else | |
false | |
fi | |
} | |
needs_asm() { | |
if only_enable; then false; return; fi | |
if [[ "${PRINT_CONFIG}" -eq 1 ]] || can_modify_at_all || should_validate; then | |
true | |
else | |
false | |
fi | |
} | |
enable_common_message() { | |
echo "Alternatively, use --enable_all|-e to allow this tool to handle all dependencies." | |
} | |
version_message() { | |
local VER; VER="${MAJOR}.${MINOR}.${POINT}-asm.${REV}+config${CONFIG_VER}"; | |
if [[ "${_CI_CRC_VERSION}" -eq 1 ]]; then | |
VER="${VER}-$(crc32 "$0")" | |
fi | |
echo "${VER}" | |
} | |
### CLI/initial setup functions ### | |
usage_short() { | |
cat << EOF | |
${SCRIPT_NAME} $(version_message) | |
usage: ${SCRIPT_NAME} [OPTION]... | |
Set up, validate, and install ASM in a Google Cloud environment. | |
Use -h|--help with -v|--verbose to show detailed descriptions. | |
OPTIONS: | |
-l|--cluster_location <LOCATION> | |
-n|--cluster_name <NAME> | |
-p|--project_id <ID> | |
-m|--mode <MODE> | |
-c|--ca <CA> | |
-o|--option <FILE NAME> | |
-s|--service_account <ACCOUNT> | |
-k|--key_file <FILE PATH> | |
-D|--output_dir <DIR PATH> | |
--co|--custom_overlay <FILE NAME> | |
--ca_cert <FILE PATH> | |
--ca_key <FILE PATH> | |
--root_cert <FILE PATH> | |
--cert_chain <FILE PATH> | |
--ca_name <CA NAME> | |
-r|--revision_name <REVISION NAME> | |
FLAGS: | |
-e|--enable_all | |
--enable_cluster_roles | |
--enable_cluster_labels | |
--enable_gcp_apis | |
--enable_gcp_iam_roles | |
--enable_gcp_components | |
--enable_registration | |
--enable_namespace_creation | |
--managed | |
--print_config | |
--disable_canonical_service | |
-v|--verbose | |
--dry_run | |
--only_validate | |
--only_enable | |
-h|--help | |
--version | |
EOF | |
} | |
usage() { | |
cat << EOF | |
${SCRIPT_NAME} $(version_message) | |
usage: ${SCRIPT_NAME} [OPTION]... | |
Set up, validate, and install ASM in a Google Cloud environment. | |
Single argument options can also be passed via environment variables by using | |
the ALL_CAPS name. Options specified via flags take precedence over environment | |
variables. | |
OPTIONS: | |
-l|--cluster_location <LOCATION> The GCP location of the target cluster. | |
-n|--cluster_name <NAME> The name of the target cluster. | |
-p|--project_id <ID> The GCP project ID. | |
-m|--mode <MODE> The type of installation to perform. | |
Passing --mode install will attempt a | |
new ASM installation. Passing --mode | |
migrate will attempt to migrate an Istio | |
installation to ASM. Passing --mode | |
upgrade will attempt to upgrade an | |
existing ASM installation to a newer | |
version. Allowed values for | |
<MODE> are {install|migrate|upgrade}. | |
-c|--ca <CA> The type of certificate authority to be | |
used. Defaults to "mesh_ca" for --mode | |
install. Specifying the CA is required | |
for --mode migrate. Allowed values for | |
<CA> are {citadel|mesh_ca|gcp_cas}. | |
-o|--option <FILE NAME> The name of a YAML file in the kpt pkg to | |
apply. For options, see the | |
anthos-service-mesh-package GitHub | |
repo under GoogleCloudPlatform. Files | |
should be in "asm/istio/options" folder, | |
and shouldn't include the .yaml extension. | |
(See https://git.io/JTDdi for options.) | |
To add multiple files, specify them with | |
multiple options one at a time. | |
-s|--service_account <ACCOUNT> The name of a service account used to | |
install ASM. If not specified, the gcloud | |
user currently configured will be used. | |
-k|--key_file <FILE PATH> The key file for a service account. This | |
option can be omitted if not using a | |
service account. | |
-D|--output_dir <DIR PATH> The directory where this script will place | |
downloaded ASM packages and configuration. | |
If not specified, a temporary directory | |
will be created. If specified and the | |
directory already contains the necessary | |
files, they will be used instead of | |
downloading them again. | |
--co|--custom_overlay <FILE PATH> The location of a YAML file to overlay on | |
the ASM IstioOperator. This option can be | |
omitted if not installing optional | |
features. To add multiple files, specify | |
them with multiple options one at a time. | |
--ca_name <CA NAME> Required only if --ca option is gcp_cas. | |
Name of the ca in the GCP CAS service used to | |
sign certificates in the format | |
'projects/project_name/locations/ \ | |
ca_region/certificateAuthorities/ca_name'. | |
-r|--revision_name <REVISION NAME> Custom revision label. Label needs to follow DNS | |
label formats (re: RFC 1123). Not supported if | |
control plane is managed. Prefixing the revision | |
name with 'asm' is recommended. | |
The following four options must be passed together and are only necessary | |
for using a custom certificate for Citadel. Users that aren't sure whether | |
they need this probably don't. | |
--ca_cert <FILE PATH> The intermediate certificate | |
--ca_key <FILE PATH> The key for the intermediate certificate | |
--root_cert <FILE PATH> The root certificate | |
--cert_chain <FILE PATH> The certificate chain | |
FLAGS: | |
The following several flags all relate to allowing the script to create, set, | |
or enable required APIs, roles, or services. These can all be performed | |
manually before running the script if desired. To allow the script to perform | |
every necessary action, pass the -e|--enable_all flag. All of these flags | |
are incompatible with --only_validate. | |
-e|--enable_all Allow the script to perform all of the | |
individual enable actions below. (Environ | |
registration won't happen unless necessary | |
for a selected option.) | |
--enable_cluster_roles Allow the script to attempt to set | |
the necessary cluster roles. | |
--enable_cluster_labels Allow the script to attempt to set | |
necessary cluster labels. | |
--enable_gcp_apis Allow the script to enable GCP APIs on | |
the user's behalf | |
--enable_gcp_iam_roles Allow the script to set the required GCP | |
IAM permissions | |
--enable_gcp_components Allow the script to enable required GCP | |
managed services and components | |
--enable_registration Allow the script to register the cluster | |
to an environ | |
--enable_namespace_creation Allow the script to create the istio-system | |
namespace for the user | |
--managed Provision a remote, managed control plane | |
instead of installing one in-cluster. | |
--print_config Instead of installing ASM, print all of | |
the compiled YAML to stdout. All other | |
output will be written to stderr, even if | |
it would normally go to stdout. Skip all | |
validations and setup. | |
--disable_canonical_service Do not install the CanonicalService | |
controller. This is required for ASM UI to | |
support various features. | |
-v|--verbose Print commands before and after execution. | |
--dry_run Print commands, but don't execute them. | |
--only_validate Run validation, but don't install. | |
--only_enable Perform the specified steps to set up the | |
current user/cluster but don't install | |
anything. | |
-h|--help Show this message and exit. | |
--version Print the version of this tool and exit. | |
EXAMPLE: | |
The following invocation will install ASM to a cluster named "my_cluster" in | |
project "my_project" in region "us-central1-c" using the default "mesh_ca" as | |
the certificate authority: | |
$> ${SCRIPT_NAME} \\ | |
-n my_cluster \\ | |
-p my_project \\ | |
-l us-central1-c \\ | |
-m install | |
EOF | |
} | |
arg_required() { | |
if [[ ! "${2:-}" || "${2:0:1}" = '-' ]]; then | |
fatal "Option ${1} requires an argument." | |
fi | |
} | |
parse_args() { | |
if [[ "${*}" = '' ]]; then | |
usage_short >&2 | |
exit 2 | |
fi | |
# shellcheck disable=SC2064 | |
trap "$(shopt -p nocasematch)" RETURN | |
shopt -s nocasematch | |
while [[ $# != 0 ]]; do | |
case "${1}" in | |
-l | --cluster_location | --cluster-location) | |
arg_required "${@}" | |
CLUSTER_LOCATION="${2}" | |
shift 2 | |
;; | |
-n | --cluster_name | --cluster-name) | |
arg_required "${@}" | |
CLUSTER_NAME="${2}" | |
shift 2 | |
;; | |
-p | --project_id | --project-id) | |
arg_required "${@}" | |
PROJECT_ID="${2}" | |
shift 2 | |
;; | |
-m | --mode) | |
arg_required "${@}" | |
MODE="$(echo "${2}" | tr '[:upper:]' '[:lower:]')" | |
shift 2 | |
;; | |
-c | --ca) | |
arg_required "${@}" | |
CA="$(echo "${2}" | tr '[:upper:]' '[:lower:]')" | |
shift 2 | |
;; | |
--ca_name | --ca-name) | |
arg_required "${@}" | |
CA_NAME="${2}" | |
shift 2 | |
;; | |
-o | --option) | |
arg_required "${@}" | |
OPTIONAL_OVERLAY="${2},${OPTIONAL_OVERLAY}" | |
if [[ "${2}" == "hub-meshca" ]]; then | |
USE_HUB_WIP=1 | |
fi | |
if [[ "${2}" == "vm" ]]; then | |
USE_VM=1 | |
fi | |
shift 2 | |
;; | |
--co | --custom_overlay | --custom-overlay) | |
arg_required "${@}" | |
CUSTOM_OVERLAY="${2},${CUSTOM_OVERLAY}" | |
shift 2 | |
;; | |
-e | --enable_all | --enable-all) | |
ENABLE_ALL=1 | |
shift 1 | |
;; | |
--enable_cluster_roles | --enable-cluster-roles) | |
ENABLE_CLUSTER_ROLES=1 | |
shift 1 | |
;; | |
--enable_cluster_labels | --enable-cluster-labels) | |
ENABLE_CLUSTER_LABELS=1 | |
shift 1 | |
;; | |
--enable_gcp_apis | --enable-gcp-apis) | |
ENABLE_GCP_APIS=1 | |
shift 1 | |
;; | |
--enable_gcp_iam_roles | --enable-gcp-iam-roles) | |
ENABLE_GCP_IAM_ROLES=1 | |
shift 1 | |
;; | |
--enable_gcp_components | --enable-gcp-components) | |
ENABLE_GCP_COMPONENTS=1 | |
shift 1 | |
;; | |
--enable_registration | --enable-registration) | |
ENABLE_REGISTRATION=1 | |
shift 1 | |
;; | |
--enable_namespace_creation | --enable-namespace-creation) | |
ENABLE_NAMESPACE_CREATION=1 | |
shift 1 | |
;; | |
--managed) | |
MANAGED=1 | |
REVISION_LABEL="asm-managed" | |
shift 1 | |
;; | |
--disable_canonical_service | --disable-canonical-service) | |
DISABLE_CANONICAL_SERVICE=1 | |
shift 1 | |
;; | |
--print_config | --print-config) | |
PRINT_CONFIG=1 | |
shift 1 | |
;; | |
-s | --service_account | --service-account) | |
arg_required "${@}" | |
SERVICE_ACCOUNT="${2}" | |
shift 2 | |
;; | |
-k | --key_file | --key-file) | |
arg_required "${@}" | |
KEY_FILE="${2}" | |
shift 2 | |
;; | |
-D | --output_dir | --output-dir) | |
arg_required "${@}" | |
OUTPUT_DIR="${2}" | |
shift 2 | |
;; | |
--dry_run | --dry-run) | |
DRY_RUN=1 | |
shift 1 | |
;; | |
--only_validate | --only-validate) | |
ONLY_VALIDATE=1 | |
shift 1 | |
;; | |
--only_enable | --only-enable) | |
ONLY_ENABLE=1 | |
shift 1 | |
;; | |
--ca_cert | --ca-cert) | |
arg_required "${@}" | |
CA_CERT="${2}" | |
CUSTOM_CA=1 | |
shift 2 | |
;; | |
--ca_key | --ca-key) | |
arg_required "${@}" | |
CA_KEY="${2}" | |
CUSTOM_CA=1 | |
shift 2 | |
;; | |
--root_cert | --root-cert) | |
arg_required "${@}" | |
CA_ROOT="${2}" | |
CUSTOM_CA=1 | |
shift 2 | |
;; | |
--cert_chain | --cert-chain) | |
arg_required "${@}" | |
CA_CHAIN="${2}" | |
CUSTOM_CA=1 | |
shift 2 | |
;; | |
-r | --revision_name | --revision-name) | |
arg_required "${@}" | |
CUSTOM_REVISION=1 | |
REVISION_LABEL="${2}" | |
shift 2 | |
;; | |
-v | --verbose) | |
VERBOSE=1 | |
shift 1 | |
;; | |
-h | --help) | |
PRINT_HELP=1 | |
shift 1 | |
;; | |
--version) | |
PRINT_VERSION=1 | |
shift 1 | |
;; | |
*) | |
fatal_with_usage "Unknown option ${1}" | |
;; | |
esac | |
done | |
if [[ "${PRINT_HELP}" -eq 1 || "${PRINT_VERSION}" -eq 1 ]]; then | |
if [[ "${PRINT_VERSION}" -eq 1 ]]; then | |
version_message | |
elif [[ "${VERBOSE}" -eq 1 ]]; then | |
usage | |
else | |
usage_short | |
fi | |
exit | |
fi | |
readonly REVISION_LABEL | |
} | |
validate_args() { | |
if [[ "${MODE}" == "install" && -z "${CA}" ]]; then | |
CA="mesh_ca" | |
fi | |
if [[ "${CUSTOM_REVISION}" -eq 1 ]]; then | |
validate_revision_label | |
fi | |
if is_managed; then | |
if [[ "${MODE}" != "install" ]]; then | |
fatal "Migrate and upgrade are incompatible with managed control plane." | |
fi | |
if [[ "${CA}" == "citadel" ]]; then | |
fatal "Citadel is not supported with managed control plane." | |
fi | |
if [[ "${CA}" == "gcp_cas" ]]; then | |
fatal "Google Certificate Authority Service integration is not supported with managed control plane." | |
fi | |
if [[ "${CUSTOM_CA}" -eq 1 ]]; then | |
fatal "Specifying a custom CA with managed control plane is not supported." | |
fi | |
if [[ "${USE_VM}" -eq 1 ]]; then | |
fatal "Adding VM workloads with managed control plane is not supported." | |
fi | |
if [[ "${CUSTOM_REVISION}" -eq 1 ]]; then | |
fatal "Specifying a revision label with managed control plane is not supported." | |
fi | |
if [[ -n "${CUSTOM_OVERLAY}" ]]; then | |
fatal "Specifying a custom overlay file with managed control plane is not supported." | |
fi | |
fi | |
local MISSING_ARGS=0 | |
while read -r REQUIRED_ARG; do | |
if [[ -z "${!REQUIRED_ARG}" ]]; then | |
MISSING_ARGS=1 | |
warn "Missing value for ${REQUIRED_ARG}" | |
fi | |
readonly "${REQUIRED_ARG}" | |
done <<EOF | |
CLUSTER_LOCATION | |
CLUSTER_NAME | |
PROJECT_ID | |
MODE | |
EOF | |
if [[ "${MODE}" != "upgrade" ]]; then | |
case "${CA}" in | |
citadel | mesh_ca | gcp_cas);; | |
"") | |
MISSING_ARGS=1 | |
warn "Missing value for CA" | |
;; | |
*) fatal "CA must be one of 'citadel', 'mesh_ca', 'gcp_cas'";; | |
esac | |
fi | |
if [[ "${MISSING_ARGS}" -ne 0 ]]; then | |
fatal_with_usage "Missing one or more required options." | |
fi | |
if [[ "${MODE}" == "upgrade" && -n "${CA}" ]]; then | |
warn "The CA flag is ignored during an update operation." | |
warn "Update will proceed with the currently installed CA." | |
fi | |
if [[ "${CA}" = "gcp_cas" ]]; then | |
validate_gcp_cas_args | |
fi | |
# shellcheck disable=SC2064 | |
case "${MODE}" in | |
install | migrate | upgrade);; | |
*) fatal "MODE must be one of 'install', 'migrate', 'upgrade'";; | |
esac | |
if [[ "${MODE}" != "install" && "${USE_HUB_WIP}" -eq 1 ]]; then | |
fatal "Hub Workload Identity Pool is only supported in new installation" | |
fi | |
if [[ "${CA}" == "citadel" && "${USE_HUB_WIP}" -eq 1 ]]; then | |
fatal "Hub Workload Identity Pool is only supported for Mesh CA" | |
fi | |
if [[ "${USE_VM}" -eq 1 && "${USE_HUB_WIP}" -eq 0 ]]; then | |
fatal "Hub Workload Identity Pool is required to add VM workloads. Run the script with the -o hub-meshca option." | |
fi | |
if [[ "${CUSTOM_CA}" -eq 1 ]]; then | |
validate_certs | |
fi | |
while read -r FLAG; do | |
if [[ "${!FLAG}" -ne 0 && "${!FLAG}" -ne 1 ]]; then | |
fatal "${FLAG} must be 0 (off) or 1 (on) if set via environment variables." | |
fi | |
readonly "${FLAG}" | |
done <<EOF | |
DRY_RUN | |
ENABLE_ALL | |
ENABLE_CLUSTER_ROLES | |
ENABLE_CLUSTER_LABELS | |
ENABLE_GCP_APIS | |
ENABLE_GCP_IAM_ROLES | |
ENABLE_GCP_COMPONENTS | |
ENABLE_REGISTRATION | |
MANAGED | |
DISABLE_CANONICAL_SERVICE | |
ONLY_VALIDATE | |
ONLY_ENABLE | |
VERBOSE | |
EOF | |
if [[ "${ENABLE_ALL}" -eq 1 || "${ENABLE_CLUSTER_ROLES}" -eq 1 || \ | |
"${ENABLE_CLUSTER_LABELS}" -eq 1 || "${ENABLE_GCP_APIS}" -eq 1 || \ | |
"${ENABLE_GCP_IAM_ROLES}" -eq 1 || "${ENABLE_GCP_COMPONENTS}" -eq 1 || \ | |
"${ENABLE_REGISTRATION}" -eq 1 || "${ENABLE_NAMESPACE_CREATION}" -eq 1 ]]; then | |
if [[ "${ONLY_VALIDATE}" -eq 1 ]]; then | |
fatal "The only_validate flag cannot be used with any --enable* flag" | |
fi | |
elif only_enable; then | |
fatal "You must specify at least one --enable* flag with --only_enable" | |
fi | |
if [[ -n "$SERVICE_ACCOUNT" && -z "$KEY_FILE" || -z "$SERVICE_ACCOUNT" && -n "$KEY_FILE" ]]; then | |
fatal "Service account and key file must be used together." | |
fi | |
# since we cd to a tmp directory, we need the absolute path for the key file | |
# and yaml file | |
if [[ -f "${KEY_FILE}" ]]; then | |
KEY_FILE="$(apath -f "${KEY_FILE}")" | |
readonly KEY_FILE | |
elif [[ -n "${KEY_FILE}" ]]; then | |
fatal "Couldn't find key file ${KEY_FILE}." | |
fi | |
while read -d ',' -r yaml_file; do | |
if [[ -f "${yaml_file}" ]]; then | |
ABS_OVERLAYS="$(apath -f "${yaml_file}"),${ABS_OVERLAYS}" | |
elif [[ -n "${yaml_file}" ]]; then | |
fatal "Couldn't find yaml file ${yaml_file}." | |
fi | |
done <<EOF | |
${CUSTOM_OVERLAY} | |
EOF | |
if [[ "${CA}" = "citadel" ]]; then | |
ABS_OVERLAYS="${OPTIONS_DIRECTORY}/citadel-ca.yaml,${ABS_OVERLAYS}" | |
elif [[ "${CA}" = "gcp_cas" ]]; then | |
ABS_OVERLAYS="${OPTIONS_DIRECTORY}/private-ca.yaml,${ABS_OVERLAYS}" | |
fi | |
CUSTOM_OVERLAY="${ABS_OVERLAYS}" | |
set_kpt_package_url | |
WORKLOAD_POOL="${PROJECT_ID}.svc.id.goog" | |
} | |
validate_revision_label() { | |
# Matches DNS label formats of RFC 1123 | |
local DNS_1123_LABEL_MAX_LEN; DNS_1123_LABEL_MAX_LEN=63; | |
readonly DNS_1123_LABEL_MAX_LEN | |
local DNS_1123_LABEL_FMT; DNS_1123_LABEL_FMT="^[a-zA-Z0-9]([-a-zA-Z0-9]*[a-zA-Z0-9])?$"; | |
readonly DNS_1123_LABEL_FMT | |
if [[ ${#REVISION_LABEL} -gt ${DNS_1123_LABEL_MAX_LEN} ]]; then | |
fatal "Revision label cannot be longer than $DNS_1123_LABEL_MAX_LEN." | |
fi | |
if ! [[ "${REVISION_LABEL}" =~ ${DNS_1123_LABEL_FMT} ]]; then | |
fatal "Revision label does not follow RFC 1123 formatting convention." | |
fi | |
} | |
validate_gcp_cas_args() { | |
local CA_NAME_TEMPLATE | |
CA_NAME_TEMPLATE="projects/project_name/locations/ca_region/certificateAuthorities/ca_name" | |
readonly CA_NAME_TEMPLATE | |
if [[ -z ${CA_NAME} ]]; then | |
fatal "A ca-name must be provided for integration with Google Certificate Authority Service." | |
elif [[ $(grep -o "/" <<< "${CA_NAME}" | wc -l) != $(grep -o "/" <<< "${CA_NAME_TEMPLATE}" | wc -l) ]]; then | |
fatal "Malformed ca-name. ca-name must be of the form ${CA_NAME_TEMPLATE}." | |
elif [[ "$(echo "${CA_NAME}" | cut -f1 -d/)" != "$(echo "${CA_NAME_TEMPLATE}" | cut -f1 -d/)" ]]; then | |
fatal "Malformed ca-name. ca-name must be of the form ${CA_NAME_TEMPLATE}." | |
fi | |
} | |
validate_certs() { | |
if [[ "${CA}" != "citadel" ]]; then | |
fatal "You must select Citadel as the CA in order to use custom certificates." | |
fi | |
if [[ -z "${CA_ROOT}" || -z "${CA_KEY}" || -z "${CA_CHAIN}" || -z "${CA_CERT}" ]]; then | |
fatal "All four certificate options must be present to use a custom cert." | |
fi | |
while read -r CERT_FILE; do | |
if ! [[ -f "${!CERT_FILE}" ]]; then | |
fatal "Couldn't find file ${!CERT_FILE}." | |
fi | |
done <<EOF | |
CA_CERT | |
CA_ROOT | |
CA_KEY | |
CA_CHAIN | |
EOF | |
CA_CERT="$(apath -f "${CA_CERT}")"; readonly CA_CERT; | |
CA_KEY="$(apath -f "${CA_KEY}")"; readonly CA_KEY; | |
CA_CHAIN="$(apath -f "${CA_CHAIN}")"; readonly CA_CHAIN; | |
CA_ROOT="$(apath -f "${CA_ROOT}")"; readonly CA_ROOT; | |
info "Checking certificate files for consistency..." | |
if ! openssl rsa -in "${CA_KEY}" -check >/dev/null 2>/dev/null; then | |
fatal "${CA_KEY} failed an openssl consistency check." | |
fi | |
if ! openssl x509 -in "${CA_CERT}" -text -noout >/dev/null; then | |
fatal "${CA_CERT} failed an openssl consistency check." | |
fi | |
if ! openssl x509 -in "${CA_CHAIN}" -text -noout >/dev/null; then | |
fatal "${CA_CHAIN} failed an openssl consistency check." | |
fi | |
if ! openssl x509 -in "${CA_ROOT}" -text -noout >/dev/null; then | |
fatal "${CA_ROOT} failed an openssl consistency check." | |
fi | |
info "Checking key matches certificate..." | |
local CERT_HASH; local KEY_HASH; | |
CERT_HASH="$(openssl x509 -noout -modulus -in "${CA_CERT}" | openssl md5)"; | |
KEY_HASH="$(openssl rsa -noout -modulus -in "${CA_KEY}" | openssl md5)"; | |
if [[ "${CERT_HASH}" != "${KEY_HASH}" ]]; then | |
fatal "Keyfile does not match the given certificate." | |
fatal "Cert: ${CA_CERT}" | |
fatal "Key: ${CA_KEY}" | |
fi | |
unset CERT_HASH; unset KEY_HASH; | |
info "Verifying certificate chain of trust..." | |
if ! openssl verify -trusted "${CA_ROOT}" -untrusted "${CA_CHAIN}" "${CA_CERT}"; then | |
fatal "Unable to verify chain of trust." | |
fi | |
} | |
set_kpt_package_url() { | |
KPT_URL="https://github.com/GoogleCloudPlatform/anthos-service-mesh-packages" | |
KPT_URL="${KPT_URL}.git/asm@${KPT_BRANCH}" | |
readonly KPT_URL; | |
} | |
auth_service_account() { | |
info "Authorizing ${SERVICE_ACCOUNT} with ${KEY_FILE}..." | |
gcloud auth activate-service-account \ | |
--project="${PROJECT_ID}" \ | |
"${SERVICE_ACCOUNT}" \ | |
--key-file="${KEY_FILE}" | |
} | |
####### | |
# set_up_local_workspace does everything that the script needs to avoid | |
# polluting the environment or current working directory | |
####### | |
set_up_local_workspace() { | |
info "Setting up necessary files..." | |
if [[ -z "${OUTPUT_DIR}" ]]; then | |
info "Creating temp directory..." | |
OUTPUT_DIR="$(mktemp -d)" | |
if [[ -z "${OUTPUT_DIR}" ]]; then | |
fatal "Encountered error when running mktemp -d!" | |
fi | |
info "" | |
info "$(starline)" | |
info "No output folder was specified with --output_dir|-D, so configuration and" | |
info "binaries will be stored in the following directory." | |
info "${OUTPUT_DIR}" | |
info "$(starline)" | |
info "" | |
sleep 2 | |
else | |
OUTPUT_DIR="$(apath -f "${OUTPUT_DIR}")" | |
if [[ ! -a "${OUTPUT_DIR}" ]]; then | |
if ! mkdir -p "${OUTPUT_DIR}"; then | |
fatal "Failed to create directory ${OUTPUT_DIR}" | |
fi | |
elif [[ ! -d "${OUTPUT_DIR}" ]]; then | |
fatal "${OUTPUT_DIR} exists and is not a directory, please specify another directory." | |
fi | |
fi | |
pushd "$OUTPUT_DIR" > /dev/null | |
KUBECONFIG="asm_kubeconfig" | |
export KUBECONFIG | |
} | |
### Environment validation functions ### | |
validate_dependencies() { | |
validate_node_pool | |
validate_k8s | |
validate_expected_control_plane | |
if [[ "${MODE}" = "migrate" ]]; then | |
validate_istio_version | |
elif [[ "${MODE}" = "upgrade" ]]; then | |
validate_asm_version | |
validate_ca_consistency | |
fi | |
} | |
organize_kpt_files() { | |
local ABS_YAML | |
while read -d ',' -r yaml_file; do | |
ABS_YAML="${OPTIONS_DIRECTORY}/${yaml_file}.yaml" | |
if [[ ! -f "${ABS_YAML}" ]]; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
Couldn't find yaml file ${yaml_file}. | |
See directory $(apath -f "${OPTIONS_DIRECTORY}") for available options. | |
EOF | |
fi | |
CUSTOM_OVERLAY="${ABS_YAML},${CUSTOM_OVERLAY}" | |
done <<EOF | |
${OPTIONAL_OVERLAY} | |
EOF | |
unset OPTIONAL_OVERLAY | |
} | |
# This is a workaround for https://github.com/istio/istio/issues/30632 | |
# which doesn't handle files with multiple operator specs correctly. | |
# This will split all of the files based off of the yaml document separator. | |
handle_multi_yaml_bug() { | |
local CSPLIT_OUTPUT; CSPLIT_OUTPUT=""; | |
local BASE_NAME | |
while read -d ',' -r yaml_file; do | |
BASE_NAME="$(basename "${yaml_file}")" | |
if [[ "$(csplit -f "overlay-${BASE_NAME}" "${yaml_file}" '/^---$/' '{*}' | wc -l)" -eq 1 ]]; then | |
CSPLIT_OUTPUT="${CSPLIT_OUTPUT},${yaml_file}" | |
else | |
for split_file in overlay-"${BASE_NAME}"*; do | |
if [[ -s "${split_file}" ]]; then | |
CSPLIT_OUTPUT="${CSPLIT_OUTPUT},$(apath -f "${split_file}")" | |
fi | |
done | |
fi | |
done <<EOF | |
${CUSTOM_OVERLAY} | |
EOF | |
if [[ -n "${CSPLIT_OUTPUT}" ]]; then | |
CUSTOM_OVERLAY="${CSPLIT_OUTPUT:1:${#CSPLIT_OUTPUT}}," | |
fi | |
} | |
validate_cli_dependencies() { | |
local NOTFOUND; NOTFOUND=""; | |
local EXITCODE; EXITCODE=0; | |
info "Checking installation tool dependencies..." | |
while read -r dependency; do | |
EXITCODE=0 | |
hash "${dependency}" 2>/dev/null || EXITCODE=$? | |
if [[ "${EXITCODE}" -ne 0 ]]; then | |
NOTFOUND="${dependency},${NOTFOUND}" | |
fi | |
done <<EOF | |
awk | |
$AGCLOUD | |
grep | |
jq | |
$AKUBECTL | |
$AKPT | |
sed | |
tr | |
head | |
csplit | |
EOF | |
while read -r FLAG; do | |
if [[ -z "${!FLAG}" ]]; then | |
NOTFOUND="${FLAG},${NOTFOUND}" | |
fi | |
done <<EOF | |
AKUBECTL | |
AGCLOUD | |
AKPT | |
EOF | |
if [[ "${CUSTOM_CA}" -eq 1 ]]; then | |
EXITCODE=0 | |
hash openssl 2>/dev/null || EXITCODE=$? | |
if [[ "${EXITCODE}" -ne 0 ]]; then | |
NOTFOUND="openssl,${NOTFOUND}" | |
fi | |
fi | |
if [[ "${#NOTFOUND}" -gt 1 ]]; then | |
NOTFOUND="$(strip_trailing_commas "${NOTFOUND}")" | |
for dep in $(echo "${NOTFOUND}" | tr ' ' '\n'); do | |
warn "Dependency not found: ${dep}" | |
done | |
fatal "One or more dependencies were not found. Please install them and retry." | |
fi | |
local OS | |
# shellcheck disable=SC2064 | |
trap "$(shopt -p nocasematch)" RETURN | |
shopt -s nocasematch | |
if [[ "$(uname -m)" != "x86_64" ]]; then | |
fatal "Installation is only supported on x86_64." | |
fi | |
} | |
download_asm() { | |
local OS | |
case "$(uname)" in | |
Linux ) OS="linux-amd64";; | |
Darwin) OS="osx";; | |
* ) fatal "$(uname) is not a supported OS.";; | |
esac | |
info "Downloading ASM.." | |
local TARBALL; TARBALL="istio-${RELEASE}-${OS}.tar.gz" | |
if [[ -z "${_CI_ASM_PKG_LOCATION}" ]]; then | |
curl -L "https://storage.googleapis.com/gke-release/asm/${TARBALL}" \ | |
| tar xz | |
else | |
local TOKEN; TOKEN="$(retry 2 gcloud --project="${PROJECT_ID}" auth print-access-token)" | |
run curl -L "https://storage.googleapis.com/${_CI_ASM_PKG_LOCATION}/asm/${TARBALL}" \ | |
--header @- <<EOF | tar xz | |
Authorization: Bearer ${TOKEN} | |
EOF | |
fi | |
ln -s "${ISTIOCTL_REL_PATH}" . | |
info "Downloading ASM kpt package..." | |
retry 3 kpt pkg get --auto-set=false "${KPT_URL}" asm | |
version_message > "${ASM_VERSION_FILE}" | |
} | |
necessary_files_exist() { | |
if [[ ! -f "${OUTPUT_DIR}/${ISTIOCTL_REL_PATH}" ]]; then | |
false | |
return | |
elif [[ ! -f "${OUTPUT_DIR}/${OPERATOR_MANIFEST}" ]]; then | |
false | |
return | |
fi | |
# Refuse to overwrite configuration downloaded from a different version | |
if [[ ! -f "${OUTPUT_DIR}/${ASM_VERSION_FILE}" ]]; then | |
warn "Re-using existing configuration in ${OUTPUT_DIR}." | |
warn_pause "If this was unintentional, please re-run with a different output directory." | |
return | |
fi | |
local EXISTING_VER; EXISTING_VER="$(cat "${ASM_VERSION_FILE}")"; | |
if [[ "${EXISTING_VER}" != "$(version_message)" ]]; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
The existing configuration in ${OUTPUT_DIR} is from a different version. | |
Existing: ${EXISTING_VER} | |
Current: $(version_message) | |
Please try again and specify a different output directory. | |
EOF | |
fi | |
} | |
validate_gcp_resources() { | |
validate_cluster | |
} | |
populate_cluster_values() { | |
if [[ -z "${GKE_CLUSTER_URI}" && -z "${GCE_NETWORK_NAME}" ]]; then | |
CLUSTER_DATA="$(retry 2 gcloud container clusters describe "${CLUSTER_NAME}" \ | |
--zone="${CLUSTER_LOCATION}" \ | |
--project="${PROJECT_ID}" \ | |
--format='value(selfLink, network)')" | |
read -r GKE_CLUSTER_URI GCE_NETWORK_NAME <<EOF | |
${CLUSTER_DATA} | |
EOF | |
GCE_NETWORK_NAME="${PROJECT_ID}-${GCE_NETWORK_NAME}" | |
readonly GKE_CLUSTER_URI; readonly GCE_NETWORK_NAME; | |
fi | |
} | |
get_project_number() { | |
local RESULT; RESULT="" | |
info "Checking for project ${PROJECT_ID}..." | |
PROJECT_NUMBER="$(gcloud projects describe "${PROJECT_ID}" --format="value(projectNumber)")" | |
if [[ -z "${PROJECT_NUMBER}" ]]; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
Unable to find project ${PROJECT_ID}. Please verify the spelling and try | |
again. To see a list of your projects, run: | |
gcloud projects list --format='value(project_id)' | |
EOF | |
fi | |
} | |
validate_cluster() { | |
local RESULT; RESULT="" | |
info "Confirming cluster information for ${PROJECT_ID}/${CLUSTER_LOCATION}/${CLUSTER_NAME}..." | |
RESULT="$(gcloud container clusters list \ | |
--project="${PROJECT_ID}" \ | |
--filter="name = ${CLUSTER_NAME} AND location = ${CLUSTER_LOCATION}" \ | |
--format="value(name)" || true)" | |
if [[ -z "${RESULT}" ]]; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
Unable to find cluster ${CLUSTER_LOCATION}/${CLUSTER_NAME}. | |
Please verify the spelling and try again. To see a list of your clusters, in | |
this project, run: | |
gcloud container clusters list --format='value(name,zone)' --project="${PROJECT_ID}" | |
EOF | |
fi | |
} | |
validate_k8s() { | |
K8S_MINOR="$(kubectl version -o json | jq .serverVersion.minor | sed 's/[^0-9]//g')" | |
if [[ "${K8S_MINOR}" -lt 15 ]]; then | |
fatal "ASM ${RELEASE} requires Kubernetes version 1.15+, found 1.${K8S_MINOR}" | |
fi | |
} | |
list_valid_pools() { | |
gcloud container node-pools list \ | |
--project="${PROJECT_ID}" \ | |
--region "${CLUSTER_LOCATION}" \ | |
--cluster "${CLUSTER_NAME}" \ | |
--filter "$(valid_pool_query "${1}")"\ | |
--format=json | |
} | |
####### | |
# valid_pool_query takes an integer argument: the minimum vCPU requirement. | |
# It outputs to stdout a query for `gcloud container node-pools list` | |
####### | |
valid_pool_query() { | |
cat <<EOF | tr '\n' ' ' | |
config.machineType.split(sep="-").slice(-1:) >= $1 | |
EOF | |
} | |
####### | |
# validate_node_pool makes sure that the cluster meets ASM's minimum compute | |
# requirements | |
####### | |
validate_node_pool() { | |
local MACHINE_CPU_REQ; MACHINE_CPU_REQ=4; readonly MACHINE_CPU_REQ; | |
local TOTAL_CPU_REQ; TOTAL_CPU_REQ=8; readonly TOTAL_CPU_REQ; | |
info "Confirming node pool requirements for ${PROJECT_ID}/${CLUSTER_LOCATION}/${CLUSTER_NAME}..." | |
local ACTUAL_CPU | |
ACTUAL_CPU="$(list_valid_pools "${MACHINE_CPU_REQ}" | \ | |
jq '.[] | | |
(if .autoscaling.enabled then .autoscaling.maxNodeCount else .initialNodeCount end) | |
* | |
(.config.machineType / "-" | .[-1] | try tonumber catch 1) | |
* (.locations | length) | |
' 2>/dev/null)" || true | |
local MAX_CPU; MAX_CPU=0; | |
for i in ${ACTUAL_CPU}; do | |
MAX_CPU="$(( i > MAX_CPU ? i : MAX_CPU))" | |
done | |
if [[ "$MAX_CPU" -lt "$TOTAL_CPU_REQ" ]]; then | |
{ read -r -d '' MSG; warn_pause "${MSG}"; } <<EOF || true | |
ASM requires you to have at least ${TOTAL_CPU_REQ} vCPUs in node pools whose | |
machine type is at least ${MACHINE_CPU_REQ} vCPUs. | |
${CLUSTER_LOCATION}/${CLUSTER_NAME} does not meet this requirement. ASM | |
may not function as expected. | |
EOF | |
fi | |
} | |
validate_expected_control_plane(){ | |
info "Checking Istio installations..." | |
check_no_istiod_outside_of_istio_system_namespace | |
if [[ "${MODE}" = "migrate" || "${MODE}" = "upgrade" ]]; then | |
check_istio_deployed | |
elif [[ "${MODE}" = "install" ]]; then | |
check_istio_not_deployed | |
fi | |
} | |
check_no_istiod_outside_of_istio_system_namespace() { | |
local IN_ANY_NAMESPACE IN_NAMESPACE | |
IN_ANY_NAMESPACE="$(kubectl get deployment -A --ignore-not-found=true | grep -c istiod || true)"; | |
IN_NAMESPACE="$(kubectl get deployment -n istio-system --ignore-not-found=true | grep -c istiod || true)"; | |
if [ "$IN_ANY_NAMESPACE" -gt "$IN_NAMESPACE" ]; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
found istiod deployment outside of istio-system namespace. This installer | |
does not support that configuration. | |
EOF | |
fi | |
} | |
get_istio_deployment_count(){ | |
local OUTPUT | |
OUTPUT="$(retry 3 kubectl get deployment \ | |
-n istio-system \ | |
--ignore-not-found=true)" | |
grep -c istiod <<EOF || true | |
$OUTPUT | |
EOF | |
} | |
check_istio_deployed(){ | |
local ISTIOD_COUNT; ISTIOD_COUNT="$(get_istio_deployment_count)"; | |
info "Found ${ISTIOD_COUNT} deployment(s)." | |
if [[ "$ISTIOD_COUNT" -eq 0 ]]; then | |
warn_pause "${MODE} mode specified but no istiod deployment found. (Expected >=1.)" | |
fi | |
} | |
check_istio_not_deployed(){ | |
local ISTIOD_COUNT; ISTIOD_COUNT="$(get_istio_deployment_count)"; | |
if [[ "$ISTIOD_COUNT" -ne 0 ]]; then | |
{ read -r -d '' MSG; warn_pause "${MSG}"; } <<EOF || true | |
Install mode specified, but ${ISTIOD_COUNT} existing istiod deployment(s) found. (Expected 0.) | |
Installation may overwrite existing control planes with the same revision. | |
EOF | |
fi | |
} | |
validate_istio_version() { | |
info "Checking existing Istio version(s)..." | |
local VERSION_OUTPUT; VERSION_OUTPUT="$(retry 3 istioctl version -o json)" | |
if [[ -z "${VERSION_OUTPUT}" ]]; then | |
fatal "Couldn't validate existing Istio versions." | |
fi | |
local FOUND_VALID_VERSION; FOUND_VALID_VERSION=0 | |
for version in $(echo "${VERSION_OUTPUT}" | jq -r '.meshVersion[].Info.version' -r); do | |
if [[ "$version" =~ ^$RELEASE_LINE || "$version" =~ ^$PREVIOUS_RELEASE_LINE ]]; then | |
info " $version (suitable for migration)" | |
FOUND_VALID_VERSION=1 | |
else | |
info " $version (not suitable for migration)" | |
fi | |
if [[ "$version" =~ "asm" ]]; then | |
fatal "Cannot migrate from version $version. Only migration from OSS Istio to the ASM distribution is supported." | |
fi | |
done | |
if [[ "$FOUND_VALID_VERSION" -eq 0 ]]; then | |
fatal "Migration requires an existing control plane in the ${RELEASE_LINE} line." | |
fi | |
} | |
validate_asm_version() { | |
info "Checking existing ASM version(s)..." | |
local VERSION_OUTPUT; VERSION_OUTPUT="$(retry 3 istioctl version -o json)" | |
if [[ -z "${VERSION_OUTPUT}" ]]; then | |
fatal "Couldn't validate existing Istio versions." | |
fi | |
local FOUND_INVALID_VERSION; FOUND_INVALID_VERSION=0 | |
for VERSION in $(echo "${VERSION_OUTPUT}" | jq -r '.meshVersion[].Info.version'); do | |
if ! [[ "${VERSION}" =~ "asm" ]]; then | |
fatal "Cannot upgrade from version ${VERSION}. Only upgrades from ASM distributions are supported." | |
fi | |
if version_valid_for_upgrade "${VERSION}"; then | |
info " ${VERSION} (suitable for migration)" | |
else | |
info " ${VERSION} (not suitable for migration)" | |
FOUND_INVALID_VERSION=1 | |
fi | |
done | |
if [[ "$FOUND_INVALID_VERSION" -eq 1 ]]; then | |
fatal "Upgrade requires all existing control planes to be between versions 1.$((MINOR-1)).0 (inclusive) and ${RELEASE} (exclusive)." | |
fi | |
} | |
version_valid_for_upgrade() { | |
local VERSION; VERSION=$1 | |
# if asm version found, pattern: 1.6.11-asm.1-586f900508ad482ed32b830dd15f6c54b32b93ed | |
local VERSION_MAJOR VERSION_MINOR VERSION_POINT VERSION_REV | |
IFS="." read -r VERSION_MAJOR VERSION_MINOR VERSION_POINT VERSION_REV <<EOF | |
${VERSION} | |
EOF | |
VERSION_POINT="$(sed 's/-.*//' <<EOF | |
${VERSION_POINT} | |
EOF | |
)" | |
VERSION_REV="$(sed 's/-.*//' <<EOF | |
${VERSION_REV} | |
EOF | |
)" | |
if is_major_minor_invalid || is_minor_point_rev_invalid; then | |
false | |
fi | |
} | |
is_major_minor_invalid() { | |
[[ "$VERSION_MAJOR" -ne 1 ]] && return 0 | |
[[ "$VERSION_MINOR" -lt $((MINOR-1)) ]] && return 0 | |
[[ "$VERSION_MINOR" -gt "$MINOR" ]] && return 0 | |
} | |
is_minor_point_rev_invalid() { | |
[[ "$VERSION_MINOR" -eq "$MINOR" ]] && is_point_rev_invalid && return 0 | |
} | |
is_point_rev_invalid() { | |
[[ "$VERSION_POINT" -gt "$POINT" ]] && return 0 | |
is_rev_invalid && return 0 | |
} | |
is_rev_invalid() { | |
[[ "$VERSION_POINT" -eq "$POINT" && "$VERSION_REV" -ge "$REV" ]] && return 0 | |
} | |
validate_ca_consistency() { | |
local CURRENT_CA; CURRENT_CA="citadel" | |
if is_meshca_installed; then | |
CURRENT_CA="mesh_ca" | |
elif is_gcp_cas_installed; then | |
CURRENT_CA="gcp_cas" | |
fi | |
info "CA already in use: ${CURRENT_CA}" | |
if [[ -z "${CA}" ]]; then | |
CA="${CURRENT_CA}" | |
fi | |
if [[ "${CA}" != "${CURRENT_CA}" ]]; then | |
fatal "CA cannot be switched while performing upgrade. Please use ${CURRENT_CA} as the CA." | |
fi | |
} | |
is_meshca_installed() { | |
local INSTALLED_CA; INSTALLED_CA="$(kubectl -n istio-system get pod -l istio=ingressgateway \ | |
-o jsonpath='{.items[].spec.containers[].env[?(@.name=="CA_ADDR")].value}')" | |
[[ "${INSTALLED_CA}" =~ meshca\.googleapis\.com ]] && return 0 | |
} | |
is_gcp_cas_installed() { | |
local INSTALLED_CA; INSTALLED_CA="$(kubectl -n istio-system get pod -l istio=istiod \ | |
-o jsonpath='{.items[].spec.containers[].env[?(@.name=="EXTERNAL_CA")].value}')" | |
[[ "${INSTALLED_CA}" = "ISTIOD_RA_CAS_API" ]] && return 0 | |
} | |
bind_user_to_iam_policy(){ | |
local ROLES; ROLES="${1}" | |
local GCLOUD_MEMBER; GCLOUD_MEMBER="${2}" | |
info "Binding ${GCLOUD_MEMBER} to required IAM roles..." | |
while read -r role; do | |
retry 3 gcloud projects add-iam-policy-binding "${PROJECT_ID}" \ | |
--member "${GCLOUD_MEMBER}" \ | |
--role="${role}" --condition=None >/dev/null | |
done <<EOF | |
${ROLES} | |
EOF | |
} | |
exit_if_out_of_iam_policy() { | |
local GCLOUD_MEMBER; GCLOUD_MEMBER="$(iam_user)"; | |
local MEMBER_ROLES | |
MEMBER_ROLES="$(gcloud projects \ | |
get-iam-policy "${PROJECT_ID}" \ | |
--flatten='bindings[].members' \ | |
--filter="bindings.members:$(iam_user)" \ | |
--format='value(bindings.role)')" | |
if [[ "${MEMBER_ROLES}" = *"roles/owner"* ]]; then | |
return | |
fi | |
local REQUIRED; REQUIRED="$(required_iam_roles)"; | |
local NOTFOUND; NOTFOUND="$(find_missing_strings "${REQUIRED}" "${MEMBER_ROLES}")" | |
if [[ -n "${NOTFOUND}" ]]; then | |
for role in $(echo "${NOTFOUND}" | tr ',' '\n'); do | |
warn "IAM role not enabled - ${role}" | |
done | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
One or more IAM roles required to install ASM is missing. Please add | |
${GCLOUD_MEMBER} to the roles above, or run | |
the script with "--enable_gcp_iam_roles" to allow the script to add | |
them on your behalf. | |
$(enable_common_message) | |
EOF | |
fi | |
} | |
iam_user() { | |
if ! is_managed; then | |
local_iam_user | |
return | |
fi | |
echo "serviceAccount:${MANAGED_SERVICE_ACCOUNT}" | |
} | |
local_iam_user() { | |
if [[ -n "${GCLOUD_USER_OR_SA}" ]]; then | |
echo "${GCLOUD_USER_OR_SA}" | |
return | |
fi | |
info "Getting account information..." | |
local ACCOUNT_NAME | |
ACCOUNT_NAME="$(retry 3 gcloud auth list \ | |
--project="${PROJECT_ID}" \ | |
--filter="status:ACTIVE" \ | |
--format="value(account)")" | |
if [[ -z "${ACCOUNT_NAME}" ]]; then | |
fatal "Failed to get account name from gcloud. Please authorize and re-try installation." | |
fi | |
local ACCOUNT_TYPE | |
ACCOUNT_TYPE="user" | |
if is_sa || [[ "${ACCOUNT_NAME}" = *.gserviceaccount.com ]]; then | |
ACCOUNT_TYPE="serviceAccount" | |
fi | |
GCLOUD_USER_OR_SA="${ACCOUNT_TYPE}:${ACCOUNT_NAME}" | |
echo "${GCLOUD_USER_OR_SA}" | |
} | |
required_iam_roles_mcp_sa() { | |
cat <<EOF | |
roles/serviceusage.serviceUsageConsumer | |
roles/container.admin | |
roles/monitoring.metricWriter | |
roles/logging.logWriter | |
roles/gkehub.gatewayAdmin | |
roles/gkehub.viewer | |
EOF | |
} | |
# [START required_iam_roles] | |
required_iam_roles() { | |
# meshconfig.admin - required for init, stackdriver, UI elements, etc. | |
# servicemanagement.admin/serviceusage.serviceUsageAdmin - enables APIs | |
if can_modify_gcp_components || \ | |
can_modify_cluster_labels || \ | |
can_modify_cluster_roles; then | |
echo roles/container.admin | |
fi | |
if can_modify_gcp_components; then | |
echo roles/meshconfig.admin | |
fi | |
if can_modify_gcp_apis; then | |
echo roles/servicemanagement.admin | |
echo roles/serviceusage.serviceUsageAdmin | |
fi | |
if can_modify_gcp_iam_roles; then | |
echo roles/resourcemanager.projectIamAdmin | |
fi | |
if is_sa; then | |
echo roles/iam.serviceAccountAdmin | |
fi | |
if can_register_cluster; then | |
echo roles/gkehub.admin | |
fi | |
if [[ "${CA}" = "gcp_cas" ]]; then | |
echo roles/privateca.admin | |
fi | |
if [[ "${_CI_I_AM_A_TEST_ROBOT}" -eq 1 ]]; then | |
echo roles/compute.admin | |
echo roles/iam.serviceAccountKeyAdmin | |
fi | |
} | |
# [END required_iam_roles] | |
# [START required_apis] | |
required_apis() { | |
cat << EOF | |
container.googleapis.com | |
monitoring.googleapis.com | |
logging.googleapis.com | |
cloudtrace.googleapis.com | |
meshtelemetry.googleapis.com | |
meshconfig.googleapis.com | |
iamcredentials.googleapis.com | |
gkeconnect.googleapis.com | |
gkehub.googleapis.com | |
cloudresourcemanager.googleapis.com | |
stackdriver.googleapis.com | |
EOF | |
case "${CA}" in | |
mesh_ca) | |
echo meshca.googleapis.com | |
;; | |
gcp_cas) | |
echo privateca.googleapis.com | |
;; | |
*);; | |
esac | |
if [[ "${_CI_I_AM_A_TEST_ROBOT}" -eq 1 ]]; then | |
echo compute.googleapis.com | |
fi | |
} | |
# [END required_apis] | |
enable_gcloud_apis(){ | |
info "Enabling required APIs..." | |
# shellcheck disable=SC2046 | |
retry 3 gcloud services enable --project="${PROJECT_ID}" $(required_apis | tr '\n' ' ') | |
} | |
get_enabled_apis() { | |
local OUTPUT | |
OUTPUT="$(retry 3 gcloud services list \ | |
--enabled \ | |
--format='get(config.name)' \ | |
--project="${PROJECT_ID}")" | |
echo "${OUTPUT}" | tr '\n' ',' | |
} | |
exit_if_apis_not_enabled() { | |
local ENABLED; ENABLED="$(get_enabled_apis)"; | |
local REQUIRED; REQUIRED="$(required_apis)"; | |
local NOTFOUND; NOTFOUND=""; | |
info "Checking required APIs..." | |
NOTFOUND="$(find_missing_strings "${REQUIRED}" "${ENABLED}")" | |
if [[ -n "${NOTFOUND}" ]]; then | |
for api in $(echo "${NOTFOUND}" | tr ' ' '\n'); do | |
warn "API not enabled - ${api}" | |
done | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
One or more APIs are not enabled. Please enable them and retry, or run the | |
script with the '--enable_gcp_apis' flag to allow the script to enable them on | |
your behalf. | |
$(enable_common_message) | |
EOF | |
fi | |
} | |
init_meshconfig() { | |
info "Initializing meshconfig API..." | |
if [[ "${USE_HUB_WIP}" -eq 1 ]]; then | |
populate_environ_info | |
info "Cluster has Membership ID ${HUB_MEMBERSHIP_ID} in the Hub of project ${ENVIRON_PROJECT_ID}" | |
if [[ "${ENVIRON_PROJECT_ID}" != "${PROJECT_ID}" ]]; then | |
info "Skip initializing meshconfig API as the Hub is not hosted in the project ${PROJECT_ID}" | |
return 0 | |
fi | |
# initialize replaces the existing Workload Identity Pools in the IAM binding, so we need to support both Hub and GKE Workload Identity Pools | |
local POST_DATA; POST_DATA='{"workloadIdentityPools":["'${ENVIRON_PROJECT_ID}'.hub.id.goog","'${ENVIRON_PROJECT_ID}'.svc.id.goog"]}' | |
run curl --request POST --fail \ | |
--data "${POST_DATA}" -o /dev/null \ | |
"https://meshconfig.googleapis.com/v1alpha1/projects/${PROJECT_ID}:initialize" \ | |
--header "Content-Type: application/json" \ | |
-K <(auth_header "$(get_auth_token)") | |
else | |
run curl --request POST --fail \ | |
--data '' -o /dev/null \ | |
"https://meshconfig.googleapis.com/v1alpha1/projects/${PROJECT_ID}:initialize" \ | |
-K <(auth_header "$(get_auth_token)") | |
fi | |
} | |
init_meshconfig_managed() { | |
info "Initializing meshconfig managed API..." | |
run curl --request POST --fail \ | |
--data '{"prepare_istiod": true}' \ | |
"https://meshconfig.googleapis.com/v1alpha1/projects/${PROJECT_ID}:initialize" \ | |
--header "X-Server-Timeout: 600" \ | |
--header "Content-Type: application/json" \ | |
-K <(auth_header "$(get_auth_token)") | |
} | |
get_auth_token() { | |
local TOKEN; TOKEN="$(retry 2 gcloud --project="${PROJECT_ID}" auth print-access-token)" | |
echo "${TOKEN}" | |
} | |
# LTS releases don't have curl 7.55 so we can't use the @- construction, | |
# using -K keeps the token from printing if this script is run with -v | |
auth_header() { | |
local TOKEN; TOKEN="${1}" | |
echo "--header \"Authorization: Bearer ${TOKEN}\"" | |
} | |
add_cluster_labels(){ | |
local LABELS; LABELS="$(get_cluster_labels)"; | |
local VERSION_TAG | |
VERSION_TAG="${RELEASE//\./-}" | |
local WANT; WANT="$(mesh_id_label; echo "asmv=${VERSION_TAG}")"; | |
local NOTFOUND; NOTFOUND="$(find_missing_strings "${WANT}" "${LABELS}")" | |
if [[ -z "${NOTFOUND}" ]]; then return 0; fi | |
if [[ -n "${LABELS}" ]]; then | |
LABELS="${LABELS}," | |
fi | |
LABELS="${LABELS}${NOTFOUND}" | |
info "Adding labels to ${CLUSTER_LOCATION}/${CLUSTER_NAME}..." | |
retry 2 gcloud container clusters update "${CLUSTER_NAME}" \ | |
--project="${PROJECT_ID}" \ | |
--zone="${CLUSTER_LOCATION}" \ | |
--update-labels="${LABELS}" | |
} | |
exit_if_cluster_unlabeled() { | |
local LABELS; LABELS="$(get_cluster_labels)"; | |
local REQUIRED; REQUIRED="$(mesh_id_label)"; | |
local NOTFOUND; NOTFOUND="$(find_missing_strings "${REQUIRED}" "${LABELS}")" | |
if [[ -n "${NOTFOUND}" ]]; then | |
for label in $(echo "${NOTFOUND}" | tr ',' '\n'); do | |
warn "Cluster label not found - ${label}" | |
done | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
One or more required cluster labels were not found. Please label them and retry, | |
or run the script with the '--enable_cluster_labels' flag to allow the script | |
to enable them on your behalf. | |
$(enable_common_message) | |
EOF | |
fi | |
} | |
mesh_id_label() { | |
echo "mesh_id=proj-${PROJECT_NUMBER}" | |
} | |
get_cluster_labels() { | |
info "Reading labels for ${CLUSTER_LOCATION}/${CLUSTER_NAME}..." | |
local LABELS | |
LABELS="$(retry 2 gcloud container clusters describe "${CLUSTER_NAME}" \ | |
--zone="${CLUSTER_LOCATION}" \ | |
--project="${PROJECT_ID}" \ | |
--format='value(resourceLabels)[delimiter=","]')"; | |
echo "${LABELS}" | |
} | |
is_cluster_registered() { | |
if ! is_membership_crd_installed; then | |
false | |
return | |
fi | |
local IDENTITY_PROVIDER | |
IDENTITY_PROVIDER="$(retry 2 kubectl get memberships.hub.gke.io \ | |
membership -ojson 2>/dev/null | jq .spec.identity_provider)" | |
if [[ -z "${IDENTITY_PROVIDER}" ]] || [[ "${IDENTITY_PROVIDER}" == 'null' ]]; then | |
false | |
fi | |
populate_environ_info | |
local WANT | |
WANT="//container.googleapis.com/projects/${PROJECT_ID}/locations/${CLUSTER_LOCATION}/clusters/${CLUSTER_NAME}" | |
local LIST | |
LIST="$(gcloud container hub memberships list --project "${ENVIRON_PROJECT_ID}" \ | |
--format=json | grep "${WANT}")" | |
if [[ -z "${LIST}" ]]; then | |
{ read -r -d '' MSG; warn "${MSG}"; } <<EOF || true | |
Cluster is registered in the project ${ENVIRON_PROJECT_ID}, but the script is | |
unable to verify in the project. The script will continue to execute. | |
EOF | |
fi | |
} | |
generate_membership_name() { | |
local MEMBERSHIP_NAME | |
MEMBERSHIP_NAME="${CLUSTER_NAME}" | |
if [[ "$(retry 2 gcloud container hub memberships list --format='value(name)' \ | |
--project "${PROJECT_ID}" | grep -c "^${MEMBERSHIP_NAME}$" || true)" -ne 0 ]]; then | |
MEMBERSHIP_NAME="${CLUSTER_NAME}-${PROJECT_ID}-${CLUSTER_LOCATION}" | |
fi | |
if [[ "${#MEMBERSHIP_NAME}" -gt 63 ]] || [[ "$(retry 2 gcloud container hub \ | |
memberships list --format='value(name)' --project "${PROJECT_ID}" | grep -c \ | |
"^${MEMBERSHIP_NAME}$" || true)" -ne 0 ]]; then | |
local RAND | |
RAND="$(tr -dc "a-z0-9" </dev/urandom | head -c8 || true)" | |
MEMBERSHIP_NAME="${CLUSTER_NAME:0:54}-${RAND}" | |
fi | |
echo "${MEMBERSHIP_NAME}" | |
} | |
register_cluster() { | |
if is_cluster_registered; then return; fi | |
if can_modify_gcp_components; then | |
enable_workload_identity | |
else | |
exit_if_no_workload_identity | |
fi | |
populate_cluster_values | |
local MEMBERSHIP_NAME | |
MEMBERSHIP_NAME="$(generate_membership_name)" | |
info "Registering the cluster as ${MEMBERSHIP_NAME}..." | |
retry 2 gcloud beta container hub memberships register "${MEMBERSHIP_NAME}" \ | |
--project="${PROJECT_ID}" \ | |
--gke-uri="${GKE_CLUSTER_URI}" \ | |
--enable-workload-identity | |
} | |
exit_if_cluster_unregistered() { | |
if ! is_cluster_registered; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
Cluster is not registered to an environ. Please register the cluster and | |
retry, or run the script with the '--enable_registration' flag to allow | |
the script to register to the current project's environ on your behalf. | |
EOF | |
fi | |
} | |
is_workload_identity_enabled() { | |
if [[ "${WI_ENABLED}" -eq 1 ]]; then return; fi | |
local ENABLED | |
ENABLED="$(gcloud container clusters describe \ | |
--project="${PROJECT_ID}" \ | |
--region "${CLUSTER_LOCATION}" \ | |
"${CLUSTER_NAME}" \ | |
--format=json | \ | |
jq .workloadIdentityConfig)" | |
if [[ "${ENABLED}" = 'null' ]]; then false; else WI_ENABLED=1; fi | |
} | |
is_membership_crd_installed() { | |
if ! kubectl api-resources --api-group=hub.gke.io | grep -q memberships; then | |
false | |
return | |
fi | |
if [[ "$(retry 2 kubectl get memberships.hub.gke.io -ojsonpath="{..metadata.name}" \ | |
| grep -w -c membership || true)" -eq 0 ]]; then | |
false | |
fi | |
} | |
populate_environ_info() { | |
if [[ -n "${ENVIRON_PROJECT_ID}" && -n "${HUB_MEMBERSHIP_ID}" ]]; then return; fi | |
if ! is_membership_crd_installed; then return; fi | |
HUB_MEMBERSHIP_ID="$(kubectl get memberships.hub.gke.io membership -o=json | jq .spec.owner.id | sed 's/^\"\/\/gkehub.googleapis.com\/projects\/\(.*\)\/locations\/global\/memberships\/\(.*\)\"$/\2/g')" | |
ENVIRON_PROJECT_ID="$(kubectl get memberships.hub.gke.io membership -o=json | jq .spec.workload_identity_pool | sed 's/^\"\(.*\).\(svc\|hub\).id.goog\"$/\1/g')" | |
} | |
enable_workload_identity(){ | |
if is_workload_identity_enabled; then return; fi | |
info "Enabling Workload Identity on ${CLUSTER_LOCATION}/${CLUSTER_NAME}..." | |
info "(This could take awhile, up to 10 minutes)" | |
retry 2 gcloud container clusters update "${CLUSTER_NAME}" \ | |
--project="${PROJECT_ID}" \ | |
--zone="${CLUSTER_LOCATION}" \ | |
--workload-pool="${WORKLOAD_POOL}" | |
} | |
exit_if_no_workload_identity() { | |
if ! is_workload_identity_enabled; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
Workload identity is not enabled on ${CLUSTER_NAME}. Please enable it and | |
retry, or run the script with the '--enable_gcp_components' flag to allow | |
the script to enable it on your behalf. | |
$(enable_common_message) | |
EOF | |
fi | |
} | |
is_stackdriver_enabled() { | |
local ENABLED | |
ENABLED="$(gcloud container clusters describe \ | |
--project="${PROJECT_ID}" \ | |
--region "${CLUSTER_LOCATION}" \ | |
"${CLUSTER_NAME}" \ | |
--format=json | \ | |
jq '. | |
| [ | |
select( | |
.loggingService == "logging.googleapis.com/kubernetes" | |
and .monitoringService == "monitoring.googleapis.com/kubernetes") | |
] | length')" | |
if [[ "${ENABLED}" -lt 1 ]]; then false; fi | |
} | |
enable_stackdriver_kubernetes(){ | |
info "Enabling Stackdriver on ${CLUSTER_LOCATION}/${CLUSTER_NAME}..." | |
retry 2 gcloud container clusters update "${CLUSTER_NAME}" \ | |
--project="${PROJECT_ID}" \ | |
--zone="${CLUSTER_LOCATION}" \ | |
--enable-stackdriver-kubernetes | |
} | |
exit_if_stackdriver_not_enabled() { | |
if ! is_stackdriver_enabled; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
Cloud Operations (Stackdriver) is not enabled on ${CLUSTER_NAME}. | |
Please enable it and retry, or run the script with the | |
'--enable_gcp_components' flag to allow the script to enable it on your behalf. | |
$(enable_common_message) | |
EOF | |
fi | |
} | |
is_user_cluster_admin() { | |
local GCLOUD_USER; GCLOUD_USER="$(gcloud config get-value core/account)" | |
local IAM_USER; IAM_USER="$(local_iam_user)" | |
local ROLES | |
ROLES="$(\ | |
kubectl get clusterrolebinding \ | |
--all-namespaces \ | |
-o jsonpath='{range .items[?(@.subjects[0].name=="'"${GCLOUD_USER}"'")]}[{.roleRef.name}]{end}'\ | |
2>/dev/null)" | |
if echo "${ROLES}" | grep -q cluster-admin; then return; fi | |
ROLES="$(gcloud projects \ | |
get-iam-policy "${PROJECT_ID}" \ | |
--flatten='bindings[].members' \ | |
--filter="bindings.members:${IAM_USER}" \ | |
--format='value(bindings.role)' 2>/dev/null)" | |
if echo "${ROLES}" | grep -q roles/container.admin; then return; fi | |
false | |
} | |
bind_user_to_cluster_admin(){ | |
info "Querying for core/account..." | |
local GCLOUD_USER; GCLOUD_USER="$(gcloud config get-value core/account)" | |
info "Binding ${GCLOUD_USER} to cluster admin role..." | |
local PREFIX; PREFIX="$(echo "${GCLOUD_USER}" | cut -f 1 -d @)" | |
local YAML; YAML="$(retry 5 kubectl create \ | |
clusterrolebinding "${PREFIX}-cluster-admin-binding" \ | |
--clusterrole=cluster-admin \ | |
--user="${GCLOUD_USER}" \ | |
--dry-run -o yaml)" | |
retry 3 kubectl apply -f - <<EOF | |
${YAML} | |
EOF | |
} | |
exit_if_not_cluster_admin() { | |
if ! is_user_cluster_admin; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
Current user must have the cluster-admin role on ${CLUSTER_NAME}. | |
Please add the cluster role binding and retry, or run the script with the | |
'--enable_cluster_roles' flag to allow the script to enable it on your behalf. | |
$(enable_common_message) | |
EOF | |
fi | |
} | |
create_istio_namespace() { | |
info "Creating istio-system namespace..." | |
if istio_namespace_exists; then return; fi | |
retry 2 kubectl create ns istio-system | |
} | |
exit_if_istio_namespace_not_exists() { | |
info "Checking for istio-system namespace..." | |
if ! istio_namespace_exists; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
The istio-system namespace doesn't exist. | |
Please create the "istio-namespace" and retry, or run the script with the | |
'--enable_namespace_creation' flag to allow the script to enable it on your behalf. | |
$(enable_common_message) | |
EOF | |
fi | |
} | |
istio_namespace_exists() { | |
if [[ "${NAMESPACE_EXISTS}" -eq 1 ]]; then return; fi | |
if [[ "$(retry 2 kubectl get ns | grep -c istio-system || true)" -eq 0 ]]; then | |
false | |
else | |
NAMESPACE_EXISTS=1 | |
fi | |
} | |
register_gce_identity_provider() { | |
info "Registering GCE Identity Provider in the cluster..." | |
retry 3 kubectl apply -f asm/identity-provider/identityprovider-crd.yaml | |
retry 3 kubectl apply -f asm/identity-provider/googleidp.yaml | |
} | |
should_enable_service_mesh_feature() { | |
if [[ "${USE_VM}" -eq 0 ]]; then | |
false | |
fi | |
} | |
enable_service_mesh_feature() { | |
info "Enabling the service mesh feature..." | |
# IAM permission: gkehub.features.create | |
retry 2 run curl -s -H "Content-Type: application/json" \ | |
-XPOST "https://gkehub.googleapis.com/v1alpha1/projects/${PROJECT_ID}/locations/global/features?feature_id=servicemesh"\ | |
-d '{servicemesh_feature_spec: {}}' \ | |
-K <(auth_header "$(get_auth_token)") | |
} | |
exit_if_service_mesh_feature_not_enabled() { | |
if ! is_service_mesh_feature_enabled; then | |
{ read -r -d '' MSG; fatal "${MSG}"; } <<EOF || true | |
The service mesh feature is not enabled on project ${PROJECT_ID}. | |
Please run the script with the '--enable_gcp_components' flag to allow the | |
script to enable it on your behalf. | |
$(enable_common_message) | |
EOF | |
fi | |
} | |
is_service_mesh_feature_enabled() { | |
local RESPONSE | |
# IAM permission: gkehub.features.get | |
RESPONSE="$(run curl -s -H "X-Goog-User-Project: ${PROJECT_ID}" \ | |
"https://gkehub.googleapis.com/v1alpha1/projects/${PROJECT_ID}/locations/global/features/servicemesh" \ | |
-K <(auth_header "$(get_auth_token)"))" | |
if [[ "$(echo "${RESPONSE}" | jq -r '.featureState.lifecycleState')" != "ENABLED" ]]; then | |
false | |
fi | |
} | |
### Installation functions ### | |
configure_package() { | |
info "Configuring kpt package..." | |
populate_cluster_values | |
populate_environ_info | |
kpt cfg set asm gcloud.container.cluster "${CLUSTER_NAME}" | |
kpt cfg set asm gcloud.core.project "${PROJECT_ID}" | |
kpt cfg set asm gcloud.project.environProjectNumber "${PROJECT_NUMBER}" | |
if [[ -n "${_CI_ENVIRON_PROJECT_NUMBER}" ]]; then | |
kpt cfg set asm gcloud.project.environProjectNumber "${_CI_ENVIRON_PROJECT_NUMBER}" | |
fi | |
kpt cfg set asm gcloud.compute.location "${CLUSTER_LOCATION}" | |
kpt cfg set asm gcloud.compute.network "${GCE_NETWORK_NAME}" | |
kpt cfg set asm anthos.servicemesh.rev "${REVISION_LABEL}" | |
kpt cfg set asm anthos.servicemesh.tag "${RELEASE}" | |
if [[ -n "${_CI_ASM_IMAGE_LOCATION}" ]]; then | |
kpt cfg set asm anthos.servicemesh.hub "${_CI_ASM_IMAGE_LOCATION}" | |
fi | |
if [[ -n "${_CI_ASM_IMAGE_TAG}" ]]; then | |
kpt cfg set asm anthos.servicemesh.tag "${_CI_ASM_IMAGE_TAG}" | |
fi | |
if [[ "${USE_HUB_WIP}" -eq 1 ]]; then | |
kpt cfg set asm gcloud.project.environProjectID "${ENVIRON_PROJECT_ID}" | |
kpt cfg set asm anthos.servicemesh.hubMembershipID "${HUB_MEMBERSHIP_ID}" | |
fi | |
if [[ -n "${CA_NAME}" && "${CA}" = "gcp_cas" ]]; then | |
kpt cfg set asm anthos.servicemesh.external_ca.ca_name "${CA_NAME}" | |
fi | |
if [[ "${USE_VM}" -eq 1 ]] && [[ "${_CI_NO_REVISION}" -eq 0 ]]; then | |
kpt cfg set asm anthos.servicemesh.istiodHost "istiod-${REVISION_LABEL}.istio-system.svc" | |
kpt cfg set asm anthos.servicemesh.istiodHostFQDN "istiod-${REVISION_LABEL}.istio-system.svc.cluster.local" | |
kpt cfg set asm anthos.servicemesh.istiod-vs-name "istiod-vs-${REVISION_LABEL}" | |
fi | |
if [[ "${CA}" == "mesh_ca" && -n "${_CI_TRUSTED_GCP_PROJECTS}" ]]; then | |
# Gather the trust domain aliases from projects. | |
TRUST_DOMAIN_ALIASES="${PROJECT_ID}.svc.id.goog" | |
while IFS=',' read -r trusted_gcp_project; do | |
TRUST_DOMAIN_ALIASES="${TRUST_DOMAIN_ALIASES} ${trusted_gcp_project}.svc.id.goog" | |
done <<EOF | |
${_CI_TRUSTED_GCP_PROJECTS} | |
EOF | |
# kpt treats words in quotes as a single param, while kpt need ${TRUST_DOMAIN_ALIASES} to be splitting params for a list. If we remove quotes, the lint will complain. | |
# eval will translate the quoted TRUST_DOMAIN_ALIASES into params to workaround both. | |
run eval kpt cfg set asm anthos.servicemesh.trustDomainAliases "${TRUST_DOMAIN_ALIASES}" | |
fi | |
if [[ "${MANAGED}" -eq 1 ]]; then | |
local VALIDATION_URL; local CLOUDRUN_ADDR; | |
read -r VALIDATION_URL CLOUDRUN_ADDR <<EOF | |
$(retry 5 fail_if_empty scrape_managed_urls) | |
EOF | |
if [[ -z "${VALIDATION_URL}" ]] || [[ -z "${CLOUDRUN_ADDR}" ]]; then | |
fatal "Failed to read configuration to install components for managed control plane!" | |
fi | |
kpt cfg set asm anthos.servicemesh.managed-controlplane.service-account "${MANAGED_SERVICE_ACCOUNT}" | |
kpt cfg set asm anthos.servicemesh.controlplane.validation-url "${VALIDATION_URL}" | |
kpt cfg set asm anthos.servicemesh.managed-controlplane.cloudrun-addr "${CLOUDRUN_ADDR}" | |
fi | |
} | |
print_config() { | |
if [[ "${MANAGED}" -eq 1 ]]; then | |
cat "${MANAGED_MANIFEST}" | |
return | |
fi | |
PARAMS="-f ${OPERATOR_MANIFEST}" | |
while read -d ',' -r yaml_file; do | |
PARAMS="${PARAMS} -f ${yaml_file} " | |
done <<EOF | |
${CUSTOM_OVERLAY} | |
EOF | |
# shellcheck disable=SC2086 | |
istioctl profile dump ${PARAMS} | |
} | |
install_secrets() { | |
info "Installing certificates into the cluster..." | |
kubectl create secret generic cacerts -n istio-system \ | |
--from-file="${CA_CERT}" \ | |
--from-file="${CA_KEY}" \ | |
--from-file="${CA_ROOT}" \ | |
--from-file="${CA_CHAIN}" | |
} | |
init_gcp_cas() { | |
local WORKLOAD_IDENTITY; WORKLOAD_IDENTITY="$PROJECT_ID.svc.id.goog[istio-system/istiod-service-account]" | |
local NAME; NAME=$(echo "${CA_NAME}" | cut -f6 -d/) | |
local CA_LOCATION; CA_LOCATION=$(echo "${CA_NAME}" | cut -f4 -d/) | |
local CA_PROJECT; CA_PROJECT=$(echo "${CA_NAME}" | cut -f2 -d/) | |
retry 3 gcloud beta privateca subordinates add-iam-policy-binding "${NAME}" \ | |
--location "${CA_LOCATION}" \ | |
--project "${CA_PROJECT}" \ | |
--member "serviceAccount:${WORKLOAD_IDENTITY}" \ | |
--role "roles/privateca.certificateManager" | |
} | |
does_istiod_exist(){ | |
local RETVAL; RETVAL=0; | |
kubectl get service \ | |
--request-timeout='20s' \ | |
-n istio-system \ | |
istiod 1>/dev/null 2>/dev/null || RETVAL=$? | |
return "${RETVAL}" | |
} | |
install_asm() { | |
if ! does_istiod_exist && [[ "${_CI_NO_REVISION}" -ne 1 ]]; then | |
info "Installing validation webhook fix..." | |
retry 3 kubectl apply -f "${VALIDATION_FIX_SERVICE}" | |
elif [[ "${MODE}" == "upgrade" ]]; then | |
cp "${VALIDATION_FIX_SERVICE}" . | |
fi | |
local PARAMS | |
PARAMS="-f ${OPERATOR_MANIFEST}" | |
while read -d ',' -r yaml_file; do | |
PARAMS="${PARAMS} -f ${yaml_file}" | |
done <<EOF | |
${CUSTOM_OVERLAY} | |
EOF | |
if [[ "${_CI_NO_REVISION}" -ne 1 ]]; then | |
PARAMS="${PARAMS} --set revision=${REVISION_LABEL}" | |
fi | |
if [[ "${K8S_MINOR}" -eq 15 ]]; then | |
PARAMS="${PARAMS} -f ${BETA_CRD_MANIFEST}" | |
fi | |
PARAMS="${PARAMS} -c ${KUBECONFIG}" | |
PARAMS="${PARAMS} --skip-confirmation" | |
info "Installing ASM control plane..." | |
# shellcheck disable=SC2086 | |
retry 5 istioctl install $PARAMS | |
# Prevent the stderr buffer from ^ messing up the terminal output below | |
sleep 1 | |
info "...done!" | |
local RAW_YAML; RAW_YAML="${REVISION_LABEL}-manifest-raw.yaml" | |
local EXPANDED_YAML; EXPANDED_YAML="${REVISION_LABEL}-manifest-expanded.yaml" | |
print_config >| "${RAW_YAML}" | |
istioctl manifest generate \ | |
<"${RAW_YAML}" \ | |
>|"${EXPANDED_YAML}" | |
if [[ "$DISABLE_CANONICAL_SERVICE" -eq 0 ]]; then | |
install_canonical_controller | |
fi | |
if [[ "${USE_VM}" -eq 1 ]]; then | |
info "Exposing the control plane for VM workloads..." | |
expose_istiod | |
# The default istiod service is exposed so that any fallback on the VM side | |
# to use the default Istiod service can still connect to the control plane. | |
kpt cfg set asm anthos.servicemesh.istiodHost "istiod.istio-system.svc" | |
kpt cfg set asm anthos.servicemesh.istiodHostFQDN "istiod.istio-system.svc.cluster.local" | |
kpt cfg set asm anthos.servicemesh.istiod-vs-name "istiod-vs" | |
expose_istiod | |
fi | |
outro | |
} | |
install_canonical_controller() { | |
info "Installing ASM CanonicalService controller in asm-system namespace..." | |
retry 3 kubectl apply -f "${CANONICAL_CONTROLLER_MANIFEST}" | |
info "Waiting for deployment..." | |
retry 3 kubectl wait --for=condition=available --timeout=600s \ | |
deployment/canonical-service-controller-manager -n asm-system | |
info "...done!" | |
} | |
expose_istiod() { | |
retry 3 kubectl apply -f "${EXPOSE_ISTIOD_SERVICE}" | |
} | |
start_managed_control_plane() { | |
info "Configuring base installation for managed control plane..." | |
kubectl apply --overwrite=true -f "${BASE_REL_PATH}" | |
local CR_IMAGE_JSON; CR_IMAGE_JSON=""; | |
if [[ -n "${_CI_CLOUDRUN_IMAGE_HUB}" ]]; then | |
CR_IMAGE_JSON=$(cat <<EOF | |
{"image": "${_CI_CLOUDRUN_IMAGE_HUB}:${_CI_CLOUDRUN_IMAGE_TAG}"} | |
EOF | |
) | |
fi | |
info "Provisioning managed control plane..." | |
retry 2 run curl --request POST \ | |
"https://meshconfig.googleapis.com/v1alpha1/projects/${PROJECT_ID}/locations/${CLUSTER_LOCATION}/clusters/${CLUSTER_NAME}:runIstiod" \ | |
--data "${CR_IMAGE_JSON}" \ | |
--header "X-Server-Timeout: 600" \ | |
--header "Content-Type: application/json" \ | |
-K <(auth_header "$(get_auth_token)") | |
} | |
scrape_managed_urls() { | |
local URL | |
URL="$(kubectl get mutatingwebhookconfiguration istiod-asm-managed -ojson | jq .webhooks[0].clientConfig.url -r)" | |
# shellcheck disable=SC2001 | |
VALIDATION_URL="$(echo "${URL}" | sed 's/inject.*$/validate/g')" | |
# shellcheck disable=SC2001 | |
CLOUDRUN_ADDR=$(echo "${URL}" | cut -d'/' -f3) | |
echo "${VALIDATION_URL} ${CLOUDRUN_ADDR}" | |
} | |
install_managed_components() { | |
info "Configuring ASM managed control plane validating webhook config..." | |
kubectl apply -f "${MANAGED_WEBHOOKS}" | |
info "Configuring service account for GKE Connect..." | |
kubectl apply -f "${CONNECT_RBAC}" | |
info "Configuring ASM managed control plane components..." | |
print_config >| managed_control_plane_gateway.yaml | |
if [[ "$DISABLE_CANONICAL_SERVICE" -eq 0 ]]; then | |
install_canonical_controller | |
fi | |
} | |
outro() { | |
info "" | |
info "$(starline)" | |
istioctl version | |
info "$(starline)" | |
info "The ASM control plane installation is now complete." | |
info "To enable automatic sidecar injection on a namespace, you can use the following command:" | |
info "kubectl label namespace <NAMESPACE> istio-injection- istio.io/rev=${REVISION_LABEL} --overwrite" | |
info "If you use 'istioctl install' afterwards to modify this installation, you will need" | |
info "to specify the option '--set revision=${REVISION_LABEL}' to target this control plane" | |
info "instead of installing a new one." | |
if [[ "${MODE}" = "migrate" || "${MODE}" = "upgrade" ]]; then | |
info "Please verify the new control plane and then: 1) migrate your workloads 2) remove old control plane." | |
info "For more information, see:" | |
info "https://cloud.google.com/service-mesh/docs/upgrading-gke#redeploying_workloads" | |
info "Before removing the old control plane, update the service used by validation if necessary." | |
info "If the 'istiod' service has a revision label different than ${REVISION_LABEL}, then apply" | |
info "${OUTPUT_DIR}/${VALIDATION_FIX_FILE_NAME} using 'kubectl apply'" | |
elif [[ "${MODE}" = "install" ]]; then | |
info "To finish the installation, enable Istio sidecar injection and restart your workloads." | |
info "For more information, see:" | |
info "https://cloud.google.com/service-mesh/docs/proxy-injection" | |
fi | |
info "The ASM package used for installation can be found at:" | |
info "${OUTPUT_DIR}/asm" | |
info "The version of istioctl that matches the installation can be found at:" | |
info "${OUTPUT_DIR}/${ISTIOCTL_REL_PATH}" | |
info "A symlink to the istioctl binary can be found at:" | |
info "${OUTPUT_DIR}/istioctl" | |
if ! is_managed; then | |
info "The combined configuration generated for installation can be found at:" | |
info "${OUTPUT_DIR}/${RAW_YAML}" | |
info "The full, expanded set of kubernetes resources can be found at:" | |
info "${OUTPUT_DIR}/${EXPANDED_YAML}" | |
else | |
info "You can find the gateway config to install with istioctl here:" | |
info "${OUTPUT_DIR}/managed_control_plane_gateway.yaml" | |
fi | |
info "$(starline)" | |
} | |
main "${@}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment