All checks were successful
CI / skip-ci-check (pull_request) Successful in 1m18s
CI / lint-and-test (pull_request) Successful in 1m23s
CI / ansible-validation (pull_request) Successful in 3m2s
CI / secret-scanning (pull_request) Successful in 1m20s
CI / dependency-scan (pull_request) Successful in 1m24s
CI / sast-scan (pull_request) Successful in 2m39s
CI / license-check (pull_request) Successful in 1m24s
CI / vault-check (pull_request) Successful in 2m19s
CI / playbook-test (pull_request) Successful in 2m25s
CI / container-scan (pull_request) Successful in 1m50s
CI / sonar-analysis (pull_request) Successful in 2m33s
CI / workflow-summary (pull_request) Successful in 1m17s
- Whitelist 0BSD license (for tslib dependency) - Fix roles_path to use absolute path: /workspace/ilia/ansible/roles (relative 'roles' was searching in wrong directories) - Add Node.js install step before checkout in sonar-analysis job (actions/checkout@v4 requires node runtime) All make test and npm test checks pass locally
662 lines
24 KiB
YAML
662 lines
24 KiB
YAML
---
|
|
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [master]
|
|
pull_request:
|
|
types: [opened, synchronize, reopened]
|
|
|
|
jobs:
|
|
# Check if CI should be skipped based on branch name or commit message
|
|
# Simple skip pattern: @skipci (case-insensitive)
|
|
skip-ci-check:
|
|
runs-on: ubuntu-latest
|
|
outputs:
|
|
should-skip: ${{ steps.check.outputs.skip }}
|
|
steps:
|
|
- name: Check out code (for commit message)
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 1
|
|
|
|
- name: Check if CI should be skipped
|
|
id: check
|
|
run: |
|
|
# Simple skip pattern: @skipci (case-insensitive)
|
|
# Works in branch names and commit messages
|
|
SKIP_PATTERN="@skipci"
|
|
|
|
# Get branch name (works for both push and PR)
|
|
BRANCH_NAME="${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}"
|
|
|
|
# Get commit message (works for both push and PR)
|
|
COMMIT_MSG="${GITHUB_EVENT_HEAD_COMMIT_MESSAGE:-}"
|
|
if [ -z "$COMMIT_MSG" ]; then
|
|
COMMIT_MSG="${GITHUB_EVENT_PULL_REQUEST_HEAD_COMMIT_MESSAGE:-}"
|
|
fi
|
|
if [ -z "$COMMIT_MSG" ]; then
|
|
COMMIT_MSG=$(git log -1 --pretty=%B 2>/dev/null || echo "")
|
|
fi
|
|
|
|
SKIP=0
|
|
|
|
# Check branch name (case-insensitive)
|
|
if echo "$BRANCH_NAME" | grep -qiF "$SKIP_PATTERN"; then
|
|
echo "Skipping CI: branch name contains '$SKIP_PATTERN'"
|
|
SKIP=1
|
|
fi
|
|
|
|
# Check commit message (case-insensitive)
|
|
if [ $SKIP -eq 0 ] && [ -n "$COMMIT_MSG" ]; then
|
|
if echo "$COMMIT_MSG" | grep -qiF "$SKIP_PATTERN"; then
|
|
echo "Skipping CI: commit message contains '$SKIP_PATTERN'"
|
|
SKIP=1
|
|
fi
|
|
fi
|
|
|
|
echo "skip=$SKIP" >> $GITHUB_OUTPUT
|
|
echo "Branch: $BRANCH_NAME"
|
|
echo "Commit: ${COMMIT_MSG:0:50}..."
|
|
echo "Skip CI: $SKIP"
|
|
|
|
lint-and-test:
|
|
needs: skip-ci-check
|
|
runs-on: ubuntu-latest
|
|
if: needs.skip-ci-check.outputs.should-skip != '1' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/master')
|
|
container:
|
|
image: node:20-bullseye
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Lint markdown
|
|
run: npm run test:markdown
|
|
|
|
- name: Check markdown links
|
|
run: npm run test:links
|
|
continue-on-error: true
|
|
|
|
ansible-validation:
|
|
needs: skip-ci-check
|
|
runs-on: ubuntu-latest
|
|
if: needs.skip-ci-check.outputs.should-skip != '1' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/master')
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl git
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Configure CI Ansible (no vault, localhost inventory)
|
|
run: |
|
|
set -e
|
|
cat > /tmp/ci-inventory.ini <<'EOF'
|
|
[all]
|
|
localhost ansible_connection=local
|
|
EOF
|
|
|
|
cat > /tmp/ci-ansible.cfg <<'EOF'
|
|
[defaults]
|
|
inventory = /tmp/ci-inventory.ini
|
|
roles_path = /workspace/ilia/ansible/roles
|
|
host_key_checking = False
|
|
stdout_callback = yaml
|
|
bin_ansible_callbacks = True
|
|
retry_files_enabled = False
|
|
interpreter_python = auto_silent
|
|
forks = 10
|
|
pipelining = True
|
|
EOF
|
|
|
|
echo "ANSIBLE_CONFIG=/tmp/ci-ansible.cfg" >> "$GITHUB_ENV"
|
|
echo "ANSIBLE_INVENTORY=/tmp/ci-inventory.ini" >> "$GITHUB_ENV"
|
|
|
|
- name: Install Python and dependencies
|
|
run: |
|
|
apt-get update && apt-get install -y python3 python3-pip
|
|
|
|
- name: Install Ansible and linting tools
|
|
run: pip3 install --no-cache-dir ansible ansible-lint yamllint
|
|
|
|
- name: Install Ansible collections
|
|
run: |
|
|
ansible-galaxy collection install -r collections/requirements.yml
|
|
|
|
- name: Validate YAML syntax
|
|
run: |
|
|
echo "Checking YAML syntax..."
|
|
find . -name "*.yml" -o -name "*.yaml" | grep -v ".git" | while read file; do
|
|
python3 -c "import yaml; yaml.safe_load(open('$file'))" || exit 1
|
|
done
|
|
|
|
- name: Run ansible-lint
|
|
run: ansible-lint
|
|
|
|
secret-scanning:
|
|
needs: skip-ci-check
|
|
if: needs.skip-ci-check.outputs.should-skip != '1'
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: zricethezav/gitleaks:latest
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apk add --no-cache nodejs npm curl
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Scan for secrets
|
|
run: gitleaks detect --source . --no-banner --redact --exit-code 0
|
|
continue-on-error: true
|
|
|
|
dependency-scan:
|
|
needs: skip-ci-check
|
|
if: needs.skip-ci-check.outputs.should-skip != '1'
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: aquasec/trivy:latest
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apk add --no-cache nodejs npm curl
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Show dependency manifests (debug)
|
|
run: |
|
|
set -e
|
|
echo "Repo root:"
|
|
ls -la
|
|
echo ""
|
|
echo "Common dependency manifests:"
|
|
ls -la package.json package-lock.json requirements.txt pyproject.toml poetry.lock Pipfile Pipfile.lock 2>/dev/null || true
|
|
echo ""
|
|
echo "Count of lock/manifests found:"
|
|
find . -maxdepth 3 -type f \( \
|
|
-name "package-lock.json" -o \
|
|
-name "pnpm-lock.yaml" -o \
|
|
-name "yarn.lock" -o \
|
|
-name "requirements.txt" -o \
|
|
-name "pyproject.toml" -o \
|
|
-name "poetry.lock" -o \
|
|
-name "Pipfile.lock" \
|
|
\) | wc -l
|
|
|
|
- name: Dependency vulnerability scan (Trivy)
|
|
run: |
|
|
trivy fs \
|
|
--scanners vuln \
|
|
--severity HIGH,CRITICAL \
|
|
--ignore-unfixed \
|
|
--timeout 10m \
|
|
--skip-dirs .git,node_modules \
|
|
--exit-code 0 \
|
|
.
|
|
|
|
- name: Secret scan (Trivy)
|
|
run: |
|
|
trivy fs \
|
|
--scanners secret \
|
|
--timeout 10m \
|
|
--skip-dirs .git,node_modules \
|
|
--exit-code 0 \
|
|
.
|
|
|
|
sast-scan:
|
|
needs: skip-ci-check
|
|
if: needs.skip-ci-check.outputs.should-skip != '1'
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Semgrep
|
|
run: |
|
|
apt-get update && apt-get install -y python3 python3-pip
|
|
pip3 install semgrep
|
|
|
|
- name: Run Semgrep scan
|
|
run: semgrep --config=auto --error
|
|
continue-on-error: true
|
|
|
|
license-check:
|
|
needs: skip-ci-check
|
|
if: needs.skip-ci-check.outputs.should-skip != '1'
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: node:20-bullseye
|
|
steps:
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install license-checker
|
|
run: npm install -g license-checker
|
|
|
|
- name: Check npm licenses
|
|
run: |
|
|
if [ -f "package.json" ]; then
|
|
npm ci
|
|
# Exclude the repo itself (private=true packages are treated as UNLICENSED by license-checker).
|
|
license-checker --excludePrivatePackages --onlyAllow 'MIT;Apache-2.0;BSD-3-Clause;ISC;BSD-2-Clause;Python-2.0;BlueOak-1.0.0;0BSD'
|
|
else
|
|
echo "No package.json found, skipping license check"
|
|
fi
|
|
|
|
vault-check:
|
|
needs: skip-ci-check
|
|
if: needs.skip-ci-check.outputs.should-skip != '1'
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Python and dependencies
|
|
run: |
|
|
apt-get update && apt-get install -y python3 python3-pip
|
|
|
|
- name: Install Ansible
|
|
run: pip3 install --no-cache-dir ansible
|
|
|
|
- name: Validate vault files are encrypted
|
|
run: |
|
|
echo "Checking for Ansible Vault files..."
|
|
# Intentionally skip *.example files: they are plaintext templates.
|
|
# Only treat conventional vault files as "must be encrypted":
|
|
# - vault.yml / vault.yaml
|
|
# - vault_*.yml / vault_*.yaml
|
|
# Avoid false-positives like host_vars/vaultwardenVM.yml (host name contains "vault").
|
|
vault_files=$(find . \( -name "vault.yml" -o -name "vault.yaml" -o -name "vault_*.yml" -o -name "vault_*.yaml" \) | grep -v ".git" | grep -v ".example" || true)
|
|
if [ -z "$vault_files" ]; then
|
|
echo "No vault files found"
|
|
exit 0
|
|
fi
|
|
failed=0
|
|
for vault_file in $vault_files; do
|
|
echo "Checking $vault_file..."
|
|
# Check if file starts with ANSIBLE_VAULT header (doesn't require password)
|
|
# Some vault files may start with '---' (YAML document start) on line 1.
|
|
if head -n 5 "$vault_file" | grep -q "^\$ANSIBLE_VAULT"; then
|
|
echo "✓ $vault_file is properly encrypted (has vault header)"
|
|
else
|
|
echo "✗ ERROR: $vault_file does not have ANSIBLE_VAULT header - may be unencrypted!"
|
|
failed=1
|
|
fi
|
|
done
|
|
if [ $failed -eq 1 ]; then
|
|
echo "Some vault files are not encrypted. Please encrypt them with: ansible-vault encrypt <file>"
|
|
exit 1
|
|
fi
|
|
echo "All vault files are properly encrypted!"
|
|
|
|
playbook-test:
|
|
needs: skip-ci-check
|
|
if: needs.skip-ci-check.outputs.should-skip != '1'
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Configure CI Ansible (no vault, localhost inventory)
|
|
run: |
|
|
set -e
|
|
cat > /tmp/ci-inventory.ini <<'EOF'
|
|
[dev]
|
|
localhost ansible_connection=local
|
|
|
|
[desktop]
|
|
localhost ansible_connection=local
|
|
|
|
[services]
|
|
localhost ansible_connection=local
|
|
|
|
[qa]
|
|
localhost ansible_connection=local
|
|
|
|
[ansible]
|
|
localhost ansible_connection=local
|
|
|
|
[tailscale]
|
|
localhost ansible_connection=local
|
|
|
|
[local]
|
|
localhost ansible_connection=local
|
|
EOF
|
|
|
|
cat > /tmp/ci-ansible.cfg <<'EOF'
|
|
[defaults]
|
|
inventory = /tmp/ci-inventory.ini
|
|
roles_path = /workspace/ilia/ansible/roles
|
|
host_key_checking = False
|
|
stdout_callback = yaml
|
|
bin_ansible_callbacks = True
|
|
retry_files_enabled = False
|
|
interpreter_python = auto_silent
|
|
forks = 10
|
|
pipelining = True
|
|
EOF
|
|
|
|
echo "ANSIBLE_CONFIG=/tmp/ci-ansible.cfg" >> "$GITHUB_ENV"
|
|
echo "ANSIBLE_INVENTORY=/tmp/ci-inventory.ini" >> "$GITHUB_ENV"
|
|
|
|
- name: Install Python and dependencies
|
|
run: |
|
|
apt-get update && apt-get install -y python3 python3-pip
|
|
|
|
- name: Install Ansible
|
|
run: pip3 install --no-cache-dir ansible
|
|
|
|
- name: Install Ansible collections
|
|
run: |
|
|
ansible-galaxy collection install -r collections/requirements.yml
|
|
|
|
- name: Validate playbooks (CI inventory, no vault)
|
|
run: |
|
|
set -e
|
|
echo "Validating playbooks against a CI-only localhost inventory (no vault required)..."
|
|
failed=0
|
|
for playbook in playbooks/*.yml site.yml configure_app.yml provision_vms.yml; do
|
|
[ -f "$playbook" ] || continue
|
|
echo "Testing $playbook..."
|
|
if ansible-playbook -i /tmp/ci-inventory.ini "$playbook" --syntax-check --list-tasks; then
|
|
echo "✓ $playbook validated (syntax-check + list-tasks)"
|
|
else
|
|
echo "✗ $playbook failed validation (syntax-check/list-tasks)"
|
|
failed=1
|
|
fi
|
|
done
|
|
|
|
if [ $failed -eq 1 ]; then
|
|
echo "❌ Some playbooks failed CI validation."
|
|
echo "This should not require production inventory or vault secrets."
|
|
exit 1
|
|
else
|
|
echo "✅ All playbooks passed CI validation"
|
|
fi
|
|
|
|
container-scan:
|
|
needs: skip-ci-check
|
|
if: needs.skip-ci-check.outputs.should-skip != '1'
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Trivy
|
|
run: |
|
|
set -e
|
|
apt-get update && apt-get install -y wget curl tar
|
|
|
|
# Use a fixed, known-good Trivy version to avoid URL/redirect issues
|
|
TRIVY_VERSION="0.58.2"
|
|
TRIVY_URL="https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz"
|
|
|
|
echo "Installing Trivy version: ${TRIVY_VERSION}"
|
|
echo "Downloading from: ${TRIVY_URL}"
|
|
|
|
if ! wget --progress=bar:force "${TRIVY_URL}" -O /tmp/trivy.tar.gz 2>&1; then
|
|
echo "❌ Failed to download Trivy archive"
|
|
echo "Checking if file was partially downloaded:"
|
|
ls -lh /tmp/trivy.tar.gz 2>/dev/null || echo "No file found"
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f /tmp/trivy.tar.gz ] || [ ! -s /tmp/trivy.tar.gz ]; then
|
|
echo "❌ Downloaded Trivy archive is missing or empty"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Download complete. File size: $(du -h /tmp/trivy.tar.gz | cut -f1)"
|
|
echo "Extracting Trivy..."
|
|
if ! tar -xzf /tmp/trivy.tar.gz -C /tmp/ trivy; then
|
|
echo "❌ Failed to extract Trivy binary from archive"
|
|
tar -tzf /tmp/trivy.tar.gz 2>&1 | head -20 || true
|
|
exit 1
|
|
fi
|
|
|
|
if [ ! -f /tmp/trivy ]; then
|
|
echo "❌ Trivy binary not found after extraction"
|
|
ls -la /tmp/ | grep trivy || ls -la /tmp/ | head -20
|
|
exit 1
|
|
fi
|
|
|
|
mv /tmp/trivy /usr/local/bin/trivy
|
|
chmod +x /usr/local/bin/trivy
|
|
/usr/local/bin/trivy --version
|
|
trivy --version
|
|
|
|
- name: Scan for Dockerfiles and container configs
|
|
run: |
|
|
if [ -f "Dockerfile" ] || [ -f "docker-compose.yml" ] || find . -name "Dockerfile*" -o -name "*.dockerfile" 2>/dev/null | grep -v ".git" | head -1 > /dev/null; then
|
|
echo "Dockerfiles found. Scanning filesystem for container-related vulnerabilities..."
|
|
echo "Note: This scans filesystem, not built images."
|
|
echo "To scan actual images, build them first and use: trivy image <image:tag>"
|
|
trivy fs --scanners vuln --severity HIGH,CRITICAL --format table . || true
|
|
else
|
|
echo "No Dockerfiles found, skipping container image scan"
|
|
exit 0
|
|
fi
|
|
continue-on-error: true
|
|
|
|
sonar-analysis:
|
|
needs: skip-ci-check
|
|
if: needs.skip-ci-check.outputs.should-skip != '1' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/master')
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ubuntu:22.04
|
|
env:
|
|
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
steps:
|
|
- name: Install Node.js for checkout action
|
|
run: |
|
|
apt-get update && apt-get install -y curl
|
|
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
|
|
apt-get install -y nodejs
|
|
|
|
- name: Check out code
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Install Java and SonarScanner
|
|
run: |
|
|
set -e
|
|
apt-get update && apt-get install -y wget curl unzip openjdk-21-jre
|
|
|
|
# Use a known working version to avoid download issues
|
|
SONAR_SCANNER_VERSION="5.0.1.3006"
|
|
SCANNER_URL="https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux.zip"
|
|
|
|
echo "Installing SonarScanner version: ${SONAR_SCANNER_VERSION}"
|
|
echo "Downloading from: ${SCANNER_URL}"
|
|
|
|
# Download with verbose error output
|
|
if ! wget --progress=bar:force "${SCANNER_URL}" -O /tmp/sonar-scanner.zip 2>&1; then
|
|
echo "❌ Failed to download SonarScanner"
|
|
echo "Checking if file was partially downloaded:"
|
|
ls -lh /tmp/sonar-scanner.zip 2>/dev/null || echo "No file found"
|
|
exit 1
|
|
fi
|
|
|
|
# Verify download
|
|
if [ ! -f /tmp/sonar-scanner.zip ] || [ ! -s /tmp/sonar-scanner.zip ]; then
|
|
echo "❌ Downloaded file is missing or empty"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Download complete. File size: $(du -h /tmp/sonar-scanner.zip | cut -f1)"
|
|
|
|
echo "Extracting SonarScanner..."
|
|
if ! unzip -q /tmp/sonar-scanner.zip -d /tmp; then
|
|
echo "❌ Failed to extract SonarScanner"
|
|
echo "Archive info:"
|
|
file /tmp/sonar-scanner.zip || true
|
|
unzip -l /tmp/sonar-scanner.zip 2>&1 | head -20 || true
|
|
exit 1
|
|
fi
|
|
|
|
# Find the extracted directory (handle both naming conventions)
|
|
EXTRACTED_DIR=""
|
|
if [ -d "/tmp/sonar-scanner-${SONAR_SCANNER_VERSION}-linux" ]; then
|
|
EXTRACTED_DIR="/tmp/sonar-scanner-${SONAR_SCANNER_VERSION}-linux"
|
|
elif [ -d "/tmp/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux" ]; then
|
|
EXTRACTED_DIR="/tmp/sonar-scanner-cli-${SONAR_SCANNER_VERSION}-linux"
|
|
else
|
|
# Try to find any sonar-scanner directory
|
|
EXTRACTED_DIR=$(find /tmp -maxdepth 1 -type d -name "*sonar-scanner*" | head -1)
|
|
fi
|
|
|
|
if [ -z "$EXTRACTED_DIR" ] || [ ! -d "$EXTRACTED_DIR" ]; then
|
|
echo "❌ SonarScanner directory not found after extraction"
|
|
echo "Contents of /tmp:"
|
|
ls -la /tmp/ | grep -E "(sonar|zip)" || ls -la /tmp/ | head -20
|
|
exit 1
|
|
fi
|
|
|
|
echo "Found extracted directory: ${EXTRACTED_DIR}"
|
|
mv "${EXTRACTED_DIR}" /opt/sonar-scanner
|
|
|
|
# Create symlink
|
|
if [ -f /opt/sonar-scanner/bin/sonar-scanner ]; then
|
|
ln -sf /opt/sonar-scanner/bin/sonar-scanner /usr/local/bin/sonar-scanner
|
|
chmod +x /opt/sonar-scanner/bin/sonar-scanner
|
|
chmod +x /usr/local/bin/sonar-scanner
|
|
else
|
|
echo "❌ sonar-scanner binary not found in /opt/sonar-scanner/bin/"
|
|
echo "Contents of /opt/sonar-scanner/bin/:"
|
|
ls -la /opt/sonar-scanner/bin/ || true
|
|
exit 1
|
|
fi
|
|
|
|
echo "Verifying installation..."
|
|
if ! sonar-scanner --version; then
|
|
echo "❌ SonarScanner verification failed"
|
|
echo "PATH: $PATH"
|
|
which sonar-scanner || echo "sonar-scanner not in PATH"
|
|
exit 1
|
|
fi
|
|
echo "✓ SonarScanner installed successfully"
|
|
|
|
- name: Verify SonarQube connection
|
|
run: |
|
|
echo "Checking SonarQube connectivity..."
|
|
if [ -z "$SONAR_HOST_URL" ] || [ -z "$SONAR_TOKEN" ]; then
|
|
echo "⚠️ Skipping SonarQube analysis: SONAR_HOST_URL or SONAR_TOKEN secrets are not set."
|
|
exit 0
|
|
fi
|
|
echo "✓ Secrets are configured"
|
|
echo "SonarQube URL: ${SONAR_HOST_URL}"
|
|
echo "Testing connectivity to SonarQube server..."
|
|
if curl -f -s -o /dev/null -w "%{http_code}" "${SONAR_HOST_URL}/api/system/status" | grep -q "200"; then
|
|
echo "✓ SonarQube server is reachable"
|
|
else
|
|
echo "⚠️ Warning: Could not verify SonarQube server connectivity (continuing anyway)"
|
|
fi
|
|
|
|
- name: Run SonarScanner
|
|
run: |
|
|
echo "Starting SonarQube analysis..."
|
|
if [ -z "$SONAR_HOST_URL" ] || [ -z "$SONAR_TOKEN" ]; then
|
|
echo "Skipping SonarQube analysis: secrets not set."
|
|
exit 0
|
|
fi
|
|
|
|
if ! sonar-scanner \
|
|
-Dsonar.projectKey=ansible \
|
|
-Dsonar.sources=. \
|
|
-Dsonar.host.url=${SONAR_HOST_URL} \
|
|
-Dsonar.token=${SONAR_TOKEN} \
|
|
-Dsonar.scm.disabled=true \
|
|
-Dsonar.python.version=3.10 \
|
|
-X; then
|
|
echo ""
|
|
echo "❌ SonarScanner analysis failed!"
|
|
echo ""
|
|
echo "Common issues:"
|
|
echo " 1. Project 'ansible' doesn't exist in SonarQube"
|
|
echo " → Create it manually in SonarQube UI"
|
|
echo " 2. Token doesn't have permission to analyze/create project"
|
|
echo " → Ensure token has 'Execute Analysis' permission"
|
|
echo " 3. Token doesn't have 'Create Projects' permission (if project doesn't exist)"
|
|
echo " → Grant this permission in SonarQube user settings"
|
|
echo ""
|
|
echo "Check SonarQube logs for more details."
|
|
# Do not fail CI on Sonar auth/project setup issues.
|
|
exit 0
|
|
fi
|
|
continue-on-error: true
|
|
|
|
workflow-summary:
|
|
runs-on: ubuntu-latest
|
|
needs: [lint-and-test, ansible-validation, secret-scanning, dependency-scan, sast-scan, license-check, vault-check, playbook-test, container-scan, sonar-analysis]
|
|
if: always()
|
|
steps:
|
|
- name: Generate workflow summary
|
|
run: |
|
|
echo "## 🔍 CI Workflow Summary" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "### Job Results" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 📝 Markdown Linting | ${{ needs.lint-and-test.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔧 Ansible Validation | ${{ needs.ansible-validation.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔐 Secret Scanning | ${{ needs.secret-scanning.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 📦 Dependency Scan | ${{ needs.dependency-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔍 SAST Scan | ${{ needs.sast-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 📄 License Check | ${{ needs.license-check.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔒 Vault Check | ${{ needs.vault-check.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 📋 Playbook Test | ${{ needs.playbook-test.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🐳 Container Scan | ${{ needs.container-scan.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "| 🔍 SonarQube Analysis | ${{ needs.sonar-analysis.result }} |" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "### 📊 Summary" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "All security and validation checks have completed." >> $GITHUB_STEP_SUMMARY || true
|
|
echo "" >> $GITHUB_STEP_SUMMARY || true
|
|
echo "**Note:** Artifact uploads are not supported in Gitea Actions. Check individual job logs for detailed reports." >> $GITHUB_STEP_SUMMARY || true
|
|
continue-on-error: true
|