summaryrefslogtreecommitdiffstats
path: root/src/ansiblelint/rules/galaxy.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/ansiblelint/rules/galaxy.py')
-rw-r--r--src/ansiblelint/rules/galaxy.py232
1 files changed, 232 insertions, 0 deletions
diff --git a/src/ansiblelint/rules/galaxy.py b/src/ansiblelint/rules/galaxy.py
new file mode 100644
index 0000000..cdaf4ed
--- /dev/null
+++ b/src/ansiblelint/rules/galaxy.py
@@ -0,0 +1,232 @@
+"""Implementation of GalaxyRule."""
+from __future__ import annotations
+
+import os
+import sys
+from functools import total_ordering
+from typing import TYPE_CHECKING, Any
+
+from ansiblelint.constants import LINE_NUMBER_KEY
+from ansiblelint.errors import MatchError
+from ansiblelint.rules import AnsibleLintRule
+
+if TYPE_CHECKING:
+ from ansiblelint.file_utils import Lintable
+
+
+class GalaxyRule(AnsibleLintRule):
+ """Rule for checking collection version is greater than 1.0.0 and checking for changelog."""
+
+ id = "galaxy"
+ description = "Confirm via galaxy.yml file if collection version is greater than or equal to 1.0.0 and check for changelog."
+ severity = "MEDIUM"
+ tags = ["metadata"]
+ version_added = "v6.11.0 (last update)"
+
+ def matchplay(self, file: Lintable, data: dict[str, Any]) -> list[MatchError]:
+ """Return matches found for a specific play (entry in playbook)."""
+ if file.kind != "galaxy": # type: ignore
+ return []
+
+ # Defined by Automation Hub Team and Partner Engineering
+ required_tag_list = [
+ "application",
+ "cloud",
+ "database",
+ "infrastructure",
+ "linux",
+ "monitoring",
+ "networking",
+ "security",
+ "storage",
+ "tools",
+ "windows",
+ ]
+
+ results = []
+
+ base_path = os.path.split(str(file.abspath))[0]
+ changelog_found = 0
+ changelog_paths = [
+ os.path.join(base_path, "changelogs", "changelog.yaml"),
+ os.path.join(base_path, "CHANGELOG.rst"),
+ os.path.join(base_path, "CHANGELOG.md"),
+ ]
+
+ for path in changelog_paths:
+ if os.path.isfile(path):
+ changelog_found = 1
+
+ galaxy_tag_list = data.get("tags", None)
+
+ # Changelog Check - building off Galaxy rule as there is no current way to check
+ # for a nonexistent file
+ if not changelog_found:
+ results.append(
+ self.create_matcherror(
+ message="No changelog found. Please add a changelog file. Refer to the galaxy.md file for more info.",
+ tag="galaxy[no-changelog]",
+ filename=file,
+ )
+ )
+
+ # Checking if galaxy.yml contains one or more required tags for certification
+ if not galaxy_tag_list or not any(
+ tag in required_tag_list for tag in galaxy_tag_list
+ ):
+ results.append(
+ self.create_matcherror(
+ message=(
+ f"galaxy.yaml must have one of the required tags: {required_tag_list}"
+ ),
+ tag="galaxy[tags]",
+ filename=file,
+ )
+ )
+
+ if "version" not in data:
+ results.append(
+ self.create_matcherror(
+ message="galaxy.yaml should have version tag.",
+ linenumber=data[LINE_NUMBER_KEY],
+ tag="galaxy[version-missing]",
+ filename=file,
+ )
+ )
+ return results
+ # returning here as it does not make sense
+ # to continue for version check below
+
+ version = data.get("version")
+ if Version(version) < Version("1.0.0"):
+ results.append(
+ self.create_matcherror(
+ message="collection version should be greater than or equal to 1.0.0",
+ # pylint: disable=protected-access
+ linenumber=version._line_number,
+ tag="galaxy[version-incorrect]",
+ filename=file,
+ )
+ )
+
+ return results
+
+
+@total_ordering
+class Version:
+ """Simple class to compare arbitrary versions."""
+
+ def __init__(self, version_string: str):
+ """Construct a Version object."""
+ self.components = version_string.split(".")
+
+ def __eq__(self, other: object) -> bool:
+ """Implement equality comparison."""
+ try:
+ other = _coerce(other)
+ except NotImplementedError:
+ return NotImplemented
+
+ return self.components == other.components
+
+ def __lt__(self, other: Version) -> bool:
+ """Implement lower-than operation."""
+ other = _coerce(other)
+
+ return self.components < other.components
+
+
+def _coerce(other: object) -> Version:
+ if isinstance(other, str):
+ other = Version(other)
+ if isinstance(other, (int, float)):
+ other = Version(str(other))
+ if isinstance(other, Version):
+ return other
+ raise NotImplementedError(f"Unable to coerce object type {type(other)} to Version")
+
+
+if "pytest" in sys.modules: # noqa: C901
+ import pytest
+
+ from ansiblelint.rules import RulesCollection # pylint: disable=ungrouped-imports
+ from ansiblelint.runner import Runner
+
+ def test_galaxy_collection_version_positive() -> None:
+ """Positive test for collection version in galaxy."""
+ collection = RulesCollection()
+ collection.register(GalaxyRule())
+ success = "examples/collection/galaxy.yml"
+ good_runner = Runner(success, rules=collection)
+ assert [] == good_runner.run()
+
+ def test_galaxy_collection_version_negative() -> None:
+ """Negative test for collection version in galaxy."""
+ collection = RulesCollection()
+ collection.register(GalaxyRule())
+ failure = "examples/meta/galaxy.yml"
+ bad_runner = Runner(failure, rules=collection)
+ errs = bad_runner.run()
+ assert len(errs) == 1
+
+ def test_galaxy_no_collection_version() -> None:
+ """Test for no collection version in galaxy."""
+ collection = RulesCollection()
+ collection.register(GalaxyRule())
+ failure = "examples/no_collection_version/galaxy.yml"
+ bad_runner = Runner(failure, rules=collection)
+ errs = bad_runner.run()
+ assert len(errs) == 1
+
+ def test_changelog_present() -> None:
+ """Positive test for finding a changelog."""
+ collection = RulesCollection()
+ collection.register(GalaxyRule())
+ good_runner = Runner("examples/collection/galaxy.yml", rules=collection)
+ assert [] == good_runner.run()
+
+ def test_changelog_missing() -> None:
+ """Negative test for finding a changelog."""
+ collection = RulesCollection()
+ collection.register(GalaxyRule())
+ bad_runner = Runner("examples/no_changelog/galaxy.yml", rules=collection)
+ result = bad_runner.run()
+ assert len(result) == 1
+ for item in result:
+ assert item.tag == "galaxy[no-changelog]"
+
+ def test_version_class() -> None:
+ """Test for version class."""
+ v = Version("1.0.0")
+ assert v == Version("1.0.0")
+ assert v != NotImplemented
+
+ def test_coerce() -> None:
+ """Test for _coerce function."""
+ assert _coerce("1.0") == Version("1.0")
+ assert _coerce(1.0) == Version("1.0")
+ expected = "Unable to coerce object type"
+ with pytest.raises(NotImplementedError, match=expected):
+ _coerce(type(Version))
+
+ def test_galaxy_tags_pass() -> None:
+ """Test for required tags."""
+ collection = RulesCollection()
+ collection.register(GalaxyRule())
+ bad_runner = Runner(
+ "examples/galaxy_no_required_tags/pass/galaxy.yml", rules=collection
+ )
+ result = bad_runner.run()
+ assert len(result) == 0
+
+ def test_galaxy_tags_fail() -> None:
+ """Test for required tags."""
+ collection = RulesCollection()
+ collection.register(GalaxyRule())
+ bad_runner = Runner(
+ "examples/galaxy_no_required_tags/fail/galaxy.yml", rules=collection
+ )
+ result = bad_runner.run()
+ assert len(result) == 1
+ for item in result:
+ assert item.tag == "galaxy[tags]"