Terraform with AWS

Part 1

Topics

  • Overview of Git

  • What is IAC ?

  • Terraform Overview

    • What is Terraform ?
    • Terraform Editions
    • Core terraform workflow
    • Phases of Terraform adoption.
  • Terraform Alternatives

    • Ansible
    • AWS Cloudformarion
    • Azurerm template
    • Google deployment manager
  • OverView of AWS Cloud

    • Walk through many services from AWS UI.
      • Get Started - AWS
      • Regions and Availablity Zone
      • AWS IAM
      • AWS Ec2 instance
      • VPC
      • RDS etc many more
  • Lab Set up

    • Installation of Terraform on different os windows/Linux
    • Installation of AWS Cli on Linux/Wndows
    • Installation of Vscode to develop the code
    • Installation of Gitbash to push and pull code to Git repo(Gitlab/Github).
  • Overview of HCL (Hashicorp Configiguration Language)

  • Terraform Block

  • Terraform Providers

    • AzureRM
    • Local
    • random
    • null
  • Terraform Resources and DataSources

  • Hashicop Configuration Language

    • Blocks
    • Arguments
    • Comments
    • Identifiers
  • Resource Blocks

  • Meta-Arguments

    • depends_on
    • count
    • for_each
    • provider
    • lifecycle
  • Terraform Variables

    • Input Variables
      • Prompt Vriables
      • Optional Variables
      • Variables Validation
      • Data Type of Variables:
        • String
        • Numbers
        • Booleans
        • Object
      • Type of Variables
        • Scaler
        • List
        • Map
        • Tuple
        • set
      • Change variables values using different ways
        • At runtime
        • with tfvars file
      • Defining variable using Terraform environment variables
    • Output values
    • local Values
  • Terraform State Management

    • Local file
    • Lock in state file
    • State management on cloud storage
    • Pull and push state file
    • update state file
  • Terraform Workspace

    • Managing multiple environment with workspace
  • Terraform Provisioners

    • Local
    • file
    • Remote-exec
  • Terraform Module

    • Local Module
    • remote module
    • Publish module on Terraform Registry

Prerequisites Tasks

  • Create AWS free Account
  • Walk through AWS Console
  • Understand IAM Account (Identity and Access Management)
  • Create users and role
  • Download and Install aws cli
  • Configure Aws cli
  • Configure Aws resources using aws Cli
  • Using Profile with Aws cli
  • Creating AWS ec2 instance from Aws console
  • Creating ssh key and try to access using key

Prerequisites Tools

IAC

  • Infrastructure as Code (IaC) tools allow you to manage infrastructure with configuration files rather than through a graphical user interface.

  • IaC allows you to build, change, and manage your infrastructure in a safe, consistent, and repeatable way by defining resource configurations that you can version, reuse, and share.

  • Terraform is HashiCorp’s infrastructure as code tool.

  • It lets you define resources and infrastructure in human-readable, declarative configuration files, and manages your infrastructure’s lifecycle.

  • Using Terraform has several advantages over manually managing your infrastructure:

    • Terraform can manage infrastructure on multiple cloud platforms.
    • The human-readable configuration language helps you write infrastructure code quickly.
    • Terraform’s state allows you to track resource changes throughout your deployments.
    • You can commit your configurations to version control to safely collaborate on infrastructure.

Terraform Alternatives

  • Ansible
  • AWS Cloudformation
  • Azure Arm Template
  • Google deployment manager

Terraform Provider

Terraform plugins called providers let Terraform interact with cloud platforms and other services via their application programming interfaces (APIs).

HashiCorp and the Terraform community have written over 1,000 providers to manage resources on Amazon Web Services (AWS), Azure, Google Cloud Platform (GCP), Kubernetes, Helm, GitHub, Splunk, and DataDog, just to name a few.

Find providers for many of the platforms and services you already use in the Terraform Registry.

  • Use the Amazon Web Services (AWS) provider to interact with the many resources supported by AWS.
  • You must configure the provider with the proper credentials before you can use it.
  • There are currently 1332 resources and 544 data sources available in the provider.
  • Example Provider configuration
# The default provider configuration; resources that begin with `aws_` will use
# it as the default, and it can be referenced as `aws`.
provider "aws" {
  region = "us-east-1"
}

# Additional provider configuration for west coast region; resources can
# reference this as `aws.west`.
provider "aws" {
  alias  = "west"
  region = "us-west-2"
}

Provider Requirements(Terraform Block)

-Each Terraform module must declare which providers it requires, so that Terraform can install and use them. Provider requirements are declared in a required_providers block.

  • A provider requirement consists of a local name, a source location, and a version constraint:
terraform {
  required_providers {
    mycloud = {
      source  = "mycorp/mycloud"
      version = "~> 1.0"
    }
  }
}
  • source - the global source address for the provider you intend to use, such as hashicorp/aws.

  • version - a version constraint specifying which subset of available provider versions the module is compatible with.

Terraform Block

  • Terraform Block
  • Provider Block
  • Data Block
  • Resource Block
  • Module Block
  • Variable Block
  • Output Block
  • Locals Block

Terraform Block

  • It is used to define global configuration and behavior for terraform execution.
  • Setting the required Terraform version.
  • Configuring the backend for storing the state file.
  • Defining experimental or optional features.
  • Specifying variables used across multiple modules or configurations.

Provide Block

The provider block is used to configure and define the provider for a specific cloud or infrastructure program,It specifies details such as the provider name and version, authentication credentials, and other settings. By correctly configuring the provider block, you ensure that Terraform knows which provider to use and how to authenticate with it.

Data Block

This block is used to fetch data from external sources or existing resources. we can use a data block to fetch information about existing resources, such as a list of available AWS AMIs or the currently existing state of a Kubernetes cluster. By utilizing data blocks, you can incorporate external data into your infrastructure configuration and make informed decisions based on that information.

  • Retrieving information about existing infrastructure to be used in the Terraform configuration.
  • Querying and filtering data for use in resource configuration.

Resource Block

It is used to declare and define the provider for a specific cloud or infrastructure program. Resources represent components such as virtual machines, networks, storage, databases, and other services. Each resource block specifies the resource type, name, and configuration parameters specific to that resource. By using resource blocks effectively, we can create and manage the desired infrastructure resources in a consistent and repeatable manner.

  • Configuring the properties and attributes of the resources.
  • Specifying dependencies and relationships between resources.

Authentication and Configuration

Configuration for the AWS Provider can be derived from several sources, which are applied in the following order:

  • Parameters in the provider configuration

  • Environment variables

  • Shared credentials files

  • Shared configuration files

  • Container credentials

  • Instance profile credentials and Region

  • Configure Authentication with Roles on Ec2 Instance

  • Configure Authentication with static Credentials

  • Create a file with name provider.tf (not mandatory)

provider "aws" {
  region     = "us-west-2"
  access_key = "my-access-key"
  secret_key = "my-secret-key"
}
  • Configure Authentication with Environment variable
 export AWS_ACCESS_KEY_ID="anaccesskey"
 export AWS_SECRET_ACCESS_KEY="asecretkey"
 export AWS_DEFAULT_REGION="us-west-2"
  • Configure Authentication with Shared Credentials File
provider "aws" {
  region                  = "us-west-2"
  shared_credentials_file = "/Users/tf_user/.aws/creds"
  profile                 = "customprofile"
}
  • Create terraform block if you need to define version constraints in the same file where you have defined aws credentials
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "3.63.0"
    }
  }
}

Terraform Language

  • The main purpose of the Terraform language is declaring resources, which represent infrastructure objects.
  • A Terraform configuration is a complete document in the Terraform language that tells Terraform how to manage a given collection of infrastructure.
  • A configuration can consist of multiple files and directories.
  • The syntax of the Terraform language consists of only a few basic elements:
resource "aws_vpc" "main" {
  cidr_block = var.base_cidr_block
}

<BLOCK TYPE> "<BLOCK LABEL>" "<BLOCK LABEL>" {
  # Block body
  Argument = vakue # Argument
}
  • Blocks are containers for other content and usually represent the configuration of some kind of object, like a resource.
  • Arguments assign a value to a name. They appear within blocks.

Random Provider

  • Define random provider
terraform {
  required_providers {
    random = {
      source = "hashicorp/random"
      version = "2.3.0"
    }
  }
}

Getting Started with Local Provider

Local Provider

  • The Local provider is used to manage local resources, such as files.
  • It has resources and datasources
  • Create a provider.tf file for local provider
terraform {
 required_providers {
   local = {
     source = "hashicorp/local"
     version = "2.4.1"
   }
 }
}

provider "local" {
 # Configuration options
}
  • Download local provider plugins
terraform init 
  • Verify the provider
terraform providers
  • Create first resource file
resource "local_file" "foo" {
  content  = "foo!"
  filename = "${path.module}/foo.bar"
}
  • Run the above configuration
terraform plan
terraform apply

Above will create a file with the name foo at the same location where we have all our tf files

  • Also check if the file is created ,state file is also created
  • Check after modifying the files and file name and see what happens.

Getting started with AWS Provider

  • Create Terraform Provider file and know about all the parameters
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "5.39.0"
    }
  }
}

provider "aws" {
  # Configuration options
}
  • Configure Authentication we have already discussed erlier

    • Using role
    • Using Credentials in provider file
    • Using Environment Variables
  • Create a tf file for ec2 instance

resource "aws_instance" "web" {
  ami           = <ami-id"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  • Update ssh key
ssh-keygen -f <aws>
resource "aws_key_pair" "key" {
    key_name =  "own"
     
    public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCx66nZKbSRhFqAdO4E283zUvAPmR1zT7gmh0E6jgR5ZIj/cgYcfLW56Nz2lUvifBlxW5ZM+EiuNMjSCIsfSukLLtzqyPkigZHM8EoPEzlKzyBGZq8F8QCe"
}
  • OR
resource "aws_key_pair" "key" {
    key_name =  "own1"
     
    public_key = file("${path.module}/aws.pub")
}
  • Another Example
resource "aws_key_pair" "key" {
    key_name_prefix =  "own1"
     
    public_key = file("${path.module}/aws.pub")
}
  • Update instance type

  • Update security group

  • update ebs volume for ec2 instance

resource "aws_ebs_volume" "example" {
  availability_zone = "us-west-2a"
  size              = 40

  tags = {
    Name = "HelloWorld"
  }
}
  • Another example
resource "aws_instance" "web1" {
  ami           = "ami-02e136e904f3da870"
  instance_type = "t2.micro"
  availability_zone = "us-east-1a"

  tags = {
    Name = "HelloWorld"
  }
}

resource "aws_ebs_volume" "awsvolume" {
  availability_zone = "us-east-1a"
  size = 30
  encrypted = "false"

  tags = {
    name = "new_volume_mounttoec2"
  }
}
  • update public ip for ec2 instance
  • Use userdata with ec2 instance
resource "aws_instance" "web1" {
  ami           = "ami-02e136e904f3da870"
  instance_type = "t2.micro"
  availability_zone = "us-east-1a"

  tags = {
    Name = "HelloWorld"
  }
}

resource "aws_ebs_volume" "awsvolume" {
  availability_zone = "us-east-1a"
  size = 30
  encrypted = "false"

  tags = {
    name = "new_volume_mounttoec2"
  }
}

resource "aws_volume_attachment" "namenews" {
    device_name = "/dev/sdd"
    instance_id = aws_instance.web1.id
    volume_id = aws_ebs_volume.awsvolume.id
  
}


resource "aws_security_group" "morning-ssh-http" {
  name        = "morning-ssh-http"
  description = "allow ssh and http traffic"

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


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

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




resource "aws_instance" "good-morning" {
  ami               = "ami-02e136e904f3da870"
  instance_type     = "t2.micro"
  availability_zone = "us-east-1a"
  security_groups   = ["${aws_security_group.morning-ssh-http.name}"]
  
  user_data = <<-EOF
                #! /bin/bash
                sudo yum install httpd -y
                sudo systemctl start httpd
                sudo systemctl enable httpd
                echo "<h1>Sample Webserver Network Nuts" | sudo tee  /var/www/html/index.html
  EOF


  tags = {
        Name = "webserver"
  }

}
  • Create ec2 instance in two different regions.
resource "aws_instance" "web1" {
  ami           = "ami-02e136e904f3da870"
  instance_type = "t2.micro"
  availability_zone = "us-east-1a"
  provider = alias.name

  tags = {
    Name = "HelloWorld"
  }
}
  • Use two different credentials with with same provider.
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "3.63.0"
    }
     google = {
      source = "hashicorp/google"
      version = "3.88.0"
    }
  }
}
provider "google" {
  
}
provider "aws" {
  region = "us-east-1"
  access_key = "AKIA4OE5FOTPLXF26KWA"
  secret_key = "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"
}
provider "aws" {
  region = "us-west-1"
  access_key = "AKIA4OE5FOTPF5MR4742"
  secret_key = "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"
  alias = "custom"
}
  • update the configuration with random provider
  • update the configuration with null resources

Data Sources Examples

  • Example1 fetch the ami info
data "aws_ami" "app_ami" {
  most_recent = true
  owners = ["self"]
  filter {
    name   = "name"
    values = ["HelloWorld"]
  }
}

resource "aws_instance" "app" {
  ami           = "${data.aws_ami.app_ami.id}"
  instance_type = "t2.micro"
}
  • Example1 fetch Instance info
data "aws_instance" "myawsinstance" {
    filter {
      name = "tag:Name"
      values = ["Terraform EC2"]
    }

    depends_on = [
      "aws_instance.ec2_example"
    ]
} 
  • Example1 fetch the ami
data "aws_ami" "myAmi" {
 owners = ["099720109477"]
 most_recent = true
 
 filter {
   name = "description"
   values = ["Canonical, Ubuntu, 20.04 LTS*"]
 }
 
 filter {
   name = "architecture"
   values = ["x86_64"]
 }
  • Subnet
data "aws_subnet" "get-subnet-id" {
  filter {
    name   = "tag:Name"
    values = ["public-1"]
  }

}
  • Print the subnet fetched from data source
output "aws_subnet_id" {
  value = data.aws_subnet.get-subnet-id.id
}



variable "vpc_id" {}

data "aws_vpc" "selected" {
  id = var.vpc_id
}

resource "aws_subnet" "example" {
  vpc_id            = data.aws_vpc.selected.id
  availability_zone = "us-west-2a"
  cidr_block        = cidrsubnet(data.aws_vpc.selected.cidr_block, 4, 1)
}
  • Data Source ec2 instance
provider "aws" {
    region     = "eu-central-1"
    access_key = "AKIATQ37NXB2JMXVGYPG"
    secret_key = "ockvEN1DzYynDuKIh56BVQv/tMqmzvKnYB8FttSp"
}

resource "aws_instance" "ec2_example" {

    ami           = "ami-0767046d1677be5a0"
    instance_type =  "t2.micro"

    tags = {
      Name = "Terraform EC2"
    }
}

data "aws_instance" "myawsinstance" {
    filter {
        name = "tag:Name"
        values = ["Terraform EC2"]
    }

    depends_on = [
      "aws_instance.ec2_example"
    ]
}

output "fetched_info_from_aws" {
  value = data.aws_instance.myawsinstance.public_ip
}

Terraform Meta Arguments

Depends_on
  • Depends_on Example 1
resource "aws_s3_bucket" "example" {
  acl    = "private"
}

resource "aws_instance" "example_c" {
  ami           = data.aws_ami.amazon_linux.id
  instance_type = "t2.micro"

  depends_on = [aws_s3_bucket.example]
}
LifeCycle
  • Example 1
resource "aws_instance" "example" {
  # ...

  lifecycle {
    ignore_changes = [
      # Ignore changes to tags, e.g. because a management agent
      # updates these based on some ruleset managed elsewhere.
      tags,
    ]
  }
}
  • Example 2
resource "aws_instance" "example" {
  # ...

  lifecycle {
    create_before_destroy = true
  }
}
  • Example 3
resource "azurerm_resource_group" "example" {
 # ...

 lifecycle {
   prevent_destroy = true
 }
}
AWS instance with Security group and EIP association
provider "aws" {
  region     = "us-west-2"
  access_key = "PUT-YOUR-ACCESS-KEY-HERE"
  secret_key = "PUT-YOUR-SECRET-KEY-HERE"
}



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

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

resource "aws_eip_association" "eip_assoc" {
  instance_id   = aws_instance.myec2.id
  allocation_id = aws_eip.lb.id
}


resource "aws_security_group" "allow_tls" {
  name        = "test-security-group"

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["${aws_eip.lb.public_ip}/32"]

#    cidr_blocks = [aws_eip.lb.public_ip/32]
  }
}

Topics to be covered

  • Meta Arguments
  • Variables
  • Locals
  • Dynamic Blocks
  • Functions

Variables

  • Parameterized variables

  • Parameters with default values

  • Terraform values from tfvars files

  • Terraform variables from TF vars

  • Override variable value on runtime

  • Check variable value using output

  • Define Locals

  • Check local values using output

  • Define a Prompt Variable

variable "variable_name" {}
  • Value at apply
terraform apply -var variable_name="value"
  • Access Variables
 var.<varname>
  • String(Data Type) Variables
variable "var1" {
  type = string
  default = "01000000-0000-4000-8000-000030080200"
}
  • List variable
variable "users" {
  type    = list
  default = ["root", "user1", "user2"]
}
  • Map variables
variable "plans" {
  type = map
  default = {
    "5USD"  = "1xCPU-1GB"
    "10USD" = "1xCPU-2GB"
    "20USD" = "2xCPU-4GB"
  }
}
  • Access varible from map
plan = var.plans["5USD"]
  • Map Example
variable "storage_sizes" {
  type = map
  default = {
    "1xCPU-1GB"  = "25"
    "1xCPU-2GB"  = "50"
    "2xCPU-4GB"  = "80"
  }
}
  • Use lookup (real example region and ami)
size = lookup(var.storage_sizes, var.plans["5USD"])
  • Boolean
variable "set_password" {
 default = false
}
  • Access Boolean Variables
create_password = var.set_password
  • Pass the value at apply end
terraform apply -var set_password="true"

Loading variables automatically

  • Files named exactly terraform.tfvars or terraform.tfvars.json.

  • Any files with names ending in .auto.tfvars or .auto.tfvars.json.

  • Define variable into a files

vi terraform.tfvars
set_password = "true"
users = ["root", "admin"]
plan = "20USD"
templates = {"ubuntu20":"01000000-0000-4000-8000-000030080200", "centos8":"01000000-0000-4000-8000-000050010300"}
template = "ubuntu20"
  • Json way
{
 "set_password": "true",
 "users": ["root", "admin"],
 "plan": "20USD"
 "templates": {"ubuntu20":"01000000-0000-4000-8000-000030200200", "centos8":"01000000-0000-4000-8000-000050010400"},
 "template": "ubuntu20"
}
  • Using tfvar file other than terrafom.tfvars
terraform apply -var-file=dev.tfvars
How to use Terraform resource meta arguments?
  • You can create multiple aws_resource using the count

  • for_each can be used for iteration and can also help you to create multiple aws_resource using the same block

  • provider is used for overriding terraform default behavior using the alias

  • With lifecycle you can prevent destroy, create resource after destroy and ignore changes to be saved inside tstate

  • count

  • for_each

  • provider

  • lifecycle

  • Count Example

resource "aws_instance" "ec2_example" {

   count         = 2 
   ami           = "ami-0767046d1677be5a0"
   instance_type =  "t2.micro"

   tags = {
           Name = "Terraform EC2"
   }
}
  • For each example
resource "aws_instance" "ec2_example" {

    for_each = {
      instance1 = "t2.micro"
      instance2 = "t2.micro" 
    }

   ami           = "ami-0767046d1677be5a0"
   instance_type =  each.value

   tags = {
           Name = "Terraform ${each.key}"
   }
} 
  • lifecycle
resource "aws_instance" "ec2_example" {

   count         = 2 
   ami           = "ami-0767046d1677be5a0"
   instance_type =  "t2.micro"

   tags = {
           Name = "Terraform EC2"
   }
   
       lifecycle {
      create_before_destroy = true
      #prevent_destroy       = true
      #ignore_changes        = [tags]
   }
}

Table of Content

  • Loops with count

  • Loops with for_each

  • for loop

  • List Variable

variable "user_names" {
  description = "IAM usernames"
  type        = list(string)
  default     = ["user1", "user2", "user3"]
}
  • Count
resource "aws_iam_user" "example" {
  count = length(var.user_names)
  name  = var.user_names[count.index]
}
  • Loops with for_each

  • Note : - It can only be used on set(string) or map(string).

  • The reason why for_each does not work on list(string) is because a list can contain duplicate values but if you are using set(string) or map(string) then it does not support duplicate values.

  • Define variable

variable "user_names" {
  description = "IAM usernames"
  type        = set(string)
  default     = ["user1", "user2", "user3s"]
} 
  • Use for each now
resource "aws_iam_user" "example" {
  for_each = var.user_names
  name  = each.value
}
  • for loop
variable "user_names" {
  description = "IAM usernames"
  type        = list(string)
  default     = ["user1", "user2", "user3"]
}
  • Test
output "print_the_names" {
  value = [for name in var.user_names : name]
}
  • For loop with map
variable "iam_users" {
  description = "map"
  type        = map(string)
  default     = {
    user1      = "normal user"
    user2  = "admin user"
    user3 = "root user"
  }
}
  • Perform a test
output "user_with_roles" {
  value = [for name, role in var.iam_users : "${name} is the ${role}"]
}
How to use Terraform locals?
  • Define Local
locals {
  staging_env = "staging"
}
  • Use local variable
resource "aws_instance" "ec2_example" {
   
   ami           = "ami-0767046d1677be5a0"
   instance_type = "t2.micro"
   subnet_id = aws_subnet.staging-subnet.id
   
   tags = {
           Name = "${local.staging_env} - Terraform EC2"
   }
}
  • Local can also use variable
locals {
    server_details = "${var.location}-${var.server_name}"
}

Conditions

  • Use below condition based on env
variable env {}

tfvars

env = "prod"


main.tf

count = var.env == "prod" ? 5 : 2
  • How to download the plugin for provider
terraform init
  • How to create a infra
terraform apply 
  • How to delete all resources created by Terraform
terraform destroy
  • How to destroy one specific resource which is created with terraform
terraform destroy -target=resource_type.resource_name
  • check state file
terraform show
  • Terraform state related commands
Subcommands:
    list                List resources in the state
    mv                  Move an item in the state
    pull                Pull current state and output to stdout
    push                Update remote state from a local state file
    replace-provider    Replace provider in the state
    rm                  Remove instances from the state
    show                Show a resource in the state
  • list Resources in state file
terraform state list
  • Filtering by Resource
terraform state list aws_instance.bar
  • Remove from state file
terraform state rm aws_instance.web
  • check specific resource in state file
terraform state show 'aws_vpc.my_vpc'

Terraform Output:

The terraform output command is used to extract the value of an output variable from the state file. ** Examples**

output "instance_ips" {
  value = aws_instance.web.*.public_ip
}

output "lb_address" {
  value = aws_alb.web.public_dns
}

output "password" {
  sensitive = true
  value = var.secret_password
}
  • to list output
 terraform output
instance_ips = [
  "54.43.114.12",
  "52.122.13.4",
  "52.4.116.53"
]
lb_address = "my-app-alb-1657023003.us-east-1.elb.amazonaws.com"
password = <sensitive>
  • Rename a resource in state file
terraform state mv packet_device.worker packet_device.helper
  • Pull state file
terraform state pull
  • Remove some resource from state file
terraform state rm 'module.foo.packet_device.worker'
  • How to define remote state file
terraform {
  backend "s3" {
    bucket = "terraform121"
    region = "us-west-2"
  }
}

Terraform Workspace

Terraform maintains separate file for each environment

  • Check terraform workspace
 terraform workspace list
  • Create a new workspace
terraform workspace new dev
  • see current workspace
terraform workspace show
  • Reconfigure state file
terraform init "-reconfigure"
  • Switch to each workspace and check state resource

    terraform workspace select default
    terraform state list
    terraform workspace select dev
    terraform state list

Terraform Provisioner

Local Exec

  • Does not require any connection

  • Example for local-exec

resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    command = "echo The server's IP address is ${self.private_ip}"
  }
}
  • Example 2
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    command = "echo ${aws_instance.web.public_ip} >>/tmp/ip.txt"
  }
}
  • Creating a file when destroy
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    when = destroy
    command = "echo ${self.public_ip} destroy!  >/tmp/ip.txt"
  }
}
  • On failure fail
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    on_failure = fail
    command = "echo ${self.public_ip} destroy!  >/tmp/ip.txt"
  }
}
  • On failure continue
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    on_failure = continue
    command = "echo ${self.public_ip} destroy!  >/tmp/ip.txt"
  }
}

Terraform Provisioner

Remote Exec

  • Requires Conection block
  • Example 1
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"
  key_name  = "mykey"
  vpc_security_group_ids = [aws_security_group.main.id]
  tags = {
    Name = "Terraform"
  }
 provisioner "remote-exec" {
  inline = ["echo $(hostname -i) >> /tmp/new.txt"]
  }
 connection {
  type = "ssh"
  host = self.public_ip
  user  = "ec2-user"
  private_key = file("/home/ec2-user/mykey.pem")
  }
}
resource "aws_security_group" "main" {
  egress = [
    {
      cidr_blocks      = [ "0.0.0.0/0", ]
      description      = ""
      from_port        = 0
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      protocol         = "-1"
      security_groups  = []
      self             = false
      to_port          = 0
    }
  ]
  ingress = [
   {
     cidr_blocks      = [ "0.0.0.0/0", ]
     description      = ""
     from_port        = 22
     ipv6_cidr_blocks = []
     prefix_list_ids  = []
     protocol         = "tcp"
     security_groups  = []
     self             = false
     to_port          = 22
  }
  ]
}
  • Terraform Logs
export TF_LOG_PATH=/tmp/terraform.log
  • Test the file created
 ssh -i /home/ec2-user/mykey.pem ec2-user@34.220.203.9

##### Dynamic Blocks

  • Without Dynamic Blocks
resource "aws_security_group" "main" {
   name   = "resource_without_dynamic_block"
   vpc_id = data.aws_vpc.main.id

   ingress {
      description = "ingress_rule_1"
      from_port   = 443
      to_port     = 443
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
   }
   
   ingress {
      description = "ingress_rule_2"
      from_port   = 80
      to_port     = 80
      protocol    = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
   }

   tags = {
      Name = "AWS security group non-dynamic block"
   }
}
  • With Local values
locals {
   ingress_rules = [{
      port        = 443
      description = "Ingress rules for port 443"
   },
   {
      port        = 80
      description = "Ingree rules for port 80"
   }]
}

resource "aws_security_group" "main" {
   name   = "resource_with_dynamic_block"
   vpc_id = data.aws_vpc.main.id

   dynamic "ingress" {
      for_each = local.ingress_rules

      content {
         description = ingress.value.description
         from_port   = ingress.value.port
         to_port     = ingress.value.port
         protocol    = "tcp"
         cidr_blocks = ["0.0.0.0/0"]
      }
   }

   tags = {
      Name = "AWS security group dynamic block"
   }
}

Testing with null resoures

  • Example 1 count
resource "null_resource" "simple" {
  count = 2
}
output "simple" {
  value = null_resource.simple
}
  • Example List
locals {
  names = ["bob", "kevin", "stewart"]
}
resource "null_resource" "names" {
  count = length(local.names)
  triggers = {
    name = local.names[count.index]
  }
}
output "names" {
  value = null_resource.names
}
  • For Each: Simple List Example
locals {
 minions = ["bob", "kevin", "stewart"]
}
resource "null_resource" "minions" {
 for_each = toset(local.minions)
 triggers = {
   name = each.value
 }
}
output "minions" {
 value = null_resource.minions
}
  • For each map
locals {
  heights = {
    bob     = "short"
    kevin   = "tall"
    stewart = "medium"
  }
}
resource "null_resource" "heights" {
  for_each = local.heights
  triggers = {
    name   = each.key
    height = each.value
  }
}
output "heights" {
  value = null_resource.heights
}
  • Another Example
locals {
  heights = {
    bob     = "short"
    stewart = "medium"
  }
}
resource "null_resource" "heights" {
  for_each = local.heights
  triggers = {
    name   = each.key
    height = each.value
  }
}
output "heights" {
  value = null_resource.heights
}
Conditions
  • [ ] Example 1
output "a1" {
  value = true ? "is true" : "is false"
}
output "a2" {
  value = false ? "is true" : "is false"
}
output "a3" {
  value = 1 == 2 ? "is true" : "is false"
}

Transformation

  • Example 1
locals {
  list = ["a","b","c"]
}
output "list" {
  value = [for s in local.list : upper(s)]
}
  • Map List
locals {
  list = {a = 1, b = 2, c = 3}
}
output "result1" {
  value = [for k,v in local.list : "${k}-${v}" ]
}
output "result2" {
  value = [for k in local.list : k ]
}

Terraform Provisioner Terraform State file Terraform Lock Terraform Cloud Terraform Gitlab CICD EKS with eksctl Connect with EKS and deploy application EKS with Terraform

Local Exec
  • Does not reqire any connection

  • Example for local-exec

resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    command = "echo The server's IP address is ${self.private_ip}"
  }
}
  • Example 2
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    command = "echo ${aws_instance.web.public_ip} >>/tmp/ip.txt"
  }
}
  • Creating a file when destroy
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    when = destroy
    command = "echo ${self.public_ip} destroy!  >/tmp/ip.txt"
  }
}
  • On failure fail
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    on_failure = fail
    command = "echo ${self.public_ip} destroy!  >/tmp/ip.txt"
  }
}
  • On failure continue
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"

  tags = {
    Name = "HelloWorld"
  }
  provisioner "local-exec" {
    on_failure = continue
    command = "echo ${self.public_ip} destroy!  >/tmp/ip.txt"
  }
}

Terraform Provisioner

Remote Exec

  • Requires Conection block
  • Example 1
resource "aws_instance" "web" {
  ami           = "ami-0c2d06d50ce30b442"
  instance_type = "t2.micro"
  key_name  = "mykey"
  vpc_security_group_ids = [aws_security_group.main.id]
  tags = {
    Name = "Terraform"
  }
 provisioner "remote-exec" {
  inline = ["echo $(hostname -i) >> /tmp/new.txt"]
  }
 connection {
  type = "ssh"
  host = self.public_ip
  user  = "ec2-user"
  private_key = file("/home/ec2-user/mykey.pem")
  }
}
resource "aws_security_group" "main" {
  egress = [
    {
      cidr_blocks      = [ "0.0.0.0/0", ]
      description      = ""
      from_port        = 0
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      protocol         = "-1"
      security_groups  = []
      self             = false
      to_port          = 0
    }
  ]
  ingress = [
   {
     cidr_blocks      = [ "0.0.0.0/0", ]
     description      = ""
     from_port        = 22
     ipv6_cidr_blocks = []
     prefix_list_ids  = []
     protocol         = "tcp"
     security_groups  = []
     self             = false
     to_port          = 22
  }
  ]
}
  • Terraform Logs
export TF_LOG_PATH=/tmp/terraform.log
  • Test the file created
 ssh -i /home/ec2-user/mykey.pem ec2-user@34.220.203.9
  • File Provisoner Example
resource "aws_instance" "web" {
  ami           = "ami-02e136e904f3da870"
  instance_type = "t2.micro"
  key_name  = "news"
  vpc_security_group_ids = [aws_security_group.main.id]
  tags = {
    Name = "Terraform"
  }
 provisioner "file" {
   source = "n/tree.txt"
   destination = "/tmp/rajveer/"
 }
 connection {
  type = "ssh"
  host = self.public_ip
  user  = "ec2-user"
  private_key = file("${path.module}/news.pem")
  }
}
resource "aws_security_group" "main" {
  egress = [
    {
      cidr_blocks      = [ "0.0.0.0/0", ]
      description      = ""
      from_port        = 0
      ipv6_cidr_blocks = []
      prefix_list_ids  = []
      protocol         = "-1"
      security_groups  = []
      self             = false
      to_port          = 0
    }
  ]
  ingress = [
   {
     cidr_blocks      = [ "0.0.0.0/0", ]
     description      = ""
     from_port        = 22
     ipv6_cidr_blocks = []
     prefix_list_ids  = []
     protocol         = "tcp"
     security_groups  = []
     self             = false
     to_port          = 22
  }
  ]
}

output "web" {
  value = aws_instance.web.public_ip
  
}