Coverage for curator/classdef.py: 95%
94 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-20 21:00 -0600
« prev ^ index » next coverage.py v7.2.7, created at 2023-07-20 21:00 -0600
1"""Other Classes"""
2import logging
3from es_client.exceptions import ConfigurationError as ESclient_ConfigError
4from es_client.helpers.utils import get_yaml
5from curator import IndexList, SnapshotList
6from curator.actions import CLASS_MAP
7from curator.exceptions import ConfigurationError
8from curator.config_utils import password_filter
9from curator.helpers.testers import validate_actions
11# Let me tell you the story of the nearly wasted afternoon and the research that went into this
12# seemingly simple work-around. Actually, no. It's even more wasted time writing that story here.
13# Suffice to say that I couldn't use the CLASS_MAP with class objects to directly map them to class
14# instances. The Wrapper class and the ActionDef.instantiate method do all of the work for me,
15# allowing me to easily and cleanly pass *args and **kwargs to the individual action classes of
16# CLASS_MAP.
18class Wrapper:
19 """Wrapper Class"""
20 def __init__(self, cls):
21 """Instantiate with passed Class (not instance or object)
23 :param cls: A class (not an instance of the class)
24 """
25 #: The class itself (not an instance of it), passed from ``cls``
26 self.class_object = cls
27 #: An instance of :py:attr:`class_object`
28 self.class_instance = None
30 def set_instance(self, *args, **kwargs):
31 """Set up :py:attr:`class_instance` from :py:attr:`class_object`"""
32 self.class_instance = self.class_object(*args, **kwargs)
34 def get_instance(self, *args, **kwargs):
35 """Return the instance with ``*args`` and ``**kwargs``
36 """
37 self.set_instance(*args, **kwargs)
38 return self.class_instance
40class ActionsFile:
41 """Class to parse and verify entire actions file
43 Individual actions are :py:class:`~.curator.classdef.ActionDef` objects
44 """
45 def __init__(self, action_file):
46 self.logger = logging.getLogger(__name__)
47 #: The full, validated configuration from ``action_file``.
48 self.fullconfig = self.get_validated(action_file)
49 self.logger.debug('Action Configuration: %s', password_filter(self.fullconfig))
50 #: A dict of all actions in the provided configuration. Each original key name is preserved
51 #: and the value is now an :py:class:`~.curator.classdef.ActionDef`, rather than a dict.
52 self.actions = None
53 self.set_actions(self.fullconfig['actions'])
55 def get_validated(self, action_file):
56 """
57 :param action_file: The path to a valid YAML action configuration file
58 :type action_file: str
60 :returns: The result from passing ``action_file`` to
61 :py:func:`~.curator.helpers.testers.validate_actions`
62 """
63 try:
64 return validate_actions(get_yaml(action_file))
65 except (ESclient_ConfigError, UnboundLocalError) as err:
66 self.logger.critical('Configuration Error: %s', err)
67 raise ConfigurationError from err
69 def parse_actions(self, all_actions):
70 """Parse the individual actions found in ``all_actions['actions']``
72 :param all_actions: All actions, each its own dictionary behind a numeric key. Making the
73 keys numeric guarantees that if they are sorted, they will always be executed in order.
75 :type all_actions: dict
77 :returns:
78 :rtype: list of :py:class:`~.curator.classdef.ActionDef`
79 """
80 acts = {}
81 for idx in all_actions.keys():
82 acts[idx] = ActionDef(all_actions[idx])
83 return acts
85 def set_actions(self, all_actions):
86 """Set the actions via :py:meth:`~.curator.classdef.ActionsFile.parse_actions`
88 :param all_actions: All actions, each its own dictionary behind a numeric key. Making the
89 keys numeric guarantees that if they are sorted, they will always be executed in order.
90 :type all_actions: dict
91 :rtype: None
92 """
93 self.actions = self.parse_actions(all_actions)
95# In this case, I just don't care that pylint thinks I'm overdoing it with attributes
96# pylint: disable=too-many-instance-attributes
97class ActionDef:
98 """Individual Action Definition Class
100 Instances of this class represent an individual action from an action file.
101 """
102 def __init__(self, action_dict):
103 #: The whole action dictionary
104 self.action_dict = action_dict
105 #: The action name
106 self.action = None
107 #: The action's class (Alias, Allocation, etc.)
108 self.action_cls = None
109 #: Only when action is alias will this be a :py:class:`~.curator.IndexList`
110 self.alias_adds = None
111 #: Only when action is alias will this be a :py:class:`~.curator.IndexList`
112 self.alias_removes = None
113 #: The list class, either :py:class:`~.curator.IndexList` or
114 #: :py:class:`~.curator.SnapshotList`. Default is :py:class:`~.curator.IndexList`
115 self.list_obj = Wrapper(IndexList)
116 #: The action ``description``
117 self.description = None
118 #: The action ``options`` :py:class:`dict`
119 self.options = {}
120 #: The action ``filters`` :py:class:`list`
121 self.filters = None
122 #: The action option ``disable_action``
123 self.disabled = None
124 #: The action option ``continue_if_exception``
125 self.cif = None
126 #: The action option ``timeout_override``
127 self.timeout_override = None
128 #: The action option ``ignore_empty_list``
129 self.iel = None
130 #: The action option ``allow_ilm_indices``
131 self.allow_ilm = None
132 self.set_root_attrs()
133 self.set_option_attrs()
134 self.log_the_options()
135 self.get_action_class()
137 def instantiate(self, attribute, *args, **kwargs):
138 """
139 Convert ``attribute`` from being a :py:class:`~.curator.classdef.Wrapper` of a Class to an
140 instantiated object of that Class.
142 This is madness or genius. You decide. This entire method plus the
143 :py:class:`~.curator.classdef.Wrapper` class came about because I couldn't cleanly
144 instantiate a class variable into a class object. It works, and that's good enough for me.
146 :param attribute: The `name` of an attribute that references a Wrapper class instance
147 :type attribute: str
148 """
149 try:
150 wrapper = getattr(self, attribute)
151 except AttributeError as exc:
152 raise AttributeError(f'Bad Attribute: {attribute}. Exception: {exc}') from exc
153 setattr(self, attribute, self.get_obj_instance(wrapper, *args, **kwargs))
155 def get_obj_instance(self, wrapper, *args, **kwargs):
156 """Get the class instance wrapper identified by ``wrapper``
157 Pass all other args and kwargs to the :py:meth:`~.curator.classdef.Wrapper.get_instance`
158 method.
160 :returns: An instance of the class that :py:class:`~.curator.classdef.Wrapper` is wrapping
161 """
162 if not isinstance(wrapper, Wrapper):
163 raise ConfigurationError(
164 f'{__name__} was passed wrapper which was of type {type(wrapper)}')
165 return wrapper.get_instance(*args, **kwargs)
167 def set_alias_extras(self):
168 """Populate the :py:attr:`alias_adds` and :py:attr:`alias_removes` attributes"""
169 self.alias_adds = Wrapper(IndexList)
170 self.alias_removes = Wrapper(IndexList)
172 def get_action_class(self):
173 """Get the action class from :py:const:`~.curator.actions.CLASS_MAP`
175 Do extra setup when action is ``alias``
177 Set :py:attr:`list_obj` to :py:class:`~.curator.SnapshotList` when
178 :py:attr:`~.curator.classdef.ActionDef.action` is ``delete_snapshots`` or ``restore``
179 """
181 self.action_cls = Wrapper(CLASS_MAP[self.action])
182 if self.action == 'alias':
183 self.set_alias_extras()
184 if self.action in ['delete_snapshots', 'restore']:
185 self.list_obj = Wrapper(SnapshotList)
187 def set_option_attrs(self):
188 """
189 Iteratively get the keys and values from :py:attr:`~.curator.classdef.ActionDef.options`
190 and set the attributes
191 """
192 attmap = {
193 'disable_action': 'disabled',
194 'continue_if_exception': 'cif',
195 'ignore_empty_list': 'iel',
196 'allow_ilm_indices': 'allow_ilm',
197 'timeout_override' : 'timeout_override'
198 }
199 for key in self.action_dict['options']:
200 if key in attmap:
201 setattr(self, attmap[key], self.action_dict['options'][key])
202 else:
203 self.options[key] = self.action_dict['options'][key]
205 def set_root_attrs(self):
206 """
207 Iteratively get the keys and values from
208 :py:attr:`~.curator.classdef.ActionDef.action_dict` and set the attributes
209 """
210 for key, value in self.action_dict.items():
211 # Gonna grab options in get_option_attrs()
212 if key == 'options':
213 continue
214 if value is not None:
215 setattr(self, key, value)
217 def log_the_options(self):
218 """Log options at initialization time"""
219 logger = logging.getLogger('curator.cli.ActionDef')
220 msg = (
221 f'For action {self.action}: disable_action={self.disabled}'
222 f'continue_if_exception={self.cif}, timeout_override={self.timeout_override}'
223 f'ignore_empty_list={self.iel}, allow_ilm_indices={self.allow_ilm}'
224 )
225 logger.debug(msg)
226 if self.allow_ilm:
227 logger.warning('Permitting operation on indices with an ILM policy')