augeas_configurator.py
1 """Class of Augeas Configurators.""" 2 import logging 3 4 import augeas 5 6 from letsencrypt import errors 7 from letsencrypt import reverter 8 from letsencrypt.plugins import common 9 10 logger = logging.getLogger(__name__) 11 12 13 class AugeasConfigurator(common.Plugin): 14 """Base Augeas Configurator class. 15 16 :ivar config: Configuration. 17 :type config: :class:`~letsencrypt.interfaces.IConfig` 18 19 :ivar aug: Augeas object 20 :type aug: :class:`augeas.Augeas` 21 22 :ivar str save_notes: Human-readable configuration change notes 23 :ivar reverter: saves and reverts checkpoints 24 :type reverter: :class:`letsencrypt.reverter.Reverter` 25 26 """ 27 def __init__(self, *args, **kwargs): 28 super(AugeasConfigurator, self).__init__(*args, **kwargs) 29 30 # Set Augeas flags to not save backup (we do it ourselves) 31 # Set Augeas to not load anything by default 32 my_flags = augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD 33 self.aug = augeas.Augeas(flags=my_flags) 34 self.save_notes = "" 35 36 # See if any temporary changes need to be recovered 37 # This needs to occur before VirtualHost objects are setup... 38 # because this will change the underlying configuration and potential 39 # vhosts 40 self.reverter = reverter.Reverter(self.config) 41 self.recovery_routine() 42 43 def check_parsing_errors(self, lens): 44 """Verify Augeas can parse all of the lens files. 45 46 :param str lens: lens to check for errors 47 48 :raises .errors.PluginError: If there has been an error in parsing with 49 the specified lens. 50 51 """ 52 error_files = self.aug.match("/augeas//error") 53 54 for path in error_files: 55 # Check to see if it was an error resulting from the use of 56 # the httpd lens 57 lens_path = self.aug.get(path + "/lens") 58 # As aug.get may return null 59 if lens_path and lens in lens_path: 60 msg = ( 61 "There has been an error in parsing the file (%s): %s", 62 # Strip off /augeas/files and /error 63 path[13:len(path) - 6], self.aug.get(path + "/message")) 64 raise errors.PluginError(msg) 65 66 # TODO: Cleanup this function 67 def save(self, title=None, temporary=False): 68 """Saves all changes to the configuration files. 69 70 This function first checks for save errors, if none are found, 71 all configuration changes made will be saved. According to the 72 function parameters. 73 74 :param str title: The title of the save. If a title is given, the 75 configuration will be saved as a new checkpoint and put in a 76 timestamped directory. 77 78 :param bool temporary: Indicates whether the changes made will 79 be quickly reversed in the future (ie. challenges) 80 81 :raises .errors.PluginError: If there was an error in Augeas, in an 82 attempt to save the configuration, or an error creating a checkpoint 83 84 """ 85 save_state = self.aug.get("/augeas/save") 86 self.aug.set("/augeas/save", "noop") 87 # Existing Errors 88 ex_errs = self.aug.match("/augeas//error") 89 try: 90 # This is a noop save 91 self.aug.save() 92 except (RuntimeError, IOError): 93 self._log_save_errors(ex_errs) 94 # Erase Save Notes 95 self.save_notes = "" 96 raise errors.PluginError( 97 "Error saving files, check logs for more info.") 98 99 # Retrieve list of modified files 100 # Note: Noop saves can cause the file to be listed twice, I used a 101 # set to remove this possibility. This is a known augeas 0.10 error. 102 save_paths = self.aug.match("/augeas/events/saved") 103 104 # If the augeas tree didn't change, no files were saved and a backup 105 # should not be created 106 if save_paths: 107 save_files = set() 108 for path in save_paths: 109 save_files.add(self.aug.get(path)[6:]) 110 111 try: 112 # Create Checkpoint 113 if temporary: 114 self.reverter.add_to_temp_checkpoint( 115 save_files, self.save_notes) 116 else: 117 self.reverter.add_to_checkpoint(save_files, self.save_notes) 118 except errors.ReverterError as err: 119 raise errors.PluginError(str(err)) 120 121 if title and not temporary: 122 try: 123 self.reverter.finalize_checkpoint(title) 124 except errors.ReverterError as err: 125 raise errors.PluginError(str(err)) 126 127 self.aug.set("/augeas/save", save_state) 128 self.save_notes = "" 129 self.aug.save() 130 131 def _log_save_errors(self, ex_errs): 132 """Log errors due to bad Augeas save. 133 134 :param list ex_errs: Existing errors before save 135 136 """ 137 # Check for the root of save problems 138 new_errs = self.aug.match("/augeas//error") 139 # logger.error("During Save - %s", mod_conf) 140 logger.error("Unable to save files: %s. Attempted Save Notes: %s", 141 ", ".join(err[13:len(err) - 6] for err in new_errs 142 # Only new errors caused by recent save 143 if err not in ex_errs), self.save_notes) 144 145 # Wrapper functions for Reverter class 146 def recovery_routine(self): 147 """Revert all previously modified files. 148 149 Reverts all modified files that have not been saved as a checkpoint 150 151 :raises .errors.PluginError: If unable to recover the configuration 152 153 """ 154 try: 155 self.reverter.recovery_routine() 156 except errors.ReverterError as err: 157 raise errors.PluginError(str(err)) 158 # Need to reload configuration after these changes take effect 159 self.aug.load() 160 161 def revert_challenge_config(self): 162 """Used to cleanup challenge configurations. 163 164 :raises .errors.PluginError: If unable to revert the challenge config. 165 166 """ 167 try: 168 self.reverter.revert_temporary_config() 169 except errors.ReverterError as err: 170 raise errors.PluginError(str(err)) 171 self.aug.load() 172 173 def rollback_checkpoints(self, rollback=1): 174 """Rollback saved checkpoints. 175 176 :param int rollback: Number of checkpoints to revert 177 178 :raises .errors.PluginError: If there is a problem with the input or 179 the function is unable to correctly revert the configuration 180 181 """ 182 try: 183 self.reverter.rollback_checkpoints(rollback) 184 except errors.ReverterError as err: 185 raise errors.PluginError(str(err)) 186 self.aug.load() 187 188 def view_config_changes(self): 189 """Show all of the configuration changes that have taken place. 190 191 :raises .errors.PluginError: If there is a problem while processing 192 the checkpoints directories. 193 194 """ 195 try: 196 self.reverter.view_config_changes() 197 except errors.ReverterError as err: 198 raise errors.PluginError(str(err))