Add lint configuration and Gitea Actions CI
Configure yamllint and ansible-lint, plus a workflow running yamllint, ansible-lint and ansible-playbook --syntax-check over every playbook. ansible-lint uses the moderate profile: on the current code it reports exactly the same violations as basic, so it costs nothing today while holding a higher bar for new code. skip_list is empty; noisy legacy rules go to warn_list with a comment on why and when to restore them. Correctness and safety rules stay fatal. Two constraints are encoded in the workflow: syntax-check must run from ansible/ because roles_path is relative, and ansible-lint needs absolute ANSIBLE_ROLES_PATH/ANSIBLE_COLLECTIONS_PATH when run from the root. The runner is not registered yet; registration notes are in the workflow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GTocXkGUUazHdKKd3r9k71
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
---
|
||||
# ansible-lint для HomeLab infras.
|
||||
#
|
||||
# Profile: moderate.
|
||||
# - `production` / `safety` дали бы сотни нарушений на текущем коде (fqcn,
|
||||
# jinja[spacing], no-handler, key-order, галактические метаданные ролей) —
|
||||
# линтер стал бы шумом, который все игнорируют.
|
||||
# - Фактическая проверка: на этом репозитории `basic` и `moderate` дают
|
||||
# ровно один и тот же набор нарушений (все сработавшие правила помечены
|
||||
# profile:basic). То есть `moderate` сегодня ничего не стоит, но держит
|
||||
# планку выше для нового кода. Отсюда выбор.
|
||||
# - Правила корректности и безопасности (syntax-check, risky-file-permissions,
|
||||
# risky-shell-pipe, risky-octal, no-changed-when, no-free-form, deprecated-*,
|
||||
# jinja[invalid], sanity) НЕ отключены — они остаются fatal.
|
||||
|
||||
profile: moderate
|
||||
|
||||
exclude_paths:
|
||||
- archive/ # исторические NixOS/docker-compose/ansible конфиги, read-only
|
||||
- ansible/.venv/ # gitignored, локальное venv
|
||||
- ansible/collections/ # gitignored, установленные galaxy-коллекции
|
||||
- ansible/generated/ # gitignored артефакты
|
||||
- .opencode/ # конфиги агентов + node_modules
|
||||
- tools/ # tools/grimmory-mcp — JS, не ansible
|
||||
- node_modules/
|
||||
- .direnv/
|
||||
- .git/
|
||||
# Зашифрованный ansible-vault: линтер не может его расшифровать и шумит
|
||||
# предупреждениями о Decryption failed.
|
||||
- ansible/inventory/host_vars/gyro/vault.yml
|
||||
|
||||
# Явно указываем, что считать плейбуками/тасками — иначе ansible-lint
|
||||
# принимает inventory/*.yml и roles/*/files/*.yml за плейбуки.
|
||||
kinds:
|
||||
- playbook: 'ansible/playbooks/*.yml'
|
||||
- tasks: 'ansible/tasks/*.yml'
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# warn_list — правила, которые СЕЙЧАС массово срабатывают на легаси-коде.
|
||||
# Они видны в выводе как warning, но не роняют CI. Это осознанный
|
||||
# «нулевой baseline»: CI зелёный, долг виден.
|
||||
# По каждому пункту — почему и стоит ли возвращать в fatal.
|
||||
# ------------------------------------------------------------------
|
||||
warn_list:
|
||||
# 40 срабатываний. Роли используют осмысленные кросс-ролевые префиксы
|
||||
# (openvpn_*, monitoring_*, emergency_*), а не имя роли. Переименование
|
||||
# затронет inventory, host_vars и все плейбуки разом.
|
||||
# ВЕРНУТЬ В FATAL: после разового переименования переменных ролей.
|
||||
- var-naming[no-role-prefix]
|
||||
|
||||
# 19 срабатываний. Имена задач в нижнем регистре ("restart gitea lxc").
|
||||
# Чисто косметика, на поведение не влияет.
|
||||
# ВЕРНУТЬ В FATAL: после массового причёсывания имён (дешёвый разовый PR).
|
||||
- name[casing]
|
||||
|
||||
# 18 срабатываний. Часть файлов без "---" в начале.
|
||||
# ВЕРНУТЬ В FATAL: тривиально чинится, но затрагивает 18 файлов.
|
||||
- yaml[document-start]
|
||||
|
||||
# 6 срабатываний. Все — верификационные команды с changed_when: false
|
||||
# (curl для проверки HTTPS-эндпоинта, systemctl is-active, git config
|
||||
# внутри чужого чекаута, docker exec caddy validate). Замена на
|
||||
# uri/systemd/git-модули здесь не улучшает код, а иногда невозможна
|
||||
# (команда исполняется внутри pct/docker exec).
|
||||
# ВЕРНУТЬ В FATAL: вряд ли — правило по сути false-positive для этого стиля.
|
||||
- command-instead-of-module
|
||||
|
||||
# 5 срабатываний: безымянные `- import_playbook:` записи в *-update.yml.
|
||||
# 1 срабатывание: безымянный `- block:` в ru-vps-mihomo-harden.yml.
|
||||
# Влияет только на читаемость вывода ansible-playbook.
|
||||
# ВЕРНУТЬ В FATAL: да, после того как проставят name (мелкий PR).
|
||||
- name[play]
|
||||
- name[missing]
|
||||
|
||||
# 1 срабатывание: emergency_access/tasks/client.yml:46 — become_user без
|
||||
# become. Это, вероятно, НАСТОЯЩИЙ баг (ключ создаётся не тем пользователем),
|
||||
# но чинить его — задача не линтера. Держим в warn_list, чтобы CI не был
|
||||
# красным с первого дня; ВЕРНУТЬ В FATAL сразу после фикса.
|
||||
- partial-become
|
||||
|
||||
# skip_list пуст намеренно: ничего не отключаем полностью, всё либо fatal,
|
||||
# либо видимый warning.
|
||||
skip_list: []
|
||||
|
||||
use_default_rules: true
|
||||
@@ -0,0 +1,134 @@
|
||||
---
|
||||
# Статические проверки Ansible-кода HomeLab infras.
|
||||
#
|
||||
# ГДЕ ЭТО ДОЛЖНО ВЫПОЛНЯТЬСЯ
|
||||
# --------------------------
|
||||
# Gitea живёт на LXC `gitea` (192.168.1.25). Gitea Actions по умолчанию
|
||||
# ВЫКЛЮЧЕНЫ и не имеют ни одного раннера — этот workflow не запустится,
|
||||
# пока раннер не зарегистрирован ОТДЕЛЬНО, вручную:
|
||||
#
|
||||
# 1. Включить Actions в Gitea:
|
||||
# app.ini -> [actions] ENABLED = true
|
||||
# и в настройках репозитория: Settings -> Actions -> Enable.
|
||||
#
|
||||
# 2. Поднять act_runner. Подходящий хост — LXC `docker-test`
|
||||
# (192.168.1.29): там уже есть Docker, а сборка контейнеров раннера
|
||||
# не мешает проду. Ставить раннер на сам LXC `gitea` не стоит —
|
||||
# CI-нагрузка не должна валить git-сервис.
|
||||
#
|
||||
# 3. Зарегистрировать раннер (на docker-test):
|
||||
# act_runner register --no-interactive \
|
||||
# --instance http://192.168.1.25:3000 \
|
||||
# --token <RUNNER_TOKEN из Gitea Settings -> Actions -> Runners> \
|
||||
# --name docker-test-runner \
|
||||
# --labels ubuntu-latest:docker://catthehacker/ubuntu:act-latest
|
||||
#
|
||||
# Метка `ubuntu-latest` обязательна — именно её просит `runs-on` ниже.
|
||||
#
|
||||
# 4. Раннеру нужен исходящий интернет (PyPI + Ansible Galaxy).
|
||||
# На docker-test трафик может идти через mihomo/OpenVPN — проверить,
|
||||
# что pip и galaxy резолвятся, иначе шаг установки упадёт.
|
||||
#
|
||||
# Регистрация раннера НЕ автоматизирована этим репозиторием: она требует
|
||||
# одноразового токена из веб-интерфейса Gitea.
|
||||
#
|
||||
# Локально те же проверки воспроизводятся через nix:
|
||||
# nix develop -c yamllint .
|
||||
# nix develop -c ansible-lint
|
||||
# nix develop -c sh -c 'cd ansible && for f in playbooks/*.yml; do ansible-playbook --syntax-check "$f"; done'
|
||||
|
||||
name: lint
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: yamllint + ansible-lint + syntax-check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
# ansible.cfg лежит в ansible/ и использует ОТНОСИТЕЛЬНЫЕ пути
|
||||
# (roles_path = roles). Из корня репозитория он не работает, поэтому
|
||||
# пути задаются абсолютно через окружение. Без этого ansible-lint
|
||||
# выдаёт 12 ложных syntax-check[specific] «role not found».
|
||||
ANSIBLE_ROLES_PATH: ${{ github.workspace }}/ansible/roles
|
||||
ANSIBLE_COLLECTIONS_PATH: ${{ github.workspace }}/ansible/collections
|
||||
ANSIBLE_INVENTORY: ${{ github.workspace }}/ansible/inventory/hosts.yml
|
||||
# Ansible шумит депрекейшенами ядра — в CI они не наши.
|
||||
ANSIBLE_DEPRECATION_WARNINGS: "false"
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: "1"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Образ catthehacker/ubuntu:act-latest уже несёт python3, но не всегда
|
||||
# python3-venv. Ставим явно, чтобы шаг не был хрупким.
|
||||
- name: Ensure python3 + venv
|
||||
run: |
|
||||
set -eux
|
||||
if ! command -v python3 >/dev/null 2>&1 || ! python3 -m venv --help >/dev/null 2>&1; then
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends python3 python3-venv python3-pip
|
||||
fi
|
||||
python3 --version
|
||||
|
||||
- name: Install ansible-core, ansible-lint, yamllint
|
||||
run: |
|
||||
set -eux
|
||||
python3 -m venv /tmp/lintenv
|
||||
. /tmp/lintenv/bin/activate
|
||||
python3 -m pip install --upgrade pip
|
||||
# ansible-core/proxmoxer/requests берём из репозитория, чтобы CI и
|
||||
# локальное окружение не разъезжались.
|
||||
python3 -m pip install -r ansible/requirements.txt
|
||||
# Линтеры пинуем: обновление ansible-lint регулярно добавляет новые
|
||||
# правила и красит CI без единого коммита в инфраструктуру.
|
||||
python3 -m pip install 'ansible-lint==25.8.2' 'yamllint==1.37.1'
|
||||
echo "/tmp/lintenv/bin" >> "$GITHUB_PATH"
|
||||
|
||||
- name: Install Galaxy collections
|
||||
run: |
|
||||
set -eux
|
||||
ansible-galaxy collection install \
|
||||
-r ansible/requirements.yml \
|
||||
-p ansible/collections
|
||||
|
||||
- name: Versions
|
||||
run: |
|
||||
set -eux
|
||||
ansible --version | head -n1
|
||||
ansible-lint --version
|
||||
yamllint --version
|
||||
|
||||
# Конфиг в /.yamllint. Падает только на ошибках (табы, дубли ключей,
|
||||
# битый YAML); стилевые замечания идут как warning и CI не роняют.
|
||||
- name: yamllint
|
||||
run: yamllint -f standard .
|
||||
|
||||
# Конфиг в /.ansible-lint, profile: moderate.
|
||||
- name: ansible-lint
|
||||
run: ansible-lint
|
||||
|
||||
# syntax-check запускается ИЗ ansible/, иначе ansible.cfg с
|
||||
# относительными roles_path не подхватывается и 13 плейбуков падают
|
||||
# с «role not found». Переменные Proxmox (PROXMOX_*) для syntax-check
|
||||
# НЕ нужны: `lookup('env', ...)` на этапе парсинга не вычисляется,
|
||||
# проверено — все 37 плейбуков проходят с пустым окружением.
|
||||
- name: ansible-playbook --syntax-check (все плейбуки)
|
||||
working-directory: ansible
|
||||
run: |
|
||||
set -u
|
||||
rc=0
|
||||
for f in playbooks/*.yml; do
|
||||
if ansible-playbook --syntax-check "$f" >/tmp/sc.log 2>&1; then
|
||||
echo "ok $f"
|
||||
else
|
||||
rc=1
|
||||
echo "FAIL $f"
|
||||
sed 's/^/ /' /tmp/sc.log
|
||||
fi
|
||||
done
|
||||
exit "$rc"
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
# yamllint для HomeLab infras.
|
||||
# Цель: ловить реальные поломки YAML (табы, дубли ключей, битые отступы,
|
||||
# незакрытые кавычки), а не навязывать стиль. Всё, что даёт шум на живом
|
||||
# Ansible-коде, ослаблено осознанно — см. комментарии.
|
||||
|
||||
extends: default
|
||||
|
||||
# gitignore-style пути, которые линтить не нужно.
|
||||
ignore: |
|
||||
/ansible/.venv/
|
||||
/ansible/collections/
|
||||
/ansible/generated/
|
||||
/archive/
|
||||
/.opencode/
|
||||
/tools/
|
||||
node_modules/
|
||||
/ansible/inventory/host_vars/*/vault.yml
|
||||
/.direnv/
|
||||
/result
|
||||
/result-*
|
||||
|
||||
rules:
|
||||
# Длинные строки — норма: pct/docker/curl-команды в one-line shell,
|
||||
# длинные URL, ZeroTier/OpenVPN-конфиги. Перенос сделал бы их менее читаемыми.
|
||||
line-length: disable
|
||||
|
||||
# Ansible исторически допускает yes/no наравне с true/false, и часть репо
|
||||
# написана так. Не ошибка — не мешаем.
|
||||
truthy:
|
||||
allowed-values: ['true', 'false', 'yes', 'no']
|
||||
check-keys: false
|
||||
|
||||
# Комментарии вида "#comment" и inline-комментарии на одном пробеле
|
||||
# встречаются часто и ни на что не влияют.
|
||||
comments:
|
||||
require-starting-space: true
|
||||
min-spaces-from-content: 1
|
||||
comments-indentation: disable
|
||||
|
||||
# Ansible-стиль: списки под ключом с отступом — вопрос вкуса, обе формы валидны.
|
||||
indentation:
|
||||
spaces: 2
|
||||
indent-sequences: consistent
|
||||
check-multi-line-strings: false
|
||||
|
||||
# Пустая строка в конце файла обязательна (реальные diff-артефакты),
|
||||
# но лишние пробелы в конце строк — только предупреждение.
|
||||
trailing-spaces: enable
|
||||
new-line-at-end-of-file: enable
|
||||
|
||||
# Ошибки, которые ломают парсинг или молча меняют смысл — строго.
|
||||
key-duplicates: enable
|
||||
octal-values:
|
||||
forbid-implicit-octal: true
|
||||
forbid-explicit-octal: true
|
||||
|
||||
# "---" в начале файла — полезная конвенция Ansible, но не критично.
|
||||
document-start:
|
||||
level: warning
|
||||
|
||||
# braces/brackets: Jinja-выражения в inline-словарях часто дают ложные
|
||||
# срабатывания на пробелах внутри {{ }}.
|
||||
braces:
|
||||
min-spaces-inside: 0
|
||||
max-spaces-inside: 1
|
||||
brackets:
|
||||
min-spaces-inside: 0
|
||||
max-spaces-inside: 1
|
||||
Reference in New Issue
Block a user