Terraform Concepts


Terraform Commands
Comment
Terraform State
Dependancy Lock file
Create AWS Instance
Create AWS RDS Instance
Create Security Group and Elastic IP
Attributes, Cross Resource Attribute References & String Interpolation
output-values
Output
Variables
Variable Definitions File (TFVARS)
Variable Definition Precedence
Data Types
Data Types List & Number Example
Data Types Map
Data Types Set
Count & Count Index Parameter
Conditional Expression
Local Values
Function
Zipmap Function
toset Function
Data Sources
fetch-ami-data-source
Debugging Terraform
Dynamic Block
Tainting / Replace
Splat Expression "*"
Graph
plan-file
terraform-Setting
large-infra
AWS Hardening with Terraform Code
Lifecycle-meta-argument-ignore_changes
Lifecycle-meta-argument-create-before-destroy
Lifecycle-meta-argument-prevent_destroy
for_each
Provisioners-local-exec
Provisioners-remote-exec
Provisioners-failure-behaviour
Provisioners-create-destroy-time-provisioner
Modules
Modules-local-Output
Modules-local-Output->ConsoleLog
Workspace
Workspace-Output
S3 Backend
State File Locking
StateFileLockDynamo
State Management
Remote State
Import
Multiple-Providers
Sensitive Parameter
Vault
Dependency-lock
Heredoc Syntax
Terraform-Older-Version
Example-1

Terraform Commands
terraform init
terraform init -migrate-state
terraform init -reconfigure
terraform plan
terraform plan -destroy
terraform plan -out=demopath
terraform apply demopath
terraform fmt
terraform apply -refresh-only -auto-approve			-> Refresh fetch real remote objects & only modify state file
terraform apply										-> Terraform will match current state to desired state
terraform apply -auto-approve
terraform destroy									-> Delete All Resources in One Folder
terraform destroy -target aws_instance.AmazonAMI	-> resource type.local resource name
terraform apply -var-file="custom.tfvars"			-> Explicitly Run Custom tfvars
terraform validate									-> Validating syntax error in terrafom configuration files 
terraform apply -replace="aws_instance.myec2		-> Recreating the resource
export TF_LOG_PATH=/tmp/crash.log
export TF_LOG=TRACE
terraform force-unlock [options] LOCK_ID
terraform workspace show
terraform workspace list
terraform workspace new dev
terraform workspace select dev
terraform state list
terraform state mv
terraform state pull
terraform state rm
terraform state show
terraform import aws_vpc.Terraform-VPC vpc-01b001b6d28e8fe52
terraform plan -generate-config-out=generated.tf
terraform plan -generate-config-out=mysg.tf -chdir D:\AWS Triad & Terraform\import

Comment
#				-> Single line comment
//				-> Alternate to #
/* and */		-> Multi line comment

Terraform State
Terraform State - terraform.tfstate -> Terraform State File has all Rources Configurations

Desired State - Resource Configurations in Main Terraform File
Current State - Present Resource Configurations in AWS 

Terraform Desired state will replace AWS Current State AWS Console Manual Changes will be replced
Condition Only Main Terraform File Resource configurations will be considered to change not Terraform State File Configurations(.tfstate)

Dependancy Lock file
.terraform.lock.hcl Dependancy Lock file: to lock in specific version
terraform init -upgrade Extend lock version

Create AWS Instance
provider "aws" {
  region     = "ap-south-1"
  access_key = "xxxxxxxxxxxxxxxxx"
  secret_key = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
resource "aws_instance" "AmazonAMI" {
  ami           = "ami-0cc9838aa7ab1dce7"
  instance_type = "t3.micro"
  tags = {
    Name = "AmazonAMIInstance"
  }
  depends_on = [aws_eip.lb]

}
resource type aws_instance
local resource name AmazonAMI
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance

Create AWS RDS Instance
provider "aws" {
  region     = "ap-south-1"
}
resource "aws_db_instance" "default" {
  allocated_storage    = 5
  storage_type         = "gp2"
  engine               = "mysql"
  engine_version       = "5.7"
  instance_class       = "db.t2.micro"
  db_name              = "mydb"
  username             = "foo"
  password             = "${file("../rds_pass.txt")}"
  parameter_group_name = "default.mysql5.7"
  skip_final_snapshot = "true"
}

Create Security Group and Elastic IP
provider "aws" {
  region = "ap-south-1"
}

resource "aws_security_group" "allow_http" {
  name        = "terraform-SG"
  description = "Managed from Terraform"
}

resource "aws_vpc_security_group_ingress_rule" "allow_tls_ipv4" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv4         = "0.0.0.0/0"
  from_port         = 80
  ip_protocol       = "tcp"
  to_port           = 80
}

resource "aws_vpc_security_group_egress_rule" "allow_all_traffic_ipv4" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv4         = "0.0.0.0/0"
  ip_protocol       = "-1" # semantically equivalent to all ports
}

resource "aws_eip" "lb" {
  domain = "vpc"
}

Attributes, Cross Resource Attribute References & String Interpolation
provider "aws" {
  region = "ap-south-1"
}

resource "aws_eip" "lb" {
  domain   = "vpc"
}

resource "aws_security_group" "example" {
  name        = "attribute-sg"
}

resource "aws_vpc_security_group_ingress_rule" "example" {
  security_group_id = aws_security_group.example.id

  cidr_ipv4   = "${aws_eip.lb.public_ip}/32"
  from_port   = 443
  ip_protocol = "tcp"
  to_port     = 443
}

Cross Resource Attribute References resourceType.localResourceName.attribute aws_security_group.example.id
String Interpolation "${aws_eip.lb.public_ip}/32"

output-values
provider "aws" {
  region = "us-east-1"
}

resource "aws_eip" "lb" {
  domain   = "vpc"
}

output "public-ip" {
  value = aws_eip.lb.public_ip
}

Output
resource "aws_iam_user" "lb" {
  name = "iamuser.${count.index}"
  count = 3
  path = "/system/"
}

output "iam_names" {
  value = aws_iam_user.lb[*].name
}

output "iam_arn" {
  value = aws_iam_user.lb[*].arn
}

Console Command terraform output iam_names
Console Command terraform output iam_arn

Variables

SecurityGroup.tf

provider "aws" {
  region = "ap-south-1"
}

resource "aws_security_group" "allow_tls" {
  name        = "terraform-firewall"
  description = "Managed from Terraform"
}

resource "aws_vpc_security_group_ingress_rule" "allow_ssh_ipv4" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv4         = var.vpc_ip
  from_port         = var.ssh_port
  ip_protocol       = "tcp"
  to_port           = var.ssh_port
}

resource "aws_vpc_security_group_ingress_rule" "allow_http_ipv4" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv4         = var.open_ip
  from_port         = var.http_port
  ip_protocol       = "tcp"
  to_port           = var.http_port
}

resource "aws_vpc_security_group_egress_rule" "allow_all_traffic_ipv4" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv4         = var.open_ip
  ip_protocol       = "-1" # semantically equivalent to all ports
}

variables.tf

variable "vpc_ip" {
  default = "158.34.56.203/32"
  description = "This is Open Private Public IP"
}

variable "open_ip" {
  default = "0.0.0.0/0"
  description = "This is Open Public IP"
}

variable "ssh_port" {
  default = "22"
}

variable "http_port" {
  default = "80"
}

Variable Definitions File (TFVARS)
SecurityGroup.tf

provider "aws" {
  region = "ap-south-1"
}

resource "aws_security_group" "allow_tls" {
  name        = "terraform-firewall"
  description = "Managed from Terraform"
}

resource "aws_vpc_security_group_ingress_rule" "allow_ssh_ipv4" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv4         = var.vpc_ip
  from_port         = var.ssh_port
  ip_protocol       = "tcp"
  to_port           = var.ssh_port
}

resource "aws_vpc_security_group_ingress_rule" "allow_http_ipv4" {
  security_group_id = aws_security_group.allow_tls.id
  cidr_ipv4         = var.open_ip
  from_port         = var.http_port
  ip_protocol       = "tcp"
  to_port           = var.http_port
}

variables.tf

variable "vpc_ip" {
  description = "Now vpc_ip taken from dev.tfvars"
}

variable "open_ip" {
  default     = "1.1.1.1/1"
  description = "Even 1.1.1.1/1 is default for open_ip it will give priority to dev.tfvars only"
}

variable "ssh_port" {}

variable "http_port" {}

dev.tfvars

vpc_ip    = "158.34.56.203/32"
open_ip   = "0.0.0.0/0"
ssh_port  = "22"
http_port = "80"


terraform apply -auto-approve -var-file="dev.tfvars"

Variable Definition Precedence
variable.tf				<- Priority 4
Environment varibles	<- Priority 3
dev.tfvars				<- Priority 2
CLI -var-file			<- Priority 1

Data Types
string
number
bool
list
set
map
null

Data Types List & Number Example


variable "my-list" {
  type = list
}

variable "my-list" {
  type = list(number)
}

output "variable_value" {
  value = var.my-list
}

IAM Example

variable "username" {
   type = number
}

resource "aws_iam_user" "user" {
  name = var.username
}

EC2 Example

resource "aws_instance" "web" {
  ami           = "ami-0c101f26f147fa7fd"
  instance_type = "t3.micro"
  vpc_security_group_ids = ["sg-06dc77ed59c310f03"]
}

vpc_security_group_ids Should be in list as per terraform registry

Data Types Map
variable "my-map" {
  type = map
  default = {
    Name = "Alice"
    Team = "Payments"
  }
}

output "variable_value" {
  value = var.my-map  		# value = var.my-map["Team"]
}

Data Types Set
variable "my-set" {
  type = set
  default = {"Apple", "Banana", "Mango"}
}

output "variable_value" {
  value = var.my-set  
}

Count & Count Index Parameter

Example

resource "aws_instance" "instance-1" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = "t2.micro"
   count = 3
}

Main Example

variable "elb_names" {
  type = list
  default = ["dev-loadbalancer", "stage-loadbalanacer","prod-loadbalancer"]
}

resource "aws_iam_user" "lb" {
  name = var.elb_names[count.index]
  count = 3
  path = "/system/"
}

Conditional Expression

variable "istest" {
	istest = false
	}

resource "aws_instance" "dev" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = "t2.micro"
   count = var.istest == true ? 3 : 0
}

resource "aws_instance" "prod" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = "t2.large"
   count = var.istest == false ? 1 : 0
}

If istest = true then 3 EC2 "t2.micro" instances will created
If istest = false then 1 EC2 "t2.large" instance will created

Local Values
locals {
  common_tags = {
    Owner = "DevOps Team"
    service = "backend"
  }
}
resource "aws_instance" "app-dev" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = "t2.micro"
   tags = local.common_tags
}

resource "aws_instance" "db-dev" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = "t2.small"
   tags = local.common_tags
}

resource "aws_ebs_volume" "db_ebs" {
  availability_zone = "us-west-2a"
  size              = 8
  tags = local.common_tags
}

Function

locals {
  time = formatdate("DD MMM YYYY hh:mm ZZZ", timestamp())
}

variable "region" {
  default = "ap-south-1"
}

variable "tags" {
  type = list
  default = ["firstec2","secondec2"]
}

variable "ami" {
  type = map
  default = {
    "us-east-1" = "ami-0323c3dd2da7fb37d"
    "us-west-2" = "ami-0d6621c01e8c2de2c"
    "ap-south-1" = "ami-0470e33cd681b2476"
  }
}

resource "aws_key_pair" "loginkey" {
  key_name   = "login-key"
  public_key = file("${path.module}/id_rsa.pub")
}

resource "aws_instance" "app-dev" {
   ami = lookup(var.ami,var.region)
   instance_type = "t2.micro"
   key_name = aws_key_pair.loginkey.key_name
   count = 2

   tags = {
     Name = element(var.tags,count.index)
   }
}


output "timestamp" {
  value = local.time
}

Zipmap Function
zipmap(["pineapple","oranges","strawberry"], ["yellow","orange","red"])

resource "aws_iam_user" "lb" {
  name = "demo-user.${count.index}"
  count = 3
  path = "/system/"
}

output "arns" {
  value = aws_iam_user.lb[*].arn
}


output "zipmap" {
  value = zipmap(aws_iam_user.lb[*].name, aws_iam_user.lb[*].arn)
}

toset Function

toset function will convert LIST of values to SET
toset(["a","b","c","a"])

toset([
		"a",
		"b",
		"c",
	])

Data Sources
Intead Hardcoding Static value Dynamically call AWS Datasouce to fetch latest data

data-source-01.tf

terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
    }
  }
}

provider "digitalocean" {
  token = "your-token-here"
}

data "digitalocean_account" "example" {}

data-source-02.tf

data "local_file" "foo" {
  filename = "${path.module}/demo.txt"
}
output "data" {
    value = data.local_file.foo.content
}

data-source-03.tf

provider "aws" {
    region = "us-east-1"
}

data "aws_instances" "example" {}

data-source-format.tf

provider "aws" {
    region = "us-east-1"
}

data "aws_instance" "example" {
 filter {
    name   = "tag:Team"
    values = ["Production"]
  }
}

fetch-ami-data-source
provider "aws" {
  region = "ap-south-1"
}

data "aws_ami" "myimage" {
  most_recent      = true
  owners           = ["amazon"]

  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.myimage.image_id
  instance_type = "t2.micro"
}

Debugging Terraform
export TF_LOG_PATH=/tmp/crash.log
export TF_LOG=TRACE

Dynamic Block

Example.tf

resource "aws_security_group" "demo_sg" {
  name        = "sample-sg"

  ingress {
    from_port   = 8300
    to_port     = 8300
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 9200
    to_port     = 9200
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 9500
    to_port     = 9500
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

dynamic-block.tf

variable "sg_ports" {
  type        = list(number)
  description = "list of ingress ports"
  default     = [8200, 8201,8300, 9200, 9500]
}

resource "aws_security_group" "dynamicsg" {
  name        = "dynamic-sg"
  description = "Ingress for Vault"

  dynamic "ingress" {
    for_each = var.sg_ports
    iterator = port
    content {
      from_port   = port.value
      to_port     = port.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }

  dynamic "egress" {
    for_each = var.sg_ports
    content {
      from_port   = egress.value
      to_port     = egress.value
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
    }
  }
}

Tainting / Replace

resource "aws_instance" "myec2" {
    ami = "ami-00c39f71452c08778"
    instance_type = "t2.micro"
}

Recreating the resource:
terraform apply -replace="aws_instance.myec2"
"terraform taint" command replaced by terraform apply -replace=""

Splat Expression "*"
resource "aws_iam_user" "lb" {
  name = "iamuser.${count.index}"
  count = 3
  path = "/system/"
}

output "arns" {
  value = aws_iam_user.lb[*].arn
}
"*" is the splat expression

Graph
resource "aws_instance" "myec2" {
   ami = "ami-082b5a644766e0e6f"
   instance_type = "t2.micro"
}

resource "aws_eip" "lb" {
  instance = aws_instance.myec2.id
  vpc      = true
}

resource "aws_security_group" "allow_tls" {
  name        = "allow_tls"

  ingress {
    description = "TLS from VPC"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["${aws_eip.lb.private_ip}/32"]

  }
}
terraform graph > graph.dot
yum install graphviz
cat graph.dot | dot -Tsvg > graph.svg

plan-file
terraform plan -out=demopath
terraform apply demopath

"terraform" Setting
terraform {
  required_version = "< 0.11"
  required_providers {
    aws = "~> 2.0"
  }
}

resource "aws_instance" "myec2" {
   ami = "ami-0b1e534a4ff9019e0"
   instance_type = "t2.micro"
}

large-infra
provider "aws" {
  region     = "ap-southeast-1"
  access_key = "YOUR-KEY"
  secret_key = "YOUR-KEY"
}

module "vpc" {
  source = "terraform-aws-modules/vpc/aws"

  name = "my-vpc"
  cidr = "10.0.0.0/16"

  azs             = ["ap-southeast-1a", "ap-southeast-1b", "ap-southeast-1c"]
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]

  tags = {
    Terraform = "true"
    Environment = "dev"
  }
}

resource "aws_security_group" "allow_ssh_conn" {
  name        = "allow_ssh_conn"
  description = "Allow SSH inbound traffic"

  ingress {
    description = "SSH into VPC"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    description = "HTTP into VPC"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    description = "Outbound Allowed"
    from_port   = 0
    to_port     = 65535
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}


resource "aws_instance" "myec2" {
   ami = "ami-0b1e534a4ff9019e0"
   instance_type = "t2.micro"
   key_name = "ec2-key"
   vpc_security_group_ids  = [aws_security_group.allow_ssh_conn.id]
}

terrafom plan -target=ec2
Setting Refresh as False: terraform plan -refresh=false
Setting Refresh along with Target flags terraform plan -refresh=false -target=aws_security_group.allow_ssh_conn

AWS Hardening with Terraform Code

vpc flow logs
security hub
aws config
cloud trial

Seperate folders for each services
ec2.tf
rds.tf
sg.tf
vpc.tf

Lifecycle-meta-argument-ignore_changes
resource "aws_instance" "myec2" {
    ami = "ami-0f34c5ae932e6f0e4"
    instance_type = "t2.micro"

    tags = {
        Name = "HelloEarth"
    }

    lifecycle {
        ignore_changes = [tags] 		
		# ignore_changes = [tags,instance_type]
		# ignore_changes = all
    }
}

Lifecycle-meta-argument-create-before-destroy
resource "aws_instance" "myec2" {
    ami = "ami-0f34c5ae932e6f0e4"
    instance_type = "t2.micro"

    tags = {
        Name = "HelloEarth"
    }

    lifecycle {
      create_before_destroy = true
    }
}

Lifecycle-meta-argument-prevent_destroy
resource "aws_instance" "myec2" {
    ami = "ami-0f34c5ae932e6f0e4"
    instance_type = "t2.micro"

    tags = {
        Name = "HelloEarth"
    }

    lifecycle {
      prevent_destroy = true
    }
}

for_each
IAM User

resource "aws_iam_user" "iam" {
  for_each = toset( ["user-01","user-02", "user-03"] )
  name     = each.key
}

EC2 Instance Example-1 

resource "aws_instance" "myec2" {
  ami = "ami-0cea098ed2ac54925"
  for_each  = {
      key1 = "t2.micro"
      key2 = "t2.medium"
   }
  instance_type    = each.value
  key_name         = each.key
  tags =  {
   Name = each.value
    }
}

EC2 Instance Example-2 

variable "instance_config" {
  type = map(any)
  default = {
    instance1 = { instance_type = "t2.micro", ami = "ami-03a6eaae9938c858c" }
    instance2 = { instance_type = "t2.small", ami = "ami-053b0d53c279acc90" }
  }
}

resource "aws_instance" "AmazonAMI" {

  for_each      = var.instance_config
  instance_type = each.value.instance_type
  ami           = each.value.ami
  key_name      = each.key
  tags = {
    Name = each.value.instance_type
  }
}

Provisioners-local-exec
Provisioners is Removed/Deprecated

resource "aws_instance" "myec2" {
   ami = "ami-04e5276ebb8451442"
   instance_type = "t2.micro"

   provisioner "local-exec" {
    command = "echo ${self.private_ip} >> server_ip.txt"
   }
}

Scenerio 1

resource "aws_iam_user" "lb" {
  name = "demoiamuser"

  provisioner "local-exec" {
    command = "echo local-exec provisioner is starting"
  }
}

Scenerio 2

resource "aws_iam_user" "lb" {
  name = "demoiamuser"

  provisioner "local-exec" {
    command = "echo local-exec provisioner is starting"
  }

  provisioner "local-exec" {
    command = "echo local-exec provisioner is starting for 2nd time"
  }
}

Provisioners-remote-exec
resource "aws_instance" "myec2" {
   ami = "ami-04e5276ebb8451442"
   instance_type = "t2.micro"
   key_name = "terraform-key"
   vpc_security_group_ids = ["sg-0edf854d7112cfbf4"]

 connection {
    type     = "ssh"
    user     = "ec2-user"
    private_key  = file("./terraform-key.pem")
    host     = self.public_ip
  }

 provisioner "remote-exec" {
    inline = [
      "sudo yum -y install nginx",
      "sudo systemctl start nginx",
    ]
  }
}

console this command if facing Issue ssh -i terraform-key.pem ec2-user@54.234.184.188

provisioner-remote-exec-types
resource "aws_security_group" "allow_ssh" {
  name        = "allow_ssh"
  description = "Allow SSH inbound traffic"

  ingress {
    description = "SSH into VPC"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    description = "Outbound Allowed"
    from_port   = 0
    to_port     = 65535
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "myec2" {
   ami = "ami-0b1e534a4ff9019e0"
   instance_type = "t2.micro"
   key_name = "ec2-key"
   vpc_security_group_ids  = [aws_security_group.allow_ssh.id]

   provisioner "remote-exec" {
     inline = [
       "sudo yum -y install nano"
     ]
   }
   provisioner "remote-exec" {
       when    = destroy
       inline = [
         "sudo yum -y remove nano"
       ]
     }
   connection {
     type = "ssh"
     user = "ec2-user"
     private_key = file("./ec2-key.pem")
     host = self.public_ip
   }
}

Provisioners-failure-behaviour

This will Fail

resource "aws_iam_user" "lb" {
  name = "demo-provisioner-user"

  provisioner "local-exec" {
    command = "echo1 This is creation time provisioner"
  }
}

on-failure to continue

resource "aws_iam_user" "lb" {
  name = "demo-provisioner-user"

  provisioner "local-exec" {
    command = "echo1 This is creation time provisioner"
    on_failure = continue
  }
}

Provisioners-create-destroy-time-provisioner

resource "aws_iam_user" "lb" {
  name = "provisioner-user"

  provisioner "local-exec" {
    command = "echo This is creation time provisioner"
  }

  provisioner "local-exec" {
    command = "echo This is destroy time provisioner"
    when    =  destroy
  }
}

Simulating failure to see Tainting of Resource

resource "aws_iam_user" "lb" {
  name = "provisioner-user"

  provisioner "local-exec" {
    command = "This is creation time provisioner"
  }

  provisioner "local-exec" {
    command = "echo This is destroy time provisioner"
    when    =  destroy
  }
}

Modules
We can centralize terraform resources and call from tf files whenever required. (module "source")
Root Module  -> module "ec2"
Child Module -> source = "./app"

Modules Locations

1. Github
2. HTTP URLs
3. S3 Buckets
4. Terraform Registry
5. Local paths

module "ec2" {
	source = "../modules/ec2"
}

module "ec2" {
	source = "git::"
	version = "0.2"
}


Modules-local-Output

Modules main.tf resource "aws_instance" "ec2" { ami = var.ami instance_type = local.instance_type key_name = var.key_name monitoring = var.monitoring vpc_security_group_ids = var.vpc_security_group_ids associate_public_ip_address = var.associate_public_ip_address instance_initiated_shutdown_behavior = var.instance_initiated_shutdown_behavior source_dest_check = var.source_dest_check disable_api_termination = var.disable_api_termination } locals { instance_type = "t2.micro" } output "ec2Output" { value = aws_instance.ec2[*] } variables.tf variable "ami" { default = "ami-00fa32593b478ad6e" } variable "key_name" { default = "VKNMatrimony" } variable "monitoring" { default = true } variable "vpc_security_group_ids" { default = ["sg-0577927322bbe2b2a"] } variable "associate_public_ip_address" { default = true } variable "instance_initiated_shutdown_behavior" { default = "stop" } variable "source_dest_check" { default = false } variable "disable_api_termination" { default = true } EC2 myec2.tf module "ec2module" { source = "../modules" associate_public_ip_address = false } resource "aws_eip" "lb" { instance = module.ec2module.ec2Output[0].id domain = "vpc" } output "ModuleOutput" { value = module.ec2module.ec2Output } output "ElasticIPOutput" { value = aws_eip.lb }

Modules-local-Output->ConsoleLog
PS D:\terraform-aws\modules\EC2> terraform apply --auto-approve

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_eip.lb will be created
  + resource "aws_eip" "lb" {
      + allocation_id        = (known after apply)
      + arn                  = (known after apply)
      + association_id       = (known after apply)
      + carrier_ip           = (known after apply)
      + customer_owned_ip    = (known after apply)
      + domain               = "vpc"
      + id                   = (known after apply)
      + instance             = (known after apply)
      + network_border_group = (known after apply)
      + network_interface    = (known after apply)
      + private_dns          = (known after apply)
      + private_ip           = (known after apply)
      + ptr_record           = (known after apply)
      + public_dns           = (known after apply)
      + public_ip            = (known after apply)
      + public_ipv4_pool     = (known after apply)
      + tags_all             = (known after apply)
      + vpc                  = (known after apply)
    }

  # module.ec2module.aws_instance.ec2 will be created
  + resource "aws_instance" "ec2" {
      + ami                                  = "ami-00fa32593b478ad6e"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = false
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = true
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = "stop"
      + instance_lifecycle                   = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t2.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = "VKNMatrimony"
      + monitoring                           = true
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = false
      + spot_instance_request_id             = (known after apply)
      + subnet_id                            = (known after apply)
      + tags_all                             = (known after apply)
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = [
          + "sg-0577927322bbe2b2a",
        ]
    }

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

Changes to Outputs:
  + ElasticIPOutput = {
      + address                   = null
      + allocation_id             = (known after apply)
      + arn                       = (known after apply)
      + associate_with_private_ip = null
      + association_id            = (known after apply)
      + carrier_ip                = (known after apply)
      + customer_owned_ip         = (known after apply)
      + customer_owned_ipv4_pool  = null
      + domain                    = "vpc"
      + id                        = (known after apply)
      + instance                  = (known after apply)
      + network_border_group      = (known after apply)
      + network_interface         = (known after apply)
      + private_dns               = (known after apply)
      + private_ip                = (known after apply)
      + ptr_record                = (known after apply)
      + public_dns                = (known after apply)
      + public_ip                 = (known after apply)
      + public_ipv4_pool          = (known after apply)
      + tags                      = null
      + tags_all                  = (known after apply)
      + timeouts                  = null
      + vpc                       = (known after apply)
    }
  + ModuleOutput    = [
      + {
          + ami                                  = "ami-00fa32593b478ad6e"
          + arn                                  = (known after apply)
          + associate_public_ip_address          = false
          + availability_zone                    = (known after apply)
          + capacity_reservation_specification   = (known after apply)
          + cpu_core_count                       = (known after apply)
          + cpu_options                          = (known after apply)
          + cpu_threads_per_core                 = (known after apply)
          + credit_specification                 = []
          + disable_api_stop                     = (known after apply)
          + disable_api_termination              = true
          + ebs_block_device                     = (known after apply)
          + ebs_optimized                        = (known after apply)
          + enclave_options                      = (known after apply)
          + ephemeral_block_device               = (known after apply)
          + get_password_data                    = false
          + hibernation                          = null
          + host_id                              = (known after apply)
          + host_resource_group_arn              = (known after apply)
          + iam_instance_profile                 = (known after apply)
          + id                                   = (known after apply)
          + instance_initiated_shutdown_behavior = "stop"
          + instance_lifecycle                   = (known after apply)
          + instance_market_options              = (known after apply)
          + instance_state                       = (known after apply)
          + instance_type                        = "t2.micro"
          + ipv6_address_count                   = (known after apply)
          + ipv6_addresses                       = (known after apply)
          + key_name                             = "VKNMatrimony"
          + launch_template                      = []
          + maintenance_options                  = (known after apply)
          + metadata_options                     = (known after apply)
          + monitoring                           = true
          + network_interface                    = (known after apply)
          + outpost_arn                          = (known after apply)
          + password_data                        = (known after apply)
          + placement_group                      = (known after apply)
          + placement_partition_number           = (known after apply)
          + primary_network_interface_id         = (known after apply)
          + private_dns                          = (known after apply)
          + private_dns_name_options             = (known after apply)
          + private_ip                           = (known after apply)
          + public_dns                           = (known after apply)
          + public_ip                            = (known after apply)
          + root_block_device                    = (known after apply)
          + secondary_private_ips                = (known after apply)
          + security_groups                      = (known after apply)
          + source_dest_check                    = false
          + spot_instance_request_id             = (known after apply)
          + subnet_id                            = (known after apply)
          + tags                                 = null
          + tags_all                             = (known after apply)
          + tenancy                              = (known after apply)
          + timeouts                             = null
          + user_data                            = (known after apply)
          + user_data_base64                     = (known after apply)
          + user_data_replace_on_change          = false
          + volume_tags                          = null
          + vpc_security_group_ids               = [
              + "sg-0577927322bbe2b2a",
            ]
        },
    ]
module.ec2module.aws_instance.ec2: Creating...
module.ec2module.aws_instance.ec2: Still creating... [10s elapsed]
module.ec2module.aws_instance.ec2: Still creating... [20s elapsed]
module.ec2module.aws_instance.ec2: Still creating... [30s elapsed]
module.ec2module.aws_instance.ec2: Creation complete after 33s [id=i-0aa1f5c14d0191cda]
aws_eip.lb: Creating...
aws_eip.lb: Creation complete after 1s [id=eipalloc-018f527379f9048b8]

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

Outputs:

ElasticIPOutput = {
  "address" = tostring(null)
  "allocation_id" = "eipalloc-018f527379f9048b8"
  "arn" = "arn:aws:ec2:ap-south-1:927437247370:elastic-ip/eipalloc-018f527379f9048b8"
  "associate_with_private_ip" = tostring(null)
  "association_id" = "eipassoc-042cab9dc8771a592"
  "carrier_ip" = ""
  "customer_owned_ip" = ""
  "customer_owned_ipv4_pool" = ""
  "domain" = "vpc"
  "id" = "eipalloc-018f527379f9048b8"
  "instance" = "i-0aa1f5c14d0191cda"
  "network_border_group" = "ap-south-1"
  "network_interface" = "eni-0a6d24ba8202461d9"
  "private_dns" = "ip-172-31-34-194.ap-south-1.compute.internal"
  "private_ip" = "172.31.34.194"
  "ptr_record" = ""
  "public_dns" = "ec2-13-200-109-120.ap-south-1.compute.amazonaws.com"
  "public_ip" = "13.200.109.120"
  "public_ipv4_pool" = "amazon"
  "tags" = tomap(null) /* of string */
  "tags_all" = tomap({})
  "timeouts" = null /* object */
  "vpc" = true
}
ModuleOutput = [
  {
    "ami" = "ami-00fa32593b478ad6e"
    "arn" = "arn:aws:ec2:ap-south-1:927437247370:instance/i-0aa1f5c14d0191cda"
    "associate_public_ip_address" = true
    "availability_zone" = "ap-south-1a"
    "capacity_reservation_specification" = tolist([
      {
        "capacity_reservation_preference" = "open"
        "capacity_reservation_target" = tolist([])
      },
    ])
    "cpu_core_count" = 1
    "cpu_options" = tolist([
      {
        "amd_sev_snp" = ""
        "core_count" = 1
        "threads_per_core" = 1
      },
    ])
    "cpu_threads_per_core" = 1
    "credit_specification" = tolist([
      {
        "cpu_credits" = "standard"
      },
    ])
    "disable_api_stop" = false
    "disable_api_termination" = true
    "ebs_block_device" = toset([])
    "ebs_optimized" = false
    "enclave_options" = tolist([
      {
        "enabled" = false
      },
    ])
    "ephemeral_block_device" = toset([])
    "get_password_data" = false
    "hibernation" = false
    "host_id" = ""
    "host_resource_group_arn" = tostring(null)
    "iam_instance_profile" = ""
    "id" = "i-0aa1f5c14d0191cda"
    "instance_initiated_shutdown_behavior" = "stop"
    "instance_lifecycle" = ""
    "instance_market_options" = tolist([])
    "instance_state" = "running"
    "instance_type" = "t2.micro"
    "ipv6_address_count" = 0
    "ipv6_addresses" = tolist([])
    "key_name" = "VKNMatrimony"
    "launch_template" = tolist([])
    "maintenance_options" = tolist([
      {
        "auto_recovery" = "default"
      },
    ])
    "metadata_options" = tolist([
      {
        "http_endpoint" = "enabled"
        "http_protocol_ipv6" = "disabled"
        "http_put_response_hop_limit" = 2
        "http_tokens" = "required"
        "instance_metadata_tags" = "disabled"
      },
    ])
    "monitoring" = true
    "network_interface" = toset([])
    "outpost_arn" = ""
    "password_data" = ""
    "placement_group" = ""
    "placement_partition_number" = 0
    "primary_network_interface_id" = "eni-0a6d24ba8202461d9"
    "private_dns" = "ip-172-31-34-194.ap-south-1.compute.internal"
    "private_dns_name_options" = tolist([
      {
        "enable_resource_name_dns_a_record" = false
        "enable_resource_name_dns_aaaa_record" = false
        "hostname_type" = "ip-name"
      },
    ])
    "private_ip" = "172.31.34.194"
    "public_dns" = "ec2-13-233-125-208.ap-south-1.compute.amazonaws.com"
    "public_ip" = "13.233.125.208"
    "root_block_device" = tolist([
      {
        "delete_on_termination" = true
        "device_name" = "/dev/xvda"
        "encrypted" = false
        "iops" = 3000
        "kms_key_id" = ""
        "tags" = tomap({})
        "tags_all" = tomap({})
        "throughput" = 125
        "volume_id" = "vol-0ebd88b75504eafc3"
        "volume_size" = 8
        "volume_type" = "gp3"
      },
    ])
    "secondary_private_ips" = toset([])
    "security_groups" = toset([
      "vijay-terraform-firewall",
    ])
    "source_dest_check" = false
    "spot_instance_request_id" = ""
    "subnet_id" = "subnet-0336a23d310337a48"
    "tags" = tomap(null) /* of string */
    "tags_all" = tomap({})
    "tenancy" = "default"
    "timeouts" = null /* object */
    "user_data" = tostring(null)
    "user_data_base64" = tostring(null)
    "user_data_replace_on_change" = false
    "volume_tags" = tomap(null) /* of string */
    "vpc_security_group_ids" = toset([
      "sg-0577927322bbe2b2a",
    ])
  },
]

Workspace
Workspaces allow multiple state files of a single configuration 

resource "aws_instance" "myec2" {
  ami           = "ami-0fbeaa7bf4665bd6f"
  instance_type = lookup(var.instance_type, terraform.workspace) #lookup(map, key, default)
}

variable "instance_type" {
  type = map(any)

  default = {
    default = "t2.medium"
    dev     = "t2.micro"
    prd     = "t2.large"
  }
}

Workspace-Output


PS D:\terraform-aws\SecurityGroup\workspace> terraform workspace Usage: terraform [global options] workspace new, list, show, select and delete Terraform workspaces. Subcommands: delete Delete a workspace list List Workspaces new Create a new workspace select Select a workspace show Show the name of the current workspace PS D:\terraform-aws\SecurityGroup\workspace> terraform workspace show default PS D:\terraform-aws\SecurityGroup\workspace> terraform workspace list * default PS D:\terraform-aws\SecurityGroup\workspace> terraform workspace new dev Created and switched to workspace "dev"! PS D:\terraform-aws\SecurityGroup\workspace> terraform workspace new prd Created and switched to workspace "prd"! PS D:\terraform-aws\SecurityGroup\workspace> terraform workspace list default dev * prd PS D:\terraform-aws\SecurityGroup\vijay> terraform workspace select dev Switched to workspace "dev". PS D:\terraform-aws\SecurityGroup\vijay> terraform plan # aws_instance.myec2 will be created + resource "aws_instance" "myec2" { + ami = "ami-0fbeaa7bf4665bd6f" + instance_type = "t2.micro" PS D:\terraform-aws\SecurityGroup\vijay> terraform workspace select prd Switched to workspace "prd". PS D:\terraform-aws\SecurityGroup\vijay> terraform plan # aws_instance.myec2 will be created + resource "aws_instance" "myec2" { + ami = "ami-0fbeaa7bf4665bd6f" + instance_type = "t2.large" .tfstate file will be saved in folder terraform.tfstate.d and inside dev folder .tfstate file will be saved

S3 Backend

backend.tf terraform { backend "s3" { bucket = "thakishore-bitbucket-terraform" key = "bitbucket-terraform.tfstate" region = "ap-south-1" } } main.tf resource "aws_eip" "lb" { domain = "vpc" }

State File Locking

sleep.tf resource "time_sleep" "wait_300_seconds" { create_duration = "300s" } If Simuntaneous users uses terraform commands new user will get shown error

StateFileLockDynamo


terraform { backend "s3" { bucket = "thakishore-terraform-backend" dynamodb_table = "terraform-state-lock-dynamo" key = "network/demo.tfstate" region = "ap-south-1" } } resource "time_sleep" "wait_150_seconds" { create_duration = "150s" } terraform apply -replace="time_sleep.wait_150_seconds demo.tfstate file will be removed if lock released

State Management

terraform state list		->	List resources within terraform state file
terraform state pull		->	Manually download and output the state from remote stateList resources within terraform state file
terraform state push		->	Manually upload a local state file to remote state	
terraform state rm time_sleep.wait_120_seconds		->	Remove items from the terraform state
terraform state show time_sleep.wait_120_seconds	->	Show the attributes of a single resource in the state
terraform state mv time_sleep.wait_150_seconds time_sleep.wait_120_seconds	->	Moves item with terraform state

Remote State

Network Team
backend.tf
terraform {
  backend "s3" {
    bucket         = "thakishore-terraform-backend"
    key            = "network/eip.tfstate"
    region         = "ap-south-1"
  }
}

eip.tf
resource "aws_eip" "lb" {
  vpc      = true
}

output "eip_addr" {
  value = aws_eip.lb.public_ip
}

Security Team
remote-state.tf

data "terraform_remote_state" "eip" {
  backend = "s3"

  config = {
    bucket = "thakishore-terraform-backend"
    key    = "network/eip.tfstate"
    region = "us-east-1"
    }
  }

sg.tf
  
  resource "aws_security_group" "allow_tls" {
  name        = "allow_tls"
  description = "Allow TLS inbound traffic"

  ingress {
    description      = "TLS from VPC"
    from_port        = 443
    to_port          = 443
    protocol         = "tcp"
    cidr_blocks      = ["${data.terraform_remote_state.eip.outputs.eip_addr}/32"]
  }

  egress {
    from_port        = 0
    to_port          = 0
    protocol         = "-1"
    cidr_blocks      = ["0.0.0.0/0"]
    ipv6_cidr_blocks = ["::/0"]
  }
}

Import

Import Block

import {
  to = aws_security_group.New-SG
  id = "sg-005168b9ac9dd37b7"
}
resource "aws_security_group" "New-SG" {
  name = "New-SG"
  # Initially, this can be empty or have some basic information
  # Terraform will update the state after import
}

Import Command

terraform import aws_security_group.New-SG sg-005168b9ac9dd37b7

Import Plan Commands

terraform plan -generate-config-out
terraform plan -generate-config-out=generated.tf
terraform plan -generate-config-out=mysg.tf -chdir D:\AWS Triad & Terraform\import

Terraform Import available from Terraform 1.5 version onwards not before

Multiple-Providers

providers.tf

provider "aws" {
  region     =  "us-west-1"
}

provider "aws" {
  alias      =  "aws02"
  region     =  "ap-south-1"
  profile    =  "account02"
}

eip.tf

resource "aws_eip" "myeip" {
  vpc = "true"
}

resource "aws_eip" "myeip01" {
  domain = "vpc"
  provider = "aws.aws02"
}

.aws
From cli aws s3 ls --profile account02

[default] aws_access_key_id = xxxxxxxxxxxxxxxxx aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [account02] aws_access_key_id = xxxxxxxxxxxxxxxxx aws_secret_access_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Sensitive Parameter

locals {
  db_password = {
    admin = "password"
  }
}

output "db_password" {
  value = local.db_password
  sensitive   = true
}

Vault
provider "vault" {
  address = "http://127.0.0.1:8200"
}

data "vault_generic_secret" "demo" {
  path = "secret/db_creds"
}

output "vault_secrets" {
  value = data.vault_generic_secret.demo.data_json
  sensitive = "true"
}

Dependency-lock
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "4.60"
    }
  }
}

# Configure the AWS Provider
provider "aws" {
  region = "us-east-1"
}

resource "aws_instance" "web" {
  ami           = ami-123
  instance_type = "t2.micro"
}

Heredoc Syntax
EOF - End of File
EOT - End of Text
END

user_data = <<-EOF
  
  $newHostname = "New-Hostname"
  Rename-Computer -NewName $newHostname -Force -PassThru
  Restart-Computer -Force
  
EOF

Terraform-Older-Version
terraform older version releases 
https://releases.hashicorp.com/terraform

provider "aws" {
  version    = "~> 2.54"
  region     = "ap-south-1"
  access_key = "xxxxxx"
  secret_key = "xxxxxxxxxxxxxxxxx"
}

provider "digitalocean" {}

terraform {
  required_version = "0.12.31"
}

resource "aws_eip" "eip" {
  vpc = true
}


Example-1
data "aws_iam_users" "users" {}

data "aws_caller_identity" "current" {}

output "accountid" {
  value = data.aws_caller_identity.current.account_id
}

resource "aws_iam_user" "user_name" {
  name = "admin-user-${data.aws_caller_identity.current.account_id}"
  path = "/system/"
}

output "user_names" {
  value = data.aws_iam_users.users.names
}

output "total_users" {
  value = length(data.aws_iam_users.users.names)
}

Outputs
Changes to Outputs:
  + accountid   = "590184002190"
  + total_users = 1
  + user_names  = [
      + "terraform",
    ]

STS Pending