Module 3, Providers and Resources
Providers
A provider is a plugin that lets Terraform communicate with an API. Without a provider, Terraform cannot create anything.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0" # any 5.x version
}
github = {
source = "integrations/github"
version = "~> 6.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
Version Constraints
| Constraint | Meaning |
|---|---|
= 5.0.0 | Exactly 5.0.0 |
!= 5.0.0 | Anything except 5.0.0 |
> 5.0 | Greater than 5.0 |
>= 5.0, < 6.0 | Between 5.0 and 6.0 |
~> 5.0 | 5.x only (pessimistic constraint) |
warning
Always pin to a major version with ~>. Never use no constraint, a provider update can break your infrastructure silently.
Multiple Provider Configurations
# Default provider
provider "aws" {
region = "us-east-1"
}
# Named provider for a second region
provider "aws" {
alias = "stockholm"
region = "eu-north-1"
}
# Use the named provider on a specific resource
resource "aws_s3_bucket" "eu_bucket" {
provider = aws.stockholm
bucket = "finpay-eu-north-1-backups"
}
Resources
resource "<PROVIDER>_<TYPE>" "<LOCAL_NAME>" {
# arguments
}
Resource References and Dependencies
Resources can reference each other. Terraform uses references to determine creation order:
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id # Terraform creates VPC first
cidr_block = "10.0.1.0/24"
}
Explicit Dependencies
When Terraform cannot detect a dependency automatically:
resource "aws_s3_bucket_policy" "allow_access" {
bucket = aws_s3_bucket.example.id
policy = data.aws_iam_policy_document.allow_access.json
depends_on = [
aws_s3_bucket_public_access_block.example
]
}
Meta-Arguments
count, multiple copies
resource "aws_instance" "server" {
count = 3
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = { Name = "server-${count.index}" }
}
for_each, create from a map or set
resource "aws_iam_user" "team" {
for_each = toset(["alice", "bob", "charlie"])
name = each.value
}
tip
Prefer for_each over count for most cases. count is index-based, removing an item from the middle triggers unexpected destroys. for_each is key-based and only affects the removed item.
lifecycle, control create/destroy behaviour
resource "aws_db_instance" "main" {
lifecycle {
prevent_destroy = true # block terraform destroy
create_before_destroy = true # zero-downtime replacement
ignore_changes = [password] # ignore console changes
}
}
Lab 2, Multi-Resource Configuration
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
tags = { Name = "lab-vpc" }
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
availability_zone = "us-east-1a"
tags = { Name = "lab-public-subnet" }
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = { Name = "lab-igw" }
}
Run terraform plan and observe the dependency graph. Visualise it:
terraform graph | dot -Tsvg > graph.svg
Knowledge Check
- What command downloads providers after writing the
required_providersblock? - What does
~> 5.0mean as a version constraint? - What is the difference between
countandfor_each? - When would you use
depends_onexplicitly? - What does
prevent_destroy = truedo? - If resource B references
aws_vpc.main.id, which resource does Terraform create first?
Exam Mapping
- HashiCorp Associate Obj 3b: Describe plugin-based architecture
- HashiCorp Associate Obj 5: Use Terraform outside of core workflow
- HashiCorp Associate Obj 8: Read, generate, and modify configuration