name: PR Lint and Validation Gate on: pull_request: branches: [ "main" ] workflow_dispatch: {} concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: yaml-and-whitespace: name: YAML and Whitespace runs-on: ubuntu-latest outputs: elapsed_seconds: ${{ steps.elapsed.outputs.seconds }} steps: - name: Start timer run: echo "START_TS=$(date +%s)" >> "$GITHUB_ENV" - name: Checkout uses: actions/checkout@v4 - name: Install yamllint run: | python3 -m pip install --upgrade pip python3 -m pip install yamllint - name: Run yamllint run: | yamllint -d "{extends: default, rules: {line-length: {max: 180}, truthy: disable}}" . - name: Check trailing whitespace run: | if git ls-files -z | xargs -0 -I{} grep -nH -E "[[:blank:]]+$" "{}" \ --exclude="*.md" --exclude="*.svg"; then echo "Trailing whitespace found" exit 1 fi - name: Capture duration id: elapsed if: always() run: | end_ts=$(date +%s) start_ts=${START_TS:-$end_ts} echo "seconds=$((end_ts - start_ts))" >> "$GITHUB_OUTPUT" helm-lint: name: Helm Lint runs-on: ubuntu-latest outputs: elapsed_seconds: ${{ steps.elapsed.outputs.seconds }} steps: - name: Start timer run: echo "START_TS=$(date +%s)" >> "$GITHUB_ENV" - name: Checkout uses: actions/checkout@v4 - name: Install Helm uses: azure/setup-helm@v4 - name: Lint all charts run: | mapfile -t charts < <(find . -type f -name Chart.yaml -print) if [ -z "${charts[0]:-}" ]; then echo "No Helm charts found; skipping" exit 0 fi failed=0 for chart in "${charts[@]}"; do chart_dir="$(dirname "$chart")" echo "Linting ${chart_dir}" if ! helm lint "$chart_dir"; then failed=1 fi done exit $failed - name: Capture duration id: elapsed if: always() run: | end_ts=$(date +%s) start_ts=${START_TS:-$end_ts} echo "seconds=$((end_ts - start_ts))" >> "$GITHUB_OUTPUT" terraform-lint: name: Terraform Validate and TFLint runs-on: ubuntu-latest outputs: elapsed_seconds: ${{ steps.elapsed.outputs.seconds }} steps: - name: Start timer run: echo "START_TS=$(date +%s)" >> "$GITHUB_ENV" - name: Checkout uses: actions/checkout@v4 - name: Setup Terraform uses: hashicorp/setup-terraform@v3 - name: Install TFLint run: | curl -s https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash - name: Validate Terraform directories run: | mapfile -t tf_dirs < <(find . -type f -name "*.tf" -exec dirname {} \; | sort -u) if [ -z "${tf_dirs[0]:-}" ]; then echo "No Terraform files found; skipping" exit 0 fi failed=0 for dir in "${tf_dirs[@]}"; do echo "Validating ${dir}" ( cd "$dir" terraform init -backend=false -input=false -no-color terraform validate -no-color tflint --chdir . ) || failed=1 done exit $failed - name: Capture duration id: elapsed if: always() run: | end_ts=$(date +%s) start_ts=${START_TS:-$end_ts} echo "seconds=$((end_ts - start_ts))" >> "$GITHUB_OUTPUT" policy-and-deps: name: Policy and Dependency Scan runs-on: ubuntu-latest outputs: elapsed_seconds: ${{ steps.elapsed.outputs.seconds }} steps: - name: Start timer run: echo "START_TS=$(date +%s)" >> "$GITHUB_ENV" - name: Checkout uses: actions/checkout@v4 - name: Install Checkov run: | python3 -m pip install --upgrade pip python3 -m pip install checkov - name: Run Checkov (Kubernetes) run: | checkov -d . --framework kubernetes --skip-path "examples/default/rendered/.*" --quiet - name: npm audit (if package.json exists) run: | if [ -f package.json ]; then npm install --no-audit --no-fund npm audit --audit-level=high else echo "No package.json found; skipping npm audit" fi - name: Maven dependency-check (if pom.xml exists) run: | if [ -f pom.xml ]; then mvn -B org.owasp:dependency-check-maven:check else echo "No pom.xml found; skipping Maven dependency-check" fi - name: Capture duration id: elapsed if: always() run: | end_ts=$(date +%s) start_ts=${START_TS:-$end_ts} echo "seconds=$((end_ts - start_ts))" >> "$GITHUB_OUTPUT" runtime-slo: name: Runtime SLO (< 3 min) if: always() needs: - yaml-and-whitespace - helm-lint - terraform-lint - policy-and-deps runs-on: ubuntu-latest steps: - name: Enforce suite runtime budget run: | y=${{ needs.yaml-and-whitespace.outputs.elapsed_seconds || 0 }} h=${{ needs.helm-lint.outputs.elapsed_seconds || 0 }} t=${{ needs.terraform-lint.outputs.elapsed_seconds || 0 }} p=${{ needs.policy-and-deps.outputs.elapsed_seconds || 0 }} # Jobs run in parallel; full-suite wall-clock is approximated by max(job runtimes). suite=$y [ $h -gt $suite ] && suite=$h [ $t -gt $suite ] && suite=$t [ $p -gt $suite ] && suite=$p echo "## PR Lint Gate Runtime" >> "$GITHUB_STEP_SUMMARY" echo "- YAML and Whitespace: ${y}s" >> "$GITHUB_STEP_SUMMARY" echo "- Helm Lint: ${h}s" >> "$GITHUB_STEP_SUMMARY" echo "- Terraform Validate and TFLint: ${t}s" >> "$GITHUB_STEP_SUMMARY" echo "- Policy and Dependency Scan: ${p}s" >> "$GITHUB_STEP_SUMMARY" echo "- Full suite (parallel max): ${suite}s" >> "$GITHUB_STEP_SUMMARY" if [ $suite -gt 180 ]; then echo "Runtime SLO exceeded: ${suite}s > 180s" exit 1 fi