From 26b43f425f6c04732818f600e1cf4bcb9a56f89a Mon Sep 17 00:00:00 2001 From: Matt Clay Date: Thu, 27 Jan 2022 13:27:43 -0800 Subject: [PATCH] ansible-test - Validate collection ns and name. Resolves https://github.com/ansible/ansible/issues/62079 --- .../ansible-test-collection-identifier.yml | 2 ++ test/lib/ansible_test/_internal/data.py | 13 ++++++++++--- .../_internal/provider/layout/collection.py | 9 +++++++++ test/lib/ansible_test/_internal/util.py | 6 ++++++ 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/ansible-test-collection-identifier.yml diff --git a/changelogs/fragments/ansible-test-collection-identifier.yml b/changelogs/fragments/ansible-test-collection-identifier.yml new file mode 100644 index 00000000000..5b520e26a61 --- /dev/null +++ b/changelogs/fragments/ansible-test-collection-identifier.yml @@ -0,0 +1,2 @@ +minor_changes: + - ansible-test - Stop early with an error if the current working directory contains an invalid collection namespace or name. diff --git a/test/lib/ansible_test/_internal/data.py b/test/lib/ansible_test/_internal/data.py index a9b0806ed74..8208945dc17 100644 --- a/test/lib/ansible_test/_internal/data.py +++ b/test/lib/ansible_test/_internal/data.py @@ -9,6 +9,7 @@ from .util import ( ApplicationError, import_plugins, is_subdir, + is_valid_identifier, ANSIBLE_LIB_ROOT, ANSIBLE_TEST_ROOT, ANSIBLE_SOURCE_ROOT, @@ -180,8 +181,7 @@ class DataContext: if self.content.unsupported: raise ApplicationError(self.explain_working_directory()) - @staticmethod - def explain_working_directory() -> str: + def explain_working_directory(self) -> str: """Return a message explaining the working directory requirements.""" blocks = [ 'The current working directory must be within the source tree being tested.', @@ -204,9 +204,16 @@ class DataContext: blocks.append(f'Expected parent directory: {os.path.dirname(cwd)}/{{namespace}}/{{collection}}/') elif os.path.basename(cwd) == 'ansible_collections': blocks.append(f'Expected parent directory: {cwd}/{{namespace}}/{{collection}}/') - else: + elif 'ansible_collections' not in cwd.split(os.path.sep): blocks.append('No "ansible_collections" parent directory was found.') + if self.content.collection: + if not is_valid_identifier(self.content.collection.namespace): + blocks.append(f'The namespace "{self.content.collection.namespace}" is an invalid identifier or a reserved keyword.') + + if not is_valid_identifier(self.content.collection.name): + blocks.append(f'The name "{self.content.collection.name}" is an invalid identifier or a reserved keyword.') + message = '\n'.join(blocks) return message diff --git a/test/lib/ansible_test/_internal/provider/layout/collection.py b/test/lib/ansible_test/_internal/provider/layout/collection.py index 5dca046f02b..6b826ee4a30 100644 --- a/test/lib/ansible_test/_internal/provider/layout/collection.py +++ b/test/lib/ansible_test/_internal/provider/layout/collection.py @@ -11,6 +11,10 @@ from . import ( LayoutMessages, ) +from ...util import ( + is_valid_identifier, +) + class CollectionLayout(LayoutProvider): """Layout provider for Ansible collections.""" @@ -28,6 +32,10 @@ class CollectionLayout(LayoutProvider): collection_root = os.path.dirname(os.path.dirname(root)) collection_dir = os.path.relpath(root, collection_root) + + collection_namespace: str + collection_name: str + collection_namespace, collection_name = collection_dir.split(os.path.sep) collection_root = os.path.dirname(collection_root) @@ -65,6 +73,7 @@ class CollectionLayout(LayoutProvider): unit_module_path='tests/unit/plugins/modules', unit_module_utils_path='tests/unit/plugins/module_utils', unit_messages=unit_messages, + unsupported=not(is_valid_identifier(collection_namespace) and is_valid_identifier(collection_name)), ) @staticmethod diff --git a/test/lib/ansible_test/_internal/util.py b/test/lib/ansible_test/_internal/util.py index 30d609d7dde..5fb33374854 100644 --- a/test/lib/ansible_test/_internal/util.py +++ b/test/lib/ansible_test/_internal/util.py @@ -6,6 +6,7 @@ import errno import fcntl import importlib.util import inspect +import keyword import os import pkgutil import random @@ -98,6 +99,11 @@ MODE_DIRECTORY = MODE_READ | stat.S_IWUSR | stat.S_IXUSR | stat.S_IXGRP | stat.S MODE_DIRECTORY_WRITE = MODE_DIRECTORY | stat.S_IWGRP | stat.S_IWOTH +def is_valid_identifier(value: str) -> bool: + """Return True if the given value is a valid non-keyword Python identifier, otherwise return False.""" + return value.isidentifier() and not keyword.iskeyword(value) + + def cache(func): # type: (t.Callable[[], TValue]) -> t.Callable[[], TValue] """Enforce exclusive access on a decorated function and cache the result.""" storage = {} # type: t.Dict[None, TValue]