Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ansible.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
host_key_checking = False
roles_path = ./roles
filter_plugins = ./filter_plugins
callback_plugins = ./callback_plugins
callback_result_format = yaml
80 changes: 80 additions & 0 deletions src/callback_plugins/foremanctl_features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

from ansible.plugins.callback import CallbackBase
import pathlib
import yaml
import os

BASE_DIR = pathlib.Path(__file__).parent.parent
STATE_DIR = pathlib.Path(os.environ.get('OBSAH_STATE', '.var/lib/foremanctl'))

def load_yaml(path: pathlib.Path) -> dict:
try:
with path.open() as f:
return yaml.safe_load(f) or {}
except FileNotFoundError:
return {}


class CallbackModule(CallbackBase):
"""Features listing callback."""

CALLBACK_VERSION = 2.0
CALLBACK_TYPE = 'stdout'
CALLBACK_NAME = 'foremanctl_features'

def __init__(self):
super().__init__()
self.feature_metadata = self._load_features_metadata()

def _load_features_metadata(self):
features_yaml = BASE_DIR / 'features.yaml'
return load_yaml(features_yaml)

def v2_runner_on_ok(self, result):
if result._task.action in ('ansible.builtin.debug', 'debug'):
self._display_features()

def _display_features(self):
params_yaml = STATE_DIR / "parameters.yaml"
params = load_yaml(params_yaml)

flavor = params.get('flavor')
added_features = params.get('features', [])

flavor_file = BASE_DIR / f"vars/flavors/{flavor}.yml"
flavor_config = load_yaml(flavor_file)
flavor_features = flavor_config.get('flavor_features', [])

enabled_features = flavor_features + added_features

enabled_list = []
available_list = []

for name in sorted(self.feature_metadata.keys()):
meta = self.feature_metadata.get(name, {}) or {}

if meta.get('internal', False):
continue

description = meta.get('description', '')

if name in enabled_features:
enabled_list.append((name, 'enabled', description))
else:
available_list.append((name, 'available', description))

self._display.display(f"{'FEATURE':<25} {'STATE':<12} DESCRIPTION")

for name, state, description in enabled_list:
self._display.display(f"{name:<25} {state:<12} {description}")

for name, state, description in available_list:
self._display.display(f"{name:<25} {state:<12} {description}")

total = len(enabled_list) + len(available_list)
self._display.display("")
self._display.display(
f"{total} features listed ({len(enabled_list)} enabled, {len(available_list)} available)."
)
71 changes: 71 additions & 0 deletions src/features.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env python3
"""Display enabled and available features for Foreman deployment."""
import yaml
import pathlib
import os

BASE_DIR = pathlib.Path(__file__).resolve().parent
STATE_DIR = pathlib.Path(os.environ.get('OBSAH_STATE', '.var/lib/foremanctl'))


def load_yaml(path: pathlib.Path) -> dict:
"""Load YAML file, return empty dict if not found."""
try:
with path.open() as f:
return yaml.safe_load(f) or {}
except FileNotFoundError:
return {}


def main():
features_yaml = BASE_DIR / "features.yaml"
all_features = load_yaml(features_yaml)

# Load persisted parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some of the comments in the file I'd avoid. The code speaks for itself and the comment becomes noise.

params_yaml = STATE_DIR / "parameters.yaml"
params = load_yaml(params_yaml)

flavor = params.get('flavor', 'katello')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am hesitant to have a default flavor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, i can omit that

added_features = params.get('features', [])

# Load flavor configuration
flavor_file = BASE_DIR / f"vars/flavors/{flavor}.yml"
flavor_config = load_yaml(flavor_file)
flavor_features = flavor_config.get('flavor_features', [])

enabled_features = flavor_features + added_features

print(f"{'FEATURE':<25} {'STATE':<12} DESCRIPTION")

# Separate enabled and available
enabled_list = []
available_list = []

for name in sorted(all_features.keys()):
meta = all_features[name] or {}

# Skip internal features
if meta.get('internal', False):
continue

description = meta.get('description', '')

if name in enabled_features:
enabled_list.append((name, 'enabled', description))
else:
available_list.append((name, 'available', description))

for name, state, description in enabled_list:
print(f"{name:<25} {state:<12} {description}")

for name, state, description in available_list:
print(f"{name:<25} {state:<12} {description}")

print()
enabled_count = len(enabled_list)
total_count = len(enabled_list) + len(available_list)
print(f"{total_count} features listed ({enabled_count} enabled, {len(available_list)} available).")


if __name__ == "__main__":
main()
9 changes: 9 additions & 0 deletions src/playbooks/features/features.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
- name: List features
hosts: quadlet
gather_facts: false

tasks:
- name: List Enabled features
ansible.builtin.debug:
msg: ""
5 changes: 5 additions & 0 deletions src/playbooks/features/metadata.obsah.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
help: |
List all enabled and available features

stdout_callback: foremanctl_features