local-exec Provisioner
รัน command บน เครื่องที่รัน Terraform (ไม่ใช่ remote resource)
Syntax
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
command = "echo Created: ${self.id} > log.txt"
}
}
Arguments
| Argument | คำอธิบาย |
|---|---|
command | Command ที่จะรัน (string) |
working_dir | Directory ที่รัน command |
interpreter | Shell ที่รัน (default: /bin/sh -c หรือ cmd /C) |
environment | Env vars ที่ส่งให้ command |
when | create (default) หรือ destroy |
on_failure | fail (default) หรือ continue |
quiet | ซ่อน output (default false) |
ตัวอย่าง: Basic
resource "aws_instance" "web" {
ami = "ami-12345"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo 'Instance ${self.id} created at $(date)' >> creation.log"
}
}
ตัวอย่าง: ใช้ self.* Reference
provisioner "local-exec" {
command = "echo 'IP: ${self.public_ip}, ID: ${self.id}' >> instances.log"
}
self = reference ตัว resource เอง (ใช้ได้ใน provisioner เท่านั้น)
ตัวอย่าง: Working Directory
provisioner "local-exec" {
command = "./deploy.sh"
working_dir = "${path.module}/scripts"
}
ตัวอย่าง: Custom Interpreter
# Bash (default จะ /bin/sh)
provisioner "local-exec" {
command = "echo 'Hello'"
interpreter = ["bash", "-c"]
}
# Python
provisioner "local-exec" {
command = "import os; print(os.environ['INSTANCE_ID'])"
interpreter = ["python3", "-c"]
environment = {
INSTANCE_ID = self.id
}
}
# PowerShell (Windows)
provisioner "local-exec" {
command = "Write-Host 'Created'"
interpreter = ["pwsh", "-Command"]
}
ตัวอย่าง: Environment Variables
provisioner "local-exec" {
command = "echo $MY_VAR"
environment = {
MY_VAR = "Hello from Terraform"
INSTANCE_ID = self.id
AWS_REGION = "ap-southeast-1"
}
}
ตัวอย่าง: Destroy Time
resource "aws_instance" "web" {
# ...
# Run when created
provisioner "local-exec" {
command = "echo 'Created' >> log.txt"
}
# Run before destroyed
provisioner "local-exec" {
when = destroy
command = "echo 'Destroying ${self.id}' >> log.txt"
}
}
ตัวอย่าง: null_resource Pattern
ใช้ provisioner โดยไม่ต้องมี real resource:
resource "null_resource" "kubeconfig" {
triggers = {
cluster_name = aws_eks_cluster.main.name
}
provisioner "local-exec" {
command = "aws eks update-kubeconfig --name ${aws_eks_cluster.main.name} --region ${data.aws_region.current.name}"
}
}
triggers = ทำ provisioner re-run เมื่อค่าเปลี่ยน
Common Use Cases
Use Case 1: Run Ansible Playbook
resource "aws_instance" "web" {
# ...
}
resource "null_resource" "ansible" {
triggers = {
instance_id = aws_instance.web.id
}
provisioner "local-exec" {
command = <<-EOF
ansible-playbook \
-i '${aws_instance.web.public_ip},' \
--user ec2-user \
--private-key ~/.ssh/id_rsa \
playbook.yml
EOF
}
}
Use Case 2: Update kubeconfig
resource "null_resource" "kubeconfig" {
triggers = {
endpoint = aws_eks_cluster.main.endpoint
}
provisioner "local-exec" {
command = "aws eks update-kubeconfig --name ${aws_eks_cluster.main.name}"
}
depends_on = [aws_eks_cluster.main]
}
Use Case 3: Notify Team
resource "null_resource" "notify" {
triggers = {
deployed_at = timestamp()
}
provisioner "local-exec" {
command = <<-EOF
curl -X POST $SLACK_WEBHOOK \
-H 'Content-type: application/json' \
--data '{"text":"Deployed prod at $(date)"}'
EOF
}
}
Use Case 4: Generate Local Files
resource "null_resource" "config" {
triggers = {
api_url = aws_alb.main.dns_name
}
provisioner "local-exec" {
command = <<-EOF
cat > .env <<ENV
API_URL=https://${aws_alb.main.dns_name}
DB_HOST=${aws_db_instance.main.address}
ENV
EOF
}
}
(แต่ใช้ local_file resource ดีกว่า)
Use Case 5: Run Database Migration
resource "null_resource" "migrate" {
triggers = {
db_endpoint = aws_db_instance.main.endpoint
}
provisioner "local-exec" {
command = "psql -h ${aws_db_instance.main.address} -U admin -d mydb -f migrations.sql"
environment = {
PGPASSWORD = random_password.db.result
}
}
depends_on = [aws_db_instance.main]
}
ทางเลือกที่ดีกว่า
Use Case: Update kubeconfig
ดีกว่า: ใช้ Helm/kubectl provider ตรงๆ:
provider "kubernetes" {
host = aws_eks_cluster.main.endpoint
cluster_ca_certificate = base64decode(aws_eks_cluster.main.certificate_authority[0].data)
token = data.aws_eks_cluster_auth.main.token
}
resource "kubernetes_namespace" "app" {
metadata { name = "myapp" }
}
Use Case: Write to local file
ดีกว่า: local_file resource:
resource "local_file" "kubeconfig" {
content = templatefile("kubeconfig.tpl", { ... })
filename = "${path.module}/kubeconfig"
}
Use Case: HTTP request
ดีกว่า: http data source:
data "http" "myip" {
url = "https://api.ipify.org"
}
Quirks & Gotchas
1. Command runs in $TERRAFORM_DIR
provisioner "local-exec" {
command = "ls"
}
# → list files ของ Terraform directory
ถ้าจะรันใน folder อื่น → ใช้ working_dir
2. Quotes Hell
HCL string + shell escaping = ปวดหัว
# ❌ จะ break
command = "echo "hello""
# ✅ ใช้ heredoc
command = <<-EOT
echo "hello"
EOT
3. Cross-Platform Issues
provisioner "local-exec" {
command = "rm -rf /tmp/cache"
# ❌ Windows ไม่มี rm
}
แก้: ใช้ Terraform built-in หรือ check OS:
provisioner "local-exec" {
command = "Remove-Item -Recurse -Force C:\\temp\\cache"
interpreter = ["pwsh", "-Command"]
}
4. Doesn't Re-run Automatically
ถ้า command ตัวเองเปลี่ยน — Terraform ไม่รู้ → ต้องใช้ triggers
resource "null_resource" "x" {
triggers = {
script_hash = filemd5("script.sh") # ← detect change
}
provisioner "local-exec" {
command = "./script.sh"
}
}
Output Capture
local-exec output แสดงใน apply log:
$ terraform apply
null_resource.kubeconfig: Provisioning with 'local-exec'...
null_resource.kubeconfig (local-exec): Executing: ["/bin/sh" "-c" "..."]
null_resource.kubeconfig (local-exec): Updated context arn:aws:eks:...
ซ่อน output:
provisioner "local-exec" {
command = "echo secret"
quiet = true
}
ตัวอย่าง: รวม
resource "aws_eks_cluster" "main" {
# ...
}
resource "null_resource" "kubeconfig" {
triggers = {
cluster_name = aws_eks_cluster.main.name
endpoint = aws_eks_cluster.main.endpoint
}
provisioner "local-exec" {
command = "aws eks update-kubeconfig --name ${aws_eks_cluster.main.name} --region ap-southeast-1"
}
provisioner "local-exec" {
when = destroy
command = "kubectl config delete-context ${self.triggers.cluster_name}"
}
}
resource "null_resource" "install_addons" {
triggers = {
cluster = null_resource.kubeconfig.id
}
provisioner "local-exec" {
command = <<-EOF
kubectl apply -f https://raw.githubusercontent.com/aws/eks-charts/main/stable/aws-load-balancer-controller/crds/crds.yaml
helm install aws-load-balancer-controller eks/aws-load-balancer-controller \
--namespace kube-system \
--set clusterName=${aws_eks_cluster.main.name}
EOF
}
}
สรุป
local-exec= รัน command บนเครื่องที่ apply- ใช้
self.<attr>reference resource ตัวเอง - ใช้
null_resource+triggersเป็น pattern หลัก - ทางเลือก: Kubernetes/Helm provider,
local_file,httpdata source - ระวัง quotes + cross-platform + triggers ที่ถูก
ต่อไป → remote-exec provisioner