diff --git a/.gitignore b/.gitignore index 0136d95c..b8a0db71 100644 --- a/.gitignore +++ b/.gitignore @@ -54,3 +54,6 @@ Brewfile.lock.json # Ignore .vscode files .vscode/ + +# Ignore schematics json +/update/*.json diff --git a/main.tf b/main.tf index bcb68aec..0d235e84 100644 --- a/main.tf +++ b/main.tf @@ -27,8 +27,8 @@ locals { # For each subnet for subnet in range(length(var.subnets)) : { - name = "${var.prefix}-${(count) * length(var.subnets) + subnet + 1}" - vsi_name = "${var.prefix}-${format("%03d", count * length(var.subnets) + subnet + 1)}" + name = "${var.subnets[subnet].name}-${count}" + vsi_name = "${var.prefix}-${substr(var.subnets[subnet].id, -4, 4)}-${format("%03d", count + 1)}" subnet_id = var.subnets[subnet].id zone = var.subnets[subnet].zone subnet_name = var.subnets[subnet].name diff --git a/storage.tf b/storage.tf index f4ff25cb..46240aca 100644 --- a/storage.tf +++ b/storage.tf @@ -13,12 +13,12 @@ locals { # For each volume for volume in var.block_storage_volumes : { - name = "${var.prefix}-${(subnet) * (var.vsi_per_subnet) + count + 1}-${volume.name}" - vol_name = "${var.prefix}-${format("%03d", subnet * var.vsi_per_subnet + count + 1)}-${volume.name}" + name = "${var.subnets[subnet].name}-${count}-${volume.name}" + vol_name = "${var.prefix}-${substr(var.subnets[subnet].id, -4, 4)}-${format("%03d", count + 1)}-${volume.name}" zone = var.subnets[subnet].zone profile = volume.profile capacity = volume.capacity - vsi_name = "${var.prefix}-${(count) * length(var.subnets) + subnet + 1}" + vsi_name = "${var.subnets[subnet].name}-${count}" iops = volume.iops encryption_key = var.kms_encryption_enabled ? var.boot_volume_encryption_key : volume.encryption_key resource_group = volume.resource_group_id != null ? volume.resource_group_id : var.resource_group_id diff --git a/update/schematics_update_v3_to_v4.sh b/update/schematics_update_v3_to_v4.sh new file mode 100644 index 00000000..35f709b1 --- /dev/null +++ b/update/schematics_update_v3_to_v4.sh @@ -0,0 +1,304 @@ +#!/usr/bin/env bash + +PRG=$(basename -- "${0}") +USAGE=" +usage: ./${PRG} + + Required environment variables: + - IBMCLOUD_API_KEY + - WORKSPACE_ID + + Dependencies: + - IBM Cloud CLI + - IBM Cloud CLI 'is' plugin + - IBM Cloud CLI 'schematics' plugin + - jq +" +VOL_RESOURCES="" +VOL_NAMES="" +REVERT=false +VPC_IBMCLOUD_API_KEY="" + +helpFunction() { + echo "" + echo "Usage: $0 -v VPC_ID -r VPC_REGION [-k VPC_IBMCLOUD_API_KEY] [-z]" + echo -e "\t-v , separated IDs or names of the VPC where the VSIs are deployed and which need to be tracked by the newer version of the Terraform module." + echo -e "\t-r Region of the VPC." + echo -e "\t-k [Optional] IBMCLOUD_API_KEY to access the VPCs, if the VPCs are deployed in a different account." + echo -e "\t-z [Optional] Flag to revert the changes done to the state file." + exit 1 # Exit script after printing help +} + +while getopts "v:r:k:z" opt; do + case "$opt" in + v) VPC_ID="$OPTARG" ;; + r) VPC_REGION="$OPTARG" ;; + k) VPC_IBMCLOUD_API_KEY="$OPTARG" ;; + z) REVERT=true ;; + ?) helpFunction ;; # Print helpFunction in case parameter is non-existent + esac +done + +# Print helpFunction in case parameters are empty +if [ "$REVERT" == false ]; then + if [ -z "$VPC_ID" ] || [ -z "$VPC_REGION" ]; then + echo "VPC_ID or REGION is empty" + helpFunction + fi +fi + +function dependency_check() { + dependencies=("ibmcloud" "jq") + for dependency in "${dependencies[@]}"; do + if ! command -v "$dependency" >/dev/null 2>&1; then + echo "\"$dependency\" is not installed. Please install $dependency." + exit 1 + fi + done + plugin_dependencies=("schematics" "vpc-infrastructure") + for plugin_dependency in "${plugin_dependencies[@]}"; do + if ! ibmcloud plugin show "$plugin_dependency" >/dev/null; then + echo "\"$plugin_dependency\" ibmcloud plugin is not installed. Please install $plugin_dependency." + exit 1 + fi + done + echo "All dependencies are available!" +} + +# Check that env contains required vars +function verify_required_env_var() { + printf "\n#### VERIFYING ENV ####\n\n" + all_env_vars_exist=true + env_var_array=(IBMCLOUD_API_KEY WORKSPACE_ID) + set +u + for var in "${env_var_array[@]}"; do + [ -z "${!var}" ] && echo "${var} not defined." && all_env_vars_exist=false + done + set -u + if [ ${all_env_vars_exist} == false ]; then + echo "One or more required environment variables are not defined. Exiting." + echo "${USAGE}" + exit 1 + fi + printf "\nVerification complete\n" +} + +# Log in to IBM Cloud using IBMCLOUD_API_KEY env var value +function ibmcloud_login() { + printf "\n#### IBM CLOUD LOGIN ####\n\n" + WORKSPACE_REGION=$(echo "$WORKSPACE_ID" | cut -d "." -f 1) + attempts=1 + until ibmcloud login --apikey "$IBMCLOUD_API_KEY" -r "$WORKSPACE_REGION" || [ $attempts -ge 3 ]; do + attempts=$((attempts + 1)) + echo "Error logging in to IBM Cloud CLI..." + sleep 3 + done + printf "\nLogin complete\n" +} + +function get_workspace_details() { + template_id="$(ibmcloud schematics workspace get --id "$WORKSPACE_ID" -o json | jq -r .template_data[0].id)" + OUTPUT="$(ibmcloud schematics state pull --id "$WORKSPACE_ID" --template "$template_id")" + STATE=${OUTPUT//'OK'/} +} + +function update_state() { + if [[ -z "$VPC_IBMCLOUD_API_KEY" ]]; then + until ibmcloud target -r "$VPC_REGION" || [ "$attempts" -ge 3 ]; do + attempts=$((attempts + 1)) + echo "Error logging in to IBM Cloud CLI..." + sleep 3 + done + else + until ibmcloud login --apikey "$VPC_IBMCLOUD_API_KEY" -r "$VPC_REGION" || [ $attempts -ge 3 ]; do + attempts=$((attempts + 1)) + echo "Error logging in to IBM Cloud CLI..." + sleep 3 + done + fi + VPC_LIST=() + VPC_ID=${VPC_ID//","/" "} + IFS=' ' read -r -a VPC_LIST <<<"$VPC_ID" + for vpc in "${!VPC_LIST[@]}"; do + VPC_DATA=$(ibmcloud is vpc "${VPC_LIST[$vpc]//$'\n'/}" --output JSON --show-attached -q) + SUBNET_LIST=() + while IFS='' read -r line; do SUBNET_LIST+=("$line"); done < <(echo "$VPC_DATA" | jq -r '.subnets[] | .id') + ADDRESS_LIST=() + while IFS='' read -r line; do ADDRESS_LIST+=("$line"); done < <(echo "$STATE" | jq -r '.resources[] | select(.type == "ibm_is_instance") | .module') + + for i in "${!SUBNET_LIST[@]}"; do + for j in "${!ADDRESS_LIST[@]}"; do + VSI_RESOURCES="$(echo "$STATE" | jq -r --arg address "${ADDRESS_LIST[$j]}" '.resources[] | select((.type == "ibm_is_instance") and (.module == $address)) | .instances')" + subnet_name=$(echo "$VPC_DATA" | jq -r --arg subnet_id "${SUBNET_LIST[$i]}" '.subnets[] | select(.id == $subnet_id) | .name') + vsi_names=$(echo "$VSI_RESOURCES" | jq -r --arg subnet_id "${SUBNET_LIST[$i]}" '.[] | select(.attributes.primary_network_interface[0].subnet == $subnet_id) | .index_key') + VSI_LIST=() + IFS=$'\n' read -r -d '' -a VSI_LIST <<<"$vsi_names" + + for x in "${!VSI_LIST[@]}"; do + SOURCE="${ADDRESS_LIST[$j]}.ibm_is_instance.vsi[\"${VSI_LIST[$x]}\"]" + DESTINATION="${ADDRESS_LIST[$j]}.ibm_is_instance.vsi[\"${subnet_name}-${x}\"]" + + if [ -n "${VSI_LIST[$x]}" ] && [ -n "${subnet_name}" ]; then + MOVED_PARAMS+=("$SOURCE, $DESTINATION") + REVERT_PARAMS+=("$DESTINATION, $SOURCE") + fi + if [ -n "${VSI_LIST[$x]}" ]; then + VOL_NAMES=$(echo "$VSI_RESOURCES" | jq -r --arg vsi "${VSI_LIST[$x]}" '.[] | select(.index_key == $vsi) | .attributes.volume_attachments[].volume_name') + + fi + if [ -n "${VSI_LIST[$x]}" ]; then + FIP_RESOURCES="$(echo "$STATE" | jq -r --arg address "${ADDRESS_LIST[$j]}" '.resources[] | select((.type == "ibm_is_floating_ip") and (.module == $address)) | .instances')" + fi + if [ -n "$FIP_RESOURCES" ]; then + FIP_SOURCE="${ADDRESS_LIST[$j]}.ibm_is_floating_ip.vsi_fip[\"${VSI_LIST[$x]}\"]" + FIP_DESTINATION="${ADDRESS_LIST[$j]}.ibm_is_floating_ip.vsi_fip[\"${subnet_name}-${x}\"]" + if [ -n "${VSI_LIST[$x]}" ] && [ -n "${subnet_name}" ]; then + MOVED_PARAMS+=("$FIP_SOURCE, $FIP_DESTINATION") + REVERT_PARAMS+=("$FIP_DESTINATION, $FIP_SOURCE") + fi + fi + str="${VSI_LIST[$x]}" + lastIndex=$(echo "$str" | awk '{print length}') + for ((l = lastIndex; l >= 0; l--)); do + if [[ "${str:$l:1}" == "-" ]]; then + str="${str::l}" + break + fi + done + if [ -n "$VOL_NAMES" ]; then + VOL_ADDRESS_LIST=() + while IFS='' read -r line; do VOL_ADDRESS_LIST+=("$line"); done < <(echo "$STATE" | jq -r '.resources[] | select(.type == "ibm_is_volume") | .module') + VOL_NAME=() + IFS=$'\n' read -r -d '' -a VOL_NAME <<<"$VOL_NAMES" + for a in "${!VOL_NAME[@]}"; do + for b in "${!VOL_ADDRESS_LIST[@]}"; do + VOL_RESOURCES="$(echo "$STATE" | jq -r --arg address "${VOL_ADDRESS_LIST[$b]}" '.resources[] | select((.type == "ibm_is_volume") and (.module == $address)) | .instances')" + vol_names=$(echo "$VOL_RESOURCES" | jq -r --arg vol1 "${VOL_NAME[$a]}" '.[] | select(.attributes.name == $vol1) | .index_key') + VOL_LIST=() + IFS=$'\n' read -r -d '' -a VOL_LIST <<<"$vol_names" + for c in "${!VOL_LIST[@]}"; do + if [ -n "${VOL_LIST[$c]}" ]; then + VOL_SOURCE="${ADDRESS_LIST[$j]}.ibm_is_volume.volume[\"${VOL_LIST[$c]}\"]" + test="${VOL_LIST[$c]/$str/}" + vol=$(echo "$test" | cut -d"-" -f3-) + VOL_DESTINATION="${ADDRESS_LIST[$j]}.ibm_is_volume.volume[\"${subnet_name}-${x}-${vol}\"]" + if [ -n "${VOL_LIST[$c]}" ] || [ -n "${subnet_name}" ]; then + MOVED_PARAMS+=("$VOL_SOURCE, $VOL_DESTINATION") + REVERT_PARAMS+=("$VOL_DESTINATION, $VOL_SOURCE") + fi + fi + done + done + done + fi + done + done + done + done + +} + +function update_schematics() { + if [[ -z "$VPC_IBMCLOUD_API_KEY" ]]; then + until ibmcloud target -r "$WORKSPACE_REGION" || [ "$attempts" -ge 3 ]; do + attempts=$((attempts + 1)) + echo "Error logging in to IBM Cloud CLI..." + sleep 3 + done + else + until ibmcloud login --apikey "$IBMCLOUD_API_KEY" -r "$WORKSPACE_REGION" || [ $attempts -ge 3 ]; do + attempts=$((attempts + 1)) + echo "Error logging in to IBM Cloud CLI..." + sleep 3 + done + fi + + ibmcloud schematics workspace commands --id "$WORKSPACE_ID" --file ./moved.json +} + +function revert_schematics() { + if ! [ -f "./revert.json" ]; then + echo "Revert.json does not exist." + else + if [ -s "./revert.json" ]; then + if [[ -z "$VPC_IBMCLOUD_API_KEY" ]]; then + until ibmcloud target -r "$WORKSPACE_REGION" || [ "$attempts" -ge 3 ]; do + attempts=$((attempts + 1)) + echo "Error logging in to IBM Cloud CLI..." + sleep 3 + done + else + until ibmcloud login --apikey "$IBMCLOUD_API_KEY" -r "$WORKSPACE_REGION" || [ $attempts -ge 3 ]; do + attempts=$((attempts + 1)) + echo "Error logging in to IBM Cloud CLI..." + sleep 3 + done + fi + ibmcloud schematics workspace commands --id "$WORKSPACE_ID" --file ./revert.json + else + echo "Revert.json is empty." + fi + fi + +} +create_json() { + for movedparam in "${!MOVED_PARAMS[@]}"; do + jq --arg command_params "${MOVED_PARAMS[$movedparam]}" --arg command_name "Move$movedparam" '.commands += [{"command": "state mv","command_params": $command_params, "command_name": $command_name, "command_onerror": "abort"}]' moved.json >temp.json && mv temp.json moved.json + done + for revertparam in "${!REVERT_PARAMS[@]}"; do + jq --arg command_params "${REVERT_PARAMS[$revertparam]}" --arg command_name "Revert$revertparam" '.commands += [{"command": "state mv","command_params": $command_params, "command_name": $command_name, "command_onerror": "continue"}]' revert.json >temp.json && mv temp.json revert.json + done + jq '.commands += [{"command": "state list","command_params": "", "command_name": "Test", "command_onerror": "continue"}]' revert.json >temp.json && mv temp.json revert.json +} + +create_json_files() { + # Define the file path and content + MOVED_JSON="./moved.json" + REVERT_JSON="./revert.json" + + # Check if the file exists + if [ -f "$MOVED_JSON" ] || [ -f "$REVERT_JSON" ]; then + # If the file exists, empty it + echo "" >"$MOVED_JSON" + echo "" >"$REVERT_JSON" + else + # If the file does not exist, create it + touch "$MOVED_JSON" + touch "$REVERT_JSON" + fi + + # Add new content to the file + echo '{ + "commands": [], + "operation_name": "workspace Command", + "description": "Executing command" + } + ' >>$MOVED_JSON + + echo '{ + "commands": [], + "operation_name": "workspace Command", + "description": "Executing command" + } + ' >>$REVERT_JSON +} + +# run +function main() { + if [ "$REVERT" == false ]; then + dependency_check + create_json_files + verify_required_env_var + ibmcloud_login + get_workspace_details + update_state + create_json + update_schematics + else + verify_required_env_var + ibmcloud_login + revert_schematics + fi +} + +main diff --git a/update/update-version.md b/update/update-version.md new file mode 100644 index 00000000..7f8723ca --- /dev/null +++ b/update/update-version.md @@ -0,0 +1,192 @@ +# Updating from v3 to v4 + +Version 4 changes the VSI module code in ways that result in significant changes when you update from version 3 to 4. When you update, the Virtual Server Instances (VSIs) that are managed by this module are deleted and re-created. + +:information_source: **Tip:** VSIs in v4 have a new prefix naming convention `prefix- + the last 4 digits of the subnet ID + a sequential number for each subnet`. For example, `prefix-3ad7-001`. When you update, your VSIs will adopt the new prefix. + +Follow these steps to update to version 4 and avoid the need to re-create the VSIs. + +## Before you begin + +Make sure you have recent versions of these command-line prerequisites. + +- [IBM Cloud CLI](https://cloud.ibm.com/docs/cli?topic=cli-getting-started) +- [IBM Cloud CLI plug-ins](https://cloud.ibm.com/docs/cli?topic=cli-plug-ins): + - `is` plug-in (vpc-infrastructure) + - For IBM Schematics deployments: `sch` plug-in (schematics) +- JSON processor `jq` (https://jqlang.github.io/jq/) +- [Curl](). To test whether curl is installed on your system, run the following command: + + ```sh + curl -V + ``` + + If you need to install curl, see https://everything.curl.dev/install/index.html. + +## Select a procedure + +Select the procedure that matches where you deployed the code. + +- [Deployed with Schematics](#deployed-with-schematics) +- [Local Terraform](#local-terraform) + +## Deployed with Schematics + +If you deployed your IBM Cloud infrastructure by using Schematics, the `schematics_update_v3_to_v4.sh` script creates a Schematics job. [View the script](schematics_update_v3_to_v4.sh). + +### Schematics process + +1. Set the environment variables: + + 1. Set the IBM Cloud API key that has access to your IBM Cloud project or Schematics workspace. Run the following command: + + ```sh + export IBMCLOUD_API_KEY="" #pragma: allowlist secret + ``` + + Replace `` with the value of your API key. + + 1. Find your Schematics workspace ID: + - If you are using IBM Cloud Projects: + 1. Go to [Projects](https://cloud.ibm.com/projects) + 1. Select the project that is associated with your VSI deployment. + 1. Click the **Configurations** tab. + 1. Click the configuration name that is associated with your VSI deployment. + 1. Under **Workspace** copy the ID. + + - If you are not using IBM Cloud Projects: + 1. Go to [Schematics Workspaces](https://cloud.ibm.com/schematics/workspaces) + 1. Select the location that the workspace is in. + 1. Select the workspace associated with your VSI deployment. + 1. Click **Settings**. + 1. Copy the **Workspace ID**. + + 1. Run the following command to set the workspace ID as an environment variable: + + ```sh + export WORKSPACE_ID="" + ``` + +1. Download the script by running this Curl command: + + ```sh + curl https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-landing-zone-vsi/main/update/schematics_update_v3_to_v4.sh > schematics_update_v3_to_v4.sh + ``` + +1. Use the IBM Cloud console to get the VPC IDs and regions of VPCs that the VSIs are deployed in. + + :information_source: **Tip:** Make sure that you're logged in with the account that owns the VSIs. This account might be different from your projects or Schematics account. + + 1. Click the Navigation menu on the left, and then click **VPC Infrastructure** > **VPCs**. + 1. Select the region in which the VPC of the VSI was deployed. + 1. Select the VPC that is associated with the VSI deployment. + 1. Copy the **VPC ID**. +1. Run the script: + + ```sh + bash schematics_update_v3_to_v4.sh -v "[,,...]" -r "" [-k ""] + ``` + + - Replace `` and `` with the information that you copied earlier. + - If the VSIs are deployed in an account that is different from the account where the Schematics workspace is located, include the `-k` option and replace `` with the IBM Cloud API key with access to the VPC. + + The script creates a job in the Schematics workspace. + +1. Monitor the status of the job by selecting the workspace from your [Schematics workspaces dashboard](https://cloud.ibm.com/schematics/workspaces). + - When the job completes successfully, go to the next step. + - If the job fails, see [Reverting changes](#reverting-changes). + +### Apply the changes in Schematics + +1. Update your code to consume version 4, and then update your Schematics workspace to the version of the code that contains the updated module. Click **Generate plan** and make sure none of the VSIs will be re-created. + + You should see in-place updates to names. No resources should be set to be destroyed or re-created. +1. Click **Apply plan**. + + If the job is successful, follow the steps in [Clean up](#clean-up). If the job fails, see [Reverting changes](#reverting-changes). + +## Local Terraform + +If you store both the Terraform code and state file locally, run the `update_v3_to_v4.sh` script locally. [View the script](schematics_update_v3_to_v4.sh). + +1. Set the IBM Cloud API key that has access to your VPCs as an environment variable by running the following command: + + ```sh + export IBMCLOUD_API_KEY="" #pragma: allowlist secret + ``` + + Replace `` with the value of your API key. + +1. Get the VPC IDs and the regions of any VPCs that the VSIs are deployed in + + - Get the VPC IDs from the Terraform output by running the `terraform output` command. Or use the following `jq` command to parse the IDs from the state: + + ```sh + terraform output -json | jq -r '.. | objects | .value' | jq -r '.. | objects | select(.vpc_id != null) | .vpc_id' | sort -u | xargs + ``` + + - Get the region by running the following `jq` command: + + ```sh + terraform output -json | jq -r '.. | objects | .value' | jq -r '.. | objects | select(.vpc_id != null) | .zone | select(. != null)' | rev | cut -c3- | rev | sort -u | xargs + ``` + +1. Download the script to the directory with the state file by running this Curl command: + + ```sh + curl https://raw.githubusercontent.com/terraform-ibm-modules/terraform-ibm-landing-zone-vsi/main/update/update_v3_to_v4.sh > update_v3_to_v4.sh + ``` + +1. Run the script from the directory with the state file: + + ```sh + bash update_v3_to_v4.sh -v "[,,..]" -r "" + ``` + + - Replace `` and `` with the information that you found earlier. + + If the job fails, see [Reverting changes](#reverting-changes). + +1. Initialize, check the planned changes, and apply the changes: + 1. Update the version of the module in your consuming code to the 4 version, as in this example: + + ```hcl + source = "terraform-ibm-modules/landing-zone-vsi/ibm" + version = "4.0.0" + ``` + + 1. Run the `terraform init` command to pull the latest version. + 1. Run the `terraform plan` command to make sure that none of the VSIs will be re-created. + + - You should see in-place updates to names. No resources should be set to be destroyed or re-created. + - The name changes include a prefix change: `prefix + the last 4 digits of the subnet ID + a sequential number for each subnet`. + + For example, + + ```sh + ~ name = "prefix-001" -> "prefix-3ad7-001" + ``` + + 1. Run `terraform apply` to upgrade to the 4 version of the module and apply the update in place to rename the VSIs. + 1. If the commands are successful, follow the steps in [Clean up](#clean-up). + +### Clean up + +After you upgrade to the newer release of the VSI module, you can remove the temporary files that are generated by the script by running this command: + +```sh +rm moved.json revert.json +``` + +## Reverting changes + +If the script fails, run the script again with the `-z` option to undo the changes. The script uses the `revert.json` file that was created when you ran the script without the `-z` option. + +```sh +bash schematics_update_v3_to_v4.sh -z +``` + +- If you ran the job in Schematics, a new workspace job reverts the state to what existed before you ran the script initially. +- If your code and state file are on your computer, the script reverts changes to the local Terraform state file. + +:exclamation: **Important:** After you revert the changes, don't run any other steps in this process. Create an IBM Cloud support case and include information about the script and errors. For more information, see [Creating support cases](https://cloud.ibm.com/docs/get-support?topic=get-support-open-case&interface=ui). diff --git a/update/update_v3_to_v4.sh b/update/update_v3_to_v4.sh new file mode 100644 index 00000000..0e696351 --- /dev/null +++ b/update/update_v3_to_v4.sh @@ -0,0 +1,237 @@ +#!/usr/bin/env bash + +PRG=$(basename -- "${0}") +USAGE=" +usage: ./${PRG} + + Required environment variables: + - IBMCLOUD_API_KEY + + Dependencies: + - IBM Cloud CLI + - IBM Cloud CLI 'is' plugin + - Terraform CLI + - jq +" + +STATE_LOCATION="" +VOL_RESOURCES="" +REVERT=false + +helpFunction() { + echo "" + echo "Usage: $0 -v \"VPC_ID_1[,VPC_ID_2,..]\" -r \"REGION\" [-t \"STATE_LOCATION\"] [-z]" + echo -e "\t-v comma separated IDs or names of the VPC in which the VSIs are deployed." + echo -e "\t-r Region of the VPC(s)." + echo -e "\t-t [Optional] Path of the terrafom state file. If no path is specified, the current working directory will be used." + echo -e "\t-z [Optional] Flag to revert the changes done to the state file." + exit 1 # Exit script after printing help +} + +while getopts "v:r:t:z" opt; do + case "$opt" in + v) VPC_ID="$OPTARG" ;; + r) REGION="$OPTARG" ;; + t) STATE_LOCATION="$OPTARG" ;; + z) REVERT=true ;; + ?) helpFunction ;; # Print helpFunction in case parameter is non-existent + esac +done + +# Print helpFunction in case parameters are empty +if [ "$REVERT" == false ]; then + if [ -z "$VPC_ID" ] || [ -z "$REGION" ]; then + echo "VPC_ID or REGION is empty" + helpFunction + fi +fi + +function dependency_check() { + dependencies=("ibmcloud" "jq") + for dependency in "${dependencies[@]}"; do + if ! command -v "$dependency" >/dev/null 2>&1; then + echo "\"$dependency\" is not installed. Please install $dependency." + exit 1 + fi + done + plugin_dependencies=("vpc-infrastructure") + for plugin_dependency in "${plugin_dependencies[@]}"; do + if ! ibmcloud plugin show "$plugin_dependency" >/dev/null; then + echo "\"$plugin_dependency\" ibmcloud plugin is not installed. Please install $plugin_dependency." + exit 1 + fi + done + echo "All dependencies are available!" +} + +# check that env contains required vars +function verify_required_env_var() { + printf "\n#### VERIFYING ENV ####\n\n" + all_env_vars_exist=true + env_var_array=(IBMCLOUD_API_KEY) + set +u + for var in "${env_var_array[@]}"; do + [ -z "${!var}" ] && echo "${var} not defined." && all_env_vars_exist=false + done + set -u + if [ ${all_env_vars_exist} == false ]; then + echo "One or more required environment variables are not defined. Exiting." + echo "${USAGE}" + exit 1 + fi + printf "\nVerification complete\n" +} + +# Login to IBM Cloud using IBMCLOUD_API_KEY env var value +function ibmcloud_login() { + printf "\n#### IBM CLOUD LOGIN ####\n\n" + attempts=1 + until ibmcloud login --apikey "$IBMCLOUD_API_KEY" -r "$REGION" || [ $attempts -ge 3 ]; do + attempts=$((attempts + 1)) + echo "Error logging in to IBM Cloud CLI..." + sleep 3 + done + printf "\nLogin complete\n" +} + +function get_details() { + terraform init -upgrade + if [ -z "$STATE_LOCATION" ]; then + STATE=$(terraform show -json) + else + STATE=$(terraform show -json "$STATE_LOCATION") + fi +} + +function update_state() { + VPC_LIST=() + VPC_ID=${VPC_ID//","/" "} + IFS=' ' read -r -a VPC_LIST <<<"$VPC_ID" + for vpc in "${!VPC_LIST[@]}"; do + VPC_DATA=$(ibmcloud is vpc "${VPC_LIST[$vpc]//$'\n'/}" --output JSON --show-attached -q) + SUBNET_LIST=() + while IFS='' read -r line; do SUBNET_LIST+=("$line"); done < <(echo "$VPC_DATA" | jq -r '.subnets[] | .id') + + for i in "${!SUBNET_LIST[@]}"; do + subnet_name=$(echo "$VPC_DATA" | jq -r --arg subnet_id "${SUBNET_LIST[$i]}" '.subnets[] | select(.id == $subnet_id) | .name') + vsi_names=$(echo "$STATE" | jq -r --arg subnet "${SUBNET_LIST[$i]}" '.. | objects | select((.values.primary_network_interface[0].subnet == $subnet) and (.type == "ibm_is_instance")) | .index') + + VSI_LIST=() + IFS=$'\n' read -r -d '' -a VSI_LIST <<<"$vsi_names" + + for j in "${!VSI_LIST[@]}"; do + SOURCE=$(echo "$STATE" | jq -r --arg vsi "${VSI_LIST[$j]}" '.. | objects | select((.index == $vsi) and (.type == "ibm_is_instance")) | .address') + DESTINATION=${SOURCE//"${VSI_LIST[$j]}"/"${subnet_name}-${j}"} + + if [ -n "$SOURCE" ] || [ -n "$DESTINATION" ]; then + MOVED_PARAMS+=("'$SOURCE' '$DESTINATION'") + REVERT_PARAMS+=("'$DESTINATION' '$SOURCE'") + fi + if [ -n "${VSI_LIST[$j]}" ]; then + VOL_RESOURCES=$(echo "$STATE" | jq -r --arg vsi "${VSI_LIST[$j]}" '.. | objects | select((.index == $vsi) and (.type == "ibm_is_instance")) | .values.volume_attachments[].volume_name') + fi + if [ -n "${VSI_LIST[$j]}" ]; then + FIP_RESOURCES=$(echo "$STATE" | jq -r --arg vsi "${VSI_LIST[$j]}" '.. | objects | select((.index == $vsi) and (.type == "ibm_is_floating_ip")) | .index') + fi + if [ -n "$FIP_RESOURCES" ]; then + FIP_SOURCE=$(echo "$STATE" | jq -r --arg vsi "${VSI_LIST[$j]}" '.. | objects | select((.index == $vsi) and (.type == "ibm_is_floating_ip")) | .address') + FIP_DESTINATION=${FIP_SOURCE//"${VSI_LIST[$j]}"/"${subnet_name}-${j}"} + if [ -n "$FIP_SOURCE" ] || [ -n "$FIP_DESTINATION" ]; then + MOVED_PARAMS+=("'$FIP_SOURCE' '$FIP_DESTINATION'") + REVERT_PARAMS+=("'$FIP_DESTINATION' '$FIP_SOURCE'") + fi + fi + if [ -n "$VOL_RESOURCES" ]; then + str="${VSI_LIST[$j]}" + lastIndex=$(echo "$str" | awk '{print length}') + for ((l = lastIndex; l >= 0; l--)); do + if [[ "${str:$l:1}" == "-" ]]; then + str="${str::l}" + break + fi + done + VOL_LIST=() + IFS=$'\n' read -r -d '' -a VOL_LIST <<<"$VOL_RESOURCES" + for x in "${!VOL_LIST[@]}"; do + VOL_SOURCE=$(echo "$STATE" | jq -r --arg index "${VOL_LIST[$x]}" '.. | objects | select((.values.name == $index) and (.type == "ibm_is_volume")) | .address') + + if [ -n "$VOL_SOURCE" ]; then + VOL_INDEX=$(echo "$STATE" | jq -r --arg index "${VOL_LIST[$x]}" '.. | objects | select((.values.name == $index) and (.type == "ibm_is_volume")) | .index') + test="${VOL_LIST[$x]/$str/}" + vol=$(echo "$test" | cut -d"-" -f3-) + VOL_DESTINATION=${VOL_SOURCE//"$VOL_INDEX"/"${subnet_name}-${j}-${vol}"} + if [ -n "$VOL_SOURCE" ] || [ -n "$VOL_DESTINATION" ]; then + MOVED_PARAMS+=("'$VOL_SOURCE' '$VOL_DESTINATION'") + REVERT_PARAMS+=("'$VOL_DESTINATION' '$VOL_SOURCE'") + fi + fi + done + fi + done + done + done +} +function update_local_state() { + while read -r line; do + eval "$line" + done <"./moved.txt" +} + +function revert_local_state() { + if ! [ -f "./revert.txt" ]; then + echo "Revert.txt does not exist." + else + if [ -s "./revert.txt" ]; then + while read -r line; do + eval "$line" + done <"./revert.txt" + else + echo "Revert.txt is empty." + fi + fi + +} +create_txt() { + for movedparam in "${!MOVED_PARAMS[@]}"; do + echo "terraform state mv ${MOVED_PARAMS[$movedparam]}" >>./moved.txt + done + for revertparam in "${!REVERT_PARAMS[@]}"; do + echo "terraform state mv ${REVERT_PARAMS[$revertparam]}" >>./revert.txt + done +} + +create_txt_files() { + # Define the file path and content + MOVED_JSON="./moved.txt" + REVERT_JSON="./revert.txt" + + # Check if the file exists + if [ -f "$MOVED_JSON" ] || [ -f "$REVERT_JSON" ]; then + # If the file exists, empty it + echo "" >"$MOVED_JSON" + echo "" >"$REVERT_JSON" + else + # If the file does not exist, create it + touch "$MOVED_JSON" + touch "$REVERT_JSON" + fi + +} + +# run +function main() { + if [ "$REVERT" == false ]; then + dependency_check + create_txt_files + verify_required_env_var + ibmcloud_login + get_details + update_state + create_txt + update_local_state + else + revert_local_state + fi +} + +main