-
-
Notifications
You must be signed in to change notification settings - Fork 0
204 lines (178 loc) · 9.53 KB
/
validate-workflows.yml
File metadata and controls
204 lines (178 loc) · 9.53 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
name: Validate Workflows
permissions:
contents: write
pull-requests: write
on:
pull_request:
paths:
- .github/workflows/**
workflow_dispatch: # Allow manual triggering for testing
jobs:
validate:
runs-on: blacksmith-2vcpu-ubuntu-2404 # trunk-ignore(actionlint/runner-label)
steps:
- name: Checkout
uses: actions/checkout@v6 # zizmor: ignore[unpinned-uses]
with:
persist-credentials: false
fetch-depth: 0 # Fetch full history for git comparisons
- name: Set up Python
uses: actions/setup-python@v5 # zizmor: ignore[unpinned-uses]
with:
python-version: '3.12'
- name: Get latest uv version
id: uv-version
run: |
# Get latest uv version from GitHub releases with error handling
if LATEST_VERSION=$(curl -s https://api.github.com/repos/astral-sh/uv/releases/latest | python3 -c "import sys, json; print(json.load(sys.stdin)['tag_name'])" 2>/dev/null); then
echo "version=$LATEST_VERSION" >> $GITHUB_OUTPUT
echo "Latest uv version: $LATEST_VERSION"
else
echo "Failed to fetch uv version from GitHub, using date-based fallback"
FALLBACK_VERSION="fallback-$(date +%Y-%m-%d)"
echo "version=$FALLBACK_VERSION" >> $GITHUB_OUTPUT
echo "Using fallback version key: $FALLBACK_VERSION"
fi
- name: Cache uv installation
uses: actions/cache@v5 # zizmor: ignore[unpinned-uses]
with:
path: ~/.local/bin/uv
key: ${{ runner.os }}-uv-${{ steps.uv-version.outputs.version }}
restore-keys: |
${{ runner.os }}-uv-
- name: Get latest zizmor version
id: zizmor-version
run: |
# Get latest zizmor version from PyPI with error handling
if LATEST_VERSION=$(curl -s https://pypi.org/pypi/zizmor/json | python3 -c "import sys, json; print(json.load(sys.stdin)['info']['version'])" 2>/dev/null); then
echo "version=$LATEST_VERSION" >> $GITHUB_OUTPUT
echo "Latest zizmor version: $LATEST_VERSION"
else
echo "Failed to fetch version from PyPI, using date-based fallback"
FALLBACK_VERSION="fallback-$(date +%Y-%m-%d)"
echo "version=$FALLBACK_VERSION" >> $GITHUB_OUTPUT
echo "Using fallback version key: $FALLBACK_VERSION"
fi
# Debug: You can temporarily override version for testing cache invalidation
# echo "version=test-version-$(date +%s)" >> $GITHUB_OUTPUT
- name: Cache zizmor tool
uses: actions/cache@v5 # zizmor: ignore[unpinned-uses]
with:
path: |
~/.local/bin/zizmor
~/.local/share/uv
key: ${{ runner.os }}-zizmor-${{ steps.zizmor-version.outputs.version }}
restore-keys: |
${{ runner.os }}-zizmor-
- name: Install zizmor
env:
ZIZMOR_VERSION: ${{ steps.zizmor-version.outputs.version }}
UV_VERSION: ${{ steps.uv-version.outputs.version }}
run: |
# Install uv if not cached
if [ ! -f ~/.local/bin/uv ]; then
echo "Installing uv ${UV_VERSION}..."
curl -LsSf https://astral.sh/uv/install.sh | sh
# Add uv to current PATH for this step
export PATH="$HOME/.local/bin:$PATH"
else
echo "Using cached uv ${UV_VERSION}"
fi
# Install zizmor if not cached (cache key includes version, so this handles updates)
if [ ! -f ~/.local/bin/zizmor ]; then
echo "Installing zizmor ${ZIZMOR_VERSION}..."
~/.local/bin/uv tool install zizmor
else
echo "Using cached zizmor ${ZIZMOR_VERSION}"
fi
# Add uv tools to PATH
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Validate all workflows
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BASE_REF: ${{ github.base_ref }}
run: |
# Exit on any error
set -euo pipefail
# Initialize error flag
has_errors=0
# Prepare logs directory at repository root
LOG_DIR="${GITHUB_WORKSPACE}/zizmor-logs"
mkdir -p "${LOG_DIR}"
# Validate inputs
if [ -z "${BASE_REF:-}" ]; then
echo "::warning::BASE_REF is not set, validating all workflow files"
# If no base ref (manual trigger), validate all workflows under .github/workflows
changed_files=$(find .github/workflows -type f \( -name "*.yml" -o -name "*.yaml" \) || true)
else
echo "Comparing against base ref: $BASE_REF"
# Debug: Show available refs
echo "Available branches:"
git branch -a | head -5
# Try different git diff approaches for PR context
if git rev-parse "origin/${BASE_REF}" >/dev/null 2>&1; then
echo "Using origin/${BASE_REF} for comparison"
changed_files=$(git diff --diff-filter=AM --name-only "origin/${BASE_REF}" | grep -E '^\.github/workflows/[^/]+\.ya?ml$' || true)
elif git rev-parse "remotes/origin/${BASE_REF}" >/dev/null 2>&1; then
echo "Using remotes/origin/${BASE_REF} for comparison"
changed_files=$(git diff --diff-filter=AM --name-only "remotes/origin/${BASE_REF}" | grep -E '^\.github/workflows/[^/]+\.ya?ml$' || true)
elif git rev-parse "${BASE_REF}" >/dev/null 2>&1; then
echo "Using ${BASE_REF} for comparison"
changed_files=$(git diff --diff-filter=AM --name-only "${BASE_REF}" | grep -E '^\.github/workflows/[^/]+\.ya?ml$' || true)
else
echo "::warning::Cannot find base ref ${BASE_REF} in any form"
echo "Falling back to validating all workflow files"
changed_files=$(find . -name "*.yml" -o -name "*.yaml" | sed 's|^\./||' || true)
fi
fi
echo "Detected changed files: $changed_files"
if [ -z "$changed_files" ]; then
echo "No workflow files changed"
# Still write an empty marker so downstream steps succeed
echo 0 > "${LOG_DIR}/has_errors.txt"
exit 0
fi
# Loop through changed workflow files
while IFS= read -r file; do
[ -z "$file" ] && continue
# Skip files that were deleted or do not exist in the checkout
if [ ! -f "$file" ]; then
echo "Skipping $file (not present in current checkout — likely deleted or renamed)"
continue
fi
# Skip non-yaml files (defensive)
if [[ ! "$file" =~ \.(ya?ml)$ ]]; then
continue
fi
echo "Validating $file..."
safe_name=$(basename -- "$file")
# Capture zizmor output per-file to logs for troubleshooting
if ! zizmor "$file" > "${LOG_DIR}/${safe_name}.log" 2>&1; then
echo "::error::Validation failed for $file (see artifact zizmor-logs/${safe_name}.log)"
has_errors=1
else
echo "Validation passed for $file" | tee -a "${LOG_DIR}/${safe_name}.log" >/dev/null
fi
done <<< "$changed_files"
# Persist error marker for later steps
echo $has_errors > "${LOG_DIR}/has_errors.txt"
if [ "$has_errors" -eq 0 ]; then
echo "All workflows validated successfully"
else
echo "One or more workflow validations failed (logs uploaded)"
fi
- name: Upload zizmor logs
if: always()
uses: actions/upload-artifact@v4 # zizmor: ignore[unpinned-uses]
with:
name: zizmor-logs
path: ${{ github.workspace }}/zizmor-logs
- name: Fail if validation errors
if: always()
run: |
set -euo pipefail
LOG_DIR="${GITHUB_WORKSPACE}/zizmor-logs"
if [ -f "${LOG_DIR}/has_errors.txt" ] && [ "$(cat "${LOG_DIR}/has_errors.txt")" -eq 1 ]; then
echo "One or more workflow validations failed (see artifact zizmor-logs)"
exit 1
fi