name: Workflow Lint & Validation on: pull_request: branches: [ main ] paths: - ".gitea/workflows/*.yml" - "tools/validate_gitea_*.py" push: branches: [ main ] paths: - ".gitea/workflows/*.yml" - "tools/validate_gitea_*.py" workflow_dispatch: jobs: lint-workflows: name: "Lint All Workflow Files" runs-on: ubuntu-latest env: PYTHONPATH: "$HOME/python_deps/lint:." steps: - name: Checkout Code uses: actions/checkout@v3 with: fetch-depth: 0 - name: Setup Python Environment run: | PYTHON_DEPS="$HOME/python_deps/lint" mkdir -p "$PYTHON_DEPS" /usr/bin/python3 -m pip install --disable-pip-version-check --quiet \ --target "$PYTHON_DEPS" pyyaml echo "✓ Python dependencies installed" - name: Validate CI Workflow Structure run: | python3 tools/validate_gitea_ci_workflow_lint_v1.py --workflow .gitea/workflows/ci.yml echo "✓ CI workflow lint passed" - name: Validate Workflow Jobs & Dependencies run: | python3 - <<'PY' import yaml from pathlib import Path workflows_dir = Path(".gitea/workflows") errors = [] for wf_file in workflows_dir.glob("*.yml"): try: with open(wf_file) as f: wf = yaml.safe_load(f) if not wf: errors.append(f"{wf_file}: Empty workflow") continue # Check required fields if "on" not in wf: errors.append(f"{wf_file}: Missing 'on' trigger") if "jobs" not in wf: errors.append(f"{wf_file}: Missing 'jobs'") # Check job structure for job_name, job_config in (wf.get("jobs") or {}).items(): if not isinstance(job_config, dict): errors.append(f"{wf_file}[{job_name}]: Invalid job structure") continue if "runs-on" not in job_config and "needs" not in job_config: errors.append(f"{wf_file}[{job_name}]: Missing 'runs-on'") # Validate 'needs' references needs = job_config.get("needs", []) if isinstance(needs, str): needs = [needs] for dep_job in needs: if dep_job not in wf.get("jobs", {}): errors.append(f"{wf_file}[{job_name}]: Invalid dependency '{dep_job}'") print(f"✓ {wf_file.name}: Valid") except yaml.YAMLError as e: errors.append(f"{wf_file}: YAML parse error — {e}") except Exception as e: errors.append(f"{wf_file}: {e}") if errors: print("\n❌ Validation errors:") for error in errors: print(f" {error}") exit(1) else: print("\n✓ All workflows validated successfully") PY validate-secrets-contract: name: "Validate Secrets Contract" runs-on: ubuntu-latest env: PYTHONPATH: "$HOME/python_deps/secrets:." steps: - name: Checkout Code uses: actions/checkout@v3 - name: Setup Python Environment run: | PYTHON_DEPS="$HOME/python_deps/secrets" mkdir -p "$PYTHON_DEPS" /usr/bin/python3 -m pip install --disable-pip-version-check --quiet \ --target "$PYTHON_DEPS" pyyaml echo "✓ Python dependencies installed" - name: Validate Gitea Secrets Contract run: | python3 tools/validate_gitea_secrets_contract_v1.py echo "✓ Secrets contract validated" notify-results: name: "Notify Lint Results" if: always() needs: [lint-workflows, validate-secrets-contract] runs-on: ubuntu-latest steps: - name: Report Workflow Validation Status env: LINT_STATUS: ${{ needs.lint-workflows.result }} SECRETS_STATUS: ${{ needs.validate-secrets-contract.result }} run: | echo "════════════════════════════════════════════════════" echo "Workflow Validation Report" echo "════════════════════════════════════════════════════" echo "" echo "Lint & Structure: $([ "$LINT_STATUS" = "success" ] && echo "✅ PASS" || echo "❌ FAIL")" echo "Secrets Contract: $([ "$SECRETS_STATUS" = "success" ] && echo "✅ PASS" || echo "❌ FAIL")" echo "" if [ "$LINT_STATUS" = "success" ] && [ "$SECRETS_STATUS" = "success" ]; then echo "✅ All workflow validations passed" exit 0 else echo "❌ Workflow validation failed — review logs above" exit 1 fi