Note_Tech

All technological notes.


Project maintained by simonangel-fong Hosted on GitHub Pages — Theme by mattgraham

Terraform - Module

Back


Module

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "6.0.1"
}

Module Provider


Passing input

# defile a varialbe to get the input in a module
# /mymodule/variable.tf
variable "myValue" {
  default = "123"
}

# define input in the root
# /main.tf
module "mymodule" {
  source = "./mymodule"
  myValue = "123" # passing input
}

Getting output

# define output in a module
# /mymodule/output.tf
output "instance_public_ip" {
  value = aws_instance_myinstance_public_ip
}


# refer the output of module
# /main.tf
# define a module
module "mymodule" {
  source = "./mymodule"
  myValue = "123" # passing input
}

# refer the output of a module
module "other_module" {
  public_ip = module.mymodule.instance_public_ip  # refer to the output of a module
}


Module Registry


Common Modules

Module Refer
AWS VPC https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest
AWS Instance https://registry.terraform.io/modules/terraform-aws-modules/ec2-instance/aws/latest
AWS ECS https://registry.terraform.io/modules/terraform-aws-modules/ecs/aws/latest
AWS EKS https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest

Root Module


Published Modules


Using Modules


Child Modules


Calling a Child Module

module "servers" {
    source = "./app-cluster"

    servers = 5
}

Argument



Count

# count with condition
module "my_module_condition" {
  count = var.enable_my_module? 1:0
}

# count with list
module "my_module_list" {
  count = ["instance1","instance2","instance3"]
}

# Actual resources:
module.my_module_list[0].resource
module.my_module_list[1].resource
module.my_module_list[2].resource

for_each in module

locals {
  mymap = {
    instance1 = "instance1 data "
    instance2 = "instance2 data "
    instance3 = "instance3 data "
  }
}

module "my_module" {
  for_each = local.mymap
  instance_name = each.key
}
# actual resource
module.my_module["instance1"].resource
module.my_module["instance2"].resource
module.my_module["instance3"].resource
variable "conditions" {
  type = list(object({
    field = string
    values = list(string)
  }))
  default = [
    {
      field = "host-header"
      values = ["mydomain.com", "www.mydomain.com"]
    }
  ]
}

resource "aws_lb_listener_rule" "alb_rule"{
  # ...

  # delare condition block
  dynamic condition{
    for_each = var.conditions
    content{
      # declare nested host_header block
      dynamic host_header {
        for_each = condition.value.field == "host-header" ? [1] : []
        content{
          values = condition.value.values
        }
      }
    }
  }
}

# actual resource
resource "aws_lb_listener_rule" "alb_rule"{
  condition{
    host_header {
      values = ["mydomain.com", "www.mydomain.com"]
    }
  }
}

Lab: Create SSM parameters with for_each

Create Module

# Variable
variable "name" {}
variable "value" {}

# ##############################
# SSM parameter
# ##############################
resource "aws_ssm_parameter" "param" {
  name  = var.name
  type  = "String"
  value = var.value
}

# Module output
output "param_arn" {
  value = aws_ssm_parameter.param.arn
}

provider "aws" {
  region = "ca-central-1"
}

# Define local variables
locals {
  my_param = {
    env     = "dev"
    version = "1.0"
    mykey   = "myval"
  }
}

# define module
module "param_module" {
  source   = "./ssm-parameter"

  # loop map varialbe
  for_each = local.my_param
  name     = each.key   # pass key
  value    = each.value # pass value
}

# module output
output "all-parameters"{
  # customized output as a map
  value = {for k, p in module.param_module: k => p.param_arn}
}


Apply

terraform init
terraform apply
terraform console
# confirm a specific parameter
module.param_module["mykey"].param_arn
# param_arn" = "arn:aws:ssm:******************:parameter/mykey

# loop all parameters
[ for k, p in module.param_module: p.param_arn]
# [
#   "arn:aws:ssm::******************::parameter/env",
#   "arn:aws:ssm::******************::parameter/mykey",
#   "arn:aws:ssm::******************::parameter/version",
# ]

# output as map
{for k, p in module.param_module: k => p.param_arn}
{
  "env" = "arn:aws:ssm::******************::parameter/env"
  "mykey" = "arn:aws:ssm::******************::parameter/mykey"
  "version" = "arn:aws:ssm::******************::parameter/version"
}

pic


Lab: Complex data type and flatten() function

Complex Data Type + flatten()

locals {
  my_param = [
    {
      "prefix" = "/myprefix"
      "parameters" = [
        {
          "name"  = "myparameter"
          "value" = "myvalue"
        },
        {
          "name"  = "env"
          "value" = "dev"
        }
      ]
    },
    {
      "prefix" = "/myapp"
      "parameters" = [
        {
          "name"  = "env"
          "value" = "prod"
        }
      ]
    }
  ]
}
terraform concolse
# get all prefix
[ for p in local.my_param: p.prefix ]
# [
#   "/myprefix",
#   "/myapp",
# ]

# custom paramters: concatenate prefix with name
[ for p in local.my_param: [ for kv in p.parameters: {"name" = "${p.prefix}/${kv.name}", "value" = kv.value} ] ]
# [
#   [
#     {
#       "name" = "/myprefix/myparameter"
#       "value" = "myvalue"
#     },
#     {
#       "name" = "/myprefix/env"
#       "value" = "dev"
#     },
#   ],
#   [
#     {
#       "name" = "/myapp/env"
#       "value" = "prod"
#     },
#   ],
# ]

# flatten function: to convert the above into a list
flatten([ for p in local.my_param: [ for kv in p.parameters: {"name" = "${p.prefix}/${kv.name}", "value" = kv.value} ] ])
# [
#   {
#     "name" = "/myprefix/myparameter"
#     "value" = "myvalue"
#   },
#   {
#     "name" = "/myprefix/env"
#     "value" = "dev"
#   },
#   {
#     "name" = "/myapp/env"
#     "value" = "prod"
#   },
# ]

Update module

# ##############################
# Variable
# ##############################
variable "parameters" {
  type = list(object(
    {
      prefix = string
      parameters = list(object(
        {
          name  = string
          value = string
        })
      )
    }
    )
  )

  default = []
}

# ##############################
# Extract data from variable using flatten function
# ##############################
locals {
  parameters = flatten([
    for p in var.parameters : [
      for kv in p.parameters :
      {
        "name"  = "${p.prefix}/${kv.name}"
        "value" = kv.value
      }
    ]
    ]
  )
}

# ##############################
# SSM parameter
# ##############################
resource "aws_ssm_parameter" "param" {
  for_each = { for kv in local.parameters : kv.name => kv.value }
  name     = each.key
  type     = "String"
  value    = each.value
}
# Define complex data type to pass to module
locals {
  my_param = [
    {
      "prefix" = "/myprefix"
      "parameters" = [
        {
          "name"  = "myparameter"
          "value" = "myvalue"
        },
        {
          "name"  = "env"
          "value" = "dev"
        }
      ]
    },
    {
      "prefix" = "/myapp"
      "parameters" = [
        {
          "name"  = "env"
          "value" = "prod"
        }
      ]
    }
  ]
}

# module
module "param_module" {
  source     = "./ssm-parameter"
  parameters = local.my_param
}
terraform init
terraform apply

terraform state list
# module.param_module.aws_ssm_parameter.param["/myapp/env"]
# module.param_module.aws_ssm_parameter.param["/myprefix/env"]
# module.param_module.aws_ssm_parameter.param["/myprefix/myparameter"]

Refactor

Rename a resource

resource "aws_ssm_parameter" "myparam" {
  type     = "String"
  name     = "env"
  value    = "test"
}
terraform state list
# aws_ssm_parameter.myparam
resource "aws_ssm_parameter" "new_myparam" {
  type     = "String"
  name     = "env"
  value    = "test"
}

moved {
  from  = aws_ssm_parameter.myparam
  to    = aws_ssm_parameter.new_myparam
}
terraform refresh
# aws_ssm_parameter.new_myparam: Refreshing state... [id=env]
terraform state list
# aws_ssm_parameter.new_myparam

Add count

resource "aws_instance" "web" {
  count         = 2
  ami           = ""
  instance_type = ""

  tags = {
    Name = "web instance"
  }
}
terraform apply
terraform state list
# aws_instance.web
resource "aws_instance" "web" {
  count         = 2
  ami           = ""
  instance_type = ""

  tags = {
    Name = "web instance"
  }
}

moved {
  from = aws_ssm_parameter.web
  to   = aws_ssm_parameter.web[0]
}
terraform apply
terraform state list
# aws_instance.web[0]
# aws_instance.web[1]

Add for_each

resource "aws_instance" "web" {
  count         = 2
  ami           = ""
  instance_type = ""

  tags = {
    Name = "web instance"
  }
}
terraform apply
terraform state list
# aws_instance.web

variable "arch_ami" {
  default = {
    amazon_linux = { ami = "ami-06131bddb5c4ff9ac" }
    ubuntu       = { ami = "ami-0c0a551d0459e9d39" }
  }
}

resource "aws_instance" "web" {
  for_each = var.arch_ami
  ami      = each.value.ami
  instance_type = "t2.micro"

  tags = {
    Name = "web instance ${each.key}"
  }
}

moved {
  from = aws_instance.web
  to   = aws_instance.web["ubuntu"]
}
terraform apply
terraform state list
# aws_instance.web["amazon_linux"]
# aws_instance.web["ubuntu"]

Rename Module

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "6.0.1"
}
terraform init
terraform apply
terraform state list
# module.vpc.aws_vpc.this[0]

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

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

moved {
    from = module.vpc
    to = module.vpc-new
}
terraform refresh
# module.vpc-new.aws_vpc.this[0]: Refreshing state...
terraform state list
# module.vpc-new.aws_vpc.this[0]

Add count with module

module "ec2-instance" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "6.0.2"

  instance_type = "t2.micro"
  subnet_id     = ""
  ami           = ""
  name          = "web"
}
terraform init
terraform apply
terraform state list
# module.ec2-instance.aws_instance.this[0]

module "ec2-instance" {
  count = 2
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "6.0.2"

  instance_type = "t2.micro"
  subnet_id     = ""
  ami           = ""
  name          = "web"
}

moved {
  from = module.ec2-instance
  to   = module.ec2-instance[1]
}
terraform state list
# module.ec2-instance[0].aws_instance.this[0]
# module.ec2-instance[1].aws_instance.this[0]

Refactor from resource to module

resource "aws_instance" "web" {
  ami           = "ami-06131bddb5c4ff9ac"
  instance_type = "t2.micro"
  subnet_id     = ""

  tags = {
    Name = "web instance"
  }
}

module "instance" {
  source        = "terraform-aws-modules/ec2-instance/aws"
  name          = "web-instance"
  ami           = "ami-06131bddb5c4ff9ac"
  instance_type = "t2.micro"
  subnet_id     = "subnet-072bd92fbc35ffaca"
}

moved {
  from = aws_instance.web
  to   = module.instance.aws_instance.web-instance
}
terraform refresh
terraform state list
# module.instance.aws_instance.web-instance