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:
1 parent
7d89d6afbe
commit
c8ea844b2b
38 files changed
+645
-314
No files matched your search
+6
-2
@@ -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)
|
||||
)
|
||||
)
|
||||
@@ -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
|
||||
@@ -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 }}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,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 }}"
|
||||
|
||||
@@ -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 }}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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,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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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())
|
||||
Reference in new issue
Block a user