diff --git a/.gitea/workflows/terraform.yml b/.gitea/workflows/terraform.yml new file mode 100644 index 0000000..04e18b2 --- /dev/null +++ b/.gitea/workflows/terraform.yml @@ -0,0 +1,200 @@ +name: Terraform CI/CD + +on: + push: + branches: + - dev + paths: + - 'terraform/**' + pull_request: + branches: + - dev + paths: + - 'terraform/**' + +env: + TF_WORKING_DIR: terraform/prod + # Backend S3 (Scaleway) + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # Terraform variables + TF_VAR_proxmox_url: ${{ secrets.PROXMOX_URL }} + TF_VAR_proxmox_api_token: ${{ secrets.PROXMOX_API_TOKEN }} + TF_VAR_tailscale_auth_key: ${{ secrets.TAILSCALE_AUTH_KEY }} + +jobs: + # =========================================================================== + # Validation - Runs on all pushes and PRs + # =========================================================================== + validate: + name: Validate + runs-on: self-hosted + container: + image: gitea.arnodo.fr/damien/terraform-ci:latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Terraform Format Check + id: fmt + run: terraform fmt -check -recursive -diff + working-directory: ${{ env.TF_WORKING_DIR }} + continue-on-error: true + + - name: TFLint + id: lint + run: tflint --recursive --format compact + working-directory: ${{ env.TF_WORKING_DIR }} + continue-on-error: true + + - name: Terraform Init + id: init + run: terraform init -input=false + working-directory: ${{ env.TF_WORKING_DIR }} + + - name: Terraform Validate + id: validate + run: terraform validate -no-color + working-directory: ${{ env.TF_WORKING_DIR }} + + - name: Validation Summary + if: always() + run: | + echo "## Validation Results" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| Check | Status |" >> $GITHUB_STEP_SUMMARY + echo "|-------|--------|" >> $GITHUB_STEP_SUMMARY + echo "| Format | ${{ steps.fmt.outcome == 'success' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY + echo "| TFLint | ${{ steps.lint.outcome == 'success' && '✅' || '⚠️' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Init | ${{ steps.init.outcome == 'success' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY + echo "| Validate | ${{ steps.validate.outcome == 'success' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY + + - name: Check Validation Status + if: steps.fmt.outcome == 'failure' || steps.init.outcome == 'failure' || steps.validate.outcome == 'failure' + run: exit 1 + + # =========================================================================== + # Plan - Runs on PRs and pushes to dev + # =========================================================================== + plan: + name: Plan + runs-on: self-hosted + container: + image: gitea.arnodo.fr/damien/terraform-ci:latest + needs: validate + outputs: + plan_exitcode: ${{ steps.plan.outputs.exitcode }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Terraform Init + run: terraform init -input=false + working-directory: ${{ env.TF_WORKING_DIR }} + + - name: Terraform Plan + id: plan + run: | + set +e + terraform plan -input=false -no-color -detailed-exitcode -out=tfplan 2>&1 | tee plan_output.txt + EXITCODE=$? + echo "exitcode=${EXITCODE}" >> $GITHUB_OUTPUT + # Exit 0 for no changes, exit 2 for changes - both are OK + if [ $EXITCODE -eq 1 ]; then + exit 1 + fi + exit 0 + working-directory: ${{ env.TF_WORKING_DIR }} + + - name: Upload Plan + uses: actions/upload-artifact@v4 + with: + name: tfplan + path: | + ${{ env.TF_WORKING_DIR }}/tfplan + ${{ env.TF_WORKING_DIR }}/plan_output.txt + retention-days: 7 + + - name: Plan Summary + if: always() + run: | + echo "## Terraform Plan" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Exit code:** \`${{ steps.plan.outputs.exitcode }}\`" >> $GITHUB_STEP_SUMMARY + echo "- \`0\` = No changes" >> $GITHUB_STEP_SUMMARY + echo "- \`2\` = Changes detected" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat plan_output.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + working-directory: ${{ env.TF_WORKING_DIR }} + + - name: Comment PR with Plan + if: gitea.event_name == 'pull_request' + env: + GIT_TOKEN: ${{ secrets.GIT_TOKEN }} + run: | + PLAN_OUTPUT=$(cat plan_output.txt | head -c 60000) + COMMENT_BODY=$(cat < + Click to expand plan output + + \`\`\`hcl + ${PLAN_OUTPUT} + \`\`\` + + + EOF + ) + + # Post comment via Gitea API + curl -s -X POST \ + -H "Authorization: token ${GIT_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "$(jq -n --arg body "$COMMENT_BODY" '{body: $body}')" \ + "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/issues/${{ gitea.event.pull_request.number }}/comments" + working-directory: ${{ env.TF_WORKING_DIR }} + + # =========================================================================== + # Apply - Runs on push to dev only (when changes detected) + # =========================================================================== + apply: + name: Apply + runs-on: self-hosted + container: + image: gitea.arnodo.fr/damien/terraform-ci:latest + needs: plan + if: | + gitea.event_name == 'push' && + gitea.ref == 'refs/heads/dev' && + needs.plan.outputs.plan_exitcode == '2' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download Plan + uses: actions/download-artifact@v4 + with: + name: tfplan + path: ${{ env.TF_WORKING_DIR }} + + - name: Terraform Init + run: terraform init -input=false + working-directory: ${{ env.TF_WORKING_DIR }} + + - name: Terraform Apply + run: terraform apply -input=false -auto-approve tfplan + working-directory: ${{ env.TF_WORKING_DIR }} + + - name: Apply Summary + if: always() + run: | + echo "## ✅ Terraform Apply Complete" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "Infrastructure has been updated successfully." >> $GITHUB_STEP_SUMMARY