55 lines
2.2 KiB
Go
55 lines
2.2 KiB
Go
package lint
|
||
|
||
import (
|
||
"strings"
|
||
|
||
"github.com/vladimir/goherence/internal/parser"
|
||
)
|
||
|
||
// shellToModuleHints — известные паттерны команд, для которых есть
|
||
// прямой декларативный модуль. Список расширяется по мере роста
|
||
// internal/module/builtin — если добавляешь builtin-модуль, стоит
|
||
// сразу подумать, какие shell-паттерны он делает лишними.
|
||
var shellToModuleHints = map[string]string{
|
||
"apt-get install": "apt",
|
||
"apt install": "apt",
|
||
"yum install": "rpm_package",
|
||
"dnf install": "rpm_package",
|
||
"systemctl": "systemd",
|
||
"mkdir -p": "file (state=directory)",
|
||
"chmod": "file (mode=...)",
|
||
"rm -rf": "file (state=absent)",
|
||
}
|
||
|
||
// ShellUsageRule ловит использование shell/command там, где уже есть
|
||
// builtin-модуль с тем же эффектом, но идемпотентный по своей природе.
|
||
// Единственный способ обойти правило — явная пометка allow_shell: true
|
||
// прямо на таске: она остаётся видна в коде плейбука навсегда, так что
|
||
// причина обхода на виду у любого, кто читает файл при code review.
|
||
type ShellUsageRule struct{}
|
||
|
||
func (r *ShellUsageRule) Name() string { return "shell-usage" }
|
||
|
||
func (r *ShellUsageRule) Check(pb *parser.Playbook) []Violation {
|
||
var out []Violation
|
||
walkTasks(pb, func(loc string, t *parser.Task) {
|
||
if t.Module != "shell" && t.Module != "command" {
|
||
return
|
||
}
|
||
if t.AllowShell {
|
||
return
|
||
}
|
||
cmd, _ := t.Args["cmd"].(string)
|
||
for pattern, alt := range shellToModuleHints {
|
||
if strings.Contains(cmd, pattern) {
|
||
out = append(out, Violation{
|
||
Rule: r.Name(), Severity: Error, Location: loc,
|
||
Message: "используй декларативный модуль " + alt +
|
||
" вместо shell/command, либо явно пометь таск allow_shell: true: " + cmd,
|
||
})
|
||
return // одного совпадения достаточно, не дублируем сообщение
|
||
}
|
||
}
|
||
})
|
||
return out
|
||
}
|