From c16cd7d002e6488ca0ae8870afe196cacb46da62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 19 Jan 2026 11:12:48 +0000 Subject: [PATCH 01/42] Bump ansible/ansible-lint from 25.11.0 to 26.1.1 Bumps [ansible/ansible-lint](https://github.com/ansible/ansible-lint) from 25.11.0 to 26.1.1. - [Release notes](https://github.com/ansible/ansible-lint/releases) - [Commits](https://github.com/ansible/ansible-lint/compare/43e758bad47344f1ce7b699c0020299f486a8026...7f6abc5ef97d0fb043a0f3d416dfbc74399fbda0) --- updated-dependencies: - dependency-name: ansible/ansible-lint dependency-version: 26.1.1 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ansible-lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ansible-lint.yml b/.github/workflows/ansible-lint.yml index 43908d7..1512b28 100644 --- a/.github/workflows/ansible-lint.yml +++ b/.github/workflows/ansible-lint.yml @@ -15,6 +15,6 @@ jobs: persist-credentials: false - name: Lint Ansible Playbook - uses: ansible/ansible-lint@43e758bad47344f1ce7b699c0020299f486a8026 + uses: ansible/ansible-lint@7f6abc5ef97d0fb043a0f3d416dfbc74399fbda0 with: setup_python: "true" From e2a76b575b918ccd13edde17b4f81f12eae7d5e5 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Tue, 3 Feb 2026 10:20:40 +0100 Subject: [PATCH 02/42] Consider '' as a non-set value As reported by Cbutler, this makes little sense. So let's just consider empty strings as non-set values. --- plugins/module_utils/load_secrets_common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/module_utils/load_secrets_common.py b/plugins/module_utils/load_secrets_common.py index f8ebe5d..9186736 100644 --- a/plugins/module_utils/load_secrets_common.py +++ b/plugins/module_utils/load_secrets_common.py @@ -312,13 +312,13 @@ def _validate_generate_mode(self, f): path = self._get_field_path(f) vault_policy = f.get("vaultPolicy", None) - if value is not None: + if value is not None and value != "": return ( False, "Secret has onMissingValue set to 'generate' but has a value set", ) - if path is not None: + if path is not None and path != "": return ( False, "Secret has onMissingValue set to 'generate' but has a path set", From c70e9ce7ea95dd72c6d839a6e438905b3c22ea9e Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Tue, 3 Feb 2026 19:12:38 +0100 Subject: [PATCH 03/42] Add initial readable callback --- plugins/callback/readable.py | 261 ++++++++++++++++++ roles/helm_check/tasks/main.yaml | 20 +- roles/pattern_install_template/tasks/main.yml | 27 +- 3 files changed, 283 insertions(+), 25 deletions(-) create mode 100644 plugins/callback/readable.py diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py new file mode 100644 index 0000000..4bcedcb --- /dev/null +++ b/plugins/callback/readable.py @@ -0,0 +1,261 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2023, Al Bowles <@akatch> +# Copyright (c) 2012-2014, Michael DeHaan +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import annotations + +DOCUMENTATION = r""" +name: readable +type: stdout +author: Al Bowles (@akatch), tweaked by Michele Baldessari +short_description: condensed Ansible output specific to Validated Patterns +description: + - Consolidated Ansible output in the style of LINUX/UNIX startup logs. +extends_documentation_fragment: + - default_callback +requirements: + - set as stdout in configuration +""" + +from os.path import basename +from ansible import constants as C +from ansible import context +from ansible.module_utils.common.text.converters import to_text +from ansible.utils.color import colorize, hostcolor +from ansible.plugins.callback.default import CallbackModule as CallbackModule_default + + +class CallbackModule(CallbackModule_default): + """ + Design goals: + - Print consolidated output that looks like a *NIX startup log + - Defaults should avoid displaying unnecessary information wherever possible + """ + + CALLBACK_VERSION = 1.0 + CALLBACK_TYPE = "stdout" + CALLBACK_NAME = "rhvp.cluster_utils.readable" + + def _run_is_verbose(self, result): + return ( + self._display.verbosity > 0 or "_ansible_verbose_always" in result._result + ) and "_ansible_verbose_override" not in result._result + + def _get_task_display_name(self, task): + """Return task display name, or None for include tasks.""" + name = task.get_name().strip().split(" : ")[-1] + return None if name.startswith("include") else name + + def _preprocess_result(self, result): + self.delegated_vars = result._result.get("_ansible_delegated_vars", None) + self._handle_exception( + result._result, use_stderr=self.get_option("display_failed_stderr") + ) + self._handle_warnings(result._result) + + def _process_result_output(self, result, msg): + task_host = result._host.get_name() + task_result = f"{task_host} {msg}" + + if self._run_is_verbose(result): + task_result = ( + f"{task_host} {msg}: {self._dump_results(result._result, indent=4)}" + ) + return task_result + + if self.delegated_vars: + task_delegate_host = self.delegated_vars["ansible_host"] + task_result = f"{task_host} -> {task_delegate_host} {msg}" + + if ( + result._result.get("msg") + and result._result.get("msg") != "All items completed" + ): + task_result += f" | msg: {to_text(result._result.get('msg'))}" + + if result._result.get("stdout"): + task_result += f" | stdout: {result._result.get('stdout')}" + + if result._result.get("stderr"): + task_result += f" | stderr: {result._result.get('stderr')}" + + return task_result + + def _display_task_start(self, task, suffix=""): + """Display task start message with optional suffix (e.g., 'via handler').""" + name = self._get_task_display_name(task) + if name is None: + return + check_mode = ( + " (check mode)" + if task.check_mode and self.get_option("check_mode_markers") + else "" + ) + suffix_str = f" ({suffix})" if suffix else "" + self._display.display(f"{name}{suffix_str}{check_mode}...") + + def v2_playbook_on_task_start(self, task, is_conditional): + self._display_task_start(task) + + def v2_playbook_on_handler_task_start(self, task): + self._display_task_start(task, suffix="via handler") + + def v2_playbook_on_play_start(self, play): + name = play.get_name().strip() + check_mode = play.check_mode and self.get_option("check_mode_markers") + + if name and play.hosts: + check_str = " (in check mode)" if check_mode else "" + msg = f"\n- {name}{check_str} on hosts: {','.join(play.hosts)} -" + else: + msg = "- check mode -" if check_mode else "---" + + self._display.display(msg) + + def v2_runner_on_skipped(self, result, ignore_errors=False): + if not self.get_option("display_skipped_hosts"): + return + self._preprocess_result(result) + task_result = self._process_result_output(result, "skipped") + self._display.display(f" {task_result}", C.COLOR_SKIP) + + def _build_msg_with_item(self, base_msg, result): + """Build message with optional item label.""" + item_value = self._get_item_label(result._result) + return f"{base_msg} | item: {item_value}" if item_value else base_msg + + def v2_runner_on_failed(self, result, ignore_errors=False): + self._preprocess_result(result) + msg = self._build_msg_with_item("failed", result) + task_result = self._process_result_output(result, msg) + self._display.display( + f" {task_result}", + C.COLOR_ERROR, + stderr=self.get_option("display_failed_stderr"), + ) + + def v2_runner_on_ok(self, result, msg="ok", display_color=C.COLOR_OK): + self._preprocess_result(result) + + # Handle debug tasks specially + if result._task.action in ("debug", "ansible.builtin.debug"): + debug_msg = result._result.get("msg", "") + if debug_msg: + self._display.display(debug_msg) + return + + if result._result.get("changed"): + msg = self._build_msg_with_item("done", result) + display_color = C.COLOR_CHANGED + elif not self.get_option("display_ok_hosts"): + return + + task_result = self._process_result_output(result, msg) + self._display.display(f" {task_result}", display_color) + + def v2_runner_item_on_skipped(self, result): + self.v2_runner_on_skipped(result) + + def v2_runner_item_on_failed(self, result): + self.v2_runner_on_failed(result) + + def v2_runner_item_on_ok(self, result): + self.v2_runner_on_ok(result) + + def v2_runner_on_unreachable(self, result): + self._preprocess_result(result) + task_result = self._process_result_output(result, "unreachable") + self._display.display( + f" {task_result}", + C.COLOR_UNREACHABLE, + stderr=self.get_option("display_failed_stderr"), + ) + + def _display_diff(self, res): + """Display diff if present and changed.""" + if res.get("diff") and res.get("changed", False): + diff = self._get_diff(res["diff"]) + if diff: + self._display.display(diff) + + def v2_on_file_diff(self, result): + if result._task.loop and "results" in result._result: + for res in result._result["results"]: + self._display_diff(res) + else: + self._display_diff(result._result) + + def _format_stats_line(self, host, stats, use_color=True): + """Format a single host's stats line.""" + color = lambda name, val, c: colorize(name, val, c if use_color else None) + return ( + f" {hostcolor(host, stats, use_color)} : " + f"{color('ok', stats['ok'], C.COLOR_OK)} " + f"{color('changed', stats['changed'], C.COLOR_CHANGED)} " + f"{color('unreachable', stats['unreachable'], C.COLOR_UNREACHABLE)} " + f"{color('failed', stats['failures'], C.COLOR_ERROR)} " + f"{color('rescued', stats['rescued'], C.COLOR_OK)} " + f"{color('ignored', stats['ignored'], C.COLOR_WARN)}" + ) + + def v2_playbook_on_stats(self, stats): + self._display.display("\n- Play recap -", screen_only=True) + + for host in sorted(stats.processed.keys()): + t = stats.summarize(host) + self._display.display( + self._format_stats_line(host, t, use_color=True), screen_only=True + ) + self._display.display( + self._format_stats_line(host, t, use_color=False), log_only=True + ) + + if not (stats.custom and self.get_option("show_custom_stats")): + return + + self._display.banner("CUSTOM STATS: ") + for k in sorted(stats.custom.keys()): + if k == "_run": + continue + stat_val = self._dump_results(stats.custom[k], indent=1).replace("\n", "") + self._display.display(f"\t{k}: {stat_val}") + + if "_run" in stats.custom: + self._display.display("", screen_only=True) + stat_val_run = self._dump_results(stats.custom["_run"], indent=1).replace( + "\n", "" + ) + self._display.display(f"\tRUN: {stat_val_run}") + self._display.display("", screen_only=True) + + def v2_playbook_on_no_hosts_matched(self): + self._display.display(" No hosts found!", color=C.COLOR_DEBUG) + + def v2_playbook_on_no_hosts_remaining(self): + self._display.display(" Ran out of hosts!", color=C.COLOR_ERROR) + + def v2_playbook_on_start(self, playbook): + check_mode = context.CLIARGS["check"] and self.get_option("check_mode_markers") + check_str = " in check mode" if check_mode else "" + self._display.display( + f"Executing playbook {basename(playbook._file_name)}{check_str}" + ) + + if self._display.verbosity > 3: + if context.CLIARGS.get("args"): + self._display.display( + f"Positional arguments: {' '.join(context.CLIARGS['args'])}", + color=C.COLOR_VERBOSE, + screen_only=True, + ) + for argument, val in context.CLIARGS.items(): + if argument != "args" and val: + self._display.vvvv(f"{argument}: {val}") + + def v2_runner_retry(self, result): + msg = f" Retrying... ({result._result['attempts']} of {result._result['retries']})" + if self._run_is_verbose(result): + msg += f"Result was: {self._dump_results(result._result)}" + self._display.display(msg, color=C.COLOR_DEBUG) diff --git a/roles/helm_check/tasks/main.yaml b/roles/helm_check/tasks/main.yaml index 35e8542..84ecd25 100644 --- a/roles/helm_check/tasks/main.yaml +++ b/roles/helm_check/tasks/main.yaml @@ -1,22 +1,18 @@ --- -- name: Announce helm check - ansible.builtin.shell: | - printf "==> Checking Helm availability... " > /dev/tty - -- name: Probe helm version +- name: Check Helm availability ansible.builtin.command: helm version --short register: _helm_probe failed_when: false - name: Report Helm version to TTY (if installed) - ansible.builtin.shell: | - printf "OK (%s)\n" "{{ _helm_probe.stdout }}" > /dev/tty + ansible.builtin.debug: + msg: "OK (%s) {{ _helm_probe.stdout }}" when: _helm_probe.rc == 0 - name: Print Helm missing/error message and exit (if not installed) - ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " Helm is not installed or not on PATH.\n" > /dev/tty - printf " Hint: Install Helm 3 and ensure 'helm' is resolvable.\n" > /dev/tty - exit 1 + ansible.builtin.fail: + msg: | + ERROR: + Helm is not installed or not on PATH. + Hint: Install Helm 3 and ensure 'helm' is resolvable. when: _helm_probe.rc != 0 diff --git a/roles/pattern_install_template/tasks/main.yml b/roles/pattern_install_template/tasks/main.yml index 09c3012..ba8e59d 100644 --- a/roles/pattern_install_template/tasks/main.yml +++ b/roles/pattern_install_template/tasks/main.yml @@ -3,10 +3,11 @@ ansible.builtin.include_role: name: helm_check -- name: Print helm template command to /dev/tty - ansible.builtin.shell: | - printf "==> Running: helm template %s --name-template %s %s\n" \ - "{{ pattern_install_chart }}" "{{ pattern_name }}" "{{ _install_helm_opts }}" > /dev/tty +- name: Print helm template command + ansible.builtin.debug: + msg: | + ==> Running: helm template {{ pattern_install_chart }} --name-template + {{ pattern_name }} {{ _install_helm_opts }}" - name: Run helm template ansible.builtin.command: > @@ -19,15 +20,15 @@ failed_when: false - name: Fail if helm template failed - ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " Helm template failed in: %s\n" "{{ pattern_dir }}" > /dev/tty - printf " Chart: %s\n" "{{ pattern_install_chart }}" > /dev/tty - printf " Name: %s\n" "{{ pattern_name }}" > /dev/tty - printf " Exit code: %s\n" "{{ _helm_template.rc }}" > /dev/tty - printf " Command output:\n" > /dev/tty - printf "%s\n" "{{ _helm_template.stderr | default(_helm_template.stdout) }}" > /dev/tty - exit 1 + ansible.builtin.fail: + msg: | + ERROR: + Helm template failed in: {{ pattern_dir }} + Chart: {{ pattern_install_chart }} + Name: {{ pattern_name }} + Exit code: {{ _helm_template.rc }} + Command output: + {{ _helm_template.stderr | default(_helm_template.stdout) }} when: _helm_template.rc != 0 - name: Set rendered YAML fact From e4a86742f7cd129a42c9204d66ca428c06a7029e Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Tue, 3 Feb 2026 19:23:51 +0100 Subject: [PATCH 04/42] Drop all /dev/tty uses --- playbooks/install.yml | 4 +- playbooks/load_secrets.yml | 4 +- playbooks/operator_deploy.yml | 10 ++--- playbooks/show.yml | 2 +- playbooks/tasks/check_argo_health.yml | 10 ++--- roles/argo_healthcheck/tasks/main.yml | 14 +++--- roles/install_settings/tasks/main.yml | 10 ++--- .../tasks/resolve_overrides.yml | 10 ++--- roles/load_secrets/tasks/main.yml | 8 ++-- roles/oc_check/tasks/main.yml | 10 ++--- roles/pattern_settings/tasks/main.yml | 12 ++--- roles/validate_cluster/tasks/main.yml | 14 +++--- roles/validate_origin/tasks/main.yml | 18 ++++---- roles/validate_prereq/tasks/main.yml | 44 +++++++++---------- roles/validate_schema/tasks/main.yml | 14 +++--- 15 files changed, 89 insertions(+), 95 deletions(-) diff --git a/playbooks/install.yml b/playbooks/install.yml index d793a28..b36e053 100644 --- a/playbooks/install.yml +++ b/playbooks/install.yml @@ -17,8 +17,8 @@ tasks: - name: Print start message - ansible.builtin.shell: | - printf "==> Waiting for all argo applications to be healthy/synced.\n" > /dev/tty + ansible.builtin.debug: + msg: "==> Waiting for all argo applications to be healthy/synced.\n" - name: Ensure oc is installed ansible.builtin.include_role: diff --git a/playbooks/load_secrets.yml b/playbooks/load_secrets.yml index 49b2670..b448671 100644 --- a/playbooks/load_secrets.yml +++ b/playbooks/load_secrets.yml @@ -16,7 +16,7 @@ block: - name: Announce secrets loading ansible.builtin.shell: | - printf "==> Loading secrets (this may take several minutes)...\n" > /dev/tty + printf "==> Loading secrets (this may take several minutes)...\n" - name: Process secrets via role ansible.builtin.include_role: @@ -24,5 +24,5 @@ - name: Print secret loading disabled message ansible.builtin.shell: | - printf "==> Secrets loading is currently disabled. To enable, update the value of .global.secretLoader.disabled in your values-global.yaml to false.\n" > /dev/tty + printf "==> Secrets loading is currently disabled. To enable, update the value of .global.secretLoader.disabled in your values-global.yaml to false.\n" when: secret_loader_disabled diff --git a/playbooks/operator_deploy.yml b/playbooks/operator_deploy.yml index adc7718..b61488b 100644 --- a/playbooks/operator_deploy.yml +++ b/playbooks/operator_deploy.yml @@ -34,8 +34,8 @@ block: - name: Preview manifest that will be applied ansible.builtin.shell: | - printf "==> Applying the following manifest to the cluster:\n\n" > /dev/tty - printf "%s\n" "{{ pattern_install_rendered_yaml }}" > /dev/tty + printf "==> Applying the following manifest to the cluster:\n\n" + printf "%s\n" "{{ pattern_install_rendered_yaml }}" - name: Apply via oc with retry ansible.builtin.command: oc apply -f - @@ -48,11 +48,11 @@ until: _apply.rc == 0 - name: Print success message - ansible.builtin.shell: printf "==> Installation succeeded!\n" > /dev/tty + ansible.builtin.shell: printf "==> Installation succeeded!\n" rescue: - name: Print failure summary and abort ansible.builtin.shell: | - printf "==> Installation failed. Error:\n" > /dev/tty - printf "%s\n" "{{ _apply.stderr | default(_apply.stdout) | default('') }}" > /dev/tty + printf "==> Installation failed. Error:\n" + printf "%s\n" "{{ _apply.stderr | default(_apply.stdout) | default('') }}" exit 1 diff --git a/playbooks/show.yml b/playbooks/show.yml index 8315b5d..0db26be 100644 --- a/playbooks/show.yml +++ b/playbooks/show.yml @@ -11,4 +11,4 @@ tasks: - name: Print rendered pattern-install chart manifests ansible.builtin.shell: | - printf "\n%s\n" "{{ pattern_install_rendered_yaml }}" > /dev/tty + printf "\n%s\n" "{{ pattern_install_rendered_yaml }}" diff --git a/playbooks/tasks/check_argo_health.yml b/playbooks/tasks/check_argo_health.yml index f4ada55..f4d1817 100644 --- a/playbooks/tasks/check_argo_health.yml +++ b/playbooks/tasks/check_argo_health.yml @@ -40,15 +40,15 @@ ansible.builtin.set_fact: unhealthy_apps: "{{ apps_summary | default([]) | selectattr('bad') | list }}" -- name: Print unhealthy/unsynced applications to /dev/tty +- name: Print unhealthy/unsynced applications when: unhealthy_apps | length > 0 ansible.builtin.shell: cmd: | - printf "==> Unhealthy or unsynced applications:\n" > /dev/tty + printf "==> Unhealthy or unsynced applications:\n" {% for app in unhealthy_apps %} - printf " {{ app.namespace }}/{{ app.name }} -> Sync: {{ app.sync }} - Health: {{ app.health }}\n" > /dev/tty + printf " {{ app.namespace }}/{{ app.name }} -> Sync: {{ app.sync }} - Health: {{ app.health }}\n" {% endfor %} - printf "==> Retrying in 60 seconds...\n" > /dev/tty + printf "==> Retrying in 60 seconds...\n" - name: Fail if any applications are not healthy/synced when: unhealthy_apps | length > 0 @@ -59,4 +59,4 @@ when: unhealthy_apps | length == 0 ansible.builtin.shell: cmd: | - printf "==> All {{ apps_summary | length }} Argo applications are healthy and synced.\n" > /dev/tty + printf "==> All {{ apps_summary | length }} Argo applications are healthy and synced.\n" diff --git a/roles/argo_healthcheck/tasks/main.yml b/roles/argo_healthcheck/tasks/main.yml index fe9abb3..f7c56b8 100644 --- a/roles/argo_healthcheck/tasks/main.yml +++ b/roles/argo_healthcheck/tasks/main.yml @@ -1,8 +1,7 @@ --- - name: Print start message - ansible.builtin.shell: - cmd: | - printf "==> Checking argo applications\n" > /dev/tty + ansible.builtin.debug: + msg: "==> Checking argo applications" - name: Get all Argo CD applications as JSON ansible.builtin.command: oc get applications.argoproj.io -A -o json @@ -38,7 +37,7 @@ - name: Print status lines ansible.builtin.shell: cmd: | - printf " {{ item.namespace }} {{ item.name }} -> Sync: {{ item.sync }} - Health: {{ item.health }}\n" > /dev/tty + printf " {{ item.namespace }} {{ item.name }} -> Sync: {{ item.sync }} - Health: {{ item.health }}\n" loop: "{{ apps_summary | default([]) }}" loop_control: label: "{{ item.namespace }}:{{ item.name }}" @@ -49,7 +48,6 @@ - name: Fail if any app is not healthy/synced when: any_bad | bool - ansible.builtin.shell: - cmd: | - printf "Some applications are not synced or are unhealthy\n" > /dev/tty - exit 1 + ansible.builtin.fail: + msg: + Some applications are not synced or are unhealthy diff --git a/roles/install_settings/tasks/main.yml b/roles/install_settings/tasks/main.yml index 5184989..380946c 100644 --- a/roles/install_settings/tasks/main.yml +++ b/roles/install_settings/tasks/main.yml @@ -4,9 +4,9 @@ - name: Validate target_origin is usable ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " No Git remote found for branch '%s' in '%s'.\n" "{{ target_branch }}" "{{ pattern_dir }}" > /dev/tty - printf " Set TARGET_ORIGIN/TARGET_BRANCH or configure a remote for the branch.\n" > /dev/tty + printf "ERROR\n" + printf " No Git remote found for branch '%s' in '%s'.\n" "{{ target_branch }}" "{{ pattern_dir }}" + printf " Set TARGET_ORIGIN/TARGET_BRANCH or configure a remote for the branch.\n" exit 1 when: (target_origin | default("") | trim) == "" @@ -19,8 +19,8 @@ - name: Fail if remote URL cannot be determined ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " Could not resolve URL for remote '%s' in '%s'.\n" "{{ target_origin }}" "{{ pattern_dir }}" > /dev/tty + printf "ERROR\n" + printf " Could not resolve URL for remote '%s' in '%s'.\n" "{{ target_origin }}" "{{ pattern_dir }}" exit 1 when: (_repo_raw.rc != 0) or ((_repo_raw.stdout | default("") | trim) == "") diff --git a/roles/install_settings/tasks/resolve_overrides.yml b/roles/install_settings/tasks/resolve_overrides.yml index 8fb3271..9d5f02d 100644 --- a/roles/install_settings/tasks/resolve_overrides.yml +++ b/roles/install_settings/tasks/resolve_overrides.yml @@ -28,8 +28,8 @@ - name: Fail if unable to determine target_branch ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " Could not determine target branch in '%s'.\n" "{{ pattern_dir }}" > /dev/tty + printf "ERROR\n" + printf " Could not determine target branch in '%s'.\n" "{{ pattern_dir }}" exit 1 when: (target_branch | trim == "") and (_br.rc != 0 or (_br.stdout | default('') | trim == "")) @@ -59,9 +59,9 @@ - name: Fail if unable to determine target_origin ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " Could not determine target_origin for branch '%s' in '%s'.\n" "{{ target_branch }}" "{{ pattern_dir }}" > /dev/tty - printf " Ensure the branch has a remote configured or pass TARGET_ORIGIN explicitly.\n" > /dev/tty + printf "ERROR\n" + printf " Could not determine target_origin for branch '%s' in '%s'.\n" "{{ target_branch }}" "{{ pattern_dir }}" + printf " Ensure the branch has a remote configured or pass TARGET_ORIGIN explicitly.\n" exit 1 when: (target_origin | trim == "") and (_origin.rc != 0 or (_origin.stdout | default('') | trim == "")) diff --git a/roles/load_secrets/tasks/main.yml b/roles/load_secrets/tasks/main.yml index 33e31bc..9198d67 100644 --- a/roles/load_secrets/tasks/main.yml +++ b/roles/load_secrets/tasks/main.yml @@ -12,10 +12,10 @@ - name: Fail if values_secrets_data is missing ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " values_secrets_data was not found.\n" > /dev/tty - printf " The find_vp_secrets role should set it.\n" > /dev/tty - printf " Ensure your values/secret files are present and readable.\n" > /dev/tty + printf "ERROR\n" + printf " values_secrets_data was not found.\n" + printf " The find_vp_secrets role should set it.\n" + printf " Ensure your values/secret files are present and readable.\n" exit 1 when: values_secrets_data is not defined diff --git a/roles/oc_check/tasks/main.yml b/roles/oc_check/tasks/main.yml index 9cd275e..15fb4a3 100644 --- a/roles/oc_check/tasks/main.yml +++ b/roles/oc_check/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: Announce oc check ansible.builtin.shell: | - printf "==> Checking oc availability... " > /dev/tty + printf "==> Checking oc availability... " - name: Probe oc version (YAML output) ansible.builtin.command: oc version --client=true -o yaml @@ -15,13 +15,13 @@ - name: Report oc version to TTY (if installed) ansible.builtin.shell: | - printf "OK (%s)\n" "{{ _oc_version.releaseClientVersion | default('unknown') }}" > /dev/tty + printf "OK (%s)\n" "{{ _oc_version.releaseClientVersion | default('unknown') }}" when: _oc_probe.rc == 0 - name: Print oc missing/error message and exit (if not installed) ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " oc is not installed or not on PATH.\n" > /dev/tty - printf " Hint: Install oc and ensure 'oc' is resolvable.\n" > /dev/tty + printf "ERROR\n" + printf " oc is not installed or not on PATH.\n" + printf " Hint: Install oc and ensure 'oc' is resolvable.\n" exit 1 when: _oc_probe.rc != 0 diff --git a/roles/pattern_settings/tasks/main.yml b/roles/pattern_settings/tasks/main.yml index 93bc71a..b987c71 100644 --- a/roles/pattern_settings/tasks/main.yml +++ b/roles/pattern_settings/tasks/main.yml @@ -8,9 +8,9 @@ - name: Fail if global.pattern is missing ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " values-global.yaml does not define .global.pattern.\n" > /dev/tty - printf " Please set a value for pattern under the 'global' key.\n" > /dev/tty + printf "ERROR\n" + printf " values-global.yaml does not define .global.pattern.\n" + printf " Please set a value for pattern under the 'global' key.\n" exit 1 when: (values_global.global.pattern | default('') | string | trim) == "" @@ -25,9 +25,9 @@ - name: Fail if main.clusterGroupName is missing ansible.builtin.shell: | - printf "ERROR\n" > /dev/tty - printf " values-global.yaml does not define .main.clusterGroupName.\n" > /dev/tty - printf " Please set a value for clusterGroupName under the 'main' key.\n" > /dev/tty + printf "ERROR\n" + printf " values-global.yaml does not define .main.clusterGroupName.\n" + printf " Please set a value for clusterGroupName under the 'main' key.\n" exit 1 when: (values_global.main.clusterGroupName | default('') | string | trim) == "" diff --git a/roles/validate_cluster/tasks/main.yml b/roles/validate_cluster/tasks/main.yml index fa64fb0..9af4e64 100644 --- a/roles/validate_cluster/tasks/main.yml +++ b/roles/validate_cluster/tasks/main.yml @@ -5,25 +5,25 @@ - name: Print cluster validation header ansible.builtin.shell: | - printf "==> Checking cluster:\n" > /dev/tty + printf "==> Checking cluster:" - name: Check that we are logged into a cluster ansible.builtin.shell: | - printf " cluster-info: " > /dev/tty + printf " cluster-info: " if oc cluster-info >/dev/null 2>&1; then - printf "OK\n" > /dev/tty + printf "OK" else - printf "Error\n" > /dev/tty + printf "Error" exit 1 fi - name: Ensure we have storage classes defined ansible.builtin.shell: | set -o pipefail - printf " storageclass: " > /dev/tty + printf " storageclass: " count="$(oc get storageclass -o name 2>/dev/null | wc -l | tr -d ' ')" if [ "${count}" -eq 0 ]; then - printf "WARNING: No storageclass found\n" > /dev/tty + printf "WARNING: No storageclass found" else - printf "OK\n" > /dev/tty + printf "OK" fi diff --git a/roles/validate_origin/tasks/main.yml b/roles/validate_origin/tasks/main.yml index 0e42c0a..8989f0a 100644 --- a/roles/validate_origin/tasks/main.yml +++ b/roles/validate_origin/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: Announce repository check ansible.builtin.shell: | - printf "Checking origin reachability:\n" > /dev/tty + printf "Checking origin reachability:" - name: Set upstream_url from values-global.yaml ansible.builtin.set_fact: @@ -16,21 +16,21 @@ - name: Print upstream notice (if present) ansible.builtin.shell: | - printf "Upstream URL set to: %s\n" "{{ _upstream_url }}" > /dev/tty + printf "Upstream URL set to: %s\n" "{{ _upstream_url }}" when: _upstream_url | trim != '' - name: Fail if repo URL is empty ansible.builtin.shell: | - printf " (no repository URL available)\n" > /dev/tty - printf "ERROR\n" > /dev/tty - printf " Could not determine repository URL to validate.\n" > /dev/tty - printf " Ensure _target_repo is resolved (install_settings role) or set an upstream URL in values-global.yaml.\n" > /dev/tty + printf " (no repository URL available)\n" + printf "ERROR\n" + printf " Could not determine repository URL to validate.\n" + printf " Ensure _target_repo is resolved (install_settings role) or set an upstream URL in values-global.yaml.\n" exit 1 when: _repo_to_check == '' - name: Print URL/branch header ansible.builtin.shell: | - printf " %s - branch '%s': " "{{ _repo_to_check }}" "{{ target_branch }}" > /dev/tty + printf " %s - branch '%s': " "{{ _repo_to_check }}" "{{ target_branch }}" - name: Validate remote branch exists # noqa: command-instead-of-module ansible.builtin.command: > @@ -40,11 +40,11 @@ - name: Report OK ansible.builtin.shell: | - printf "OK\n" > /dev/tty + printf "OK" when: _lsremote.rc == 0 - name: Report NOT FOUND and exit ansible.builtin.shell: | - printf "NOT FOUND\n" > /dev/tty + printf "NOT FOUND" exit 1 when: _lsremote.rc != 0 diff --git a/roles/validate_prereq/tasks/main.yml b/roles/validate_prereq/tasks/main.yml index a8ef04a..5da408b 100644 --- a/roles/validate_prereq/tasks/main.yml +++ b/roles/validate_prereq/tasks/main.yml @@ -1,14 +1,10 @@ --- - name: Announce prerequisite checks (host) - ansible.builtin.shell: | - printf "==> Checking prerequisites...\n" > /dev/tty + ansible.builtin.debug: + msg: "==> Checking prerequisites..." - name: Pattern name and clustergroup name length validation block: - - name: Print start message - ansible.builtin.shell: | - printf " Ensuring pattern and clustergroup names are within bounds... " > /dev/tty - - name: Get length of pattern name ansible.builtin.set_fact: _pattern_name_len: "{{ pattern_name | length }}" @@ -24,20 +20,20 @@ - name: Print success message ansible.builtin.shell: | - printf "OK\n" > /dev/tty + printf "OK" when: _name_valid - name: Print failure message - ansible.builtin.shell: | - {% raw %}printf "FAIL\n\n" > /dev/tty - printf " Validation Explanation:\n" > /dev/tty - printf " A DNS-compatible name is constructed in the 'clustergroup' Helm chart using the following pattern:\n" > /dev/tty - printf " -> {{ .Values.clusterGroup.name }}-gitops-server-{{ .Values.global.pattern }}-{{ .Values.clusterGroup.name }}\n\n" > /dev/tty - printf " The total length is calculated as:\n" > /dev/tty - printf " (2 * length of 'clusterGroup.name') + length of 'global.pattern' + 15 (for '-gitops-server-') + 1 (for the namespace separator '-')\n\n" > /dev/tty - printf " To stay under the 63-character limit, the variable part of the name must be less than 47 characters:\n" > /dev/tty - printf " (2 * length of 'clusterGroup.name') + length of 'global.pattern' < 47\n" > /dev/tty - exit 1{% endraw %} + ansible.builtin.fail: + msg: | + FAIL + Validation Explanation: + A DNS-compatible name is constructed in the 'clustergroup' Helm chart using the following pattern: + -> {{ .Values.clusterGroup.name }}-gitops-server-{{ .Values.global.pattern }}-{{ .Values.clusterGroup.name }} + The total length is calculated as:\n" + (2 * length of 'clusterGroup.name') + length of 'global.pattern' + 15 (for '-gitops-server-') + 1 (for the namespace separator '-') + To stay under the 63-character limit, the variable part of the name must be less than 47 characters: + (2 * length of 'clusterGroup.name') + length of 'global.pattern' < 47 when: not _name_valid - name: Detect whether we are running inside a container @@ -50,22 +46,22 @@ block: - name: Check python-kubernetes (host) ansible.builtin.shell: | - printf " Looking for python-kubernetes module... " > /dev/tty + printf " Looking for python-kubernetes module... " if {{ (ansible_python_interpreter | default('/usr/bin/python3')) }} -c 'import kubernetes' >/dev/null 2>&1; then - printf "OK\n" > /dev/tty + printf "OK\n" else - printf "Not found\n" > /dev/tty + printf "Not found\n" exit 1 fi - name: Check kubernetes.core collection (host) ansible.builtin.shell: | set -o pipefail - printf " Looking for kubernetes.core collection... " > /dev/tty + printf " Looking for kubernetes.core collection... " if ansible-galaxy collection list | grep -q 'kubernetes.core'; then - printf "OK\n" > /dev/tty + printf "OK\n" else - printf "Not found\n" > /dev/tty + printf "Not found\n" exit 1 fi @@ -78,6 +74,6 @@ - name: Enforce multiSourceConfig is enabled (container) ansible.builtin.shell: | - printf "You must set \".main.multiSourceConfig.enabled: true\" in 'values-global.yaml'.\n" > /dev/tty + printf "You must set \".main.multiSourceConfig.enabled: true\" in 'values-global.yaml'.\n" exit 1 when: not _msc_enabled diff --git a/roles/validate_schema/tasks/main.yml b/roles/validate_schema/tasks/main.yml index 3d3e63a..5d06650 100644 --- a/roles/validate_schema/tasks/main.yml +++ b/roles/validate_schema/tasks/main.yml @@ -19,9 +19,9 @@ - name: Print start message ansible.builtin.shell: cmd: | - printf "==> Validating clustergroup schema of: " > /dev/tty - for f in {{ values_files_sorted | map('basename') | list | join(' ') }}; do printf " $f" > /dev/tty; done - printf "\n" > /dev/tty + printf "==> Validating clustergroup schema of: " + for f in {{ values_files_sorted | map('basename') | list | join(' ') }}; do printf " $f"; done + printf "" - name: Determine clustergroup chart CLI extras ansible.builtin.set_fact: @@ -47,12 +47,12 @@ when: (helm_validate.results | selectattr('rc', 'ne', 0) | list | length) > 0 ansible.builtin.shell: cmd: | - printf "Schema validation failed for the following values files:\n" > /dev/tty + printf "Schema validation failed for the following values files:" {% for result in helm_validate.results %} {% if result.rc != 0 %} - printf " - {{ result.item | basename }} (exit code: {{ result.rc }})\n" > /dev/tty - printf " To reproduce: cd {{ pattern_dir }} && helm template {{ clustergroup_chart }} {{ clustergroup_version_cli }} {{ extra_helm_opts }} -f \"{{ result.item }}\"\n" > /dev/tty + printf " - {{ result.item | basename }} (exit code: {{ result.rc }})\n" + printf " To reproduce: cd {{ pattern_dir }} && helm template {{ clustergroup_chart }} {{ clustergroup_version_cli }} {{ extra_helm_opts }} -f \"{{ result.item }}\"\n" {% endif %} {% endfor %} - printf "\nRe-run the above commands to see the detailed error output.\n" > /dev/tty + printf "\nRe-run the above commands to see the detailed error output.\n" exit 1 From 9f78d4dbc9a42d41692ebd6fabe87a93f9b0e755 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Tue, 3 Feb 2026 19:26:46 +0100 Subject: [PATCH 05/42] Drop stats --- plugins/callback/readable.py | 42 +----------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 4bcedcb..f7054e2 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -187,48 +187,8 @@ def v2_on_file_diff(self, result): else: self._display_diff(result._result) - def _format_stats_line(self, host, stats, use_color=True): - """Format a single host's stats line.""" - color = lambda name, val, c: colorize(name, val, c if use_color else None) - return ( - f" {hostcolor(host, stats, use_color)} : " - f"{color('ok', stats['ok'], C.COLOR_OK)} " - f"{color('changed', stats['changed'], C.COLOR_CHANGED)} " - f"{color('unreachable', stats['unreachable'], C.COLOR_UNREACHABLE)} " - f"{color('failed', stats['failures'], C.COLOR_ERROR)} " - f"{color('rescued', stats['rescued'], C.COLOR_OK)} " - f"{color('ignored', stats['ignored'], C.COLOR_WARN)}" - ) - def v2_playbook_on_stats(self, stats): - self._display.display("\n- Play recap -", screen_only=True) - - for host in sorted(stats.processed.keys()): - t = stats.summarize(host) - self._display.display( - self._format_stats_line(host, t, use_color=True), screen_only=True - ) - self._display.display( - self._format_stats_line(host, t, use_color=False), log_only=True - ) - - if not (stats.custom and self.get_option("show_custom_stats")): - return - - self._display.banner("CUSTOM STATS: ") - for k in sorted(stats.custom.keys()): - if k == "_run": - continue - stat_val = self._dump_results(stats.custom[k], indent=1).replace("\n", "") - self._display.display(f"\t{k}: {stat_val}") - - if "_run" in stats.custom: - self._display.display("", screen_only=True) - stat_val_run = self._dump_results(stats.custom["_run"], indent=1).replace( - "\n", "" - ) - self._display.display(f"\tRUN: {stat_val_run}") - self._display.display("", screen_only=True) + return def v2_playbook_on_no_hosts_matched(self): self._display.display(" No hosts found!", color=C.COLOR_DEBUG) From 67113ad208c50d6716195fb512f6daacdc09463f Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Tue, 3 Feb 2026 19:28:41 +0100 Subject: [PATCH 06/42] Drop display on start --- plugins/callback/readable.py | 30 ++---------------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index f7054e2..98fa95e 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -173,19 +173,8 @@ def v2_runner_on_unreachable(self, result): stderr=self.get_option("display_failed_stderr"), ) - def _display_diff(self, res): - """Display diff if present and changed.""" - if res.get("diff") and res.get("changed", False): - diff = self._get_diff(res["diff"]) - if diff: - self._display.display(diff) - def v2_on_file_diff(self, result): - if result._task.loop and "results" in result._result: - for res in result._result["results"]: - self._display_diff(res) - else: - self._display_diff(result._result) + return def v2_playbook_on_stats(self, stats): return @@ -197,22 +186,7 @@ def v2_playbook_on_no_hosts_remaining(self): self._display.display(" Ran out of hosts!", color=C.COLOR_ERROR) def v2_playbook_on_start(self, playbook): - check_mode = context.CLIARGS["check"] and self.get_option("check_mode_markers") - check_str = " in check mode" if check_mode else "" - self._display.display( - f"Executing playbook {basename(playbook._file_name)}{check_str}" - ) - - if self._display.verbosity > 3: - if context.CLIARGS.get("args"): - self._display.display( - f"Positional arguments: {' '.join(context.CLIARGS['args'])}", - color=C.COLOR_VERBOSE, - screen_only=True, - ) - for argument, val in context.CLIARGS.items(): - if argument != "args" and val: - self._display.vvvv(f"{argument}: {val}") + return def v2_runner_retry(self, result): msg = f" Retrying... ({result._result['attempts']} of {result._result['retries']})" From dc80dd2429fc36bb294f3a91dbdb78a9eb163f22 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Tue, 3 Feb 2026 19:36:41 +0100 Subject: [PATCH 07/42] Fix warning on values-secret yaml file Avoids the following: [WARNING]: Encountered 1 template error. error 1 - object of type 'dict' has no attribute 'stat' Origin: /pattern-home/.ansible/collections/ansible_collections/rhvp/cluster_utils/roles/find_vp_secrets/tasks/main.yml:18:9 16 when: custom_env_values_secret | default('') | length > 0 17 18 - name: Set values-secret yaml file to {{ custom_file_values_secret.stat.path }} ^ column 9 --- roles/find_vp_secrets/tasks/main.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/roles/find_vp_secrets/tasks/main.yml b/roles/find_vp_secrets/tasks/main.yml index 2c031f1..9c99590 100644 --- a/roles/find_vp_secrets/tasks/main.yml +++ b/roles/find_vp_secrets/tasks/main.yml @@ -15,12 +15,13 @@ register: custom_file_values_secret when: custom_env_values_secret | default('') | length > 0 -- name: Set values-secret yaml file to {{ custom_file_values_secret.stat.path }} +- name: Set values-secret yaml file to {{ custom_file_values_secret.stat.path | default('unset') }} ansible.builtin.set_fact: found_file: "{{ custom_file_values_secret.stat.path }}" when: - - custom_env_values_secret | default('') | length > 0 + - custom_file_values_secret.stat is defined - custom_file_values_secret.stat.exists + - custom_env_values_secret | default('') | length > 0 # FIXME(bandini): Eventually around end of 2023(?) we should drop # ~/values-secret-{{ pattern_name }}.yaml and ~/values-secret.yaml From bb889c1471c3ad4c9db745c6311b29c8222dd444 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Tue, 3 Feb 2026 19:47:58 +0100 Subject: [PATCH 08/42] Avoid warning about from_yaml and dict Fixes the following warning: Determine how to load secrets... [DEPRECATION WARNING]: The from_yaml filter ignored non-string input of type 'dict'. This feature will be removed from ansible-core version 2.23. Origin: :6:1 {'version': '2.0', 'secrets': [{'name': 'config-demo', 'vaultPrefixes': ['global'], 'fields': [{'name': 'secret', [...] --- playbooks/display_secrets_info.yml | 2 +- playbooks/process_secrets.yml | 2 +- roles/load_secrets/tasks/main.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/playbooks/display_secrets_info.yml b/playbooks/display_secrets_info.yml index ca900eb..f6025b8 100644 --- a/playbooks/display_secrets_info.yml +++ b/playbooks/display_secrets_info.yml @@ -27,7 +27,7 @@ # This will allow us to determine schema version and which backend to use - name: Determine how to load secrets ansible.builtin.set_fact: - secrets_yaml: '{{ values_secrets_data | from_yaml }}' + secrets_yaml: "{{ values_secrets_data if values_secrets_data is not string else values_secrets_data | from_yaml }}" - name: Parse secrets data no_log: '{{ hide_sensitive_output }}' diff --git a/playbooks/process_secrets.yml b/playbooks/process_secrets.yml index 6379111..e20281c 100644 --- a/playbooks/process_secrets.yml +++ b/playbooks/process_secrets.yml @@ -21,7 +21,7 @@ # This will allow us to determine schema version and which backend to use - name: Determine how to load secrets ansible.builtin.set_fact: - secrets_yaml: '{{ values_secrets_data | from_yaml }}' + secrets_yaml: "{{ values_secrets_data if values_secrets_data is not string else values_secrets_data | from_yaml }}" - name: Parse secrets data no_log: '{{ hide_sensitive_output | default(true) }}' diff --git a/roles/load_secrets/tasks/main.yml b/roles/load_secrets/tasks/main.yml index 9198d67..f583177 100644 --- a/roles/load_secrets/tasks/main.yml +++ b/roles/load_secrets/tasks/main.yml @@ -18,10 +18,10 @@ printf " Ensure your values/secret files are present and readable.\n" exit 1 when: values_secrets_data is not defined - + - name: Determine how to load secrets ansible.builtin.set_fact: - secrets_yaml: "{{ values_secrets_data | from_yaml }}" + secrets_yaml: "{{ values_secrets_data if values_secrets_data is not string else values_secrets_data | from_yaml }}" - name: Parse secrets data no_log: "{{ hide_sensitive_output | default(true) }}" From a00f49718f5fb1bbad986c8a8cc5676d7ae52ff4 Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Tue, 3 Feb 2026 20:23:52 -0500 Subject: [PATCH 09/42] start cleaning up install/show messages --- playbooks/install.yml | 6 +- playbooks/operator_deploy.yml | 18 ++-- playbooks/show.yml | 10 ++- roles/helm_check/tasks/main.yaml | 20 ++--- roles/install_settings/tasks/main.yml | 82 +++++++++++-------- .../tasks/resolve_overrides.yml | 61 ++++++++------ roles/oc_check/tasks/main.yml | 31 +++---- roles/pattern_install_template/tasks/main.yml | 28 +------ roles/pattern_settings/tasks/main.yml | 26 +++--- .../tasks/resolve_overrides.yml | 1 + roles/validate_origin/tasks/main.yml | 50 ++++------- 11 files changed, 153 insertions(+), 180 deletions(-) diff --git a/playbooks/install.yml b/playbooks/install.yml index b36e053..56064ee 100644 --- a/playbooks/install.yml +++ b/playbooks/install.yml @@ -18,11 +18,7 @@ tasks: - name: Print start message ansible.builtin.debug: - msg: "==> Waiting for all argo applications to be healthy/synced.\n" - - - name: Ensure oc is installed - ansible.builtin.include_role: - name: oc_check + msg: "Waiting for all argo applications to be healthy/synced." - name: Wait for all Argo applications to be healthy and synced with retry logic ansible.builtin.include_tasks: tasks/retry_argo_healthcheck.yml diff --git a/playbooks/operator_deploy.yml b/playbooks/operator_deploy.yml index b61488b..00fc573 100644 --- a/playbooks/operator_deploy.yml +++ b/playbooks/operator_deploy.yml @@ -32,11 +32,6 @@ - name: Apply rendered pattern-install chart manifests (with retry) block: - - name: Preview manifest that will be applied - ansible.builtin.shell: | - printf "==> Applying the following manifest to the cluster:\n\n" - printf "%s\n" "{{ pattern_install_rendered_yaml }}" - - name: Apply via oc with retry ansible.builtin.command: oc apply -f - args: @@ -48,11 +43,12 @@ until: _apply.rc == 0 - name: Print success message - ansible.builtin.shell: printf "==> Installation succeeded!\n" + ansible.builtin.debug: + msg: "Installation succeeded!" rescue: - - name: Print failure summary and abort - ansible.builtin.shell: | - printf "==> Installation failed. Error:\n" - printf "%s\n" "{{ _apply.stderr | default(_apply.stdout) | default('') }}" - exit 1 + - name: Print failure summary + ansible.builtin.fail: + msg: | + Failed to install pattern after 10 retries. Error: + {{ _apply.stderr | default(_apply.stdout) | default('') }} diff --git a/playbooks/show.yml b/playbooks/show.yml index 0db26be..f24efff 100644 --- a/playbooks/show.yml +++ b/playbooks/show.yml @@ -3,12 +3,16 @@ hosts: localhost connection: local gather_facts: false + vars: + include_crds: false + roles: - role: pattern_settings # set general pattern vars - role: install_settings # set pattern-install specific vars - role: pattern_install_template # render the pattern-install helm chart tasks: - - name: Print rendered pattern-install chart manifests - ansible.builtin.shell: | - printf "\n%s\n" "{{ pattern_install_rendered_yaml }}" + - name: Print rendered pattern-install chart + ansible.builtin.debug: + msg: | + {{ pattern_install_rendered_yaml }} diff --git a/roles/helm_check/tasks/main.yaml b/roles/helm_check/tasks/main.yaml index 84ecd25..63fc82d 100644 --- a/roles/helm_check/tasks/main.yaml +++ b/roles/helm_check/tasks/main.yaml @@ -4,15 +4,15 @@ register: _helm_probe failed_when: false -- name: Report Helm version to TTY (if installed) - ansible.builtin.debug: - msg: "OK (%s) {{ _helm_probe.stdout }}" - when: _helm_probe.rc == 0 - - name: Print Helm missing/error message and exit (if not installed) - ansible.builtin.fail: + ansible.builtin.assert: + that: _helm_probe.rc == 0 + fail_msg: | + Helm is not installed or not on PATH. + Try running your command again with the './pattern.sh' script, inside the + utility-container, or locally after installing Helm 3 and verifying it is available on PATH. + +- name: Report Helm version + ansible.builtin.debug: msg: | - ERROR: - Helm is not installed or not on PATH. - Hint: Install Helm 3 and ensure 'helm' is resolvable. - when: _helm_probe.rc != 0 + {{ _helm_probe.stdout | trim }} diff --git a/roles/install_settings/tasks/main.yml b/roles/install_settings/tasks/main.yml index 380946c..2eb932c 100644 --- a/roles/install_settings/tasks/main.yml +++ b/roles/install_settings/tasks/main.yml @@ -2,39 +2,35 @@ - name: Resolve defaults with ansible var/env var overrides ansible.builtin.import_tasks: resolve_overrides.yml -- name: Validate target_origin is usable - ansible.builtin.shell: | - printf "ERROR\n" - printf " No Git remote found for branch '%s' in '%s'.\n" "{{ target_branch }}" "{{ pattern_dir }}" - printf " Set TARGET_ORIGIN/TARGET_BRANCH or configure a remote for the branch.\n" - exit 1 - when: (target_origin | default("") | trim) == "" +- name: Resolve target_remote_url + block: + - name: Derive remote URL via git + ansible.builtin.command: "git remote get-url {{ target_origin }}" + args: + chdir: "{{ pattern_dir }}" + register: _repo_raw + failed_when: false -- name: Get remote URL for {{ target_origin }} # noqa: command-instead-of-module - ansible.builtin.command: "git remote get-url {{ target_origin }}" - args: - chdir: "{{ pattern_dir }}" - register: _repo_raw - failed_when: false + - name: Set target_remote_url fact + ansible.builtin.set_fact: + target_remote_url: "{{ _repo_raw.stdout | trim }}" + when: _repo_raw.rc == 0 and _repo_raw.stdout | trim != "" -- name: Fail if remote URL cannot be determined - ansible.builtin.shell: | - printf "ERROR\n" - printf " Could not resolve URL for remote '%s' in '%s'.\n" "{{ target_origin }}" "{{ pattern_dir }}" - exit 1 - when: (_repo_raw.rc != 0) or ((_repo_raw.stdout | default("") | trim) == "") + - name: Ensure target_remote_url is set + ansible.builtin.assert: + that: + - target_remote_url is defined + - target_remote_url != "" + fail_msg: | + Could not resolve URL for remote '{{ target_origin }}' in '{{ pattern_dir }}'. + Ensure the remote is configured correctly in git. -- name: Compute _target_repo (convert SSH→HTTPS if no token_secret) - ansible.builtin.set_fact: - _target_repo: >- - {{ - (_repo_raw.stdout | trim) - if (token_secret | default("") | trim != "") - else ( - (_repo_raw.stdout | trim) - | regex_replace('^git@([^:]+):(.+)$', 'https://\1/\2') - ) - }} + - name: Convert SSH git remote URL to HTTPS equivalent when token_secret is not given + ansible.builtin.set_fact: + target_remote_url: "{{ target_remote_url | regex_replace('^git@([^:]+):(.+)$', 'https://\\1/\\2') }}" + when: + - (token_secret | default("") | trim) == "" + - target_remote_url is search('^git@') - name: Build secret_opts ansible.builtin.set_fact: @@ -52,14 +48,34 @@ else "--set main.clusterGroupName=%s" | format(target_clustergroup) }} +- name: Build include_crds_opt + ansible.builtin.set_fact: + include_crds_opt: >- + {{ + "--include-crds" if (include_crds | default(true) | bool) + else "" + }} -- name: Assemble _install_helm_opts (string) +- name: Assemble _install_helm_opts ansible.builtin.set_fact: - _install_helm_opts: >- + install_helm_opts: >- -f values-global.yaml - --set main.git.repoURL="{{ _target_repo }}" + --set main.git.repoURL="{{ target_remote_url }}" --set main.git.revision={{ target_branch }} {{ secret_opts }} {{ clustergroup_opt }} {{ uuid_helm_opts }} {{ extra_helm_opts }} + {{ include_crds_opt }} + +- name: Assemble helm template command + ansible.builtin.set_fact: + helm_template_command: >- + helm template {{ pattern_install_chart }} + --name-template {{ pattern_name }} + {{ install_helm_opts }} + +- name: Print helm template command + ansible.builtin.debug: + msg: | + {{ helm_template_command }} diff --git a/roles/install_settings/tasks/resolve_overrides.yml b/roles/install_settings/tasks/resolve_overrides.yml index 9d5f02d..8374044 100644 --- a/roles/install_settings/tasks/resolve_overrides.yml +++ b/roles/install_settings/tasks/resolve_overrides.yml @@ -5,70 +5,74 @@ pattern_install_chart | default(lookup("env", "PATTERN_INSTALL_CHART"), true) | default("oci://quay.io/validatedpatterns/pattern-install", true) + | trim }} - name: Resolve target_branch block: - - name: Seed from CLI/env + - name: Check CLI/env for target_branch override ansible.builtin.set_fact: target_branch: >- {{ target_branch | default(lookup("env", "TARGET_BRANCH"), true) | default("", false) + | trim }} - - name: Derive via git (when unset) - ansible.builtin.command: "git rev-parse --abbrev-ref HEAD" + - name: Derive target_branch via git (when not overridden) + ansible.builtin.command: "git branch --show-current" args: chdir: "{{ pattern_dir }}" register: _br - when: target_branch | trim == "" + when: target_branch == "" failed_when: false - - name: Fail if unable to determine target_branch - ansible.builtin.shell: | - printf "ERROR\n" - printf " Could not determine target branch in '%s'.\n" "{{ pattern_dir }}" - exit 1 - when: (target_branch | trim == "") and (_br.rc != 0 or (_br.stdout | default('') | trim == "")) - - - name: Set from git + - name: Set target_branch using git ansible.builtin.set_fact: target_branch: "{{ _br.stdout | trim }}" - when: target_branch | trim == "" + when: target_branch == "" and _br.rc == 0 + + - name: Ensure target_branch is set + ansible.builtin.assert: + that: target_branch != "" + fail_msg: | + Could not determine target branch in '{{ pattern_dir }}'. + Ensure that you are on a git branch or pass explicitly via + the 'target_branch' variable or 'TARGET_BRANCH' environment variable. - name: Resolve target_origin block: - - name: Seed from CLI/env + - name: Check CLI/env for target_origin override ansible.builtin.set_fact: target_origin: >- {{ target_origin | default(lookup("env", "TARGET_ORIGIN"), true) | default("", false) + | trim }} - - name: Derive via git (when unset) # noqa: command-instead-of-module + - name: Derive target_origin via git (when not overridden) ansible.builtin.command: "git config branch.{{ target_branch }}.remote" args: chdir: "{{ pattern_dir }}" register: _origin - when: target_origin | trim == "" + when: target_origin == "" failed_when: false - - name: Fail if unable to determine target_origin - ansible.builtin.shell: | - printf "ERROR\n" - printf " Could not determine target_origin for branch '%s' in '%s'.\n" "{{ target_branch }}" "{{ pattern_dir }}" - printf " Ensure the branch has a remote configured or pass TARGET_ORIGIN explicitly.\n" - exit 1 - when: (target_origin | trim == "") and (_origin.rc != 0 or (_origin.stdout | default('') | trim == "")) - - - name: Set from git + - name: Set target_origin using git ansible.builtin.set_fact: target_origin: "{{ _origin.stdout | trim }}" - when: target_origin | trim == "" + when: target_origin == "" and _origin.rc == 0 + + - name: Ensure target_origin is set + ansible.builtin.assert: + that: target_origin != "" + fail_msg: | + Could not determine target origin for branch '{{ target_branch }}' in '{{ pattern_dir }}'. + Ensure branch '{{ target_branch }}' has a remote configured or pass explicitly via + the 'target_origin' variable or 'TARGET_ORIGIN' environment variable. - name: Resolve target_clustergroup ansible.builtin.set_fact: @@ -77,6 +81,7 @@ target_clustergroup | default(lookup("env", "TARGET_CLUSTERGROUP"), true) | default(main_clustergroup, true) + | trim }} - name: Resolve token_secret @@ -86,6 +91,7 @@ token_secret | default(lookup("env", "TOKEN_SECRET"), true) | default("", false) + | trim }} - name: Resolve token_namespace @@ -95,6 +101,7 @@ token_namespace | default(lookup("env", "TOKEN_NAMESPACE"), true) | default("", false) + | trim }} - name: Resolve extra_helm_opts @@ -104,6 +111,7 @@ extra_helm_opts | default(lookup("env", "EXTRA_HELM_OPTS"), true) | default("", false) + | trim }} - name: Resolve uuid_helm_opts (from UUID_FILE) @@ -115,6 +123,7 @@ uuid_file | default(lookup('env','UUID_FILE'), true) | default((lookup('env','HOME') | default('')) ~ '/.config/validated-patterns/pattern-uuid', true) + | trim }} - name: Stat UUID file diff --git a/roles/oc_check/tasks/main.yml b/roles/oc_check/tasks/main.yml index 15fb4a3..386888a 100644 --- a/roles/oc_check/tasks/main.yml +++ b/roles/oc_check/tasks/main.yml @@ -1,27 +1,22 @@ --- -- name: Announce oc check - ansible.builtin.shell: | - printf "==> Checking oc availability... " - -- name: Probe oc version (YAML output) +- name: Check oc availability ansible.builtin.command: oc version --client=true -o yaml register: _oc_probe failed_when: false +- name: Print oc missing/error message and exit (if not installed) + ansible.builtin.assert: + that: _oc_probe.rc == 0 + fail_msg: | + oc is not installed or not on PATH. + Try running your command again with the './pattern.sh' script, inside the + utility-container, or locally after installing oc and verifying it is available on PATH. + - name: Parse oc version output ansible.builtin.set_fact: _oc_version: "{{ _oc_probe.stdout | from_yaml }}" - when: _oc_probe.rc == 0 -- name: Report oc version to TTY (if installed) - ansible.builtin.shell: | - printf "OK (%s)\n" "{{ _oc_version.releaseClientVersion | default('unknown') }}" - when: _oc_probe.rc == 0 - -- name: Print oc missing/error message and exit (if not installed) - ansible.builtin.shell: | - printf "ERROR\n" - printf " oc is not installed or not on PATH.\n" - printf " Hint: Install oc and ensure 'oc' is resolvable.\n" - exit 1 - when: _oc_probe.rc != 0 +- name: Report oc version + ansible.builtin.debug: + msg: | + {{ _oc_version.releaseClientVersion | default('unknown') | trim }} diff --git a/roles/pattern_install_template/tasks/main.yml b/roles/pattern_install_template/tasks/main.yml index ba8e59d..6990e2a 100644 --- a/roles/pattern_install_template/tasks/main.yml +++ b/roles/pattern_install_template/tasks/main.yml @@ -3,35 +3,13 @@ ansible.builtin.include_role: name: helm_check -- name: Print helm template command - ansible.builtin.debug: - msg: | - ==> Running: helm template {{ pattern_install_chart }} --name-template - {{ pattern_name }} {{ _install_helm_opts }}" - -- name: Run helm template - ansible.builtin.command: > - helm template --include-crds {{ pattern_install_chart }} - --name-template {{ pattern_name }} - {{ _install_helm_opts }} +- name: Run helm template command + ansible.builtin.command: | + {{ helm_template_command }} args: chdir: "{{ pattern_dir }}" register: _helm_template - failed_when: false - -- name: Fail if helm template failed - ansible.builtin.fail: - msg: | - ERROR: - Helm template failed in: {{ pattern_dir }} - Chart: {{ pattern_install_chart }} - Name: {{ pattern_name }} - Exit code: {{ _helm_template.rc }} - Command output: - {{ _helm_template.stderr | default(_helm_template.stdout) }} - when: _helm_template.rc != 0 - name: Set rendered YAML fact ansible.builtin.set_fact: pattern_install_rendered_yaml: "{{ _helm_template.stdout }}" - when: _helm_template.rc == 0 diff --git a/roles/pattern_settings/tasks/main.yml b/roles/pattern_settings/tasks/main.yml index b987c71..211d51d 100644 --- a/roles/pattern_settings/tasks/main.yml +++ b/roles/pattern_settings/tasks/main.yml @@ -6,13 +6,12 @@ ansible.builtin.set_fact: values_global: "{{ lookup('file', pattern_dir + '/values-global.yaml') | from_yaml }}" -- name: Fail if global.pattern is missing - ansible.builtin.shell: | - printf "ERROR\n" - printf " values-global.yaml does not define .global.pattern.\n" - printf " Please set a value for pattern under the 'global' key.\n" - exit 1 - when: (values_global.global.pattern | default('') | string | trim) == "" +- name: Ensure .global.pattern is set + ansible.builtin.assert: + that: (values_global.global.pattern | default('') | string | trim) != "" + fail_msg: | + values-global.yaml does not define .global.pattern. + Please set a value for pattern under the 'global' key. - name: Resolve pattern_name ansible.builtin.set_fact: @@ -23,13 +22,12 @@ | default((values_global.global.pattern | string | trim), true) }} -- name: Fail if main.clusterGroupName is missing - ansible.builtin.shell: | - printf "ERROR\n" - printf " values-global.yaml does not define .main.clusterGroupName.\n" - printf " Please set a value for clusterGroupName under the 'main' key.\n" - exit 1 - when: (values_global.main.clusterGroupName | default('') | string | trim) == "" +- name: Ensure main.clusterGroupName is set + ansible.builtin.assert: + that: (values_global.main.clusterGroupName | default('') | string | trim) != "" + fail_msg: | + values-global.yaml does not define .main.clusterGroupName. + Please set a value for clusterGroupName under the 'main' key. - name: Set fact for main clustergroup ansible.builtin.set_fact: diff --git a/roles/pattern_settings/tasks/resolve_overrides.yml b/roles/pattern_settings/tasks/resolve_overrides.yml index c372e1e..fe63bf1 100644 --- a/roles/pattern_settings/tasks/resolve_overrides.yml +++ b/roles/pattern_settings/tasks/resolve_overrides.yml @@ -7,4 +7,5 @@ | default(lookup("env","PATTERN_DIR"), true) | default(lookup("env","PWD"), true) | default(lookup("pipe","pwd"), true) + | trim }} diff --git a/roles/validate_origin/tasks/main.yml b/roles/validate_origin/tasks/main.yml index 8989f0a..06873bf 100644 --- a/roles/validate_origin/tasks/main.yml +++ b/roles/validate_origin/tasks/main.yml @@ -1,50 +1,30 @@ --- -- name: Announce repository check - ansible.builtin.shell: | - printf "Checking origin reachability:" - -- name: Set upstream_url from values-global.yaml +- name: Check if upstream_url is set in values-global.yaml ansible.builtin.set_fact: _upstream_url: "{{ values_global.main.git.repoUpstreamURL | default('') | string | trim }}" -- name: Select URL to validate +- name: Select origin URL to validate ansible.builtin.set_fact: _repo_to_check: >- {{ - (_upstream_url if (_upstream_url | trim != '') else _target_repo) | trim + _upstream_url if _upstream_url + else target_remote_url }} -- name: Print upstream notice (if present) - ansible.builtin.shell: | - printf "Upstream URL set to: %s\n" "{{ _upstream_url }}" - when: _upstream_url | trim != '' - -- name: Fail if repo URL is empty - ansible.builtin.shell: | - printf " (no repository URL available)\n" - printf "ERROR\n" - printf " Could not determine repository URL to validate.\n" - printf " Ensure _target_repo is resolved (install_settings role) or set an upstream URL in values-global.yaml.\n" - exit 1 - when: _repo_to_check == '' +- name: Print validation message + ansible.builtin.debug: + msg: | + Validating that branch '{{ target_branch }}' is reachable on remote repo '{{ _repo_to_check }}' -- name: Print URL/branch header - ansible.builtin.shell: | - printf " %s - branch '%s': " "{{ _repo_to_check }}" "{{ target_branch }}" - -- name: Validate remote branch exists # noqa: command-instead-of-module +- name: Validate remote branch exists is reachable on remote repo ansible.builtin.command: > git ls-remote --exit-code --heads {{ _repo_to_check }} {{ target_branch }} register: _lsremote failed_when: false -- name: Report OK - ansible.builtin.shell: | - printf "OK" - when: _lsremote.rc == 0 - -- name: Report NOT FOUND and exit - ansible.builtin.shell: | - printf "NOT FOUND" - exit 1 - when: _lsremote.rc != 0 +- name: Print error message on failure + ansible.builtin.assert: + that: _lsremote.rc != 0 + fail_msg: | + Branch '{{ target_branch }}' is not reachable on remote repo '{{ _repo_to_check }}'. + Ensure that your branch is pushed to the origin repo. From 6c8a936bd30b82cd4936816b1299be529aae119e Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 08:43:00 +0100 Subject: [PATCH 10/42] Assert tasks output should be simpler --- plugins/callback/readable.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 98fa95e..8310537 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -9,7 +9,7 @@ DOCUMENTATION = r""" name: readable type: stdout -author: Al Bowles (@akatch), tweaked by Michele Baldessari +author: Al Bowles (@akatch), tweaked by Michele Baldessari & Drew Minnear short_description: condensed Ansible output specific to Validated Patterns description: - Consolidated Ansible output in the style of LINUX/UNIX startup logs. @@ -19,11 +19,8 @@ - set as stdout in configuration """ -from os.path import basename from ansible import constants as C -from ansible import context from ansible.module_utils.common.text.converters import to_text -from ansible.utils.color import colorize, hostcolor from ansible.plugins.callback.default import CallbackModule as CallbackModule_default @@ -140,7 +137,12 @@ def v2_runner_on_ok(self, result, msg="ok", display_color=C.COLOR_OK): self._preprocess_result(result) # Handle debug tasks specially - if result._task.action in ("debug", "ansible.builtin.debug"): + if result._task.action in ( + "debug", + "ansible.builtin.debug", + "assert", + "ansible.builtin.assert", + ): debug_msg = result._result.get("msg", "") if debug_msg: self._display.display(debug_msg) From 15ff8492618e552a64a4e812c526010241c51081 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 09:19:44 +0100 Subject: [PATCH 11/42] Do not print stdout on command and shell --- plugins/callback/readable.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 8310537..b30d36e 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -142,6 +142,10 @@ def v2_runner_on_ok(self, result, msg="ok", display_color=C.COLOR_OK): "ansible.builtin.debug", "assert", "ansible.builtin.assert", + "command", + "ansible.builtin.command", + "shell", + "ansible.builtin.shell", ): debug_msg = result._result.get("msg", "") if debug_msg: From c94ca50ec50e615a5515afcd85da4db178f2e7e9 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 09:27:09 +0100 Subject: [PATCH 12/42] Stop printing hostname and also use less newlines --- plugins/callback/readable.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index b30d36e..edc37dc 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -53,18 +53,19 @@ def _preprocess_result(self, result): self._handle_warnings(result._result) def _process_result_output(self, result, msg): - task_host = result._host.get_name() - task_result = f"{task_host} {msg}" + # task_host = f"{result._host.get_name()} " + task_host = "" + task_result = f"{task_host}{msg}" if self._run_is_verbose(result): task_result = ( - f"{task_host} {msg}: {self._dump_results(result._result, indent=4)}" + f"{task_host}{msg}: {self._dump_results(result._result, indent=4)}" ) return task_result if self.delegated_vars: task_delegate_host = self.delegated_vars["ansible_host"] - task_result = f"{task_host} -> {task_delegate_host} {msg}" + task_result = f"{task_host}-> {task_delegate_host} {msg}" if ( result._result.get("msg") @@ -91,7 +92,7 @@ def _display_task_start(self, task, suffix=""): else "" ) suffix_str = f" ({suffix})" if suffix else "" - self._display.display(f"{name}{suffix_str}{check_mode}...") + self._display.display(f"{name}{suffix_str}{check_mode}...", newline=False) def v2_playbook_on_task_start(self, task, is_conditional): self._display_task_start(task) From 396d5494561372442f30ec5838c4d78f76fb53c1 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 09:48:06 +0100 Subject: [PATCH 13/42] Use newline before command/shell/assert msg output and use verbose color for it --- plugins/callback/readable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index edc37dc..8826888 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -150,7 +150,7 @@ def v2_runner_on_ok(self, result, msg="ok", display_color=C.COLOR_OK): ): debug_msg = result._result.get("msg", "") if debug_msg: - self._display.display(debug_msg) + self._display.display(f"\n{debug_msg}", C.COLOR_VERBOSE) return if result._result.get("changed"): From d014c42e8f59e83b9ae0e0f031bb1ed62e992d70 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 10:11:26 +0100 Subject: [PATCH 14/42] Do not print the error when ignore_errors: true, only a warning --- plugins/callback/readable.py | 5 ++++- roles/validate_cluster/tasks/main.yml | 21 ++++++++++----------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 8826888..94c4cda 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -125,6 +125,9 @@ def _build_msg_with_item(self, base_msg, result): return f"{base_msg} | item: {item_value}" if item_value else base_msg def v2_runner_on_failed(self, result, ignore_errors=False): + if ignore_errors: + self._display.display(" error (ignored)", C.COLOR_WARN) + return self._preprocess_result(result) msg = self._build_msg_with_item("failed", result) task_result = self._process_result_output(result, msg) @@ -166,7 +169,7 @@ def v2_runner_item_on_skipped(self, result): self.v2_runner_on_skipped(result) def v2_runner_item_on_failed(self, result): - self.v2_runner_on_failed(result) + self.v2_runner_on_failed(result, ignore_errors=result._task.ignore_errors) def v2_runner_item_on_ok(self, result): self.v2_runner_on_ok(result) diff --git a/roles/validate_cluster/tasks/main.yml b/roles/validate_cluster/tasks/main.yml index 9af4e64..4e59921 100644 --- a/roles/validate_cluster/tasks/main.yml +++ b/roles/validate_cluster/tasks/main.yml @@ -3,19 +3,18 @@ ansible.builtin.include_role: name: oc_check -- name: Print cluster validation header - ansible.builtin.shell: | - printf "==> Checking cluster:" - - name: Check that we are logged into a cluster ansible.builtin.shell: | - printf " cluster-info: " - if oc cluster-info >/dev/null 2>&1; then - printf "OK" - else - printf "Error" - exit 1 - fi + oc cluster-info >/dev/null 2>&1 + register: check_oc_cluster + ignore_errors: true + +- name: Error out if we cannot get to the cluster + ansible.builtin.fail: + msg: | + We could not log into the cluster. Please check your KUBECONFIG variable + or your ~/.kube/config settings. + when: check_oc_cluster is failed - name: Ensure we have storage classes defined ansible.builtin.shell: | From eba1f1f182c3e187c2409bbf973ba72135e79bcf Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 10:14:49 +0100 Subject: [PATCH 15/42] Fix warning on disable_validate_origin This was the warning: Resolve disable_validate_origin flag...[WARNING]: Deprecation warnings can be disabled by setting `deprecation_warnings=False` in ansible.cfg. [DEPRECATION WARNING]: The `bool` filter coerced invalid value '' (str) to False. This feature will be removed from ansible-core version 2.23. --- playbooks/operator_deploy.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/playbooks/operator_deploy.yml b/playbooks/operator_deploy.yml index 00fc573..d39e0c2 100644 --- a/playbooks/operator_deploy.yml +++ b/playbooks/operator_deploy.yml @@ -18,10 +18,9 @@ ansible.builtin.set_fact: disable_validate_origin: >- {{ - ( - disable_validate_origin - | default(lookup('env', 'DISABLE_VALIDATE_ORIGIN'), true) - | default('false', false) + disable_validate_origin | default( + lookup('env', 'DISABLE_VALIDATE_ORIGIN') or 'false', + true ) | bool }} From 79d7462ce0c68ba4bbb4ab41bf6cf657ca47a341 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 10:36:29 +0100 Subject: [PATCH 16/42] Use "is failed" instead of .rc != 0 --- roles/helm_check/tasks/main.yaml | 2 +- roles/install_settings/tasks/main.yml | 2 +- roles/install_settings/tasks/resolve_overrides.yml | 2 +- roles/oc_check/tasks/main.yml | 2 +- roles/validate_origin/tasks/main.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/roles/helm_check/tasks/main.yaml b/roles/helm_check/tasks/main.yaml index 63fc82d..8342398 100644 --- a/roles/helm_check/tasks/main.yaml +++ b/roles/helm_check/tasks/main.yaml @@ -6,7 +6,7 @@ - name: Print Helm missing/error message and exit (if not installed) ansible.builtin.assert: - that: _helm_probe.rc == 0 + that: _helm_probe is not failed fail_msg: | Helm is not installed or not on PATH. Try running your command again with the './pattern.sh' script, inside the diff --git a/roles/install_settings/tasks/main.yml b/roles/install_settings/tasks/main.yml index 2eb932c..039cc8a 100644 --- a/roles/install_settings/tasks/main.yml +++ b/roles/install_settings/tasks/main.yml @@ -14,7 +14,7 @@ - name: Set target_remote_url fact ansible.builtin.set_fact: target_remote_url: "{{ _repo_raw.stdout | trim }}" - when: _repo_raw.rc == 0 and _repo_raw.stdout | trim != "" + when: _repo_raw is not failed and _repo_raw.stdout | trim != "" - name: Ensure target_remote_url is set ansible.builtin.assert: diff --git a/roles/install_settings/tasks/resolve_overrides.yml b/roles/install_settings/tasks/resolve_overrides.yml index 8374044..4fcad27 100644 --- a/roles/install_settings/tasks/resolve_overrides.yml +++ b/roles/install_settings/tasks/resolve_overrides.yml @@ -64,7 +64,7 @@ - name: Set target_origin using git ansible.builtin.set_fact: target_origin: "{{ _origin.stdout | trim }}" - when: target_origin == "" and _origin.rc == 0 + when: target_origin == "" and _origin is not failed - name: Ensure target_origin is set ansible.builtin.assert: diff --git a/roles/oc_check/tasks/main.yml b/roles/oc_check/tasks/main.yml index 386888a..7bae8c5 100644 --- a/roles/oc_check/tasks/main.yml +++ b/roles/oc_check/tasks/main.yml @@ -6,7 +6,7 @@ - name: Print oc missing/error message and exit (if not installed) ansible.builtin.assert: - that: _oc_probe.rc == 0 + that: _oc_probe is not failed fail_msg: | oc is not installed or not on PATH. Try running your command again with the './pattern.sh' script, inside the diff --git a/roles/validate_origin/tasks/main.yml b/roles/validate_origin/tasks/main.yml index 06873bf..fa0c153 100644 --- a/roles/validate_origin/tasks/main.yml +++ b/roles/validate_origin/tasks/main.yml @@ -24,7 +24,7 @@ - name: Print error message on failure ansible.builtin.assert: - that: _lsremote.rc != 0 + that: _lsremote is not failed fail_msg: | Branch '{{ target_branch }}' is not reachable on remote repo '{{ _repo_to_check }}'. Ensure that your branch is pushed to the origin repo. From 8961be7b19340bcd4b65f183ea0912d673c89e01 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 10:53:36 +0100 Subject: [PATCH 17/42] Use debug instead of shell in health_checks --- playbooks/tasks/check_argo_health.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/playbooks/tasks/check_argo_health.yml b/playbooks/tasks/check_argo_health.yml index f4d1817..332083c 100644 --- a/playbooks/tasks/check_argo_health.yml +++ b/playbooks/tasks/check_argo_health.yml @@ -42,13 +42,13 @@ - name: Print unhealthy/unsynced applications when: unhealthy_apps | length > 0 - ansible.builtin.shell: - cmd: | - printf "==> Unhealthy or unsynced applications:\n" + ansible.builtin.debug: + msg: | + ==> Unhealthy or unsynced applications: {% for app in unhealthy_apps %} - printf " {{ app.namespace }}/{{ app.name }} -> Sync: {{ app.sync }} - Health: {{ app.health }}\n" + {{ app.namespace }}/{{ app.name }} -> Sync: {{ app.sync }} - Health: {{ app.health }} {% endfor %} - printf "==> Retrying in 60 seconds...\n" + ==> Retrying in 60 seconds... - name: Fail if any applications are not healthy/synced when: unhealthy_apps | length > 0 From edf50b18d00ad018f55174ba301d3a443ff06515 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 11:18:44 +0100 Subject: [PATCH 18/42] Do not output stdout on kubernetes.core.k8s_{info,exec} --- plugins/callback/readable.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 94c4cda..04abf6c 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -150,6 +150,8 @@ def v2_runner_on_ok(self, result, msg="ok", display_color=C.COLOR_OK): "ansible.builtin.command", "shell", "ansible.builtin.shell", + "kubernetes.core.k8s_info", + "kubernetes.core.k8s_exec", ): debug_msg = result._result.get("msg", "") if debug_msg: From 52daa54476883f7bf6da4d9f978bdc25fc6a8463 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 11:26:31 +0100 Subject: [PATCH 19/42] Drop another printf --- roles/argo_healthcheck/tasks/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/roles/argo_healthcheck/tasks/main.yml b/roles/argo_healthcheck/tasks/main.yml index f7c56b8..bf36846 100644 --- a/roles/argo_healthcheck/tasks/main.yml +++ b/roles/argo_healthcheck/tasks/main.yml @@ -35,9 +35,9 @@ loop: "{{ apps_items }}" - name: Print status lines - ansible.builtin.shell: - cmd: | - printf " {{ item.namespace }} {{ item.name }} -> Sync: {{ item.sync }} - Health: {{ item.health }}\n" + ansible.builtin.debug: + msg: | + {{ item.namespace }} {{ item.name }} -> Sync: {{ item.sync }} - Health: {{ item.health }} loop: "{{ apps_summary | default([]) }}" loop_control: label: "{{ item.namespace }}:{{ item.name }}" From b4e5259c38039a94d0815bd6c6f599c31dcf40ae Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 11:33:13 +0100 Subject: [PATCH 20/42] Do not display 'ok' for each task in a loop --- plugins/callback/readable.py | 68 +++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 04abf6c..0fb8b2c 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -24,6 +24,20 @@ from ansible.plugins.callback.default import CallbackModule as CallbackModule_default +NO_STDOUT_TASKS = ( + "debug", + "ansible.builtin.debug", + "assert", + "ansible.builtin.assert", + "command", + "ansible.builtin.command", + "shell", + "ansible.builtin.shell", + "kubernetes.core.k8s_info", + "kubernetes.core.k8s_exec", +) + + class CallbackModule(CallbackModule_default): """ Design goals: @@ -35,6 +49,21 @@ class CallbackModule(CallbackModule_default): CALLBACK_TYPE = "stdout" CALLBACK_NAME = "rhvp.cluster_utils.readable" + def __init__(self): + super().__init__() + self._loop_item_count = 0 + self._loop_had_change = False + + def _finalize_loop_if_needed(self): + """If we were processing loop items, finalize with ok/done.""" + if self._loop_item_count > 0: + if self._loop_had_change: + self._display.display("done", C.COLOR_CHANGED) + else: + self._display.display("ok", C.COLOR_OK) + self._loop_item_count = 0 + self._loop_had_change = False + def _run_is_verbose(self, result): return ( self._display.verbosity > 0 or "_ansible_verbose_always" in result._result @@ -95,12 +124,15 @@ def _display_task_start(self, task, suffix=""): self._display.display(f"{name}{suffix_str}{check_mode}...", newline=False) def v2_playbook_on_task_start(self, task, is_conditional): + self._finalize_loop_if_needed() self._display_task_start(task) def v2_playbook_on_handler_task_start(self, task): + self._finalize_loop_if_needed() self._display_task_start(task, suffix="via handler") def v2_playbook_on_play_start(self, play): + self._finalize_loop_if_needed() name = play.get_name().strip() check_mode = play.check_mode and self.get_option("check_mode_markers") @@ -140,19 +172,12 @@ def v2_runner_on_failed(self, result, ignore_errors=False): def v2_runner_on_ok(self, result, msg="ok", display_color=C.COLOR_OK): self._preprocess_result(result) + # Skip aggregated loop results - items were already handled + if result._result.get("results"): + return + # Handle debug tasks specially - if result._task.action in ( - "debug", - "ansible.builtin.debug", - "assert", - "ansible.builtin.assert", - "command", - "ansible.builtin.command", - "shell", - "ansible.builtin.shell", - "kubernetes.core.k8s_info", - "kubernetes.core.k8s_exec", - ): + if result._task.action in NO_STDOUT_TASKS: debug_msg = result._result.get("msg", "") if debug_msg: self._display.display(f"\n{debug_msg}", C.COLOR_VERBOSE) @@ -171,10 +196,26 @@ def v2_runner_item_on_skipped(self, result): self.v2_runner_on_skipped(result) def v2_runner_item_on_failed(self, result): + # Reset loop state - failure message will be printed instead + self._loop_item_count = 0 + self._loop_had_change = False self.v2_runner_on_failed(result, ignore_errors=result._task.ignore_errors) def v2_runner_item_on_ok(self, result): - self.v2_runner_on_ok(result) + self._preprocess_result(result) + + # Handle debug tasks specially - print their output + if result._task.action in NO_STDOUT_TASKS: + debug_msg = result._result.get("msg", "") + if debug_msg: + self._display.display(f"\n{debug_msg}", C.COLOR_VERBOSE) + return + + if result._result.get("changed"): + self._loop_had_change = True + + self._loop_item_count += 1 + self._display.display(".", newline=False) def v2_runner_on_unreachable(self, result): self._preprocess_result(result) @@ -189,6 +230,7 @@ def v2_on_file_diff(self, result): return def v2_playbook_on_stats(self, stats): + self._finalize_loop_if_needed() return def v2_playbook_on_no_hosts_matched(self): From 159efe9cfa145db1239b688b917b4b69a1decadf Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 12:58:05 +0100 Subject: [PATCH 21/42] Introduce a "simple_failure" tag This will avoid printing failure exceptions, so that the Fail if any applications are not healthy/synced... 1 application(s) are not healthy/synced Tasks don't print the whole exception --- playbooks/tasks/check_argo_health.yml | 11 ++--- plugins/callback/readable.py | 63 +++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/playbooks/tasks/check_argo_health.yml b/playbooks/tasks/check_argo_health.yml index 332083c..7b85ef1 100644 --- a/playbooks/tasks/check_argo_health.yml +++ b/playbooks/tasks/check_argo_health.yml @@ -44,19 +44,20 @@ when: unhealthy_apps | length > 0 ansible.builtin.debug: msg: | - ==> Unhealthy or unsynced applications: + ==> Unhealthy or unsynced applications (will retry in 60 seconds): {% for app in unhealthy_apps %} {{ app.namespace }}/{{ app.name }} -> Sync: {{ app.sync }} - Health: {{ app.health }} {% endfor %} - ==> Retrying in 60 seconds... - name: Fail if any applications are not healthy/synced when: unhealthy_apps | length > 0 ansible.builtin.fail: msg: "{{ unhealthy_apps | length }} application(s) are not healthy/synced" + tags: + - simple_failure - name: Print success message when: unhealthy_apps | length == 0 - ansible.builtin.shell: - cmd: | - printf "==> All {{ apps_summary | length }} Argo applications are healthy and synced.\n" + ansible.builtin.debug: + msg: | + ==> All {{ apps_summary | length }} Argo applications are healthy and synced. diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 0fb8b2c..e5f2cdc 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -33,6 +33,8 @@ "ansible.builtin.command", "shell", "ansible.builtin.shell", + "pause", + "ansible.builtin.pause", "kubernetes.core.k8s_info", "kubernetes.core.k8s_exec", ) @@ -156,10 +158,71 @@ def _build_msg_with_item(self, base_msg, result): item_value = self._get_item_label(result._result) return f"{base_msg} | item: {item_value}" if item_value else base_msg + def _should_use_simple_failure_output(self, result): + """Determine if we should use simple failure output without full details.""" + # Use simple output if the task has 'simple_failure' tag or variable + if hasattr(result._task, 'tags') and 'simple_failure' in result._task.tags: + return True + + # Use simple output if task variables contain simple_failure: true + task_vars = getattr(result._task, 'vars', {}) + if task_vars.get('simple_failure', False): + return True + + # Use simple output for ansible.builtin.fail tasks + if result._task.action in ('fail', 'ansible.builtin.fail'): + return True + + return False + + def _handle_exception(self, result_dict, use_stderr=None): + """Override exception handling to suppress for simple failures.""" + # Check if we're in a context where we want simple output + # Note: This is called before v2_runner_on_failed, so we need to check the current task + if hasattr(self, '_current_task') and self._current_task: + # Create a mock result to check if this should use simple output + class MockResult: + def __init__(self, task): + self._task = task + self._result = result_dict + + mock_result = MockResult(self._current_task) + if self._should_use_simple_failure_output(mock_result): + return # Skip exception handling for simple failures + + # Use parent's exception handling for regular failures + super()._handle_exception(result_dict, use_stderr) + + def v2_playbook_on_task_start(self, task, is_conditional): + # Store current task for exception handling + self._current_task = task + self._finalize_loop_if_needed() + self._display_task_start(task) + + def v2_playbook_on_handler_task_start(self, task): + # Store current task for exception handling + self._current_task = task + self._finalize_loop_if_needed() + self._display_task_start(task, suffix="via handler") + def v2_runner_on_failed(self, result, ignore_errors=False): if ignore_errors: self._display.display(" error (ignored)", C.COLOR_WARN) return + + # Check if we should use simple failure output BEFORE any processing + if self._should_use_simple_failure_output(result): + # For simple output, completely bypass parent error handling + # Only handle warnings and display the clean message + if result._result.get('warnings'): + for warning in result._result['warnings']: + self._display.warning(warning) + + simple_msg = result._result.get('msg', 'Task failed') + self._display.display(f" {simple_msg}") + return + + # Use full detailed output for other failures self._preprocess_result(result) msg = self._build_msg_with_item("failed", result) task_result = self._process_result_output(result, msg) From 7c093a02c06fed466631342c8b1a7ccf8d968045 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 15:22:25 +0100 Subject: [PATCH 22/42] Drop simple_failure flag, does not work as intended --- playbooks/tasks/check_argo_health.yml | 2 - plugins/callback/readable.py | 57 ++++++--------------------- 2 files changed, 12 insertions(+), 47 deletions(-) diff --git a/playbooks/tasks/check_argo_health.yml b/playbooks/tasks/check_argo_health.yml index 7b85ef1..569fa8f 100644 --- a/playbooks/tasks/check_argo_health.yml +++ b/playbooks/tasks/check_argo_health.yml @@ -53,8 +53,6 @@ when: unhealthy_apps | length > 0 ansible.builtin.fail: msg: "{{ unhealthy_apps | length }} application(s) are not healthy/synced" - tags: - - simple_failure - name: Print success message when: unhealthy_apps | length == 0 diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index e5f2cdc..180b029 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -125,14 +125,6 @@ def _display_task_start(self, task, suffix=""): suffix_str = f" ({suffix})" if suffix else "" self._display.display(f"{name}{suffix_str}{check_mode}...", newline=False) - def v2_playbook_on_task_start(self, task, is_conditional): - self._finalize_loop_if_needed() - self._display_task_start(task) - - def v2_playbook_on_handler_task_start(self, task): - self._finalize_loop_if_needed() - self._display_task_start(task, suffix="via handler") - def v2_playbook_on_play_start(self, play): self._finalize_loop_if_needed() name = play.get_name().strip() @@ -158,39 +150,17 @@ def _build_msg_with_item(self, base_msg, result): item_value = self._get_item_label(result._result) return f"{base_msg} | item: {item_value}" if item_value else base_msg - def _should_use_simple_failure_output(self, result): - """Determine if we should use simple failure output without full details.""" - # Use simple output if the task has 'simple_failure' tag or variable - if hasattr(result._task, 'tags') and 'simple_failure' in result._task.tags: - return True - - # Use simple output if task variables contain simple_failure: true - task_vars = getattr(result._task, 'vars', {}) - if task_vars.get('simple_failure', False): - return True - - # Use simple output for ansible.builtin.fail tasks - if result._task.action in ('fail', 'ansible.builtin.fail'): - return True - - return False + def _is_fail_task(self, result): + """Check if this is a fail task that should use simple message output.""" + return result._task.action in ('fail', 'ansible.builtin.fail') def _handle_exception(self, result_dict, use_stderr=None): - """Override exception handling to suppress for simple failures.""" - # Check if we're in a context where we want simple output - # Note: This is called before v2_runner_on_failed, so we need to check the current task + """Override exception handling to suppress for fail tasks.""" + # Skip exception handling for fail tasks - we just want to show the msg if hasattr(self, '_current_task') and self._current_task: - # Create a mock result to check if this should use simple output - class MockResult: - def __init__(self, task): - self._task = task - self._result = result_dict - - mock_result = MockResult(self._current_task) - if self._should_use_simple_failure_output(mock_result): - return # Skip exception handling for simple failures + if self._current_task.action in ('fail', 'ansible.builtin.fail'): + return - # Use parent's exception handling for regular failures super()._handle_exception(result_dict, use_stderr) def v2_playbook_on_task_start(self, task, is_conditional): @@ -210,19 +180,16 @@ def v2_runner_on_failed(self, result, ignore_errors=False): self._display.display(" error (ignored)", C.COLOR_WARN) return - # Check if we should use simple failure output BEFORE any processing - if self._should_use_simple_failure_output(result): - # For simple output, completely bypass parent error handling - # Only handle warnings and display the clean message + # For fail tasks, just display the message cleanly + if self._is_fail_task(result): if result._result.get('warnings'): for warning in result._result['warnings']: self._display.warning(warning) - - simple_msg = result._result.get('msg', 'Task failed') - self._display.display(f" {simple_msg}") + msg = result._result.get('msg', 'Task failed') + self._display.display(f" {msg}") return - # Use full detailed output for other failures + # Full detailed output for other failures self._preprocess_result(result) msg = self._build_msg_with_item("failed", result) task_result = self._process_result_output(result, msg) From 90f5cc02e9707330442c730ea7021c066934a96e Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 15:32:07 +0100 Subject: [PATCH 23/42] Do not pring msg when a failed task has failed_when: false --- plugins/callback/readable.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 180b029..5b3aa7f 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -206,6 +206,12 @@ def v2_runner_on_ok(self, result, msg="ok", display_color=C.COLOR_OK): if result._result.get("results"): return + # Handle fail tasks that succeeded due to failed_when=false + if self._is_fail_task(result): + # For fail tasks that didn't actually fail, just show ok + self._display.display(" ok", C.COLOR_OK) + return + # Handle debug tasks specially if result._task.action in NO_STDOUT_TASKS: debug_msg = result._result.get("msg", "") From 8d2e6876af0b413314a7d53532693df2dc97b6a4 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 15:47:26 +0100 Subject: [PATCH 24/42] Fix linting errors --- plugins/callback/readable.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 5b3aa7f..4fb725b 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -152,16 +152,16 @@ def _build_msg_with_item(self, base_msg, result): def _is_fail_task(self, result): """Check if this is a fail task that should use simple message output.""" - return result._task.action in ('fail', 'ansible.builtin.fail') + return result._task.action in ("fail", "ansible.builtin.fail") - def _handle_exception(self, result_dict, use_stderr=None): + def _handle_exception(self, result, use_stderr=None): """Override exception handling to suppress for fail tasks.""" # Skip exception handling for fail tasks - we just want to show the msg - if hasattr(self, '_current_task') and self._current_task: - if self._current_task.action in ('fail', 'ansible.builtin.fail'): + if hasattr(self, "_current_task") and self._current_task: + if self._current_task.action in ("fail", "ansible.builtin.fail"): return - super()._handle_exception(result_dict, use_stderr) + super()._handle_exception(result, use_stderr) def v2_playbook_on_task_start(self, task, is_conditional): # Store current task for exception handling @@ -182,10 +182,10 @@ def v2_runner_on_failed(self, result, ignore_errors=False): # For fail tasks, just display the message cleanly if self._is_fail_task(result): - if result._result.get('warnings'): - for warning in result._result['warnings']: + if result._result.get("warnings"): + for warning in result._result["warnings"]: self._display.warning(warning) - msg = result._result.get('msg', 'Task failed') + msg = result._result.get("msg", "Task failed") self._display.display(f" {msg}") return From eaee1d3535bbcfecd1dca0181641cbad03f0ab8c Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 15:52:37 +0100 Subject: [PATCH 25/42] Some more ansible-lint fixes --- playbooks/display_secrets_info.yml | 2 +- playbooks/process_secrets.yml | 2 +- roles/load_secrets/tasks/main.yml | 4 ++-- roles/validate_prereq/tasks/main.yml | 15 +++++++-------- roles/vault_utils/tasks/vault_spokes_init.yaml | 2 +- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/playbooks/display_secrets_info.yml b/playbooks/display_secrets_info.yml index f6025b8..8ce8619 100644 --- a/playbooks/display_secrets_info.yml +++ b/playbooks/display_secrets_info.yml @@ -27,7 +27,7 @@ # This will allow us to determine schema version and which backend to use - name: Determine how to load secrets ansible.builtin.set_fact: - secrets_yaml: "{{ values_secrets_data if values_secrets_data is not string else values_secrets_data | from_yaml }}" + secrets_yaml: "{{ values_secrets_data if values_secrets_data is not string else values_secrets_data | from_yaml }}" - name: Parse secrets data no_log: '{{ hide_sensitive_output }}' diff --git a/playbooks/process_secrets.yml b/playbooks/process_secrets.yml index e20281c..6329dda 100644 --- a/playbooks/process_secrets.yml +++ b/playbooks/process_secrets.yml @@ -21,7 +21,7 @@ # This will allow us to determine schema version and which backend to use - name: Determine how to load secrets ansible.builtin.set_fact: - secrets_yaml: "{{ values_secrets_data if values_secrets_data is not string else values_secrets_data | from_yaml }}" + secrets_yaml: "{{ values_secrets_data if values_secrets_data is not string else values_secrets_data | from_yaml }}" - name: Parse secrets data no_log: '{{ hide_sensitive_output | default(true) }}' diff --git a/roles/load_secrets/tasks/main.yml b/roles/load_secrets/tasks/main.yml index f583177..b7a0f2f 100644 --- a/roles/load_secrets/tasks/main.yml +++ b/roles/load_secrets/tasks/main.yml @@ -18,10 +18,10 @@ printf " Ensure your values/secret files are present and readable.\n" exit 1 when: values_secrets_data is not defined - + - name: Determine how to load secrets ansible.builtin.set_fact: - secrets_yaml: "{{ values_secrets_data if values_secrets_data is not string else values_secrets_data | from_yaml }}" + secrets_yaml: "{{ values_secrets_data if values_secrets_data is not string else values_secrets_data | from_yaml }}" - name: Parse secrets data no_log: "{{ hide_sensitive_output | default(true) }}" diff --git a/roles/validate_prereq/tasks/main.yml b/roles/validate_prereq/tasks/main.yml index 5da408b..7ba73c9 100644 --- a/roles/validate_prereq/tasks/main.yml +++ b/roles/validate_prereq/tasks/main.yml @@ -26,14 +26,13 @@ - name: Print failure message ansible.builtin.fail: msg: | - FAIL - Validation Explanation: - A DNS-compatible name is constructed in the 'clustergroup' Helm chart using the following pattern: - -> {{ .Values.clusterGroup.name }}-gitops-server-{{ .Values.global.pattern }}-{{ .Values.clusterGroup.name }} - The total length is calculated as:\n" - (2 * length of 'clusterGroup.name') + length of 'global.pattern' + 15 (for '-gitops-server-') + 1 (for the namespace separator '-') - To stay under the 63-character limit, the variable part of the name must be less than 47 characters: - (2 * length of 'clusterGroup.name') + length of 'global.pattern' < 47 + FAIL - Validation Explanation: + A DNS-compatible name is constructed in the 'clustergroup' Helm chart using the following pattern: + -> {{ .Values.clusterGroup.name }}-gitops-server-{{ .Values.global.pattern }}-{{ .Values.clusterGroup.name }} + The total length is calculated as:\n" + (2 * length of 'clusterGroup.name') + length of 'global.pattern' + 15 (for '-gitops-server-') + 1 (for the namespace separator '-') + To stay under the 63-character limit, the variable part of the name must be less than 47 characters: + (2 * length of 'clusterGroup.name') + length of 'global.pattern' < 47 when: not _name_valid - name: Detect whether we are running inside a container diff --git a/roles/vault_utils/tasks/vault_spokes_init.yaml b/roles/vault_utils/tasks/vault_spokes_init.yaml index 40651dd..ae0215c 100644 --- a/roles/vault_utils/tasks/vault_spokes_init.yaml +++ b/roles/vault_utils/tasks/vault_spokes_init.yaml @@ -37,7 +37,7 @@ ansible.builtin.set_fact: clusters: "{{ clusters | default({}) | combine({item.metadata.name: {'server_api': item.spec.managedClusterClientConfigs[0].url, - 'cluster_fqdn': _cluster_fqdn }}, recursive=True) }}" + 'cluster_fqdn': _cluster_fqdn}}, recursive=True) }}" loop: "{{ resources }}" vars: _cluster_fqdn: "{{ item.status.clusterClaims | selectattr('name', 'equalto', 'consoleurl.cluster.open-cluster-management.io') From 77511e47375fbf37b8e6f6af9cb677c46527cef3 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 15:53:24 +0100 Subject: [PATCH 26/42] Fix python isort --- plugins/callback/readable.py | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 4fb725b..23078d0 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -23,7 +23,6 @@ from ansible.module_utils.common.text.converters import to_text from ansible.plugins.callback.default import CallbackModule as CallbackModule_default - NO_STDOUT_TASKS = ( "debug", "ansible.builtin.debug", From b5394a64d1d8cf70df04dad4800ac94f83f0f4ab Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 16:01:25 +0100 Subject: [PATCH 27/42] Fix dns validation message --- roles/validate_prereq/tasks/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/roles/validate_prereq/tasks/main.yml b/roles/validate_prereq/tasks/main.yml index 7ba73c9..c54a8a1 100644 --- a/roles/validate_prereq/tasks/main.yml +++ b/roles/validate_prereq/tasks/main.yml @@ -28,8 +28,8 @@ msg: | FAIL - Validation Explanation: A DNS-compatible name is constructed in the 'clustergroup' Helm chart using the following pattern: - -> {{ .Values.clusterGroup.name }}-gitops-server-{{ .Values.global.pattern }}-{{ .Values.clusterGroup.name }} - The total length is calculated as:\n" + -> {% raw %}{{ .Values.clusterGroup.name }}-gitops-server-{{ .Values.global.pattern }}-{{ .Values.clusterGroup.name }}{% endraw %} + The total length is calculated as: (2 * length of 'clusterGroup.name') + length of 'global.pattern' + 15 (for '-gitops-server-') + 1 (for the namespace separator '-') To stay under the 63-character limit, the variable part of the name must be less than 47 characters: (2 * length of 'clusterGroup.name') + length of 'global.pattern' < 47 From 4185945c1170542d8e78aab6def885f01f53d1fe Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 16:23:39 +0100 Subject: [PATCH 28/42] Ignore ansible-lint warnings on git command --- roles/install_settings/tasks/main.yml | 2 +- roles/install_settings/tasks/resolve_overrides.yml | 4 ++-- roles/validate_origin/tasks/main.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/roles/install_settings/tasks/main.yml b/roles/install_settings/tasks/main.yml index 039cc8a..b5e0e8b 100644 --- a/roles/install_settings/tasks/main.yml +++ b/roles/install_settings/tasks/main.yml @@ -5,7 +5,7 @@ - name: Resolve target_remote_url block: - name: Derive remote URL via git - ansible.builtin.command: "git remote get-url {{ target_origin }}" + ansible.builtin.command: "git remote get-url {{ target_origin }}" # noqa: command-instead-of-module args: chdir: "{{ pattern_dir }}" register: _repo_raw diff --git a/roles/install_settings/tasks/resolve_overrides.yml b/roles/install_settings/tasks/resolve_overrides.yml index 4fcad27..10c788e 100644 --- a/roles/install_settings/tasks/resolve_overrides.yml +++ b/roles/install_settings/tasks/resolve_overrides.yml @@ -21,7 +21,7 @@ }} - name: Derive target_branch via git (when not overridden) - ansible.builtin.command: "git branch --show-current" + ansible.builtin.command: "git branch --show-current" # noqa: command-instead-of-module args: chdir: "{{ pattern_dir }}" register: _br @@ -54,7 +54,7 @@ }} - name: Derive target_origin via git (when not overridden) - ansible.builtin.command: "git config branch.{{ target_branch }}.remote" + ansible.builtin.command: "git config branch.{{ target_branch }}.remote" # noqa: command-instead-of-module args: chdir: "{{ pattern_dir }}" register: _origin diff --git a/roles/validate_origin/tasks/main.yml b/roles/validate_origin/tasks/main.yml index fa0c153..39bcc9f 100644 --- a/roles/validate_origin/tasks/main.yml +++ b/roles/validate_origin/tasks/main.yml @@ -18,7 +18,7 @@ - name: Validate remote branch exists is reachable on remote repo ansible.builtin.command: > - git ls-remote --exit-code --heads {{ _repo_to_check }} {{ target_branch }} + git ls-remote --exit-code --heads {{ _repo_to_check }} {{ target_branch }} # noqa: command-instead-of-module register: _lsremote failed_when: false From 856cc0deaa5bd5c1726afbe5d5da4f9335d38156 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Wed, 4 Feb 2026 16:26:47 +0100 Subject: [PATCH 29/42] Ignore ansible-lint warnings on git command (2) --- roles/validate_origin/tasks/main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/roles/validate_origin/tasks/main.yml b/roles/validate_origin/tasks/main.yml index 39bcc9f..1a52ff6 100644 --- a/roles/validate_origin/tasks/main.yml +++ b/roles/validate_origin/tasks/main.yml @@ -17,8 +17,7 @@ Validating that branch '{{ target_branch }}' is reachable on remote repo '{{ _repo_to_check }}' - name: Validate remote branch exists is reachable on remote repo - ansible.builtin.command: > - git ls-remote --exit-code --heads {{ _repo_to_check }} {{ target_branch }} # noqa: command-instead-of-module + ansible.builtin.command: git ls-remote --exit-code --heads {{ _repo_to_check }} {{ target_branch }} # noqa: command-instead-of-module register: _lsremote failed_when: false From 68db517d760d02be4eb2a885672fab906b1c56cb Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 4 Feb 2026 14:28:41 -0500 Subject: [PATCH 30/42] more cleanup of common roles/playbooks --- playbooks/argo_healthcheck.yml | 6 +- playbooks/install.yml | 2 +- playbooks/load_secrets.yml | 15 ++--- playbooks/operator_deploy.yml | 20 +++--- playbooks/tasks/check_argo_health.yml | 82 ++++++++----------------- roles/argo_healthcheck/tasks/main.yml | 53 ---------------- roles/validate_cluster/tasks/main.yml | 44 ++++++-------- roles/validate_prereq/tasks/main.yml | 88 +++++++-------------------- roles/validate_schema/tasks/main.yml | 55 ++++++----------- 9 files changed, 108 insertions(+), 257 deletions(-) delete mode 100644 roles/argo_healthcheck/tasks/main.yml diff --git a/playbooks/argo_healthcheck.yml b/playbooks/argo_healthcheck.yml index fa812fc..2259876 100644 --- a/playbooks/argo_healthcheck.yml +++ b/playbooks/argo_healthcheck.yml @@ -3,6 +3,6 @@ hosts: localhost connection: local gather_facts: false - roles: - - role: oc_check - - role: argo_healthcheck + tasks: + - name: Check health of argo applications + ansible.builtin.include_tasks: tasks/check_argo_health.yml diff --git a/playbooks/install.yml b/playbooks/install.yml index 56064ee..25a5ac7 100644 --- a/playbooks/install.yml +++ b/playbooks/install.yml @@ -11,7 +11,7 @@ gather_facts: false vars: - max_retries: 30 + max_retries: 60 retry_count: 0 retry_delay: 60 diff --git a/playbooks/load_secrets.yml b/playbooks/load_secrets.yml index b448671..f3b2c49 100644 --- a/playbooks/load_secrets.yml +++ b/playbooks/load_secrets.yml @@ -12,17 +12,12 @@ secret_loader_disabled: "{{ values_global.global.secretLoader.disabled | default(false) | bool }}" - name: Load secrets (when enabled) + ansible.builtin.include_role: + name: load_secrets when: not secret_loader_disabled - block: - - name: Announce secrets loading - ansible.builtin.shell: | - printf "==> Loading secrets (this may take several minutes)...\n" - - - name: Process secrets via role - ansible.builtin.include_role: - name: load_secrets - name: Print secret loading disabled message - ansible.builtin.shell: | - printf "==> Secrets loading is currently disabled. To enable, update the value of .global.secretLoader.disabled in your values-global.yaml to false.\n" + ansible.builtin.debug: + msg: | + Secrets loading is currently disabled. To enable, update the value of ''.global.secretLoader.disabled' in 'values-global.yaml' to 'false'. when: secret_loader_disabled diff --git a/playbooks/operator_deploy.yml b/playbooks/operator_deploy.yml index d39e0c2..0ac643f 100644 --- a/playbooks/operator_deploy.yml +++ b/playbooks/operator_deploy.yml @@ -29,25 +29,25 @@ name: validate_origin when: not disable_validate_origin - - name: Apply rendered pattern-install chart manifests (with retry) + - name: Apply rendered pattern-install chart manifests block: - - name: Apply via oc with retry - ansible.builtin.command: oc apply -f - - args: - stdin: "{{ pattern_install_rendered_yaml }}" - stdin_add_newline: false + - name: Apply manifests via native k8s module + kubernetes.core.k8s: + definition: "{{ pattern_install_rendered_yaml }}" + state: present register: _apply retries: 10 delay: 15 - until: _apply.rc == 0 + until: not _apply.failed - name: Print success message ansible.builtin.debug: - msg: "Installation succeeded!" + msg: | + Installation of {{ pattern_name }} succeeded! rescue: - name: Print failure summary ansible.builtin.fail: msg: | - Failed to install pattern after 10 retries. Error: - {{ _apply.stderr | default(_apply.stdout) | default('') }} + Failed to install pattern after 10 retries. + Error: {{ _apply.error | default(_apply.msg) | default('Unknown error') }} diff --git a/playbooks/tasks/check_argo_health.yml b/playbooks/tasks/check_argo_health.yml index 569fa8f..67db96e 100644 --- a/playbooks/tasks/check_argo_health.yml +++ b/playbooks/tasks/check_argo_health.yml @@ -1,61 +1,31 @@ --- -- name: Get all Argo CD applications as JSON - ansible.builtin.command: oc get applications.argoproj.io -A -o json - register: apps_raw - changed_when: false +- name: Get all Argo CD applications + kubernetes.core.k8s_info: + api_version: argoproj.io/v1alpha1 + kind: Application + register: argo_apps -- name: Extract and analyze applications +- name: Process Application Statuses ansible.builtin.set_fact: - apps_items: >- - {{ - (apps_raw.stdout | default('{}')) - | from_json - | json_query('items') - | default([]) - }} - -- name: Reset applications summary - ansible.builtin.set_fact: - apps_summary: [] - -- name: Build applications summary - ansible.builtin.set_fact: - apps_summary: >- - {{ apps_summary + [ - { - 'namespace': (item.metadata.namespace | default('')), - 'name': (item.metadata.name | default('')), - 'sync': (item.status.sync.status | default('')), - 'health': (item.status.health.status | default('')), - 'bad': ((item.status.sync.status | default('')) != 'Synced') - or ((item.status.health.status | default('')) != 'Healthy') - } - ] - }} - loop: "{{ apps_items }}" - loop_control: - label: "{{ item.metadata.namespace }}:{{ item.metadata.name }}" - -- name: Filter unhealthy or unsynced applications - ansible.builtin.set_fact: - unhealthy_apps: "{{ apps_summary | default([]) | selectattr('bad') | list }}" - -- name: Print unhealthy/unsynced applications - when: unhealthy_apps | length > 0 - ansible.builtin.debug: - msg: | - ==> Unhealthy or unsynced applications (will retry in 60 seconds): - {% for app in unhealthy_apps %} - {{ app.namespace }}/{{ app.name }} -> Sync: {{ app.sync }} - Health: {{ app.health }} + apps_summary: "{{ summary_yaml | from_yaml }}" + vars: + summary_yaml: | + {% for item in argo_apps.resources -%} + - namespace: {{ item.metadata.namespace }} + name: {{ item.metadata.name }} + sync: {{ item.status.sync.status | default('Unknown') }} + health: {{ item.status.health.status | default('Unknown') }} + bad: {{ (item.status.sync.status | default('Unknown') != 'Synced' or item.status.health.status | default('Unknown') != 'Healthy') | lower }} {% endfor %} -- name: Fail if any applications are not healthy/synced - when: unhealthy_apps | length > 0 - ansible.builtin.fail: - msg: "{{ unhealthy_apps | length }} application(s) are not healthy/synced" - -- name: Print success message - when: unhealthy_apps | length == 0 - ansible.builtin.debug: - msg: | - ==> All {{ apps_summary | length }} Argo applications are healthy and synced. +- name: Validate Cluster Health + vars: + bad_apps: "{{ apps_summary | selectattr('bad') | list }}" + ansible.builtin.assert: + that: bad_apps | length == 0 + fail_msg: | + The following ArgoCD applications are out-of-sync or unhealthy: + {% for app in bad_apps %} + - {{ app.namespace }}/{{ app.name }} (Sync: {{ app.sync }}, Health: {{ app.health }}) + {% endfor %} + quiet: true diff --git a/roles/argo_healthcheck/tasks/main.yml b/roles/argo_healthcheck/tasks/main.yml deleted file mode 100644 index bf36846..0000000 --- a/roles/argo_healthcheck/tasks/main.yml +++ /dev/null @@ -1,53 +0,0 @@ ---- -- name: Print start message - ansible.builtin.debug: - msg: "==> Checking argo applications" - -- name: Get all Argo CD applications as JSON - ansible.builtin.command: oc get applications.argoproj.io -A -o json - register: apps_raw - failed_when: false - -- name: Extract items list - ansible.builtin.set_fact: - apps_items: >- - {{ - (apps_raw.stdout | default('{}')) - | from_json - | json_query('items') - | default([]) - }} - -- name: Build applications summary - ansible.builtin.set_fact: - apps_summary: >- - {{ (apps_summary | default([])) + [ - { - 'namespace': (item.metadata.namespace | default('')), - 'name': (item.metadata.name | default('')), - 'sync': (item.status.sync.status | default('')), - 'health': (item.status.health.status | default('')), - 'bad': ((item.status.sync.status | default('')) != 'Synced') - or ((item.status.health.status | default('')) != 'Healthy') - } - ] - }} - loop: "{{ apps_items }}" - -- name: Print status lines - ansible.builtin.debug: - msg: | - {{ item.namespace }} {{ item.name }} -> Sync: {{ item.sync }} - Health: {{ item.health }} - loop: "{{ apps_summary | default([]) }}" - loop_control: - label: "{{ item.namespace }}:{{ item.name }}" - -- name: Determine if any app is not healthy/synced - ansible.builtin.set_fact: - any_bad: "{{ (apps_summary | default([])) | selectattr('bad') | list | length > 0 }}" - -- name: Fail if any app is not healthy/synced - when: any_bad | bool - ansible.builtin.fail: - msg: - Some applications are not synced or are unhealthy diff --git a/roles/validate_cluster/tasks/main.yml b/roles/validate_cluster/tasks/main.yml index 4e59921..c808c97 100644 --- a/roles/validate_cluster/tasks/main.yml +++ b/roles/validate_cluster/tasks/main.yml @@ -1,28 +1,24 @@ --- -- name: Ensure oc is available - ansible.builtin.include_role: - name: oc_check +- name: Verify cluster connectivity and authentication + kubernetes.core.k8s_cluster_info: + register: cluster_info + failed_when: false -- name: Check that we are logged into a cluster - ansible.builtin.shell: | - oc cluster-info >/dev/null 2>&1 - register: check_oc_cluster - ignore_errors: true +- name: Assert cluster is reachable + ansible.builtin.assert: + that: cluster_info.version is defined + fail_msg: | + Could not connect to the cluster. + Ensure your KUBECONFIG is set or you are logged in (e.g., 'oc login'). + quiet: true -- name: Error out if we cannot get to the cluster - ansible.builtin.fail: - msg: | - We could not log into the cluster. Please check your KUBECONFIG variable - or your ~/.kube/config settings. - when: check_oc_cluster is failed +- name: Fetch StorageClasses + kubernetes.core.k8s_info: + kind: StorageClass + register: sc_list -- name: Ensure we have storage classes defined - ansible.builtin.shell: | - set -o pipefail - printf " storageclass: " - count="$(oc get storageclass -o name 2>/dev/null | wc -l | tr -d ' ')" - if [ "${count}" -eq 0 ]; then - printf "WARNING: No storageclass found" - else - printf "OK" - fi +- name: StorageClass Status + ansible.builtin.debug: + msg: >- + {{ 'OK: StorageClass found.' if (sc_list.resources | default([]) | length > 0) + else 'WARNING: No storageclass found!' }} diff --git a/roles/validate_prereq/tasks/main.yml b/roles/validate_prereq/tasks/main.yml index c54a8a1..255ded6 100644 --- a/roles/validate_prereq/tasks/main.yml +++ b/roles/validate_prereq/tasks/main.yml @@ -1,8 +1,4 @@ --- -- name: Announce prerequisite checks (host) - ansible.builtin.debug: - msg: "==> Checking prerequisites..." - - name: Pattern name and clustergroup name length validation block: - name: Get length of pattern name @@ -13,66 +9,28 @@ ansible.builtin.set_fact: _clustergroup_name_len: "{{ target_clustergroup | length }}" - - name: Calculate DNS part length for ArgoCD deploy - ansible.builtin.set_fact: - _name_valid: >- - {{ (_pattern_name_len | int) + 2 * (_clustergroup_name_len | int) < 47 }} - - - name: Print success message - ansible.builtin.shell: | - printf "OK" - when: _name_valid - - - name: Print failure message - ansible.builtin.fail: - msg: | - FAIL - Validation Explanation: + - name: Ensure ArgoCD will have valid DNS hostname based on pattern name and clustergroup lengths + ansible.builtin.assert: + that: (_pattern_name_len | int) + 2 * (_clustergroup_name_len | int) < 47 + fail_msg: | A DNS-compatible name is constructed in the 'clustergroup' Helm chart using the following pattern: - -> {% raw %}{{ .Values.clusterGroup.name }}-gitops-server-{{ .Values.global.pattern }}-{{ .Values.clusterGroup.name }}{% endraw %} - The total length is calculated as: - (2 * length of 'clusterGroup.name') + length of 'global.pattern' + 15 (for '-gitops-server-') + 1 (for the namespace separator '-') - To stay under the 63-character limit, the variable part of the name must be less than 47 characters: + -> {%raw%}{{ .Values.clusterGroup.name }}-gitops-server-{{ .Values.global.pattern }}-{{ .Values.clusterGroup.name }}{%endraw%} + To stay under the 63-character DNS limit, the variable part of the name must be less than 47 characters: (2 * length of 'clusterGroup.name') + length of 'global.pattern' < 47 - when: not _name_valid - -- name: Detect whether we are running inside a container - ansible.builtin.stat: - path: /run/.containerenv - register: _containerenv - -- name: Host validations (prerequisites on the local machine) - when: not _containerenv.stat.exists - block: - - name: Check python-kubernetes (host) - ansible.builtin.shell: | - printf " Looking for python-kubernetes module... " - if {{ (ansible_python_interpreter | default('/usr/bin/python3')) }} -c 'import kubernetes' >/dev/null 2>&1; then - printf "OK\n" - else - printf "Not found\n" - exit 1 - fi - - - name: Check kubernetes.core collection (host) - ansible.builtin.shell: | - set -o pipefail - printf " Looking for kubernetes.core collection... " - if ansible-galaxy collection list | grep -q 'kubernetes.core'; then - printf "OK\n" - else - printf "Not found\n" - exit 1 - fi - -- name: Container validations - when: _containerenv.stat.exists - block: - - name: Compute multiSourceConfig.enabled (default false) - ansible.builtin.set_fact: - _msc_enabled: "{{ values_global.main.multiSourceConfig.enabled | default(false) | bool }}" - - - name: Enforce multiSourceConfig is enabled (container) - ansible.builtin.shell: | - printf "You must set \".main.multiSourceConfig.enabled: true\" in 'values-global.yaml'.\n" - exit 1 - when: not _msc_enabled + With your current values this DNS part is '{{ target_clustergroup }}-gitops-server-{{ pattern_name }}-{{ target_clustergroup }}' and exceeds the 63 character limit. + +- name: Ensure kubernetes python module is installed + ansible.builtin.pip: + name: kubernetes + state: present + extra_args: "-q" + +- name: Ensure kubernetes.core collection is installed + community.general.ansible_galaxy_install: + name: kubernetes.core + type: collection + +- name: Ensure multi source is enabled + ansible.builtin.assert: + that: (values_global.main.multiSourceConfig.enabled | default(false) | bool) == true + fail_msg: "You must set '.main.multiSourceConfig.enabled' to 'true' in 'values-global.yaml'." diff --git a/roles/validate_schema/tasks/main.yml b/roles/validate_schema/tasks/main.yml index 5d06650..d0b595f 100644 --- a/roles/validate_schema/tasks/main.yml +++ b/roles/validate_schema/tasks/main.yml @@ -6,31 +6,16 @@ file_type: file register: _values_files -- name: Sort file list +- name: Sort values files and determine clustergroup version cli arg ansible.builtin.set_fact: - values_files_sorted: >- - {{ - _values_files.files - | map(attribute='path') - | list - | sort - }} - -- name: Print start message - ansible.builtin.shell: - cmd: | - printf "==> Validating clustergroup schema of: " - for f in {{ values_files_sorted | map('basename') | list | join(' ') }}; do printf " $f"; done - printf "" - -- name: Determine clustergroup chart CLI extras - ansible.builtin.set_fact: - clustergroup_version: "{{ values_global.main.multiSourceConfig.clusterGroupChartVersion | default('') }}" + values_files_sorted: "{{ _values_files.files | map(attribute='path') | sort }}" clustergroup_version_cli: >- - {{ - (values_global.main.multiSourceConfig.clusterGroupChartVersion | default('')) - | ternary('--version ' + values_global.main.multiSourceConfig.clusterGroupChartVersion, '') - }} + {{ ('--version ' + values_global.main.multiSourceConfig.clusterGroupChartVersion) + if (values_global.main.multiSourceConfig.clusterGroupChartVersion | default('')) else '' }} + +- name: Log files being validated + ansible.builtin.debug: + msg: "Validating clustergroup schema against: {{ values_files_sorted | map('basename') | join(', ') }}" - name: Validate each file with helm template ansible.builtin.command: >- @@ -43,16 +28,16 @@ loop_control: label: "{{ item | basename }}" -- name: Exit with error if any validation failed - when: (helm_validate.results | selectattr('rc', 'ne', 0) | list | length) > 0 - ansible.builtin.shell: - cmd: | - printf "Schema validation failed for the following values files:" - {% for result in helm_validate.results %} - {% if result.rc != 0 %} - printf " - {{ result.item | basename }} (exit code: {{ result.rc }})\n" - printf " To reproduce: cd {{ pattern_dir }} && helm template {{ clustergroup_chart }} {{ clustergroup_version_cli }} {{ extra_helm_opts }} -f \"{{ result.item }}\"\n" - {% endif %} +- name: Check validation results + vars: + failed_validations: "{{ helm_validate.results | selectattr('rc', 'ne', 0) | list }}" + ansible.builtin.assert: + that: failed_validations | length == 0 + fail_msg: | + Schema validation failed for the following files: + {% for error in failed_validations %} + - {{ error.item | basename }} (RC: {{ error.rc }}) + Reproduction: cd {{ pattern_dir }} && helm template {{ clustergroup_chart }} {{ clustergroup_version_cli }} {{ extra_helm_opts }} -f "{{ error.item | basename }}" + Error: {{ error.stderr }} {% endfor %} - printf "\nRe-run the above commands to see the detailed error output.\n" - exit 1 + quiet: true From cbb2059039512a4e64e9f7ae27c8c5d0f8bea003 Mon Sep 17 00:00:00 2001 From: Drew Minnear Date: Wed, 4 Feb 2026 16:05:28 -0500 Subject: [PATCH 31/42] argo healthcheck fails when 0 argo apps found --- playbooks/tasks/check_argo_health.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/playbooks/tasks/check_argo_health.yml b/playbooks/tasks/check_argo_health.yml index 67db96e..6e40b95 100644 --- a/playbooks/tasks/check_argo_health.yml +++ b/playbooks/tasks/check_argo_health.yml @@ -7,7 +7,7 @@ - name: Process Application Statuses ansible.builtin.set_fact: - apps_summary: "{{ summary_yaml | from_yaml }}" + apps_summary: "{{ (summary_yaml | from_yaml) | default([], true) }}" vars: summary_yaml: | {% for item in argo_apps.resources -%} @@ -22,10 +22,16 @@ vars: bad_apps: "{{ apps_summary | selectattr('bad') | list }}" ansible.builtin.assert: - that: bad_apps | length == 0 + that: + - apps_summary | length > 0 + - bad_apps | length == 0 fail_msg: | + {% if apps_summary | length == 0 %} + No ArgoCD applications found in the cluster. + {% else %} The following ArgoCD applications are out-of-sync or unhealthy: {% for app in bad_apps %} - {{ app.namespace }}/{{ app.name }} (Sync: {{ app.sync }}, Health: {{ app.health }}) {% endfor %} + {% endif %} quiet: true From 1320e173719a16cf2c3ee9d33a597c68c000b1e8 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Thu, 5 Feb 2026 09:05:32 +0100 Subject: [PATCH 32/42] When an assert has the quiet: true attribute only display the msg in normal color --- plugins/callback/readable.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 23078d0..bda1721 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -153,12 +153,23 @@ def _is_fail_task(self, result): """Check if this is a fail task that should use simple message output.""" return result._task.action in ("fail", "ansible.builtin.fail") + def _is_quiet_assert_task(self, result): + """Check if this is an assert task with quiet: true.""" + return ( + result._task.action in ("assert", "ansible.builtin.assert") and + result._task.args.get("quiet") is True + ) + def _handle_exception(self, result, use_stderr=None): - """Override exception handling to suppress for fail tasks.""" + """Override exception handling to suppress for fail tasks and quiet assert tasks.""" # Skip exception handling for fail tasks - we just want to show the msg if hasattr(self, "_current_task") and self._current_task: if self._current_task.action in ("fail", "ansible.builtin.fail"): return + # Skip exception handling for quiet assert tasks + if (self._current_task.action in ("assert", "ansible.builtin.assert") and + self._current_task.args.get("quiet") is True): + return super()._handle_exception(result, use_stderr) @@ -179,6 +190,15 @@ def v2_runner_on_failed(self, result, ignore_errors=False): self._display.display(" error (ignored)", C.COLOR_WARN) return + # For quiet assert tasks, just display the fail_msg in normal color + if self._is_quiet_assert_task(result): + if result._result.get("warnings"): + for warning in result._result["warnings"]: + self._display.warning(warning) + msg = result._result.get("msg", "Assertion failed") + self._display.display(f" {msg}") + return + # For fail tasks, just display the message cleanly if self._is_fail_task(result): if result._result.get("warnings"): From 116ccfe70d0051655e52414738bdf02a48b7af5c Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Thu, 5 Feb 2026 09:21:20 +0100 Subject: [PATCH 33/42] Stop printing the included: playbooks --- plugins/callback/readable.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index bda1721..22bb0bc 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -259,7 +259,7 @@ def v2_runner_item_on_failed(self, result): def v2_runner_item_on_ok(self, result): self._preprocess_result(result) - # Handle debug tasks specially - print their output + # Handle debug tasks specially if result._task.action in NO_STDOUT_TASKS: debug_msg = result._result.get("msg", "") if debug_msg: @@ -284,6 +284,10 @@ def v2_runner_on_unreachable(self, result): def v2_on_file_diff(self, result): return + def v2_playbook_on_include(self, included_file): + """Suppress 'included:' messages.""" + return + def v2_playbook_on_stats(self, stats): self._finalize_loop_if_needed() return From 9180612633d4d538aba7c1d1f87ab6e80b198071 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Thu, 5 Feb 2026 09:24:10 +0100 Subject: [PATCH 34/42] Some linting fixes --- plugins/callback/readable.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 22bb0bc..660a63b 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -156,8 +156,8 @@ def _is_fail_task(self, result): def _is_quiet_assert_task(self, result): """Check if this is an assert task with quiet: true.""" return ( - result._task.action in ("assert", "ansible.builtin.assert") and - result._task.args.get("quiet") is True + result._task.action in ("assert", "ansible.builtin.assert") + and result._task.args.get("quiet") is True ) def _handle_exception(self, result, use_stderr=None): @@ -167,8 +167,10 @@ def _handle_exception(self, result, use_stderr=None): if self._current_task.action in ("fail", "ansible.builtin.fail"): return # Skip exception handling for quiet assert tasks - if (self._current_task.action in ("assert", "ansible.builtin.assert") and - self._current_task.args.get("quiet") is True): + if ( + self._current_task.action in ("assert", "ansible.builtin.assert") + and self._current_task.args.get("quiet") is True + ): return super()._handle_exception(result, use_stderr) From 379204c4b7269af9f22924c666bc451e4c27bd6f Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Thu, 5 Feb 2026 19:32:17 +0100 Subject: [PATCH 35/42] Stop printing skipped tasks --- plugins/callback/readable.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index 660a63b..d0977bd 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -138,11 +138,8 @@ def v2_playbook_on_play_start(self, play): self._display.display(msg) def v2_runner_on_skipped(self, result, ignore_errors=False): - if not self.get_option("display_skipped_hosts"): - return - self._preprocess_result(result) - task_result = self._process_result_output(result, "skipped") - self._display.display(f" {task_result}", C.COLOR_SKIP) + # Suppress all skipped task output + return def _build_msg_with_item(self, base_msg, result): """Build message with optional item label.""" From 1c15aba8e0668648a76b7abdfc4efa20ad58fa2e Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Thu, 5 Feb 2026 19:35:20 +0100 Subject: [PATCH 36/42] Add pip to the skip stdout modules --- plugins/callback/readable.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/callback/readable.py b/plugins/callback/readable.py index d0977bd..5884339 100644 --- a/plugins/callback/readable.py +++ b/plugins/callback/readable.py @@ -36,6 +36,7 @@ "ansible.builtin.pause", "kubernetes.core.k8s_info", "kubernetes.core.k8s_exec", + "ansible.builtin.pip", ) From fde0b0195e715627db4f01d07ff114258e07455a Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Mon, 16 Feb 2026 11:08:51 +0100 Subject: [PATCH 37/42] Add community.general to the requirements.yml --- requirements.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.yml b/requirements.yml index 29f0724..fb6b6f2 100644 --- a/requirements.yml +++ b/requirements.yml @@ -1,4 +1,5 @@ --- collections: + - name: community.general - name: kubernetes.core - name: community.okd From 94a10917bdc27bd8b1ec3f89ac487dfaedbb5961 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Mon, 16 Feb 2026 11:29:36 +0100 Subject: [PATCH 38/42] Update super-linter to v8.5.0 podman inspect --format='{{index .RepoDigests 0}}' ghcr.io/super-linter/super-linter:slim-v8 --- .github/workflows/superlinter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/superlinter.yml b/.github/workflows/superlinter.yml index d772de5..a5dfa2c 100644 --- a/.github/workflows/superlinter.yml +++ b/.github/workflows/superlinter.yml @@ -22,7 +22,7 @@ jobs: # Run Linter against code base # ################################ - name: Lint Code Base - uses: super-linter/super-linter/slim@2bdd90ed3262e023ac84bf8fe35dc480721fc1f2 + uses: super-linter/super-linter/slim@v8.5.0 # v8.5.0 env: VALIDATE_ALL_CODEBASE: true DEFAULT_BRANCH: main From ec3acb885d615bc889ba5a5d2992dbe6641f583d Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Mon, 16 Feb 2026 11:37:12 +0100 Subject: [PATCH 39/42] Spelling fixes --- playbooks/install.yml | 2 +- playbooks/load_secrets.yml | 2 +- playbooks/operator_deploy.yml | 2 +- plugins/module_utils/load_secrets_v1.py | 4 ++-- plugins/modules/vault_load_parsed_secrets.py | 4 ++-- roles/iib_ci/README.md | 6 +++--- roles/iib_ci/tasks/mirror-related-images.yml | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/playbooks/install.yml b/playbooks/install.yml index 25a5ac7..2ff805a 100644 --- a/playbooks/install.yml +++ b/playbooks/install.yml @@ -2,7 +2,7 @@ - name: Install the pattern via pattern-install chart ansible.builtin.import_playbook: operator_deploy.yml -- name: Load secrets (if not explicity disabled in values-global.yaml) +- name: Load secrets (if not explicitly disabled in values-global.yaml) ansible.builtin.import_playbook: load_secrets.yml - name: Wait for pattern to finish installation (all Argo apps should be healthy/synced) diff --git a/playbooks/load_secrets.yml b/playbooks/load_secrets.yml index f3b2c49..a8d7a6a 100644 --- a/playbooks/load_secrets.yml +++ b/playbooks/load_secrets.yml @@ -7,7 +7,7 @@ - role: pattern_settings tasks: - - name: Check values-global to see if secret loading is explicity disabled + - name: Check values-global to see if secret loading is explicitly disabled ansible.builtin.set_fact: secret_loader_disabled: "{{ values_global.global.secretLoader.disabled | default(false) | bool }}" diff --git a/playbooks/operator_deploy.yml b/playbooks/operator_deploy.yml index 0ac643f..9758b4d 100644 --- a/playbooks/operator_deploy.yml +++ b/playbooks/operator_deploy.yml @@ -7,7 +7,7 @@ roles: - role: pattern_settings # set general pattern vars - role: install_settings # set pattern-install specific vars - - role: validate_prereq # ensure installation depencies are present + - role: validate_prereq # ensure installation dependencies are present - role: validate_cluster # ensure a cluster is connected and has a default storage class - role: pattern_install_template # render the pattern-install helm chart diff --git a/plugins/module_utils/load_secrets_v1.py b/plugins/module_utils/load_secrets_v1.py index ff0b0c9..9701c06 100644 --- a/plugins/module_utils/load_secrets_v1.py +++ b/plugins/module_utils/load_secrets_v1.py @@ -16,6 +16,7 @@ """ Module that implements V1 of the values-secret.yaml spec """ + from __future__ import absolute_import, division, print_function __metaclass__ = type @@ -38,7 +39,6 @@ class LoadSecretsV1: - def __init__( self, module, @@ -125,7 +125,7 @@ def sanitize_values(self): self.check_for_missing_secrets() secrets = self.syaml.get("secrets", {}) - # We need to explicitely check for None because the file might contain the + # We need to explicitly check for None because the file might contain the # top-level 'secrets:' or 'files:' key but have nothing else under it which will # return None and not {} if secrets is None: diff --git a/plugins/modules/vault_load_parsed_secrets.py b/plugins/modules/vault_load_parsed_secrets.py index 0c36b4c..cd06f8c 100644 --- a/plugins/modules/vault_load_parsed_secrets.py +++ b/plugins/modules/vault_load_parsed_secrets.py @@ -22,6 +22,7 @@ version: 2.0 """ + from __future__ import absolute_import, division, print_function __metaclass__ = type @@ -41,7 +42,7 @@ - Martin Jackson (@mhjacks) description: - Takes parsed secrets objects and vault policies (as delivered by parse_secrets_info) and runs the commands to - load them into a vault instance. The relevent metadata will exist in the parsed secrets object. Returns count + load them into a vault instance. The relevant metadata will exist in the parsed secrets object. Returns count of secrets injected. options: parsed_secrets: @@ -98,7 +99,6 @@ class VaultSecretLoader: - def __init__( self, module, diff --git a/roles/iib_ci/README.md b/roles/iib_ci/README.md index c2017ce..ac216cb 100644 --- a/roles/iib_ci/README.md +++ b/roles/iib_ci/README.md @@ -50,7 +50,7 @@ make EXTRA_HELM_OPTS="--set main.gitops.operatorSource=iib-${IIB} --set main.git ### ACM operator The advanced-cluster-management operator is a little bit more complex than the others because it -also installes another operator called MCE multicluster-engine. So to install ACM you typically +also installs another operator called MCE multicluster-engine. So to install ACM you typically need two IIBs (one for acm and one for mce). With those two at hand, do the following (the ordering must be consistent: the first IIB corresponds to the first OPERATOR, etc). The following operation needs to be done on both hub *and* spokes: @@ -91,7 +91,7 @@ make EXTRA_HELM_OPTS="--set main.extraParameters[0].name=clusterGroup.subscripti ## Useful commands -* List IIBs for an operator: +- List IIBs for an operator: ```sh ansible-playbook common/ansible/playbooks/iib-ci/lookup.yml @@ -104,7 +104,7 @@ ok: [localhost] => (item=v4.13) => { Override the `operator` value with the desired bundle name to figure out the last IIBs for it. -* List all images uploaded to the internal registry: +- List all images uploaded to the internal registry: ```sh oc exec -it -n openshift-image-registry $(oc get pods -n openshift-image-registry -o json | jq -r '.items[].metadata.name | select(. | test("^image-registry-"))' | head -n1) -- bash -c "curl -k -u kubeadmin:$(oc whoami -t) https://localhost:5000/v2/_catalog" diff --git a/roles/iib_ci/tasks/mirror-related-images.yml b/roles/iib_ci/tasks/mirror-related-images.yml index 74a0bc3..0e2996c 100644 --- a/roles/iib_ci/tasks/mirror-related-images.yml +++ b/roles/iib_ci/tasks/mirror-related-images.yml @@ -131,7 +131,7 @@ mode: "0644" # NOTE(bandini): mirror.map *must* have a tag (we use the IIB number) on the image on the right side -# otherwise, the image will be uplaoded and will exist in S3 but it won't exist in the registry's catalog!! +# otherwise, the image will be uploaded and will exist in S3 but it won't exist in the registry's catalog!! - name: Mirror all the needed images ansible.builtin.shell: | set -o pipefail From 1e4d59824e595081106401c4bb04026c93f25823 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Mon, 16 Feb 2026 11:37:56 +0100 Subject: [PATCH 40/42] Disable black for now Neither fedora nor pip have the version inside super-linter so fixing this stuff is super tedious --- .github/workflows/superlinter.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/superlinter.yml b/.github/workflows/superlinter.yml index a5dfa2c..bc2a9c1 100644 --- a/.github/workflows/superlinter.yml +++ b/.github/workflows/superlinter.yml @@ -36,6 +36,7 @@ jobs: VALIDATE_JSON_PRETTIER: false VALIDATE_MARKDOWN_PRETTIER: false VALIDATE_KUBERNETES_KUBECONFORM: false + VALIDATE_PYTHON_BLACK: false VALIDATE_PYTHON_PYLINT: false VALIDATE_PYTHON_PYINK: false VALIDATE_PYTHON_RUFF_FORMAT: false From 5b372960a2764b7018e614fb3b5cbfc8c8b12645 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Mon, 16 Feb 2026 11:39:06 +0100 Subject: [PATCH 41/42] Add dependabot cooldown --- .github/dependabot.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ce740cc..888fc67 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -6,3 +6,5 @@ updates: directory: "/" schedule: interval: "weekly" + cooldown: + default-days: 7 From c7bda53a8647a439f895d7845e932a1acdaa4b69 Mon Sep 17 00:00:00 2001 From: Michele Baldessari Date: Mon, 16 Feb 2026 11:46:31 +0100 Subject: [PATCH 42/42] Pin actions to hashes --- .github/workflows/ansible-lint.yml | 2 +- .github/workflows/ansible-sanitytest.yml | 4 ++-- .github/workflows/ansible-unittest.yml | 4 ++-- .github/workflows/jsonschema.yaml | 4 ++-- .github/workflows/superlinter.yml | 4 ++-- .../workflows/trigger-utility-imperative-container-builds.yml | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ansible-lint.yml b/.github/workflows/ansible-lint.yml index 43908d7..f4e0405 100644 --- a/.github/workflows/ansible-lint.yml +++ b/.github/workflows/ansible-lint.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false diff --git a/.github/workflows/ansible-sanitytest.yml b/.github/workflows/ansible-sanitytest.yml index 0675adf..d2be96f 100644 --- a/.github/workflows/ansible-sanitytest.yml +++ b/.github/workflows/ansible-sanitytest.yml @@ -16,13 +16,13 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: path: ansible_collections/rhvp/cluster_utils persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/ansible-unittest.yml b/.github/workflows/ansible-unittest.yml index c195aee..fa9f1fd 100644 --- a/.github/workflows/ansible-unittest.yml +++ b/.github/workflows/ansible-unittest.yml @@ -16,13 +16,13 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: path: ansible_collections/rhvp/cluster_utils persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/jsonschema.yaml b/.github/workflows/jsonschema.yaml index 53310d4..b9c1424 100644 --- a/.github/workflows/jsonschema.yaml +++ b/.github/workflows/jsonschema.yaml @@ -15,12 +15,12 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v6 + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/superlinter.yml b/.github/workflows/superlinter.yml index bc2a9c1..9492da0 100644 --- a/.github/workflows/superlinter.yml +++ b/.github/workflows/superlinter.yml @@ -12,7 +12,7 @@ jobs: steps: - name: Checkout Code - uses: actions/checkout@v6 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: # Full git history is needed to get a proper list of changed files within `super-linter` fetch-depth: 0 @@ -22,7 +22,7 @@ jobs: # Run Linter against code base # ################################ - name: Lint Code Base - uses: super-linter/super-linter/slim@v8.5.0 # v8.5.0 + uses: super-linter/super-linter/slim@61abc07d755095a68f4987d1c2c3d1d64408f1f9 # v8.5.0 env: VALIDATE_ALL_CODEBASE: true DEFAULT_BRANCH: main diff --git a/.github/workflows/trigger-utility-imperative-container-builds.yml b/.github/workflows/trigger-utility-imperative-container-builds.yml index b26be07..ba4ae2b 100644 --- a/.github/workflows/trigger-utility-imperative-container-builds.yml +++ b/.github/workflows/trigger-utility-imperative-container-builds.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Generate GitHub App token id: generate-token - uses: actions/create-github-app-token@v2 + uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2 with: app-id: ${{ secrets.GH_WORKFLOW_AUTOMATION_CLIENT_ID }} private-key: ${{ secrets.GH_WORKFLOW_AUTOMATION_PRIVATE_KEY }}