diff --git a/.github/workflows/ansible-lint-and-test.yml b/.github/workflows/ansible-lint-and-test.yml index 85e2361b..d1b4d8b4 100644 --- a/.github/workflows/ansible-lint-and-test.yml +++ b/.github/workflows/ansible-lint-and-test.yml @@ -4,16 +4,86 @@ on: pull_request: paths: - "ansible/**" + - "!ansible/**/*.md" + - "!ansible/**/README*" - ".github/workflows/ansible-lint-and-test.yml" push: branches: - main paths: - "ansible/**" + - "!ansible/**/*.md" + - "!ansible/**/README*" - ".github/workflows/ansible-lint-and-test.yml" + workflow_dispatch: jobs: + detect-changes: + if: github.event_name != 'workflow_dispatch' + runs-on: ubuntu-latest + outputs: + roles_json: ${{ steps.normalize.outputs.roles_json }} + playbooks_json: ${{ steps.normalize.outputs.playbooks_json }} + targets_json: ${{ steps.normalize.outputs.targets_json }} + has_targets: ${{ steps.normalize.outputs.has_targets }} + has_playbooks: ${{ steps.normalize.outputs.has_playbooks }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Detect changed files + uses: dorny/paths-filter@v3 + id: filter + with: + list-files: json + filters: | + roles: + - ansible/roles/** + playbooks: + - ansible/playbooks/**/*.yml + - ansible/playbooks/**/*.yaml + - ansible/site.yml + + - name: Normalize changed paths to targets + id: normalize + run: | + set -euo pipefail + + ROLES_FILES='${{ steps.filter.outputs.roles_files }}' + PLAYBOOKS_FILES='${{ steps.filter.outputs.playbooks_files }}' + + # Extract unique role directories using jq only + ROLES_JSON=$(echo "$ROLES_FILES" | jq -c '[.[] | select(startswith("ansible/roles/")) | split("/") | "ansible/roles/" + .[2]] | unique') + + # Extract unique playbook files + PLAYBOOKS_JSON=$(echo "$PLAYBOOKS_FILES" | jq -c 'unique') + + # Combine roles + playbooks for targets + TARGETS_JSON=$(jq -nc --argjson r "$ROLES_JSON" --argjson p "$PLAYBOOKS_JSON" '$r + $p | unique') + + # Boolean flags + HAS_TARGETS=$(echo "$TARGETS_JSON" | jq -r 'length > 0') + HAS_PLAYBOOKS=$(echo "$PLAYBOOKS_JSON" | jq -r 'length > 0') + + { + echo "roles_json=$ROLES_JSON" + echo "playbooks_json=$PLAYBOOKS_JSON" + echo "targets_json=$TARGETS_JSON" + echo "has_targets=$HAS_TARGETS" + echo "has_playbooks=$HAS_PLAYBOOKS" + } >> "$GITHUB_OUTPUT" + + echo "Changed roles: $ROLES_JSON" + echo "Changed playbooks: $PLAYBOOKS_JSON" + echo "Combined targets: $TARGETS_JSON" + echo "Has targets: $HAS_TARGETS" + echo "Has playbooks: $HAS_PLAYBOOKS" + ansible-yamllint: + if: github.event_name != 'workflow_dispatch' && needs.detect-changes.outputs.has_targets == 'true' + needs: detect-changes runs-on: ubuntu-latest permissions: contents: read @@ -42,10 +112,20 @@ jobs: --pull=always run: | pip install --quiet yamllint - echo "Running yamllint on Ansible YAML files..." - yamllint ansible/ + echo "Running yamllint on changed Ansible YAML files..." + python3 -c ' + import json, sys, subprocess + targets = json.loads(sys.argv[1]) + if targets: + print(f"Linting {len(targets)} target(s): {targets}") + subprocess.run(["yamllint"] + targets, check=True) + else: + print("No targets to lint") + ' '${{ needs.detect-changes.outputs.targets_json }}' ansible-lint: + if: github.event_name != 'workflow_dispatch' && needs.detect-changes.outputs.has_targets == 'true' + needs: detect-changes runs-on: ubuntu-latest permissions: contents: read @@ -75,10 +155,20 @@ jobs: run: | ansible-galaxy collection install -r ansible/requirements.yml pip install --quiet ansible-lint - echo "Running ansible-lint on roles and playbooks..." - ansible-lint ansible/playbooks/ ansible/roles/ ansible/site.yml --exclude ansible/old_roles/ + echo "Running ansible-lint on changed roles and playbooks..." + python3 -c ' + import json, sys, subprocess + targets = json.loads(sys.argv[1]) + if targets: + print(f"Linting {len(targets)} target(s): {targets}") + subprocess.run(["ansible-lint"] + targets + ["--exclude", "ansible/old_roles/"], check=True) + else: + print("No targets to lint") + ' '${{ needs.detect-changes.outputs.targets_json }}' ansible-playbook-syntax: + if: github.event_name != 'workflow_dispatch' && needs.detect-changes.outputs.has_playbooks == 'true' + needs: detect-changes runs-on: ubuntu-latest permissions: contents: read @@ -86,17 +176,7 @@ jobs: strategy: fail-fast: false matrix: - playbook: - - ansible/playbooks/audio-production.yml - - ansible/playbooks/coachlight-infra-stack.yml - - ansible/playbooks/file_copier_examples.yml - - ansible/playbooks/k3s-cluster-setup.yml - - ansible/playbooks/omada-netbox-sync.yml - - ansible/playbooks/omada-tester.yml - - ansible/playbooks/op_tester.yml - - ansible/playbooks/proxmox.yml - - ansible/playbooks/workstations.yml - - ansible/site.yml + playbook: ${{ fromJSON(needs.detect-changes.outputs.playbooks_json) }} steps: - name: Normalize repo name to lowercase @@ -123,3 +203,66 @@ jobs: ansible-galaxy collection install -r ansible/requirements.yml echo "Checking syntax for ${{ matrix.playbook }}..." ansible-playbook ${{ matrix.playbook }} --syntax-check + + full-lint: + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + permissions: + contents: read + packages: read + + steps: + - name: Normalize repo name to lowercase + id: normalize + run: | + echo "REPO_LOWER=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT + + - name: Set tooling image + id: image + run: | + echo "IMAGE=ghcr.io/${{ steps.normalize.outputs.REPO_LOWER }}/fedora-43-homelab-tooling:latest" >> $GITHUB_OUTPUT + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run yamllint inside tooling container + uses: addnab/docker-run-action@v3 + with: + image: ${{ steps.image.outputs.IMAGE }} + options: | + -v ${{ github.workspace }}:/workspace + --pull=always + run: | + pip install --quiet yamllint + echo "Running yamllint on all Ansible YAML files..." + yamllint ansible/ + + - name: Run ansible-lint inside tooling container + uses: addnab/docker-run-action@v3 + with: + image: ${{ steps.image.outputs.IMAGE }} + options: | + -v ${{ github.workspace }}:/workspace + --pull=always + run: | + ansible-galaxy collection install -r ansible/requirements.yml + pip install --quiet ansible-lint + echo "Running ansible-lint on all roles and playbooks..." + ansible-lint ansible/playbooks/ ansible/roles/ ansible/site.yml --exclude ansible/old_roles/ + + - name: Run syntax check on all playbooks + uses: addnab/docker-run-action@v3 + with: + image: ${{ steps.image.outputs.IMAGE }} + options: | + -v ${{ github.workspace }}:/workspace + --pull=always + run: | + ansible-galaxy collection install -r ansible/requirements.yml + echo "Checking syntax for all playbooks..." + for playbook in ansible/playbooks/*.yml ansible/site.yml; do + if [ -f "$playbook" ]; then + echo "Checking syntax for $playbook..." + ansible-playbook "$playbook" --syntax-check + fi + done diff --git a/semicolon_delimited_script b/semicolon_delimited_script new file mode 100644 index 00000000..0582116c --- /dev/null +++ b/semicolon_delimited_script @@ -0,0 +1,5 @@ +ansible --version +ansible-lint --version +yamllint --version +python3 --version +