{"id":1233,"date":"2026-10-13T10:45:55","date_gmt":"2026-10-13T10:45:55","guid":{"rendered":"https:\/\/www.devopsschool.com\/tutorials\/?p=1233"},"modified":"2026-10-13T10:45:57","modified_gmt":"2026-10-13T10:45:57","slug":"terraform-lab-azure-hands-on-conditional-expressions-and-logic","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/tutorials\/terraform-lab-azure-hands-on-conditional-expressions-and-logic\/","title":{"rendered":"Terraform Lab \u2013 Azure Hands-On \u2013\u00a0Conditional Expressions and Logic"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This workbook teaches the complete set of Terraform conditionals, filtering, validation, null handling, and related functions from the requested syllabus. Every numbered concept includes&nbsp;<strong>one standalone AWS EC2 example and one standalone Azure Ubuntu Linux VM example<\/strong>, with provider configuration, authentication instructions, network dependencies, complete HCL, observable outputs, a change-to-test command, and cleanup steps.<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Important:<\/strong>&nbsp;Each example is&nbsp;<strong>independent<\/strong>. Do NOT paste all 64 Terraform examples into a single directory. The full&nbsp;<code>main.tf<\/code>&nbsp;for each example is included below, and the companion ZIP contains one directory per cloud\/scenario, pre-populated with the same files. Only the conditional-module scenario uses a small local child module; both files are included.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">A. Learning objectives<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">After the workbook, students can:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Select values with ternary expressions and combine logical\/comparison operators.<\/li>\n\n\n\n<li>Create zero\/one resources using\u00a0<code>count<\/code>\u00a0and filter selected resources with\u00a0<code>for_each<\/code>.<\/li>\n\n\n\n<li>Conditionally create repeatable nested configuration via\u00a0<code>dynamic<\/code>.<\/li>\n\n\n\n<li>Distinguish\u00a0<em>variable validation<\/em>,\u00a0<em>resource preconditions\/postconditions<\/em>,\u00a0<em>output preconditions<\/em>, and\u00a0<em>nonblocking checks<\/em>.<\/li>\n\n\n\n<li>Use\u00a0<code>try<\/code>,\u00a0<code>can<\/code>,\u00a0<code>lookup<\/code>,\u00a0<code>coalesce<\/code>,\u00a0<code>coalescelist<\/code>,\u00a0<code>contains<\/code>,\u00a0<code>alltrue<\/code>,\u00a0<code>anytrue<\/code>,\u00a0<code>length<\/code>, regex,\u00a0<code>startswith<\/code>, and\u00a0<code>endswith<\/code>\u00a0safely.<\/li>\n\n\n\n<li>Work with\u00a0<code>null<\/code>, optional attributes,\u00a0<code>nullable<\/code>, conditional maps,\u00a0<code>merge<\/code>,\u00a0<code>compact<\/code>, and\u00a0<code>flatten<\/code>.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">B. Correct mental model<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>condition ? true_value : false_value<\/code><\/strong>\u00a0selects one value. Terraform does not have a general-purpose\u00a0<code>if { } else { }<\/code>\u00a0statement for resource declarations.<\/li>\n\n\n\n<li><strong><code>count<\/code><\/strong>\u00a0repeats a resource using numeric indexes (<code>[0]<\/code>,\u00a0<code>[1]<\/code>, &#8230;).\u00a0<strong><code>for_each<\/code><\/strong>\u00a0repeats it using stable keys. Use\u00a0<code>count = condition ? 1 : 0<\/code>\u00a0for an optional VM.<\/li>\n\n\n\n<li><strong><code>for<\/code>\u00a0with\u00a0<code>if<\/code><\/strong>\u00a0filters data; it does not directly create cloud resources unless its result is supplied to something like\u00a0<code>for_each<\/code>.<\/li>\n\n\n\n<li><strong><code>dynamic<\/code><\/strong>\u00a0makes zero\/more\u00a0<em>supported nested blocks<\/em>. It cannot generate top-level\u00a0<code>resource<\/code>,\u00a0<code>module<\/code>,\u00a0<code>lifecycle<\/code>, or\u00a0<code>provisioner<\/code>\u00a0blocks.<\/li>\n\n\n\n<li><strong>Preconditions \/ postconditions<\/strong>\u00a0stop an operation when their assumptions fail. A\u00a0<strong><code>check<\/code><\/strong>\u00a0emits warnings but normally does not stop plan\/apply.<\/li>\n\n\n\n<li><strong><code>try()<\/code>\u00a0and\u00a0<code>can()<\/code><\/strong>\u00a0catch expression evaluation errors, not arbitrary Terraform syntax errors or invalid references to undeclared resources.<\/li>\n\n\n\n<li><strong><code>null<\/code><\/strong>\u00a0normally means omit an optional resource argument; it cannot satisfy a required argument.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">C. Cloud prerequisites (do once per cloud)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">AWS prerequisites<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>An AWS account with EC2, VPC, security-group, SSM Parameter Store read access, and IAM permissions to create\/delete the listed resources.<\/li>\n\n\n\n<li>Terraform 1.15.x \/ 1.16.x and AWS CLI. Configure AWS authentication using\u00a0<code>aws configure<\/code>\u00a0or AWS SSO.\u00a0<strong>Do not put credentials in Terraform code.<\/strong><\/li>\n\n\n\n<li>Region\u00a0<strong><code>us-east-1<\/code><\/strong>; instance type\u00a0<strong><code>t3.micro<\/code><\/strong>\u00a0(large alternative:\u00a0<code>t3.small<\/code>);\u00a0<strong>Amazon Linux 2023<\/strong>\u00a0uses the official SSM public parameter instead of a region-specific hard-coded AMI ID.<\/li>\n\n\n\n<li>AWS configurations build their own VPC, subnet, Internet Gateway, route table, security group (no inbound access), and EC2 instance(s).\u00a0<strong>SSH is not enabled<\/strong>; the learning goal is Terraform logic, observed through plan, AWS CLI, and outputs. To add shell access later, use a separately secured SSM\/EC2 Instance Connect lab.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>aws sts get-caller-identity\naws configure get region\naws ssm get-parameter --region us-east-1 \\\n  --name \/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64 \\\n  --query Parameter.Value --output text\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Azure prerequisites<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>An Azure subscription, Azure CLI, permissions to create resource groups\/networks\/VMs, and registered\u00a0<code>Microsoft.Compute<\/code>\u00a0\/\u00a0<code>Microsoft.Network<\/code>\u00a0namespaces.<\/li>\n\n\n\n<li>Region\u00a0<strong><code>eastus<\/code><\/strong>; VM size\u00a0<strong><code>Standard_B1s<\/code><\/strong>\u00a0(large alternative:\u00a0<code>Standard_B2s<\/code>); official Canonical Ubuntu 24.04 LTS image\u00a0<strong><code>Canonical:ubuntu-24_04-lts:server:latest<\/code><\/strong>.<\/li>\n\n\n\n<li>Each Azure lab includes a Resource Group, VNet, subnet, Network Security Group and SSH rule, subnet-NSG association, public IP, NIC, OS disk and Ubuntu VM (except when a condition sets VM count to zero). SSH is allowed\u00a0<strong>only from the IP in\u00a0<code>TF_VAR_admin_cidr<\/code><\/strong>.<\/li>\n\n\n\n<li>Azure VM password is supplied at runtime,\u00a0<strong>never hard-coded in the file<\/strong>. The password is still stored in Terraform state; protect the state, shell session and saved plan. For production prefer SSH keys \/ managed access.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>az login\naz account set --subscription \"YOUR_AZURE_SUBSCRIPTION_ID\"\nexport ARM_SUBSCRIPTION_ID=\"$(az account show --query id -o tsv)\"\n\n<em># Set the external IPv4 address of the workstation used for SSH.<\/em>\nexport TF_VAR_admin_cidr=\"$(curl -fsS -4 https:\/\/api.ipify.org)\/32\"\n\n<em># Interactive, private Azure VM password input (12+ chars, strong complexity).<\/em>\nprintf \"Enter a strong Azure lab VM password: \"\nread -r -s TF_VAR_vm_password\nprintf \"\n\"\nexport TF_VAR_vm_password\n\n<em># Image and VM SKU verification (region\/subscription restrictions may apply)<\/em>\naz vm image show --location eastus \\\n  --urn Canonical:ubuntu-24_04-lts:server:latest --output table\naz vm list-skus --location eastus --size Standard_B1s --all --output table\n<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">The provided Azure source-IP variable is required for all Azure labs, even if you never SSH. The IP restriction is designed for supervised short-lived training. Change it if your current office\/VPN egress IP changes. Never use&nbsp;<code>0.0.0.0\/0<\/code>&nbsp;for password-based SSH access.<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">General per-lab workflow<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Use&nbsp;<strong>one directory and state per example<\/strong>&nbsp;(the ZIP already supplies them):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><em># Example; choose one cloud and scenario directory.<\/em>\ncd aws\/01-ternary        <em># or: cd azure\/01-ternary<\/em>\nterraform init\nterraform fmt -recursive\nterraform fmt -check -recursive\nterraform validate\nterraform plan\nterraform apply\nterraform output\n\n<em># Inspect the EC2 or Azure VM \/ outputs, try that scenario's test command,<\/em>\n<em># and DESTROY the lab before moving on to avoid charges.<\/em>\nterraform plan -destroy\nterraform destroy\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>For cloud verification:\u00a0<strong>AWS<\/strong>\u00a0<code>terraform output -json instance_ids<\/code>, then check those IDs in the AWS EC2 Console (or use\u00a0<code>aws ec2 describe-instances<\/code>).\u00a0<strong>Azure<\/strong>\u00a0<code>az vm list -g rg-cond-01-eastus -o table<\/code>\u00a0(substitute the current scenario number).<\/li>\n\n\n\n<li>In all examples, a\u00a0<em>test<\/em>\u00a0command shown as\u00a0<code>terraform plan -var=...<\/code>\u00a0demonstrates changed configuration without automatically applying it. If you apply a test change, use the same override at destroy time or restore the default value before cleaning up; carefully inspect plans for replacements.<\/li>\n\n\n\n<li>Azure VM size\/image availability, subscription quotas, required provider registrations, Azure policies and cloud costs can prevent an otherwise correctly structured lab from deploying. Avoid running all 64 labs concurrently.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">D. Complete 32-concept syllabus and standalone files<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-right\" data-align=\"right\">#<\/th><th class=\"has-text-align-left\" data-align=\"left\">Concept<\/th><th class=\"has-text-align-left\" data-align=\"left\">AWS folder<\/th><th class=\"has-text-align-left\" data-align=\"left\">Azure folder<\/th><\/tr><\/thead><tbody><tr><td class=\"has-text-align-right\" data-align=\"right\">1<\/td><td>Conditional expression (ternary)<\/td><td><code>aws\/01-ternary<\/code><\/td><td><code>azure\/01-ternary<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">2<\/td><td>Logical AND (&amp;&amp;)<\/td><td><code>aws\/02-and<\/code><\/td><td><code>azure\/02-and<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">3<\/td><td>Logical OR (<\/td><td><\/td><td>)<\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">4<\/td><td>Logical NOT (!)<\/td><td><code>aws\/04-not<\/code><\/td><td><code>azure\/04-not<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">5<\/td><td>Comparison operators<\/td><td><code>aws\/05-comparisons<\/code><\/td><td><code>azure\/05-comparisons<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">6<\/td><td>Parentheses and compound logic<\/td><td><code>aws\/06-compound<\/code><\/td><td><code>azure\/06-compound<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">7<\/td><td>count with condition<\/td><td><code>aws\/07-count<\/code><\/td><td><code>azure\/07-count<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">8<\/td><td>for_each with filtering<\/td><td><code>aws\/08-for-each<\/code><\/td><td><code>azure\/08-for-each<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">9<\/td><td>for expressions with if<\/td><td><code>aws\/09-for-if<\/code><\/td><td><code>azure\/09-for-if<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">10<\/td><td>Conditional dynamic blocks<\/td><td><code>aws\/10-dynamic<\/code><\/td><td><code>azure\/10-dynamic<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">11<\/td><td>Conditional module instantiation<\/td><td><code>aws\/11-module<\/code><\/td><td><code>azure\/11-module<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">12<\/td><td>Input variable validation<\/td><td><code>aws\/12-validation<\/code><\/td><td><code>azure\/12-validation<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">13<\/td><td>Resource precondition<\/td><td><code>aws\/13-precondition<\/code><\/td><td><code>azure\/13-precondition<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">14<\/td><td>Resource postcondition<\/td><td><code>aws\/14-postcondition<\/code><\/td><td><code>azure\/14-postcondition<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">15<\/td><td>Output precondition<\/td><td><code>aws\/15-output-precondition<\/code><\/td><td><code>azure\/15-output-precondition<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">16<\/td><td>check block (warning)<\/td><td><code>aws\/16-check<\/code><\/td><td><code>azure\/16-check<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">17<\/td><td>try()<\/td><td><code>aws\/17-try<\/code><\/td><td><code>azure\/17-try<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">18<\/td><td>can()<\/td><td><code>aws\/18-can<\/code><\/td><td><code>azure\/18-can<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">19<\/td><td>lookup()<\/td><td><code>aws\/19-lookup<\/code><\/td><td><code>azure\/19-lookup<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">20<\/td><td>coalesce()<\/td><td><code>aws\/20-coalesce<\/code><\/td><td><code>azure\/20-coalesce<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">21<\/td><td>coalescelist()<\/td><td><code>aws\/21-coalescelist<\/code><\/td><td><code>azure\/21-coalescelist<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">22<\/td><td>contains()<\/td><td><code>aws\/22-contains<\/code><\/td><td><code>azure\/22-contains<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">23<\/td><td>alltrue()<\/td><td><code>aws\/23-alltrue<\/code><\/td><td><code>azure\/23-alltrue<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">24<\/td><td>anytrue()<\/td><td><code>aws\/24-anytrue<\/code><\/td><td><code>azure\/24-anytrue<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">25<\/td><td>length()<\/td><td><code>aws\/25-length<\/code><\/td><td><code>azure\/25-length<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">26<\/td><td>regex() and regexall()<\/td><td><code>aws\/26-regex<\/code><\/td><td><code>azure\/26-regex<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">27<\/td><td>startswith() and endswith()<\/td><td><code>aws\/27-startswith-endswith<\/code><\/td><td><code>azure\/27-startswith-endswith<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">28<\/td><td>null values<\/td><td><code>aws\/28-null<\/code><\/td><td><code>azure\/28-null<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">29<\/td><td>Optional object attributes<\/td><td><code>aws\/29-optional<\/code><\/td><td><code>azure\/29-optional<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">30<\/td><td>nullable variable setting<\/td><td><code>aws\/30-nullable<\/code><\/td><td><code>azure\/30-nullable<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">31<\/td><td>merge() and conditional maps<\/td><td><code>aws\/31-merge<\/code><\/td><td><code>azure\/31-merge<\/code><\/td><\/tr><tr><td class=\"has-text-align-right\" data-align=\"right\">32<\/td><td>compact() and flatten()<\/td><td><code>aws\/32-compact-flatten<\/code><\/td><td><code>azure\/32-compact-flatten<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">1. Conditional expression (ternary)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Expressions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Choose an EC2\/VM size with condition ? value_if_true : value_if_false.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;A ternary is a one-line choice: test a Boolean expression, then use either the first or second value. Terraform has no resource-level&nbsp;<code>if\/else<\/code>&nbsp;statement, so this is the usual way to select a VM size or optional setting. Both result branches should have compatible types.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/01-ternary\/main.tf<\/code>. Default: small instance. With environment=prod: larger instance.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 01: Conditional expression (ternary)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-01-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-01-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-01-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-01-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-01-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-01-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = var.environment == \"prod\" ? \"t3.small\" : \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-01-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"selected_size\" {\n  value = var.environment == \"prod\" ? \"t3.small\" : \"t3.micro\"\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=prod'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Default: small instance. With environment=prod: larger instance. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/01-ternary\/main.tf<\/code>. Default: small instance. With environment=prod: larger instance.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 01: Conditional expression (ternary)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-01-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-01\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-01\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-01\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-01\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-01\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-01\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = var.environment == \"prod\" ? \"Standard_B2s\" : \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"selected_size\" {\n  value = var.environment == \"prod\" ? \"Standard_B2s\" : \"Standard_B1s\"\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=prod'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Default: small instance. With environment=prod: larger instance. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">2. Logical AND (&amp;&amp;)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Expressions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Both conditions must be true before enabling an option.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>&amp;&amp;<\/code>&nbsp;means both conditions must be true. Here, environment must be production&nbsp;<strong>and<\/strong>&nbsp;monitoring must be requested. The EC2 example enables detailed EC2 monitoring (which can cost extra); the Azure example demonstrates the decision as a tag, not a Monitor Agent installation.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/02-and\/main.tf<\/code>. The decision is true only when both inputs are true. AWS uses EC2 detailed monitoring; Azure records the decision as a VM tag (not Azure Monitor deployment).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 02: Logical AND (&amp;&amp;)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"prod\"\n}\n\nvariable \"enable_monitoring\" {\n  type = bool\n  default = true\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  monitor_enabled = var.environment == \"prod\" &amp;&amp; var.enable_monitoring\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-02-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-02-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-02-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-02-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-02-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-02-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  monitoring = local.monitor_enabled\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-02-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"monitoring_decision\" {\n  value = local.monitor_enabled\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='enable_monitoring=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;The decision is true only when both inputs are true. AWS uses EC2 detailed monitoring; Azure records the decision as a VM tag (not Azure Monitor deployment). Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/02-and\/main.tf<\/code>. The decision is true only when both inputs are true. AWS uses EC2 detailed monitoring; Azure records the decision as a VM tag (not Azure Monitor deployment).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 02: Logical AND (&amp;&amp;)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"prod\"\n}\n\nvariable \"enable_monitoring\" {\n  type = bool\n  default = true\n}\nlocals {\n  monitor_enabled = var.environment == \"prod\" &amp;&amp; var.enable_monitoring\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-02-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-02\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-02\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-02\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-02\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-02\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-02\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { MonitoringRequested = tostring(local.monitor_enabled) }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"monitoring_decision\" {\n  value = local.monitor_enabled\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='enable_monitoring=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;The decision is true only when both inputs are true. AWS uses EC2 detailed monitoring; Azure records the decision as a VM tag (not Azure Monitor deployment). Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">3. Logical OR (||)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Expressions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Match either of two environments to select a tag.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>||<\/code>&nbsp;means either condition may be true. The&nbsp;<code>dev<\/code>&nbsp;and&nbsp;<code>test<\/code>&nbsp;values both count as nonproduction, and anything else takes the other branch. This affects visible tags rather than requiring extra networking.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/03-or\/main.tf<\/code>. dev or test -&gt; nonproduction=true; prod -&gt; false.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 03: Logical OR (||)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  nonproduction = var.environment == \"dev\" || var.environment == \"test\"\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-03-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-03-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-03-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-03-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-03-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-03-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = local.nonproduction ? \"logic-nonprod\" : \"logic-production\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"nonproduction\" {\n  value = local.nonproduction\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=prod'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;dev or test -&gt; nonproduction=true; prod -&gt; false. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/03-or\/main.tf<\/code>. dev or test -&gt; nonproduction=true; prod -&gt; false.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 03: Logical OR (||)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n}\nlocals {\n  nonproduction = var.environment == \"dev\" || var.environment == \"test\"\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-03-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-03\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-03\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-03\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-03\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-03\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-03\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Tier = local.nonproduction ? \"nonprod\" : \"production\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"nonproduction\" {\n  value = local.nonproduction\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=prod'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;dev or test -&gt; nonproduction=true; prod -&gt; false. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">4. Logical NOT (!)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Expressions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Invert a flag to choose a smaller or larger VM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>!<\/code>&nbsp;reverses a Boolean.&nbsp;<code>!false<\/code>&nbsp;is true;&nbsp;<code>!true<\/code>&nbsp;is false. The VM size selection makes it easy to see what changes when you flip the flag.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/04-not\/main.tf<\/code>. NOT reverses the Boolean. false -&gt; smaller; true -&gt; larger.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 04: Logical NOT (!)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"high_capacity\" {\n  type = bool\n  default = false\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-04-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-04-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-04-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-04-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-04-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-04-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = !var.high_capacity ? \"t3.micro\" : \"t3.small\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-04-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"small_selected\" {\n  value = !var.high_capacity\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='high_capacity=true'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;NOT reverses the Boolean. false -&gt; smaller; true -&gt; larger. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/04-not\/main.tf<\/code>. NOT reverses the Boolean. false -&gt; smaller; true -&gt; larger.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 04: Logical NOT (!)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"high_capacity\" {\n  type = bool\n  default = false\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-04-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-04\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-04\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-04\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-04\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-04\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-04\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = !var.high_capacity ? \"Standard_B1s\" : \"Standard_B2s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"small_selected\" {\n  value = !var.high_capacity\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='high_capacity=true'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;NOT reverses the Boolean. false -&gt; smaller; true -&gt; larger. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">5. Comparison operators<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Expressions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Compare numbers and expose ==, !=, &gt;, &lt;, &gt;=, &lt;=.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;Comparison operators produce&nbsp;<code>true<\/code>&nbsp;or&nbsp;<code>false<\/code>. The output map shows equality (<code>==<\/code>), inequality (<code>!=<\/code>), greater\/less-than and their inclusive variants. The sample also uses&nbsp;<code>&gt; 2<\/code>&nbsp;to select the larger VM.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/05-comparisons\/main.tf<\/code>. Outputs show all six comparison operators; target above 2 selects the larger VM.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 05: Comparison operators\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"replica_target\" {\n  type = number\n  default = 2\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  comparisons = {\n    eq  = var.replica_target == 2\n    ne  = var.replica_target != 2\n    gt  = var.replica_target &gt; 2\n    lt  = var.replica_target &lt; 2\n    gte = var.replica_target &gt;= 2\n    lte = var.replica_target &lt;= 2\n  }\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-05-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-05-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-05-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-05-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-05-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-05-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = local.comparisons.gt ? \"t3.small\" : \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-05-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"comparison_results\" {\n  value = local.comparisons\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='replica_target=3'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Outputs show all six comparison operators; target above 2 selects the larger VM. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/05-comparisons\/main.tf<\/code>. Outputs show all six comparison operators; target above 2 selects the larger VM.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 05: Comparison operators\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"replica_target\" {\n  type = number\n  default = 2\n}\nlocals {\n  comparisons = {\n    eq  = var.replica_target == 2\n    ne  = var.replica_target != 2\n    gt  = var.replica_target &gt; 2\n    lt  = var.replica_target &lt; 2\n    gte = var.replica_target &gt;= 2\n    lte = var.replica_target &lt;= 2\n  }\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-05-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-05\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-05\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-05\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-05\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-05\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-05\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = local.comparisons.gt ? \"Standard_B2s\" : \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"comparison_results\" {\n  value = local.comparisons\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='replica_target=3'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Outputs show all six comparison operators; target above 2 selects the larger VM. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">6. Parentheses and compound logic<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Expressions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Use grouping to control operator precedence.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;Parentheses show which parts should be evaluated together. This lab checks&nbsp;<code>prod AND (critical OR force)<\/code>. Explicit grouping makes multi-part logic easier to read and review.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">6.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/06-compound\/main.tf<\/code>. Grouping evaluates the OR first, then AND with environment=prod.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 06: Parentheses and compound logic\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"prod\"\n}\n\nvariable \"critical\" {\n  type = bool\n  default = false\n}\n\nvariable \"force\" {\n  type = bool\n  default = true\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  selected = (var.environment == \"prod\") &amp;&amp; (var.critical || var.force)\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-06-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-06-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-06-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-06-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-06-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-06-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = local.selected ? \"t3.small\" : \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-06-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"compound_result\" {\n  value = local.selected\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='force=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Grouping evaluates the OR first, then AND with environment=prod. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">6.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/06-compound\/main.tf<\/code>. Grouping evaluates the OR first, then AND with environment=prod.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 06: Parentheses and compound logic\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"prod\"\n}\n\nvariable \"critical\" {\n  type = bool\n  default = false\n}\n\nvariable \"force\" {\n  type = bool\n  default = true\n}\nlocals {\n  selected = (var.environment == \"prod\") &amp;&amp; (var.critical || var.force)\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-06-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-06\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-06\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-06\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-06\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-06\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-06\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = local.selected ? \"Standard_B2s\" : \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"compound_result\" {\n  value = local.selected\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='force=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Grouping evaluates the OR first, then AND with environment=prod. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">7. count with condition<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Resource creation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Create zero or one VM based on a Boolean.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;Set&nbsp;<code>count<\/code>&nbsp;to&nbsp;<code>1<\/code>&nbsp;to make one VM, or&nbsp;<code>0<\/code>&nbsp;to make none. The surrounding VPC\/VNet networking stays because its resources are not conditional. Using a splat (<code>[*].id<\/code>) makes outputs safe even when the VM count is zero.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">7.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/07-count\/main.tf<\/code>. Default creates one VM. false creates zero VMs (the network resources remain).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 07: count with condition\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"create_vm\" {\n  type = bool\n  default = true\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-07-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-07-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-07-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-07-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-07-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-07-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n  count = var.create_vm ? 1 : 0\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-07-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = aws_instance.lab&#91;*].id\n}\n\noutput \"public_ips\" {\n  value = aws_instance.lab&#91;*].public_ip\n}\n\noutput \"vm_created\" {\n  value = var.create_vm\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='create_vm=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Default creates one VM. false creates zero VMs (the network resources remain). Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">7.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/07-count\/main.tf<\/code>. Default creates one VM. false creates zero VMs (the network resources remain).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 07: count with condition\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"create_vm\" {\n  type = bool\n  default = true\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-07-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-07\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-07\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-07\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-07\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-07\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n  count = var.create_vm ? 1 : 0\n  name                            = \"vm-cond-07\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = azurerm_linux_virtual_machine.lab&#91;*].id\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"vm_created\" {\n  value = var.create_vm\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='create_vm=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Default creates one VM. false creates zero VMs (the network resources remain). Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">8. for_each with filtering<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Resource creation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Create only enabled VMs from a map.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>for_each<\/code>&nbsp;makes one VM per item in a map. The preceding&nbsp;<code>for ... if enabled<\/code>&nbsp;expression removes disabled entries before Terraform creates anything. Azure must also make one NIC and public IP for each selected VM.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">8.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/08-for-each\/main.tf<\/code>. Default creates two VMs, for web and api. jobs is excluded.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 08: for_each with filtering\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"workloads\" {\n  type = map(bool)\n  default = { web = true, jobs = false, api = true }\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  enabled_workloads = { for name, enabled in var.workloads : name =&gt; enabled if enabled }\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-08-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-08-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-08-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-08-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-08-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-08-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n  for_each = local.enabled_workloads\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-08-${each.key}\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;for vm in values(aws_instance.lab) : vm.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;for vm in values(aws_instance.lab) : vm.public_ip]\n}\n\noutput \"selected_workloads\" {\n  value = keys(local.enabled_workloads)\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='workloads={web=true,jobs=true,api=false}'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Default creates two VMs, for web and api. jobs is excluded. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">8.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/08-for-each\/main.tf<\/code>. Default creates two VMs, for web and api. jobs is excluded.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 08: for_each with filtering\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"workloads\" {\n  type = map(bool)\n  default = { web = true, jobs = false, api = true }\n}\nlocals {\n  enabled_workloads = { for name, enabled in var.workloads : name =&gt; enabled if enabled }\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-08-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-08\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-08\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-08\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n  for_each = local.enabled_workloads\n  name                = \"pip-cond-08-${each.key}\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n  for_each = local.enabled_workloads\n  name                = \"nic-cond-08-${each.key}\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab&#91;each.key].id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n  for_each = local.enabled_workloads\n  name                            = \"vm-cond-08-${each.key}\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab&#91;each.key].id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;for vm in values(azurerm_linux_virtual_machine.lab) : vm.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;for pip in values(azurerm_public_ip.lab) : pip.ip_address]\n}\n\noutput \"selected_workloads\" {\n  value = keys(local.enabled_workloads)\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='workloads={web=true,jobs=true,api=false}'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Default creates two VMs, for web and api. jobs is excluded. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">9. for expressions with if<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Resource creation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Filter names with a for expression before using the result.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;A&nbsp;<code>for<\/code>&nbsp;expression transforms a collection, and its trailing&nbsp;<code>if<\/code>&nbsp;filters out unwanted items. The lab creates one VM but publishes the filtered list in a tag and output, keeping the example focused on data selection.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/09-for-if\/main.tf<\/code>. Filters out batch01 and keeps web01, web02. Creates one demonstration VM; the filtered list is exposed in a tag and output.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 09: for expressions with if\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"workload_names\" {\n  type = list(string)\n  default = &#91;\"web01\", \"batch01\", \"web02\"]\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  web_names = &#91;for name in var.workload_names : name if startswith(name, \"web\")]\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-09-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-09-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-09-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-09-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-09-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-09-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-web-filter\", Selected = join(\",\", local.web_names) }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"selected_workload_names\" {\n  value = local.web_names\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='workload_names=&#91;\"batch\",\"web03\"]'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Filters out batch01 and keeps web01, web02. Creates one demonstration VM; the filtered list is exposed in a tag and output. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">9.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/09-for-if\/main.tf<\/code>. Filters out batch01 and keeps web01, web02. Creates one demonstration VM; the filtered list is exposed in a tag and output.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 09: for expressions with if\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"workload_names\" {\n  type = list(string)\n  default = &#91;\"web01\", \"batch01\", \"web02\"]\n}\nlocals {\n  web_names = &#91;for name in var.workload_names : name if startswith(name, \"web\")]\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-09-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-09\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-09\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-09\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-09\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-09\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-09\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Selected = join(\",\", local.web_names) }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"selected_workload_names\" {\n  value = local.web_names\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='workload_names=&#91;\"batch\",\"web03\"]'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Filters out batch01 and keeps web01, web02. Creates one demonstration VM; the filtered list is exposed in a tag and output. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">10. Conditional dynamic blocks<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Resource creation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Include or omit an optional nested VM block.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;An optional nested block may exist once or not at all.&nbsp;<code>dynamic<\/code>&nbsp;loops over&nbsp;<code>[1]<\/code>&nbsp;when enabled and&nbsp;<code>[]<\/code>&nbsp;when disabled. AWS optionally attaches an additional EBS disk, while Azure optionally enables managed boot diagnostics.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">10.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/10-dynamic\/main.tf<\/code>. false omits the nested block; true generates one optional nested block. AWS adds an EBS data disk; Azure enables managed boot diagnostics.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 10: Conditional dynamic blocks\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"enable_optional_block\" {\n  type = bool\n  default = false\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-10-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-10-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-10-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-10-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-10-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-10-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  dynamic \"ebs_block_device\" {\n    for_each = var.enable_optional_block ? &#91;1] : &#91;]\n    content {\n      device_name           = \"\/dev\/sdf\"\n      volume_size           = 8\n      volume_type           = \"gp3\"\n      encrypted             = true\n      delete_on_termination = true\n    }\n  }\n  tags = { Name = \"logic-10-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"optional_block_requested\" {\n  value = var.enable_optional_block\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='enable_optional_block=true'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;false omits the nested block; true generates one optional nested block. AWS adds an EBS data disk; Azure enables managed boot diagnostics. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">10.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/10-dynamic\/main.tf<\/code>. false omits the nested block; true generates one optional nested block. AWS adds an EBS data disk; Azure enables managed boot diagnostics.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 10: Conditional dynamic blocks\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"enable_optional_block\" {\n  type = bool\n  default = false\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-10-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-10\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-10\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-10\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-10\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-10\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-10\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  dynamic \"boot_diagnostics\" {\n    for_each = var.enable_optional_block ? &#91;1] : &#91;]\n    content {}\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"optional_block_requested\" {\n  value = var.enable_optional_block\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='enable_optional_block=true'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;false omits the nested block; true generates one optional nested block. AWS adds an EBS data disk; Azure enables managed boot diagnostics. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">11. Conditional module instantiation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Resource creation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Create zero or one VM via a local child module.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;A&nbsp;<code>module<\/code>&nbsp;call also supports&nbsp;<code>count<\/code>, so a whole child module can be present or absent. A module must have source code: this is the only topic requiring two&nbsp;<code>.tf<\/code>&nbsp;files, supplied completely in the Markdown and prepared in the ZIP.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">11.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/11-module\/main.tf<\/code>&nbsp;and&nbsp;<code>modules\/vm\/main.tf<\/code>. True creates one local child module and its VM; false creates zero module instances while leaving root networking.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 11: Conditional module instantiation\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"enable_module\" {\n  type = bool\n  default = true\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-11-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-11-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-11-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-11-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-11-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-11-sg\" }\n}\nmodule \"vm\" {\n  source = \".\/modules\/vm\"\n  count  = var.enable_module ? 1 : 0\n\n  ami               = data.aws_ssm_parameter.al2023.value\n  subnet_id         = aws_subnet.lab.id\n  security_group_id = aws_security_group.lab.id\n  name              = \"logic-11-ec2\"\n}\n\noutput \"instance_ids\" {\n  value = module.vm&#91;*].id\n}\n\noutput \"public_ips\" {\n  value = module.vm&#91;*].public_ip\n}\n\noutput \"module_enabled\" {\n  value = var.enable_module\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Local child module:&nbsp;<code>modules\/vm\/main.tf<\/code><\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform {\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nvariable \"ami\" {\n  type = string\n}\nvariable \"subnet_id\" {\n  type = string\n}\nvariable \"security_group_id\" {\n  type = string\n}\nvariable \"name\" {\n  type = string\n}\n\nresource \"aws_instance\" \"vm\" {\n  ami                         = var.ami\n  instance_type               = \"t3.micro\"\n  subnet_id                   = var.subnet_id\n  vpc_security_group_ids      = &#91;var.security_group_id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = var.name }\n}\n\noutput \"id\" {\n  value = aws_instance.vm.id\n}\noutput \"public_ip\" {\n  value = aws_instance.vm.public_ip\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='enable_module=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;True creates one local child module and its VM; false creates zero module instances while leaving root networking. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">11.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/11-module\/main.tf<\/code>&nbsp;and&nbsp;<code>modules\/vm\/main.tf<\/code>. True creates one local child module and its VM; false creates zero module instances while leaving root networking.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 11: Conditional module instantiation\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"enable_module\" {\n  type = bool\n  default = true\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-11-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-11\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-11\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-11\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-11\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-11\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\nmodule \"vm\" {\n  source = \".\/modules\/vm\"\n  count  = var.enable_module ? 1 : 0\n\n  name                = \"vm-cond-11\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  nic_id              = azurerm_network_interface.lab.id\n  vm_password         = var.vm_password\n}\n\noutput \"vm_ids\" {\n  value = module.vm&#91;*].id\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"module_enabled\" {\n  value = var.enable_module\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Local child module:&nbsp;<code>modules\/vm\/main.tf<\/code><\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform {\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nvariable \"name\" {\n  type = string\n}\nvariable \"location\" {\n  type = string\n}\nvariable \"resource_group_name\" {\n  type = string\n}\nvariable \"nic_id\" {\n  type = string\n}\nvariable \"vm_password\" {\n  type      = string\n  sensitive = true\n}\n\nresource \"azurerm_linux_virtual_machine\" \"vm\" {\n  name                            = var.name\n  location                        = var.location\n  resource_group_name             = var.resource_group_name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;var.nic_id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n}\n\noutput \"id\" {\n  value = azurerm_linux_virtual_machine.vm.id\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='enable_module=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;True creates one local child module and its VM; false creates zero module instances while leaving root networking. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">12. Input variable validation<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Validation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Reject unsupported environment names.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>validation<\/code>&nbsp;belongs inside an input variable. Terraform refuses unsupported values before normal deployment, returning the error message you chose. This is best for rules about user-supplied input.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">12.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/12-validation\/main.tf<\/code>. Bad input is rejected during planning with the custom variable error.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 12: Input variable validation\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n  validation {\n    condition     = contains(&#91;\"dev\", \"stage\", \"prod\"], var.environment)\n    error_message = \"environment must be dev, stage or prod.\"\n  }\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-12-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-12-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-12-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-12-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-12-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-12-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-12-ec2\", Environment = var.environment }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"environment\" {\n  value = var.environment\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=invalid'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Bad input is rejected during planning with the custom variable error. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">12.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/12-validation\/main.tf<\/code>. Bad input is rejected during planning with the custom variable error.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 12: Input variable validation\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n  validation {\n    condition     = contains(&#91;\"dev\", \"stage\", \"prod\"], var.environment)\n    error_message = \"environment must be dev, stage or prod.\"\n  }\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-12-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-12\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-12\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-12\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-12\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-12\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-12\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Environment = var.environment }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"environment\" {\n  value = var.environment\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=invalid'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Bad input is rejected during planning with the custom variable error. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">13. Resource precondition<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Validation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Reject an unsupported VM size before the operation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>lifecycle.precondition<\/code>&nbsp;is a resource guard. If the requested VM size is outside a small approved list, Terraform raises a useful error instead of trying to provision the instance.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">13.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/13-precondition\/main.tf<\/code>. An invalid size fails the precondition before VM creation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 13: Resource precondition\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"requested_size\" {\n  type = string\n  default = \"t3.micro\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-13-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-13-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-13-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-13-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-13-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-13-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = var.requested_size\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-13-ec2\" }\n  lifecycle {\n    precondition {\n      condition     = contains(&#91;\"t3.micro\", \"t3.small\"], var.requested_size)\n      error_message = \"requested_size is outside the approved training sizes.\"\n    }\n  }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='requested_size=invalid-size'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;An invalid size fails the precondition before VM creation. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">13.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/13-precondition\/main.tf<\/code>. An invalid size fails the precondition before VM creation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 13: Resource precondition\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"requested_size\" {\n  type = string\n  default = \"Standard_B1s\"\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-13-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-13\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-13\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-13\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-13\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-13\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-13\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = var.requested_size\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n  lifecycle {\n    precondition {\n      condition     = contains(&#91;\"Standard_B1s\", \"Standard_B2s\"], var.requested_size)\n      error_message = \"requested_size is outside the approved training sizes.\"\n    }\n  }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='requested_size=invalid-size'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;An invalid size fails the precondition before VM creation. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">14. Resource postcondition<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Validation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Verify a resulting VM ID meets an expectation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>lifecycle.postcondition<\/code>&nbsp;checks a result after Terraform can read it. Here, the provider-created VM ID must start with the normal AWS or Azure prefix. A failure blocks successful completion and dependent operations.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">14.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/14-postcondition\/main.tf<\/code>. Default verifies the created EC2 or Azure resource ID. A wrong prefix fails after the ID is known (plan for existing VMs, otherwise apply).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 14: Resource postcondition\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"expected_id_prefix\" {\n  type = string\n  default = \"i-\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-14-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-14-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-14-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-14-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-14-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-14-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-14-ec2\" }\n  lifecycle {\n    postcondition {\n      condition     = startswith(lower(self.id), lower(var.expected_id_prefix))\n      error_message = \"The created VM ID does not start with expected_id_prefix.\"\n    }\n  }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"expected_id_prefix\" {\n  value = var.expected_id_prefix\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='expected_id_prefix=wrong-prefix'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Default verifies the created EC2 or Azure resource ID. A wrong prefix fails after the ID is known (plan for existing VMs, otherwise apply). Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">14.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/14-postcondition\/main.tf<\/code>. Default verifies the created EC2 or Azure resource ID. A wrong prefix fails after the ID is known (plan for existing VMs, otherwise apply).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 14: Resource postcondition\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"expected_id_prefix\" {\n  type = string\n  default = \"\/subscriptions\/\"\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-14-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-14\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-14\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-14\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-14\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-14\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-14\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n  lifecycle {\n    postcondition {\n      condition     = startswith(lower(self.id), lower(var.expected_id_prefix))\n      error_message = \"The created VM ID does not start with expected_id_prefix.\"\n    }\n  }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"expected_id_prefix\" {\n  value = var.expected_id_prefix\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='expected_id_prefix=wrong-prefix'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Default verifies the created EC2 or Azure resource ID. A wrong prefix fails after the ID is known (plan for existing VMs, otherwise apply). Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">15. Output precondition<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Validation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Check a condition before publishing an output.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;An&nbsp;<code>output<\/code>&nbsp;may have a&nbsp;<code>precondition<\/code>&nbsp;block. Instead of blindly reporting a resource ID, Terraform checks the ID format before publishing the output value. This is a hard assertion, not a warning.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">15.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/15-output-precondition\/main.tf<\/code>. The output precondition passes for the real resource ID, and rejects a wrong expected prefix.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 15: Output precondition\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"expected_id_prefix\" {\n  type = string\n  default = \"i-\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-15-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-15-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-15-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-15-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-15-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-15-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-15-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"validated_vm_id\" {\n  value = aws_instance.lab.id\n  precondition {\n    condition     = startswith(lower(aws_instance.lab.id), lower(var.expected_id_prefix))\n    error_message = \"VM resource ID must start with expected_id_prefix.\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='expected_id_prefix=wrong-prefix'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;The output precondition passes for the real resource ID, and rejects a wrong expected prefix. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">15.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/15-output-precondition\/main.tf<\/code>. The output precondition passes for the real resource ID, and rejects a wrong expected prefix.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 15: Output precondition\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"expected_id_prefix\" {\n  type = string\n  default = \"\/subscriptions\/\"\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-15-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-15\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-15\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-15\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-15\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-15\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-15\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"validated_vm_id\" {\n  value = azurerm_linux_virtual_machine.lab.id\n  precondition {\n    condition     = startswith(lower(azurerm_linux_virtual_machine.lab.id), lower(var.expected_id_prefix))\n    error_message = \"VM resource ID must start with expected_id_prefix.\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='expected_id_prefix=wrong-prefix'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;The output precondition passes for the real resource ID, and rejects a wrong expected prefix. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">16. check block (warning)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Validation.&nbsp;<strong>Student goal:<\/strong>&nbsp;Report a nonblocking configuration assertion.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;A&nbsp;<code>check<\/code>&nbsp;block evaluates an assertion but normally only reports a warning. It is good for ongoing diagnostics, not for mandatory security policy or preventing a resource from being built.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">16.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/16-check\/main.tf<\/code>. A failed check is reported as a warning; Terraform does not block the operation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 16: check block (warning)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"expected_name_prefix\" {\n  type = string\n  default = \"logic\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-16-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-16-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-16-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-16-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-16-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-16-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-16-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"expected_name_prefix\" {\n  value = var.expected_name_prefix\n}\n\ncheck \"vm_name_standard\" {\n  assert {\n    condition     = startswith(aws_instance.lab.tags&#91;\"Name\"], var.expected_name_prefix)\n    error_message = \"VM name does not match the expected prefix (warning, not failure).\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='expected_name_prefix=other-name'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;A failed check is reported as a warning; Terraform does not block the operation. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">16.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/16-check\/main.tf<\/code>. A failed check is reported as a warning; Terraform does not block the operation.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 16: check block (warning)\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"expected_name_prefix\" {\n  type = string\n  default = \"vm-cond\"\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-16-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-16\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-16\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-16\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-16\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-16\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-16\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"expected_name_prefix\" {\n  value = var.expected_name_prefix\n}\n\ncheck \"vm_name_standard\" {\n  assert {\n    condition     = startswith(azurerm_linux_virtual_machine.lab.name, var.expected_name_prefix)\n    error_message = \"VM name does not match the expected prefix (warning, not failure).\"\n  }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='expected_name_prefix=other-name'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;A failed check is reported as a warning; Terraform does not block the operation. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">17. try()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Fall back when an optional map key is missing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>try(first, fallback)<\/code>&nbsp;picks the first expression that can evaluate successfully. A missing optional map key triggers the fallback size. It will not repair syntax mistakes or references to undeclared Terraform objects.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">17.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/17-try\/main.tf<\/code>. With a missing key, try() returns the small-size fallback.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 17: try()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"settings\" {\n  type = map(string)\n  default = {}\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  selected_size = try(var.settings&#91;\"instance_type\"], \"t3.micro\")\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-17-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-17-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-17-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-17-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-17-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-17-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = local.selected_size\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-17-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"chosen_size\" {\n  value = local.selected_size\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='settings={instance_type=\"t3.small\"}'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;With a missing key, try() returns the small-size fallback. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">17.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/17-try\/main.tf<\/code>. With a missing key, try() returns the small-size fallback.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 17: try()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"settings\" {\n  type = map(string)\n  default = {}\n}\nlocals {\n  selected_size = try(var.settings&#91;\"vm_size\"], \"Standard_B1s\")\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-17-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-17\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-17\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-17\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-17\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-17\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-17\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = local.selected_size\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"chosen_size\" {\n  value = local.selected_size\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='settings={vm_size=\"Standard_B2s\"}'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;With a missing key, try() returns the small-size fallback. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">18. can()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Turn a regex evaluation error into variable validation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>regex<\/code>&nbsp;fails when a pattern does not match.&nbsp;<code>can(regex(...))<\/code>&nbsp;converts that evaluation failure into&nbsp;<code>false<\/code>, making regex-based variable validation simple and safe.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">18.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/18-can\/main.tf<\/code>. can(regex(&#8230;)) returns false for a mismatch, so input validation fails gracefully.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 18: can()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"deployment_code\" {\n  type = string\n  default = \"dev01\"\n  validation {\n    condition     = can(regex(\"^(dev|test|prod)&#91;0-9]+$\", var.deployment_code))\n    error_message = \"deployment_code must look like dev01, test02, prod03.\"\n  }\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-18-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-18-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-18-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-18-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-18-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-18-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-18-ec2\", Code = var.deployment_code }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"validated_code\" {\n  value = var.deployment_code\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='deployment_code=invalid'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;can(regex(&#8230;)) returns false for a mismatch, so input validation fails gracefully. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">18.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/18-can\/main.tf<\/code>. can(regex(&#8230;)) returns false for a mismatch, so input validation fails gracefully.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 18: can()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"deployment_code\" {\n  type = string\n  default = \"dev01\"\n  validation {\n    condition     = can(regex(\"^(dev|test|prod)&#91;0-9]+$\", var.deployment_code))\n    error_message = \"deployment_code must look like dev01, test02, prod03.\"\n  }\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-18-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-18\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-18\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-18\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-18\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-18\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-18\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Code = var.deployment_code }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"validated_code\" {\n  value = var.deployment_code\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='deployment_code=invalid'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;can(regex(&#8230;)) returns false for a mismatch, so input validation fails gracefully. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">19. lookup()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Get a VM size from an environment map with a fallback.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>lookup(map, key, fallback)<\/code>&nbsp;reads a map item without failing when the key is absent. An unknown environment therefore gets a sensible small VM size.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">19.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/19-lookup\/main.tf<\/code>. Known keys select a size; unknown chooses the provided fallback.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 19: lookup()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n}\n\nvariable \"size_by_env\" {\n  type = map(string)\n  default = { dev = \"t3.micro\", prod = \"t3.small\" }\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  selected_size = lookup(var.size_by_env, var.environment, \"t3.micro\")\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-19-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-19-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-19-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-19-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-19-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-19-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = local.selected_size\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-19-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"selected_size\" {\n  value = local.selected_size\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=unknown'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Known keys select a size; unknown chooses the provided fallback. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">19.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/19-lookup\/main.tf<\/code>. Known keys select a size; unknown chooses the provided fallback.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 19: lookup()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n}\n\nvariable \"size_by_env\" {\n  type = map(string)\n  default = { dev = \"Standard_B1s\", prod = \"Standard_B2s\" }\n}\nlocals {\n  selected_size = lookup(var.size_by_env, var.environment, \"Standard_B1s\")\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-19-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-19\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-19\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-19\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-19\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-19\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-19\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = local.selected_size\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Purpose = \"conditional-logic\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"selected_size\" {\n  value = local.selected_size\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=unknown'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Known keys select a size; unknown chooses the provided fallback. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">20. coalesce()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Use the first non-null, nonempty display name.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>coalesce()<\/code>&nbsp;chooses the first value that is neither null nor the empty string. Use it to provide a user-friendly label if the optional display name is missing.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">20.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/20-coalesce\/main.tf<\/code>. null or empty string uses lab-default; a supplied string wins.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 20: coalesce()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"display_name\" {\n  type = string\n  default = null\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  display_name = coalesce(var.display_name, \"lab-default\")\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-20-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-20-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-20-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-20-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-20-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-20-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = local.display_name }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"display_name\" {\n  value = local.display_name\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='display_name=student-lab'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;null or empty string uses lab-default; a supplied string wins. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">20.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/20-coalesce\/main.tf<\/code>. null or empty string uses lab-default; a supplied string wins.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 20: coalesce()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"display_name\" {\n  type = string\n  default = null\n}\nlocals {\n  display_name = coalesce(var.display_name, \"lab-default\")\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-20-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-20\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-20\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-20\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-20\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-20\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-20\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { DisplayName = local.display_name }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"display_name\" {\n  value = local.display_name\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='display_name=student-lab'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;null or empty string uses lab-default; a supplied string wins. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">21. coalescelist()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Choose the first nonempty list of owners.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>coalescelist()<\/code>&nbsp;takes the first&nbsp;<strong>nonempty list<\/strong>. An empty owner list falls back to a default team, but a real list of owners is preserved.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">21.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/21-coalescelist\/main.tf<\/code>. Empty list uses platform-team; a nonempty list is retained.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 21: coalescelist()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"preferred_owners\" {\n  type = list(string)\n  default = &#91;]\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  selected_owners = coalescelist(var.preferred_owners, &#91;\"platform-team\"])\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-21-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-21-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-21-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-21-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-21-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-21-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-21-ec2\", Owners = join(\",\", local.selected_owners) }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"selected_owners\" {\n  value = local.selected_owners\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='preferred_owners=&#91;\"devops\",\"security\"]'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Empty list uses platform-team; a nonempty list is retained. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">21.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/21-coalescelist\/main.tf<\/code>. Empty list uses platform-team; a nonempty list is retained.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 21: coalescelist()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"preferred_owners\" {\n  type = list(string)\n  default = &#91;]\n}\nlocals {\n  selected_owners = coalescelist(var.preferred_owners, &#91;\"platform-team\"])\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-21-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-21\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-21\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-21\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-21\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-21\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-21\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Owners = join(\",\", local.selected_owners) }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"selected_owners\" {\n  value = local.selected_owners\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='preferred_owners=&#91;\"devops\",\"security\"]'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Empty list uses platform-team; a nonempty list is retained. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">22. contains()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Test membership in a list of environments.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>contains(collection, value)<\/code>&nbsp;returns a Boolean membership result. It is clearer than a long chain such as&nbsp;<code>env == \"prod\" || env == \"stage\"<\/code>&nbsp;when the options form a list.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">22.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/22-contains\/main.tf<\/code>. prod and stage return true; dev returns false. AWS also toggles detailed monitoring.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 22: contains()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"prod\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  is_restricted_env = contains(&#91;\"prod\", \"stage\"], var.environment)\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-22-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-22-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-22-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-22-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-22-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-22-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  monitoring = local.is_restricted_env\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-22-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"restricted_environment\" {\n  value = local.is_restricted_env\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=dev'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;prod and stage return true; dev returns false. AWS also toggles detailed monitoring. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">22.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/22-contains\/main.tf<\/code>. prod and stage return true; dev returns false. AWS also toggles detailed monitoring.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 22: contains()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"prod\"\n}\nlocals {\n  is_restricted_env = contains(&#91;\"prod\", \"stage\"], var.environment)\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-22-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-22\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-22\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-22\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-22\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-22\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-22\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { RestrictedEnv = tostring(local.is_restricted_env) }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"restricted_environment\" {\n  value = local.is_restricted_env\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=dev'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;prod and stage return true; dev returns false. AWS also toggles detailed monitoring. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">23. alltrue()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Check whether every port number is in range.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>alltrue()<\/code>&nbsp;requires every item to be true. A&nbsp;<code>for<\/code>&nbsp;expression converts numeric ports to true\/false checks before&nbsp;<code>alltrue<\/code>&nbsp;combines them. Remember: on an empty list,&nbsp;<code>alltrue([])<\/code>&nbsp;is true.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">23.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/23-alltrue\/main.tf<\/code>. 22\/443 -&gt; true; adding 70000 -&gt; false. alltrue([]) is true (vacuous truth).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 23: alltrue()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"ports\" {\n  type = list(number)\n  default = &#91;22, 443]\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  all_ports_valid = alltrue(&#91;for p in var.ports : p &gt;= 1 &amp;&amp; p &lt;= 65535])\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-23-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-23-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-23-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-23-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-23-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-23-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-23-ec2\", PortsValid = tostring(local.all_ports_valid) }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"all_ports_valid\" {\n  value = local.all_ports_valid\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='ports=&#91;22,70000]'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;22\/443 -&gt; true; adding 70000 -&gt; false. alltrue([]) is true (vacuous truth). Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">23.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/23-alltrue\/main.tf<\/code>. 22\/443 -&gt; true; adding 70000 -&gt; false. alltrue([]) is true (vacuous truth).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 23: alltrue()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"ports\" {\n  type = list(number)\n  default = &#91;22, 443]\n}\nlocals {\n  all_ports_valid = alltrue(&#91;for p in var.ports : p &gt;= 1 &amp;&amp; p &lt;= 65535])\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-23-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-23\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-23\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-23\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-23\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-23\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-23\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { PortsValid = tostring(local.all_ports_valid) }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"all_ports_valid\" {\n  value = local.all_ports_valid\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='ports=&#91;22,70000]'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;22\/443 -&gt; true; adding 70000 -&gt; false. alltrue([]) is true (vacuous truth). Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">24. anytrue()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Check whether at least one feature is active.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>anytrue()<\/code>&nbsp;returns true when at least one item is true. It turns several feature switches into one concise decision. An empty list or all-false flags return false.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">24.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/24-anytrue\/main.tf<\/code>. true when any supplied flag is true; all false -&gt; false. AWS toggles EC2 detailed monitoring, not backups\/tracing.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 24: anytrue()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"metrics_enabled\" {\n  type = bool\n  default = false\n}\n\nvariable \"backups_enabled\" {\n  type = bool\n  default = true\n}\n\nvariable \"tracing_enabled\" {\n  type = bool\n  default = false\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  any_feature_enabled = anytrue(&#91;var.metrics_enabled, var.backups_enabled, var.tracing_enabled])\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-24-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-24-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-24-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-24-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-24-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-24-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  monitoring = local.any_feature_enabled\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-24-ec2\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"any_feature_enabled\" {\n  value = local.any_feature_enabled\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='backups_enabled=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;true when any supplied flag is true; all false -&gt; false. AWS toggles EC2 detailed monitoring, not backups\/tracing. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">24.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/24-anytrue\/main.tf<\/code>. true when any supplied flag is true; all false -&gt; false. AWS toggles EC2 detailed monitoring, not backups\/tracing.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 24: anytrue()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"metrics_enabled\" {\n  type = bool\n  default = false\n}\n\nvariable \"backups_enabled\" {\n  type = bool\n  default = true\n}\n\nvariable \"tracing_enabled\" {\n  type = bool\n  default = false\n}\nlocals {\n  any_feature_enabled = anytrue(&#91;var.metrics_enabled, var.backups_enabled, var.tracing_enabled])\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-24-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-24\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-24\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-24\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-24\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-24\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-24\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { AnyFeature = tostring(local.any_feature_enabled) }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"any_feature_enabled\" {\n  value = local.any_feature_enabled\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='backups_enabled=false'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;true when any supplied flag is true; all false -&gt; false. AWS toggles EC2 detailed monitoring, not backups\/tracing. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">25. length()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Choose a label based on the size of a list.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>length()<\/code>&nbsp;counts items in a list\/map or characters in a string. Checking&nbsp;<code>length(var.owners) &gt; 0<\/code>&nbsp;determines whether an owner has been assigned.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">25.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/25-length\/main.tf<\/code>. Returns 2 by default and 0 with [] and selects the corresponding tag.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 25: length()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"owners\" {\n  type = list(string)\n  default = &#91;\"alice\", \"bob\"]\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  has_owners = length(var.owners) &gt; 0\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-25-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-25-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-25-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-25-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-25-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-25-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-25-ec2\", OwnerStatus = local.has_owners ? \"assigned\" : \"unassigned\" }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"owner_count\" {\n  value = length(var.owners)\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='owners=&#91;]'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Returns 2 by default and 0 with [] and selects the corresponding tag. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">25.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/25-length\/main.tf<\/code>. Returns 2 by default and 0 with [] and selects the corresponding tag.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 25: length()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"owners\" {\n  type = list(string)\n  default = &#91;\"alice\", \"bob\"]\n}\nlocals {\n  has_owners = length(var.owners) &gt; 0\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-25-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-25\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-25\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-25\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-25\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-25\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-25\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { OwnerStatus = local.has_owners ? \"assigned\" : \"unassigned\" }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"owner_count\" {\n  value = length(var.owners)\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='owners=&#91;]'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Returns 2 by default and 0 with [] and selects the corresponding tag. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">26. regex() and regexall()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Extract letters and detect digits in a label.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>regex()<\/code>&nbsp;returns the first match or an error;&nbsp;<code>regexall()<\/code>&nbsp;collects every match and safely returns an empty list if there is no match. The example extracts letters and checks whether the label contains any digits.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">26.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/26-regex\/main.tf<\/code>. web01 -&gt; letters=web, has_digits=true. regex throws on no match; try() handles this fallback.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 26: regex() and regexall()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"label\" {\n  type = string\n  default = \"web01\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  letters = try(regex(\"&#91;a-z]+\", lower(var.label)), \"none\")\n    has_digits = length(regexall(\"&#91;0-9]+\", var.label)) &gt; 0\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-26-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-26-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-26-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-26-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-26-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-26-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-26-ec2\", Letters = local.letters, Digits = tostring(local.has_digits) }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"regex_results\" {\n  value = { letters = local.letters, has_digits = local.has_digits }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='label=backend'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;web01 -&gt; letters=web, has_digits=true. regex throws on no match; try() handles this fallback. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">26.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/26-regex\/main.tf<\/code>. web01 -&gt; letters=web, has_digits=true. regex throws on no match; try() handles this fallback.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 26: regex() and regexall()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"label\" {\n  type = string\n  default = \"web01\"\n}\nlocals {\n  letters = try(regex(\"&#91;a-z]+\", lower(var.label)), \"none\")\n    has_digits = length(regexall(\"&#91;0-9]+\", var.label)) &gt; 0\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-26-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-26\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-26\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-26\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-26\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-26\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-26\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Letters = local.letters, Digits = tostring(local.has_digits) }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"regex_results\" {\n  value = { letters = local.letters, has_digits = local.has_digits }\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='label=backend'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;web01 -&gt; letters=web, has_digits=true. regex throws on no match; try() handles this fallback. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">27. startswith() and endswith()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Functions.&nbsp;<strong>Student goal:<\/strong>&nbsp;Check standardized workload naming patterns.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>startswith()<\/code>&nbsp;tests the beginning of a string and&nbsp;<code>endswith()<\/code>&nbsp;tests the ending. Combining them lets teams consistently label workloads such as&nbsp;<code>prod-payments-api<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">27.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/27-startswith-endswith\/main.tf<\/code>. True for prod-payments-api; false for prefixes or suffixes that do not match.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 27: startswith() and endswith()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"workload_name\" {\n  type = string\n  default = \"prod-payments-api\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  standard_name = startswith(var.workload_name, \"prod-\") &amp;&amp; endswith(var.workload_name, \"-api\")\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-27-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-27-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-27-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-27-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-27-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-27-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-27-ec2\", NamingValid = tostring(local.standard_name) }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"standard_name\" {\n  value = local.standard_name\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='workload_name=dev-payments-job'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;True for prod-payments-api; false for prefixes or suffixes that do not match. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">27.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/27-startswith-endswith\/main.tf<\/code>. True for prod-payments-api; false for prefixes or suffixes that do not match.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 27: startswith() and endswith()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"workload_name\" {\n  type = string\n  default = \"prod-payments-api\"\n}\nlocals {\n  standard_name = startswith(var.workload_name, \"prod-\") &amp;&amp; endswith(var.workload_name, \"-api\")\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-27-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-27\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-27\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-27\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-27\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-27\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-27\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { NamingValid = tostring(local.standard_name) }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"standard_name\" {\n  value = local.standard_name\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='workload_name=dev-payments-job'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;True for prod-payments-api; false for prefixes or suffixes that do not match. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">28. null values<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Null and optional.&nbsp;<strong>Student goal:<\/strong>&nbsp;Omit optional VM tags when no owner is provided.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>null<\/code>&nbsp;means&nbsp;<em>not supplied<\/em>&nbsp;for an optional argument. In this example,&nbsp;<code>tags = null<\/code>&nbsp;omits VM tags; setting an owner supplies a tag map. It is different from&nbsp;<code>tags = {}<\/code>&nbsp;or an empty string.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">28.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/28-null\/main.tf<\/code>. No owner -&gt; tags argument omitted with null; setting owner creates the tag.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 28: null values\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"owner\" {\n  type = string\n  default = null\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-28-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-28-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-28-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-28-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-28-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-28-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = var.owner == null ? null : { Owner = var.owner }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"owner_value\" {\n  value = var.owner\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='owner=platform'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;No owner -&gt; tags argument omitted with null; setting owner creates the tag. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">28.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/28-null\/main.tf<\/code>. No owner -&gt; tags argument omitted with null; setting owner creates the tag.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 28: null values\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"owner\" {\n  type = string\n  default = null\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-28-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-28\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-28\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-28\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-28\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-28\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-28\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = var.owner == null ? null : { Owner = var.owner }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"owner_value\" {\n  value = var.owner\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='owner=platform'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;No owner -&gt; tags argument omitted with null; setting owner creates the tag. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">29. Optional object attributes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Null and optional.&nbsp;<strong>Student goal:<\/strong>&nbsp;Supply defaults for optional settings of a typed object.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;Optional attributes in an&nbsp;<code>object(...)<\/code>&nbsp;type automatically receive declared defaults. Callers can pass&nbsp;<code>{}<\/code>&nbsp;or override only&nbsp;<code>tier<\/code>, leaving&nbsp;<code>owner<\/code>&nbsp;to default to&nbsp;<code>student<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">29.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/29-optional\/main.tf<\/code>. With {}, defaults are applied: tier=small and owner=student. Override only tier.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 29: Optional object attributes\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"vm_options\" {\n  type = object({ tier = optional(string, \"small\"), owner = optional(string, \"student\") })\n  default = {}\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-29-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-29-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-29-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-29-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-29-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-29-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = var.vm_options.tier == \"large\" ? \"t3.small\" : \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-29-ec2\", Owner = var.vm_options.owner }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"effective_options\" {\n  value = var.vm_options\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='vm_options={tier=\"large\"}'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;With {}, defaults are applied: tier=small and owner=student. Override only tier. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">29.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/29-optional\/main.tf<\/code>. With {}, defaults are applied: tier=small and owner=student. Override only tier.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 29: Optional object attributes\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"vm_options\" {\n  type = object({ tier = optional(string, \"small\"), owner = optional(string, \"student\") })\n  default = {}\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-29-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-29\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-29\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-29\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-29\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-29\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-29\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = var.vm_options.tier == \"large\" ? \"Standard_B2s\" : \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Owner = var.vm_options.owner }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"effective_options\" {\n  value = var.vm_options\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='vm_options={tier=\"large\"}'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;With {}, defaults are applied: tier=small and owner=student. Override only tier. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">30. nullable variable setting<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Null and optional.&nbsp;<strong>Student goal:<\/strong>&nbsp;Require a non-null owner variable.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>nullable = false<\/code>&nbsp;stops a variable from accepting the&nbsp;<code>null<\/code>&nbsp;value. This variable has no default, so students must supply a non-null owner using&nbsp;<code>TF_VAR_owner<\/code>&nbsp;before planning.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">30.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/30-nullable\/main.tf<\/code>. No default: owner must be provided, and null is disallowed. Supply TF_VAR_owner=student.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 30: nullable variable setting\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"owner\" {\n  type = string\n  nullable = false\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-30-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-30-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-30-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-30-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-30-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-30-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-30-ec2\", Owner = var.owner }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"required_owner\" {\n  value = var.owner\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export TF_VAR_owner=\"student\"\nterraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export TF_VAR_owner=\"student\" <em># required for this one lab<\/em>\nterraform plan\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;No default: owner must be provided, and null is disallowed. Supply TF_VAR_owner=student. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">30.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/30-nullable\/main.tf<\/code>. No default: owner must be provided, and null is disallowed. Supply TF_VAR_owner=student.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 30: nullable variable setting\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"owner\" {\n  type = string\n  nullable = false\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-30-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-30\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-30\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-30\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-30\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-30\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-30\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Owner = var.owner }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"required_owner\" {\n  value = var.owner\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export TF_VAR_owner=\"student\"\nterraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export TF_VAR_owner=\"student\" <em># required for this one lab<\/em>\nterraform plan\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;No default: owner must be provided, and null is disallowed. Supply TF_VAR_owner=student. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">31. merge() and conditional maps<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Null and optional.&nbsp;<strong>Student goal:<\/strong>&nbsp;Add environment-specific tags only for production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>merge()<\/code>&nbsp;combines maps. The conditional map contributes&nbsp;<code>Compliance = \"strict\"<\/code>&nbsp;only for production;&nbsp;<code>tomap()<\/code>&nbsp;makes both conditional branches compatible maps. The common tags remain for all environments.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">31.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/31-merge\/main.tf<\/code>. dev gets common tags only; prod also gets Compliance=strict.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 31: merge() and conditional maps\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  common_tags = { Course = \"terraform-conditions\", Owner = \"student\" }\n    extra_tags  = var.environment == \"prod\" ? tomap({ Compliance = \"strict\" }) : tomap({})\n    final_tags  = merge(local.common_tags, local.extra_tags)\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-31-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-31-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-31-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-31-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-31-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-31-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = merge(local.final_tags, { Name = \"logic-31-ec2\" })\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"final_tags\" {\n  value = local.final_tags\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=prod'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;dev gets common tags only; prod also gets Compliance=strict. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">31.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/31-merge\/main.tf<\/code>. dev gets common tags only; prod also gets Compliance=strict.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 31: merge() and conditional maps\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"environment\" {\n  type = string\n  default = \"dev\"\n}\nlocals {\n  common_tags = { Course = \"terraform-conditions\", Owner = \"student\" }\n    extra_tags  = var.environment == \"prod\" ? tomap({ Compliance = \"strict\" }) : tomap({})\n    final_tags  = merge(local.common_tags, local.extra_tags)\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-31-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-31\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-31\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-31\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-31\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-31\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-31\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = local.final_tags\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"final_tags\" {\n  value = local.final_tags\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='environment=prod'\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;dev gets common tags only; prod also gets Compliance=strict. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">32. compact() and flatten()<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Category:<\/strong>&nbsp;Null and optional.&nbsp;<strong>Student goal:<\/strong>&nbsp;Flatten nested owner lists and remove blank entries.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Plain English:<\/strong>&nbsp;<code>flatten()<\/code>&nbsp;turns a list of lists into a single list;&nbsp;<code>compact()<\/code>&nbsp;removes null or empty strings from that list. The resulting names can be joined safely for a tag or inspected as an output.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">32.1 AWS EC2 &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>aws\/32-compact-flatten\/main.tf<\/code>. Nested lists become one flat list, and blank strings are removed.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone AWS lab 32: compact() and flatten()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    aws = {\n      source  = \"hashicorp\/aws\"\n      version = \"~&gt; 6.0\"\n    }\n  }\n}\n\nprovider \"aws\" {\n  region = \"us-east-1\"\n}\n\nvariable \"nested_owners\" {\n  type = list(list(string))\n  default = &#91;&#91;\"platform\", \"\"], &#91;\"security\", \"operations\"]]\n}\n# Official Amazon Linux 2023 public AMI parameter (region-specific AMI ID).\ndata \"aws_ssm_parameter\" \"al2023\" {\n  name = \"\/aws\/service\/ami-amazon-linux-latest\/al2023-ami-kernel-default-x86_64\"\n}\n\nlocals {\n  owners = compact(flatten(var.nested_owners))\n}\n\nresource \"aws_vpc\" \"lab\" {\n  cidr_block           = \"10.70.0.0\/16\"\n  enable_dns_support   = true\n  enable_dns_hostnames = true\n  tags = { Name = \"cond-32-vpc\" }\n}\n\nresource \"aws_subnet\" \"lab\" {\n  vpc_id                  = aws_vpc.lab.id\n  cidr_block              = \"10.70.1.0\/24\"\n  map_public_ip_on_launch = true\n  tags = { Name = \"cond-32-subnet\" }\n}\n\nresource \"aws_internet_gateway\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  tags   = { Name = \"cond-32-igw\" }\n}\n\nresource \"aws_route_table\" \"lab\" {\n  vpc_id = aws_vpc.lab.id\n  route {\n    cidr_block = \"0.0.0.0\/0\"\n    gateway_id = aws_internet_gateway.lab.id\n  }\n  tags = { Name = \"cond-32-rt\" }\n}\n\nresource \"aws_route_table_association\" \"lab\" {\n  subnet_id      = aws_subnet.lab.id\n  route_table_id = aws_route_table.lab.id\n}\n\nresource \"aws_security_group\" \"lab\" {\n  name_prefix = \"cond-32-\"\n  description = \"No inbound traffic; outbound for package access\"\n  vpc_id      = aws_vpc.lab.id\n\n  # No inbound rules: this learning VM is NOT reachable from the internet.\n  egress {\n    from_port   = 0\n    to_port     = 0\n    protocol    = \"-1\"\n    cidr_blocks = &#91;\"0.0.0.0\/0\"]\n  }\n  tags = { Name = \"cond-32-sg\" }\n}\n\nresource \"aws_instance\" \"lab\" {\n\n  ami                         = data.aws_ssm_parameter.al2023.value\n  instance_type               = \"t3.micro\"\n  subnet_id                   = aws_subnet.lab.id\n  vpc_security_group_ids      = &#91;aws_security_group.lab.id]\n  associate_public_ip_address = true\n  metadata_options {\n    http_tokens = \"required\"\n  }\n  tags = { Name = \"logic-32-ec2\", Owners = join(\",\", local.owners) }\n}\n\noutput \"instance_ids\" {\n  value = &#91;aws_instance.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;aws_instance.lab.public_ip]\n}\n\noutput \"clean_owners\" {\n  value = local.owners\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='nested_owners=&#91;&#91;\"devops\",\"\"],&#91;\"qa\"]]' \n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Nested lists become one flat list, and blank strings are removed. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">32.2 Azure Ubuntu Linux VM &#8211; complete standalone code<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Files:<\/strong>&nbsp;<code>azure\/32-compact-flatten\/main.tf<\/code>. Nested lists become one flat list, and blank strings are removed.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Standalone Azure lab 32: compact() and flatten()\nterraform {\n  required_version = \"&gt;= 1.15.0, &lt; 2.0.0\"\n  required_providers {\n    azurerm = {\n      source  = \"hashicorp\/azurerm\"\n      version = \"~&gt; 5.9.0\"\n    }\n  }\n}\n\nprovider \"azurerm\" {\n  features {}\n}\n\nvariable \"vm_password\" {\n  type        = string\n  sensitive   = true\n  description = \"Unique strong lab password (supplied by TF_VAR_vm_password).\"\n  validation {\n    condition     = length(var.vm_password) &gt;= 12\n    error_message = \"Use a VM password of at least 12 characters.\"\n  }\n}\n\nvariable \"admin_cidr\" {\n  type        = string\n  description = \"Your own public IPv4 address with \/32 (supplied by TF_VAR_admin_cidr).\"\n  validation {\n    condition     = can(cidrhost(var.admin_cidr, 0)) &amp;&amp; endswith(var.admin_cidr, \"\/32\")\n    error_message = \"Enter your public IPv4 as a \/32 CIDR, such as YOUR.PUBLIC.IP.ADDRESS\/32.\"\n  }\n}\n\nvariable \"nested_owners\" {\n  type = list(list(string))\n  default = &#91;&#91;\"platform\", \"\"], &#91;\"security\", \"operations\"]]\n}\nlocals {\n  owners = compact(flatten(var.nested_owners))\n}\n\nresource \"azurerm_resource_group\" \"lab\" {\n  name     = \"rg-cond-32-eastus\"\n  location = \"eastus\"\n}\n\nresource \"azurerm_virtual_network\" \"lab\" {\n  name                = \"vnet-cond-32\"\n  address_space       = &#91;\"10.70.0.0\/16\"]\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_subnet\" \"lab\" {\n  name                 = \"snet-cond-32\"\n  resource_group_name  = azurerm_resource_group.lab.name\n  virtual_network_name = azurerm_virtual_network.lab.name\n  address_prefixes     = &#91;\"10.70.1.0\/24\"]\n}\n\nresource \"azurerm_network_security_group\" \"lab\" {\n  name                = \"nsg-cond-32\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n}\n\nresource \"azurerm_network_security_rule\" \"ssh\" {\n  name                        = \"AllowSSHFromStudent\"\n  priority                    = 100\n  direction                   = \"Inbound\"\n  access                      = \"Allow\"\n  protocol                    = \"Tcp\"\n  source_port_range           = \"*\"\n  destination_port_range      = \"22\"\n  source_address_prefix       = var.admin_cidr\n  destination_address_prefix  = \"*\"\n  resource_group_name         = azurerm_resource_group.lab.name\n  network_security_group_name = azurerm_network_security_group.lab.name\n}\n\nresource \"azurerm_subnet_network_security_group_association\" \"lab\" {\n  subnet_id                 = azurerm_subnet.lab.id\n  network_security_group_id = azurerm_network_security_group.lab.id\n}\n\nresource \"azurerm_public_ip\" \"lab\" {\n\n  name                = \"pip-cond-32\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  allocation_method   = \"Static\"\n  sku                 = \"Standard\"\n}\n\nresource \"azurerm_network_interface\" \"lab\" {\n\n  name                = \"nic-cond-32\"\n  location            = azurerm_resource_group.lab.location\n  resource_group_name = azurerm_resource_group.lab.name\n  ip_configuration {\n    name                          = \"internal\"\n    subnet_id                     = azurerm_subnet.lab.id\n    private_ip_address_allocation = \"Dynamic\"\n    public_ip_address_id          = azurerm_public_ip.lab.id\n  }\n}\n\n\nresource \"azurerm_linux_virtual_machine\" \"lab\" {\n\n  name                            = \"vm-cond-32\"\n  location                        = azurerm_resource_group.lab.location\n  resource_group_name             = azurerm_resource_group.lab.name\n  size                            = \"Standard_B1s\"\n  admin_username                  = \"labadmin\"\n  admin_password                  = var.vm_password\n  disable_password_authentication = false\n  network_interface_ids           = &#91;azurerm_network_interface.lab.id]\n  os_disk {\n    caching              = \"ReadWrite\"\n    storage_account_type = \"Standard_LRS\"\n  }\n  source_image_reference {\n    publisher = \"Canonical\"\n    offer     = \"ubuntu-24_04-lts\"\n    sku       = \"server\"\n    version   = \"latest\"\n  }\n  tags = { Owners = join(\",\", local.owners) }\n}\n\noutput \"vm_ids\" {\n  value = &#91;azurerm_linux_virtual_machine.lab.id]\n}\n\noutput \"public_ips\" {\n  value = &#91;azurerm_public_ip.lab.ip_address]\n}\n\noutput \"clean_owners\" {\n  value = local.owners\n}\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Run and observe:<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform init\nterraform validate\nterraform plan\nterraform apply\nterraform output\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Experiment (run after init; preview only):<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform plan -var='nested_owners=&#91;&#91;\"devops\",\"\"],&#91;\"qa\"]]' \n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Expected learning:<\/strong>&nbsp;Nested lists become one flat list, and blank strings are removed. Clean up with&nbsp;<code>terraform destroy<\/code>&nbsp;in this exact lab directory.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">E. Validation differences (important for students)<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th class=\"has-text-align-left\" data-align=\"left\">Mechanism<\/th><th class=\"has-text-align-left\" data-align=\"left\">Timing<\/th><th class=\"has-text-align-left\" data-align=\"left\">Failure behavior<\/th><th class=\"has-text-align-left\" data-align=\"left\">Typical use<\/th><\/tr><\/thead><tbody><tr><td><code>variable<\/code>&nbsp;<code>validation<\/code><\/td><td>As Terraform evaluates inputs<\/td><td><strong>Error \/ stop<\/strong><\/td><td>Reject invalid regions, names, formats<\/td><\/tr><tr><td>Resource&nbsp;<code>lifecycle.precondition<\/code><\/td><td>Before resource operation, once inputs are known<\/td><td><strong>Error \/ stop<\/strong><\/td><td>Guard assumptions before provisioning<\/td><\/tr><tr><td>Resource&nbsp;<code>lifecycle.postcondition<\/code><\/td><td>After resource\/data read or change when values are known<\/td><td><strong>Error \/ stop<\/strong><\/td><td>Assert properties of the resulting object<\/td><\/tr><tr><td>Output&nbsp;<code>precondition<\/code><\/td><td>Before a root output is recorded\/published<\/td><td><strong>Error \/ stop<\/strong><\/td><td>Ensure output can be trusted<\/td><\/tr><tr><td><code>check<\/code>&nbsp;\/&nbsp;<code>assert<\/code><\/td><td>Checked during plan\/apply and rechecked as appropriate<\/td><td><strong>Warning, normally continues<\/strong><\/td><td>Nonblocking quality\/health indicator<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">F. Common mistakes and troubleshooting<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Copying several labs into one directory:<\/strong>\u00a0all use similar Terraform local names; duplicate declarations will fail. Copy exactly one scenario&#8217;s\u00a0<code>main.tf<\/code>\u00a0into a fresh directory, or use the ZIP folders directly.<\/li>\n\n\n\n<li><strong>Setting\u00a0<code>count = 0<\/code>\u00a0and still referencing\u00a0<code>[0]<\/code>:<\/strong>\u00a0use conditional indexing or\u00a0<code>try()<\/code>; the templates demonstrate splats (<code>[*].id<\/code>) that return an empty list safely.<\/li>\n\n\n\n<li><strong>Using\u00a0<code>for_each<\/code>\u00a0with an unknown key set:<\/strong>\u00a0its keys must be known before apply. Filter a known map\/list of inputs rather than discovered remote IDs.<\/li>\n\n\n\n<li><strong>Expecting a Terraform\u00a0<code>if { } else { }<\/code>\u00a0statement:<\/strong>\u00a0Terraform uses conditional expressions and collection filtering, not a top-level imperative\u00a0<code>if<\/code>\u00a0statement.<\/li>\n\n\n\n<li><strong>Confusing a dynamic block with a resource:<\/strong>\u00a0<code>dynamic<\/code>\u00a0can only generate repeatable nested blocks supported by the provider schema.<\/li>\n\n\n\n<li><strong>Assuming\u00a0<code>try<\/code>\u00a0catches static errors:<\/strong>\u00a0it cannot repair typos such as undeclared\u00a0<code>local<\/code>\u00a0or undeclared\u00a0<code>resource<\/code>\u00a0references.<\/li>\n\n\n\n<li><strong>Confusing\u00a0<code>can<\/code>\u00a0with\u00a0<code>try<\/code>:<\/strong>\u00a0<code>can()<\/code>\u00a0yields Boolean success\/failure, especially useful for validation;\u00a0<code>try()<\/code>\u00a0returns a fallback value.<\/li>\n\n\n\n<li><strong>Assuming\u00a0<code>null<\/code>\u00a0and empty values are identical:<\/strong>\u00a0null means absent\/omitted in an optional argument;\u00a0<code>[]<\/code>,\u00a0<code>{}<\/code>, and\u00a0<code>\"\"<\/code>\u00a0mean empty, which may behave differently.<\/li>\n\n\n\n<li><strong>Assuming an empty list makes\u00a0<code>alltrue<\/code>\u00a0false:<\/strong>\u00a0<code>alltrue([])<\/code>\u00a0is true;\u00a0<code>anytrue([])<\/code>\u00a0is false.<\/li>\n\n\n\n<li><strong>Assuming a failed\u00a0<code>check<\/code>\u00a0blocks deployment:<\/strong>\u00a0check assertions are advisory warnings, not policy enforcement.<\/li>\n\n\n\n<li><strong>Using\u00a0<code>count<\/code>\u00a0or\u00a0<code>for_each<\/code>\u00a0to refactor an existing applied resource without state moves:<\/strong>\u00a0that changes state addresses, and may lead to recreation. These labs deliberately use distinct directories and states.<\/li>\n\n\n\n<li><strong>Azure subscription missing:<\/strong>\u00a0AzureRM 5.x needs a subscription ID in the provider configuration or environment (<code>ARM_SUBSCRIPTION_ID<\/code>), and resource providers must be registered.<\/li>\n\n\n\n<li><strong>Azure invalid credentials:<\/strong>\u00a0Azure password must satisfy the subscription&#8217;s password rules; the Terraform variable only tests minimum length.<\/li>\n\n\n\n<li><strong>EC2 instance cannot be reached by SSH:<\/strong>\u00a0intentional. AWS security groups have no inbound SSH and no key pair; inspect instance IDs\/metadata via Terraform outputs and the AWS Console.<\/li>\n\n\n\n<li><strong>Unexpected\u00a0<code>-\/+<\/code>\u00a0replacements:<\/strong>\u00a0changing a VM name, image, subnet, or another immutable argument can force recreation; inspect the plan before applying.<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">G. Quick verification and cleanup<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform fmt -recursive\nterraform fmt -check -recursive\nterraform validate\nterraform plan\nterraform apply\nterraform output -json\nterraform state list\nterraform plan -destroy\nterraform destroy\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For an AWS VM ID from a single-instance lab:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>terraform output -json instance_ids\naws ec2 describe-instances --region us-east-1 --filters \\\n  \"Name=tag:Name,Values=logic-*\" \\\n  --query 'Reservations&#91;].Instances&#91;].&#91;InstanceId,State.Name]' --output table\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For the Azure scenario number&nbsp;<code>01<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>az group show -n rg-cond-01-eastus -o table\naz vm list -g rg-cond-01-eastus -o table\nterraform output -json vm_ids\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Do not run&nbsp;<code>terraform destroy<\/code>&nbsp;in an unrelated directory. Do not share state or&nbsp;<code>terraform.tfvars<\/code>&nbsp;containing real secrets publicly.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">H. Official reference sources<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/expressions\/conditionals\">HashiCorp: Conditional expressions<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/expressions\/for\">HashiCorp: For expressions and filtering<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/expressions\/dynamic-blocks\">HashiCorp: Dynamic blocks<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/validate\">HashiCorp: Validation, preconditions, postconditions and checks<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/block\/check\">HashiCorp: Check block<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/functions\">HashiCorp: Functions reference<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/expressions\/type-constraints#optional-object-type-attributes\">HashiCorp: Optional object attributes<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/expressions\/types#null\">HashiCorp: Null values<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/developer.hashicorp.com\/terraform\/language\/block\/variable\">HashiCorp: Input variables and nullable<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/docs.aws.amazon.com\/AWSEC2\/latest\/UserGuide\/finding-an-ami-parameter-store.html\">AWS: Amazon Linux SSM AMI parameters<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/registry.terraform.io\/providers\/hashicorp\/aws\/latest\/docs\/resources\/instance\">Terraform Registry: AWS instance<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/Azure\/azure-rest-api-specs\/blob\/main\/arm-compute\/quickstart-templates\/aliases.json\">Azure: official Ubuntu 24.04 image alias<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/registry.terraform.io\/providers\/hashicorp\/azurerm\/latest\/docs\/resources\/linux_virtual_machine\">Terraform Registry: Azure Linux VM<\/a><\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Documentation verification note<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The examples have been generated from common complete cloud-network\/VM templates and cross-checked for structural consistency and current official expression semantics.&nbsp;<strong>They have not been executed against live AWS or Azure accounts from this environment.<\/strong>&nbsp;A real&nbsp;<code>terraform init<\/code>,&nbsp;<code>validate<\/code>,&nbsp;<code>plan<\/code>, and subscription-specific image\/SKU check remain necessary before claiming a particular deployment works in a particular account. Costs, quotas, credentials, and cloud policy can affect results.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This workbook teaches the complete set of Terraform conditionals, filtering, validation, null handling, and related functions from the requested syllabus. Every numbered concept includes&nbsp;one standalone AWS EC2&#8230; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1233","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1233","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/comments?post=1233"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1233\/revisions"}],"predecessor-version":[{"id":1234,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/posts\/1233\/revisions\/1234"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/media?parent=1233"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/categories?post=1233"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/tutorials\/wp-json\/wp\/v2\/tags?post=1233"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}