Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow rules to expose all tags they can produce #3464

Merged
merged 1 commit into from
May 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/ansiblelint/_internal/rules.py
Expand Up @@ -149,6 +149,14 @@ def __repr__(self) -> str:
"""Return a AnsibleLintRule instance representation."""
return self.id + ": " + self.shortdesc

@classmethod
def ids(cls) -> dict[str, str]:
"""Return a dictionary ids and their messages.

This is used by the ``--list-tags`` option to ansible-lint.
"""
return getattr(cls, "_ids", {cls.id: cls.shortdesc})


# pylint: enable=unused-argument

Expand Down
10 changes: 8 additions & 2 deletions src/ansiblelint/rules/__init__.py
Expand Up @@ -503,23 +503,29 @@ def list_tags(self) -> str:
"metadata": "Invalid metadata, likely related to galaxy, collections or roles",
"opt-in": "Rules that are not used unless manually added to `enable_list`",
"security": "Rules related o potentially security issues, like exposing credentials",
"syntax": "Related to wrong or deprecated syntax",
"unpredictability": "Warn about code that might not work in a predictable way",
"unskippable": "Indicate a fatal error that cannot be ignored or disabled",
"yaml": "External linter which will also produce its own rule codes",
}

tags = defaultdict(list)
for rule in self.rules:
# Fail early if a rule does not have any of our required tags
if not set(rule.tags).intersection(tag_desc.keys()):
msg = f"Rule {rule} does not have any of the required tags: {', '.join(tag_desc.keys())}"
raise RuntimeError(msg)
for tag in rule.tags:
tags[tag].append(rule.id)
for id_ in rule.ids():
tags[tag].append(id_)
result = "# List of tags and rules they cover\n"
for tag in sorted(tags):
desc = tag_desc.get(tag, None)
if desc:
result += f"{tag}: # {desc}\n"
else:
result += f"{tag}:\n"
for name in tags[tag]:
for name in sorted(tags[tag]):
result += f" - {name}\n"
return result

Expand Down
3 changes: 3 additions & 0 deletions src/ansiblelint/rules/args.py
Expand Up @@ -94,6 +94,9 @@ class ArgsRule(AnsibleLintRule):
tags = ["syntax", "experimental"]
version_added = "v6.10.0"
module_aliases: dict[str, str] = {"block/always/rescue": "block/always/rescue"}
_ids = {
"args[module]": description,
}

def matchtask( # noqa: C901
self,
Expand Down
7 changes: 6 additions & 1 deletion src/ansiblelint/rules/fqcn.py
Expand Up @@ -103,6 +103,11 @@ class FQCNBuiltinsRule(AnsibleLintRule, TransformMixin):
tags = ["formatting"]
version_added = "v6.8.0"
module_aliases: dict[str, str] = {"block/always/rescue": "block/always/rescue"}
_ids = {
"fqcn[action-core]": "Use FQCN for builtin module actions",
"fqcn[action]": "Use FQCN for module actions",
"fqcn[canonical]": "You should use canonical module name",
}

def matchtask(
self,
Expand Down Expand Up @@ -188,7 +193,7 @@ def transform(
lintable: Lintable,
data: CommentedMap | CommentedSeq | str,
) -> None:
if match.tag in {"fqcn[action-core]", "fqcn[action]", "fqcn[canonical]"}:
if match.tag in self.ids():
target_task = self.seek(match.yaml_path, data)
# Unfortunately, a lot of data about Ansible content gets lost here, you only get a simple dict.
# For now, just parse the error messages for the data about action names etc. and fix this later.
Expand Down
7 changes: 7 additions & 0 deletions src/ansiblelint/rules/galaxy.py
Expand Up @@ -21,6 +21,13 @@ class GalaxyRule(AnsibleLintRule):
severity = "MEDIUM"
tags = ["metadata"]
version_added = "v6.11.0 (last update)"
_ids = {
"galaxy[tags]": "galaxy.yaml must have one of the required tags",
"galaxy[no-changelog]": "No changelog found. Please add a changelog file. Refer to the galaxy.md file for more info.",
"galaxy[version-missing]": "galaxy.yaml should have version tag.",
"galaxy[version-incorrect]": "collection version should be greater than or equal to 1.0.0",
"galaxy[no-runtime]": "meta/runtime.yml file not found.",
}

def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
"""Return matches found for a specific play (entry in playbook)."""
Expand Down
6 changes: 5 additions & 1 deletion src/ansiblelint/rules/jinja.py
Expand Up @@ -70,6 +70,10 @@ class JinjaRule(AnsibleLintRule):
"invalid": "Syntax error in jinja2 template: {value}",
"spacing": "Jinja2 spacing could be improved: {value} -> {reformatted}",
}
_ids = {
"jinja[invalid]": "Invalid jinja2 syntax",
"jinja[spacing]": "Jinja2 spacing could be improved",
}

def _msg(self, tag: str, value: str, reformatted: str) -> str:
"""Generate error message."""
Expand Down Expand Up @@ -246,7 +250,7 @@ def unlex(self, tokens: list[Token]) -> str:
return result

# pylint: disable=too-many-branches,too-many-statements,too-many-locals
def check_whitespace( # noqa: max-complexity: 13
def check_whitespace( # noqa: C901
self,
text: str,
key: str,
Expand Down
3 changes: 3 additions & 0 deletions src/ansiblelint/rules/key_order.py
Expand Up @@ -55,6 +55,9 @@ class KeyOrderRule(AnsibleLintRule):
tags = ["formatting"]
version_added = "v6.6.2"
needs_raw_task = True
_ids = {
"key-order[task]": "You can improve the task key order",
}

def matchtask(
self,
Expand Down
4 changes: 4 additions & 0 deletions src/ansiblelint/rules/latest.py
Expand Up @@ -22,6 +22,10 @@ class LatestRule(AnsibleLintRule):
severity = "MEDIUM"
tags = ["idempotency"]
version_added = "v6.5.2"
_ids = {
"latest[git]": "Use a commit hash or tag instead of 'latest' for git",
"latest[hg]": "Use a commit hash or tag instead of 'latest' for hg",
}

def matchtask(
self,
Expand Down
4 changes: 4 additions & 0 deletions src/ansiblelint/rules/loop_var_prefix.py
Expand Up @@ -30,6 +30,10 @@ class RoleLoopVarPrefix(AnsibleLintRule):
tags = ["idiom"]
prefix = re.compile("")
severity = "MEDIUM"
_ids = {
"loop-var-prefix[wrong]": "Loop variable name does not match regex.",
"loop-var-prefix[missing]": "Replace unsafe implicit `item` loop variable.",
}

def matchtask(
self,
Expand Down
4 changes: 4 additions & 0 deletions src/ansiblelint/rules/meta_runtime.py
Expand Up @@ -31,6 +31,10 @@ class CheckRequiresAnsibleVersion(AnsibleLintRule):
# Refer to https://access.redhat.com/support/policy/updates/ansible-automation-platform
# Also add devel to this list
supported_ansible = ["2.9.10", "2.11.", "2.12.", "2.13.", "2.14.", "2.15.", "2.16."]
_ids = {
"meta-runtime[unsupported-version]": "requires_ansible key must be set to a supported version.",
"meta-runtime[invalid-version]": "'requires_ansible' is not a valid requirement specification",
}

def matchyaml(self, file: Lintable) -> list[MatchError]:
"""Find violations inside meta files.
Expand Down
7 changes: 7 additions & 0 deletions src/ansiblelint/rules/name.py
Expand Up @@ -29,6 +29,13 @@ class NameRule(AnsibleLintRule, TransformMixin):
tags = ["idiom"]
version_added = "v6.9.1 (last update)"
_re_templated_inside = re.compile(r".*\{\{.*\}\}.*\w.*$")
_ids = {
"name[play]": "All plays should be named.",
"name[missing]": "All tasks should be named.",
"name[prefix]": "Task name should start with a prefix.",
"name[casing]": "All names should start with an uppercase letter.",
"name[template]": "Jinja templates should only be at the end of 'name'",
}

def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
"""Return matches found for a specific play (entry in playbook)."""
Expand Down
4 changes: 4 additions & 0 deletions src/ansiblelint/rules/no_free_form.py
Expand Up @@ -26,6 +26,10 @@ class NoFreeFormRule(AnsibleLintRule):
cmd_shell_re = re.compile(
r"(chdir|creates|executable|removes|stdin|stdin_add_newline|warn)=",
)
_ids = {
"no-free-form[raw]": "Avoid embedding `executable=` inside raw calls, use explicit args dictionary instead.",
"no-free-form[raw-non-string]": "Passing a non string value to `raw` module is neither documented or supported.",
}

def matchtask(
self,
Expand Down
3 changes: 3 additions & 0 deletions src/ansiblelint/rules/role_name.py
Expand Up @@ -62,6 +62,9 @@ class RoleNames(AnsibleLintRule):
severity = "HIGH"
tags = ["deprecations", "metadata"]
version_added = "v6.8.5"
_ids = {
"role-name[path]": "Avoid using paths when importing roles.",
}

def matchtask(
self,
Expand Down
4 changes: 4 additions & 0 deletions src/ansiblelint/rules/run_once.py
Expand Up @@ -22,6 +22,10 @@ class RunOnce(AnsibleLintRule):

tags = ["idiom"]
severity = "MEDIUM"
_ids = {
"run-once[task]": "Using run_once may behave differently if strategy is set to free.",
"run-once[play]": "Play uses strategy: free",
}

def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
"""Return matches found for a specific playbook."""
Expand Down
6 changes: 5 additions & 1 deletion src/ansiblelint/rules/sanity.py
Expand Up @@ -22,7 +22,7 @@ class CheckSanityIgnoreFiles(AnsibleLintRule):
"Identifies non-allowed entries in the `tests/sanity/ignore*.txt files."
)
severity = "MEDIUM"
tags = []
tags = ["idiom"]
version_added = "v6.14.0"

# Partner Engineering defines this list. Please contact PE for changes.
Expand All @@ -48,6 +48,10 @@ class CheckSanityIgnoreFiles(AnsibleLintRule):
"compile-3.5",
"compile-3.5!skip",
]
_ids = {
"sanity[cannot-ignore]": "Ignore file contains ... at line ..., which is not a permitted ignore.",
"sanity[bad-ignore]": "Ignore file entry at ... is formatted incorrectly. Please review.",
}

def matchyaml(self, file: Lintable) -> list[MatchError]:
"""Evaluate sanity ignore lists for disallowed ignores.
Expand Down
17 changes: 17 additions & 0 deletions src/ansiblelint/rules/schema.py
Expand Up @@ -57,6 +57,23 @@ class ValidateSchemaRule(AnsibleLintRule):
severity = "VERY_HIGH"
tags = ["core"]
version_added = "v6.1.0"
_ids = {
"schema[ansible-lint-config]": "",
"schema[ansible-navigator-config]": "",
"schema[changelog]": "",
"schema[execution-environment]": "",
"schema[galaxy]": "",
"schema[inventory]": "",
"schema[meta]": "",
"schema[meta-runtime]": "",
"schema[molecule]": "",
"schema[playbook]": "",
"schema[requirements]": "",
"schema[role-arg-spec]": "",
"schema[rulebook]": "",
"schema[tasks]": "",
"schema[vars]": "",
}

def matchtask(
self,
Expand Down
5 changes: 5 additions & 0 deletions src/ansiblelint/rules/var_naming.py
Expand Up @@ -91,6 +91,11 @@ class VariableNamingRule(AnsibleLintRule):
"ansible_user",
"ansible_remote_tmp", # no included in docs
}
_ids = {
"var-naming[no-reserved]": "Variables names must not be Ansible reserved names.",
"var-naming[no-jinja]": "Variables names must not contain jinja2 templating.",
"var-naming[pattern]": f"Variables names should match {re_pattern_str} regex.",
}

# pylint: disable=too-many-return-statements
def get_var_naming_matcherror(
Expand Down
25 changes: 25 additions & 0 deletions src/ansiblelint/rules/yaml_rule.py
Expand Up @@ -33,6 +33,31 @@ class YamllintRule(AnsibleLintRule):
link = "https://yamllint.readthedocs.io/en/stable/rules.html"
# ensure this rule runs before most of other common rules
_order = 1
_ids = {
"yaml[anchors]": "",
"yaml[braces]": "",
"yaml[brackets]": "",
"yaml[colons]": "",
"yaml[commas]": "",
"yaml[comments-indentation]": "",
"yaml[comments]": "",
"yaml[document-end]": "",
"yaml[document-start]": "",
"yaml[empty-lines]": "",
"yaml[empty-values]": "",
"yaml[float-values]": "",
"yaml[hyphens]": "",
"yaml[indentation]": "",
"yaml[key-duplicates]": "",
"yaml[key-ordering]": "",
"yaml[line-length]": "",
"yaml[new-line-at-end-of-file]": "",
"yaml[new-lines]": "",
"yaml[octal-values]": "",
"yaml[quoted-strings]": "",
"yaml[trailing-spaces]": "",
"yaml[truthy]": "",
}

def matchyaml(self, file: Lintable) -> list[MatchError]:
"""Return matches found for a specific YAML text."""
Expand Down
2 changes: 1 addition & 1 deletion src/ansiblelint/schemas/__store__.json
Expand Up @@ -24,7 +24,7 @@
"url": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/inventory.json"
},
"meta": {
"etag": "24aa044eddbf2fc92e31775bcc625fd8e7689cb14542ac59c0e3b94d9a9b163a",
"etag": "7b5ac2250a4ae70ef657cd9906e6c13f4941daba71724e3342b7fa7e7239a334",
"url": "https://raw.githubusercontent.com/ansible/ansible-lint/main/src/ansiblelint/schemas/meta.json"
},
"meta-runtime": {
Expand Down