feat(phase4): ArgoCD-driven Terraform apply via PostSync Hook Job

- Create Phase 4 ArgoCD Application (terraform-apply)
- PostSync Hook Job runs: terraform init && terraform apply -auto-approve
- ServiceAccount + ClusterRole for cluster-admin RBAC
- S3 credentials encrypted with SOPS (terraform-s3-secrets.enc.yaml)
- Pre-commit hook blocks local 'terraform apply' — all changes via git push
- True IaC: modify terraform/*.tf → git push → ArgoCD applies automatically
This commit is contained in:
Story Crater Bot
2026-07-15 16:39:27 -07:00
parent 842360288d
commit e71c7ad37e
5 changed files with 152 additions and 3 deletions
+26
View File
@@ -0,0 +1,26 @@
# Phase 4 — IaC (Terraform) — infrastructure-as-code via Hook Job
# ArgoCD-driven terraform apply. All changes via git push.
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: terraform-apply
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "4"
spec:
project: homelab
sources:
- repoURL: https://forgejo.riotpiao.homelab.com/riotpiao.com/homelab.git
targetRevision: main
path: k8s/hooks/phase4
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
@@ -0,0 +1,68 @@
apiVersion: batch/v1
kind: Job
metadata:
name: terraform-apply
namespace: argocd
annotations:
argocd.argoproj.io/hook: PostSync
argocd.argoproj.io/hook-delete-policy: HookSucceeded
spec:
backoffLimit: 3
template:
spec:
serviceAccountName: terraform-apply
containers:
- name: terraform
image: hashicorp/terraform:latest
command: ["/bin/sh", "-c"]
args:
- |
set -e
echo "=== Terraform Apply Hook (ArgoCD PostSync) ==="
# Clone repo
git clone --depth 1 https://forgejo.riotpiao.homelab.com/riotpiao.com/homelab.git /repo
cd /repo/terraform
# Set S3 credentials
export AWS_ACCESS_KEY_ID=$(cat /run/secrets/s3/AWS_ACCESS_KEY_ID)
export AWS_SECRET_ACCESS_KEY=$(cat /run/secrets/s3/AWS_SECRET_ACCESS_KEY)
export AWS_ENDPOINT_URL=$(cat /run/secrets/s3/AWS_ENDPOINT_URL)
# Initialize and apply
echo "Initializing Terraform..."
terraform init
echo "Planning Terraform changes..."
terraform plan -out=tfplan
echo "Applying Terraform..."
terraform apply -auto-approve tfplan
echo "✓ Terraform apply complete"
volumeMounts:
- name: s3-creds
mountPath: /run/secrets/s3
readOnly: true
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "1"
volumes:
- name: s3-creds
projected:
sources:
- secret:
name: terraform-s3-creds
items:
- key: AWS_ACCESS_KEY_ID
path: AWS_ACCESS_KEY_ID
- key: AWS_SECRET_ACCESS_KEY
path: AWS_SECRET_ACCESS_KEY
- key: AWS_ENDPOINT_URL
path: AWS_ENDPOINT_URL
restartPolicy: Never
+30
View File
@@ -0,0 +1,30 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: terraform-apply
namespace: argocd
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: terraform-apply
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: terraform-apply
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: terraform-apply
subjects:
- kind: ServiceAccount
name: terraform-apply
namespace: argocd
@@ -0,0 +1,25 @@
apiVersion: ENC[AES256_GCM,data:kiY=,iv:wvX0y4RDJQbk7fGdXWbSQr60WfM3M5rvc7SCPb1q25g=,tag:C71TErBwHF5FKHzeLXaMzA==,type:str]
kind: ENC[AES256_GCM,data:WILs0jRC,iv:vkkqDYv45946c21yEWBZf0USa0b3IZuhQAXhdSjDKvU=,tag:Sn6ViACi321IUkd8YHfpsQ==,type:str]
metadata:
name: ENC[AES256_GCM,data:WQw0C3Y0MhkHyhJyY7oD9Ib9,iv:+OaUam5cBOwL8eXuxi5clYvV8HvjQgJ7N7mcjmIcpOg=,tag:II2lOgY0edI+bvd3fSreiQ==,type:str]
namespace: ENC[AES256_GCM,data:hSaYZZ+E,iv:BpL3rZSdoM1zvsXNZWISUQBEm6Blv7REGeeVQnHBDP4=,tag:tlmhyRvE3coSFehyKFTsGw==,type:str]
type: ENC[AES256_GCM,data:FQ2B9iNr,iv:aZ4Ah0TzsMciw0NiMBc3vPsMHAczwzeTuhTos+GjuCg=,tag:7V3t9VkcHlhHK9+4Oo6i2A==,type:str]
stringData:
AWS_ACCESS_KEY_ID: ENC[AES256_GCM,data:i4b932+5rglSbg==,iv:6z2HLtTYXUfwkuPzzNZakJ+8UYkpHNs1otrz/lScuLM=,tag:4FxelmrF5p+EF79hGB+ejQ==,type:str]
AWS_SECRET_ACCESS_KEY: ENC[AES256_GCM,data:ATzJ0GwtWVgMc+pnLxoW0zLB37h94HxSoUTsad9Efpe9z0zh3HBdquCr4ek=,iv:dPxiRhnxqEEbtZZW4aSRnByv1rQCcbc0R/ot9bCBiHg=,tag:IpXFDVjc6kJKP7NqDXpAAQ==,type:str]
AWS_ENDPOINT_URL: ENC[AES256_GCM,data:9V0dljvWeFiG4bWAoUs0LSkztGgDIjaSyEaO+C0A4Cb1G1jj+xY=,iv:lnUyU+Vz4kD/VdhA7aKXXGXLcmnXuxkYJa/HT7jICEk=,tag:oafx5JSV20OHdFq5PaJVhQ==,type:str]
sops:
age:
- enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMaFNEREVhZmFJNkVxMDdz
S2hBSG9EQ2grVU1NNHNFcDRDN1ljUldodzA0CkhLOE5YSGNqWVRNbDNNR25yclho
bHJ3c0J0WVVSRHAycWFzSGt2Y2EyS2cKLS0tIFloSkF2YnRUZ25VRDF5NnFlR1Uz
eDFvS2d5S2lQNkJQZDhGY0YvU3FTTVEK0DhLUMtCwsz0hH5WCp1xTZ6sm06hL0cw
KVTiIt/vX6jbWTW3tr6hXdRkZkyXxfHqcY04pWKYIZG82l3DyTipuQ==
-----END AGE ENCRYPTED FILE-----
recipient: age1smu533f803gmd0jq60s2zaj9zlznajy0ca6rtewd4r37mr2hs3uqsrldfh
lastmodified: "2026-07-15T23:39:08Z"
mac: ENC[AES256_GCM,data:DMasKoeAWIR4OHE2cHuYBVZgiEesh1NFraT9D1oqqWnnTyrLaBXSlC0f++g9fMjqba5+50mfgMvK5uimAcHhMVQQadhNrOYY821WcHbaRTPVJty12Ho/ahIaZVzxviQPeeWwh0biXIZdicYYY+jBnh/yXRlPgh0kv4BnuBIq39Y=,iv:/PjQPhfr29B9+rJxjYJfbItKjsmI++lIDVcWnwUTJtg=,tag:olnTGWGQaAE3IJNEy2zUrw==,type:str]
unencrypted_suffix: _unencrypted
version: 3.13.2
+3 -3
View File
@@ -96,7 +96,7 @@ resource "helm_release" "argocd" {
{
name = "GIT_SSL_CAINFO"
value = "/etc/ssl/certs/homelab-ca.crt"
}
},
{
name = "SOPS_AGE_KEY_FILE"
value = "/home/argocd/.sops/keys.txt"
@@ -108,7 +108,7 @@ resource "helm_release" "argocd" {
secret = {
secretName = "homelab-ca-secret"
}
}
},
{
name = "sops-age"
secret = {
@@ -122,7 +122,7 @@ resource "helm_release" "argocd" {
name = "homelab-ca"
mountPath = "/etc/ssl/certs/homelab-ca.crt"
subPath = "tls.crt"
}
},
{
name = "sops-age"
mountPath = "/home/argocd/.sops"