Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite feature function to plugins #3276

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ ignore = [
"tmt/steps/discover/*.py",
"tmt/steps/execute/*.py",
"tmt/steps/finish/*.py",
"tmt/steps/prepare/feature.py",
"tmt/steps/prepare/feature/__init__.py",
"tmt/steps/prepare/__init__.py",
"tmt/steps/prepare/install.py",
"tmt/steps/provision/*.py",
Expand Down
2 changes: 1 addition & 1 deletion stories/cli/try.fmf
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ link:

link:
- implemented-by: /tmt/trying.py
- implemented-by: /tmt/steps/prepare/feature.py
- implemented-by: /tmt/steps/prepare/feature/__init__.py

/install:
story:
Expand Down
1 change: 1 addition & 0 deletions tmt/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ def _discover_packages() -> list[tuple[str, Path]]:
('tmt.frameworks', Path('frameworks')),
('tmt.checks', Path('checks')),
('tmt.package_managers', Path('package_managers')),
('tmt.steps.prepare.feature', Path('steps/prepare/feature')),
]


Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import dataclasses
from typing import Optional, cast
from typing import Callable, Optional, cast

import tmt
import tmt.base
Expand All @@ -9,12 +9,50 @@
import tmt.steps.prepare
import tmt.steps.provision
import tmt.utils
from tmt.plugins import PluginRegistry
from tmt.result import PhaseResult
from tmt.steps.provision import Guest
from tmt.utils import Path, field

FEATURE_PLAYEBOOK_DIRECTORY = tmt.utils.resource_files('steps/prepare/feature')

FeatureClass = type['Feature']
_FEATURE_PLUGIN_REGISTRY: PluginRegistry[FeatureClass] = PluginRegistry()


def provides_feature(
feature: str) -> Callable[[FeatureClass], FeatureClass]:
"""
A decorator for registering feature plugins.
Decorate a feature plugin class to register a feature.
"""

def _provides_feature(feature_cls: FeatureClass) -> FeatureClass:
_FEATURE_PLUGIN_REGISTRY.register_plugin(
plugin_id=feature,
plugin=feature_cls,
logger=tmt.log.Logger.get_bootstrap_logger())

return feature_cls

return _provides_feature


def find_plugin(name: str) -> 'FeatureClass':
"""
Find a plugin by its name.

:raises GeneralError: when the plugin does not exist.
"""

plugin = _FEATURE_PLUGIN_REGISTRY.get_plugin(name)

if plugin is None:
raise tmt.utils.GeneralError(
f"Feature plugin '{name}' was not found in feature registry.")

return plugin


class Feature(tmt.utils.Common):
""" Base class for ``feature`` prepare plugin implementations """
Expand All @@ -32,51 +70,37 @@ def __init__(

self.guest = guest

def _find_playbook(self, filename: str) -> Optional[Path]:
@classmethod
def enable(cls, guest: Guest, logger: tmt.log.Logger) -> None:
raise NotImplementedError

@classmethod
def disable(cls, guest: Guest, logger: tmt.log.Logger) -> None:
raise NotImplementedError

@classmethod
def _find_playbook(cls, filename: str, logger: tmt.log.Logger) -> Optional[Path]:
filepath = FEATURE_PLAYEBOOK_DIRECTORY / filename
if filepath.exists():
return filepath

self.warn(f"Cannot find any suitable playbook for '{filename}'.")
logger.warning(f"Cannot find any suitable playbook for '{filename}'.", 0)
return None


class ToggleableFeature(Feature):
def _run_playbook(self, op: str, playbook_filename: str) -> None:
playbook_path = self._find_playbook(playbook_filename)
@classmethod
def _run_playbook(
cls,
op: str,
playbook_filename: str,
guest: Guest,
logger: tmt.log.Logger) -> None:
playbook_path = cls._find_playbook(playbook_filename, logger)
if not playbook_path:
raise tmt.utils.GeneralError(
f"{op.capitalize()} {self.NAME.upper()} is not supported on this guest.")

self.info(f'{op.capitalize()} {self.NAME.upper()}')
self.guest.ansible(playbook_path)

def _enable(self, playbook_filename: str) -> None:
self._run_playbook('enable', playbook_filename)

def _disable(self, playbook_filename: str) -> None:
self._run_playbook('disable', playbook_filename)

def enable(self) -> None:
raise NotImplementedError

def disable(self) -> None:
raise NotImplementedError


class EPEL(ToggleableFeature):
NAME = 'epel'
f"{op.capitalize()} {cls.NAME.upper()} is not supported on this guest.")

def enable(self) -> None:
self._enable('epel-enable.yaml')

def disable(self) -> None:
self._disable('epel-disable.yaml')


_FEATURES: dict[str, type[Feature]] = {
EPEL.NAME: EPEL
}
logger.info(f'{op.capitalize()} {cls.NAME.upper()}')
guest.ansible(playbook_path)


@dataclasses.dataclass
Expand Down Expand Up @@ -126,23 +150,20 @@ def go(
if self.opt('dry'):
return []

# Enable or disable epel
for feature_key in _FEATURES:
value = cast(Optional[str], getattr(self.data, feature_key, None))
for feature_id in _FEATURE_PLUGIN_REGISTRY.iter_plugin_ids():
feature = find_plugin(feature_id)

value = cast(Optional[str], getattr(self.data, feature.NAME, None))
if value is None:
continue

feature = _FEATURES[feature_key](parent=self, guest=guest, logger=logger)
if isinstance(feature, ToggleableFeature):
value = value.lower()
if value == 'enabled':
feature.enable()
elif value == 'disabled':
feature.disable()
else:
raise tmt.utils.GeneralError(f"Unknown feature setting '{value}'.")
value = value.lower()
if value == 'enabled':
feature.enable(guest, logger)
elif value == 'disabled':
feature.disable(guest, logger)
else:
raise tmt.utils.GeneralError(f"Unsupported feature '{feature_key}'.")
raise tmt.utils.GeneralError(f"Unknown feature setting '{value}'.")

return results

Expand Down
25 changes: 25 additions & 0 deletions tmt/steps/prepare/feature/epel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from typing import TYPE_CHECKING, Any

import tmt.log
from tmt.steps.provision import Guest

if TYPE_CHECKING:
pass

from tmt.steps.prepare.feature import Feature, provides_feature


@provides_feature('epel')
class Epel(Feature):
NAME = "epel"

def __init__(self, *args: Any, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)

@classmethod
def enable(cls, guest: Guest, logger: tmt.log.Logger) -> None:
cls._run_playbook('enable', "epel-enable.yaml", guest, logger)

@classmethod
def disable(cls, guest: Guest, logger: tmt.log.Logger) -> None:
cls._run_playbook('disable', "epel-disable.yaml", guest, logger)
Loading