Skip to content

Commit

Permalink
Adding logic for configuring supported ansible versions (#4203)
Browse files Browse the repository at this point in the history
  • Loading branch information
alisonlhart authored Jun 4, 2024
1 parent 3f6f87f commit 2d9f1ed
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 25 deletions.
4 changes: 4 additions & 0 deletions .ansible-lint
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,7 @@ task_name_prefix: "{stem} | "

# Limit the depth of the nested blocks:
# max_block_depth: 20

# Also recognize these versions of Ansible as supported:
# supported_ansible_also:
# - "2.14"
2 changes: 1 addition & 1 deletion .github/workflows/tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
env:
# Number of expected test passes, safety measure for accidental skip of
# tests. Update value if you add/remove tests.
PYTEST_REQPASS: 881
PYTEST_REQPASS: 882
steps:
- uses: actions/checkout@v4
with:
Expand Down
2 changes: 2 additions & 0 deletions examples/broken_supported_ansible_also/.ansible-lint
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Invalid supported_ansible_also type
supported_ansible_also: True
2 changes: 0 additions & 2 deletions examples/meta_runtime_version_checks/pass/meta/runtime.yml

This file was deleted.

2 changes: 2 additions & 0 deletions examples/meta_runtime_version_checks/pass_0/meta/runtime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
requires_ansible: ">=2.15.0,<2.17.0"
2 changes: 2 additions & 0 deletions examples/meta_runtime_version_checks/pass_1/meta/runtime.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
requires_ansible: ">=2.9.10"
1 change: 1 addition & 0 deletions src/ansiblelint/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,6 +499,7 @@ def merge_config(file_config: dict[Any, Any], cli_config: Options) -> Options:
"enable_list": [],
"only_builtins_allow_collections": [],
"only_builtins_allow_modules": [],
"supported_ansible_also": [],
# do not include "write_list" here. See special logic below.
}

Expand Down
8 changes: 8 additions & 0 deletions src/ansiblelint/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ class Options: # pylint: disable=too-many-instance-attributes
ignore_file: Path | None = None
max_tasks: int = 100
max_block_depth: int = 20
# Refer to https://docs.ansible.com/ansible/latest/reference_appendices/release_and_maintenance.html#ansible-core-support-matrix
_default_supported = ["2.15.", "2.16.", "2.17."]
supported_ansible_also: list[str] = field(default_factory=list)

@property
def nodeps(self) -> bool:
Expand All @@ -186,6 +189,11 @@ def __post_init__(self) -> None:
if self.nodeps:
self.offline = True

@property
def supported_ansible(self) -> list[str]:
"""Returns list of ansible versions that are considered supported."""
return sorted([*self._default_supported, *self.supported_ansible_also])


options = Options()

Expand Down
31 changes: 24 additions & 7 deletions src/ansiblelint/rules/meta_runtime.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
# meta-runtime

This rule checks the meta/runtime.yml `requires_ansible` key against the list of currently supported versions of ansible-core.
This rule checks the meta/runtime.yml `requires_ansible` key against the list of
currently supported versions of ansible-core.

This rule can produce messages such as:

- `meta-runtime[unsupported-version]` - `requires_ansible` key must refer to a currently supported version such as: >=2.14.0, >=2.15.0, >=2.16.0
- `meta-runtime[invalid-version]` - `requires_ansible` is not a valid requirement specification
- `meta-runtime[unsupported-version]` - `requires_ansible` key must refer to a
currently supported version such as: >=2.14.0, >=2.15.0, >=2.16.0
- `meta-runtime[invalid-version]` - `requires_ansible` is not a valid
requirement specification

Please note that the linter will allow only a full version of Ansible such `2.16.0` and not allow their short form, like `2.16`. This is a safety measure
for asking authors to mention an explicit version that they tested with. Over the years we spotted multiple problems caused by the use of the short versions, users
ended up trying an outdated version that was never tested against by the collection maintainer.
Please note that the linter will allow only a full version of Ansible such
`2.16.0` and not allow their short form, like `2.16`. This is a safety measure
for asking authors to mention an explicit version that they tested with. Over
the years we spotted multiple problems caused by the use of the short versions,
users ended up trying an outdated version that was never tested against by the
collection maintainer.

## Problematic code

Expand All @@ -19,7 +25,6 @@ ended up trying an outdated version that was never tested against by the collect
requires_ansible: ">=2.9"
```
```yaml
# runtime.yml
---
Expand All @@ -33,3 +38,15 @@ requires_ansible: "2.15"
---
requires_ansible: ">=2.15.0"
```
## Configuration
In addition to the internal list of supported Ansible versions, users can
configure additional values. This allows those that want to maintain content
that requires a version of ansible-core that is already out of support.
```yaml
# Also recognize these versions of Ansible as supported:
supported_ansible_also:
- "2.14"
```
51 changes: 36 additions & 15 deletions src/ansiblelint/rules/meta_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,8 @@ class CheckRequiresAnsibleVersion(AnsibleLintRule):
tags = ["metadata"]
version_added = "v6.11.0 (last update)"

# Refer to https://access.redhat.com/support/policy/updates/ansible-automation-platform
# Also add devel to this list
supported_ansible = ["2.15.", "2.16.", "2.17."]
supported_ansible_examples = [f">={x}0" for x in supported_ansible]
_ids = {
"meta-runtime[unsupported-version]": f"'requires_ansible' key must refer to a currently supported version such as: {', '.join(supported_ansible_examples)}",
"meta-runtime[unsupported-version]": "'requires_ansible' key must refer to a currently supported version",
"meta-runtime[invalid-version]": "'requires_ansible' is not a valid requirement specification",
}

Expand All @@ -50,22 +46,26 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
if file.kind != "meta-runtime":
return []

version_required = file.data.get("requires_ansible", None)
requires_ansible = file.data.get("requires_ansible", None)

if version_required:
if not any(
version in version_required for version in self.supported_ansible
if requires_ansible:
if self.options and not any(
version in requires_ansible
for version in self.options.supported_ansible
):
supported_ansible = [f">={x}0" for x in self.options.supported_ansible]
msg = f"'requires_ansible' key must refer to a currently supported version such as: {', '.join(supported_ansible)}"

results.append(
self.create_matcherror(
message=self._ids["meta-runtime[unsupported-version]"],
message=msg,
tag="meta-runtime[unsupported-version]",
filename=file,
),
)

try:
SpecifierSet(version_required)
SpecifierSet(requires_ansible)
except ValueError:
results.append(
self.create_matcherror(
Expand All @@ -90,10 +90,10 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
("test_file", "failures", "tags"),
(
pytest.param(
"examples/meta_runtime_version_checks/pass/meta/runtime.yml",
"examples/meta_runtime_version_checks/pass_0/meta/runtime.yml",
0,
"meta-runtime[unsupported-version]",
id="pass",
id="pass0",
),
pytest.param(
"examples/meta_runtime_version_checks/fail_0/meta/runtime.yml",
Expand All @@ -115,16 +115,37 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
),
),
)
def test_meta_supported_version(
def test_default_meta_supported_version(
default_rules_collection: RulesCollection,
test_file: str,
failures: int,
tags: str,
) -> None:
"""Test rule matches."""
"""Test for default supported ansible versions."""
default_rules_collection.register(CheckRequiresAnsibleVersion())
results = Runner(test_file, rules=default_rules_collection).run()
for result in results:
assert result.rule.id == CheckRequiresAnsibleVersion().id
assert result.tag == tags
assert len(results) == failures

@pytest.mark.parametrize(
("test_file", "failures"),
(
pytest.param(
"examples/meta_runtime_version_checks/pass_1/meta/runtime.yml",
0,
id="pass1",
),
),
)
def test_added_meta_supported_version(
default_rules_collection: RulesCollection,
test_file: str,
failures: int,
) -> None:
"""Test for added supported ansible versions in the config."""
default_rules_collection.register(CheckRequiresAnsibleVersion())
default_rules_collection.options.supported_ansible_also = ["2.9"]
results = Runner(test_file, rules=default_rules_collection).run()
assert len(results) == failures
8 changes: 8 additions & 0 deletions src/ansiblelint/rules/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,14 @@ def matchyaml(self, file: Lintable) -> list[MatchError]:
],
id="ansible-lint-config-broken",
),
pytest.param(
"examples/broken_supported_ansible_also/.ansible-lint",
"ansible-lint-config",
[
r".*supported_ansible_also True is not of type 'array'.*https://",
],
id="ansible-lint-config-broken",
),
pytest.param(
"examples/ansible-navigator.yml",
"ansible-navigator-config",
Expand Down
7 changes: 7 additions & 0 deletions src/ansiblelint/schemas/ansible-lint-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,13 @@
"title": "Strict",
"type": "boolean"
},
"supported_ansible_also": {
"items": {
"type": "string"
},
"title": "Add supported ansible versions",
"type": "array"
},
"tags": {
"items": {
"type": "string"
Expand Down

0 comments on commit 2d9f1ed

Please sign in to comment.