Skip to content

Commit 6c90337

Browse files
authored
Merge pull request #14601 from vojtapolasek/make_yamllint_filepaths_stricter
yamllint: cover all changed files ending in yaml, yml or fmf
2 parents b4dd9c8 + 5fcf646 commit 6c90337

File tree

3 files changed

+124
-33
lines changed

3 files changed

+124
-33
lines changed

.github/workflows/ci_lint.yml

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ on:
33
pull_request:
44
branches: [master, 'stabilization*']
55
permissions:
6-
contents: read
6+
contents: read
77
jobs:
88
yamllint:
9-
name: Yaml Lint on Changed Controls and Profiles Files
9+
name: Yaml Lint on Changed yaml files
1010
runs-on: ubuntu-latest
1111
steps:
1212
- name: Install Git
@@ -27,37 +27,41 @@ jobs:
2727
url="repos/$repo/pulls/$pr_number/files"
2828
response=$(gh api "$url" --paginate)
2929
echo "$response" | jq -r '.[].filename' > filenames.txt
30-
cat filenames.txt
31-
32-
if grep -q "controls/" filenames.txt; then
33-
echo "CONTROLS_CHANGES=true" >> $GITHUB_ENV
34-
else
35-
echo "CONTROLS_CHANGES=false" >> $GITHUB_ENV
36-
fi
37-
if grep -q "\.profile" filenames.txt; then
38-
echo "PROFILES_CHANGES=true" >> $GITHUB_ENV
39-
else
40-
echo "PROFILES_CHANGES=false" >> $GITHUB_ENV
41-
fi
4230
env:
4331
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4432

4533
- name: Install yamllint
46-
if: ${{ env.CONTROLS_CHANGES == 'true' || env.PROFILES_CHANGES == 'true' }}
4734
run: pip install yamllint
4835

49-
- name: Run yamllint in Control Files Modified by PR
50-
if: ${{ env.CONTROLS_CHANGES == 'true' }}
51-
run: |
52-
for control_file in $(cat filenames.txt | grep "controls/"); do
53-
echo "Running yamllint on $control_file..."
54-
yamllint "$control_file"
55-
done
56-
57-
- name: Run yamllint in Profile Files Modified by PR
58-
if: ${{ env.PROFILES_CHANGES == 'true' }}
36+
- name: Run yamllint on files modified by the PR
5937
run: |
60-
for profile_file in $(cat filenames.txt | grep "\.profile"); do
61-
echo "Running yamllint on $profile_file..."
62-
yamllint "$profile_file"
38+
exit_code=0
39+
for file in $(grep -E '\.(yml|yaml|fmf|profile)$' filenames.txt); do
40+
if [[ ! -f "$file" ]]; then
41+
continue
42+
fi
43+
echo "Running yamllint on $file..."
44+
if grep -qP '\{\{[%{#]' "$file"; then
45+
# File contains Jinja2 constructs — strip them before linting.
46+
# yamllint -s exits: 0 = clean, 1 = errors, 2 = warnings only.
47+
output=$(python3 utils/strip_jinja_for_yamllint.py "$file" \
48+
| yamllint -s -c .yamllint - 2>&1)
49+
rc=$?
50+
# Show all output (warnings and errors), replacing "stdin"
51+
# with the actual filename since yamllint reads from a pipe.
52+
if [ -n "$output" ]; then
53+
echo "$output" | sed "s|^stdin|$file|"
54+
fi
55+
# Fail only on errors (exit code 1), not warnings (exit code 2).
56+
if [ "$rc" -eq 1 ]; then
57+
exit_code=1
58+
fi
59+
else
60+
yamllint -s -c .yamllint "$file"
61+
rc=$?
62+
if [ "$rc" -eq 1 ]; then
63+
exit_code=1
64+
fi
65+
fi
6366
done
67+
exit $exit_code

.yamllint

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
11
---
22
extends: default
3+
locale: en_US.UTF-8
4+
yaml-files:
5+
- "*.yaml"
6+
- "*.yml"
7+
- "*.fmf"
8+
- "*.profile"
39

410
# https://yamllint.readthedocs.io/en/stable/rules.html
511
rules:
6-
comments: disable
7-
comments-indentation: disable
8-
document-start: disable
12+
truthy: disable # do not check for strict true / false boolean values
13+
comments: disable # disable syntax checking of comments
14+
comments-indentation: disable # disable indentation checks for comments
15+
document-start: disable # do not require the document start marker
916
empty-lines:
10-
level: warning
17+
level: warning # only warn about empty lines
1118
indentation:
19+
# pass if spaces are used for indentation and number of spaces is consistent through a file
1220
spaces: consistent
13-
line-length: disable
21+
line-length:
22+
max: 99 # allow lines up to 99 chars

utils/strip_jinja_for_yamllint.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#!/usr/bin/env python3
2+
"""Strip Jinja2 constructs from YAML files to make them yamllint-safe.
3+
4+
This project uses Jinja2 templating ({{% %}}, {{{ }}}, {{# #}}) inside YAML
5+
files. yamllint cannot parse these constructs, so this script removes them
6+
while preserving line numbers (replaced regions become blank lines) so that
7+
yamllint error messages still point to the correct source lines.
8+
9+
Usage:
10+
python3 utils/strip_jinja_for_yamllint.py FILE
11+
12+
The cleaned content is written to stdout.
13+
"""
14+
15+
import re
16+
import sys
17+
18+
19+
def _replace_with_blanks(match):
20+
"""Replace a match with the same number of newlines to preserve line numbers."""
21+
return "\n" * match.group(0).count("\n")
22+
23+
24+
def strip_jinja(content):
25+
# 1. Remove whole-line Jinja block tags: {{% ... %}} on their own line(s).
26+
# Match the entire line (including leading whitespace) to avoid leaving
27+
# trailing spaces behind.
28+
content = re.sub(
29+
r"^[ \t]*\{\{%.*?%\}\}[ \t]*$",
30+
_replace_with_blanks,
31+
content,
32+
flags=re.MULTILINE | re.DOTALL,
33+
)
34+
# Remove any remaining inline block tags (rare).
35+
content = re.sub(r"\{\{%.*?%\}\}", _replace_with_blanks, content, flags=re.DOTALL)
36+
37+
# 2. Remove whole-line Jinja comments: {{# ... #}}
38+
content = re.sub(
39+
r"^[ \t]*\{\{#.*?#\}\}[ \t]*$",
40+
_replace_with_blanks,
41+
content,
42+
flags=re.MULTILINE | re.DOTALL,
43+
)
44+
# Remove any remaining inline comments.
45+
content = re.sub(r"\{\{#.*?#\}\}", "", content, flags=re.DOTALL)
46+
47+
# 3a. Standalone Jinja expressions occupying entire lines — these typically
48+
# expand to top-level YAML keys (e.g. ocil/ocil_clause macros) or
49+
# Ansible tasks, so replacing them with a placeholder string would
50+
# produce invalid YAML. Replace with a YAML-safe comment placeholder
51+
# to avoid trailing whitespace on otherwise blank lines.
52+
content = re.sub(
53+
r"^([ \t]*)\{\{\{.*?\}\}\}[ \t]*$",
54+
lambda m: m.group(1) + "# jinja" + "\n" * (m.group(0).count("\n") - 1)
55+
if m.group(0).count("\n") > 0
56+
else m.group(1) + "# jinja",
57+
content,
58+
flags=re.MULTILINE | re.DOTALL,
59+
)
60+
61+
# 3b. Inline Jinja expressions embedded inside a YAML value — replace
62+
# with a short placeholder so the surrounding YAML stays valid.
63+
content = re.sub(r"\{\{\{.*?\}\}\}", "JINJA_EXPRESSION", content)
64+
65+
return content
66+
67+
68+
def main():
69+
if len(sys.argv) != 2:
70+
print(f"Usage: {sys.argv[0]} FILE", file=sys.stderr)
71+
sys.exit(2)
72+
73+
with open(sys.argv[1]) as f:
74+
sys.stdout.write(strip_jinja(f.read()))
75+
76+
77+
if __name__ == "__main__":
78+
main()

0 commit comments

Comments
 (0)