-
Notifications
You must be signed in to change notification settings - Fork 6
240 lines (213 loc) · 7.63 KB
/
syntax-check.yaml
File metadata and controls
240 lines (213 loc) · 7.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
---
name: Ansible Syntax Check
on:
merge_group:
pull_request:
branches:
- main
types:
- opened
- synchronize
- reopened
push:
branches:
- main
schedule:
# Runs every Sunday at 4 AM (see https://crontab.guru)
- cron: "0 4 * * 0"
workflow_dispatch:
inputs:
ROLE:
description: 'Role to test (e.g. "elk", "ad", "vulns_acls")'
required: false
default: ''
type: string
permissions:
contents: read
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
env:
ANSIBLE_FORCE_COLOR: "1"
COLLECTION_NAMESPACE: dreadnode
COLLECTION_NAME: goad
COLLECTION_PATH: ansible_collections/dreadnode/goad
REQUIREMENTS_FILE: .hooks/requirements.txt
PY_COLORS: "1"
PYTHON_VERSION: "3.14.5"
ROLE: ${{ github.event.inputs.ROLE }}
ANSIBLE_COLLECTIONS_PATH: ~/.ansible/collections
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
roles: ${{ steps.detect.outputs.roles }}
test_all: ${{ steps.check-event.outputs.test_all }}
steps:
- name: Checkout git repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: ${{ env.COLLECTION_PATH }}
fetch-depth: 0
- name: Check event type
id: check-event
env:
EVENT_NAME: ${{ github.event_name }}
run: |
if [[ "$EVENT_NAME" == "push" ]] || \
[[ "$EVENT_NAME" == "schedule" ]] || \
[[ "$EVENT_NAME" == "merge_group" ]] || \
[[ "$EVENT_NAME" == "workflow_dispatch" && -z "$ROLE" ]]; then
echo "test_all=true" >> "$GITHUB_OUTPUT"
else
echo "test_all=false" >> "$GITHUB_OUTPUT"
fi
- name: Detect changed roles
id: detect
if: steps.check-event.outputs.test_all == 'false'
working-directory: ${{ env.COLLECTION_PATH }}
env:
EVENT_NAME: ${{ github.event_name }}
PR_BASE_SHA: ${{ github.event.pull_request.base.sha }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if [[ "$EVENT_NAME" == "pull_request" ]]; then
BASE="$PR_BASE_SHA"
HEAD="$PR_HEAD_SHA"
else
BASE="origin/main"
HEAD="HEAD"
fi
CHANGED_FILES=$(git diff --name-only "$BASE"..."$HEAD")
echo "Changed files:"
echo "$CHANGED_FILES"
ROLES=$(echo "$CHANGED_FILES" | grep '^ansible/roles/' | cut -d'/' -f3 | sort -u | tr '\n' ' ')
echo "roles=$ROLES" >> "$GITHUB_OUTPUT"
echo "Changed roles: $ROLES"
validate-inputs:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.ROLE != '' }}
steps:
- name: Checkout git repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: ${{ env.COLLECTION_PATH }}
- name: Validate inputs
env:
COLL_PATH: ${{ env.COLLECTION_PATH }}
run: |
if [[ -n "$ROLE" ]]; then
if [[ ! -d "$COLL_PATH/ansible/roles/$ROLE" ]]; then
echo "::error::Role '$ROLE' not found in ansible/roles/"
exit 1
fi
if [[ ! -f "$COLL_PATH/ansible/roles/$ROLE/tasks/main.yml" ]]; then
echo "::error::Role '$ROLE' has no tasks/main.yml"
exit 1
fi
fi
syntax-check:
needs: detect-changes
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout git repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
path: ${{ env.COLLECTION_PATH }}
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
cache-dependency-path: '${{ env.COLLECTION_PATH }}/${{ env.REQUIREMENTS_FILE }}'
- name: Cache Ansible collections
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
with:
path: ~/.ansible/collections
key: ${{ runner.os }}-ansible-${{ github.ref }}-${{ hashFiles('**/requirements.yml') }}
- name: Install dependencies
env:
COLL_PATH: ${{ env.COLLECTION_PATH }}
REQS_FILE: ${{ env.REQUIREMENTS_FILE }}
run: |
python3 -m pip install -r "${COLL_PATH}/${REQS_FILE}"
- name: Install galaxy dependencies
working-directory: ${{ env.COLLECTION_PATH }}/ansible
run: |
ansible-galaxy collection install -r requirements.yml --force
- name: Build and install collection locally
working-directory: ${{ env.COLLECTION_PATH }}/ansible
env:
COLL_NS: ${{ env.COLLECTION_NAMESPACE }}
COLL_NAME: ${{ env.COLLECTION_NAME }}
run: |
ansible-galaxy collection build --force
ansible-galaxy collection install "${COLL_NS}-${COLL_NAME}"-*.tar.gz -p ~/.ansible/collections --force --pre
- name: Syntax check roles
env:
ANSIBLE_CONFIG: ${{ env.COLLECTION_PATH }}/ansible/ansible.cfg
ANSIBLE_ROLES_PATH: ${{ env.COLLECTION_PATH }}/ansible/roles
TEST_ALL: ${{ needs.detect-changes.outputs.test_all }}
CHANGED_ROLES: ${{ needs.detect-changes.outputs.roles }}
SINGLE_ROLE: ${{ env.ROLE }}
COLL_PATH: ${{ env.COLLECTION_PATH }}
run: |
set -e
FAILED=0
PASSED=0
SKIPPED=0
ROLES_DIR="$COLL_PATH/ansible/roles"
TMPDIR=$(mktemp -d)
for role_dir in "$ROLES_DIR"/*/; do
role=$(basename "$role_dir")
# Skip roles without tasks
if [ ! -f "$role_dir/tasks/main.yml" ]; then
continue
fi
# If a single role was specified, only test that one
if [ -n "$SINGLE_ROLE" ]; then
if [ "$role" != "$SINGLE_ROLE" ]; then
continue
fi
# If not testing all, filter to changed roles
elif [ "$TEST_ALL" != "true" ] && [ -n "$CHANGED_ROLES" ]; then
if ! echo "$CHANGED_ROLES" | grep -qw "$role"; then
SKIPPED=$((SKIPPED + 1))
continue
fi
fi
echo "::group::Syntax check: $role"
# Generate temporary playbook
cat > "$TMPDIR/check_${role}.yml" <<PLAYBOOK
---
- name: Syntax check ${role}
hosts: all
gather_facts: false
tasks:
- name: Include role
ansible.builtin.include_role:
name: dreadnode.goad.${role}
PLAYBOOK
if ansible-playbook --syntax-check "$TMPDIR/check_${role}.yml"; then
echo "PASS: $role"
PASSED=$((PASSED + 1))
else
echo "::error::Syntax check failed for role: $role"
FAILED=$((FAILED + 1))
fi
echo "::endgroup::"
done
rm -rf "$TMPDIR"
echo ""
echo "=== Results ==="
echo "Passed: $PASSED"
echo "Failed: $FAILED"
echo "Skipped: $SKIPPED"
if [ "$FAILED" -gt 0 ]; then
echo "::error::$FAILED role(s) failed syntax check"
exit 1
fi
if [ "$PASSED" -eq 0 ] && [ -z "$SINGLE_ROLE" ]; then
echo "No roles were checked. This may indicate a problem with change detection."
fi