ansible-lint: Rework validate-all-file into custom rules

Test roles/ subdirectories as well (used by Zuul)

In addition to that container_exec_become will also
fail when "docker exec" or "podman exec" will be used
instead of "{{ kolla_container_engine }} exec"

Generated-By: Claude

Change-Id: I75c1e48c36bb0284e2927b1cb354ce9ec47967d7
Signed-off-by: Michal Nasiadka <mnasiadka@gmail.com>
This commit is contained in:
Michal Nasiadka committed 2026-01-09 08:34:19 +00:00
1 parent 7d89d6afbe
commit c8ea844b2b
38 files changed
+645 -314

No files matched your search

+6 -2
View File
@@ -1,13 +1,13 @@
---
exclude_paths:
- releasenotes
- roles
- tests
- zuul.d
rulesdir:
- .ansible-lint-rules/
# NOTE(mnasiadka): Switched to false due to rules skipped via .ansible-lint-ignore causing
# failures
strict: false
use_default_rules: true
skip_list:
# disable experimental rules
- experimental
@@ -16,5 +16,9 @@ skip_list:
- fqcn-builtins
# FQCNs again, now for module actions
- fqcn[action]
# Role names
- role-name
- var-naming[no-role-prefix]
- yaml[line-length]
use_default_rules: true
@@ -0,0 +1,271 @@
# 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 container exec commands."""
from typing import Any
from ansiblelint.errors import MatchError
from ansiblelint.file_utils import Lintable
from ansiblelint.rules import AnsibleLintRule
class ContainerExecBecomeRule(AnsibleLintRule):
id = "container-exec-become"
description = """shell/command tasks starting with 'docker exec' or
'podman exec' should have become set to True"""
severity = "MEDIUM"
tags = ["security", "container"]
version_added = "v6.0.0"
version_changed = "v6.0.0"
def matchyaml(self, file: Lintable) -> list[MatchError]:
"""Check if container exec commands 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.
Check for container exec commands 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 shell or command module
module_name = None
command_value = None
# Check for shell/command modules
shell_modules = [
'ansible.builtin.shell', 'shell',
'ansible.builtin.command', 'command'
]
for shell_module in shell_modules:
if shell_module in task:
module_name = shell_module
# Get the command - can be a string or dict
cmd = task[shell_module]
if isinstance(cmd, str):
command_value = cmd
elif isinstance(cmd, dict):
# Command might be in 'cmd' key or the module args
command_value = (cmd.get('cmd') or
cmd.get('_raw_params'))
break
# If this is a shell/command task, check if it starts with
# docker/podman exec
if module_name and command_value:
# Strip leading whitespace and newlines, check if it starts
# with docker/podman exec
cmd_stripped = command_value.strip()
# Check for hardcoded docker/podman exec (should use variable)
uses_hardcoded_exec = (
cmd_stripped.startswith('docker exec') or
cmd_stripped.startswith('podman exec')
)
if uses_hardcoded_exec:
# Flag hardcoded container engine usage
task_name = task.get('name', 'unnamed task')
if cmd_stripped.startswith('docker exec'):
exec_type = 'docker exec'
else:
exec_type = 'podman exec'
message = (f"Task '{task_name}' uses hardcoded "
f"'{exec_type}' - use "
f"'{{{{ kolla_container_engine }}}} exec' "
f"instead")
errors.append(
self.create_matcherror(
message=message,
filename=file,
lineno=task.get('__line__', 1)
)
)
# Continue to also check become requirement
# Check for variable-based container engine followed by exec
# e.g., "{{ kolla_container_engine }} exec",
# "{{ container_engine }} exec"
import re
variable_exec_pattern = r'^\{\{[^}]+\}\}\s+exec\s+'
uses_variable_exec = re.match(variable_exec_pattern,
cmd_stripped)
is_container_exec = uses_hardcoded_exec or uses_variable_exec
if is_container_exec:
# Check if become is True
if effective_become is not True:
task_name = task.get('name', 'unnamed task')
# Determine exec type for error message
if cmd_stripped.startswith('docker exec'):
exec_type = 'docker exec'
elif cmd_stripped.startswith('podman exec'):
exec_type = 'podman exec'
else:
exec_type = 'container exec (via variable)'
if task_become is False:
message = (f"Task '{task_name}' uses "
f"'{exec_type}' but has "
f"'become: 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"Task '{task_name}' uses "
f"'{exec_type}' and inherits "
f"'become: false' from block")
elif task_become is None and parent_become is None:
message = (f"Task '{task_name}' uses "
f"'{exec_type}' but is missing "
f"'become: true' at task or "
f"block level")
else:
message = (f"Task '{task_name}' uses "
f"'{exec_type}' but has "
f"'become: {task_become}' instead of "
f"'true'")
errors.append(
self.create_matcherror(
message=message,
filename=file,
lineno=task.get('__line__', 1)
)
)
@@ -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)
)
)
+2 -2
View File
@@ -1,5 +1,5 @@
---
- name: "Copying over {{ project_name }} haproxy config"
- name: "Copying over haproxy config for {{ project_name }}"
vars:
service: "{{ item.value }}"
haproxy_templates:
@@ -20,7 +20,7 @@
- enable_haproxy | bool
with_dict: "{{ project_services }}"
- name: "Add configuration for {{ project_name }} when using single external frontend"
- name: "Add configuration when using single external frontend for {{ project_name }}"
vars:
service: "{{ item.value }}"
blockinfile:
@@ -1,8 +1,10 @@
---
- import_role:
- name: Import haproxy-config role
import_role:
name: haproxy-config
when: haproxy_config_install | bool
- import_role:
- name: Import proxysql-config role
import_role:
name: proxysql-config
when: enable_proxysql | bool
+1
View File
@@ -20,6 +20,7 @@
template:
src: module-load.conf.j2
dest: "/etc/modules-load.d/{{ item.name }}.conf"
mode: "0644"
loop: "{{ modules }}"
loop_control:
label: "{{ item.name }}"
+2 -2
View File
@@ -1,5 +1,5 @@
---
- name: "Copying over {{ proxysql_project }} ProxySQL users config"
- name: "Copying over ProxySQL users config for {{ proxysql_project }}"
template:
src: "users.yaml.j2"
dest: "{{ node_config_directory }}/proxysql/users/{{ proxysql_project }}.yaml"
@@ -8,7 +8,7 @@
when:
- proxysql_config_users | bool
- name: "Copying over {{ proxysql_project }} ProxySQL rules config"
- name: "Copying over ProxySQL rules config for {{ proxysql_project }}"
template:
src: "rules.yaml.j2"
dest: "{{ node_config_directory }}/proxysql/rules/{{ proxysql_project }}.yaml"
@@ -1,5 +1,5 @@
---
- name: "{{ project_name }} | Copying over extra CA certificates"
- name: "Copying over extra CA certificates for {{ project_name }}"
become: true
copy:
src: "{{ kolla_certificates_dir }}/ca/"
@@ -9,7 +9,7 @@
- kolla_copy_ca_into_containers | bool
with_dict: "{{ project_services | select_services_enabled_and_mapped_to_host }}"
- name: "{{ project_name }} | Copying over backend internal TLS certificate"
- name: "Copying over backend internal TLS certificate for {{ project_name }}"
vars:
certs:
- "{{ kolla_certificates_dir }}/{{ inventory_hostname }}/{{ project_name }}-cert.pem"
@@ -27,7 +27,7 @@
- inventory_hostname is in groups["tls-backend"]
with_dict: "{{ project_services | select_services_enabled_and_mapped_to_host }}"
- name: "{{ project_name }} | Copying over backend internal TLS key"
- name: "Copying over backend internal TLS key for {{ project_name }}"
vars:
keys:
- "{{ kolla_certificates_dir }}/{{ inventory_hostname }}/{{ project_name }}-key.pem"
@@ -1,5 +1,5 @@
---
- name: "{{ kolla_role_name | default(project_name) }} | Check containers with iteration"
- name: "Check containers with iteration for {{ kolla_role_name | default(project_name) }}"
become: true
vars:
service: "{{ outer_item.value }}"
@@ -23,13 +23,13 @@
command: "{{ service.command | default(omit) }}"
cgroupns_mode: "{{ service.cgroupns_mode | default(omit) }}"
loop:
- "{{ range(1,(service.iterate_var | int) + 1) | list }}"
- "{{ range(1, (service.iterate_var | int) + 1) | list }}"
register: container_check
# NOTE(yoctozepto): Must be a separate task because one cannot see the whole
# result in the previous task and Ansible has a quirk regarding notifiers.
# For details see https://github.com/ansible/ansible/issues/22579
- name: "{{ kolla_role_name | default(project_name) }} | Notify handlers to restart containers"
- name: "Notify handlers to restart containers for {{ kolla_role_name | default(project_name) }}"
debug:
msg: Notifying handlers
changed_when: container_check is changed
@@ -1,7 +1,7 @@
---
# NOTE(r-krcek): List of arguments should follow argument_spec in
# kolla_container module
- name: "{{ kolla_role_name | default(project_name) }} | Check containers"
- name: "Check containers for {{ kolla_role_name | default(project_name) }}"
become: true
vars:
service: "{{ item.value }}"
@@ -31,7 +31,7 @@
# NOTE(yoctozepto): Must be a separate task because one cannot see the whole
# result in the previous task and Ansible has a quirk regarding notifiers.
# For details see https://github.com/ansible/ansible/issues/22579
- name: "{{ kolla_role_name | default(project_name) }} | Notify handlers to restart containers"
- name: "Notify handlers to restart containers for {{ kolla_role_name | default(project_name) }}"
vars:
service: "{{ item.value }}"
debug:
@@ -40,7 +40,7 @@
when: not (service.iterate | default(False)) | bool
notify: "{{ container_check.results | select('changed') | map(attribute='item.key') | map('regex_replace', '^(.*)$', 'Restart \\1 container') | list }}"
- name: "{{ kolla_role_name | default(project_name) }} | Check containers that require iteration"
- name: "Check containers that require iteration for {{ kolla_role_name | default(project_name) }}"
vars:
service: "{{ outer_item.value }}"
include_tasks: iterated.yml
+3 -3
View File
@@ -1,6 +1,6 @@
---
# Check whether all necessary containers are running.
- name: "{{ kolla_role_name | default(project_name) }} | Get container facts"
- name: "Get container facts for {{ kolla_role_name | default(project_name) }}"
become: true
kolla_container_facts:
action: get_containers
@@ -9,7 +9,7 @@
register: container_facts
when: service_check_enabled_container_names | length > 0
- name: "{{ kolla_role_name | default(project_name) }} | Fail if containers are missing or not running"
- name: "Fail if containers are missing or not running for {{ kolla_role_name | default(project_name) }}"
vars:
missing_containers: >-
{{ service_check_enabled_container_names |
@@ -23,7 +23,7 @@
- container_facts.containers is defined
- missing_containers | length > 0
- name: "{{ kolla_role_name | default(project_name) }} | Fail if containers are unhealthy"
- name: "Fail if containers are unhealthy for {{ kolla_role_name | default(project_name) }}"
vars:
unhealthy_containers: >-
{{ container_facts.containers |
@@ -1,5 +1,5 @@
---
- name: "{{ project_name }} | Validate configurations for each service"
- name: "Validate configurations for each service for {{ project_name }}"
vars:
service_name: "{{ outer_item.key }}"
service: "{{ outer_item.value }}"
@@ -1,5 +1,5 @@
---
- name: "{{ project_name }} : {{ service.container_name }} | Get info on container"
- name: "Get info on container for {{ service.container_name }}"
become: true
kolla_container_facts:
action: get_containers
@@ -8,7 +8,7 @@
- "{{ service.container_name }}"
register: container_info
- name: "{{ project_name }} : {{ service.container_name }} | Validate configurations"
- name: "Validate configurations for {{ service.container_name }}"
become: true
command: >
{{ kolla_container_engine }} exec {{ service.container_name }}
@@ -23,21 +23,23 @@
loop_var: inner_item
changed_when: false
- name: "{{ project_name }} : {{ service.container_name }} | Ensure log directory exists"
- name: "Ensure log directory exists for {{ service.container_name }}"
become: true
file:
path: "{{ output_dir }}"
state: directory
mode: "0755"
when:
- result.results | map(attribute='rc', default=0) | select('equalto', 1) | list | length > 0
- result.results | map(attribute='stderr', default="") | select('ne', "") | list | length > 0
delegate_to: localhost
- name: "{{ project_name }} : {{ service.container_name }} | Log configuration errors"
- name: "Log configuration errors for {{ service.container_name }}"
become: true
copy:
content: "{{ inner_item.stderr }}"
dest: "{{ output_dir }}/{{ inner_item.inner_item.config | basename }}.err"
mode: "0644"
when:
- container_info.containers | length > 0
- inner_item.rc is defined
@@ -1,5 +1,5 @@
---
- name: "{{ kolla_role_name | default(project_name) }} | Pull images"
- name: "Pull images for {{ kolla_role_name | default(project_name) }}"
vars:
service: "{{ item.value }}"
become: true
@@ -1,6 +1,9 @@
---
- block:
- name: "{{ project_name }} | Creating/deleting services"
- name: "Create/delete services/endpoints/projects/users/roles for {{ project_name }}"
become: true
run_once: true
block:
- name: "Creating/deleting services for {{ project_name }}"
kolla_toolbox:
container_engine: "{{ kolla_container_engine }}"
module_name: openstack.cloud.catalog_service
@@ -22,7 +25,7 @@
delay: "{{ service_ks_register_delay }}"
when: item.enabled | default(True) | bool
- name: "{{ project_name }} | Creating/deleting endpoints"
- name: "Creating/deleting endpoints for {{ project_name }}"
kolla_toolbox:
container_engine: "{{ kolla_container_engine }}"
module_name: openstack.cloud.endpoint
@@ -47,7 +50,7 @@
delay: "{{ service_ks_register_delay }}"
when: item.0.enabled | default(True) | bool
- name: "{{ project_name }} | Creating projects"
- name: "Creating projects for {{ project_name }}"
kolla_toolbox:
container_engine: "{{ kolla_container_engine }}"
module_name: openstack.cloud.project
@@ -64,7 +67,7 @@
retries: "{{ service_ks_register_retries }}"
delay: "{{ service_ks_register_delay }}"
- name: "{{ project_name }} | Creating/deleting users"
- name: "Creating/deleting users for {{ project_name }}"
kolla_toolbox:
container_engine: "{{ kolla_container_engine }}"
module_name: openstack.cloud.identity_user
@@ -90,7 +93,7 @@
retries: "{{ service_ks_register_retries }}"
delay: "{{ service_ks_register_delay }}"
- name: "{{ project_name }} | Creating roles"
- name: "Creating roles for {{ project_name }}"
kolla_toolbox:
container_engine: "{{ kolla_container_engine }}"
module_name: openstack.cloud.identity_role
@@ -106,7 +109,7 @@
retries: "{{ service_ks_register_retries }}"
delay: "{{ service_ks_register_delay }}"
- name: "{{ project_name }} | Granting/revoking user roles"
- name: "Granting/revoking user roles for {{ project_name }}"
kolla_toolbox:
container_engine: "{{ kolla_container_engine }}"
module_name: openstack.cloud.role_assignment
@@ -128,6 +131,3 @@
until: service_ks_register_result is success
retries: "{{ service_ks_register_retries }}"
delay: "{{ service_ks_register_delay }}"
become: true
run_once: True
@@ -1,5 +1,5 @@
---
- name: "{{ project_name }} | Validate inventory groups"
- name: "Validate inventory groups for {{ project_name }}"
vars:
service_name: "{{ item.key }}"
service: "{{ item.value }}"
+1 -1
View File
@@ -1,5 +1,5 @@
---
- name: "Stopping {{ service_name }} containers"
- name: "Stopping containers for {{ service_name }}"
vars:
service: "{{ item.value }}"
become: true
@@ -1,5 +1,5 @@
---
- name: "Copying over {{ service_name }} uWSGI config"
- name: "Copying over uWSGI config for {{ service_name }}"
template:
src: "uwsgi.ini.j2"
dest: "{{ service_uwsgi_config_file }}"
+9 -3
View File
@@ -1,12 +1,18 @@
---
- name: Create bridge
become: true
command: ip link add {{ bridge_name }} type bridge
changed_when: true
ansible.builtin.command:
cmd: ip link add {{ bridge_name }} type bridge
- name: Bring bridge interface up
become: true
command: ip link set {{ bridge_name }} up
changed_when: true
ansible.builtin.command:
cmd: ip link set {{ bridge_name }} up
- name: Add member interface to bridge
become: true
command: ip link set {{ bridge_member_name }} master {{ bridge_name }}
changed_when: true
ansible.builtin.command:
cmd: ip link set {{ bridge_member_name }} master {{ bridge_name }}
+21 -12
View File
@@ -1,39 +1,43 @@
---
- include_tasks: pkg_{{ ansible_os_family | lower }}.yml
- name: Include package installation
include_tasks: pkg_{{ ansible_os_family | lower }}.yml
- name: Ensure /etc/ceph exists
file:
path: /etc/ceph
state: directory
become: True
mode: "0755"
become: true
- name: Generate ssh key for cephadm
openssh_keypair:
path: "/etc/ceph/cephadm.id"
mode: "0600"
size: 4096
comment: "cephadm"
become: True
become: true
register: cephadm_ssh_key
- name: Save public key
copy:
content: "{{ cephadm_ssh_key.public_key }}"
dest: /etc/ceph/cephadm.pub
become: True
mode: "0644"
become: true
- name: Copy cephadm public key to all hosts
authorized_key:
user: root
state: present
key: "{{ cephadm_ssh_key.public_key }}"
become: True
become: true
with_inventory_hostnames:
- all
delegate_to: "{{ item }}"
- name: Bootstrap cephadm
vars:
mon_ip: "{{ hostvars[inventory_hostname]['ansible_' + api_interface_name | replace('-','_')].ipv4.address }}"
mon_ip: "{{ hostvars[inventory_hostname]['ansible_' + api_interface_name | replace('-', '_')].ipv4.address }}"
command:
cmd: >
cephadm
@@ -46,7 +50,8 @@
--skip-firewalld
--log-to-file
--mon-ip {{ mon_ip }}
become: True
become: true
changed_when: true
register: cephadm_bootstrap_output
- name: Get ceph fsid
@@ -59,20 +64,23 @@
template:
src: templates/cephadm.yml.j2
dest: "/var/run/ceph/{{ ceph_fsid }}/cluster.yml"
become: True
mode: "0600"
become: true
- name: Template out command spec
template:
src: templates/commands.sh.j2
dest: "/var/run/ceph/{{ ceph_fsid }}/commands.sh"
become: True
mode: "0600"
become: true
- name: Run commands
command:
cmd: >
cephadm shell --
bash -x /var/run/ceph/commands.sh
become: True
become: true
changed_when: true
# TODO(mnasiadka): Fix merge_configs to support tabs
- name: Generate ceph.conf without tabs
@@ -80,8 +88,9 @@
ceph_conf_fixed: |
[global]
fsid = {{ ceph_fsid }}
mon_host = {% for host in groups['all'] %} {{ hostvars[host]['ansible_' + api_interface_name | replace('-','_')].ipv4.address }} {% if not loop.last %},{% endif %} {% endfor %}
mon_host = {% for host in groups['all'] %} {{ hostvars[host]['ansible_' + api_interface_name | replace('-', '_')].ipv4.address }} {% if not loop.last %},{% endif %} {% endfor %}
copy:
content: "{{ ceph_conf_fixed }}"
dest: "/etc/ceph/ceph.conf.fixed"
become: True
mode: "0644"
become: true
+5 -5
View File
@@ -1,23 +1,23 @@
---
- name: Ensure apt sources list directory exists
become: true
file:
path: /etc/apt/sources.list.d
state: directory
recurse: yes
become: True
recurse: true
- name: Enable Ceph apt repository
become: true
apt_repository:
repo: "{{ cephadm_ceph_apt_repo }}"
filename: ceph
become: True
when: not cephadm_use_package_from_distribution | bool
- name: Install cephadm
become: true
apt:
name:
- cephadm
- python3-jinja2
- python3-yaml
install_recommends: False
become: True
install_recommends: false
+6 -6
View File
@@ -1,32 +1,32 @@
---
- name: Ensure yum repos directory exists
become: true
file:
path: /etc/yum.repos.d/
state: directory
recurse: yes
become: True
recurse: true
- name: Install centos-release-ceph
become: true
dnf:
name: "centos-release-ceph-{{ cephadm_ceph_release }}"
become: True
when: cephadm_use_package_from_distribution
- name: Enable Ceph base yum repository
become: true
yum_repository:
name: "{{ item.shortname }}"
description: "{{ item.name }}"
baseurl: "{{ item.url }}"
gpgkey: "{{ cephadm_ceph_rpm_gpg_key }}"
loop: "{{ cephadm_ceph_rpm_repos }}"
become: True
when: not cephadm_use_package_from_distribution
# NOTE(mnasiadka): cephadm bootstrap failing on jinja2 missing
- name: Install cephadm and jinja2
become: true
dnf:
name:
- cephadm
- python3-jinja2
install_weak_deps: False
become: True
install_weak_deps: false
@@ -6,4 +6,4 @@
kolla-ansible deploy-bifrost
-i /etc/kolla/inventory
>/tmp/logs/ansible/deploy-bifrost 2>&1
changed_when: true
@@ -7,6 +7,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/certificates 2>&1
changed_when: true
- name: Init pebble when Lets Encrypt is enabled
when: (le_enabled | default(False)) | bool
@@ -30,10 +31,12 @@
become: true
ansible.builtin.command:
cmd: "docker cp pebble:/test/certs/pebble.minica.pem /etc/kolla/certificates/ca/pebble-root.crt"
changed_when: true
- name: "Fetch pebble.crt and store it in /etc/kolla/certificates/ca/"
become: true
ansible.builtin.get_url:
url: "https://127.0.0.1:15000/roots/0"
dest: "/etc/kolla/certificates/ca/pebble.crt"
mode: "0644"
validate_certs: false
@@ -7,6 +7,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/deploy-prechecks 2>&1
changed_when: false
- name: Run kolla-ansible pull
ansible.builtin.shell:
@@ -16,6 +17,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/pull 2>&1
changed_when: true
- name: Run kolla-ansible deploy
ansible.builtin.shell:
@@ -25,6 +27,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/deploy 2>&1
changed_when: true
- name: Run kolla-ansible post-deploy
ansible.builtin.shell:
@@ -34,6 +37,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/post-deploy 2>&1
changed_when: true
- name: Run kolla-ansible validate-config
ansible.builtin.shell:
@@ -43,6 +47,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/validate-config 2>&1
changed_when: false
when: not is_upgrade | bool
- name: Run kolla-ansible check
@@ -53,3 +58,4 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/check 2>&1
changed_when: false
+5 -2
View File
@@ -7,5 +7,8 @@
mode: "0777"
recurse: true
- import_tasks: certificates.yml
- import_tasks: deploy.yml
- name: Import certificates.yml
import_tasks: certificates.yml
- name: Import deploy.yml
import_tasks: deploy.yml
@@ -7,6 +7,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/reconfigure-prechecks 2>&1
changed_when: false
- name: Remove OVN DB containers and volumes on primary to test recreation (docker)
become: true
@@ -42,8 +43,9 @@
# NOTE(blanson): there is apparently no module to do that (!?)
- name: List docker containers
ansible.builtin.command:
cmd: "docker ps --format '{{'{{'}}.Names{{'}}'}}'"
cmd: "docker ps --format '{{ '{{' }}.Names{{ '}}' }}'"
register: _deploy_docker_ps_raw
changed_when: false
- name: Get details of all containers on host
community.docker.docker_container_info:
@@ -137,6 +139,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/reconfigure 2>&1
changed_when: true
- name: "Capture state of containers post reconfigure (docker)"
become: true
@@ -149,8 +152,9 @@
block:
- name: List docker containers
ansible.builtin.command:
cmd: "docker ps --format '{{'{{'}}.Names{{'}}'}}'"
cmd: "docker ps --format '{{ '{{' }}.Names{{ '}}' }}'"
register: _reconfigure_docker_ps_raw
changed_when: false
- name: Get details of all containers on host
community.docker.docker_container_info:
@@ -15,8 +15,9 @@
- name: Get free loop device
become: true
ansible.builtin.shell:
ansible.builtin.command:
cmd: "losetup -f"
changed_when: false
register: _loop_device
- name: Mount file on loop device
@@ -25,6 +26,7 @@
cmd: >
losetup {{ _loop_device.stdout }}
{{ kolla_ansible_setup_disks_file_path }}
changed_when: true
- name: Create LVM extents on loop device
become: true
+4 -2
View File
@@ -14,6 +14,8 @@
creates: "/home/zuul/tempest"
- name: Discover tempest config
environment:
OS_CLIENT_CONFIG_FILE: "/etc/kolla/clouds.yaml"
vars:
ver: "{{ kolla_ansible_tempest_cirros_ver }}"
image: "https://download.cirros-cloud.net/{{ ver }}/cirros-{{ ver }}-x86_64-disk.img"
@@ -26,8 +28,7 @@
--os-cloud kolla-admin
compute.build_timeout {{ kolla_ansible_tempest_build_timeout }}
>/tmp/logs/ansible/test-init-tempest-discover 2>&1
environment:
OS_CLIENT_CONFIG_FILE: "/etc/kolla/clouds.yaml"
changed_when: false
- name: Run tempest tests
environment:
@@ -49,3 +50,4 @@
--exclude-regex '{{ kolla_ansible_tempest_exclude_regex | join("|") }}'
{% endif %}
>/tmp/logs/ansible/{{ tempest_log_file }} 2>&1
changed_when: true
@@ -5,6 +5,7 @@
cmd: >
{{ container_engine }} exec bifrost_deploy
bash -c 'OS_CLOUD=bifrost baremetal driver list'
changed_when: false
register: bdl
until: bdl.rc == 0
retries: 5
@@ -16,6 +17,7 @@
cmd: >
{{ container_engine }} exec bifrost_deploy
bash -c 'OS_CLOUD=bifrost baremetal node list'
changed_when: false
- name: Create baremetal node
become: true
@@ -24,6 +26,7 @@
{{ container_engine }} exec bifrost_deploy
bash -c 'OS_CLOUD=bifrost baremetal node create
--driver redfish --name test-node'
changed_when: true
- name: Delete baremetal node
become: true
@@ -32,4 +35,4 @@
{{ container_engine }} exec bifrost_deploy
bash -c 'OS_CLOUD=bifrost baremetal node delete
test-node'
changed_when: true
@@ -32,11 +32,13 @@
ansible.builtin.file:
path: "/home/zuul/testinfra"
state: directory
mode: "0755"
- name: Ensure screenshots directory exists
ansible.builtin.file:
path: "/home/zuul/testinfra/screenshots"
state: directory
mode: "0755"
- name: Ensure required packages are installed
ansible.builtin.pip:
@@ -88,3 +90,4 @@
--html=/home/zuul/testinfra/test-results-testinfra.html --self-contained-html
-v tests/testinfra
chdir: "{{ zuul.project.src_dir }}"
changed_when: false
@@ -6,3 +6,4 @@
kolla-ansible deploy-bifrost
-i /etc/kolla/inventory
>/tmp/logs/ansible/upgrade-bifrost 2>&1
changed_when: true
+7 -1
View File
@@ -7,6 +7,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/upgrade-certificates 2>&1
changed_when: true
# NOTE(mnasiadka): Need to run bootstrap before upgrade
- name: Run kolla-ansible bootstrap-servers
@@ -17,6 +18,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/upgrade-bootstrap 2>&1
changed_when: true
- name: Run kolla-ansible prechecks
ansible.builtin.shell:
@@ -26,6 +28,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/upgrade-prechecks 2>&1
changed_when: false
- name: Run kolla-ansible pull
ansible.builtin.shell:
@@ -35,6 +38,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/upgrade-pull 2>&1
changed_when: true
- name: Run kolla-ansible upgrade
ansible.builtin.shell:
@@ -44,6 +48,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/upgrade 2>&1
changed_when: true
- name: Run kolla-ansible post-deploy
ansible.builtin.shell:
@@ -53,6 +58,7 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/upgrade-post-deploy 2>&1
changed_when: true
- name: Run kolla-ansible validate-config on upgrades
ansible.builtin.shell:
@@ -62,4 +68,4 @@
-i /etc/kolla/inventory
-vvv
>/tmp/logs/ansible/upgrade-validate-config 2>&1
changed_when: false
@@ -21,13 +21,17 @@
# broadcast address as ansible picks up scope ('global') as the interface's
# broadcast address which fails checks logic
managed_network_broadcast_address: "{{ managed_network_cidr | ansible.utils.ipaddr('broadcast') }}"
command: ip address add {{ managed_network_cidr }} broadcast {{ managed_network_broadcast_address }} dev {{ managed_interface_name }}
ansible.builtin.command:
cmd: ip address add {{ managed_network_cidr }} broadcast {{ managed_network_broadcast_address }} dev {{ managed_interface_name }}
changed_when: true
when: managed_network_address_family == 'ipv4'
# NOTE(yoctozepto): IPv6 has no broadcast address, let's not create confusion by setting it
- name: Add IPv6 address for managed network
become: true
command: ip address add {{ managed_interface_address }}/{{ managed_network_prefix_length }} dev {{ managed_interface_name }}
ansible.builtin.command:
cmd: ip address add {{ managed_interface_address }}/{{ managed_network_prefix_length }} dev {{ managed_interface_name }}
changed_when: true
when: managed_network_address_family == 'ipv6'
- name: Accept traffic on the managed network (IN)
@@ -57,7 +61,9 @@
# hence we only check whether it's no longer tentative
- name: Ensure IPv6 addresses on the managed interface are no longer tentative
become: true
command: ip -o address show tentative dev {{ managed_interface_name }}
ansible.builtin.command:
cmd: ip -o address show tentative dev {{ managed_interface_name }}
changed_when: false
register: tentative_addresses
until: tentative_addresses.stdout == ''
retries: 30
@@ -69,5 +75,7 @@
become: true
vars:
ping_command: "{{ 'ping' if managed_network_address_family == 'ipv4' else 'ping6' }}"
command: "{{ ping_command }} -c1 {{ hostvars[item].managed_interface_address }}"
ansible.builtin.command:
cmd: "{{ ping_command }} -c1 {{ hostvars[item].managed_interface_address }}"
changed_when: false
with_inventory_hostnames: all
+12 -4
View File
@@ -21,7 +21,9 @@
- name: Create VXLAN interface
become: true
command: ip link add {{ vxlan_interface_name }} type vxlan id {{ vxlan_vni }} local {{ tunnel_local_address }} dstport {{ vxlan_dstport }}
ansible.builtin.command:
cmd: ip link add {{ vxlan_interface_name }} type vxlan id {{ vxlan_vni }} local {{ tunnel_local_address }} dstport {{ vxlan_dstport }}
changed_when: true
- name: Set VXLAN interface MTU
become: true
@@ -35,17 +37,23 @@
first }}
# Allow 50 bytes overhead for VXLAN headers.
mtu: "{{ parent_interface.mtu | int - 50 }}"
command: ip link set {{ vxlan_interface_name }} mtu {{ mtu }}
ansible.builtin.command:
cmd: ip link set {{ vxlan_interface_name }} mtu {{ mtu }}
changed_when: true
# emulate BUM by multiplicating traffic to unicast targets
- name: Add fdb entries for BUM traffic
become: true
vars:
dest_ip: "{{ hostvars[item].tunnel_local_address }}"
command: bridge fdb append 00:00:00:00:00:00 dev {{ vxlan_interface_name }} dst {{ dest_ip }}
ansible.builtin.command:
cmd: bridge fdb append 00:00:00:00:00:00 dev {{ vxlan_interface_name }} dst {{ dest_ip }}
changed_when: true
with_inventory_hostnames: all
when: item != inventory_hostname
- name: Bring VXLAN interface up
become: true
command: ip link set {{ vxlan_interface_name }} up
ansible.builtin.command:
cmd: ip link set {{ vxlan_interface_name }} up
changed_when: true
+9 -3
View File
@@ -1,12 +1,18 @@
---
- name: Create veth pair
become: true
command: ip link add {{ veth_pair.0 }} type veth peer name {{ veth_pair.1 }}
ansible.builtin.command:
cmd: ip link add {{ veth_pair.0 }} type veth peer name {{ veth_pair.1 }}
changed_when: true
- name: Bring veth pair up
become: true
shell: ip link set {{ veth_pair.0 }} up && ip link set {{ veth_pair.1 }} up
ansible.builtin.shell:
cmd: ip link set {{ veth_pair.0 }} up && ip link set {{ veth_pair.1 }} up
changed_when: true
- name: Add first veth pair port to bridge
become: true
command: ip link set {{ veth_pair.0 }} master {{ bridge_name }}
ansible.builtin.command:
cmd: ip link set {{ veth_pair.0 }} master {{ bridge_name }}
changed_when: true
-227
View File
@@ -1,227 +0,0 @@
#!/usr/bin/env python
# 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.
import collections
import fnmatch
import json
import logging
import os
import re
import sys
import jinja2
import yaml
from kolla_ansible.put_address_in_context import put_address_in_context
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
NEWLINE_EOF_INCLUDE_PATTERNS = ['*.j2', '*.yml', '*.py', '*.sh']
NEWLINE_EOF_EXCLUDE_PATTERNS = ['.tox', '.testrepository', '.git']
# Render json file by using jinja2 template is OK
JSON_J2_INCLUDE_PATTERNS = ['*.json.j2', '*.json']
JSON_J2_EXCLUDE_PATTERNS = ['.tox', '.testrepository', '.git']
YAML_INCLUDE_PATTERNS = ['*.yml']
YAML_EXCLUDE_PATTERNS = ['.tox', '.testrepository', '.git',
'defaults', 'templates', 'vars']
KOLLA_NETWORKS = [
'api',
'storage',
'migration',
'tunnel',
'octavia_network',
'bifrost_network',
'dns', # designate
]
logging.basicConfig()
LOG = logging.getLogger(__name__)
def check_newline_eof():
includes = r'|'.join([fnmatch.translate(x)
for x in NEWLINE_EOF_INCLUDE_PATTERNS])
excludes = r'|'.join([fnmatch.translate(x)
for x in NEWLINE_EOF_EXCLUDE_PATTERNS])
return_code = 0
def has_newline_eof(path):
with open(path, 'r') as f:
data = f.read()
if data and data[-1] != '\n':
LOG.error('%s file error: no newline at end of file', path)
return False
return True
for root, dirs, files in os.walk(PROJECT_ROOT):
dirs[:] = [d for d in dirs if not re.match(excludes, d)]
for f in files:
if not re.match(excludes, f) and re.match(includes, f):
if not has_newline_eof(os.path.join(root, f)):
return_code = 1
return return_code
def check_json_j2():
includes = r'|'.join([fnmatch.translate(x)
for x in JSON_J2_INCLUDE_PATTERNS])
excludes = r'|'.join([fnmatch.translate(x)
for x in JSON_J2_EXCLUDE_PATTERNS])
return_code = 0
def bool_filter(value):
return True
def basename_filter(text):
return text.split('\\')[-1]
def kolla_address_filter_mock(network_name, hostname=None):
# no validation is possible for the hostname
if network_name not in KOLLA_NETWORKS:
raise ValueError("{network_name} not in KOLLA_NETWORKS"
.format(network_name=network_name))
return "127.0.0.1"
# Mock ansible hostvars variable, which is a nested dict
def hostvars():
return collections.defaultdict(hostvars)
# Mock Ansible groups variable, which is a dict of lists.
def groups():
return collections.defaultdict(list)
def validate_json_j2(root, filename):
env = jinja2.Environment( # nosec: not used to render HTML
loader=jinja2.FileSystemLoader(root))
env.filters['bool'] = bool_filter
env.filters['basename'] = basename_filter
env.filters['kolla_address'] = kolla_address_filter_mock
env.filters['put_address_in_context'] = \
put_address_in_context
template = env.get_template(filename)
# Mock ansible variables.
context = {
'hostvars': hostvars(),
'groups': groups(),
'inventory_hostname': 'hostname',
'api_interface_address': '',
'kolla_internal_fqdn': '',
'octavia_provider_drivers': '',
'ovn_sb_db_relay_active_inactivity_probe': 120000,
'ovn_sb_db_relay_passive_inactivity_probe': 60000,
'ovn_sb_db_relay_max_backoff': 60000,
'rabbitmq_ha_replica_count': 2,
'rabbitmq_message_ttl_ms': 600000,
'rabbitmq_queue_expiry_ms': 3600000,
}
data = template.render(**context)
json.loads(data)
for root, dirs, files in os.walk(PROJECT_ROOT):
dirs[:] = [d for d in dirs if not re.match(excludes, d)]
for filename in files:
if not re.match(excludes, filename) and \
re.match(includes, filename):
fullpath = os.path.join(root, filename)
try:
validate_json_j2(root, filename)
except (ValueError, jinja2.exceptions.TemplateError):
return_code = 1
LOG.exception('%s file error', fullpath)
return return_code
def check_task_contents():
"""All tasks that use Docker should have 'become: true'."""
includes = r'|'.join([fnmatch.translate(x)
for x in YAML_INCLUDE_PATTERNS])
excludes = r'|'.join([fnmatch.translate(x)
for x in YAML_EXCLUDE_PATTERNS])
roles_path = os.path.join(PROJECT_ROOT, 'ansible', 'roles')
return_code = 0
for root, dirs, files in os.walk(roles_path):
dirs[:] = [d for d in dirs if not re.match(excludes, d)]
for filename in files:
if not re.match(excludes, filename) and \
re.match(includes, filename):
fullpath = os.path.join(root, filename)
with open(fullpath) as fp:
tasks = yaml.safe_load(fp)
tasks = tasks or []
for task in tasks:
if task.get('block'):
block = task
for task in task['block']:
if check_container_become(fullpath, task, block):
return_code = 1
else:
if check_container_become(fullpath, task):
return_code = 1
return return_code
def check_container_become(fullpath, task, block=''):
ce_modules = ('kolla_container', 'kolla_container_facts', 'kolla_toolbox')
cmd_modules = ('command', 'shell')
return_code = 0
for module in ce_modules:
if (module in task and not task.get('become') and
not block.get('become')):
return_code = 1
LOG.error("Use of %s module without become in "
"task %s in %s",
module, task['name'], fullpath)
for module in cmd_modules:
ce_without_become = False
if (module in task and not task.get('become')):
if (isinstance(task[module], str) and
(task[module].startswith('docker') or
task[module].startswith('podman')) and
not block.get('become')):
ce_without_become = True
if (isinstance(task[module], dict) and
(task[module]['cmd'].startswith('docker') or
task[module]['cmd'].startswith('podman')) and
not block.get('become')):
ce_without_become = True
if ce_without_become:
return_code = 1
LOG.error("Use of container engine in %s "
"module without "
"become in task %s in %s block %s",
module, task['name'], fullpath, block)
return return_code
def main():
checks = (
check_newline_eof,
check_json_j2,
check_task_contents,
)
return sum([check() for check in checks])
if __name__ == "__main__":
sys.exit(main())
-1
View File
@@ -156,5 +156,4 @@ commands = j2lint ansible/ roles/ tests/ --ignore single-statement-per-line
setenv = {[testenv:linters]setenv}
deps = {[testenv:linters]deps}
commands =
python {toxinidir}/tools/validate-all-file.py
ansible-lint -p