diff --git a/.ansible-lint b/.ansible-lint index dbb1a0c08f..1281a21f65 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -31,6 +31,12 @@ mock_roles: # Enable checking of loop variable prefixes in roles loop_var_prefix: "^(__|{role}_)" +# Enforce role-related variable names to start with the pattern below. +# By default Ansible Lint accepts "role name" prefix with optional leading underscore(s). +# With this option for example you can prefix role variables with the collection name +# which the role belongs to instead of role name. +role_var_prefix: "^_*{role}_" + # Enforce variable names to follow pattern below, in addition to Ansible own # requirements, like avoiding python identifiers. To disable add `var-naming` # to skip_list. diff --git a/examples/roles/role_vars_prefix_detection/.ansible-lint b/examples/roles/role_vars_prefix_detection/.ansible-lint new file mode 100644 index 0000000000..038d6b506c --- /dev/null +++ b/examples/roles/role_vars_prefix_detection/.ansible-lint @@ -0,0 +1,2 @@ +--- +role_var_prefix: "^_*({role}_|fo|bar)" diff --git a/src/ansiblelint/config.py b/src/ansiblelint/config.py index a06b3fc4d8..10aeddec7c 100644 --- a/src/ansiblelint/config.py +++ b/src/ansiblelint/config.py @@ -182,6 +182,7 @@ class Options: # pylint: disable=too-many-instance-attributes # Refer to https://docs.ansible.com/projects/ansible/latest/reference_appendices/release_and_maintenance.html#ansible-core-support-matrix _default_supported = ["2.15.", "2.16.", "2.17.", "2.18.", "2.19."] supported_ansible_also: list[str] = field(default_factory=list) + role_var_prefix: str | None = None @property def nodeps(self) -> bool: diff --git a/src/ansiblelint/rules/var_naming.md b/src/ansiblelint/rules/var_naming.md index 0d7148d7db..97cedc40f1 100644 --- a/src/ansiblelint/rules/var_naming.md +++ b/src/ansiblelint/rules/var_naming.md @@ -29,12 +29,9 @@ Possible errors messages: !!! note When using `include_role` or `import_role` with `vars`, vars should start - with included role name prefix. As this role might not be compliant - with this rule yet, you might need to temporarily disable this rule using - a `# noqa: var-naming[no-role-prefix]` comment. - - In all other task types variable names defined in `vars` are considered - task-scoped and do not require the role prefix. + with included role name prefix. If you want to tweak the role vars' + prefix pattern such as prefixing them with the collection name, you could + use `role_var_prefix` configuration option. ## Settings diff --git a/src/ansiblelint/rules/var_naming.py b/src/ansiblelint/rules/var_naming.py index a19196236c..4ad75262c7 100644 --- a/src/ansiblelint/rules/var_naming.py +++ b/src/ansiblelint/rules/var_naming.py @@ -10,11 +10,7 @@ from ansible.vars.reserved import get_reserved_names from ansiblelint.config import Options, options -from ansiblelint.constants import ( - ANNOTATION_KEYS, - PLAYBOOK_ROLE_KEYWORDS, - RC, -) +from ansiblelint.constants import ANNOTATION_KEYS, PLAYBOOK_ROLE_KEYWORDS, RC from ansiblelint.file_utils import Lintable from ansiblelint.rules import AnsibleLintRule, RulesCollection from ansiblelint.runner import Runner @@ -45,6 +41,7 @@ class VariableNamingRule(AnsibleLintRule): needs_raw_task = True re_pattern_str = options.var_naming_pattern or "^[a-z_][a-z0-9_]*$" re_pattern = re.compile(re_pattern_str) + re_role_var_prefix_pattern_str = options.role_var_prefix or "^_*{role}_" reserved_names = get_reserved_names() # List of special variables that should be treated as read-only. This list # does not include connection variables, which we expect users to tune in @@ -179,13 +176,16 @@ def get_var_naming_matcherror( if ( prefix - and not ident.lstrip("_").startswith(f"{prefix.value}_") + and not re.match( + self.re_role_var_prefix_pattern_str.format(role=prefix.value), + ident, + ) and not has_jinja(prefix.value) and is_fqcn_or_name(prefix.value) ): return self.create_matcherror( tag="var-naming[no-role-prefix]", - message=f"Variables names from within roles should use {prefix.value}_ as a prefix.", + message=f"Variables names from within roles should use /{self.re_role_var_prefix_pattern_str.format(role=prefix.value)}/ pattern as a prefix.", filename=file, data=ident, ) @@ -347,8 +347,8 @@ def _parse_prefix(self, fqcn: str) -> Prefix: if "pytest" in sys.modules: import pytest - from ansiblelint.testing import ( # pylint: disable=ungrouped-imports - run_ansible_lint, + from ansiblelint.testing import ( + run_ansible_lint, # pylint: disable=ungrouped-imports ) @pytest.mark.parametrize( @@ -432,6 +432,17 @@ def test_var_naming_with_role_prefix( for result in results: assert result.tag == "var-naming[no-role-prefix]" + def test_var_naming_with_custom_role_prefix() -> None: + """Test rule matches.""" + role_path = "examples/roles/role_vars_prefix_detection" + conf_path = "examples/roles/role_vars_prefix_detection/.ansible-lint" + result = run_ansible_lint( + f"--config-file={conf_path}", + role_path, + ) + assert result.returncode == RC.SUCCESS + assert "var-naming[no-role-prefix]" not in result.stdout + @pytest.mark.libyaml def test_var_naming_with_role_prefix_plays( default_rules_collection: RulesCollection, diff --git a/src/ansiblelint/schemas/ansible-lint-config.json b/src/ansiblelint/schemas/ansible-lint-config.json index 01e9e08c03..cc2e60a34e 100644 --- a/src/ansiblelint/schemas/ansible-lint-config.json +++ b/src/ansiblelint/schemas/ansible-lint-config.json @@ -129,6 +129,10 @@ "title": "Quiet", "type": "boolean" }, + "role_var_prefix": { + "title": "Role Var Prefix", + "type": "string" + }, "rules": { "additionalProperties": { "$ref": "#/$defs/rule" diff --git a/test/test_cli_role_paths.py b/test/test_cli_role_paths.py index bb80b3cda5..22fd887633 100644 --- a/test/test_cli_role_paths.py +++ b/test/test_cli_role_paths.py @@ -232,8 +232,6 @@ def test_run_role_identified_prefix_missing(local_test_dir: Path) -> None: ) assert result.returncode == RC.VIOLATIONS_FOUND assert ( - "Variables names from within roles should use bar_ as a prefix" in result.stdout - ) - assert ( - "Variables names from within roles should use bar_ as a prefix" in result.stdout + "Variables names from within roles should use /^_*bar_/ pattern as a prefix." + in result.stdout )