#!/bin/bash DEBUG_SERVER="debug-server" IMG_URL="gcr.io/cloud-tagging-10302018/gtm-cloud-image:stable" TMP_DIR="$(mktemp -d)" ZIP_URL="https://storage.googleapis.com/serverside-tagging/gtm-cloud-image.zip" TEST_ENV="testing" PROD_ENV="production" EXISTING_APP_ENGINE_SERVICE_PROMPT=\ "There is an existing App Engine application that does not look like a Server-side GTM server. Continuing with this script will override the existing deployment. Do you wish to continue? (y/N): " WELCOME_TEXT=\ "Please input the following information to set up your tagging server. For more information about the configuration, input '?'. To use the recommended setting or your current setting, leave blank." CONTAINER_CONFIG_HELP=\ " The container config describes your server-side container. Copy it from Google Tag Manager." POLICY_SCRIPT_HELP=\ " The policy script URL is an optional URL that specifies the policies that govern custom template permissions. A value of '' means that there is no policy script URL. Enter 'None' to clear the URL. For more information, see: https://developers.google.com/tag-manager/templates/policies" DEPLOYMENT_TYPE_HELP=\ " A $TEST_ENV deployment type deploys the server-side container to the App Engine Standard Environment with a single server. It is suitable for limited traffic volume and should be free for low amounts of traffic; however, you may incur some costs depending on traffic volume. A $PROD_ENV deployment type deploys the server-side container to the App Engine Flexible Environment and allocates additional servers to ensure redundancy and avoid data loss in case of outages or capacity limitations. The only supported options are: ${TEST_ENV} or ${PROD_ENV}" AUTOSCALING_HELP=\ " Autoscaling will automatically create and shut down servers as the volume of traffic fluctuates over time. The only supported options are: on or off." NUM_INSTANCES_HELP=\ " The number of servers running the container at any given time. Each server costs roughly \$40 per month." MIN_INSTANCES_HELP=\ " The minimum number of servers running the container at any given time. Each server costs roughly \$40 per month." MAX_INSTANCES_HELP=\ " The maximum number of servers running the container at any given time. These servers are started up when needed, and turned off when they are not needed. If you find there's constantly the maximum number of servers running, consider setting this number higher." CPU_TARGET_HELP=\ " The target CPU utilization ratio to use when scaling. The default value is recommended. Must be between 0 and 1." DOWNGRADE_NOTE=\ "NOTE: You are downgrading from a ${PROD_ENV} deployment to a ${TEST_ENV} deployment. This may cause a drop in availability depending on your traffic." UPGRADE_NOTE=\ "NOTE: You are upgrading from a ${TEST_ENV} deployment to a ${PROD_ENV} deployment. The linked billing account will start incurring charges." SAME_SETTINGS=\ "Your configured settings are the same as the current deployment." BETWEEN_ZERO_AND_ONE_REGEX="^(0?\.([0-9]+[1-9]+0*|[1-9]+[0-9]*))$" POSITIVE_INT_REGEX="^[1-9]+[0-9]*$" trap "rm -rf ${TMP_DIR}" EXIT trap "exit" INT set -e create_testing_config() { local config_file="${TMP_DIR}/${TEST_ENV}.yaml" local config=\ "service: default runtime: nodejs10 instance_class: F1 automatic_scaling: max_instances: 1 env_variables: CONTAINER_CONFIG: $1 INCLUDE_DEBUG_SERVER: true POLICY_SCRIPT_URL: $2 handlers: - url: /.* secure: always script: auto" echo "${config}" > "${config_file}" echo "${config_file}" } create_flex_tagging_server_config() { local config_file="${TMP_DIR}/${PROD_ENV}.yaml" local config=\ "service: default runtime: nodejs env: flex resources: cpu: 1 memory_gb: 0.5 disk_size_gb: 10 readiness_check: path: '/healthz' failure_threshold: 3 check_interval_sec: 10 liveness_check: path: '/healthz' failure_threshold: 10 check_interval_sec: 10 env_variables: CONTAINER_CONFIG: $1 POLICY_SCRIPT_URL: $2" echo "${config}" > "${config_file}" echo "${config_file}" } add_autoscaling_to_config() { echo \ "automatic_scaling: min_num_instances: $2 max_num_instances: $3 cpu_utilization: target_utilization: $4" >> "$1" } add_manual_scaling_to_config() { echo \ "manual_scaling: instances: $2" >> "$1" } create_debug_config() { local config_file="${TMP_DIR}/debug.yaml" local config=\ "service: ${DEBUG_SERVER} runtime: nodejs10 instance_class: F1 automatic_scaling: max_instances: 1 env_variables: CONTAINER_CONFIG: $1 RUN_AS_DEBUG_SERVER: true" echo "${config}" > "${config_file}" echo "${config_file}" } create_testing_dispatch_config() { local config_file="${TMP_DIR}/dispatch.yaml" local config=\ "dispatch: - url: '*/*' service: default" echo "${config}" > "${config_file}" echo "${config_file}" } create_production_dispatch_config() { local config_file="${TMP_DIR}/dispatch.yaml" local config=\ "dispatch: - url: '*/gtm/*' service: ${DEBUG_SERVER} - url: '*/*' service: default" echo "${config}" > "${config_file}" echo "${config_file}" } wait_all_operations_complete() { local pending_operations pending_operations="$(\ gcloud app operations list --pending --verbosity=none -q 2> /dev/null | sed -n 's/\([a-z0-9-]\{1,\}\)[[:blank:]]\{1,\}.*/\1/p')" || true if [[ ! -z "${pending_operations}" ]]; then echo "There are existing operations. Waiting for them to finish before continuing." for operation in ${pending_operations}; do gcloud app operations wait "${operation}" --verbosity=none -q > /dev/null done fi } testing_deployment() { local source_zip="${TMP_DIR}/source.zip" curl -fsSL --output "${source_zip}" "${ZIP_URL}" && unzip -q "${source_zip}" -d "${TMP_DIR}" && rm "${source_zip}" wait_all_operations_complete if ! gcloud app services list --verbosity=none > /dev/null 2> /dev/null; then gcloud app deploy "$(create_testing_config "$1" "$2")" \ --version "${TEST_ENV}" else gcloud app deploy -q "$(create_testing_config "$1" "$2")" \ --version "${TEST_ENV}" --stop-previous-version fi gcloud app deploy -q "$(create_testing_dispatch_config)" gcloud app services delete -q "${DEBUG_SERVER}" --verbosity=none || true } production_deployment() { local config config="$(create_flex_tagging_server_config "$1" "$2" "$3")" if [[ $# == 3 ]]; then add_manual_scaling_to_config "${config}" "$3" elif [[ $# == 5 ]]; then add_autoscaling_to_config "${config}" "$3" "$4" "$5" fi wait_all_operations_complete local is_stopped is_stopped="$(gcloud app versions list --verbosity=none | sed -n '/default.*production.*STOPPED/p')" || true if [[ ! -z "${is_stopped}" ]]; then gcloud app versions start -q "${PROD_ENV}" \ --verbosity=none --service default || true fi if ! gcloud app services list --verbosity=none > /dev/null 2> /dev/null; then gcloud app deploy "${config}" \ --image-url "${IMG_URL}" --version "${PROD_ENV}" else gcloud app deploy -q "${config}" \ --image-url "${IMG_URL}" --version "${PROD_ENV}" --stop-previous-version fi local source_zip="${TMP_DIR}/source.zip" curl -fsSL "${ZIP_URL}" --output "${source_zip}" && unzip -q "${source_zip}" -d "${TMP_DIR}" && rm "${source_zip}" gcloud app deploy -q "$(create_debug_config "${container_config}")" \ --version "${PROD_ENV}" gcloud app deploy -q "$(create_production_dispatch_config)" gcloud app versions delete -q "${TEST_ENV}" --verbosity=none || true } extract_from_yaml() { echo "$1" | sed -n "s/[[:blank:]]*$2: \(.*\)/\1/p" } generate_suggested() { echo "$([[ -z "$1" ]] && echo "$2" || echo "Current: $1")" } cur_app_versions="$(gcloud app versions list --verbosity=none)" || true cur_deployment_type="$(\ echo "${cur_app_versions}" | sed -n \ "s/default[[:blank:]]\{1,\}\(${TEST_ENV}\)[[:blank:]]\{1,\}1\.00.*/\1/p")" if [[ -z "${cur_deployment_type}" ]]; then cur_deployment_type="$(\ echo "${cur_app_versions}" | sed -n \ "s/default[[:blank:]]\{1,\}\(${PROD_ENV}\)[[:blank:]]\{1,\}1\.00.*/\1/p")" fi cur_settings="$(\ gcloud app versions describe "${cur_deployment_type}" \ --verbosity=none -s default)" || true cur_container_config="$(\ extract_from_yaml "${cur_settings}" "CONTAINER_CONFIG")" if gcloud app services list --verbosity=none > /dev/null 2> /dev/null \ && [[ -z "${cur_container_config}" && -z "${cur_policy_script_url}" ]]; then cur_deployment_type='' while true; do printf "${EXISTING_APP_ENGINE_SERVICE_PROMPT}" read confirmation if [[ -z "${confirmation}" || "${confirmation}" =~ ^[Nn]$ ]]; then exit 0 fi if [[ "${confirmation}" =~ ^[Yy]$ ]]; then echo "" break fi done fi cur_policy_script_url="$(\ extract_from_yaml "${cur_settings}" "POLICY_SCRIPT_URL")" if [[ "${cur_deployment_type}" == "${PROD_ENV}" ]]; then if [[ ! -z "$(echo "${cur_settings}" | sed -n '/automaticScaling/p')" ]]; then cur_autoscaling="on" elif [[ ! -z "$(echo "${cur_settings}" | sed -n '/manualScaling/p')" ]]; then cur_autoscaling="off" fi cur_num_instances="$(\ extract_from_yaml "${cur_settings}" "instances")" cur_min_instances="$(\ extract_from_yaml "${cur_settings}" "minTotalInstances")" cur_max_instances="$(\ extract_from_yaml "${cur_settings}" "maxTotalInstances")" cur_cpu_target="$(\ extract_from_yaml "${cur_settings}" "targetUtilization")" fi echo "${WELCOME_TEXT}" while [[ -z "${container_config}" || "${container_config}" == '?' ]]; do suggested="$(generate_suggested "${cur_container_config}" "Required")" printf "Container Config (${suggested}): " read container_config if [[ -z "${container_config}" ]]; then container_config="${cur_container_config}" fi if [[ "${container_config}" == '?' ]]; then echo "${CONTAINER_CONFIG_HELP}" elif [[ -z "${container_config}" ]]; then echo " Container config cannot be empty." fi done while true; do suggested="$(generate_suggested "${cur_policy_script_url}" "Optional")" printf "Policy Script URL (${suggested}): " read policy_script_url if [[ "${policy_script_url}" =~ ^[Nn][Oo][Nn][Ee]$ ]]; then policy_script_url="''" elif [[ "${policy_script_url}" == '""' ]]; then policy_script_url="''" fi if [[ "$policy_script_url" == '?' ]]; then echo "${POLICY_SCRIPT_HELP}" elif [[ -z "${policy_script_url}" ]]; then if [[ ! -z "${cur_policy_script_url}" ]]; then policy_script_url="${cur_policy_script_url}" else policy_script_url="''" fi break else break fi done while [[ ("${deployment_type}" != "${TEST_ENV}" && \ "${deployment_type}" != "${PROD_ENV}") || "${deployment_type}" == '?' ]]; do suggested="$(\ generate_suggested "${cur_deployment_type}" "${TEST_ENV}/${PROD_ENV}")" printf "Deployment Type (${suggested}): " read deployment_type deployment_type="$(echo "${deployment_type}" | tr '[:upper:]' '[:lower:]')" if [[ -z "${deployment_type}" ]]; then deployment_type="${cur_deployment_type}" fi if [[ "${deployment_type}" == '?' ]]; then echo "${DEPLOYMENT_TYPE_HELP}" elif [[ "${deployment_type}" != "${TEST_ENV}" && \ "${deployment_type}" != "${PROD_ENV}" ]]; then echo " The only supported options are: ${TEST_ENV} or ${PROD_ENV}" fi done if [[ "${deployment_type}" == "${TEST_ENV}" ]]; then echo "" echo "Your configured settings are" echo "Container Config: ${container_config}" echo "Policy Script URL: ${policy_script_url}" echo "Deployment Type: ${deployment_type}" if [[ "${cur_deployment_type}" == "${PROD_ENV}" ]]; then echo "${DOWNGRADE_NOTE}" fi while true; do read -p "Do you wish to continue? (y/N): " confirmation confirmation="$(echo "${confirmation}" | tr '[:upper:]' '[:lower:]')" if [[ -z "${confirmation}" || "${confirmation}" == 'n' ]]; then exit 0 fi if [[ "${confirmation}" == 'y' ]]; then break fi done if [[ "${deployment_type}" == "${cur_deployment_type}" && "${container_config}" == "${cur_container_config}" && "${policy_script_url}" == "${cur_policy_script_url}" ]]; then echo "${SAME_SETTINGS}" exit 0 fi echo "As you wish." testing_deployment "${container_config}" "${policy_script_url}" echo "" echo "Your server deployment is complete." exit 0 fi while [[ ("${autoscaling}" != "on" && "${autoscaling}" != "off") || \ "${autoscaling}" == '?' ]]; do recommended="on" suggested="$(\ generate_suggested "${cur_autoscaling}" "Recommended: ${recommended}")" printf "Autoscaling (${suggested}): " read autoscaling autoscaling="$(echo "${autoscaling}" | tr '[:upper:]' '[:lower:]')" if [[ "${autoscaling}" == '?' ]]; then echo "${AUTOSCALING_HELP}" elif [[ -z "${autoscaling}" ]]; then if [[ ! -z "${cur_autoscaling}" ]]; then autoscaling="${cur_autoscaling}" else autoscaling="${recommended}" fi elif [[ "${autoscaling}" != "on" && "${autoscaling}" != "off" ]]; then echo " The only supported options are: on or off" fi done if [[ "${autoscaling}" == "off" ]]; then while [[ ! "${num_instances}" =~ ${POSITIVE_INT_REGEX} || \ "${num_instances}" == '?' ]]; do recommended="3" suggested="$(\ generate_suggested "${cur_num_instances}" "Recommended: ${recommended}")" printf "Number of Servers (${suggested}): " read num_instances if [[ "${num_instances}" == '?' ]]; then echo "${NUM_INSTANCES_HELP}" elif [[ -z "${num_instances}" ]]; then if [[ ! -z "${cur_num_instances}" ]]; then num_instances="${cur_num_instances}" else num_instances="${recommended}" fi elif [[ ! "${num_instances}" =~ ${POSITIVE_INT_REGEX} ]]; then echo " The input must be a positive integer." fi done else while [[ ! "${min_instances}" =~ ${POSITIVE_INT_REGEX} || \ "${min_instances}" == '?' || "${min_instances}" -le 0 ]]; do recommended="3" suggested="$(\ generate_suggested "${cur_min_instances}" "Recommended: ${recommended}")" printf "Minimum Number of Servers (${suggested}): " read min_instances if [[ "${min_instances}" == '?' ]]; then echo "${MIN_INSTANCES_HELP}" elif [[ -z "${min_instances}" ]]; then if [[ ! -z "${cur_min_instances}" ]]; then min_instances="${cur_min_instances}" else min_instances="${recommended}" fi elif [[ ! "${min_instances}" =~ ${POSITIVE_INT_REGEX} ]]; then echo " The input must be a positive integer." fi done while [[ ! "${max_instances}" =~ ${POSITIVE_INT_REGEX} || \ "${max_instances}" == '?' || \ "${min_instances}" -gt "${max_instances}" ]]; do recommended="6" suggested="$(\ generate_suggested "${cur_max_instances}" "Recommended: ${recommended}")" printf "Maximum Number of Servers (${suggested}): " read max_instances if [[ "${max_instances}" == '?' ]]; then echo "${MAX_INSTANCES_HELP}" elif [[ -z "${max_instances}" ]]; then if [[ ! -z "${cur_max_instances}" ]]; then max_instances="${cur_max_instances}" else max_instances="${recommended}" fi elif [[ ! "${max_instances}" =~ ${POSITIVE_INT_REGEX} ]]; then echo " The input must be a positive integer." elif [[ "${min_instances}" -gt "${max_instances}" ]]; then echo " The input must be equal or greater than the minimum number of servers." fi done while [[ -z "${cpu_target}" || "${cpu_target}" == '?' || \ ! "${cpu_target}" =~ ${BETWEEN_ZERO_AND_ONE_REGEX} ]]; do recommended="0.6" suggested="$(\ generate_suggested "${cur_cpu_target}" "Recommended: ${recommended}")" printf "CPU Target Utilization (${suggested}): " read cpu_target if [[ "${cpu_target}" == '?' ]]; then echo "${CPU_TARGET_HELP}" elif [[ -z "${cpu_target}" ]]; then if [[ ! -z "${cur_cpu_target}" ]]; then cpu_target="${cur_cpu_target}" else cpu_target="${recommended}" fi elif [[ ! "${cpu_target}" =~ ${BETWEEN_ZERO_AND_ONE_REGEX} ]]; then echo " The input must be between 0 and 1." fi done fi echo "" echo "Your configured settings are" echo "Container Config: ${container_config}" echo "Policy Script URL: ${policy_script_url}" echo "Deployment Type: ${deployment_type}" echo "Autoscaling: ${autoscaling}" if [[ "${autoscaling}" == "off" ]]; then echo "Number of Servers: ${num_instances}" else echo "Minimum Number of Servers: ${min_instances}" echo "Maximum Number of Servers: ${max_instances}" echo "CPU Target Utilization: ${cpu_target}" fi if [[ "${cur_deployment_type}" == "${TEST_ENV}" ]]; then echo "${UPGRADE_NOTE}" fi while true; do read -p "Do you wish to continue? (y/N): " confirmation confirmation="$(echo "${confirmation}" | tr '[:upper:]' '[:lower:]')" if [[ -z "${confirmation}" || "${confirmation}" == 'n' ]]; then exit 0 fi if [[ "${confirmation}" == "y" ]]; then break fi done if [[ "${deployment_type}" == "${cur_deployment_type}" && "${container_config}" == "${cur_container_config}" && "${policy_script_url}" == "${cur_policy_script_url}" && "${num_instances}" == "${cur_num_instances}" && "${min_instances}" == "${cur_min_instances}" && "${max_instances}" == "${cur_max_instances}" && "${cpu_target}" == "${cur_cpu_target}" ]]; then echo "${SAME_SETTINGS}" exit 0 fi echo "As you wish." if [[ "${autoscaling}" == "off" ]]; then production_deployment "${container_config}" "${policy_script_url}" \ "${num_instances}" else production_deployment "${container_config}" "${policy_script_url}" \ "${min_instances}" "${max_instances}" "${cpu_target}" fi echo "" echo "Your server deployment is complete."