Terraform · lesson 5 of 17
Beginner
7- 1 Terraform Fundamentals (IaC)
- 2 Terraform Workflow (init → validate → plan → apply)
- 3 Terraform State (tfstate, remote state, locking)
- 4 Terraform Variables and Outputs
- 5 Providers and Resources (the building blocks of Terraform)
- 6 Terraform Locals: Computed Values Without Repetition
- 7 Terraform Modules (reuse, composition, and clean structure)
Intermediate
7- 8 Terraform count and for_each (repeat resources safely)
- 9 The lifecycle Block: prevent_destroy, create_before_destroy, ignore_changes
- 10 Data Sources and Dependencies (read existing infrastructure)
- 11 sensitive = true: What It Hides, and What It Doesn't
- 12 Remote State Safety (locking, drift, and recovery)
- 13 Workspaces and Environments (dev/stage/prod patterns)
- 14 Terraform CI/CD with GitHub Actions (plan/apply)
Advanced
3Also part of Terraform
4Providers and Resources (the building blocks of Terraform)
How providers supply resource types, how arguments differ from attributes, how references order an apply, and what a provider version mismatch looks like.
By SkillByExample · Updated
Terraform core can’t create anything by itself. Providers are plugins that know an API, and
each one supplies resource types. A resource block declares one object of one of those
types. These examples use Terraform 1.16.5 with hashicorp/tls 4.4.1, which generates keys
locally, and hashicorp/local 2.9.1.
Two providers, two resources, one reference
terraform {
required_providers {
tls = {
source = "hashicorp/tls"
version = "~> 4.4"
}
local = {
source = "hashicorp/local"
version = "~> 2.9"
}
}
}
resource "tls_private_key" "deploy" {
algorithm = "ED25519"
}
resource "local_file" "public_key" {
filename = "${path.module}/deploy.pub"
content = tls_private_key.deploy.public_key_openssh
file_permission = "0644"
}
output "fingerprint" {
value = tls_private_key.deploy.public_key_fingerprint_sha256
}
$ terraform providers -no-color
Providers required by configuration:
.
├── provider[registry.terraform.io/hashicorp/tls] ~> 4.4
└── provider[registry.terraform.io/hashicorp/local] ~> 2.9
$ terraform apply -auto-approve -no-color
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:
# local_file.public_key will be created
+ resource "local_file" "public_key" {
+ content = (known after apply)
+ content_base64sha256 = (known after apply)
+ content_base64sha512 = (known after apply)
+ content_md5 = (known after apply)
+ content_sha1 = (known after apply)
+ content_sha256 = (known after apply)
+ content_sha512 = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0644"
+ filename = "./deploy.pub"
+ id = (known after apply)
}
# tls_private_key.deploy will be created
+ resource "tls_private_key" "deploy" {
+ algorithm = "ED25519"
+ ecdsa_curve = "P224"
+ id = (known after apply)
+ private_key_openssh = (sensitive value)
+ private_key_pem = (sensitive value)
+ private_key_pem_pkcs8 = (sensitive value)
+ public_key_fingerprint_md5 = (known after apply)
+ public_key_fingerprint_sha256 = (known after apply)
+ public_key_openssh = (known after apply)
+ public_key_pem = (known after apply)
+ rsa_bits = 2048
}
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ fingerprint = (known after apply)
tls_private_key.deploy: Creating...
tls_private_key.deploy: Creation complete after 0s [id=326c983f49d516fe21443965e46a53f376e69b8a]
local_file.public_key: Creating...
local_file.public_key: Creation complete after 0s [id=3c811d52ba68748759f707775a4b1ebe6abc1744]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
fingerprint = "SHA256:8PniQvkxlD5eGfOfHBNAzdd3evjjMFbCMwB+vfog3cs"
$ cat deploy.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJhQi/hTjnuXUp3aEK2/XBQKnVyTgv53Z7eD2tncT542
The resource type’s prefix picks the provider: tls_private_key comes from the tls provider.
The block’s address is type.name, here tls_private_key.deploy. You set one argument,
algorithm, but the plan lists eleven attributes. The provider fills in defaults
(rsa_bits = 2048 is irrelevant for ED25519 but still present) and computes the key material.
The private key fields are marked (sensitive value) by the provider itself. Referring to
public_key_openssh is what made Terraform create the key before the file.
A typo is caught by the provider’s schema
Change tls_private_key to tls_privatekey in the resource block only:
$ terraform validate -no-color
Error: Invalid resource type
on main.tf line 14, in resource "tls_privatekey" "deploy":
14: resource "tls_privatekey" "deploy" {
The provider hashicorp/tls does not support resource type "tls_privatekey".
Did you mean "tls_private_key"?
Error: Reference to undeclared resource
on main.tf line 20, in resource "local_file" "public_key":
20: content = tls_private_key.deploy.public_key_openssh
A managed resource "tls_private_key" "deploy" has not been declared in the
root module.
Error: Reference to undeclared resource
on main.tf line 25, in output "fingerprint":
25: value = tls_private_key.deploy.public_key_fingerprint_sha256
A managed resource "tls_private_key" "deploy" has not been declared in the
root module.
The first error is the real one. The provider binary carries a schema listing every resource type and argument, and Terraform uses it to suggest the correct name. The other two errors follow from it: every reference to the correct address now points at nothing. Fix the first error in a list and re-run before chasing the rest.
Changing the version constraint
Edit the tls constraint from ~> 4.4 to ~> 3.0 after applying:
$ terraform plan -no-color
Error: Inconsistent dependency lock file
The following dependency selections recorded in the lock file are
inconsistent with the current configuration:
- provider registry.terraform.io/hashicorp/tls: locked version selection 4.4.1 doesn't match the updated version constraints "~> 3.0"
To update the locked dependency selections to match a changed configuration,
run:
terraform init -upgrade
$ terraform init -upgrade -no-color
Initializing the backend...
Initializing provider plugins...
- Finding hashicorp/tls versions matching "~> 3.0"...
- Finding hashicorp/local versions matching "~> 2.9"...
- Installing hashicorp/tls v3.4.0...
- Installed hashicorp/tls v3.4.0 (signed by HashiCorp)
- Using previously-installed hashicorp/local v2.9.1
Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.
$ terraform plan -no-color
Planning failed. Terraform encountered an error while generating this plan.
Error: Resource instance managed by newer provider version
The current state of tls_private_key.deploy was created by a newer provider
version than is currently selected. Upgrade the tls provider to work with
this state.
There are two separate guards here. The lock file won’t silently switch versions, so changing
the constraint requires an explicit init -upgrade. Then the state refuses an older provider,
because 4.4.1 wrote the key in a format 3.4.0 may not understand. Upgrades go forward easily,
but going back to an older version is blocked. Put the constraint back to ~> 4.4 and run
terraform init -upgrade again to recover.
Practice
1. Ask for an attribute the resource doesn’t have: change the output to
tls_private_key.deploy.fingerprint.
Solution
$ terraform validate -no-color
Error: Unsupported attribute
on main.tf line 25, in output "fingerprint":
25: value = tls_private_key.deploy.fingerprint
This object has no argument, nested block, or exported attribute named
"fingerprint".
Attribute names come from the same schema as resource types, so a wrong one never reaches the plan. The provider’s documentation page lists every attribute under “Attributes Reference”.
2. Read attributes interactively with terraform console instead of adding outputs.
Solution
$ echo "tls_private_key.deploy.algorithm" | terraform console
"ED25519"
$ echo 'split(" ", local_file.public_key.content)[0]' | terraform console
"ssh-ed25519"
terraform console evaluates any expression against the current state, including function
calls. It’s the fastest way to check an expression before writing it into a resource.
3. Switch algorithm to "RSA" and predict how many resources the plan replaces.
Solution
$ terraform plan -no-color | grep -E "forces|Plan:"
EOT -> (known after apply) # forces replacement
~ algorithm = "ED25519" -> "RSA" # forces replacement
Plan: 2 to add, 0 to change, 2 to destroy.
Two. A key can’t change algorithm in place, and the file’s content comes from the key, so the file has to be replaced too. With a cloud provider, the same chain would also replace every server that has the old key installed.
Next: locals, which compute a value once and reuse it everywhere.