aboutsummaryrefslogtreecommitdiff
path: root/.ansible-lint-rules/kolla_container_become.py
diff options
context:
space:
mode:
Diffstat (limited to '.ansible-lint-rules/kolla_container_become.py')
-rw-r--r--.ansible-lint-rules/kolla_container_become.py209
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)
+ )
+ )