diff --git a/examples/reqs_broken/requirements.yml b/examples/reqs_broken/requirements.yml new file mode 100644 index 00000000..d55cb09c --- /dev/null +++ b/examples/reqs_broken/requirements.yml @@ -0,0 +1,4 @@ +roles: [] +collections: [] +integration_tests_dependencies: [] # <-- invalid key +unit_tests_dependencies: [] # <-- invalid key diff --git a/src/ansible_compat/constants.py b/src/ansible_compat/constants.py index 162f6997..eb308e2e 100644 --- a/src/ansible_compat/constants.py +++ b/src/ansible_compat/constants.py @@ -1,6 +1,16 @@ """Constants used by ansible_compat.""" +REQUIREMENT_LOCATIONS = [ + "requirements.yml", + "roles/requirements.yml", + "collections/requirements.yml", + # These is more of less the official way to store test requirements in collections so far, comments shows number of repos using this reported by https://sourcegraph.com/ at the time of writing + "tests/requirements.yml", # 170 + "tests/integration/requirements.yml", # 3 + "tests/unit/requirements.yml", # 1 +] + # Minimal version of Ansible we support for runtime ANSIBLE_MIN_VERSION = "2.12" diff --git a/src/ansible_compat/runtime.py b/src/ansible_compat/runtime.py index 6188a396..bbb49c2e 100644 --- a/src/ansible_compat/runtime.py +++ b/src/ansible_compat/runtime.py @@ -22,7 +22,11 @@ ansible_collections_path, parse_ansible_version, ) -from ansible_compat.constants import MSG_INVALID_FQRL, RC_ANSIBLE_OPTIONS_ERROR +from ansible_compat.constants import ( + MSG_INVALID_FQRL, + RC_ANSIBLE_OPTIONS_ERROR, + REQUIREMENT_LOCATIONS, +) from ansible_compat.errors import ( AnsibleCommandError, AnsibleCompatError, @@ -411,6 +415,12 @@ def install_requirements( # noqa: C901 msg = f"{requirement} file is not a valid Ansible requirements file." raise InvalidPrerequisiteError(msg) + if isinstance(reqs_yaml, dict): + for key in reqs_yaml: + if key not in ("roles", "collections"): + msg = f"{requirement} file is not a valid Ansible requirements file. Only 'roles' and 'collections' keys are allowed at root level. Recognized valid locations are: {', '.join(REQUIREMENT_LOCATIONS)}" + raise InvalidPrerequisiteError(msg) + if isinstance(reqs_yaml, list) or "roles" in reqs_yaml: cmd = [ "ansible-galaxy", @@ -485,13 +495,7 @@ def prepare_environment( # noqa: C901 # are part of Tower specification # https://docs.ansible.com/ansible-tower/latest/html/userguide/projects.html#ansible-galaxy-support # https://docs.ansible.com/ansible-tower/latest/html/userguide/projects.html#collections-support - for req_file in [ - "requirements.yml", - "roles/requirements.yml", - "collections/requirements.yml", - # These is more of less the official way to store test requirements in collections so far: - "tests/requirements.yml", - ]: + for req_file in REQUIREMENT_LOCATIONS: self.install_requirements(Path(req_file), retry=retry, offline=offline) galaxy_path = Path("galaxy.yml") diff --git a/test/test_runtime.py b/test/test_runtime.py index c45b4660..4759f21e 100644 --- a/test/test_runtime.py +++ b/test/test_runtime.py @@ -258,6 +258,13 @@ def test_prerun_reqs_v2(caplog: pytest.LogCaptureFixture, runtime: Runtime) -> N ) +def test_prerun_reqs_broken(runtime: Runtime) -> None: + """Checks that the we report invalid requirements.yml file.""" + path = (Path(__file__).parent.parent / "examples" / "reqs_broken").resolve() + with cwd(path), pytest.raises(InvalidPrerequisiteError): + runtime.prepare_environment() + + def test__update_env_no_old_value_no_default_no_value(monkeypatch: MonkeyPatch) -> None: """Make sure empty value does not touch environment.""" monkeypatch.delenv("DUMMY_VAR", raising=False) diff --git a/tox.ini b/tox.ini index 1e6eadca..1f4416bf 100644 --- a/tox.ini +++ b/tox.ini @@ -68,7 +68,7 @@ setenv = PIP_DISABLE_PIP_VERSION_CHECK = 1 PIP_CONSTRAINT = {toxinidir}/requirements.txt PRE_COMMIT_COLOR = always - PYTEST_REQPASS = 80 + PYTEST_REQPASS = 81 FORCE_COLOR = 1 allowlist_externals = ansible