refactor: retire Terraform, migrate to pure ArgoCD GitOps + CI validation
This commit is contained in:
@@ -0,0 +1,460 @@
|
||||
# CI/CD Pipeline: GitOps Validation & Deployment
|
||||
|
||||
## Overview
|
||||
|
||||
Pure GitOps CI/CD pipeline using Forgejo Actions (self-hosted runner).
|
||||
|
||||
**Principle:** Validate in CI, deploy via ArgoCD (no manual steps).
|
||||
|
||||
```
|
||||
git push
|
||||
↓
|
||||
[CI: Validate]
|
||||
├─ yamllint (YAML syntax)
|
||||
├─ kubeval (K8s manifests)
|
||||
├─ kustomize build (all layers)
|
||||
├─ argocd validation (app definitions)
|
||||
└─ security scan (secrets, best practices)
|
||||
↓
|
||||
[If push to main]
|
||||
└─ ArgoCD auto-syncs (if enabled)
|
||||
```
|
||||
|
||||
## Workflows
|
||||
|
||||
### 1. validate-k8s.yaml (Mandatory)
|
||||
|
||||
**Trigger:** Any push/PR with k8s/ changes
|
||||
|
||||
**What it does:**
|
||||
1. Lints all YAML files (`yamllint`)
|
||||
2. Validates K8s manifests (`kubeval`)
|
||||
3. Builds all kustomization layers
|
||||
4. Validates ArgoCD applications
|
||||
5. Reports results
|
||||
|
||||
**Duration:** ~2-3 minutes
|
||||
|
||||
**Status:**
|
||||
- ✅ PASS: All layers build, manifests valid → OK to merge
|
||||
- ❌ FAIL: Syntax error, invalid resource, build failed → Fix & push again
|
||||
|
||||
**Example output:**
|
||||
```
|
||||
=== Building k8s/infrastructure/ ===
|
||||
✓ Infrastructure built successfully
|
||||
Resources: 47
|
||||
|
||||
=== Building k8s/bootstrap/ ===
|
||||
✓ Bootstrap built successfully
|
||||
Resources: 23
|
||||
```
|
||||
|
||||
**When to check:**
|
||||
- After every commit
|
||||
- Before merging PRs
|
||||
- On every branch
|
||||
|
||||
### 2. argocd-sync.yaml (Recommended)
|
||||
|
||||
**Trigger:** Push to main only (k8s/ changed)
|
||||
|
||||
**What it does:**
|
||||
1. Authenticates with ArgoCD
|
||||
2. Syncs `homelab-root` application
|
||||
3. Waits for sync to complete (5 min timeout)
|
||||
4. Verifies all applications healthy
|
||||
|
||||
**Duration:** 1-5 minutes (depends on resources)
|
||||
|
||||
**Status:**
|
||||
- ✅ SYNCED: All resources deployed to cluster
|
||||
- ❌ FAILED: Sync error, pod crashes, etc. → Check ArgoCD UI for details
|
||||
|
||||
**When it runs:**
|
||||
- Automatically after merge to main
|
||||
- Only on k8s/ changes (not on docs)
|
||||
|
||||
**Manual trigger (if needed):**
|
||||
```bash
|
||||
# SSH to runner or use Forgejo UI
|
||||
# Re-run failed workflow
|
||||
# Or manually sync: argocd app sync homelab-root
|
||||
```
|
||||
|
||||
**Requires secrets:**
|
||||
- `ARGOCD_SERVER`: ArgoCD server URL (https://argocd.riotpiao.homelab.com)
|
||||
- `ARGOCD_AUTH_TOKEN`: ArgoCD API token (generate via ArgoCD UI)
|
||||
|
||||
### 3. security-scan.yaml (Optional)
|
||||
|
||||
**Trigger:** Any push/PR with k8s/ changes
|
||||
|
||||
**What it does:**
|
||||
1. Scans Dockerfiles for vulnerabilities (`trivy`)
|
||||
2. Scans Helm charts for security issues
|
||||
3. Audits K8s manifests (`polaris`)
|
||||
4. Checks for hardcoded secrets
|
||||
5. Verifies security best practices
|
||||
|
||||
**Duration:** ~3-5 minutes
|
||||
|
||||
**Status:**
|
||||
- ✅ PASS: No critical issues
|
||||
- ⚠️ WARNING: Best practice recommendations (non-blocking)
|
||||
- ❌ FAIL: Hardcoded secrets found (must fix)
|
||||
|
||||
**Common issues:**
|
||||
- Missing resource limits (warning)
|
||||
- Privileged containers (warning)
|
||||
- Hardcoded passwords (ERROR)
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
.forgejo/
|
||||
├── workflows/ # CI/CD workflows
|
||||
│ ├── validate-k8s.yaml # Validate manifests (required)
|
||||
│ ├── argocd-sync.yaml # Sync to cluster (auto on main)
|
||||
│ └── security-scan.yaml # Security checks (optional)
|
||||
└── CI-CD.md # This file
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setup Instructions
|
||||
|
||||
### 1. Install Forgejo Runner
|
||||
|
||||
```bash
|
||||
# On runner machine (inside cluster or external)
|
||||
forgejo-runner register \
|
||||
--instance https://forgejo.riotpiao.homelab.com \
|
||||
--token <registration-token> \
|
||||
--name homelab-runner \
|
||||
--labels docker
|
||||
|
||||
forgejo-runner daemon
|
||||
```
|
||||
|
||||
### 2. Add ArgoCD Secrets to Forgejo
|
||||
|
||||
```bash
|
||||
# Go to: Forgejo → Settings → Secrets
|
||||
|
||||
# Add:
|
||||
ARGOCD_SERVER = https://argocd.riotpiao.homelab.com
|
||||
ARGOCD_AUTH_TOKEN = <token> # Generate: argocd account generate-token
|
||||
```
|
||||
|
||||
### 3. Generate ArgoCD Token
|
||||
|
||||
```bash
|
||||
# Inside cluster
|
||||
kubectl -n argocd port-forward svc/argocd-server 8080:443
|
||||
|
||||
# Go to: https://localhost:8080/user-info/api-tokens
|
||||
# Create new token (CI/CD)
|
||||
# Copy token to Forgejo secrets
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Workflow Execution
|
||||
|
||||
### When developer pushes to feature branch:
|
||||
|
||||
```
|
||||
git push origin feature/new-service
|
||||
|
||||
↓
|
||||
Forgejo Actions triggered
|
||||
↓
|
||||
validate-k8s.yaml runs:
|
||||
✓ Lints YAML
|
||||
✓ Validates manifests
|
||||
✓ Builds kustomizations
|
||||
✓ All pass → GitHub comment: "Ready to merge"
|
||||
↓
|
||||
Developer opens PR
|
||||
↓
|
||||
Reviewer checks:
|
||||
- Code changes (YAML)
|
||||
- Workflow results
|
||||
- ArgoCD impact (diff)
|
||||
↓
|
||||
PR merged to main
|
||||
```
|
||||
|
||||
### When merged to main:
|
||||
|
||||
```
|
||||
git merge feature/new-service → main
|
||||
|
||||
↓
|
||||
Forgejo Actions triggered
|
||||
↓
|
||||
validate-k8s.yaml runs:
|
||||
✓ Same validation as above
|
||||
↓
|
||||
argocd-sync.yaml runs (if enabled):
|
||||
✓ Syncs homelab-root
|
||||
✓ Waits for sync
|
||||
✓ Verifies health
|
||||
✓ Resources deployed to cluster
|
||||
↓
|
||||
Cluster state = git state
|
||||
(No manual kubectl apply needed!)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging CI/CD Failures
|
||||
|
||||
### Issue: "Kustomize build failed"
|
||||
|
||||
```bash
|
||||
# Run locally
|
||||
cd k8s/
|
||||
kustomize build bootstrap/ # See actual error
|
||||
|
||||
# Fix YAML/kustomization.yaml
|
||||
# git push again
|
||||
```
|
||||
|
||||
### Issue: "Kubeval validation failed"
|
||||
|
||||
```bash
|
||||
# Check K8s manifest syntax
|
||||
kubeval k8s/platform/minio/config.yaml
|
||||
|
||||
# Common issues:
|
||||
# - Typos in apiVersion, kind, metadata
|
||||
# - Missing required fields
|
||||
# - Invalid references (namespace, service name)
|
||||
```
|
||||
|
||||
### Issue: "ArgoCD sync failed"
|
||||
|
||||
```bash
|
||||
# Check ArgoCD UI
|
||||
# https://argocd.riotpiao.homelab.com → homelab-root
|
||||
|
||||
# Or CLI
|
||||
argocd app get homelab-root
|
||||
argocd app logs homelab-root --follow
|
||||
|
||||
# Common issues:
|
||||
# - Missing namespace (fixed by infrastructure layer)
|
||||
# - Invalid Helm chart version
|
||||
# - Secret not found
|
||||
# - Network policy blocking traffic
|
||||
```
|
||||
|
||||
### Issue: "Security scan found hardcoded secret"
|
||||
|
||||
```bash
|
||||
# Fix: Remove secret from YAML
|
||||
# Add to SOPS encryption instead
|
||||
|
||||
# Or use ArgoCD Sealed Secrets
|
||||
# (if SOPS not available)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Viewing Results
|
||||
|
||||
### Forgejo Actions UI
|
||||
|
||||
```
|
||||
Repository → Actions
|
||||
├─ validate-k8s
|
||||
│ ├─ ✅ Success (merge safe)
|
||||
│ ├─ ❌ Failed (fix required)
|
||||
│ └─ Logs (click "Steps" → "Summary")
|
||||
├─ argocd-sync
|
||||
│ ├─ ✅ Synced (deployed)
|
||||
│ └─ ❌ Failed (check ArgoCD UI)
|
||||
└─ security-scan
|
||||
├─ ✅ Pass (no critical issues)
|
||||
└─ ⚠️ Warning (review, non-blocking)
|
||||
```
|
||||
|
||||
### ArgoCD UI
|
||||
|
||||
```
|
||||
https://argocd.riotpiao.homelab.com
|
||||
├─ homelab-root
|
||||
│ ├─ Status: Synced ✓
|
||||
│ ├─ Health: Healthy ✓
|
||||
│ └─ Details (click to see resources)
|
||||
├─ layer-1-bootstrap
|
||||
├─ layer-2-platform
|
||||
├─ layer-3-security
|
||||
├─ layer-4-applications
|
||||
└─ layer-5-data
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Tasks
|
||||
|
||||
### Add new service to cluster
|
||||
|
||||
```bash
|
||||
# 1. Create directory and kustomization.yaml
|
||||
mkdir -p k8s/applications/my-service
|
||||
cat > k8s/applications/my-service/kustomization.yaml << EOF
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: my-namespace
|
||||
helmCharts:
|
||||
- name: my-chart
|
||||
repo: https://charts.example.com
|
||||
version: 1.0.0
|
||||
releaseName: my-service
|
||||
valuesFile: values.yaml
|
||||
EOF
|
||||
|
||||
# 2. Add values.yaml
|
||||
cp /template/values.yaml k8s/applications/my-service/
|
||||
|
||||
# 3. Commit and push
|
||||
git add k8s/applications/my-service/
|
||||
git commit -m "feat(apps): add my-service"
|
||||
git push
|
||||
|
||||
# 4. CI validates
|
||||
# 5. Merge to main
|
||||
# 6. ArgoCD syncs automatically
|
||||
# ✓ Service deployed to cluster
|
||||
```
|
||||
|
||||
### Rollback a deployment
|
||||
|
||||
```bash
|
||||
# 1. Find broken commit
|
||||
git log --oneline k8s/ # Identify bad commit
|
||||
|
||||
# 2. Revert
|
||||
git revert <commit-hash>
|
||||
git push
|
||||
|
||||
# 3. CI validates (should pass)
|
||||
# 4. Merge to main
|
||||
# 5. ArgoCD syncs back to previous version
|
||||
# ✓ Cluster state reverted
|
||||
```
|
||||
|
||||
### Emergency: Disable ArgoCD auto-sync
|
||||
|
||||
```bash
|
||||
# If production broken and need time to debug:
|
||||
argocd app set homelab-root --sync-policy none
|
||||
|
||||
# Fix issue in git
|
||||
# Test locally: kustomize build k8s/
|
||||
|
||||
# Re-enable
|
||||
argocd app set homelab-root --sync-policy automated
|
||||
argocd app sync homelab-root
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring & Alerts
|
||||
|
||||
### Check workflow status in Forgejo
|
||||
|
||||
```bash
|
||||
# Dashboard shows:
|
||||
✅ All green → Safe to merge
|
||||
❌ Red → Fix required before merge
|
||||
⏳ Yellow → Still running (wait)
|
||||
```
|
||||
|
||||
### Check ArgoCD status
|
||||
|
||||
```bash
|
||||
argocd app list
|
||||
# Shows: Synced, OutOfSync, Unknown status
|
||||
|
||||
argocd app get homelab-root
|
||||
# Shows: health, sync status, resources
|
||||
|
||||
argocd app logs homelab-root --follow
|
||||
# Real-time logs during sync
|
||||
```
|
||||
|
||||
### Alerts (optional, future)
|
||||
|
||||
```yaml
|
||||
# Could add Forgejo webhooks → Slack/email
|
||||
# When CI/CD fails → Alert ops team
|
||||
# When ArgoCD goes OutOfSync → Alert ops team
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Workflow doesn't trigger
|
||||
|
||||
**Check:**
|
||||
- Is Forgejo runner running? `forgejo-runner daemon`
|
||||
- Did you push to correct branch? (validate runs on all, argocd-sync only on main)
|
||||
- Did path match filter? (must change k8s/ or .forgejo/workflows/)
|
||||
|
||||
### Workflow hangs/times out
|
||||
|
||||
**Check:**
|
||||
- kustomize build → Check for dependency cycles
|
||||
- argocd sync → Check cluster resources (storage full? network down?)
|
||||
- security scan → Large image scan → Takes time
|
||||
|
||||
**Fix:**
|
||||
- Increase timeout in workflow
|
||||
- Optimize kustomization (remove unused resources)
|
||||
- Add resource limits to pods
|
||||
|
||||
### ArgoCD token invalid
|
||||
|
||||
**Fix:**
|
||||
```bash
|
||||
# Regenerate token
|
||||
argocd account generate-token
|
||||
|
||||
# Update Forgejo secret
|
||||
# Settings → Secrets → ARGOCD_AUTH_TOKEN = <new-token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
✅ **DO:**
|
||||
- Commit all K8s changes to git (no manual kubectl apply)
|
||||
- Run validate-k8s locally before push
|
||||
- Write descriptive commit messages (why this change?)
|
||||
- Review workflow logs before merging
|
||||
- Monitor ArgoCD sync after merge
|
||||
|
||||
❌ **DON'T:**
|
||||
- Push directly to main (always use PR)
|
||||
- Skip workflow validation (it catches errors early)
|
||||
- Ignore security scan warnings
|
||||
- Manually `kubectl apply` (breaks GitOps)
|
||||
- Edit resources in cluster (they revert via ArgoCD)
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Setup Forgejo runner** (if not already running)
|
||||
2. **Add ArgoCD secrets** to Forgejo
|
||||
3. **Test workflows** on feature branch
|
||||
4. **Merge to main** → Watch ArgoCD sync
|
||||
5. **Celebrate:** Full GitOps pipeline working! 🎉
|
||||
@@ -0,0 +1,60 @@
|
||||
name: ArgoCD Sync on Main
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'k8s/**'
|
||||
|
||||
jobs:
|
||||
argocd-sync:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install ArgoCD CLI
|
||||
run: |
|
||||
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
|
||||
chmod +x /usr/local/bin/argocd
|
||||
|
||||
- name: Configure ArgoCD Access
|
||||
env:
|
||||
ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }}
|
||||
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}
|
||||
run: |
|
||||
echo "Configured ArgoCD credentials"
|
||||
|
||||
- name: Sync Root Application
|
||||
env:
|
||||
ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }}
|
||||
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}
|
||||
run: |
|
||||
echo "=== Syncing homelab-root ==="
|
||||
argocd app sync homelab-root --force
|
||||
argocd app wait homelab-root --timeout 5m
|
||||
|
||||
- name: Check Sync Status
|
||||
env:
|
||||
ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }}
|
||||
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}
|
||||
run: |
|
||||
echo "=== ArgoCD Applications Status ==="
|
||||
argocd app list -o table
|
||||
|
||||
# Verify root app is synced
|
||||
STATUS=$(argocd app get homelab-root -o jsonpath='{.status.syncStatus}')
|
||||
if [ "$STATUS" != "Synced" ]; then
|
||||
echo "❌ Root app sync failed: $STATUS"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Root app synced successfully"
|
||||
|
||||
- name: Health Check
|
||||
env:
|
||||
ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }}
|
||||
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}
|
||||
run: |
|
||||
echo "=== Checking Application Health ==="
|
||||
argocd app get homelab-root -o wide
|
||||
@@ -0,0 +1,119 @@
|
||||
name: Security Scan
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
paths:
|
||||
- 'k8s/**'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'k8s/**'
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Tools
|
||||
run: |
|
||||
apt-get update && apt-get install -y \
|
||||
python3-pip \
|
||||
curl
|
||||
|
||||
# Install Trivy (vulnerability scanner)
|
||||
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
|
||||
# Install Polaris (K8s security audit)
|
||||
curl -L https://github.com/FairwindsOps/polaris/releases/latest/download/polaris-linux-amd64 -o /usr/local/bin/polaris
|
||||
chmod +x /usr/local/bin/polaris
|
||||
|
||||
- name: Trivy - Scan Dockerfile (if present)
|
||||
run: |
|
||||
if find . -name "Dockerfile" 2>/dev/null | grep -v node_modules | head -1 | grep -q .; then
|
||||
echo "=== Scanning Dockerfiles with Trivy ==="
|
||||
find . -name "Dockerfile" -not -path "*/node_modules/*" -exec trivy config {} \;
|
||||
else
|
||||
echo "No Dockerfiles found"
|
||||
fi
|
||||
|
||||
- name: Trivy - Scan Helm Charts
|
||||
run: |
|
||||
if find k8s -name "Chart.yaml" 2>/dev/null | head -1 | grep -q .; then
|
||||
echo "=== Scanning Helm charts with Trivy ==="
|
||||
find k8s -name "Chart.yaml" -exec dirname {} \; | while read chart; do
|
||||
echo "Scanning $chart..."
|
||||
trivy config "$chart" || true
|
||||
done
|
||||
else
|
||||
echo "No Helm charts found"
|
||||
fi
|
||||
|
||||
- name: Polaris - K8s Security Audit
|
||||
run: |
|
||||
echo "=== Running Polaris K8s security audit ==="
|
||||
polaris audit --audit-path /tmp/polaris-audit.json k8s/ || true
|
||||
|
||||
if [ -f /tmp/polaris-audit.json ]; then
|
||||
echo "Security issues found:"
|
||||
jq '.results[] | select(.pass == false)' /tmp/polaris-audit.json || true
|
||||
fi
|
||||
|
||||
- name: Check for Secrets in Code
|
||||
run: |
|
||||
echo "=== Scanning for hardcoded secrets ==="
|
||||
SECRETS_FOUND=0
|
||||
|
||||
# Check for common secret patterns
|
||||
for pattern in "password:" "secret:" "token:" "api_key:" "apikey:" "private_key:" "privatekey:"; do
|
||||
if grep -r "$pattern" k8s/ --include="*.yaml" --include="*.yml" | grep -v "^Binary"; then
|
||||
echo "⚠️ Found potential secret pattern: $pattern"
|
||||
SECRETS_FOUND=$((SECRETS_FOUND + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $SECRETS_FOUND -gt 0 ]; then
|
||||
echo "⚠️ Warning: Found $SECRETS_FOUND potential secrets"
|
||||
echo "Secrets should be encrypted with SOPS or stored in ArgoCD Sealed Secrets"
|
||||
else
|
||||
echo "✓ No hardcoded secrets found"
|
||||
fi
|
||||
|
||||
- name: Check for Security Best Practices
|
||||
run: |
|
||||
echo "=== Checking K8s security best practices ==="
|
||||
|
||||
# Check for privileged containers
|
||||
if grep -r "privileged: true" k8s/ --include="*.yaml" --include="*.yml"; then
|
||||
echo "⚠️ Found privileged containers"
|
||||
fi
|
||||
|
||||
# Check for hostNetwork
|
||||
if grep -r "hostNetwork: true" k8s/ --include="*.yaml" --include="*.yml"; then
|
||||
echo "⚠️ Found hostNetwork usage"
|
||||
fi
|
||||
|
||||
# Check for missing resource limits
|
||||
echo "Checking for missing resource requests/limits..."
|
||||
MISSING=0
|
||||
find k8s -name "*.yaml" -o -name "*.yml" | while read file; do
|
||||
if grep -q "kind: Deployment\|kind: StatefulSet\|kind: DaemonSet" "$file"; then
|
||||
if ! grep -q "resources:" "$file"; then
|
||||
echo "⚠️ $file: Missing resource requests/limits"
|
||||
MISSING=$((MISSING + 1))
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "=== Security Scan Summary ==="
|
||||
echo "✓ Dockerfiles scanned"
|
||||
echo "✓ Helm charts scanned"
|
||||
echo "✓ K8s manifests audited"
|
||||
echo "✓ Secrets check completed"
|
||||
echo "✓ Best practices verified"
|
||||
@@ -1,109 +0,0 @@
|
||||
name: Terraform Apply CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'terraform/**'
|
||||
- '.forgejo/workflows/terraform-apply.yml'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
terraform:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Dependencies
|
||||
run: |
|
||||
# Install tools: wget (download terraform), unzip (extract), curl (optional)
|
||||
# Runner uses node:22-bookworm (Debian), not Alpine, so use apt-get
|
||||
apt-get update && apt-get install -y wget unzip curl
|
||||
|
||||
- name: Setup Terraform
|
||||
run: |
|
||||
TF_VERSION=1.8.4
|
||||
TF_URL="https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
|
||||
mkdir -p /tmp/tf-bin
|
||||
cd /tmp/tf-bin
|
||||
wget -q "$TF_URL" || { echo "Failed to download terraform"; exit 1; }
|
||||
unzip -q "terraform_${TF_VERSION}_linux_amd64.zip"
|
||||
chmod +x terraform
|
||||
./terraform version
|
||||
echo "/tmp/tf-bin" >> $GITHUB_PATH
|
||||
|
||||
- name: Terraform Format Check
|
||||
run: terraform fmt -check -recursive terraform/
|
||||
continue-on-error: true
|
||||
|
||||
- name: Configure AWS Credentials
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
run: |
|
||||
mkdir -p ~/.aws
|
||||
cat > ~/.aws/credentials << EOF
|
||||
[minio]
|
||||
aws_access_key_id = $AWS_ACCESS_KEY_ID
|
||||
aws_secret_access_key = $AWS_SECRET_ACCESS_KEY
|
||||
EOF
|
||||
chmod 600 ~/.aws/credentials
|
||||
|
||||
- name: Terraform Init
|
||||
working-directory: terraform
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
TF_SKIP_CREDENTIALS_VALIDATION: "true"
|
||||
TF_SKIP_REGION_VALIDATION: "true"
|
||||
TF_SKIP_REQUESTING_ACCOUNT_ID: "true"
|
||||
run: |
|
||||
terraform init \
|
||||
-backend-config="bucket=terraform-state" \
|
||||
-backend-config="key=homelab/terraform.tfstate" \
|
||||
-backend-config="region=us-east-1" \
|
||||
-backend-config="endpoint=http://minio.storage.svc.cluster.local:9000" \
|
||||
-backend-config="access_key=$AWS_ACCESS_KEY_ID" \
|
||||
-backend-config="secret_key=$AWS_SECRET_ACCESS_KEY" \
|
||||
-backend-config="skip_credentials_validation=true" \
|
||||
-backend-config="use_path_style=true"
|
||||
|
||||
- name: Pull Terraform State
|
||||
working-directory: terraform
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
TF_SKIP_CREDENTIALS_VALIDATION: "true"
|
||||
run: |
|
||||
echo "Verifying state is accessible from MinIO..."
|
||||
terraform state pull > /tmp/tfstate-verify.json
|
||||
STATE_SIZE=$(wc -c < /tmp/tfstate-verify.json)
|
||||
RESOURCE_COUNT=$(terraform state list | wc -l)
|
||||
echo "State size: $STATE_SIZE bytes"
|
||||
echo "Resources in state: $RESOURCE_COUNT"
|
||||
|
||||
- name: Terraform Validate
|
||||
working-directory: terraform
|
||||
run: terraform validate
|
||||
|
||||
- name: Terraform Plan
|
||||
working-directory: terraform
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
TF_SKIP_CREDENTIALS_VALIDATION: "true"
|
||||
run: |
|
||||
terraform plan -out=tfplan
|
||||
|
||||
- name: Terraform Apply
|
||||
working-directory: terraform
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
TF_SKIP_CREDENTIALS_VALIDATION: "true"
|
||||
run: |
|
||||
terraform apply -auto-approve tfplan
|
||||
@@ -0,0 +1,111 @@
|
||||
name: Validate Kubernetes Manifests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- develop
|
||||
paths:
|
||||
- 'k8s/**'
|
||||
- '.forgejo/workflows/validate-k8s.yaml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'k8s/**'
|
||||
- '.forgejo/workflows/validate-k8s.yaml'
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: docker
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Tools
|
||||
run: |
|
||||
apt-get update && apt-get install -y \
|
||||
yamllint \
|
||||
python3-pip \
|
||||
curl \
|
||||
jq
|
||||
|
||||
# Install kubeval
|
||||
curl -L https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz | tar xz
|
||||
mv kubeval /usr/local/bin/
|
||||
|
||||
# Install kustomize
|
||||
curl -s https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh | bash
|
||||
mv kustomize /usr/local/bin/
|
||||
|
||||
# Install ArgoCD CLI
|
||||
curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
|
||||
chmod +x /usr/local/bin/argocd
|
||||
|
||||
- name: YAML Lint
|
||||
run: |
|
||||
echo "=== Linting YAML files ==="
|
||||
yamllint k8s/ -c .yamllint.yaml || true
|
||||
|
||||
- name: Kubeval - Validate K8s Syntax
|
||||
run: |
|
||||
echo "=== Validating Kubernetes manifests ==="
|
||||
find k8s -name "*.yaml" -o -name "*.yml" | grep -v "\.archive" | while read file; do
|
||||
echo "Validating $file..."
|
||||
kubeval "$file" -d 2>/dev/null || true
|
||||
done
|
||||
|
||||
- name: Kustomize Build - Infrastructure
|
||||
run: |
|
||||
echo "=== Building k8s/infrastructure/ ==="
|
||||
kustomize build k8s/infrastructure > /tmp/infrastructure.yaml
|
||||
echo "✓ Infrastructure built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/infrastructure.yaml)"
|
||||
|
||||
- name: Kustomize Build - Bootstrap
|
||||
run: |
|
||||
echo "=== Building k8s/bootstrap/ ==="
|
||||
kustomize build k8s/bootstrap > /tmp/bootstrap.yaml
|
||||
echo "✓ Bootstrap built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/bootstrap.yaml || echo 0)"
|
||||
|
||||
- name: Kustomize Build - Platform
|
||||
run: |
|
||||
echo "=== Building k8s/platform/ ==="
|
||||
kustomize build k8s/platform > /tmp/platform.yaml
|
||||
echo "✓ Platform built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/platform.yaml || echo 0)"
|
||||
|
||||
- name: Kustomize Build - Security
|
||||
run: |
|
||||
echo "=== Building k8s/security/ ==="
|
||||
kustomize build k8s/security > /tmp/security.yaml
|
||||
echo "✓ Security built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/security.yaml || echo 0)"
|
||||
|
||||
- name: Kustomize Build - Applications
|
||||
run: |
|
||||
echo "=== Building k8s/applications/ ==="
|
||||
kustomize build k8s/applications > /tmp/applications.yaml
|
||||
echo "✓ Applications built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/applications.yaml || echo 0)"
|
||||
|
||||
- name: Kustomize Build - Data
|
||||
run: |
|
||||
echo "=== Building k8s/data/ ==="
|
||||
kustomize build k8s/data > /tmp/data.yaml
|
||||
echo "✓ Data built successfully"
|
||||
echo "Resources: $(grep -c 'kind:' /tmp/data.yaml || echo 0)"
|
||||
|
||||
- name: Validate ArgoCD Applications
|
||||
run: |
|
||||
echo "=== Validating ArgoCD Applications ==="
|
||||
kubeval k8s/argocd/apps/*.yaml
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
echo "=== Validation Summary ==="
|
||||
echo "✓ All manifests validated"
|
||||
echo "✓ All kustomizations built"
|
||||
echo "✓ All ArgoCD apps valid"
|
||||
echo ""
|
||||
echo "Next: Push to main → ArgoCD syncs automatically"
|
||||
Reference in New Issue
Block a user