Functional Programming

Example

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

data "aws_ami" "ubuntu" {
  most_recent = true

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

  owners = ["099720109477"]
}

resource "aws_instance" "hello" {
  ami           = data.aws_ami.ubuntu.id
  instance_type = "t2.micro"
}

Với file config trên ta phải dùng hardcode, khá bất tiện do đó ta sẽ dùng variable

Input Variables

Ta có thể định nghĩa trong main.tf hoặc tạo riêng 1 file variable.tf khác:

variable "instance_type" {
  type = string
  description = "Instance type of the EC2"
}

  • Type: bắt buộc phải khai báo
  • Basic type: string, number, bool
  • Complex type: list(), set(), map(), object(), tuple()
  • Trong terraform, type numbertype bool sẽ được convert thành type string khi cần thiết. Nghĩa là 1 sẽ thành "1", true sẽ thành "true"
  • Thay vì gán cứng => instance_type = var.instance_type

Đây chỉ là mới khởi tạo variable thoi, ta cần gán giá trị cho nó nữa

Gán giá trị cho variable

  • Để gán giá trị cho biến, ta sẽ tạo một file tên là terraform.tfvars:
instance_type = "t2.micro"
  • Khi ta chạy terraform apply thì file terraform.tfvars sẽ được terraform sử dụng mặc định để load giá trị cho biến
  • Nếu không muốn set mặc định như thế, thì ta tạo 1 file khác ví dụ production.tfvars với nội dung tương tự:

terraform apply -var-file="production.tfvars"

Validating variables

Ta có thể chỉ định những giá trị được phép của variable bằng cách:

variable "instance_type" {
  type = string
  description = "Instance type of the EC2"

  validation {
    condition = contains(["t2.micro", "t3.small"], var.instance_type)
    error_message = "Value not allow."
  }
}

Output

Giá trị của output block sẽ được in ra terminal, cú pháp của output như sau.

  • Để in được giá trị public IP của EC2, ta thêm vào file main.tf đoạn code sau:
#...
output "ec2" {
  value = {
    public_ip = aws_instance.hello.public_ip
  }
}

Count parameter

  • Là một thuộc tính trong terraform chứ không phải của Resource type block thuộc provider
  • Resource type block chỉ có chứa các thuộc tính mà provider cung cấp
  • Meta argument là thuộc tính của terraform => nghĩa là ta có thể sử dụng nó ở bất kì resource block nào.
  • Cập nhật lại file main.tf mà sẽ tạo ra 5 EC2 như sau:
provider "aws" {
  region = "us-west-2"
}

data "aws_ami" "ubuntu" {
  most_recent = true

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

  owners = ["099720109477"]
}

resource "aws_instance" "hello" {
  count         = 5
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
}

output "ec2" {
  value = {
    public_ip1 = aws_instance.hello[0].public_ip
    public_ip2 = aws_instance.hello[1].public_ip
    public_ip3 = aws_instance.hello[2].public_ip
    public_ip4 = aws_instance.hello[3].public_ip
    public_ip5 = aws_instance.hello[4].public_ip
  }
}

For expressions

for <value> in <list> : <return value>

  • Ví dụ dùng for:

    • Tạo ra một array mới với giá trị của array mới sẽ được upper: [for s in var.words : upper(s)]
    • Tạo ra một object mới với value của object được upper: { for k, v in var.words : k => upper(s) }
  • Ta sẽ dùng for để rút gọn phần output IP của EC2. Cập nhật lại file main.tf:

#...
resource "aws_instance" "hello" {
  count         = 5
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
}

output "ec2" {
  value = {
    public_ip = [ for v in aws_instance.hello : v.public_ip ]
  }
}

Phần output trên sẽ in ra cho ta giá trị public_ip là một mảng IP của tất cả EC2 được tạo ra. Còn nếu muốn in output ra theo kiểu { public_ip1: <value>, public_ip2: <value> } thì ta có thể dùng format function.

Format function

#...
resource "aws_instance" "hello" {
  count         = 5
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
}

output "ec2" {
  value = { for i, v in aws_instance.hello : format("Public_IP %d: ", i + 1) => v.public_ip }
}

Demo

Tạo 3 EC2 instances, với các thứ đã học ở trên.

main.tf

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.38"
    }
  }
}

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

data "aws_ami" "ubuntu" {
  most_recent = true

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

  owners = ["099720109477"]
}

resource "aws_instance" "hello" {
  count         = 3
  ami           = data.aws_ami.ubuntu.id
  instance_type = var.instance_type
  tags = {
    Name = "Hjn4-practice"
  }
}

output "ec2" {
  value = { for i, v in aws_instance.hello : format("Public_IP%d", i + 1) => v.public_ip }
}

variable.tf

variable "instance_type" {
  type        = string
  description = "Instance type of the EC2"

  validation {
    condition = contains(["t2.micro"], var.instance_type)
    error_message = "Just allow t2.micro"
  }

}

terraform.tfvars

instance_type = "t2.micro"

Result

#...
aws_instance.hello[0]: Creation complete after 32s [id=i-0f6fede3edd0a9b23]
aws_instance.hello[2]: Creation complete after 32s [id=i-04be0977e38218fbe]
aws_instance.hello[1]: Creation complete after 32s [id=i-05cf123cd516073b0]

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

Outputs:

ec2 = {
  "Public_IP1" = "18.143.78.17"
  "Public_IP2" = "54.169.140.195"
  "Public_IP3" = "18.139.110.75"
}

alt text

Static Web S3 Bucket

Sẽ quay lại đọc hiểu code của phần push file lên S3

# provider "aws" {
#   region     = var.region
#   access_key = var.access_key
#   secret_key = var.secret_key
# }

# Creating Random String for bucket name
# resource "random_string" "random" {
#   length  = 6
#   special = false
#   upper   = false
# }

# provider "aws" {
#   region = "ap-southeast-1"
# }

# Creating S3 Bucket 
resource "aws_s3_bucket" "bucket" {
  bucket = "terraform-learn-bucket-hjn4" #${random_string.random.result}"
  # force_destroy = true
}

resource "aws_s3_bucket_website_configuration" "static_web" {
  bucket = aws_s3_bucket.bucket.id
  index_document {
    suffix = "index.html"
  }
  error_document {
    key = "error/index.html"
  }
}

resource "aws_s3_bucket_policy" "bucket_policy" {
  bucket = aws_s3_bucket.bucket.id
  policy = file("s3_static_web_policy.json")
}

# Public Access
resource "aws_s3_bucket_public_access_block" "public_access_block" {
  bucket                  = aws_s3_bucket.bucket.id
  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

locals {
  mime_types = {
    html  = "text/html"
    css   = "text/css"
    scss  = "text/x-scss"
    md    = "text/markdown"
    MD    = "text/markdown"
    ttf   = "font/ttf"
    woff  = "font/woff"
    woff2 = "font/woff2"
    otf   = "font/otf"
    js    = "application/javascript"
    map   = "application/javascript"
    json  = "application/json"
    jpg   = "image/jpeg"
    png   = "image/png"
    svg   = "image/svg+xml"
    eot   = "application/vnd.ms-fontobject"
  }
}

# will upload all the files present under HTML folder to the S3 bucket
resource "aws_s3_object" "upload_object" {
  for_each     = fileset(path.module, "static-web/**/*")
  bucket       = aws_s3_bucket.bucket.id
  key          = replace(each.value, "static-web", "")
  source       = each.value
  etag         = filemd5("${each.value}")
  content_type = lookup(local.mime_types, split(".", each.value)[length(split(".", each.value)) - 1])
}

  • s3_static_web_policy.json:
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": [
                "arn:aws:s3:::terraform-learn-bucket-hjn4/*"
            ]
        }
    ]
}

  • outputs.tf:
output "s3_bucket_id" {
  value = aws_s3_bucket_website_configuration.static_web.website_endpoint
}

  • Lần đầu plan ta không bị báo lỗi gì cả
  • Tuy nhiên sau đó ta apply thì error:
│ Error: putting S3 Bucket (terraform-learn-bucket-hjn4) Policy: operation error S3: PutBucketPolicy, https response error StatusCode: 403, RequestID: XS5Z24E8SPP069YE, HostID: r/WDyVv2jIMYw7V0ueTKf2HguRPsD1hBpBgvb9yc6DKdM5QIMD5/HrRssxVuaGk+FxINZiZ+JfY=, api error AccessDenied: Access Denied
│ 
│   with aws_s3_bucket_policy.bucket_policy,
│   on main.tf line 34, in resource "aws_s3_bucket_policy" "bucket_policy":
│   34: resource "aws_s3_bucket_policy" "bucket_policy" {
  • Lúc này bucket đã được tạo tuy nhiên vẫn chưa có policy, access denied, do đó ta sẽ apply lại 1 lần nữa để put policy:
aws_s3_bucket_policy.bucket_policy: Creating...
aws_s3_bucket_policy.bucket_policy: Creation complete after 1s [id=terraform-learn-bucket-hjn4]

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

Outputs:

s3_bucket_id = "terraform-learn-bucket-hjn4.s3-website-ap-southeast-1.amazonaws.com"

file("s3_static_policy.json"): được dùng để load cả content file for_each = fileset(path.module, "static-web/**/*"): tải toàn bộ subdirs, files trong thư mục

  • Tạm thời chưa cần hiểu code phần aws_s3_bucket_object
  • Phần quan trọng ở đây là fileset function, thay vì chỉ tải một file, thì fileset sẽ tải toàn bộ file trong thư mục đó lên theo dạng set.

Local values

  • Không giống như variable block, ta cần phải khai báo type, thì locals block ta sẽ gán thẳng giá trị cho nó. Ví dụ như sau:
locals {
  one = 1
  two = 2
  name = "max"
  flag = true
}

  • Để truy cập giá trị local thì ta dùng cú pháp local.<KEY>