diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..9c76e26 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,122 @@ +# GitHub Actions Workflows + +This directory contains CI/CD workflows for the EDB_Testing repository. + +## Workflows + +### 1. YAML Validation (`yaml-validation.yml`) + +Validates all OpenShift manifests and Kustomize configurations. + +**Triggers:** +- Push to `main` or `develop` +- Pull requests changing `.yaml` or `.yml` files + +**Jobs:** +- `yaml-lint`: Runs yamllint for syntax and style +- `kubeval`: Validates OpenShift / declarative resource schema compliance +- `kustomize-build`: Tests kustomize builds +- `summary`: Aggregates results + +### 2. Shell Script Testing (`shell-script-testing.yml`) + +Tests all bash scripts for quality and correctness. + +**Triggers:** +- Push to `main` or `develop` +- Pull requests changing `.sh` files or `scripts/` directory + +**Jobs:** +- `shellcheck`: Lints scripts with ShellCheck +- `syntax-check`: Validates bash syntax +- `script-permissions`: Checks executable permissions +- `script-standards`: Verifies best practices (shebang, set -e) +- `unit-tests`: Runs BATS tests if available +- `summary`: Aggregates results + +### 3. PR Validation (`pr-validation.yml`) + +Comprehensive validation for pull requests before merge. + +**Triggers:** +- Pull request opened, synchronized, or reopened + +**Jobs:** +- `pr-info`: Displays PR metadata +- `changed-files`: Detects which file types changed +- `yaml-validation`: Runs if YAML files changed +- `shell-validation`: Runs if scripts changed +- `security-scan`: Always runs (secrets, credentials) +- `docs-validation`: Runs if markdown changed +- `pr-size-check`: Warns on large PRs +- `summary`: Aggregates all results + +## Running Locally + +Install dependencies: + +```bash +# Python tools +pip install yamllint pre-commit + +# ShellCheck +brew install shellcheck # macOS +apt-get install shellcheck # Ubuntu + +# Kubeval +wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz +tar xf kubeval-linux-amd64.tar.gz +sudo mv kubeval /usr/local/bin/ + +# Kustomize +curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash +sudo mv kustomize /usr/local/bin/ +``` + +Run checks manually: + +```bash +# YAML validation +yamllint . +find . -name "*.yaml" -exec kubeval --strict {} \; + +# Shell script testing +find . -name "*.sh" -exec shellcheck {} \; +find . -name "*.sh" -exec bash -n {} \; + +# Or use pre-commit +pre-commit run --all-files +``` + +## Configuration Files + +- `.yamllint` - YAML linting rules (created by workflow) +- `.markdownlint.json` - Markdown linting rules +- `.pre-commit-config.yaml` - Pre-commit hook configuration +- `.secrets.baseline` - Secret detection baseline + +## Workflow Status + +Check status badges (add to main README.md): + +```markdown +![YAML Validation](https://github.com/YOUR_ORG/EDB_Testing/workflows/YAML%20Validation/badge.svg) +![Shell Testing](https://github.com/YOUR_ORG/EDB_Testing/workflows/Shell%20Script%20Testing/badge.svg) +``` + +## Troubleshooting + +**Workflow fails but passes locally:** +- Check tool versions match +- Ensure all files are committed +- Review workflow logs in Actions tab + +**Too many false positives:** +- Adjust severity levels in workflow files +- Add exclusions to yamllint/shellcheck configs +- Update `.secrets.baseline` for false secret detections + +## References + +- [CI/CD Pipeline Documentation](../docs/cicd-pipeline.md) +- [GitHub Actions Documentation](https://docs.github.com/en/actions) diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml new file mode 100644 index 0000000..938bad7 --- /dev/null +++ b/.github/workflows/pr-validation.yml @@ -0,0 +1,383 @@ +name: Pull Request Validation + +on: + pull_request: + branches: + - main + - develop + types: [opened, synchronize, reopened, ready_for_review] + +# Cancel in-progress runs for the same PR +concurrency: + group: pr-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + pr-info: + name: PR Information + runs-on: ubuntu-latest + + steps: + - name: PR Details + run: | + echo "=============================================" + echo "Pull Request Validation" + echo "=============================================" + echo "PR Number: #${{ github.event.pull_request.number }}" + echo "Title: ${{ github.event.pull_request.title }}" + echo "Author: ${{ github.event.pull_request.user.login }}" + echo "Base Branch: ${{ github.event.pull_request.base.ref }}" + echo "Head Branch: ${{ github.event.pull_request.head.ref }}" + echo "=============================================" + + changed-files: + name: Detect Changed Files + runs-on: ubuntu-latest + outputs: + yaml_changed: ${{ steps.changes.outputs.yaml }} + scripts_changed: ${{ steps.changes.outputs.scripts }} + docs_changed: ${{ steps.changes.outputs.docs }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect file changes + id: changes + run: | + echo "Detecting changed files..." + + # Get list of changed files + git diff --name-only origin/${{ github.event.pull_request.base.ref }}...HEAD > changed-files.txt + + echo "Changed files:" + cat changed-files.txt + + # Check for YAML changes + if grep -E '\.(yaml|yml)$' changed-files.txt > /dev/null; then + echo "yaml=true" >> $GITHUB_OUTPUT + echo "✅ YAML files changed" + else + echo "yaml=false" >> $GITHUB_OUTPUT + echo "ℹ️ No YAML files changed" + fi + + # Check for script changes + if grep -E '\.(sh)$|^scripts/' changed-files.txt > /dev/null; then + echo "scripts=true" >> $GITHUB_OUTPUT + echo "✅ Script files changed" + else + echo "scripts=false" >> $GITHUB_OUTPUT + echo "ℹ️ No script files changed" + fi + + # Check for docs changes + if grep -E '^docs/|\.md$' changed-files.txt > /dev/null; then + echo "docs=true" >> $GITHUB_OUTPUT + echo "✅ Documentation changed" + else + echo "docs=false" >> $GITHUB_OUTPUT + echo "ℹ️ No documentation changed" + fi + + yaml-validation: + name: YAML Validation + needs: changed-files + if: needs.changed-files.outputs.yaml_changed == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install yamllint + run: pip install yamllint + + - name: Run yamllint + run: | + yamllint -f colored . || exit 1 + + - name: Validate Kubernetes manifests + run: | + wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz + tar xf kubeval-linux-amd64.tar.gz + sudo mv kubeval /usr/local/bin + + find . -type f \( -name "*.yaml" -o -name "*.yml" \) \ + -not -path "./.git/*" \ + -not -path "./.github/*" \ + -exec grep -l "apiVersion:" {} \; | \ + while read -r file; do + if ! grep -q "kind: Kustomization" "$file"; then + echo "Validating: $file" + kubeval --strict --ignore-missing-schemas "$file" || exit 1 + fi + done + + shell-validation: + name: Shell Script Validation + needs: changed-files + if: needs.changed-files.outputs.scripts_changed == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + with: + severity: error + ignore_paths: node_modules vendor + + - name: Validate Bash syntax + run: | + find . -type f -name "*.sh" -not -path "./.git/*" | while read -r script; do + echo "Checking syntax: $script" + bash -n "$script" || exit 1 + done + + security-scan: + name: Security Scanning + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Scan for secrets + run: | + echo "Scanning for exposed secrets..." + + # Simple pattern matching for common secrets + PATTERNS=( + "password\s*=\s*['\"][^'\"]+['\"]" + "api[_-]?key\s*=\s*['\"][^'\"]+['\"]" + "secret\s*=\s*['\"][^'\"]+['\"]" + "token\s*=\s*['\"][^'\"]+['\"]" + "BEGIN RSA PRIVATE KEY" + "BEGIN PRIVATE KEY" + ) + + FOUND=0 + for pattern in "${PATTERNS[@]}"; do + if grep -r -i -E "$pattern" . \ + --exclude-dir=.git \ + --exclude-dir=node_modules \ + --exclude-dir=vendor \ + --exclude="*.md" \ + --exclude="pr-validation.yml"; then + echo "⚠️ Potential secret found matching pattern: $pattern" + FOUND=1 + fi + done + + if [ $FOUND -eq 1 ]; then + echo "" + echo "❌ Potential secrets detected in code" + echo "Please review and remove any hardcoded secrets" + exit 1 + else + echo "✅ No obvious secrets detected" + fi + + - name: Check for TODO/FIXME markers + run: | + echo "Checking for TODO/FIXME markers..." + + if grep -r -n -E "TODO|FIXME" \ + --include="*.sh" \ + --include="*.yaml" \ + --include="*.yml" \ + --exclude-dir=.git \ + --exclude-dir=node_modules > todos.txt; then + echo "ℹ️ Found TODO/FIXME markers:" + cat todos.txt + else + echo "✅ No TODO/FIXME markers found" + fi + + docs-validation: + name: Documentation Validation + needs: changed-files + if: needs.changed-files.outputs.docs_changed == 'true' + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install markdownlint + run: | + sudo npm install -g markdownlint-cli + + - name: Run markdownlint + run: | + # Create basic config + cat > .markdownlint.json </dev/null | while read -r file; do + echo "Checking: $file" + + # Extract markdown links [text](path) + grep -o '\[.*\](.*\.md)' "$file" | grep -o '(.*\.md)' | tr -d '()' | while read -r link; do + # Resolve relative path + link_path=$(dirname "$file")/"$link" + + if [ ! -f "$link_path" ]; then + echo " ⚠️ Broken link: $link in $file" + fi + done + done || true + + pr-size-check: + name: PR Size Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check PR size + run: | + echo "Analyzing PR size..." + + # Count changed files + FILES_CHANGED=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}...HEAD | wc -l) + + # Count lines changed + LINES_CHANGED=$(git diff --shortstat origin/${{ github.event.pull_request.base.ref }}...HEAD | grep -oE '[0-9]+ insertion|[0-9]+ deletion' | grep -oE '[0-9]+' | awk '{s+=$1} END {print s}') + + echo "Files changed: $FILES_CHANGED" + echo "Lines changed: ${LINES_CHANGED:-0}" + + # Warning thresholds + if [ "$FILES_CHANGED" -gt 50 ]; then + echo "⚠️ Large PR: $FILES_CHANGED files changed (consider splitting)" + fi + + if [ "${LINES_CHANGED:-0}" -gt 1000 ]; then + echo "⚠️ Large PR: ${LINES_CHANGED} lines changed (consider splitting)" + fi + + # No failure, just informational + exit 0 + + summary: + name: Validation Summary + runs-on: ubuntu-latest + needs: [pr-info, changed-files, yaml-validation, shell-validation, security-scan, docs-validation, pr-size-check] + if: always() + + steps: + - name: Generate summary + run: | + echo "=============================================" + echo "PR Validation Summary" + echo "=============================================" + echo "PR #${{ github.event.pull_request.number }}: ${{ github.event.pull_request.title }}" + echo "" + + echo "File Changes:" + echo " YAML files: ${{ needs.changed-files.outputs.yaml_changed }}" + echo " Scripts: ${{ needs.changed-files.outputs.scripts_changed }}" + echo " Docs: ${{ needs.changed-files.outputs.docs_changed }}" + echo "" + + echo "Validation Results:" + + # Check YAML validation (if it ran) + if [ "${{ needs.changed-files.outputs.yaml_changed }}" == "true" ]; then + if [ "${{ needs.yaml-validation.result }}" == "success" ]; then + echo " ✅ YAML Validation: PASSED" + else + echo " ❌ YAML Validation: FAILED" + fi + else + echo " ⏭️ YAML Validation: SKIPPED (no changes)" + fi + + # Check shell validation (if it ran) + if [ "${{ needs.changed-files.outputs.scripts_changed }}" == "true" ]; then + if [ "${{ needs.shell-validation.result }}" == "success" ]; then + echo " ✅ Shell Validation: PASSED" + else + echo " ❌ Shell Validation: FAILED" + fi + else + echo " ⏭️ Shell Validation: SKIPPED (no changes)" + fi + + # Security scan (always runs) + if [ "${{ needs.security-scan.result }}" == "success" ]; then + echo " ✅ Security Scan: PASSED" + else + echo " ❌ Security Scan: FAILED" + fi + + # Docs validation (if it ran) + if [ "${{ needs.changed-files.outputs.docs_changed }}" == "true" ]; then + if [ "${{ needs.docs-validation.result }}" == "success" ]; then + echo " ✅ Docs Validation: PASSED" + else + echo " ⚠️ Docs Validation: WARNINGS" + fi + else + echo " ⏭️ Docs Validation: SKIPPED (no changes)" + fi + + echo "" + echo "=============================================" + + # Determine overall status + FAILED=false + + # YAML validation is required if YAML files changed + if [ "${{ needs.changed-files.outputs.yaml_changed }}" == "true" ] && \ + [ "${{ needs.yaml-validation.result }}" != "success" ]; then + FAILED=true + fi + + # Shell validation is required if scripts changed + if [ "${{ needs.changed-files.outputs.scripts_changed }}" == "true" ] && \ + [ "${{ needs.shell-validation.result }}" != "success" ]; then + FAILED=true + fi + + # Security scan is always required + if [ "${{ needs.security-scan.result }}" != "success" ]; then + FAILED=true + fi + + # Docs validation is optional (warning only) + + if [ "$FAILED" == "true" ]; then + echo "❌ PR validation FAILED - please fix issues before merging" + exit 1 + else + echo "✅ PR validation PASSED - ready for review" + exit 0 + fi diff --git a/.github/workflows/shell-script-testing.yml b/.github/workflows/shell-script-testing.yml new file mode 100644 index 0000000..3b1d27f --- /dev/null +++ b/.github/workflows/shell-script-testing.yml @@ -0,0 +1,326 @@ +name: Shell Script Testing + +on: + push: + branches: + - main + - develop + paths: + - '**.sh' + - 'scripts/**' + - '.github/workflows/shell-script-testing.yml' + pull_request: + paths: + - '**.sh' + - 'scripts/**' + - '.github/workflows/shell-script-testing.yml' + +jobs: + shellcheck: + name: ShellCheck Linting + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + with: + severity: warning + ignore_paths: | + node_modules + vendor + .git + env: + SHELLCHECK_OPTS: -e SC1091 -e SC2148 -e SC2034 -e SC2155 + + - name: Detailed ShellCheck Analysis + run: | + echo "Running detailed ShellCheck analysis..." + + # Find all shell scripts + find . -type f -name "*.sh" \ + -not -path "./.git/*" \ + -not -path "./node_modules/*" \ + -not -path "./vendor/*" > shell-scripts.txt + + FAIL_COUNT=0 + WARN_COUNT=0 + TOTAL_COUNT=0 + + while IFS= read -r script; do + echo "" + echo "Checking: $script" + TOTAL_COUNT=$((TOTAL_COUNT + 1)) + + # Run shellcheck and capture output + if shellcheck -f gcc -S warning -e SC1091 -e SC2034 -e SC2155 "$script" > shellcheck-output.txt 2>&1; then + echo " ✅ No issues found" + else + if grep -q "error:" shellcheck-output.txt; then + echo " ❌ Errors found:" + cat shellcheck-output.txt + FAIL_COUNT=$((FAIL_COUNT + 1)) + else + echo " ⚠️ Warnings found:" + cat shellcheck-output.txt + WARN_COUNT=$((WARN_COUNT + 1)) + fi + fi + done < shell-scripts.txt + + echo "" + echo "=============================================" + echo "ShellCheck Summary:" + echo "Total scripts: $TOTAL_COUNT" + echo "Errors: $FAIL_COUNT" + echo "Warnings: $WARN_COUNT" + echo "Clean: $((TOTAL_COUNT - FAIL_COUNT - WARN_COUNT))" + echo "=============================================" + + # Fail if there are errors + if [ $FAIL_COUNT -gt 0 ]; then + echo "❌ Some scripts have errors" + exit 1 + fi + + syntax-check: + name: Bash Syntax Validation + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Validate Bash syntax + run: | + echo "Validating Bash syntax for all scripts..." + + find . -type f -name "*.sh" \ + -not -path "./.git/*" \ + -not -path "./node_modules/*" \ + -not -path "./vendor/*" > shell-scripts.txt + + FAIL_COUNT=0 + TOTAL_COUNT=0 + + while IFS= read -r script; do + echo "" + echo "Syntax check: $script" + TOTAL_COUNT=$((TOTAL_COUNT + 1)) + + if bash -n "$script" 2>&1; then + echo " ✅ Syntax valid" + else + echo " ❌ Syntax error" + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi + done < shell-scripts.txt + + echo "" + echo "=============================================" + echo "Syntax Check Summary:" + echo "Total scripts: $TOTAL_COUNT" + echo "Failed: $FAIL_COUNT" + echo "Passed: $((TOTAL_COUNT - FAIL_COUNT))" + echo "=============================================" + + if [ $FAIL_COUNT -gt 0 ]; then + exit 1 + fi + + script-permissions: + name: Check Script Permissions + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Verify executable permissions + run: | + echo "Checking script permissions..." + + find scripts/ -type f -name "*.sh" 2>/dev/null > scripts-list.txt || echo "No scripts directory found" + + NON_EXEC_COUNT=0 + TOTAL_COUNT=0 + + if [ -s scripts-list.txt ]; then + while IFS= read -r script; do + TOTAL_COUNT=$((TOTAL_COUNT + 1)) + + if [ -x "$script" ]; then + echo "✅ $script - executable" + else + echo "⚠️ $script - NOT executable" + NON_EXEC_COUNT=$((NON_EXEC_COUNT + 1)) + fi + done < scripts-list.txt + + echo "" + echo "=============================================" + echo "Permission Check Summary:" + echo "Total scripts: $TOTAL_COUNT" + echo "Executable: $((TOTAL_COUNT - NON_EXEC_COUNT))" + echo "Non-executable: $NON_EXEC_COUNT" + echo "=============================================" + + if [ $NON_EXEC_COUNT -gt 0 ]; then + echo "" + echo "⚠️ Warning: Some scripts are not executable" + echo "Run: chmod +x " + fi + else + echo "No scripts found in scripts/ directory" + fi + + script-standards: + name: Script Standards Check + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check script standards + run: | + echo "Checking script standards (shebang, set -e, etc.)..." + + find scripts/ -type f -name "*.sh" 2>/dev/null > scripts-list.txt || exit 0 + + if [ ! -s scripts-list.txt ]; then + echo "No scripts found" + exit 0 + fi + + NO_SHEBANG=0 + NO_SET_E=0 + TOTAL_COUNT=0 + + while IFS= read -r script; do + TOTAL_COUNT=$((TOTAL_COUNT + 1)) + echo "" + echo "Checking: $script" + + # Check for shebang + if ! head -n 1 "$script" | grep -q "^#!"; then + echo " ⚠️ Missing shebang (#!/bin/bash)" + NO_SHEBANG=$((NO_SHEBANG + 1)) + else + echo " ✅ Has shebang" + fi + + # Check for set -e or set -euo pipefail + if grep -q "^set -e" "$script" || grep -q "^set -[a-z]*e" "$script"; then + echo " ✅ Has error handling (set -e)" + else + echo " ⚠️ Missing 'set -e' for error handling" + NO_SET_E=$((NO_SET_E + 1)) + fi + + # Check for license header + if head -n 20 "$script" | grep -qi "copyright\|license"; then + echo " ✅ Has license header" + else + echo " ℹ️ No license header found" + fi + + done < scripts-list.txt + + echo "" + echo "=============================================" + echo "Standards Check Summary:" + echo "Total scripts: $TOTAL_COUNT" + echo "Missing shebang: $NO_SHEBANG" + echo "Missing set -e: $NO_SET_E" + echo "=============================================" + + # Don't fail on standards issues, just warn + if [ $NO_SHEBANG -gt 0 ] || [ $NO_SET_E -gt 0 ]; then + echo "" + echo "⚠️ Some scripts don't follow best practices" + echo "Consider adding:" + echo " - Shebang: #!/bin/bash" + echo " - Error handling: set -e" + fi + + unit-tests: + name: Run Script Unit Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install BATS (Bash Automated Testing System) + run: | + sudo apt-get update + sudo apt-get install -y bats + + - name: Check for test files + id: check_tests + run: | + if find . -name "*.bats" -o -name "*test.sh" | grep -q .; then + echo "tests_exist=true" >> $GITHUB_OUTPUT + else + echo "tests_exist=false" >> $GITHUB_OUTPUT + fi + + - name: Run unit tests + if: steps.check_tests.outputs.tests_exist == 'true' + run: | + echo "Running BATS tests..." + find . -name "*.bats" -exec bats {} \; + + - name: No tests found + if: steps.check_tests.outputs.tests_exist == 'false' + run: | + echo "ℹ️ No unit tests found (.bats or *test.sh files)" + echo "Consider adding tests for critical scripts" + + summary: + name: Testing Summary + runs-on: ubuntu-latest + needs: [shellcheck, syntax-check, script-permissions, script-standards, unit-tests] + if: always() + + steps: + - name: Check results + run: | + echo "Shell Script Testing Pipeline Complete" + echo "=======================================" + + # Required checks (must pass) + REQUIRED_PASS=true + if [ "${{ needs.shellcheck.result }}" != "success" ]; then + echo "❌ ShellCheck: FAILED" + REQUIRED_PASS=false + else + echo "✅ ShellCheck: PASSED" + fi + + if [ "${{ needs.syntax-check.result }}" != "success" ]; then + echo "❌ Syntax Check: FAILED" + REQUIRED_PASS=false + else + echo "✅ Syntax Check: PASSED" + fi + + # Optional checks (warnings only) + echo "" + echo "Additional Checks:" + echo " Script Permissions: ${{ needs.script-permissions.result }}" + echo " Script Standards: ${{ needs.script-standards.result }}" + echo " Unit Tests: ${{ needs.unit-tests.result }}" + + if [ "$REQUIRED_PASS" = "false" ]; then + echo "" + echo "❌ Required checks failed" + exit 1 + else + echo "" + echo "✅ All required checks passed" + exit 0 + fi diff --git a/.github/workflows/yaml-validation.yml b/.github/workflows/yaml-validation.yml new file mode 100644 index 0000000..d044860 --- /dev/null +++ b/.github/workflows/yaml-validation.yml @@ -0,0 +1,202 @@ +name: YAML Validation + +on: + push: + branches: + - main + - develop + paths: + - '**.yaml' + - '**.yml' + - '.github/workflows/yaml-validation.yml' + pull_request: + paths: + - '**.yaml' + - '**.yml' + - '.github/workflows/yaml-validation.yml' + +jobs: + yaml-lint: + name: YAML Lint + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Install yamllint + run: | + pip install yamllint + + - name: Run yamllint + run: | + echo "Running yamllint on YAML files..." + + # Run yamllint and capture output + yamllint -f colored . 2>&1 | tee yamllint-results.txt + + # Count errors (not warnings) + ERROR_COUNT=$(grep -c "error" yamllint-results.txt || true) + + echo "" + echo "=============================================" + echo "yamllint Summary:" + echo "Errors found: $ERROR_COUNT" + echo "=============================================" + + # Only fail on actual errors, not warnings + if [ $ERROR_COUNT -gt 0 ]; then + echo "❌ YAML lint errors found" + exit 1 + else + echo "✅ No YAML lint errors" + exit 0 + fi + + kubeval: + name: OpenShift manifest validation + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download kubeval + run: | + wget https://github.com/instrumenta/kubeval/releases/latest/download/kubeval-linux-amd64.tar.gz + tar xf kubeval-linux-amd64.tar.gz + sudo mv kubeval /usr/local/bin + + - name: Validate OpenShift manifests + run: | + echo "Validating OpenShift manifests..." + + # Find all YAML files that look like API resource manifests + find . -type f \( -name "*.yaml" -o -name "*.yml" \) \ + -not -path "./.git/*" \ + -not -path "./.github/*" \ + -not -path "./node_modules/*" \ + -not -path "./vendor/*" \ + -exec grep -l "apiVersion:" {} \; > openshift-manifests-list.txt + + # Validate each manifest + FAIL_COUNT=0 + TOTAL_COUNT=0 + + while IFS= read -r file; do + echo "" + echo "Validating: $file" + TOTAL_COUNT=$((TOTAL_COUNT + 1)) + + # Skip validation for certain known patterns + if grep -q "kind: Kustomization" "$file"; then + echo " ⏭️ Skipping Kustomization file" + continue + fi + + # Run kubeval with OpenShift support + if kubeval --strict --ignore-missing-schemas "$file"; then + echo " ✅ Valid" + else + echo " ❌ Validation failed" + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi + done < openshift-manifests-list.txt + + echo "" + echo "=============================================" + echo "Validation Summary:" + echo "Total manifests checked: $TOTAL_COUNT" + echo "Failed: $FAIL_COUNT" + echo "Passed: $((TOTAL_COUNT - FAIL_COUNT))" + echo "=============================================" + + # Exit with error if any failures + if [ $FAIL_COUNT -gt 0 ]; then + echo "❌ Some manifests failed validation" + exit 1 + else + echo "✅ All manifests passed validation" + exit 0 + fi + + kustomize-build: + name: Kustomize Build Test + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Kustomize + run: | + KUSTOMIZE_VERSION="v5.5.0" + curl -sLO "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz" + tar xzf "kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz" + sudo mv kustomize /usr/local/bin/ + kustomize version + + - name: Find and test Kustomize builds + run: | + echo "Finding kustomization.yaml files..." + + find . -type f -name "kustomization.yaml" -o -name "kustomization.yml" > kustomizations.txt + + FAIL_COUNT=0 + SUCCESS_COUNT=0 + + while IFS= read -r kustomization; do + dir=$(dirname "$kustomization") + echo "" + echo "Testing Kustomize build: $dir" + + if kustomize build "$dir" > /dev/null 2>&1; then + echo " ✅ Build successful" + SUCCESS_COUNT=$((SUCCESS_COUNT + 1)) + else + echo " ❌ Build failed" + kustomize build "$dir" || true + FAIL_COUNT=$((FAIL_COUNT + 1)) + fi + done < kustomizations.txt + + echo "" + echo "=============================================" + echo "Kustomize Build Summary:" + echo "Successful: $SUCCESS_COUNT" + echo "Failed: $FAIL_COUNT" + echo "=============================================" + + if [ $FAIL_COUNT -gt 0 ]; then + exit 1 + fi + + summary: + name: Validation Summary + runs-on: ubuntu-latest + needs: [yaml-lint, kubeval, kustomize-build] + if: always() + + steps: + - name: Check results + run: | + echo "YAML Validation Pipeline Complete" + echo "==================================" + + if [ "${{ needs.yaml-lint.result }}" == "success" ] && \ + [ "${{ needs.kubeval.result }}" == "success" ] && \ + [ "${{ needs.kustomize-build.result }}" == "success" ]; then + echo "✅ All validation checks passed" + exit 0 + else + echo "❌ Some validation checks failed:" + echo " YAML Lint: ${{ needs.yaml-lint.result }}" + echo " Kubeval: ${{ needs.kubeval.result }}" + echo " Kustomize Build: ${{ needs.kustomize-build.result }}" + exit 1 + fi diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..cff8ce2 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,10 @@ +{ + "default": true, + "MD003": { "style": "atx" }, + "MD007": { "indent": 2 }, + "MD013": false, + "MD024": { "siblings_only": true }, + "MD033": false, + "MD041": false, + "MD046": { "style": "fenced" } +} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..474428a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,135 @@ +# Pre-commit hooks for EDB_Testing repository +# Install: pip install pre-commit +# Setup: pre-commit install +# Run manually: pre-commit run --all-files + +repos: + # General file checks + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + name: Trim trailing whitespace + args: [--markdown-linebreak-ext=md] + + - id: end-of-file-fixer + name: Fix end of files + exclude: ^\.git/ + + - id: check-yaml + name: Check YAML syntax + args: [--allow-multiple-documents] + exclude: ^\.github/ + + - id: check-added-large-files + name: Check for large files + args: [--maxkb=1024] + + - id: check-merge-conflict + name: Check for merge conflicts + + - id: check-case-conflict + name: Check for case conflicts + + - id: mixed-line-ending + name: Check line endings + args: [--fix=lf] + + - id: detect-private-key + name: Detect private keys + + # Shell script validation + - repo: https://github.com/shellcheck-py/shellcheck-py + rev: v0.9.0.6 + hooks: + - id: shellcheck + name: ShellCheck + args: + - --severity=warning + - --exclude=SC1091 # Not following sourced files + - --exclude=SC2148 # Shebang hints + files: \.sh$ + + # YAML linting + - repo: https://github.com/adrienverge/yamllint + rev: v1.33.0 + hooks: + - id: yamllint + name: Lint YAML files + args: + - --strict + - --config-data + - | + extends: default + rules: + line-length: + max: 120 + level: warning + indentation: + spaces: 2 + comments: + min-spaces-from-content: 1 + document-start: disable + truthy: + allowed-values: ['true', 'false', 'on', 'off'] + files: \.(yaml|yml)$ + exclude: ^\.github/ + + # Markdown linting + - repo: https://github.com/igorshubovych/markdownlint-cli + rev: v0.37.0 + hooks: + - id: markdownlint + name: Lint Markdown files + args: + - --config + - .markdownlint.json + files: \.md$ + + # Secret scanning + - repo: https://github.com/Yelp/detect-secrets + rev: v1.4.0 + hooks: + - id: detect-secrets + name: Detect secrets + args: + - --baseline + - .secrets.baseline + exclude: ^\.git/ + + # Custom local hooks + - repo: local + hooks: + - id: bash-syntax + name: Bash syntax check + entry: bash -n + language: system + files: \.sh$ + + - id: openshift-manifest-validate + name: Validate OpenShift manifests + entry: scripts/hooks/validate-openshift-manifests.sh + language: script + files: \.(yaml|yml)$ + pass_filenames: true + require_serial: true + + - id: script-executable + name: Ensure scripts are executable + entry: scripts/hooks/check-script-permissions.sh + language: script + files: ^scripts/.*\.sh$ + pass_filenames: true + + - id: no-tabs + name: Check for tabs in YAML/scripts + entry: grep -n $'\t' + language: system + files: \.(yaml|yml|sh)$ + exclude: ^Makefile + +# Global settings +default_language_version: + python: python3.11 + +fail_fast: false diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 0000000..c53e156 --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,71 @@ +{ + "version": "1.4.0", + "plugins_used": [ + { + "name": "ArtifactoryDetector" + }, + { + "name": "AWSKeyDetector" + }, + { + "name": "Base64HighEntropyString", + "limit": 4.5 + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "CloudantDetector" + }, + { + "name": "HexHighEntropyString", + "limit": 3.0 + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "name": "KeywordDetector", + "keyword_exclude": "" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "filters_used": [ + { + "path": "detect_secrets.filters.allowlist.is_line_allowlisted" + }, + { + "path": "detect_secrets.filters.common.is_baseline_file", + "filename": ".secrets.baseline" + }, + { + "path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies", + "min_level": 2 + } + ], + "results": {}, + "generated_at": "2026-03-30T00:00:00Z" +} diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..10d6104 --- /dev/null +++ b/.yamllint @@ -0,0 +1,20 @@ +--- +extends: default + +rules: + line-length: + max: 160 + level: warning + indentation: + spaces: 2 + comments: + min-spaces-from-content: 1 + comments-indentation: {} + document-start: disable + truthy: + allowed-values: ['true', 'false', 'on', 'off'] + +ignore: | + .github/ + node_modules/ + vendor/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..071b8a0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,621 @@ +# Contributing to EDB_Testing + +Thank you for your interest in contributing to the AAP with EnterpriseDB PostgreSQL Multi-Datacenter project! + +## Table of Contents + +- [Getting Started](#getting-started) +- [Documentation Standards](#documentation-standards) +- [Code Standards](#code-standards) +- [Testing Requirements](#testing-requirements) +- [Pull Request Process](#pull-request-process) +- [Commit Message Guidelines](#commit-message-guidelines) +- [Development Workflow](#development-workflow) + +--- + +## Getting Started + +### Prerequisites + +Before contributing, ensure you have: + +- Git configured on your machine +- Python 3.11+ (for pre-commit hooks) +- Access to an OpenShift cluster (for testing manifests) +- Basic understanding of PostgreSQL, Ansible Automation Platform, and Kubernetes + +### Initial Setup + +1. **Fork and clone the repository:** + ```bash + git clone https://github.com/your-username/EDB_Testing.git + cd EDB_Testing + ``` + +2. **Install pre-commit hooks:** + ```bash + pip install pre-commit + pre-commit install + ``` + +3. **Review the documentation:** + - [Documentation Index](docs/INDEX.md) - Complete documentation navigation + - [Architecture](README.md#architecture) - System overview + - [CI/CD Pipeline](docs/cicd-pipeline.md) - Automated testing workflows + +--- + +## Documentation Standards + +### File Naming + +- Use lowercase with hyphens: `my-document.md` +- Place in appropriate directory: + - `/docs/` for general documentation + - `/aap-deploy/` for AAP deployment docs + - `/db-deploy/` for database deployment docs + - `/scripts/` for script documentation + +### Formatting + +**Headings:** +- Use `#` for title (one per document) +- Use `##` for major sections +- Use `###` for subsections +- Maximum depth: `####` (avoid deeper nesting) + +**Code Blocks:** +```markdown +```bash +# Use language tags for syntax highlighting +kubectl get pods -n edb-postgres +\``` +``` + +**Preferred language tags:** +- `bash` - Shell commands +- `yaml` - Kubernetes manifests +- `sql` - Database queries +- `python` - Python scripts +- `json` - JSON data + +**Cross-References:** +- Use relative paths: `[Link Text](../path/to/file.md)` +- Never use absolute paths: ~~`[Link](/Users/...)`~~ +- Link to sections: `[Section](#section-name)` + +**Consistency:** +- PostgreSQL (not "Postgres" or "postgres") +- OpenShift (not "OCP" except in context) +- Ansible Automation Platform (AAP) - use abbreviation after first mention +- Datacenter (one word, not "data center") +- DC1 / DC2 (datacenter naming) + +### Table of Contents + +Add TOC to documents > 200 lines: + +```markdown +## Table of Contents + +- [Section 1](#section-1) +- [Section 2](#section-2) +``` + +### Documentation Checklist + +- [ ] File named with lowercase and hyphens +- [ ] TOC included (if > 200 lines) +- [ ] Cross-references use relative paths +- [ ] Code blocks have language tags +- [ ] Terminology consistent (PostgreSQL, AAP, etc.) +- [ ] No absolute file paths in examples +- [ ] Tested commands work as documented +- [ ] Updated [INDEX.md](docs/INDEX.md) if adding new documentation + +--- + +## Code Standards + +### Shell Scripts + +**Requirements:** +- Shebang: `#!/bin/bash` +- Copyright header (see existing scripts) +- Set error handling: `set -e` +- Executable permissions: `chmod +x script.sh` + +**Style:** +- Use descriptive variable names: `DB_NAMESPACE` not `ns` +- Quote variables: `"$VAR"` not `$VAR` +- Use functions for repeated logic +- Add usage/help message +- Comment complex sections + +**Example:** +```bash +#!/bin/bash +# Copyright 2026 EnterpriseDB Corporation +# +# Description: Brief description of script purpose +# +# Usage: ./script-name.sh + +set -e + +# Configuration +DB_NAMESPACE="${1:-edb-postgres}" + +# Function: Check prerequisites +check_prerequisites() { + if ! command -v oc &> /dev/null; then + echo "❌ Error: oc command not found" + exit 1 + fi +} + +# Main execution +main() { + check_prerequisites + echo "✓ Prerequisites validated" +} + +main "$@" +``` + +**Validation:** +- Must pass ShellCheck (SC2148, SC1091 excluded) +- Syntax validated: `bash -n script.sh` +- Executable: `test -x script.sh` + +### YAML Manifests + +**Requirements:** +- Indentation: 2 spaces (no tabs) +- Line length: ≤ 120 characters +- Valid Kubernetes schema (kubeval) +- Kustomize buildable (if in Kustomize directory) + +**Style:** +- Consistent resource naming: `kebab-case` +- Namespace specified unless default intended +- Labels for resource organization +- Comments for non-obvious configuration + +**Example:** +```yaml +apiVersion: v1 +kind: Service +metadata: + name: postgresql-rw + namespace: edb-postgres + labels: + app: postgresql + role: primary +spec: + type: ClusterIP + selector: + cnpg.io/cluster: postgresql + role: primary + ports: + - port: 5432 + targetPort: 5432 + name: postgres +``` + +**Validation:** +- yamllint passes (see [.yamllint](.yamllint) config) +- kubeval validates schema +- kustomize build succeeds (if applicable) + +--- + +## Testing Requirements + +### Pre-Commit Validation + +All contributions must pass pre-commit hooks: + +```bash +pre-commit run --all-files +``` + +**Hooks include:** +- Trailing whitespace removal +- YAML syntax validation +- Shell script checking (ShellCheck) +- Markdown linting +- Secret detection +- Kubernetes manifest validation + +### Script Testing + +**For new or modified scripts:** + +1. **Syntax validation:** + ```bash + bash -n scripts/your-script.sh + ``` + +2. **ShellCheck:** + ```bash + shellcheck scripts/your-script.sh + ``` + +3. **Functional testing:** + - Test on local OpenShift (CRC) if possible + - Document test results in PR description + - Include example output + +4. **Cross-platform compatibility:** + - Test on Linux (RHEL 9 preferred) + - Test on macOS (if applicable) + - Use Python fallbacks for OS-specific commands (e.g., `date`) + +### Kubernetes Manifest Testing + +**For new or modified manifests:** + +1. **Schema validation:** + ```bash + kubeval --strict manifest.yaml + ``` + +2. **Kustomize build:** + ```bash + cd db-deploy/sample-cluster + kustomize build base/ + ``` + +3. **Deployment validation:** + - Test on development cluster + - Verify resources created + - Check pod status + - Validate functionality + +### Documentation Testing + +**For new or modified documentation:** + +1. **Link validation:** + - All cross-references work + - External links accessible + - No broken anchors + +2. **Command validation:** + - Commands execute successfully + - Output matches documentation + - Examples copyable (no special characters) + +3. **Readability:** + - Technical accuracy verified + - Grammar and spelling checked + - Clear and concise + +--- + +## Pull Request Process + +### Before Submitting + +- [ ] Code/docs tested locally +- [ ] Pre-commit hooks pass +- [ ] Commits follow message guidelines +- [ ] PR description complete +- [ ] Related issue referenced (if applicable) + +### PR Title Format + +``` +: + +Examples: +feat: Add PITR restore script +fix: Correct split-brain prevention logic +docs: Update DR testing guide +refactor: Simplify AAP scaling logic +test: Add component tests for measure-rto-rpo.sh +``` + +**Types:** +- `feat` - New feature or capability +- `fix` - Bug fix +- `docs` - Documentation changes +- `refactor` - Code refactoring (no functionality change) +- `test` - Adding or updating tests +- `chore` - Maintenance tasks (dependencies, CI/CD) + +### PR Description Template + +```markdown +## Description +Brief description of changes and motivation. + +## Changes Made +- Bullet list of specific changes +- Include file paths for major changes +- Note any breaking changes + +## Testing +How was this tested? +- [ ] Local testing on CRC +- [ ] Integration testing on dev cluster +- [ ] Pre-commit hooks passed +- [ ] CI/CD pipeline green + +## Related Issues +Closes #123 +Relates to #456 + +## Checklist +- [ ] Documentation updated +- [ ] Tests pass +- [ ] No breaking changes (or documented) +- [ ] INDEX.md updated (if new docs) +``` + +### Review Process + +1. **Automated Checks:** + - GitHub Actions workflows must pass + - All CI/CD checks green + +2. **Manual Review:** + - Minimum 1 approval required + - Focus areas: + - Code quality and standards + - Security implications + - Documentation accuracy + - Breaking changes + +3. **Merge:** + - Squash and merge (default) + - Rebase for feature branches (if requested) + - Delete branch after merge + +--- + +## Commit Message Guidelines + +### Format + +``` +(): + + + +