diff options
Diffstat (limited to '.ansible-lint-rules/kolla_container_become.py')
| -rw-r--r-- | .ansible-lint-rules/kolla_container_become.py | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/.ansible-lint-rules/kolla_container_become.py b/.ansible-lint-rules/kolla_container_become.py new file mode 100644 index 000000000..867b728dd --- /dev/null +++ b/.ansible-lint-rules/kolla_container_become.py @@ -0,0 +1,209 @@ +# Copyright 2026 Michal Nasiadka +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Custom ansible-lint rule to enforce become on + kolla_container/kolla_container_facts/kolla_toolbox modules.""" + +from typing import Any + +from ansiblelint.errors import MatchError +from ansiblelint.file_utils import Lintable +from ansiblelint.rules import AnsibleLintRule + + +class KollaContainerBecomeRule(AnsibleLintRule): + """Kolla container modules should have become set to true.""" + + id = "kolla-container-become" + description = """Kolla container module invocations should have become set + to true (task or block level)""" + severity = "MEDIUM" + tags = ["security", "kolla"] + version_added = "v6.0.0" + version_changed = "v6.0.0" + + def matchyaml(self, file: Lintable) -> list[MatchError]: + """Check if Kolla container module tasks have become set to True. + + Args: + file: The file being linted + + Returns: + List of MatchError objects if violations found + """ + errors = [] + + # Get the parsed YAML data + data = file.data + if not data: + return errors + + # Process plays or task files + if isinstance(data, list): + # Check if this is a playbook (contains plays) or + # a task file (just tasks) + if data and isinstance(data[0], dict): + # If first item has 'hosts' or 'import_playbook', + # it's a playbook + first_item = data[0] + if 'hosts' in first_item or 'import_playbook' in first_item: + # Process as playbook + for play_idx, play in enumerate(data): + if not isinstance(play, dict): + continue + + # Check tasks at play level + self._check_tasks(play.get('tasks', []), None, errors, + file, f".[{play_idx}].tasks") + + # Check pre_tasks + self._check_tasks(play.get('pre_tasks', []), None, + errors, file, + f".[{play_idx}].pre_tasks") + + # Check post_tasks + self._check_tasks(play.get('post_tasks', []), None, + errors, file, + f".[{play_idx}].post_tasks") + + # Check handlers + self._check_tasks(play.get('handlers', []), None, + errors, file, + f".[{play_idx}].handlers") + else: + # Process as task file (list of tasks directly) + self._check_tasks(data, None, errors, file, ".") + + return errors + + def _check_tasks( + self, + tasks: list[dict[str, Any]], + parent_become: bool | None, + errors: list[MatchError], + file: Lintable, + path: str + ) -> None: + """Recursively check tasks and blocks. + + Checks kolla_container/kolla_container_facts/kolla_toolbox modules + with become. + + Args: + tasks: List of tasks to check + parent_become: The become value inherited from parent block + errors: List to append errors to + file: The file being linted + path: Current path in the YAML structure + """ + + if not tasks: + return + + for task_idx, task in enumerate(tasks): + if not isinstance(task, dict): + continue + + task_path = f"{path}[{task_idx}]" + + # Determine effective become value for this task + task_become = task.get('become') + if task_become is not None: + effective_become = task_become + else: + effective_become = parent_become + + # Check if this is a block + if 'block' in task: + # For blocks, pass down the effective become value + block_become = task.get('become') + if block_become is not None: + inherited_become = block_become + else: + inherited_become = parent_become + + # Recursively check tasks in the block + self._check_tasks( + task.get('block', []), + inherited_become, + errors, + file, + f"{task_path}.block" + ) + + # Also check rescue and always sections + if 'rescue' in task: + self._check_tasks( + task.get('rescue', []), + inherited_become, + errors, + file, + f"{task_path}.rescue" + ) + + if 'always' in task: + self._check_tasks( + task.get('always', []), + inherited_become, + errors, + file, + f"{task_path}.always" + ) + + continue + + # Check if this task uses Kolla container modules + module_name = None + + # Check for Kolla modules in various formats + kolla_modules = ['kolla_container', 'kolla_container_facts', + 'kolla_toolbox'] + + for kolla_module in kolla_modules: + if kolla_module in task: + module_name = kolla_module + break + + # If this is a Kolla container task, check for become + if module_name: + if effective_become is not True: + task_name = task.get('name', 'unnamed task') + + if task_become is False: + message = (f"Kolla container task '{task_name}' " + f"(module: {module_name}) has 'become: " + f"false'") + if parent_become is True: + message += """ which overrides block-level + 'become: true'""" + elif task_become is None and parent_become is False: + message = (f"Kolla container task '{task_name}' " + f"(module: {module_name}) inherits " + f"'become: false' from block") + elif task_become is None and parent_become is None: + message = (f"Kolla container task '{task_name}' " + f"(module: {module_name}) is missing " + f"'become: true' at task or block level") + else: + message = (f"Kolla container task '{task_name}' " + f"(module: {module_name}) has 'become: " + f"{task_become}' instead of 'true'") + + errors.append( + self.create_matcherror( + message=message, + filename=file, + lineno=task.get('__line__', 1) + ) + ) |
