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