diff --git a/.gitignore b/.gitignore index 50a20da3..fd08989d 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ **/*.log version.txt public/ -.data \ No newline at end of file +walkthrough/ +.data diff --git a/Dockerfile b/Dockerfile index 1fcd8353..d090c4db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -278,6 +278,7 @@ COPY ./scripts/tfstate_azurerm.sh . COPY ./scripts/functions.sh . COPY ./scripts/banner.sh . COPY ./scripts/clone.sh . +COPY ./scripts/walkthrough.sh . COPY ./scripts/sshd.sh . COPY ./scripts/backend.hcl.tf . COPY ./scripts/ci.sh . diff --git a/scripts/clone.sh b/scripts/clone.sh index cb5d1ce5..c4dcc6d5 100644 --- a/scripts/clone.sh +++ b/scripts/clone.sh @@ -8,10 +8,9 @@ export landingzone_branch=${landingzone_branch:="master"} current_path=$(pwd) - function display_clone_instructions { - while (( "$#" )); do + while (("$#")); do case "${1}" in --intro) echo @@ -57,6 +56,14 @@ function display_clone_instructions { echo shift 1 ;; + --clone-project-name) + echo "--clone-project-name specify the GitHub repo to download from, default is Azure/caf-terraform-landingzones" + echo + echo " Example: --clone-project-name Azure/caf-terraform-landingzones-starter --clone-branch starter will download the starter branch of the Azure/caf-terraform-landingzones-starter repo" + echo " Note: the default --cone-branch is master and this is not available in the example repo above so the starter branch is specified." + echo + shift 1 + ;; --examples) echo "By default the rover will clone the azure/caf-terraform-landingzones into the local rover folder /tf/caf/landinzones" echo @@ -75,6 +82,13 @@ function display_clone_instructions { done } +function set_clone_exports { + export clone_destination=$1 + export clone_folder=$2 + export clone_folder_strip=$3 + export clone_project_name=$4 + export landingzone_branch=$5 +} function clone_repository { echo "@calling clone_repository" @@ -103,51 +117,58 @@ function clone_repository { function process_clone_parameter { echo "@calling process_clone_parameter with $@" - case "${1}" in - --clone) - if [ $# -eq 1 ]; then - display_clone_instructions ${1} - exit 21 - else - export caf_command="clone" - export landingzone_branch=${landingzone_branch:="master"} - export clone_project_name=${2} - export clone_folder_strip=1 - fi - ;; - --clone-branch) - echo $# - if [ $# -eq 1 ]; then - display_clone_instructions ${1} - exit 22 - else - export landingzone_branch=${2} - fi - ;; - --clone-destination) - if [ $# -eq 1 ]; then - display_clone_instructions ${1} - exit 23 - else - export clone_destination=${2} - fi - ;; - --clone-folder) - if [ $# -eq 1 ]; then - display_clone_instructions ${1} - exit 24 - else - export clone_folder=${2} - fi - ;; - --clone-folder-strip) - if [ $# -eq 1 ]; then - display_clone_instructions ${1} - exit 24 - else - export clone_folder_strip=${2} - fi - ;; + --clone) + if [ $# -eq 1 ]; then + display_clone_instructions ${1} + exit 21 + else + export caf_command="clone" + export landingzone_branch=${landingzone_branch:="master"} + export clone_project_name=${2} + export clone_folder_strip=1 + fi + ;; + --clone-branch) + echo $# + if [ $# -eq 1 ]; then + display_clone_instructions ${1} + exit 22 + else + export landingzone_branch=${2} + fi + ;; + --clone-destination) + if [ $# -eq 1 ]; then + display_clone_instructions ${1} + exit 23 + else + export clone_destination=${2} + fi + ;; + --clone-folder) + if [ $# -eq 1 ]; then + display_clone_instructions ${1} + exit 24 + else + export clone_folder=${2} + fi + ;; + --clone-folder-strip) + if [ $# -eq 1 ]; then + display_clone_instructions ${1} + exit 24 + else + export clone_folder_strip=${2} + fi + ;; + --clone-project-name) + if [ $# -eq 1 ]; then + display_clone_instructions ${1} + exit 24 + else + export clone_project_name=${2} + fi + ;; esac } diff --git a/scripts/functions.sh b/scripts/functions.sh index c0c4a3b9..4bed4fe2 100644 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -1,18 +1,16 @@ - - error() { local parent_lineno="$1" local message="$2" local code="${3:-1}" local line_message="" if [ "$parent_lineno" != "" ]; then - line_message="on or near line ${parent_lineno}" + line_message="on or near line ${parent_lineno}" fi - if [[ -n "$message" ]] ; then - >&2 echo -e "\e[41mError $line_message: ${message}; exiting with status ${code}\e[0m" + if [[ -n "$message" ]]; then + echo >&2 -e "\e[41mError $line_message: ${message}; exiting with status ${code}\e[0m" else - >&2 echo -e "\e[41mError $line_message; exiting with status ${code}\e[0m" + echo >&2 -e "\e[41mError $line_message; exiting with status ${code}\e[0m" fi echo "" @@ -22,15 +20,14 @@ error() { } error_message() { - >&2 printf "\e[91m$@\n\e[0m" + printf >&2 "\e[91m$@\n\e[0m" } - debug() { - local message=$1 - if [ "$debug_mode" == "true" ]; then - echo "$message" - fi + local message=$1 + if [ "$debug_mode" == "true" ]; then + echo "$message" + fi } information() { @@ -45,35 +42,32 @@ success() { # Execute a command and re-execute it with a backoff retry logic. This is mainly to handle throttling situations in CI # function execute_with_backoff { - local max_attempts=${ATTEMPTS-5} - local timeout=${TIMEOUT-20} - local attempt=0 - local exitCode=0 - - while [[ $attempt < $max_attempts ]] - do - set +e - "$@" - exitCode=$? - set -e - - if [[ $exitCode == 0 ]] - then - break - fi + local max_attempts=${ATTEMPTS-5} + local timeout=${TIMEOUT-20} + local attempt=0 + local exitCode=0 + + while [[ $attempt < $max_attempts ]]; do + set +e + "$@" + exitCode=$? + set -e + + if [[ $exitCode == 0 ]]; then + break + fi - echo "Failure! Return code ${exitCode} - Retrying in $timeout.." 1>&2 - sleep $timeout - attempt=$(( attempt + 1 )) - timeout=$(( timeout * 2 )) - done + echo "Failure! Return code ${exitCode} - Retrying in $timeout.." 1>&2 + sleep $timeout + attempt=$((attempt + 1)) + timeout=$((timeout * 2)) + done - if [[ $exitCode != 0 ]] - then - echo "Hit the max retry count ($@)" 1>&2 - fi + if [[ $exitCode != 0 ]]; then + echo "Hit the max retry count ($@)" 1>&2 + fi - return $exitCode + return $exitCode } function process_actions { @@ -84,6 +78,10 @@ function process_actions { workspace ${tf_command} exit 0 ;; + walkthrough) + execute_walkthrough + exit 0 + ;; clone) clone_repository exit 0 @@ -145,7 +143,7 @@ function display_instructions { if [ -d "/tf/caf/public/landingzones" ]; then for i in $(ls -d /tf/caf/public/landingzones/*); do echo ${i%%/}; done - echo "" + echo "" fi } @@ -215,24 +213,24 @@ function verify_azure_session { fi if [ "${caf_command}" == "logout" ]; then - echo "Closing Azure session" - az logout || true + echo "Closing Azure session" + az logout || true - # Cleaup any service principal session - unset ARM_TENANT_ID - unset ARM_SUBSCRIPTION_ID - unset ARM_CLIENT_ID - unset ARM_CLIENT_SECRET + # Cleaup any service principal session + unset ARM_TENANT_ID + unset ARM_SUBSCRIPTION_ID + unset ARM_CLIENT_ID + unset ARM_CLIENT_SECRET - echo "Azure session closed" - exit + echo "Azure session closed" + exit fi echo "Checking existing Azure session" session=$(az account show -o json 2>/dev/null || true) if [ "$session" == '' ]; then - display_login_instructions - error ${LINENO} "you must login to an Azure subscription first or 'rover login' again" 2 + display_login_instructions + error ${LINENO} "you must login to an Azure subscription first or 'rover login' again" 2 fi } @@ -242,7 +240,7 @@ function check_subscription_required_role { role=$(az role assignment list --role "${1}" --assignee ${TF_VAR_logged_user_objectId} --include-inherited --include-groups) if [ "${role}" == "[]" ]; then - error ${LINENO} "the current account must have ${1} privilege on the subscription to deploy launchpad." 2 + error ${LINENO} "the current account must have ${1} privilege on the subscription to deploy launchpad." 2 else echo "User is ${1} of the subscription" fi @@ -263,10 +261,10 @@ function list_deployed_landingzones { --subscription ${TF_VAR_tfstate_subscription_id} \ -c ${TF_VAR_workspace} \ --auth-mode login \ - --account-name ${storage_account_name} -o json | \ - jq -r '["landing zone", "size in Kb", "last modification"], (.[] | [.name, .properties.contentLength / 1024, .properties.lastModified]) | @csv' | \ - awk 'BEGIN{ FS=OFS="," }NR>1{ $2=sprintf("%.2f",$2) }1' | \ - column -t -s ',' + --account-name ${storage_account_name} -o json | + jq -r '["landing zone", "size in Kb", "last modification"], (.[] | [.name, .properties.contentLength / 1024, .properties.lastModified]) | @csv' | + awk 'BEGIN{ FS=OFS="," }NR>1{ $2=sprintf("%.2f",$2) }1' | + column -t -s ',' echo "" } @@ -301,7 +299,6 @@ function login_as_launchpad { export TF_VAR_tfstate_key=${TF_VAR_tf_name} - if [ ${caf_command} == "landingzone" ]; then if [ ${impersonate} = true ]; then @@ -339,29 +336,29 @@ function deploy_landingzone { RETURN_CODE=$? && echo "Terraform init return code ${RETURN_CODE}" case "${tf_action}" in - "plan") - echo "calling plan" - plan - ;; - "apply") - echo "calling plan and apply" - plan - apply - ;; - "validate") - echo "calling validate" - validate - ;; - "destroy") - echo "calling destroy" - destroy - ;; - "init") - echo "init no-op" - ;; - *) - other - ;; + "plan") + echo "calling plan" + plan + ;; + "apply") + echo "calling plan and apply" + plan + apply + ;; + "validate") + echo "calling validate" + validate + ;; + "destroy") + echo "calling destroy" + destroy + ;; + "init") + echo "init no-op" + ;; + *) + other + ;; esac rm -f "${TF_DATA_DIR}/tfstates/${TF_VAR_level}/${TF_VAR_workspace}/${TF_VAR_tf_plan}" @@ -378,23 +375,23 @@ function workspace { get_storage_id if [ "${id}" == "null" ]; then - display_launchpad_instructions - exit 1000 + display_launchpad_instructions + exit 1000 fi case "${1}" in - "list") - workspace_list - ;; - "create") - workspace_create ${2} - ;; - "delete") - workspace_delete ${2} - ;; - *) - echo "launchpad workspace [ list | create | delete ]" - ;; + "list") + workspace_list + ;; + "create") + workspace_create ${2} + ;; + "delete") + workspace_delete ${2} + ;; + *) + echo "launchpad workspace [ list | create | delete ]" + ;; esac } @@ -409,13 +406,13 @@ function workspace_list { export storage_account_name=$(echo ${stg} | jq -r .name) echo " Listing workspaces:" - echo "" + echo "" az storage container list \ --subscription ${TF_VAR_tfstate_subscription_id} \ --auth-mode "login" \ - --account-name ${storage_account_name} -o json | \ - jq -r '["workspace", "last modification", "lease ststus"], (.[] | [.name, .properties.lastModified, .properties.leaseStatus]) | @csv' | \ - column -t -s ',' + --account-name ${storage_account_name} -o json | + jq -r '["workspace", "last modification", "lease ststus"], (.[] | [.name, .properties.lastModified, .properties.leaseStatus]) | @csv' | + column -t -s ',' echo "" } @@ -430,7 +427,7 @@ function workspace_create { export storage_account_name=$(echo ${stg} | jq -r .name) echo " Create $1 workspace" - echo "" + echo "" az storage container create \ --subscription ${TF_VAR_tfstate_subscription_id} \ --name $1 \ @@ -451,7 +448,7 @@ function workspace_delete { export storage_account_name=$(echo ${stg} | jq -r .name) echo " Delete $1 workspace" - echo "" + echo "" az storage container delete \ --subscription ${TF_VAR_tfstate_subscription_id} \ --name $1 \ @@ -484,7 +481,7 @@ function clean_up_variables { unset ARM_ENVIRONMENT echo "clean_up backend_files" - find /tf/caf -name backend.azurerm.tf -delete + find /tf/caf -name backend.azurerm.tf -delete } @@ -498,15 +495,15 @@ function get_resource_from_assignedIdentityInfo { fi case $msi in - *"MSIResource"*) - msiResource=${msi//MSIResource-} + *"MSIResource"*) + msiResource=${msi//MSIResource-/} ;; - *"MSIClient"*) - msiResource=$(az identity list --query "[?clientId=='${msi//MSIClient-}'].{id:id}" -o tsv) + *"MSIClient"*) + msiResource=$(az identity list --query "[?clientId=='${msi//MSIClient-/}'].{id:id}" -o tsv) ;; - *) - echo "Warning: MSI identifier unknown." - msiResource=${msi//MSIResource-} + *) + echo "Warning: MSI identifier unknown." + msiResource=${msi//MSIResource-/} ;; esac @@ -524,16 +521,16 @@ function export_azure_cloud_env { if [ -z "$cloud_name" ]; then case $AZURE_ENVIRONMENT in - AzureCloud) + AzureCloud) tf_cloud_env='public' ;; - AzureChinaCloud) + AzureChinaCloud) tf_cloud_env='china' ;; - AzureUSGovernment) + AzureUSGovernment) tf_cloud_env='usgovernment' ;; - AzureGermanCloud) + AzureGermanCloud) tf_cloud_env='german' ;; esac @@ -700,33 +697,33 @@ function landing_zone { get_storage_id case "${1}" in - "list") - echo "Listing the deployed landing zones" - list_deployed_landingzones - ;; - *) - echo "rover landingzone [ list ]" - ;; + "list") + echo "Listing the deployed landing zones" + list_deployed_landingzones + ;; + *) + echo "rover landingzone [ list ]" + ;; esac } function expand_tfvars_folder { - echo " Expanding variable files: ${1}/*.tfvars" + echo " Expanding variable files: ${1}/*.tfvars" - for filename in "${1}"/*.tfvars; do - if [ "${filename}" != "${1}/*.tfvars" ]; then - PARAMS+="-var-file ${filename} " - fi - done + for filename in "${1}"/*.tfvars; do + if [ "${filename}" != "${1}/*.tfvars" ]; then + PARAMS+="-var-file ${filename} " + fi + done - echo " Expanding variable files: ${1}/*.tfvars.json" + echo " Expanding variable files: ${1}/*.tfvars.json" - for filename in "${1}"/*.tfvars.json; do - if [ "${filename}" != "${1}/*.tfvars.json" ]; then - PARAMS+="-var-file ${filename} " - fi - done + for filename in "${1}"/*.tfvars.json; do + if [ "${filename}" != "${1}/*.tfvars.json" ]; then + PARAMS+="-var-file ${filename} " + fi + done } # @@ -758,8 +755,8 @@ function process_target_subscription { account=$(az account show -o json) - target_subscription_name=$( echo ${account} | jq -r .name) - target_subscription_id=$( echo ${account} | jq -r .id) + target_subscription_name=$(echo ${account} | jq -r .name) + target_subscription_id=$(echo ${account} | jq -r .id) export ARM_SUBSCRIPTION_ID=$(echo ${account} | jq -r .id) @@ -769,22 +766,21 @@ function process_target_subscription { export TF_VAR_tfstate_subscription_id=${ARM_SUBSCRIPTION_ID} fi - export target_subscription_name=$( echo ${account} | jq -r .name) - export target_subscription_id=$( echo ${account} | jq -r .id) + export target_subscription_name=$(echo ${account} | jq -r .name) + export target_subscription_id=$(echo ${account} | jq -r .id) echo "caf_command ${caf_command}" echo "target_subscription_id ${target_subscription_id}" echo "TF_VAR_tfstate_subscription_id ${TF_VAR_tfstate_subscription_id}" # Check if rover mode is set to launchpad - if [[ ( "${caf_command}" == "launchpad" ) && ( "${target_subscription_id}" != "${TF_VAR_tfstate_subscription_id}" ) ]]; then + if [[ ("${caf_command}" == "launchpad") && ("${target_subscription_id}" != "${TF_VAR_tfstate_subscription_id}") ]]; then error 51 "To deploy the launchpad, the target and tfstate subscription must be the same." fi echo "Resources from this landing zone are going to be deployed in the following subscription:" echo ${account} | jq -r - echo "debug: ${TF_VAR_tfstate_subscription_id}" tfstate_subscription_name=$(az account show -s ${TF_VAR_tfstate_subscription_id} --output json | jq -r .name) echo "Tfstates subscription set to ${TF_VAR_tfstate_subscription_id} (${tfstate_subscription_name})" diff --git a/scripts/rover.sh b/scripts/rover.sh index e96654e7..3b0c9018 100755 --- a/scripts/rover.sh +++ b/scripts/rover.sh @@ -1,11 +1,11 @@ #!/bin/bash - # Initialize the launchpad first with rover # deploy a landingzone with # rover -lz [landingzone_folder_name] -a [plan | apply | destroy] [parameters] source /tf/rover/clone.sh +source /tf/rover/walkthrough.sh source /tf/rover/tfstate_azurerm.sh source /tf/rover/functions.sh source /tf/rover/banner.sh @@ -41,6 +41,10 @@ mkdir -p ${TF_PLUGIN_CACHE_DIR} while (( "$#" )); do case "${1}" in + --walkthrough) + export caf_command="walkthrough" + shift 1 + ;; --clone|--clone-branch|--clone-folder|--clone-destination|--clone-folder-strip) export caf_command="clone" process_clone_parameter $@ @@ -197,7 +201,6 @@ while (( "$#" )); do esac done - set -ETe trap 'error ${LINENO}' ERR 1 2 3 6 @@ -208,22 +211,25 @@ process_target_subscription echo "" echo "mode : '$(echo ${caf_command})'" -echo "terraform command output file : '$(echo ${tf_output_file})'" -echo "terraform plan output file : '$(echo ${tf_output_plan_file})'" -echo "tf_action : '$(echo ${tf_action})'" -echo "command and parameters : '$(echo ${tf_command})'" -echo "" -echo "level (current) : '$(echo ${TF_VAR_level})'" -echo "environment : '$(echo ${TF_VAR_environment})'" -echo "workspace : '$(echo ${TF_VAR_workspace})'" -echo "tfstate : '$(echo ${TF_VAR_tf_name})'" -echo "tfstate subscription id : '$(echo ${TF_VAR_tfstate_subscription_id})'" -echo "target subscription : '$(echo ${target_subscription_name})'" -echo "CI/CD enabled : '$(echo ${devops})'" -echo "Symphony Yaml file path : '$(echo ${symphony_yaml_file})'" -echo "Run all tasks : '$(echo ${symphony_run_all_tasks})'" + +if [ ${caf_command} != "walkthrough" ]; then + echo "terraform command output file : '$(echo ${tf_output_file})'" + echo "terraform plan output file : '$(echo ${tf_output_plan_file})'" + echo "tf_action : '$(echo ${tf_action})'" + echo "command and parameters : '$(echo ${tf_command})'" + echo "" + echo "level (current) : '$(echo ${TF_VAR_level})'" + echo "environment : '$(echo ${TF_VAR_environment})'" + echo "workspace : '$(echo ${TF_VAR_workspace})'" + echo "tfstate : '$(echo ${TF_VAR_tf_name})'" + echo "tfstate subscription id : '$(echo ${TF_VAR_tfstate_subscription_id})'" + echo "target subscription : '$(echo ${target_subscription_name})'" + echo "CI/CD enabled : '$(echo ${devops})'" + echo "Symphony Yaml file path : '$(echo ${symphony_yaml_file})'" + echo "Run all tasks : '$(echo ${symphony_run_all_tasks})'" +fi if [ $symphony_run_all_tasks == false ]; then - echo "Running task : '$(echo ${ci_task_name})'" + echo "Running task : '$(echo ${ci_task_name})'" fi echo "" diff --git a/scripts/tfstate_azurerm.sh b/scripts/tfstate_azurerm.sh index 50a20edf..e543771f 100644 --- a/scripts/tfstate_azurerm.sh +++ b/scripts/tfstate_azurerm.sh @@ -1,4 +1,3 @@ - function initialize_state { echo "@calling initialize_state" @@ -40,28 +39,28 @@ function initialize_state { RETURN_CODE=$? && echo "Line ${LINENO} - Terraform init return code ${RETURN_CODE}" case "${tf_action}" in - "plan") - echo "calling plan" - plan - ;; - "apply") - echo "calling plan and apply" - plan - apply - get_storage_id - upload_tfstate - ;; - "validate") - echo "calling validate" - validate - ;; - "destroy") - echo "No more tfstate file" - exit - ;; - *) - other - ;; + "plan") + echo "calling plan" + plan + ;; + "apply") + echo "calling plan and apply" + plan + apply + get_storage_id + upload_tfstate + ;; + "validate") + echo "calling validate" + validate + ;; + "destroy") + echo "No more tfstate file" + exit + ;; + *) + other + ;; esac rm -rf backend.azurerm.tf @@ -198,7 +197,6 @@ function destroy_from_remote_state { cd "${current_path}" } - function terraform_init_remote { case ${terraform_version} in @@ -242,7 +240,6 @@ function plan { pwd mkdir -p "${TF_DATA_DIR}/tfstates/${TF_VAR_level}/${TF_VAR_workspace}" - rm -f $STDERR_FILE case ${terraform_version} in @@ -270,9 +267,8 @@ function plan { cp "${TF_DATA_DIR}/tfstates/${TF_VAR_level}/${TF_VAR_workspace}/${TF_VAR_tf_plan}" "${tf_output_plan_file}" fi - if [ -s $STDERR_FILE ]; then - if [ ${tf_output_file+x} ]; then cat $STDERR_FILE >> ${tf_output_file}; fi + if [ ${tf_output_file+x} ]; then cat $STDERR_FILE >>${tf_output_file}; fi echo "Terraform returned errors:" cat $STDERR_FILE RETURN_CODE=2000 @@ -289,7 +285,6 @@ function apply { echo 'running terraform apply' rm -f $STDERR_FILE - case ${terraform_version} in *"15"* | *"1."*) echo "Terraform version 0.15 or greater" @@ -305,11 +300,10 @@ function apply { ;; esac - RETURN_CODE=$? && echo "Terraform apply return code: ${RETURN_CODE}" if [ -s $STDERR_FILE ]; then - if [ ${tf_output_file+x} ]; then cat $STDERR_FILE >> ${tf_output_file}; fi + if [ ${tf_output_file+x} ]; then cat $STDERR_FILE >>${tf_output_file}; fi echo "Terraform returned errors:" cat $STDERR_FILE RETURN_CODE=2001 @@ -330,7 +324,7 @@ function validate { RETURN_CODE=$? && echo "Terraform validate return code: ${RETURN_CODE}" if [ -s $STDERR_FILE ]; then - if [ ${tf_output_file+x} ]; then cat $STDERR_FILE >> ${tf_output_file}; fi + if [ ${tf_output_file+x} ]; then cat $STDERR_FILE >>${tf_output_file}; fi echo "Terraform returned errors:" cat $STDERR_FILE RETURN_CODE=2002 @@ -353,7 +347,6 @@ function destroy { echo " -TF_VAR_workspace: ${TF_VAR_workspace}" echo " -TF_VAR_tf_name: ${TF_VAR_tf_name}" - rm -f "${TF_DATA_DIR}/terraform.tfstate" sudo rm -f ${landingzone_name}/backend.azurerm.tf @@ -378,12 +371,12 @@ function destroy { terraform -chdir=${landingzone_name} \ destroy \ -refresh=false \ - ${tf_command} + ${tf_command} ${tf_approve} ;; *) terraform destroy \ -refresh=false \ - ${tf_command} \ + ${tf_command} ${tf_approve} \ ${landingzone_name} ;; esac @@ -405,7 +398,6 @@ function destroy { unset ARM_CLIENT_SECRET fi - case ${terraform_version} in *"15"* | *"1."*) echo "Terraform version 0.15 or greater" @@ -432,12 +424,12 @@ function destroy { *"15"* | *"1."*) echo "Terraform version 0.15 or greater" terraform -chdir=${landingzone_name} \ - destroy ${tf_command} \ + destroy ${tf_command} ${tf_approve} \ -refresh=false \ -state="${TF_DATA_DIR}/tfstates/${TF_VAR_level}/${TF_VAR_workspace}/${TF_VAR_tf_name}" ;; *) - terraform destroy ${tf_command} \ + terraform destroy ${tf_command} ${tf_approve} \ -refresh=false \ -state="${TF_DATA_DIR}/tfstates/${TF_VAR_level}/${TF_VAR_workspace}/${TF_VAR_tf_name}" \ ${landingzone_name} @@ -450,7 +442,6 @@ function destroy { fi fi - echo "Removing ${TF_DATA_DIR}/tfstates/${TF_VAR_level}/${TF_VAR_workspace}/${TF_VAR_tf_name}" rm -f "${TF_DATA_DIR}/tfstates/${TF_VAR_level}/${TF_VAR_workspace}/${TF_VAR_tf_name}" get_storage_id @@ -459,7 +450,7 @@ function destroy { echo "Delete state file on storage account:" echo " -tfstate: ${TF_VAR_tf_name}" stg_name=$(az storage account show \ - --ids ${id} -o json | \ + --ids ${id} -o json | jq -r .name) && echo " -stg_name: ${stg_name}" fileExists=$(az storage blob exists \ @@ -467,7 +458,7 @@ function destroy { --name ${TF_VAR_tf_name} \ --container-name ${TF_VAR_workspace} \ --auth-mode login \ - --account-name ${stg_name} -o json | \ + --account-name ${stg_name} -o json | jq .exists) if [ "${fileExists}" == "true" ]; then @@ -483,7 +474,7 @@ function destroy { fi fi - rm -rf ${TF_DATA_DIR}/tfstates/${TF_VAR_level}/${TF_VAR_workspace} + rm -rf ${TF_DATA_DIR}/tfstates/${TF_VAR_level}/${TF_VAR_workspace} clean_up_variables } @@ -502,7 +493,7 @@ function other { RETURN_CODE=$? && echo "Terraform ${tf_action} return code: ${RETURN_CODE}" if [ -s $STDERR_FILE ]; then - if [ ${tf_output_file+x} ]; then cat $STDERR_FILE >> ${tf_output_file}; fi + if [ ${tf_output_file+x} ]; then cat $STDERR_FILE >>${tf_output_file}; fi echo "Terraform returned errors:" cat $STDERR_FILE RETURN_CODE=2003 diff --git a/scripts/walkthrough.sh b/scripts/walkthrough.sh new file mode 100644 index 00000000..ec9f557d --- /dev/null +++ b/scripts/walkthrough.sh @@ -0,0 +1,283 @@ +#!/bin/bash + +export walkthrough_path="/tf/caf/walkthrough" +export config_name="demo" + +function execute_walkthrough { + clone_landing_zones + + clone_configurations + + select_walkthrough_config + + generate_walkthrough_assets + + execute_deployments +} + +function clone_landing_zones { + echo_section_break + echo "*************************************** Rover Walkthrough ***************************************" + echo "*************************************************************************************************" + echo "" + echo "This CAF Rover Walkthrough will guide you through a complete starter solution deployment." + echo "" + echo "Overview of steps:" + echo " 1. Download logic landingzones (Terraform modules)" + echo " 2. Download configuration files (Platform and Solution deployment specifications)" + echo " 3. Rover command generation (deploy and destroy)" + echo " 4. Deploy to Azure!" + echo "" + echo -n "Ready to get started? (y/n): " + read proceed + check_exit_case $proceed + echo_section_break + echo "" + echo "Step 1 - Download the logic repository. This exposes terraform modules for the launchpad and solution " + echo "landingzones. You can download this directly at any time and reuse for multiple deployments." + echo "" + echo "Download logic repository for walkthrough" + echo " - https://github.com/azure/caf-terraform-landingzones" + echo "" + echo -n "Ready to proceed? (y/n): " + read proceed + check_exit_case $proceed + + rm -rf ${walkthrough_path} + + set_clone_exports "${walkthrough_path}/landingzones" "/caf_launchpad" "1" "Azure/caf-terraform-landingzones" "master" + clone_repository + + set_clone_exports "${walkthrough_path}/landingzones" "/caf_solution" "1" "Azure/caf-terraform-landingzones" "master" + clone_repository + echo_section_break +} + +function clone_configurations { + echo "" + echo "Step 2 - Download the configuration repository. These contain terraform configuration files for the solution" + echo "sets you want to create and are organized by levels for the proper enterprise seperation of concerns." + echo "" + echo "Learn more about levels" + echo " - https://github.com/Azure/caf-terraform-landingzones/blob/master/documentation/code_architecture/hierarchy.md" + echo "" + echo "Examples to follow" + echo " - https://github.com/aztfmod/terraform-azurerm-caf/tree/master/examples" + echo "" + echo "Download configuration starter CAF repository for walkthrough" + echo " - https://github.com/Azure/caf-terraform-landingzones-starter" + echo "" + echo -n "Ready to proceed? (y/n): " + read proceed + check_exit_case $proceed + + set_clone_exports "${walkthrough_path}/configuration" "/configuration" "2" "Azure/caf-terraform-landingzones-starter" "starter" + # set_clone_exports "${walkthrough_path}/configuration" "/configuration" "2" "davesee/caf-terraform-landingzones-starter" "walkthrough" + clone_repository + echo_section_break +} + +function select_walkthrough_config { + echo "The following configurations were found in the starter repo. Currenlty ONLY demo works with the walkthrough. This was accomplished by standardizing the tfstate file names to match the containing folder name." + d=$(pwd) + cd ${walkthrough_path}/configuration/ + ls -d */ | sort | sed 's/\///' + cd $d + echo "" + + config="" + echo -n "Enter 'demo' to confirm this configuration: " + + while [ -z $config ]; do + read config + + if [ $config == "end" ]; then + echo "Goodbye!" + exit 0 + + elif [ -d "${walkthrough_path}/configuration/$config/" ]; then + echo "" + echo "Found configuration, removing the others..." + find /tf/caf/walkthrough/configuration -maxdepth 1 -mindepth 1 -type d ! -name $config + find /tf/caf/walkthrough/configuration -maxdepth 1 -mindepth 1 -type d ! -name $config -exec rm -rf {} + + + export config_name=$config + echo_section_break + else + echo "Configuration '$config' not found, please try again (configuration name): " + fi + done +} + +function generate_walkthrough_assets { + echo "" + echo "Step 3 - Generate the deployment and destroy scripts. These rover commands use the landingzone modules" + echo "and configuration tfvars files to execute Terraform init, plan, apply and destroy commands for you." + echo "" + echo "The launchpad in the Platform / Level 0 configuration is always deployed first and is used for remote" + echo "state storage. This pattern creates a self contained environment. State is also available between levels" + echo "as defined in each configuration's landingzone.tfvars global_settings_key and tfstates values." + echo "" + echo -n "Ready to proceed? (y/n): " + read proceed + check_exit_case $proceed + + rm -f ${walkthrough_path}/deploy.sh ${walkthrough_path}/destroy.sh ${walkthrough_path}/README.md + + write_md "# Deployment Steps" + init_sh + + index=0 + for path in $(find ${walkthrough_path}/configuration/${config_name} -type f \( -name "configuration.tfvars" -o -name "landingzone.tfvars" \) | sort); do + config_array[$index]="$(get_level_string $path):$path" + ((index += 1)) + done + + IFS=$'\n' sorted_ascending=($(sort <<<"${config_array[*]}")) + unset IFS + + for i in "${sorted_ascending[@]}"; do + level_a=$(echo $i | awk 'BEGIN {FS=":"}{print $1}') + config_a=$(echo $i | awk 'BEGIN {FS=":"}{print $2}') + echo "rover commands generated for $level_a deployment for $(basename ${config_a%\/*})" + + write_doc ${config_a%\/*} + write_bash_apply ${config_a%\/*} + done + + IFS=$'\n' sorted_descending=($(sort -r <<<"${config_array[*]}")) + unset IFS + + for i in "${sorted_descending[@]}"; do + level_d=$(echo $i | awk 'BEGIN {FS=":"}{print $1}') + config_d=$(echo $i | awk 'BEGIN {FS=":"}{print $2}') + write_bash_destroy ${config_d%\/*} + done + + end_sh + echo "" + echo "Deployment scripts and instructions generated!" + echo_section_break +} + +function execute_deployments { + echo "" + echo "Step 4 - Deploy to Azure. The generated bash script will deploy ALL levels ascending in order" + echo "for you. In an enterprise deployment, the platform configurations may be stored and deployed" + echo "in a repo with a pipeline of its own while solution application repos keep changes focused, " + echo "allow greater frequency of deployment and require lower level of permission." + echo "${walkthrough_path}/deploy.sh" + echo "" + echo "The destroy script removes the Azure levels and resources in reverse order. You can run this" + echo "manually at any time after the deployment to remove the Azure resources." + echo "${walkthrough_path}/destroy.sh" + echo "" + echo "You can also stop here and follow the README.md instructions to deploy individual configurations." + echo "${walkthrough_path}/README.md" + echo "" + echo "This deployment script creates ALL resources in the currently logged in Azure subscription" + echo "and is the final step, but may take up to an hour to run." + echo "" + echo -n "Ready to proceed? (y/n): " + read proceed + check_exit_case $proceed + + bash ${walkthrough_path}/deploy.sh & +} + +function get_level_string { + var_folder=$1 + + start=$(echo $var_folder | grep -b -o "level" | awk 'BEGIN {FS=":"}{print $1}') + level="level${var_folder:$(expr $start + 5):1}" + + echo $level +} + +function write_doc { + local var_folder=$1 + + local level=$(get_level_string "$var_folder") + + write_md "## Deploy $level $(basename $var_folder)" + write_md "\`\`\`bash" + write_md "rover \\" + [[ $(basename $var_folder) = "launchpad" ]] && write_md " -launchpad \\" + [[ $(basename $var_folder) = "launchpad" ]] && write_md " -lz ${walkthrough_path}/landingzones/caf_launchpad \\" || write_md " -lz ${walkthrough_path}/landingzones/caf_solution \\" + write_md " -var-folder $var_folder \\" + write_md " -tfstate $(basename $var_folder).tfstate \\" + write_md " -level $level \\" + write_md " -env ${config_name} \\" + write_md " -a [ apply | destroy | plan ]" + write_md "\`\`\`" +} + +function write_bash_apply { + local var_folder=$1 + + local level=$(get_level_string "$var_folder") + + write_sh "# Deploy $level $(basename $var_folder)" + write_sh "bash /tf/rover/rover.sh \\" + [[ $(basename $var_folder) = "launchpad" ]] && write_sh "-lz ${walkthrough_path}/landingzones/caf_launchpad \\" || write_sh "-lz ${walkthrough_path}/landingzones/caf_solution \\" + [[ $(basename $var_folder) = "launchpad" ]] && write_sh "-launchpad \\" + write_sh "-var-folder $var_folder \\" + write_sh "-tfstate $(basename $var_folder).tfstate \\" + write_sh "-level $level \\" + write_sh "-env ${config_name} \\" + write_sh "-a apply &" + write_sh "wait\n" +} + +function write_bash_destroy { + local var_folder=$1 + + local level=$(get_level_string "$var_folder") + + write_sh_destroy "# Deploy $level $(basename $var_folder)" + write_sh_destroy "bash /tf/rover/rover.sh \\" + [[ $(basename $var_folder) = "launchpad" ]] && write_sh_destroy "-lz ${walkthrough_path}/landingzones/caf_launchpad \\" || write_sh_destroy "-lz ${walkthrough_path}/landingzones/caf_solution \\" + [[ $(basename $var_folder) = "launchpad" ]] && write_sh_destroy "-launchpad \\" + write_sh_destroy "-var-folder $var_folder \\" + write_sh_destroy "-tfstate $(basename $var_folder).tfstate \\" + write_sh_destroy "-level $level \\" + write_sh_destroy "-env ${config_name} \\" + write_sh_destroy "-a destroy &" + write_sh_destroy "wait\n" +} + +function init_sh { + write_sh "#!/bin/bash\n\nfunction main {\n" + write_sh_destroy "#!/bin/bash\n\nexport tf_approve=--auto-approve\n\nfunction main {\n" +} + +function end_sh { + write_sh "}\n\nmain" + write_sh_destroy "}\n\nmain" + chmod +x ${walkthrough_path}/deploy.sh ${walkthrough_path}/destroy.sh +} + +function write_md { + echo -e $1 >>${walkthrough_path}/README.md +} + +function write_sh { + echo -e $1 >>${walkthrough_path}/deploy.sh +} + +function write_sh_destroy { + echo -e $1 >>${walkthrough_path}/destroy.sh +} + +function echo_section_break { + echo -e "\n*************************************************************************************************" +} + +function check_exit_case { + local proceed=$1 + + if [ $proceed != "y" ]; then + echo "Goodbye!" + exit 0 + fi +}