refactor: drop default standards version and rename to rules

This commit is contained in:
Robert Kaussow 2024-01-25 21:09:05 +01:00
parent 7f5c2a51b1
commit 137e559d57
Signed by: xoxys
GPG Key ID: 4E692A2EAECC03C0
48 changed files with 197 additions and 321 deletions

View File

@ -7,8 +7,8 @@ import sys
from ansiblelater import LOG, __version__, logger from ansiblelater import LOG, __version__, logger
from ansiblelater.candidate import Candidate from ansiblelater.candidate import Candidate
from ansiblelater.rule import SingleRules
from ansiblelater.settings import Settings from ansiblelater.settings import Settings
from ansiblelater.standard import SingleStandards
def main(): def main():
@ -22,33 +22,33 @@ def main():
parser.add_argument( parser.add_argument(
"-r", "-r",
"--rules-dir", "--rules-dir",
dest="rules.standards", dest="rules.dir",
metavar="RULES", metavar="DIR",
action="append", action="append",
help="directory of standard rules", help="directory of rules",
) )
parser.add_argument( parser.add_argument(
"-B", "-B",
"--no-buildin", "--no-builtin",
dest="rules.buildin", dest="rules.builtin",
action="store_false", action="store_false",
help="disables build-in standard rules", help="disables built-in rules",
) )
parser.add_argument( parser.add_argument(
"-s", "-i",
"--standards", "--include-rules",
dest="rules.filter", dest="rules.include_filter",
metavar="FILTER", metavar="TAGS",
action="append", action="append",
help="limit standards to given ID's", help="limit rules to given id/tags",
) )
parser.add_argument( parser.add_argument(
"-x", "-x",
"--exclude-standards", "--exclude-rules",
dest="rules.exclude_filter", dest="rules.exclude_filter",
metavar="EXCLUDE_FILTER", metavar="TAGS",
action="append", action="append",
help="exclude standards by given ID's", help="exclude rules by given it/tags",
) )
parser.add_argument( parser.add_argument(
"-v", dest="logging.level", action="append_const", const=-1, help="increase log level" "-v", dest="logging.level", action="append_const", const=-1, help="increase log level"
@ -65,7 +65,7 @@ def main():
config = settings.config config = settings.config
logger.update_logger(LOG, config["logging"]["level"], config["logging"]["json"]) logger.update_logger(LOG, config["logging"]["level"], config["logging"]["json"])
SingleStandards(config["rules"]["standards"]) SingleRules(config["rules"]["dir"])
workers = max(multiprocessing.cpu_count() - 2, 2) workers = max(multiprocessing.cpu_count() - 2, 2)
p = multiprocessing.Pool(workers) p = multiprocessing.Pool(workers)

View File

@ -3,14 +3,12 @@
import codecs import codecs
import copy import copy
import os import os
import re
from ansible.plugins.loader import module_loader from ansible.plugins.loader import module_loader
from packaging.version import Version
from ansiblelater import LOG, utils from ansiblelater import LOG
from ansiblelater.logger import flag_extra from ansiblelater.logger import flag_extra
from ansiblelater.standard import SingleStandards, StandardBase from ansiblelater.rule import RuleBase, SingleRules
class Candidate: class Candidate:
@ -21,7 +19,7 @@ class Candidate:
bundled with necessary meta informations for rule processing. bundled with necessary meta informations for rule processing.
""" """
def __init__(self, filename, settings={}, standards=[]): # noqa def __init__(self, filename, settings={}, rules=[]): # noqa
self.path = filename self.path = filename
self.binary = False self.binary = False
self.vault = False self.vault = False
@ -37,107 +35,58 @@ class Candidate:
except UnicodeDecodeError: except UnicodeDecodeError:
self.binary = True self.binary = True
def _get_version(self): def _filter_rules(self):
name = type(self).__name__ target_rules = []
path = self.path includes = self.config["rules"]["include_filter"]
version = None
config_version = self.config["rules"]["version"].strip()
if config_version:
version_config_re = re.compile(r"([\d.]+)")
match = version_config_re.match(config_version)
if match:
version = match.group(1)
if not self.binary:
if isinstance(self, RoleFile):
parentdir = os.path.dirname(os.path.abspath(self.path))
while parentdir != os.path.dirname(parentdir):
meta_file = os.path.join(parentdir, "meta", "main.yml")
if os.path.exists(meta_file):
path = meta_file
break
parentdir = os.path.dirname(parentdir)
version_file_re = re.compile(r"^# Standards:\s*([\d.]+)")
with codecs.open(path, mode="rb", encoding="utf-8") as f:
for line in f:
match = version_file_re.match(line)
if match:
version = match.group(1)
if version:
LOG.info(f"{name} {path} declares standards version {version}")
return version
def _filter_standards(self):
target_standards = []
includes = self.config["rules"]["filter"]
excludes = self.config["rules"]["exclude_filter"] excludes = self.config["rules"]["exclude_filter"]
if len(includes) == 0: if len(includes) == 0:
includes = [s.sid for s in self.standards] includes = [s.sid for s in self.rules]
for standard in self.standards: for rule in self.rules:
if standard.sid in includes and standard.sid not in excludes: if rule.sid in includes and rule.sid not in excludes:
target_standards.append(standard) target_rules.append(rule)
return target_standards return target_rules
def review(self): def review(self):
errors = 0 errors = 0
self.standards = SingleStandards(self.config["rules"]["standards"]).rules self.rules = SingleRules(self.config["rules"]["dir"]).rules
self.version_config = self._get_version()
self.version = self.version_config or utils.standards_latest(self.standards)
for standard in self._filter_standards(): for rule in self._filter_rules():
if type(self).__name__.lower() not in standard.types: if type(self).__name__.lower() not in rule.types:
continue continue
result = standard.check(self, self.config) result = rule.check(self, self.config)
if not result: if not result:
LOG.error( LOG.error(f"rule '{rule.sid}' returns an empty result object. Check failed!")
f"Standard '{standard.sid}' returns an empty result object. Check failed!"
)
continue continue
labels = { labels = {
"tag": "review", "tag": "review",
"standard": standard.description, "rule": rule.description,
"file": self.path, "file": self.path,
"passed": True, "passed": True,
} }
if standard.sid and standard.sid.strip(): if rule.sid and rule.sid.strip():
labels["sid"] = standard.sid labels["sid"] = rule.sid
for err in result.errors: for err in result.errors:
err_labels = copy.copy(labels) err_labels = copy.copy(labels)
err_labels["passed"] = False err_labels["passed"] = False
sid = self._format_id(standard.sid) sid = self._format_id(rule.sid)
path = self.path path = self.path
description = standard.description description = rule.description
if isinstance(err, StandardBase.Error): if isinstance(err, RuleBase.Error):
err_labels.update(err.to_dict()) err_labels.update(err.to_dict())
if not standard.version: msg = f"{sid}rule '{description}' not met:\n{path}:{err}"
LOG.warning(
f"{sid}Best practice '{description}' not met:\n{path}:{err}",
extra=flag_extra(err_labels),
)
elif Version(standard.version) > Version(self.version):
LOG.warning(
f"{sid}Future standard '{description}' not met:\n{path}:{err}",
extra=flag_extra(err_labels),
)
else:
msg = f"{sid}Standard '{description}' not met:\n{path}:{err}"
if standard.sid not in self.config["rules"]["warning_filter"]: if rule.sid not in self.config["rules"]["warning_filter"]:
LOG.error(msg, extra=flag_extra(err_labels)) LOG.error(msg, extra=flag_extra(err_labels))
errors = errors + 1 errors = errors + 1
else: else:
@ -146,54 +95,54 @@ class Candidate:
return errors return errors
@staticmethod @staticmethod
def classify(filename, settings={}, standards=[]): # noqa def classify(filename, settings={}, rules=[]): # noqa
parentdir = os.path.basename(os.path.dirname(filename)) parentdir = os.path.basename(os.path.dirname(filename))
basename = os.path.basename(filename) basename = os.path.basename(filename)
ext = os.path.splitext(filename)[1][1:] ext = os.path.splitext(filename)[1][1:]
if parentdir in ["tasks"]: if parentdir in ["tasks"]:
return Task(filename, settings, standards) return Task(filename, settings, rules)
if parentdir in ["handlers"]: if parentdir in ["handlers"]:
return Handler(filename, settings, standards) return Handler(filename, settings, rules)
if parentdir in ["vars", "defaults"]: if parentdir in ["vars", "defaults"]:
return RoleVars(filename, settings, standards) return RoleVars(filename, settings, rules)
if "group_vars" in filename.split(os.sep): if "group_vars" in filename.split(os.sep):
return GroupVars(filename, settings, standards) return GroupVars(filename, settings, rules)
if "host_vars" in filename.split(os.sep): if "host_vars" in filename.split(os.sep):
return HostVars(filename, settings, standards) return HostVars(filename, settings, rules)
if parentdir in ["meta"] and "main" in basename: if parentdir in ["meta"] and "main" in basename:
return Meta(filename, settings, standards) return Meta(filename, settings, rules)
if parentdir in ["meta"] and "argument_specs" in basename: if parentdir in ["meta"] and "argument_specs" in basename:
return ArgumentSpecs(filename, settings, standards) return ArgumentSpecs(filename, settings, rules)
if parentdir in [ if parentdir in [
"library", "library",
"lookup_plugins", "lookup_plugins",
"callback_plugins", "callback_plugins",
"filter_plugins", "filter_plugins",
] or filename.endswith(".py"): ] or filename.endswith(".py"):
return Code(filename, settings, standards) return Code(filename, settings, rules)
if basename == "inventory" or basename == "hosts" or parentdir in ["inventories"]: if basename == "inventory" or basename == "hosts" or parentdir in ["inventories"]:
return Inventory(filename, settings, standards) return Inventory(filename, settings, rules)
if "rolesfile" in basename or ("requirements" in basename and ext in ["yaml", "yml"]): if "rolesfile" in basename or ("requirements" in basename and ext in ["yaml", "yml"]):
return Rolesfile(filename, settings, standards) return Rolesfile(filename, settings, rules)
if "Makefile" in basename: if "Makefile" in basename:
return Makefile(filename, settings, standards) return Makefile(filename, settings, rules)
if "templates" in filename.split(os.sep) or basename.endswith(".j2"): if "templates" in filename.split(os.sep) or basename.endswith(".j2"):
return Template(filename, settings, standards) return Template(filename, settings, rules)
if "files" in filename.split(os.sep): if "files" in filename.split(os.sep):
return File(filename, settings, standards) return File(filename, settings, rules)
if basename.endswith(".yml") or basename.endswith(".yaml"): if basename.endswith(".yml") or basename.endswith(".yaml"):
return Playbook(filename, settings, standards) return Playbook(filename, settings, rules)
if "README" in basename: if "README" in basename:
return Doc(filename, settings, standards) return Doc(filename, settings, rules)
return None return None
def _format_id(self, standard_id): def _format_id(self, rule_id):
sid = standard_id.strip() sid = rule_id.strip()
if sid: if sid:
standard_id = f"[{sid}] " rule_id = f"[{sid}] "
return standard_id return rule_id
def __repr__(self): def __repr__(self):
return f"{type(self).__name__} ({self.path})" return f"{type(self).__name__} ({self.path})"
@ -205,8 +154,8 @@ class Candidate:
class RoleFile(Candidate): class RoleFile(Candidate):
"""Object classified as Ansible role file.""" """Object classified as Ansible role file."""
def __init__(self, filename, settings={}, standards=[]): # noqa def __init__(self, filename, settings={}, rules=[]): # noqa
super().__init__(filename, settings, standards) super().__init__(filename, settings, rules)
parentdir = os.path.dirname(os.path.abspath(filename)) parentdir = os.path.dirname(os.path.abspath(filename))
while parentdir != os.path.dirname(parentdir): while parentdir != os.path.dirname(parentdir):
@ -226,16 +175,16 @@ class Playbook(Candidate):
class Task(RoleFile): class Task(RoleFile):
"""Object classified as Ansible task file.""" """Object classified as Ansible task file."""
def __init__(self, filename, settings={}, standards=[]): # noqa def __init__(self, filename, settings={}, rules=[]): # noqa
super().__init__(filename, settings, standards) super().__init__(filename, settings, rules)
self.filetype = "tasks" self.filetype = "tasks"
class Handler(RoleFile): class Handler(RoleFile):
"""Object classified as Ansible handler file.""" """Object classified as Ansible handler file."""
def __init__(self, filename, settings={}, standards=[]): # noqa def __init__(self, filename, settings={}, rules=[]): # noqa
super().__init__(filename, settings, standards) super().__init__(filename, settings, rules)
self.filetype = "handlers" self.filetype = "handlers"

View File

@ -1,4 +1,4 @@
"""Standard definition.""" """Rule definition."""
import copy import copy
import importlib import importlib
@ -27,22 +27,21 @@ from ansiblelater.utils.yamlhelper import (
) )
class StandardMeta(type): class RuleMeta(type):
def __call__(cls, *args): def __call__(cls, *args):
mcls = type.__call__(cls, *args) mcls = type.__call__(cls, *args)
mcls.sid = cls.sid mcls.sid = cls.sid
mcls.description = getattr(cls, "description", "__unknown__") mcls.description = getattr(cls, "description", "__unknown__")
mcls.helptext = getattr(cls, "helptext", "") mcls.helptext = getattr(cls, "helptext", "")
mcls.version = getattr(cls, "version", None)
mcls.types = getattr(cls, "types", []) mcls.types = getattr(cls, "types", [])
return mcls return mcls
class StandardExtendedMeta(StandardMeta, ABCMeta): class RuleExtendedMeta(RuleMeta, ABCMeta):
pass pass
class StandardBase(metaclass=StandardExtendedMeta): class RuleBase(metaclass=RuleExtendedMeta):
SHELL_PIPE_CHARS = "&|<>;$\n*[]{}?" SHELL_PIPE_CHARS = "&|<>;$\n*[]{}?"
@property @property
@ -55,7 +54,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
pass pass
def __repr__(self): def __repr__(self):
return f"Standard: {self.description} (version: {self.version}, types: {self.types})" return f"Rule: {self.description} (types: {self.types})"
@staticmethod @staticmethod
def get_tasks(candidate, settings): # noqa def get_tasks(candidate, settings): # noqa
@ -69,11 +68,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
except LaterError as ex: except LaterError as ex:
e = ex.original e = ex.original
errors.append( errors.append(
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}") RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
) )
candidate.faulty = True candidate.faulty = True
except LaterAnsibleError as e: except LaterAnsibleError as e:
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}")) errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
candidate.faulty = True candidate.faulty = True
return yamllines, errors return yamllines, errors
@ -93,11 +92,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
except LaterError as ex: except LaterError as ex:
e = ex.original e = ex.original
errors.append( errors.append(
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}") RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
) )
candidate.faulty = True candidate.faulty = True
except LaterAnsibleError as e: except LaterAnsibleError as e:
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}")) errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
candidate.faulty = True candidate.faulty = True
return tasks, errors return tasks, errors
@ -115,11 +114,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
except LaterError as ex: except LaterError as ex:
e = ex.original e = ex.original
errors.append( errors.append(
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}") RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
) )
candidate.faulty = True candidate.faulty = True
except LaterAnsibleError as e: except LaterAnsibleError as e:
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}")) errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
candidate.faulty = True candidate.faulty = True
return normalized, errors return normalized, errors
@ -159,11 +158,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
except LaterError as ex: except LaterError as ex:
e = ex.original e = ex.original
errors.append( errors.append(
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}") RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
) )
candidate.faulty = True candidate.faulty = True
except LaterAnsibleError as e: except LaterAnsibleError as e:
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}")) errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
candidate.faulty = True candidate.faulty = True
return normalized, errors return normalized, errors
@ -184,11 +183,11 @@ class StandardBase(metaclass=StandardExtendedMeta):
except LaterError as ex: except LaterError as ex:
e = ex.original e = ex.original
errors.append( errors.append(
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}") RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
) )
candidate.faulty = True candidate.faulty = True
except LaterAnsibleError as e: except LaterAnsibleError as e:
errors.append(StandardBase.Error(e.line, f"syntax error: {e.message}")) errors.append(RuleBase.Error(e.line, f"syntax error: {e.message}"))
candidate.faulty = True candidate.faulty = True
return yamllines, errors return yamllines, errors
@ -210,7 +209,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
content = yaml.safe_load(f) content = yaml.safe_load(f)
except yaml.YAMLError as e: except yaml.YAMLError as e:
errors.append( errors.append(
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}") RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
) )
candidate.faulty = True candidate.faulty = True
@ -224,14 +223,14 @@ class StandardBase(metaclass=StandardExtendedMeta):
try: try:
with open(candidate.path, encoding="utf-8") as f: with open(candidate.path, encoding="utf-8") as f:
for problem in linter.run(f, YamlLintConfig(options)): for problem in linter.run(f, YamlLintConfig(options)):
errors.append(StandardBase.Error(problem.line, problem.desc)) errors.append(RuleBase.Error(problem.line, problem.desc))
except yaml.YAMLError as e: except yaml.YAMLError as e:
errors.append( errors.append(
StandardBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}") RuleBase.Error(e.problem_mark.line + 1, f"syntax error: {e.problem}")
) )
candidate.faulty = True candidate.faulty = True
except (TypeError, ValueError) as e: except (TypeError, ValueError) as e:
errors.append(StandardBase.Error(None, f"yamllint error: {e}")) errors.append(RuleBase.Error(None, f"yamllint error: {e}"))
candidate.faulty = True candidate.faulty = True
return errors return errors
@ -302,7 +301,7 @@ class StandardBase(metaclass=StandardExtendedMeta):
return "\n".join([f"{self.candidate}:{error}" for error in self.errors]) return "\n".join([f"{self.candidate}:{error}" for error in self.errors])
class StandardLoader: class RulesLoader:
def __init__(self, source): def __init__(self, source):
self.rules = [] self.rules = []
@ -331,10 +330,7 @@ class StandardLoader:
def _is_plugin(self, obj): def _is_plugin(self, obj):
return ( return (
inspect.isclass(obj) inspect.isclass(obj) and issubclass(obj, RuleBase) and obj is not RuleBase and not None
and issubclass(obj, StandardBase)
and obj is not StandardBase
and not None
) )
def validate(self): def validate(self):
@ -343,11 +339,11 @@ class StandardLoader:
all_std = len(normalized_std) all_std = len(normalized_std)
if all_std != unique_std: if all_std != unique_std:
sysexit_with_message( sysexit_with_message(
"Detect duplicate ID's in standards definition. Please use unique ID's only." "Found duplicate tags in rules definition. Please use unique tags only."
) )
class SingleStandards(StandardLoader, metaclass=Singleton): class SingleRules(RulesLoader, metaclass=Singleton):
"""Singleton config class.""" """Singleton config class."""
pass pass

View File

@ -1,11 +1,10 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckBecomeUser(StandardBase): class CheckBecomeUser(RuleBase):
sid = "ANSIBLE0015" sid = "ANSIBLE0015"
description = "Become should be combined with become_user" description = "Become should be combined with become_user"
helptext = "the task has `become` enabled but `become_user` is missing" helptext = "the task has `become` enabled but `become_user` is missing"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,14 +1,13 @@
import re import re
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
from ansiblelater.utils import count_spaces from ansiblelater.utils import count_spaces
class CheckBracesSpaces(StandardBase): class CheckBracesSpaces(RuleBase):
sid = "ANSIBLE0004" sid = "ANSIBLE0004"
description = "YAML should use consistent number of spaces around variables" description = "YAML should use consistent number of spaces around variables"
helptext = "no suitable numbers of spaces (min: {min} max: {max})" helptext = "no suitable numbers of spaces (min: {min} max: {max})"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -18,14 +18,13 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE. # THE SOFTWARE.
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckChangedInWhen(StandardBase): class CheckChangedInWhen(RuleBase):
sid = "ANSIBLE0026" sid = "ANSIBLE0026"
description = "Use handlers instead of `when: changed`" description = "Use handlers instead of `when: changed`"
helptext = "tasks using `when: result.changed` setting are effectively acting as a handler" helptext = "tasks using `when: result.changed` setting are effectively acting as a handler"
version = "0.2"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,14 +1,13 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckCommandHasChanges(StandardBase): class CheckCommandHasChanges(RuleBase):
sid = "ANSIBLE0011" sid = "ANSIBLE0011"
description = "Commands should be idempotent" description = "Commands should be idempotent"
helptext = ( helptext = (
"commands should only read while using `changed_when` or try to be " "commands should only read while using `changed_when` or try to be "
"idempotent while using controls like `creates`, `removes` or `when`" "idempotent while using controls like `creates`, `removes` or `when`"
) )
version = "0.1"
types = ["playbook", "task"] types = ["playbook", "task"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -20,14 +20,13 @@
import os import os
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckCommandInsteadOfArgument(StandardBase): class CheckCommandInsteadOfArgument(RuleBase):
sid = "ANSIBLE0017" sid = "ANSIBLE0017"
description = "Commands should not be used in place of module arguments" description = "Commands should not be used in place of module arguments"
helptext = "{exec} used in place of file modules argument {arg}" helptext = "{exec} used in place of file modules argument {arg}"
version = "0.2"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
import os import os
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckCommandInsteadOfModule(StandardBase): class CheckCommandInsteadOfModule(RuleBase):
sid = "ANSIBLE0008" sid = "ANSIBLE0008"
description = "Commands should not be used in place of modules" description = "Commands should not be used in place of modules"
helptext = "{exec} command used in place of {module} module" helptext = "{exec} command used in place of {module} module"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,14 +1,13 @@
import re import re
from ansiblelater.candidate import Template from ansiblelater.candidate import Template
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckCompareToEmptyString(StandardBase): class CheckCompareToEmptyString(RuleBase):
sid = "ANSIBLE0012" sid = "ANSIBLE0012"
description = 'Don\'t compare to empty string ""' description = 'Don\'t compare to empty string ""'
helptext = "use `when: var` rather than `when: var !=` (or conversely `when: not var`)" helptext = "use `when: var` rather than `when: var !=` (or conversely `when: not var`)"
version = "0.1"
types = ["playbook", "task", "handler", "template"] types = ["playbook", "task", "handler", "template"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,14 +1,13 @@
import re import re
from ansiblelater.candidate import Template from ansiblelater.candidate import Template
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckCompareToLiteralBool(StandardBase): class CheckCompareToLiteralBool(RuleBase):
sid = "ANSIBLE0013" sid = "ANSIBLE0013"
description = "Don't compare to True or False" description = "Don't compare to True or False"
helptext = "use `when: var` rather than `when: var == True` (or conversely `when: not var`)" helptext = "use `when: var` rather than `when: var == True` (or conversely `when: not var`)"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,11 +1,10 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckDeprecated(StandardBase): class CheckDeprecated(RuleBase):
sid = "ANSIBLE9999" sid = "ANSIBLE9999"
description = "Deprecated features should not be used" description = "Deprecated features should not be used"
helptext = "'{old}' is deprecated and should not be used anymore. Use '{new}' instead." helptext = "'{old}' is deprecated and should not be used anymore. Use '{new}' instead."
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -20,18 +20,17 @@
import os import os
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
from ansiblelater.utils import has_glob, has_jinja from ansiblelater.utils import has_glob, has_jinja
class CheckDeprecatedBareVars(StandardBase): class CheckDeprecatedBareVars(RuleBase):
sid = "ANSIBLE0027" sid = "ANSIBLE0027"
description = "Deprecated bare variables in loops must not be used" description = "Deprecated bare variables in loops must not be used"
helptext = ( helptext = (
"bare var '{barevar}' in '{loop_type}' must use full var syntax '{{{{ {barevar} }}}}' " "bare var '{barevar}' in '{loop_type}' must use full var syntax '{{{{ {barevar} }}}}' "
"or be converted to a list" "or be converted to a list"
) )
version = "0.3"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -19,17 +19,16 @@
# THE SOFTWARE. # THE SOFTWARE.
import re import re
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckFilePermissionMissing(StandardBase): class CheckFilePermissionMissing(RuleBase):
sid = "ANSIBLE0018" sid = "ANSIBLE0018"
description = "File permissions unset or incorrect" description = "File permissions unset or incorrect"
helptext = ( helptext = (
"`mode` parameter should set permissions explicitly (e.g. `mode: 0644`) " "`mode` parameter should set permissions explicitly (e.g. `mode: 0644`) "
"to avoid unexpected file permissions" "to avoid unexpected file permissions"
) )
version = "0.2"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
_modules = { _modules = {

View File

@ -18,14 +18,13 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE. # THE SOFTWARE.
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckFilePermissionOctal(StandardBase): class CheckFilePermissionOctal(RuleBase):
sid = "ANSIBLE0019" sid = "ANSIBLE0019"
description = "Octal file permissions must contain leading zero or be a string" description = "Octal file permissions must contain leading zero or be a string"
helptext = "numeric file permissions without leading zero can behave in unexpected ways" helptext = "numeric file permissions without leading zero can behave in unexpected ways"
version = "0.2"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
import re import re
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckFilterSeparation(StandardBase): class CheckFilterSeparation(RuleBase):
sid = "ANSIBLE0016" sid = "ANSIBLE0016"
description = "Jinja2 filters should be separated with spaces" description = "Jinja2 filters should be separated with spaces"
helptext = "no suitable numbers of spaces (required: 1)" helptext = "no suitable numbers of spaces (required: 1)"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -18,14 +18,13 @@
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE. # THE SOFTWARE.
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckGitHasVersion(StandardBase): class CheckGitHasVersion(RuleBase):
sid = "ANSIBLE0020" sid = "ANSIBLE0020"
description = "Git checkouts should use explicit version" description = "Git checkouts should use explicit version"
helptext = "git checkouts should point to an explicit commit or tag, not `latest`" helptext = "git checkouts should point to an explicit commit or tag, not `latest`"
version = "0.2"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,11 +1,10 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckInstallUseLatest(StandardBase): class CheckInstallUseLatest(RuleBase):
sid = "ANSIBLE0009" sid = "ANSIBLE0009"
description = "Package installs should use present, not latest" description = "Package installs should use present, not latest"
helptext = "package installs should use `state=present` with or without a version" helptext = "package installs should use `state=present` with or without a version"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
import re import re
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckLiteralBoolFormat(StandardBase): class CheckLiteralBoolFormat(RuleBase):
sid = "ANSIBLE0014" sid = "ANSIBLE0014"
description = "Literal bools should be consistent" description = "Literal bools should be consistent"
helptext = "literal bools should be written as `{bools}`" helptext = "literal bools should be written as `{bools}`"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp> # Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp>
# Copyright (c) 2018, Ansible Project # Copyright (c) 2018, Ansible Project
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckLocalAction(StandardBase): class CheckLocalAction(RuleBase):
sid = "ANSIBLE0024" sid = "ANSIBLE0024"
description = "Don't use local_action" description = "Don't use local_action"
helptext = "`delegate_to: localhost` should be used instead of `local_action`" helptext = "`delegate_to: localhost` should be used instead of `local_action`"
version = "0.2"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,14 +1,13 @@
# Copyright (c) 2018, Ansible Project # Copyright (c) 2018, Ansible Project
from nested_lookup import nested_lookup from nested_lookup import nested_lookup
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckMetaChangeFromDefault(StandardBase): class CheckMetaChangeFromDefault(RuleBase):
sid = "ANSIBLE0021" sid = "ANSIBLE0021"
description = "Roles meta/main.yml default values should be changed" description = "Roles meta/main.yml default values should be changed"
helptext = "meta/main.yml default values should be changed for: `{field}`" helptext = "meta/main.yml default values should be changed for: `{field}`"
version = "0.2"
types = ["meta"] types = ["meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
from nested_lookup import nested_lookup from nested_lookup import nested_lookup
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckMetaMain(StandardBase): class CheckMetaMain(RuleBase):
sid = "ANSIBLE0002" sid = "ANSIBLE0002"
description = "Roles must contain suitable meta/main.yml" description = "Roles must contain suitable meta/main.yml"
helptext = "file should contain `{key}` key" helptext = "file should contain `{key}` key"
version = "0.1"
types = ["meta"] types = ["meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
from collections import defaultdict from collections import defaultdict
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckNameFormat(StandardBase): class CheckNameFormat(RuleBase):
sid = "ANSIBLE0007" sid = "ANSIBLE0007"
description = "Name of tasks and handlers must be formatted" description = "Name of tasks and handlers must be formatted"
helptext = "name '{name}' should start with uppercase" helptext = "name '{name}' should start with uppercase"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,11 +1,10 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckNamedTask(StandardBase): class CheckNamedTask(RuleBase):
sid = "ANSIBLE0006" sid = "ANSIBLE0006"
description = "Tasks and handlers must be named" description = "Tasks and handlers must be named"
helptext = "module '{module}' used without or empty `name` attribute" helptext = "module '{module}' used without or empty `name` attribute"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,11 +1,10 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckNativeYaml(StandardBase): class CheckNativeYaml(RuleBase):
sid = "LINT0008" sid = "LINT0008"
description = "Use YAML format for tasks and handlers rather than key=value" description = "Use YAML format for tasks and handlers rather than key=value"
helptext = "task arguments appear to be in key value rather than YAML format" helptext = "task arguments appear to be in key value rather than YAML format"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -21,17 +21,16 @@
# THE SOFTWARE. # THE SOFTWARE.
import re import re
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckNestedJinja(StandardBase): class CheckNestedJinja(RuleBase):
sid = "ANSIBLE0023" sid = "ANSIBLE0023"
description = "Don't use nested Jinja2 pattern" description = "Don't use nested Jinja2 pattern"
helptext = ( helptext = (
"there should not be any nested jinja pattern " "there should not be any nested jinja pattern "
"like `{{ list_one + {{ list_two | max }} }}`" "like `{{ list_one + {{ list_two | max }} }}`"
) )
version = "0.2"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
# Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp> # Copyright (c) 2016, Tsukinowa Inc. <info@tsukinowa.jp>
# Copyright (c) 2018, Ansible Project # Copyright (c) 2018, Ansible Project
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckRelativeRolePaths(StandardBase): class CheckRelativeRolePaths(RuleBase):
sid = "ANSIBLE0025" sid = "ANSIBLE0025"
description = "Don't use a relative path in a role" description = "Don't use a relative path in a role"
helptext = "`copy` and `template` modules don't need relative path for `src`" helptext = "`copy` and `template` modules don't need relative path for `src`"
version = "0.2"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
from ansible.parsing.yaml.objects import AnsibleMapping from ansible.parsing.yaml.objects import AnsibleMapping
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckScmInSrc(StandardBase): class CheckScmInSrc(RuleBase):
sid = "ANSIBLE0005" sid = "ANSIBLE0005"
description = "Use `scm:` key rather than `src: scm+url`" description = "Use `scm:` key rather than `src: scm+url`"
helptext = "usage of `src: scm+url` not recommended" helptext = "usage of `src: scm+url` not recommended"
version = "0.1"
types = ["rolesfile"] types = ["rolesfile"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,11 +1,10 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckShellInsteadCommand(StandardBase): class CheckShellInsteadCommand(RuleBase):
sid = "ANSIBLE0010" sid = "ANSIBLE0010"
description = "Shell should only be used when essential" description = "Shell should only be used when essential"
helptext = "shell should only be used when piping, redirecting or chaining commands" helptext = "shell should only be used when piping, redirecting or chaining commands"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,14 +1,13 @@
import re import re
from collections import defaultdict from collections import defaultdict
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckTaskSeparation(StandardBase): class CheckTaskSeparation(RuleBase):
sid = "ANSIBLE0001" sid = "ANSIBLE0001"
description = "Single tasks should be separated by empty line" description = "Single tasks should be separated by empty line"
helptext = "missing task separation (required: 1 empty line)" helptext = "missing task separation (required: 1 empty line)"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
from collections import defaultdict from collections import defaultdict
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckUniqueNamedTask(StandardBase): class CheckUniqueNamedTask(RuleBase):
sid = "ANSIBLE0003" sid = "ANSIBLE0003"
description = "Tasks and handlers must be uniquely named within a single file" description = "Tasks and handlers must be uniquely named within a single file"
helptext = "name '{name}' appears multiple times" helptext = "name '{name}' appears multiple times"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,16 +0,0 @@
from ansiblelater.standard import StandardBase
class CheckVersion(StandardBase):
sid = "ANSIBLE9998"
description = "Standards version should be pinned"
helptext = "Standards version not set. Using latest standards version {version}"
types = ["task", "handler", "rolevars", "meta", "template", "file", "playbook"]
def check(self, candidate, settings): # noqa
errors = []
if not candidate.version_config:
errors.append(self.Error(None, self.helptext.format(version=candidate.version)))
return self.Result(candidate.path, errors)

View File

@ -1,13 +1,12 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckWhenFormat(StandardBase): class CheckWhenFormat(RuleBase):
sid = "ANSIBLE0022" sid = "ANSIBLE0022"
description = "Don't use Jinja2 in when" description = "Don't use Jinja2 in when"
helptext = ( helptext = (
"`when` is a raw Jinja2 expression, redundant {{ }} " "should be removed from variable(s)" "`when` is a raw Jinja2 expression, redundant {{ }} " "should be removed from variable(s)"
) )
version = "0.2"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,10 +1,9 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckYamlColons(StandardBase): class CheckYamlColons(RuleBase):
sid = "LINT0005" sid = "LINT0005"
description = "YAML should use consistent number of spaces around colons" description = "YAML should use consistent number of spaces around colons"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,10 +1,9 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckYamlDocumentEnd(StandardBase): class CheckYamlDocumentEnd(RuleBase):
sid = "LINT0009" sid = "LINT0009"
description = "YAML should contain document end marker" description = "YAML should contain document end marker"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,10 +1,9 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckYamlDocumentStart(StandardBase): class CheckYamlDocumentStart(RuleBase):
sid = "LINT0004" sid = "LINT0004"
description = "YAML should contain document start marker" description = "YAML should contain document start marker"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,10 +1,9 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckYamlEmptyLines(StandardBase): class CheckYamlEmptyLines(RuleBase):
sid = "LINT0001" sid = "LINT0001"
description = "YAML should not contain unnecessarily empty lines" description = "YAML should not contain unnecessarily empty lines"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,13 +1,12 @@
import os import os
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckYamlFile(StandardBase): class CheckYamlFile(RuleBase):
sid = "LINT0006" sid = "LINT0006"
description = "Roles file should be in yaml format" description = "Roles file should be in yaml format"
helptext = "file does not have a .yml extension" helptext = "file does not have a .yml extension"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,11 +1,10 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckYamlHasContent(StandardBase): class CheckYamlHasContent(RuleBase):
sid = "LINT0007" sid = "LINT0007"
description = "Files should contain useful content" description = "Files should contain useful content"
helptext = "the file appears to have no useful content" helptext = "the file appears to have no useful content"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "defaults", "meta"] types = ["playbook", "task", "handler", "rolevars", "defaults", "meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,10 +1,9 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckYamlHyphens(StandardBase): class CheckYamlHyphens(RuleBase):
sid = "LINT0003" sid = "LINT0003"
description = "YAML should use consistent number of spaces after hyphens" description = "YAML should use consistent number of spaces after hyphens"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -1,10 +1,9 @@
from ansiblelater.standard import StandardBase from ansiblelater.rule import RuleBase
class CheckYamlIndent(StandardBase): class CheckYamlIndent(RuleBase):
sid = "LINT0002" sid = "LINT0002"
description = "YAML should not contain unnecessarily empty lines" description = "YAML should not contain unnecessarily empty lines"
version = "0.1"
types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"] types = ["playbook", "task", "handler", "rolevars", "hostvars", "groupvars", "meta"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -104,13 +104,13 @@ class Settings:
if f not in defaults["ansible"]["custom_modules"]: if f not in defaults["ansible"]["custom_modules"]:
defaults["ansible"]["custom_modules"].append(f) defaults["ansible"]["custom_modules"].append(f)
if defaults["rules"]["buildin"]: if defaults["rules"]["builtin"]:
defaults["rules"]["standards"].append( defaults["rules"]["dir"].append(
os.path.join(resource_filename("ansiblelater", "rules")) os.path.join(resource_filename("ansiblelater", "rules"))
) )
defaults["rules"]["standards"] = [ defaults["rules"]["dir"] = [
os.path.relpath(os.path.normpath(p)) for p in defaults["rules"]["standards"] os.path.relpath(os.path.normpath(p)) for p in defaults["rules"]["dir"]
] ]
return defaults return defaults
@ -118,9 +118,9 @@ class Settings:
def _get_defaults(self): def _get_defaults(self):
defaults = { defaults = {
"rules": { "rules": {
"buildin": True, "builtin": True,
"standards": [], "dir": [],
"filter": [], "include_filter": [],
"exclude_filter": [], "exclude_filter": [],
"warning_filter": [ "warning_filter": [
"ANSIBLE9999", "ANSIBLE9999",
@ -128,7 +128,6 @@ class Settings:
], ],
"ignore_dotfiles": True, "ignore_dotfiles": True,
"exclude_files": [], "exclude_files": [],
"version": "",
}, },
"logging": { "logging": {
"level": "WARNING", "level": "WARNING",

View File

@ -6,7 +6,6 @@ import sys
from contextlib import suppress from contextlib import suppress
import yaml import yaml
from packaging.version import Version
from ansiblelater import logger from ansiblelater import logger
@ -35,12 +34,6 @@ def count_spaces(c_string):
return (leading_spaces, trailing_spaces) return (leading_spaces, trailing_spaces)
def standards_latest(standards):
return max(
[standard.version for standard in standards if standard.version] or ["0.1"], key=Version
)
def lines_ranges(lines_spec): def lines_ranges(lines_spec):
if not lines_spec: if not lines_spec:
return None return None

View File

@ -1,18 +1,17 @@
--- ---
title: Minimal standard checks title: Write a rule
--- ---
A typical standards check will look like: A typical rule check will look like:
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
<!-- spellchecker-disable --> <!-- spellchecker-disable -->
{{< highlight Python "linenos=table" >}} {{< highlight Python "linenos=table" >}}
class CheckBecomeUser(StandardBase): class CheckBecomeUser(RuleBase):
sid = "ANSIBLE0015" sid = "ANSIBLE0015"
description = "Become should be combined with become_user" description = "Become should be combined with become_user"
helptext = "the task has `become` enabled but `become_user` is missing" helptext = "the task has `become` enabled but `become_user` is missing"
version = "0.1"
types = ["playbook", "task", "handler"] types = ["playbook", "task", "handler"]
def check(self, candidate, settings): def check(self, candidate, settings):

View File

@ -8,28 +8,27 @@ You can get all available CLI options by running `ansible-later --help`:
<!-- spellchecker-disable --> <!-- spellchecker-disable -->
{{< highlight Shell "linenos=table" >}} {{< highlight Shell "linenos=table" >}}
$ ansible-later --help $ ansible-later --help
usage: ansible-later [-h] [-c CONFIG_FILE] [-r RULES.STANDARDS] usage: ansible-later [-h] [-c CONFIG] [-r DIR] [-B] [-i TAGS] [-x TAGS] [-v] [-q] [-V] [rules.files ...]
[-s RULES.FILTER] [-v] [-q] [--version]
[rules.files [rules.files ...]]
Validate Ansible files against best practice guideline Validate Ansible files against best practice guideline
positional arguments: positional arguments:
rules.files rules.files
optional arguments: options:
-h, --help show this help message and exit -h, --help show this help message and exit
-c CONFIG_FILE, --config CONFIG_FILE -c CONFIG, --config CONFIG
location of configuration file path to configuration file
-r RULES.STANDARDS, --rules RULES.STANDARDS -r DIR, --rules-dir DIR
location of standards rules directory of rules
-s RULES.FILTER, --standards RULES.FILTER -B, --no-builtin disables built-in rules
limit standards to given ID's -i TAGS, --include-rules TAGS
-x RULES.EXCLUDE_FILTER, --exclude-standards RULES.EXCLUDE_FILTER limit rules to given id/tags
exclude standards by given ID's -x TAGS, --exclude-rules TAGS
exclude rules by given it/tags
-v increase log level -v increase log level
-q decrease log level -q decrease log level
--version show program's version number and exit -V, --version show program's version number and exit
{{< /highlight >}} {{< /highlight >}}
<!-- spellchecker-enable --> <!-- spellchecker-enable -->
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->

View File

@ -58,8 +58,8 @@ logging:
# Global settings for all defined rules # Global settings for all defined rules
rules: rules:
# Disable build-in rules if required # Disable built-in rules if required
buildin: True builtin: True
# List of files to exclude # List of files to exclude
exclude_files: [] exclude_files: []
@ -75,8 +75,7 @@ rules:
exclude_filter: [] exclude_filter: []
# List of rule ID's that should be displayed as a warning instead of an error. By default, # List of rule ID's that should be displayed as a warning instead of an error. By default,
# only rules whose version is higher than the current default version are marked as warnings. # no rules are marked as warnings. This list allows to degrade errors to warnings for each rule.
# This list allows to degrade errors to warnings for each rule.
warning_filter: warning_filter:
- "ANSIBLE9999" - "ANSIBLE9999"
- "ANSIBLE9998" - "ANSIBLE9998"
@ -85,12 +84,8 @@ rules:
# You can disable this setting and handle dotfiles by yourself with `exclude_files`. # You can disable this setting and handle dotfiles by yourself with `exclude_files`.
ignore_dotfiles: True ignore_dotfiles: True
# List of directories to load standard rules from (defaults to build-in) # List of directories to load rules from (defaults to built-in)
standards: [] dir: []
# Standard version to use. Standard version set in a roles meta file
# or playbook will takes precedence.
version:
# Block to control included yamllint rules. # Block to control included yamllint rules.
# See https://yamllint.readthedocs.io/en/stable/rules.html # See https://yamllint.readthedocs.io/en/stable/rules.html

View File

@ -2,7 +2,7 @@
title: Included rules title: Included rules
--- ---
Reviews are useless without some rules or standards to check against. ansible-later comes with a set of built-in checks, which are explained in the following table. Reviews are useless without some rules to check against. `ansible-later` comes with a set of built-in checks, which are explained in the following table.
| Rule | ID | Description | Parameter | | Rule | ID | Description | Parameter |
| ----------------------------- | ----------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------- | | ----------------------------- | ----------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------- |
@ -42,5 +42,4 @@ Reviews are useless without some rules or standards to check against. ansible-la
| CheckRelativeRolePaths | ANSIBLE0025 | Don't use a relative path in a role. | | | CheckRelativeRolePaths | ANSIBLE0025 | Don't use a relative path in a role. | |
| CheckChangedInWhen | ANSIBLE0026 | Use handlers instead of `when: changed`. | | | CheckChangedInWhen | ANSIBLE0026 | Use handlers instead of `when: changed`. | |
| CheckChangedInWhen | ANSIBLE0027 | Deprecated bare variables in loops must not be used. | | | CheckChangedInWhen | ANSIBLE0027 | Deprecated bare variables in loops must not be used. | |
| CheckVersion | ANSIBLE9998 | Standards version should be pinned. | |
| CheckDeprecated | ANSIBLE9999 | Deprecated features of `ansible-later` should not be used. | | | CheckDeprecated | ANSIBLE9999 | Deprecated features of `ansible-later` should not be used. | |

View File

@ -23,5 +23,5 @@ main:
sub: sub:
- name: Candidates - name: Candidates
ref: "/build_rules/candidates" ref: "/build_rules/candidates"
- name: Standards checks - name: Rules
ref: "/build_rules/standards_check" ref: "/build_rules/rule"