Variables & Outputs

Sign Up to Build

About this Architecture

Here is some information about this architecture.

How to Build This Solution

Here are the steps you can follow to build this solution on your own.

The Terraform configuration language provides several features for working with variables and outputs. On the variables side, there are input values that you can send in to Terraform from external sources if desired, as well as local values. You can also output return values from Terraform resources.

These features make the Terraform language more flexible and easier to use.

Let’s explore them now.

Input Variables

Input variables are parameters that can be used in your Terraform configuration. Think of them like function arguments; you define what they are, and how they are used in your configuration code. They can be used to customize your Terraform modules without changing the source code.

You can then set the input values either using the Terraform CLI, or via environment variables.

Input variables are usually defined in a file named variables.tf in the root folder, or in module sub-directories (you’ll learn more about modules in later lessons).

Here’s an example variables.tf file. Here, we are creating variables for a EC2 image ID that will be used in the project. We can set this one, and refer to it many times. We’re also doing the same for the availability zones.

variable "image_id" {
  type = string
}

variable "availability_zone_names" {
  type    = list(string)
  default = ["us-west-1a"]
}

Code Review

  • Variable blocks start with variable, and the name immediately follows in quotes e.g. "image_id"

  • In the variable block, you can define a number of optional values, including the default value, the type, the description, validation code, and a sensitive flag

  • You can read more about input variables here

Variable Arguments

The variable block supports the following set of arguments:

  • default - the default value of the variable.

  • type - the value type (more below)

  • description - a helpful summary of the value

  • validation - a block to define validation rules

  • sensitive - limits the output of the value

  • nullable - specify if the variable can be null within a module

Variable Types

The type argument allows you to restrict what value is accepted for the variable. If you don’t set this, any value will be accepted. Setting them is a best practice!

The types are as follows:

  • string

  • number

  • bool

You can also use type constructors to create complex types.

  • list(<TYPE>)

  • set(<TYPE>)

  • map(<TYPE>)

  • object({<ATTR NAME> = <TYPE>, ... })

  • tuple([<TYPE>, ...])

Output Values

Cloud resources have unique attributes about them. For example, EC2 instances have IDs and public IP values. These values can be used for a couple of different things. First, you may want to know what they are for record keeping.

Second, you may need them in other parts of your configuration. A common need is to use the values from network resources, like subnet and security group IDs, in other resources, like EC2 instances.

You typically define outputs in the outputs.tf file. Here’s an example of what that looks like:

output "instance_ip_addr" {
  value = aws_instance.server.private_ip
}

Code Review

  • Output blocks start with output, and are followed by the output name e.g. "instance_ip_addr"

  • In the code block you define the value to return. This value will be specific to the resource you are reading from. In our example, we are returning the aws_instance private IP address

In a later lesson you will learn how to use output values from child modules.

Local Values

Local values are temporary local variables you can use in your code. They can be used to set temporary, random, or calculated values. Here’s an example:

locals {
  project_name  = "chat"
  team          = "Game Team"
}

Code Review

  • Local value blocks start with locals

  • Inside the block you can set variables and values

  • It is possible to calculate values as well

Lab Time!

Let’s put your newfound knowledge to work with a lab!

This lab assumes you already have Terraform installed. If you don’t, you can go to the lesson in the first module.

Get Your AWS Credentials

If you're using the Skillmix Labs feature, open the lab settings (the beaker icon) on the right side of the code editor. Then, click the Start Lab button to start hte lab environment.

Wait for the credentials to load. Then run this in the terminal.

Be sure to enter in your own access key and secret key and name your profile 'smx-lab'.

$ aws configure --profile smx-lab
AWS Access Key ID [None]: 
AWS Secret Access Key [None]: 
Default region name [None]: us-west-2
Default output format [None]: 

Note: If you're using your own AWS account you'll need to ensure that you've created and configured a named AWS CLI profile named smx-lab.

Create a Project Directory

You need a directory to create the Terraform files in. Find a good place on your computer to create this directory in. Use your CLI or Command Prompt to navigate to it.

# create the diretory
$ mkdir terraform-lab 
$ cd terraform-lab

Create the Variables File

Let’s start by creating and configuring out variables.tf file. We’ll define the variables that will be used in the project.

# create the file
$ touch variables.tf

Open the file in an editor. Add the following text to it:

variable "region" {
  description = "The AWS region"
  default     = "us-west-2"
  type        = string
}

Code Review

  • Here we have created one variable for the region. We have given it a default value of us-west-2

Create the Outputs File

The outputs file can be used to print information about our resources to our terminal. We will use it to print the IDs of the resources that are created.

Start off by creating the outputs file:

# create the file
$ touch outputs.tf

Open the file in an editor. Add the following text to it:

output "security_group_id" {
  value = aws_security_group.web_instance_sg.id
}

output "launch_template_id" {
  value = aws_launch_template.web_launch_template.id
}

output "asg_id" {
  value = aws_autoscaling_group.asg.id
}

Create the main.tf File

Now we can create the main.tf file. This file will hold the data source and resource blocks of our project. In your working directory, create a file called main.tf.

# create the file
$ touch main.tf

Add the Terraform & Provider Block

In this lab we will use the AWS provider. As mentioned, there are many providers you can choose from. You can look at the Terraform Provider Registry to see what’s available.

Open the main.tf file and add the provider block as follows:

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.42"
    }
  }
  required_version = ">= 0.15.3"
}

provider "aws" {
  profile = "skillmix-lab"
  region  = var.region
}

Code Review

  • Most of this code should look familiar with one exception. Take a look at the “region” attribute of the provider block. Here we use var.region, which is an input from our variables file

Add the VPC & Subnet Data Sources

Our Auto Scaling Group configuration needs to know our VPC and Subnet IDs. In this lab, those resources are already created on the AWS account. Since our Terraform configuration did not create them, we need to read those values using data sources.

Append this code to the main.tf file:

data "aws_vpc" "lab_vpc" {
  filter {
    name = "tag:Name"
    values = ["Skillmix Lab"]
  }
}

data "aws_subnet" "lab_subnet" {
  filter {
    name = "tag:Name"
    values = ["Skillmix Lab Public Subnet (AZ1)"]
  }
}

Code Review

  • The first data block above uses the AWS provider’s AWS VPC data source. It uses a name tag filter to search for the right VPC

  • The second data block does the same thing, but for the lab subnet

Add the Security Group

Now we need to define a security group resource. This security group will be used for the auto scaling group’s EC2 instances. Note that we aren’t defining any ingress or egress rules right now.

Append this code to the main.tf file:

resource "aws_security_group" "web_instance_sg" {
  name        = "web-server-security-group"
  description = "Allowing requests to the web servers"
  vpc_id = data.aws_vpc.lab_vpc.id

  tags = {
    Name = "web-server-security-group"
  }
}

Code Review

  • This resource block creates an "aws_security_group" named "web_instance_sg". It provides several settings, most notably the vpc_id. This ID is read from our data source using the notation data.aws_vpc.lab_vpc.id

Add the Launch Template & Auto Scaling Group Resources

Now we can add the code to create the Launch Template and Auto Scaling Group resources.

The Launch Template defines settings like the AMI ID to use, the instance type, etc to use in our Auto Scaling Group.

The Auto Scaling Group defines settings such as the number of instances to keep in the group, the availability zones used, etc.

Append this code to the main.tf file:

resource "aws_launch_template" "web_launch_template" {
  name          = "web-launch-template"
  image_id      = "ami-098e42ae54c764c35"
  instance_type = "t2.micro"
  vpc_security_group_ids = [aws_security_group.web_instance_sg.id]
}

resource "aws_autoscaling_group" "asg" {
  vpc_zone_identifier = [data.aws_subnet.lab_subnet.id]
  desired_capacity   = 1
  max_size           = 1
  min_size           = 1

  launch_template {
    id      = aws_launch_template.web_launch_template.id
    version = "$Latest"
  }
}

Code Review

  • The first resource block creates a "aws_launch_template" named "web_launch_template". It defines the AMI ID, instance type, and the security group to launch the instance into

  • The second resource block creates a "aws_autoscaling_group" named "asg". Here we set the VPC subnet to be used, the capacity settings, and the launch template

Main File Review

Here’s the full main.tf file to ensure you’ve built it correctly.

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 3.42"
    }
  }
  required_version = ">= 0.15.3"
}

provider "aws" {
  profile = "skillmix-lab"
  region  = var.region
}

data "aws_vpc" "lab_vpc" {
  filter {
    name = "tag:Name"
    values = ["Skillmix Lab"]
  }
}

data "aws_subnet" "lab_subnet" {
  filter {
    name = "tag:Name"
    values = ["Skillmix Lab Public Subnet (AZ1)"]
  }
}

resource "aws_security_group" "web_instance_sg" {
  name        = "web-server-security-group"
  description = "Allowing requests to the web servers"
  vpc_id = data.aws_vpc.lab_vpc.id

  tags = {
    Name = "web-server-security-group"
  }
}

resource "aws_launch_template" "web_launch_template" {
  name          = "web-launch-template"
  image_id      = "ami-098e42ae54c764c35"
  instance_type = "t2.micro"
  vpc_security_group_ids = [aws_security_group.web_instance_sg.id]
}

resource "aws_autoscaling_group" "asg" {
  vpc_zone_identifier = [data.aws_subnet.lab_subnet.id]
  desired_capacity   = 1
  max_size           = 1
  min_size           = 1

  launch_template {
    id      = aws_launch_template.web_launch_template.id
    version = "$Latest"
  }
}

Run Terraform Init

Run the command to initialize our Terraform project. This will setup files and folders, and download dependencies.

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching "~> 3.42"...
- Installing hashicorp/aws v3.42.0...
- Installed hashicorp/aws v3.42.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Run Terraform Plan

Run this command to create our execution plan.

$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_autoscaling_group.asg will be created
  + resource "aws_autoscaling_group" "asg" {
      + arn                       = (known after apply)
      + availability_zones        = (known after apply)
      + default_cooldown          = (known after apply)
      + desired_capacity          = 1
      + force_delete              = false
      + force_delete_warm_pool    = false
      + health_check_grace_period = 300
      + health_check_type         = (known after apply)
      + id                        = (known after apply)
      + max_size                  = 1
      + metrics_granularity       = "1Minute"
      + min_size                  = 1
      + name                      = (known after apply)
      + protect_from_scale_in     = false
      + service_linked_role_arn   = (known after apply)
      + vpc_zone_identifier       = [
          + "subnet-072b5e72aeb900fc2",
        ]
      + wait_for_capacity_timeout = "10m"

      + launch_template {
          + id      = (known after apply)
          + name    = (known after apply)
          + version = "$Latest"
        }
    }

# additional output...

Run Terraform Apply

Let’s create our infrastructure! Run the apply command, accept the changes, and watch the magic happen.

The Auto Scaling Group will launch one EC2 instance. Terraform will wait for this operation to complete successfully before moving on.

$ terraform apply

# additional output...

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_security_group.web_instance_sg: Creating...
aws_security_group.web_instance_sg: Creation complete after 7s [id=sg-05a06626e70e1d1b4]
aws_launch_template.web_launch_template: Creating...
aws_launch_template.web_launch_template: Creation complete after 4s [id=lt-045072099d78e4680]
aws_autoscaling_group.asg: Creating...
aws_autoscaling_group.asg: Still creating... [10s elapsed]
aws_autoscaling_group.asg: Still creating... [20s elapsed]
aws_autoscaling_group.asg: Still creating... [30s elapsed]
aws_autoscaling_group.asg: Still creating... [40s elapsed]
aws_autoscaling_group.asg: Creation complete after 48s [id=tf-asg-20210529090734650800000002]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

asg_id = "tf-asg-20210529090734650800000002"
launch_template_id = "lt-045072099d78e4680"
security_group_id = "sg-05a06626e70e1d1b4"

Note: Take note of the end of the output. You’ll see the IDs that we configured in the outputs.tf file. You can login to the console to see the resources that were created. You should have a running EC2 instance.

Run Terraform Show

Terraform keeps its state in a file called terraform.tfstate. This file will show up in your root directory. You can run terraform show to inspect its contents.

This command will show you things like the instance ID, ARN, IPs, etc.

$ terraform show

# aws_instance.web_server:
resource "aws_instance" "web_server" {
    ami                                  = "ami-0cf6f5c8a62fa5da6"
    arn                                  = "arn:aws:ec2:us-west-2:013084617604:instance/i-0b428c12917f18248"
    associate_public_ip_address          = true
    availability_zone                    = "us-west-2b"
    cpu_core_count                       = 1
    cpu_threads_per_core                 = 1
    disable_api_termination              = false
    ebs_optimized                        = false
    get_password_data                    = false
    hibernation                          = false
    id                                   = "i-0b428c12917f18248"
    instance_initiated_shutdown_behavior = "stop"
    instance_state                       = "running"
    instance_type                        = "t2.micro"
    ipv6_address_count                   = 0
    ipv6_addresses                       = []
    monitoring                           = false
    primary_network_interface_id         = "eni-0089fd346335d31cb"
    private_dns                          = "ip-10-192-11-47.us-west-2.compute.internal"
    private_ip                           = "10.192.11.47"
    public_ip                            = "52.27.82.42"
    secondary_private_ips                = []
    security_groups                      = []
    source_dest_check                    = true
    subnet_id                            = "subnet-0c2d1e42f315784f8"
    tags                                 = {
        "Name" = "skillmix-lab-instance"
    }

# additional output...

Extra Credit

The security group was created without any ingress or egress rules. Read through the security group documentation to see if you can do it. Add the rules and then use the plan and apply commands to deploy the change.

Run Terraform Destroy

Now it’s time to complete the lifecycle with terraform destroy. This command will destroy all of the resources created by the project.

$ terraform destroy

# additional output...

Plan: 0 to add, 0 to change, 1 to destroy.

Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

aws_instance.web_server: Destroying... [id=i-0b428c12917f18248]