Chào các bạn, trong bài viết này mình xin được chia sẽ từ một case study thực tế mà mình đang gặp phải, đó là quản lý input (variable) của Terraform.
Thông thường để tạo resource nào đó bằng Terraform, thì chúng ta có xu hướng tạo module, sau đó gọi module đó với các tham số đưa vào, nhưng thử tưởng tượng nếu chúng ta tạo một resource gì đó mà nhiều lần (tài khoảng aws, ….) thì phần input vào module sẽ trở nên dài, rất dài và dẫn đến khó đọc khó quản lý.
Tại sao lại là YAML
Yaml là một định đạng file mà con người có thể dễ đọc, yaml thường được sử dụng để khai báo file cấu hình ví dụ như file cấu hình của K8s. Vì là định dạng có cấu trúc nên yaml có thể được tạo ra bằng cách generate.
Case study thực tế
mình có một nhu cầu là tạo repo git một cách tự động dựa vào những request mà user submit. Mình quyết định sử dụng định dạng YAML để chuẩn hóa những input mà user sẽ đưa vào cũng như phù hợp với phong cách GitOps.
Design template
template sẽ trông như sau:
Gitlab:
- project_name: project-three-dev
group_path: 'quannhm/nguyen/hoang/minh'
description: "create by terraform"
member:
- role: Security
username: tinhpham
expires_at: "2022-12-31"
Trong file này sẽ gồm một số trường ví dụ như:
member:
- role: Security
username: foo
expires_at: "2022-12-31"
- role: Security
username: bar
expires_at: "2022-12-31"
- role: Security
username: bub
expires_at: "2022-12-31"
hoặc nếu muốn tạo nhiều mooi môi trường cho một project trong một lần submit thì clone thêm nhiều trường project
Gitlab:
- project_name: project-three-dev
group_path: 'quannhm/nguyen/hoang/minh'
description: "create by terraform"
member:
- role: Security
username: foo
expires_at: "2022-12-31"
- project_name: project-three-uat
group_path: 'quannhm/nguyen/hoang/minh'
description: "create by terraform"
member:
- role: Security
username: bar
expires_at: "2022-12-31"
YAML parsing
File tempalte mình sẻ tổ chức nó thành nhiều file khác nhau tương ứng vơi request của user và được lưu trử trong một thư mục là template đặt trong share repo, để đảm bảo tuân thủ GitOps.
Về phía Terraform cũng hổ trợ một số build-in function để đọc file định dạng YAML.
locals {
get_all_yaml = fileset(path.module, "template/*.yml")
read_yaml = flatten([for yaml in local.get_all_yaml : yamldecode(file(yaml))["Gitlab"]]) # this converts all the queues into a list of maps
# i.e the list looks as follows
# [
# {
# "env" = [
# "dev",
# ]
# "group_id" = "quannhm/nguyen/hoang/minh"
# "manager_email" = "vubuivn@gmail.com"
# "member" = [
# {
# "email" = "tinhphamtrung04@gmail.com"
# "role" = "AppOps"
# "expires_at" = "2022-12-31"
# },
# ]
# "project_name" = "example-queue-dlq"
# },
# ...
}
Data sau khi đợc từ file YAML sẻ trông như phần comment ở trên.
để tiện cho việc ssử dụng để tạo project mình sẻ làm gọn lại phần data
locals {
projects = flatten([for resource_info in local.read_yaml :
{
"project_name" = resource_info.project_name
"group_path" = lookup(resource_info, "group_path", "quannhm")
"description" = lookup(resource_info, "description", "ahihihih")
}
])
# i.e the list looks as follows
# [
# {
# "group_id" = "57106689"
# "project_name" = "project-one"
# },
# {
# "group_id" = "57106689"
# "project_name" = "project-two"
# },
# ...
}
Mình sẻ truyền vào module tạo prọect. Mình cần taọ một data dạng map để truyền vào hàm for_each ở module tạo project. bằng cách:
for obj in local.projects : obj.project_name => obj
module "create_project" {
source = "./modules/project"
for_each = {
for obj in local.projects : obj.project_name => obj
}
project_name = each.value.project_name
group_path = each.value.group_path
description = each.value.description
}
Sử lý phàn data để add member vào project
locals {
project_memberships = flatten([for resource_info in local.read_yaml :
{
for user_name in resource_info.member : user_name.username => [user_name.role, user_name.expires_at]
}
])
# i.e the list looks as follows
# {
# "foo" = [
# "Security",
# "2022-12-31",
# ]
# "bar" = [
# "Mgmt",
# "2022-12-31",
# ]
# },
}
Truyền data vào module add member, trong phần cấu hình mình dùng để thêm user vào project, mình cần tạo một map giữa project name và project_memberships để sử dụng cho hàm for_each. Về phần project id, phần này sẻ có sau khi chạy lệnh terraform apply nên ình sử dụng hàm lookup để trả về id.
locals {
projec_ids = {
for index, obj in module.create_project : index => values(obj)
}
# before
# {
# "project-one" = {
# "project_ids" = "a"
# "project_urls" = "b"
# }
# }
# the output of module "create_project" looks as follows
# Changes to Outputs:
# {
# "project-one" = [
# "a",
# "b",
# ]
# }
project_membership_with_proj = {
for index, obj in local.projects : obj.project_name => local.project_memberships[index]
}
}
module "invitation_users" {
source = "./modules/invitation_users"
for_each = local.project_membership_with_proj
project_membership = each.value
project_id = lookup(local.projec_ids, each.key)[0]
depends_on = [
module.create_project,
local.projec_ids,
local.project_membership_with_proj
]
}
file modules/invitation_users/main.tf dùng để thêm member vào project có nội dung như sau.
locals {
gitlab_access_level = {
Owner = "maintainer"
AppOps = "developer"
Security = "reporter"
Mgmt = "maintainer"
CloudOps = "maintainer"
}
}
data "gitlab_user" "user_id" {
for_each = var.project_membership
username = each.key
}
resource "gitlab_project_membership" "main" {
for_each = var.project_membership
project_id = var.project_id
user_id = data.gitlab_user.user_id[each.key].user_id
access_level = lookup(local.gitlab_access_level, each.value[0]) // guest, reporter, developer, maintainer, owner, master
expires_at = each.value[1]
}