DEV Community

Ali KHYAR
Ali KHYAR

Posted on

Terraform 101 - Part 2/3: State, Variables, Outputs, and provisioners | By Ali KHYAR

Part 1/3: History, Workflow, and Resource Addressing: link here

State:

Concepts and local storage:

State in Terraform is the mechanism with which Terraform acts the way it does, it's what helps it map real-world resources to your configuration. why it is essential? Because with it Terraform can track which resources are deployed, so that next time when we try to deploy a new configuration code, Terraform decides which resources need to be created, updated, or destroyed, this is done by comparing the state file with the configuration code.

Terraform state is tracked through a flat file named by default terraform.tfstate, a JSON dump that contains metadata and data about deployed resources. If no backend is specified in the configuration code, the state will be stored locally, but for better practices, the state should be stored remotely for better integrity and availability across teams.
As recommended to store Terraform state remotely, it is also recommended to not lose it because you have no way to know what resources got deployed previously. You wouldn't like as well the state file to fall into wrong hands, because it may contain sensitive data about your resources.

Terraform state has 3 common sub-commands:

Image description

The first command is used to list tracked resources by Terraform state. The second one is used to show details of a tracked resource. The last command is used to remove resources from the state file so they won't be tracked anymore.

Let's see the configuration below which provisions a docker image resource and spin up a container of that image locally:

Image description

First, we will initialize the state, by using terraform init a command which will create a folder named .terraform which is a local cache where Terraform retains some files it will need for subsequent operations against this configuration (providers …), terraform init also creates .terraform.lock.hcl, a dependency lock file that gets created or updated whenever terraform init gets run.
After initializing the working directory we can run terraform plan (not mandatory) to see what resources will get deployed, then run terraform applyto deploy the actual resources. Running terraform apply will create a state file namedterraform.tfstatewhich keeps track of managed resources.

Running terraform state list will show tracked resources which in this case will return:

Image description

which are the two resources we deployed. We can see more information about the state of each resource by running terraform state show <resource_type.resource_name> that will return in the case of the docker image:

Image description

Let's remove the docker image resource from being tracked by using terraform state rm docker_image.busybox-image then destroy the resources with terraform destroydoing that will remove the image from being tracked, thus it won't be destroyed and the terraform will only destroy the container as the command output shows:

Image description

State Storage:

The default behavior of Terraform state is being locally stored, but for better availability, security, and visibility across teams, it's a better practice to store state remotely like in HashiCorp Consul, AWS S3, or Azure Storage Account. Remote state storage allows among many other things sharing outputs to other code elsewhere. You can set up where the state file is stored in the terraform block, using the backend attribute. In AWS S3, the configuration will look like this:

Image description

This assumes we have a bucket created called mybucket. The Terraform state is written to the key path/to/my/key.
Using Azure, you can also store the state as a Blob with the given Key within the Blob Container within the Blob Storage Account. Those are some configuration examples:

Image description

Variables:

Variables in Terraform serve as variables serve in programming languages, it is a way of storing data, so that you make configuration code clean and reusable. Variables are declared within Terraform using the following syntax:

Image description

Variables types are two:

  • base: string ("anything btw double quotes"), number(15, 0.15..), bool(true, false)
  • complex: list(["same", "type"]), set, maps({name = "Mabel", age = 52}), object({ port = number service = string }), tuple we can define a variable type to be combined with one or more types, for instance:

Image description

Terraform variables are referenced in configuration code with var.name_of_var, and read precedence starts with ones passed through the OS environment variables and then the terraform.tfvars file, then variables in the main configuration code.
Other parameters that are useful in variables declaration are

  • validation: useful to find errors while script didn't start running, common example below, to test IP address validation against a regex expression using the built-in function regex (we will take a look into the built-in function in part 3/3)

Image description

  • sensitive data: often you need to configure your infrastructure using sensitive or secret information such as usernames, passwords, API tokens, or Personally Identifiable Information (PII). When you do so, you need to ensure that this data is not accidentally exposed in CLI output, log output, or source control, a common solution is to set sensitive flag to be true within the variable configuration.

Outputs:

Output values give you information back to the CLI about deployed resources, they are like return in programming languages functions. Below is an output that gives back the private IP address of deployed ec2 instance (resource type: aws_instance) named my-ec2:

Image description

output variable values are shown in the CLI after successful terraform apply. You can still set senstive=true to outputs, in case they contain sensitive values.

Provisioners:

provisioners give users the ability to execute commands and scripts through Terraform resources. You can run those commands/scripts either on the machine where terraform is installed or through the resources that were created with Terraform. Each provisioner is attached to a certain resource, and it has the ability to connect to it (if it needs to) via ssh and such protocols.

There are two provisioner types: "creation time" and "destroy-time" provisioners which you can set to run when a resource is being created or destroyed.

Although provisioner looks like a good feature, HashiCorp recommends not using provisioners unless the cloud provider doesn't offer a mechanism that runs commands or scripts through resources. One Con provisioners have, is that they are not tracked by Terraform state.

Provisioners are recommended to be used if Terraform declarative model doesn't already offer the action to be taken. If while applying configuration the code exits with non-zero code, it's considered failed and the resource is tainted.

The configuration below runs two provisioners on a null resource:

Image description

one that runs in resource create and another (that contains destroy) that runs when destroying the resource, the two provisioners add 0, and 1 respectively to a file named status.txt. So as you can tell, when first provision the null_resoure there will be in status.txt 0 , and after destroying it, there will be 01 in that file.

Conclusion:

I hope this blog helps you up in understanding a bit more about terraform state, variables, outputs, and provisioners. In the next blog, I'm going to talk about Terraform modules, built-in functions, and dynamic blocks.


Part 3/3: Modules, Built-in Functions, Type Constraints, and Dynamic Blocks: link here

Top comments (0)