All checks were successful
CI / skip-ci-check (push) Successful in 1m18s
CI / lint-and-test (push) Successful in 1m23s
CI / ansible-validation (push) Successful in 3m2s
CI / secret-scanning (push) Successful in 1m19s
CI / dependency-scan (push) Successful in 1m24s
CI / sast-scan (push) Successful in 2m32s
CI / license-check (push) Successful in 1m23s
CI / vault-check (push) Successful in 2m22s
CI / playbook-test (push) Successful in 2m25s
CI / container-scan (push) Successful in 1m51s
CI / sonar-analysis (push) Successful in 2m32s
CI / workflow-summary (push) Successful in 1m17s
### Summary
This PR refactors the playbook layout to reduce duplication and make host intent clearer (servers vs workstations), splits monitoring by host type, and restores full Zsh setup for developers while keeping servers aliases-only.
### Key changes
- **New playbooks**
- `playbooks/servers.yml`: baseline for server-class hosts (no desktop apps)
- `playbooks/workstations.yml`: baseline for dev/desktop/local + **desktop apps only on `desktop` group**
- **Monitoring split**
- `roles/monitoring_server`: server monitoring + intrusion prevention (includes `fail2ban`, sysstat)
- `roles/monitoring_desktop`: desktop-oriented monitoring tooling
- Updated playbooks to use the correct monitoring role per host type
- **Shell role: server-safe + developer-friendly**
- `roles/shell` now supports two modes:
- `shell_mode: minimal` (default): aliases-only, does not overwrite `.zshrc`
- `shell_mode: full`: installs Oh My Zsh + Powerlevel10k + plugins and deploys a managed `.zshrc`
- `playbooks/development.yml` and `playbooks/workstations.yml` use `shell_mode: full`
- `playbooks/servers.yml` remains **aliases-only**
- **Applications**
- Applications role runs only on `desktop` group (via `workstations.yml`)
- Removed Brave installs/repo management
- Added **CopyQ** to desktop apps (`applications_desktop_packages`)
- **Docs + architecture**
- Added canonical doc tree under `project-docs/` (overview/architecture/standards/workflow/decisions)
- Consolidated architecture docs: `docs/reference/architecture.md` is now a pointer to `project-docs/architecture.md`
- Fixed broken doc links by adding the missing referenced pages under `docs/`
### Behavior changes (important)
- Desktop GUI apps install **only** on the `desktop` inventory group (not on servers, not on dev VMs unless they are in `desktop`).
- Dev/workstation Zsh is now provisioned in **full mode** (managed `.zshrc` + p10k).
### How to test (local CI parity)
```bash
make test
npm test
```
Optional dry runs (interactive sudo may be required):
```bash
make check
make check-local
```
### Rollout guidance
- Apply to a single host first:
- Workstations: `make workstations HOST=<devhost>`
- Servers: `make servers HOST=<serverhost>`
- Then expand to group runs.
Reviewed-on: #4
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
|