diff --git a/src/ansible.cfg b/src/ansible.cfg index e27074e0c..2b4e69983 100644 --- a/src/ansible.cfg +++ b/src/ansible.cfg @@ -2,4 +2,6 @@ host_key_checking = False roles_path = ./roles filter_plugins = ./filter_plugins +callback_plugins = ./callback_plugins callback_result_format = yaml +stdout_callback = foremanctl diff --git a/src/callback_plugins/foremanctl.py b/src/callback_plugins/foremanctl.py new file mode 100644 index 000000000..81213fc80 --- /dev/null +++ b/src/callback_plugins/foremanctl.py @@ -0,0 +1,51 @@ +from ansible.plugins.callback.default import CallbackModule as DefaultCallbackModule + +DOCUMENTATION = """ + name: foremanctl + type: stdout + short_description: foremanctl stdout callback + description: + - Suppresses default Ansible output for plays tagged with + foremanctl_suppress_default_output, displaying only task msg rather than ansible default output. + extends_documentation_fragment: + - default_callback + - result_format_callback + requirements: + - set as stdout in configuration +""" + +class CallbackModule(DefaultCallbackModule): + """Foremanctl callback.""" + + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'stdout' + CALLBACK_NAME = 'foremanctl' + + FALLBACK_TO_DEFAULT = True + + def v2_playbook_on_start(self, playbook): + plays = playbook.get_plays() + tags = plays[0].tags + if 'foremanctl_suppress_default_output' in tags: + self.FALLBACK_TO_DEFAULT = False + if self.FALLBACK_TO_DEFAULT: + super().v2_playbook_on_start(playbook) + + def v2_playbook_on_play_start(self, play): + if self.FALLBACK_TO_DEFAULT: + super().v2_playbook_on_play_start(play) + + def v2_playbook_on_task_start(self, task, is_conditional): + if self.FALLBACK_TO_DEFAULT: + super().v2_playbook_on_task_start(task, is_conditional) + + def v2_runner_on_ok(self, result): + if self.FALLBACK_TO_DEFAULT: + super().v2_runner_on_ok(result) + else: + if msg := result._result.get('msg'): + self._display.display(msg) + + def v2_playbook_on_stats(self, stats): + if self.FALLBACK_TO_DEFAULT: + super().v2_playbook_on_stats(stats) diff --git a/src/filter_plugins/foremanctl.py b/src/filter_plugins/foremanctl.py index 964dab743..fb88c9cac 100644 --- a/src/filter_plugins/foremanctl.py +++ b/src/filter_plugins/foremanctl.py @@ -55,6 +55,23 @@ def available_foreman_plugins(_value): plugins = [FEATURE_MAP.get(feature).get('foreman', {}).get('plugin_name') for feature in FEATURE_MAP.keys()] return compact_list(plugins) +def list_all_features(enabled_features, only_enabled=False): + enabled_list = [] + available_list = [] + for name, meta in FEATURE_MAP.items(): + if meta.get('internal', False): + continue + description = meta.get('description', '') + if name in enabled_features: + enabled_list.append((name, 'enabled', description)) + elif not only_enabled: + available_list.append((name, 'available', description)) + + output = [f"{'FEATURE':<25} {'STATE':<12} DESCRIPTION"] + for name, state, description in enabled_list + available_list: + output.append(f"{name:<25} {state:<12} {description}") + + return "\n".join(output) def foreman_proxy_plugins(value): dependencies = list(get_dependencies(filter_features(value))) @@ -76,4 +93,5 @@ def filters(self): 'available_foreman_plugins': available_foreman_plugins, 'features_to_foreman_proxy_plugins': foreman_proxy_plugins, 'available_foreman_proxy_plugins': available_foreman_proxy_plugins, + 'list_all_features': list_all_features, } diff --git a/src/playbooks/features/features.yaml b/src/playbooks/features/features.yaml new file mode 100644 index 000000000..8bf525ad8 --- /dev/null +++ b/src/playbooks/features/features.yaml @@ -0,0 +1,16 @@ +--- +- name: List features + hosts: quadlet + gather_facts: false + tags: + - foremanctl_suppress_default_output + vars: + list_enabled: false + vars_files: + - "../../vars/defaults.yml" + - "../../vars/flavors/{{ flavor }}.yml" + - "../../vars/base.yaml" + tasks: + - name: Print features + ansible.builtin.debug: + msg: "{{ enabled_features | list_all_features(list_enabled) }}" diff --git a/src/playbooks/features/metadata.obsah.yaml b/src/playbooks/features/metadata.obsah.yaml new file mode 100644 index 000000000..5480d4ff9 --- /dev/null +++ b/src/playbooks/features/metadata.obsah.yaml @@ -0,0 +1,9 @@ +--- +help: List all enabled and available features + +variables: + list_enabled: + parameter: --list-enabled + help: List only enabled features + action: store_true + persist: false diff --git a/tests/features_test.py b/tests/features_test.py new file mode 100644 index 000000000..92309c950 --- /dev/null +++ b/tests/features_test.py @@ -0,0 +1,22 @@ +import subprocess + +def test_foremanctl_features(): + command = ['./foremanctl', 'features'] + result = subprocess.run(command, capture_output=True, text=True) + + assert result.returncode == 0 + + for noise in ['PLAY [', 'TASK [', 'ok:', 'changed:', 'PLAY RECAP']: + assert noise not in result.stdout, f"Ansible output not suppressed: found '{noise}'" + + for feature in ['foreman', 'foreman-proxy', 'azure-rm']: + assert feature in result.stdout, f"Expected feature '{feature}' in output" + +def test_foremanctl_features_list_enabled(): + command = ['./foremanctl', 'features', '--list-enabled'] + result = subprocess.run(command, capture_output=True, text=True) + + assert result.returncode == 0 + + assert 'enabled' in result.stdout + assert 'available' not in result.stdout