Skip to content

Commit

Permalink
Mark galaxy rule as required only for shared profile (#2441)
Browse files Browse the repository at this point in the history
- makes galaxy rule opt-in, so it would not be executed when a profile
  is not selected
- adds galaxy rule to shared profile, so we check requirements when
  before uploading (production profile inherits shared one)
  • Loading branch information
ssbarnea authored Sep 18, 2022
1 parent 9614067 commit 76317f1
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 22 deletions.
7 changes: 1 addition & 6 deletions src/ansiblelint/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,18 +192,13 @@ def main(argv: list[str] | None = None) -> int: # noqa: C901
from ansiblelint.rules import RulesCollection
from ansiblelint.runner import _get_matches

rules = RulesCollection(options.rulesdirs)
rules = RulesCollection(options.rulesdirs, profile=options.profile)

if options.profile == []:
from ansiblelint.generate_docs import profiles_as_rich

console.print(profiles_as_rich())
return 0
if options.profile:
from ansiblelint.rules import filter_rules_with_profile

filter_rules_with_profile(rules, options.profile[0])
# When profile is mentioned, we filter-out the rules based on it

if options.listrules or options.listtags:
return _do_list(rules)
Expand Down
10 changes: 6 additions & 4 deletions src/ansiblelint/data/profiles.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@ shared:
extends: safety
description: >
The shared profile is for those that want to publish their content, roles
or collection to either [galaxy](https://galaxy.ansible.com) or to another
automation-hub instance. In addition to all the rules related to packaging
and publishing, like metadata checks, this also includes some rules that
are known to be good practices for keeping the code readable and
or collection to either [galaxy.ansible.com](https://galaxy.ansible.com),
[automation-hub](https://console.redhat.com/ansible/automation-hub) or
another private instance of these. In addition to all the rules related to
packaging and publishing, like metadata checks, this also includes some
rules that are known to be good practices for keeping the code readable and
maintainable.
rules:
galaxy: # <-- applies to both galaxy and automation-hub
ignore-errors:
layout:
url: https://github.com/ansible/ansible-lint/issues/1900
Expand Down
6 changes: 6 additions & 0 deletions src/ansiblelint/generate_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ def profiles_as_md(header: bool = False, docs_url: str = RULE_DOC_URL) -> str:
The rules that have a `*` suffix, are not implemented yet but we documented
them with links to their issues.
```{note}
Special rule tags such `opt-in` and `experimental` are automatically removed
when a rule is included in a profile, directly or indirectly. This means that
they will always execute once included.
```
"""

for name, profile in PROFILES.items():
Expand Down
36 changes: 31 additions & 5 deletions src/ansiblelint/rules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,9 +364,12 @@ def __init__(
self,
rulesdirs: list[str] | None = None,
options: Namespace = default_options,
profile: list[str] | None = None,
conditional: bool = True,
) -> None:
"""Initialize a RulesCollection instance."""
self.options = options
self.profile = profile or []
if rulesdirs is None:
rulesdirs = []
self.rulesdirs = expand_paths_vars(rulesdirs)
Expand All @@ -377,15 +380,21 @@ def __init__(
[RuntimeErrorRule(), AnsibleParserErrorRule(), LoadingFailureRule()]
)
for rule in load_plugins(rulesdirs):
self.register(rule)
self.register(rule, conditional=conditional)
self.rules = sorted(self.rules)

def register(self, obj: AnsibleLintRule) -> None:
# when we have a profile we unload some of the rules
if self.profile:
filter_rules_with_profile(self.rules, self.profile[0])

def register(self, obj: AnsibleLintRule, conditional: bool = False) -> None:
"""Register a rule."""
# We skip opt-in rules which were not manually enabled.
# But we do include opt-in rules when listing all rules or tags
if any(
[
not conditional,
self.profile, # when profile is used we load all rules and filter later
"opt-in" not in obj.tags,
obj.id in self.options.enable_list,
self.options.listrules,
Expand Down Expand Up @@ -497,19 +506,36 @@ def listtags(self) -> str:
return result


def filter_rules_with_profile(rule_col: RulesCollection, profile: str) -> None:
def filter_rules_with_profile(rule_col: list[BaseRule], profile: str) -> None:
"""Unload rules that are not part of the specified profile."""
included = set()
extends = profile
total_rules = len(rule_col)
while extends:
for rule in PROFILES[extends]["rules"]:
_logger.debug("Activating rule `%s` due to profile `%s`", rule, extends)
included.add(rule)
extends = PROFILES[extends].get("extends", None)
for rule in list(rule_col.rules):
for rule in rule_col:
if rule.id not in included:
_logger.debug(
"Unloading %s rule due to not being part of %s profile.",
rule.id,
profile,
)
rule_col.rules.remove(rule)
rule_col.remove(rule)
else:
for tag in ("opt-in", "experimental"):
if tag in rule.tags:
_logger.debug(
"Removing tag `%s` from `%s` rule because `%s` profile makes it mandatory.",
tag,
rule.id,
profile,
)
rule.tags.remove(tag)
# rule_col.rules.remove(rule)
# break
if "opt-in" in rule.tags:
rule.tags.remove("opt-in")
_logger.debug("%s/%s rules included in the profile", len(rule_col), total_rules)
4 changes: 2 additions & 2 deletions src/ansiblelint/rules/galaxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ class GalaxyRule(AnsibleLintRule):
id = "galaxy"
description = "Confirm via galaxy.yml file if collection version is greater than or equal to 1.0.0"
severity = "MEDIUM"
tags = ["metadata"]
version_added = "v6.5.0 (last update)"
tags = ["metadata", "opt-in", "experimental"]
version_added = "v6.6.0 (last update)"

def matchplay(self, file: Lintable, data: odict[str, Any]) -> list[MatchError]:
"""Return matches found for a specific play (entry in playbook)."""
Expand Down
2 changes: 1 addition & 1 deletion test/test_profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def test_profile_min() -> None:
collection.register(ShellWithoutPipefail())
assert len(collection.rules) == 4, "Failed to register new rule."

filter_rules_with_profile(collection, "min")
filter_rules_with_profile(collection.rules, "min")
assert (
len(collection.rules) == 3
), "Failed to unload rule that is not part of 'min' profile."
Expand Down
8 changes: 4 additions & 4 deletions test/test_rules_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ def test_rich_rule_listing() -> None:
def test_rules_id_format() -> None:
"""Assure all our rules have consistent format."""
rule_id_re = re.compile("^[a-z-]{4,30}$")
options.enable_list = ["no-same-owner", "no-log-password", "no-same-owner"]
# options.enable_list = ["no-same-owner", "no-log-password", "no-same-owner"]
rules = RulesCollection(
[os.path.abspath("./src/ansiblelint/rules")], options=options
[os.path.abspath("./src/ansiblelint/rules")], options=options, conditional=False
)
keys: set[str] = set()
for rule in rules:
Expand All @@ -166,5 +166,5 @@ def test_rules_id_format() -> None:
rule.help != "" or rule.description or rule.__doc__
), f"Rule {rule.id} must have at least one of: .help, .description, .__doc__"
assert "yaml" in keys, "yaml rule is missing"
assert len(rules) == 42 # update this number when adding new rules!
assert len(keys) == 42, "Duplicate rule ids?"
assert len(rules) == 45 # update this number when adding new rules!
assert len(keys) == len(rules), "Duplicate rule ids?"

0 comments on commit 76317f1

Please sign in to comment.