name: Cluster CI Pipeline on: push: branches: - main - develop paths: - 'k8s/**' - '.forgejo/workflows/cluster-ci.yaml' pull_request: paths: - 'k8s/**' jobs: ci: runs-on: docker steps: # === Checkout === - name: Checkout run: | REPO_URL="${{ gitea.server_url }}/${{ gitea.repository }}.git" CLONE_URL="https://${{ secrets.CI_RUNNER }}:${{ secrets.CI_RUNNER_SECRET }}@${REPO_URL#https://}" git clone --depth 1 "$CLONE_URL" . git fetch origin main git checkout main # === Install Tools === - name: Install Tools run: | unset GITHUB_TOKEN apt-get update && apt-get install -y \ yamllint \ python3-pip \ curl \ jq # kubeval curl -L https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz | tar xz mv -f kubeval /usr/local/bin/ # kustomize rm -f kustomize curl -s https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh | bash mv -f kustomize /usr/local/bin/ # argocd 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 # trivy curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin # polaris curl -L https://github.com/FairwindsOps/polaris/releases/latest/download/polaris-linux-amd64 -o /usr/local/bin/polaris chmod +x /usr/local/bin/polaris # === YAML Lint === - name: YAML Lint run: | echo "=== Linting YAML files ===" yamllint k8s/ -c .yamllint.yaml || true # === Kubeval - Validate K8s Syntax === - 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 # === Kustomize Build - All overlays === - 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 # === Trivy - Scan Dockerfile === - name: Trivy - Scan Dockerfile 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 # === Trivy - Scan Helm Charts === - 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 # === Polaris - K8s Security Audit === - 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 # === Check for Secrets in Code === - name: Check for Secrets in Code run: | echo "=== Scanning for hardcoded secrets ===" SECRETS_FOUND=0 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 # === Check K8s Security Best Practices === - name: Check K8s Security Best Practices run: | echo "=== Checking K8s security best practices ===" if grep -r "privileged: true" k8s/ --include="*.yaml" --include="*.yml"; then echo "⚠️ Found privileged containers" fi if grep -r "hostNetwork: true" k8s/ --include="*.yaml" --include="*.yml"; then echo "⚠️ Found hostNetwork usage" fi echo "Checking for missing resource 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 # === ArgoCD Sync (main branch only) === - name: Sync ArgoCD if: github.ref == 'refs/heads/main' && github.event_name == 'push' 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 if: github.ref == 'refs/heads/main' && github.event_name == 'push' env: ARGOCD_SERVER: ${{ secrets.ARGOCD_SERVER }} ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }} run: | echo "=== ArgoCD Applications Status ===" argocd app list -o table 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 if: github.ref == 'refs/heads/main' && github.event_name == 'push' 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 # === Summary === - name: Summary if: always() run: | echo "=== CI Pipeline Summary ===" echo "✓ YAML linted" echo "✓ Manifests validated" echo "✓ Kustomizations built" echo "✓ Security scans completed" echo "✓ Secrets check passed" echo "✓ Best practices verified" echo "" echo "✓ All checks passed"