Skip to content
Aa

Terraform reference

The Terraform root pins f5-sales-demo/xcsh 15.0.0 and immutable commits of the origin server and traffic generator. It uses the signed-in Azure subscription, uses the selected deployer and environment, and stores local state under $HOME/.local/state/statistics. The two default East US 2 VMs are Standard_D16s_v3 and Standard_F16s_v2.

Use Terraform 1.8 or later and the Azure CLI in your prepared Bash session. For a running deployment, use its existing local state; initializing a different state path can recreate resources.

  1. Sign in to Azure and select the approved demo subscription:

    Terminal window
    az login
    az account set --subscription '<XCSH_AZURE_SUBSCRIPTION_ID>'
  2. Bind Terraform to that subscription:

    Terminal window
    export TF_VAR_subscription_id="<XCSH_AZURE_SUBSCRIPTION_ID>"
    export TF_CLI_CONFIG_FILE=/dev/null

Use the form values with the native Terraform environment interface. Keep credentials in XCSH_API_TOKEN; tfvars must contain no credentials. For an existing deployment, load its private identity overrides before planning.

Terminal window
export TF_VAR_namespace="<XCSH_NAMESPACE>"
export TF_VAR_domainname="<XCSH_DOMAINNAME>"
export TF_VAR_lb_name="<XCSH_LB_NAME>"
export TF_VAR_origin_pool_name="<XCSH_ORIGIN_POOL_NAME>"
export TF_VAR_healthcheck_name="<XCSH_HC_NAME>"
export TF_VAR_waf_name="<XCSH_WAF_NAME>"
export TF_VAR_deployer="<XCSH_DEPLOYER>"
export TF_VAR_environment="<XCSH_ENVIRONMENT>"
export TF_VAR_owner="<XCSH_OWNER>"
export TF_VAR_purpose="<XCSH_PURPOSE>"
export TF_VAR_timer_name="<XCSH_TIMER_NAME>"
export TF_VAR_location="<XCSH_AZURE_REGION>"
export TF_VAR_ssh_public_key_path="<XCSH_SSH_PUBLIC_KEY_PATH>"

A sanitized example tfvars file provides the same inputs. Copy the personalized block below to a private tfvars file; TF_VAR_subscription_id stays in the environment.

namespace = "<XCSH_NAMESPACE>"
domainname = "<XCSH_DOMAINNAME>"
lb_name = "<XCSH_LB_NAME>"
origin_pool_name = "<XCSH_ORIGIN_POOL_NAME>"
healthcheck_name = "<XCSH_HC_NAME>"
waf_name = "<XCSH_WAF_NAME>"
deployer = "<XCSH_DEPLOYER>"
environment = "<XCSH_ENVIRONMENT>"
owner = "<XCSH_OWNER>"
purpose = "<XCSH_PURPOSE>"
timer_name = "<XCSH_TIMER_NAME>"
location = "<XCSH_AZURE_REGION>"
ssh_public_key_path = "<XCSH_SSH_PUBLIC_KEY_PATH>"

For a first deployment, confirm that the hostname and named namespace objects are unowned. For this running demo, use the owner’s existing private state.

  1. Create the private state directory.

    Terminal window
    XCSH_STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/statistics"
    install -d -m 0700 "$XCSH_STATE_DIR"
  2. Initialize the local backend.

    Terminal window
    terraform -chdir=terraform init -input=false \
    -backend-config="path=$XCSH_STATE_DIR/terraform.tfstate"
  3. Validate the configuration.

    Terminal window
    terraform -chdir=terraform validate
  4. Save a plan outside Git.

    Terminal window
    terraform -chdir=terraform plan -input=false \
    -out="$XCSH_STATE_DIR/statistics.tfplan"
  5. Inspect the planned resource actions.

    Terminal window
    terraform -chdir=terraform show -json "$XCSH_STATE_DIR/statistics.tfplan" |
    jq -r '.resource_changes[] | [.address, (.change.actions | join(","))] | @tsv'

A first deployment plan should contain only creates for the dedicated VMs, networking, namespace, health check, origin pool, blocking WAF, HTTPS load balancer, and generator timer extension. Investigate any update, replacement, or deletion before applying. The origin module permits inbound ports 22, 80, 443, and 8888; the generator permits 22.

For a running deployment, use the same pinned configuration, subscription, credentials, and existing private state. A fresh read-only plan checks whether Terraform proposes any change:

Terminal window
XCSH_PLAN_EXIT=0
terraform -chdir=terraform plan -input=false -detailed-exitcode \
-out="$XCSH_STATE_DIR/statistics-idempotence.tfplan" || XCSH_PLAN_EXIT=$?
case "$XCSH_PLAN_EXIT" in
0) printf 'No changes; the deployment is idempotent.\n' ;;
2) printf 'Changes proposed; investigate before any apply.\n' ;;
1) printf 'Terraform plan failed; resolve the error.\n' ;;
esac
test "$XCSH_PLAN_EXIT" -eq 0

Exit 0 confirms no changes. Exit 2 requires investigation of the saved plan; exit 1 is an error. Retain plans and receipts outside Git. Running resource queries needs no infrastructure apply.

Apply the saved plan:

Terminal window
terraform -chdir=terraform apply -input=false "$XCSH_STATE_DIR/statistics.tfplan"

Follow Demo readiness after the origin and generator finish cloud-init. Keep the timer running so later API examples have fresh traffic.

The first reviewed apply completed on 5 October 2026. The local receipt reported:

Apply complete! Resources: 22 added, 0 changed, 0 destroyed.
Outputs:
demo_hostname = "<XCSH_DOMAINNAME>"
generator_vm_name = "<XCSH_GENERATOR_VM_NAME>"
origin_vm_name = "<XCSH_ORIGIN_VM_NAME>"

Keep the current demo running. Execute these commands only when its owner decides to retire the infrastructure. Use the same credentials, subscription, backend, and state as deployment.

Terminal window
test -f "$XCSH_STATE_DIR/terraform.tfstate"
terraform -chdir=terraform plan -destroy -input=false \
-out="$XCSH_STATE_DIR/statistics-destroy.tfplan"
terraform -chdir=terraform show -json "$XCSH_STATE_DIR/statistics-destroy.tfplan" |
jq -r '.resource_changes[] | [.address, (.change.actions | join(","))] | @tsv'

Review every resource address and deletion before applying that exact saved plan.

Terminal window
terraform -chdir=terraform apply -input=false "$XCSH_STATE_DIR/statistics-destroy.tfplan"

Verify that the owned namespace objects and demo DNS answer are absent. Retain the protected local state and ignored .env until the owner separately decides to remove them. State, saved plans, and cloud command output may contain credentials or infrastructure identities; keep them outside Git with owner-only permissions.