diff options
Diffstat (limited to '')
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | action_plugins/borg_validate.py | 81 | ||||
-rw-r--r-- | tasks/main.yml | 2 |
3 files changed, 85 insertions, 2 deletions
@@ -69,9 +69,9 @@ On servers, the role creates a `borg` user with `/srv/borg` as a home directory On clients, the role creates a borg ssh key for the root user and generates a backup script in `/usr/local/bin/adyxax_backup.sh`. The role also adds a cron job that will run the backup script each nigh. Lastly it makes sure the client's borg repository is properly initialised on the server. -### Action plugin +### Action plugins -There is an action plugin that parses the borg_server entries from all host_vars and set a borg fact for both machines to be backed up and for machines that are specified as backup targets (so that they do not require any manual configuration or variables). This action plugin also enforces the job rules to make sure those are valid and without ambiguities. +There is an action plugin that validates host_vars types and values for each borg role variable, and another that parses the borg_server entries from all host_vars and set a borg fact for both machines to be backed up and for machines that are specified as backup targets (so that they do not require any manual configuration or variables). This action plugin also enforces the job rules to make sure those are valid and without ambiguities. ### Ansible fact diff --git a/action_plugins/borg_validate.py b/action_plugins/borg_validate.py new file mode 100644 index 0000000..16d97ec --- /dev/null +++ b/action_plugins/borg_validate.py @@ -0,0 +1,81 @@ +from ansible.plugins.action import ActionBase + +import re + +class ActionModule(ActionBase): + def run(self, tmp=None, task_vars=None): + if task_vars is None: + task_vars = dict() + result = super(ActionModule, self).run(tmp, task_vars) + result['changed'] = False + result['failed'] = False + + error_msgs = [] + + ### host_vars validations ############################################ + if 'borg_server' in task_vars: + # a borg server must exist + if not isinstance(task_vars['borg_server'], str): + error_msgs.append(f"The borg_server variable must be of type string for host {task_vars['ansible_host']}") + elif not task_vars['borg_server'] in task_vars['hostvars'].keys(): + error_msgs.append(f"The borg_server {task_vars['borg_server']} configured for host {task_vars['ansible_host']} does not exist") + else: + # a borg client needs a list of jobs + if 'borg_jobs' not in task_vars: + error_msgs.append(f"No borg_jobs defined for host {task_vars['ansible_host']} while it has a borg_server configured") + elif not isinstance(task_vars['borg_jobs'], list): + error_msgs.append(f"The borg_jobs variable must be of type list for host {task_vars['ansible_host']}") + else: + for job in task_vars['borg_jobs']: + # a job is a dict with specific keys + if not isinstance(job, dict): + error_msgs.append(f"The borg_jobs list elements must be of type dict for host {task_vars['ansible_host']}") + continue + for key in job.keys(): + if not key in ['name', 'path', 'command_to_pipe', 'pre_command', 'post_command', 'exclude']: + error_msgs.append(f"Invalid key {key} in a job for host {task_vars['ansible_host']}") + # a job name is a mandatory string + if not 'name' in job.keys(): + error_msgs.append(f"Invalid job for host {task_vars['ansible_host']} : no name defined") + elif not isinstance(job['name'], str): + error_msgs.append(f"Invalid job name for host {task_vars['ansible_host']} : name must be of type string") + elif not re.match(r'^[a-zA-Z0-9_]+$', job['name']): + error_msgs.append(f"Invalid job name for host {task_vars['ansible_host']} : name must match ^[a-zA-Z0-9_]+$") + # path and command_to_pipe are mutually exclusive + if 'path' in job.keys() and 'command_to_pipe' in job.keys(): + error_msgs.append(f"Invalid job for host {task_vars['ansible_host']} : it needs either a path or a command_to_pipe, not both") + elif 'path' not in job.keys() and 'command_to_pipe' not in job.keys(): + error_msgs.append(f"Invalid job for host {task_vars['ansible_host']} : it needs either a path or a command_to_pipe") + elif 'path' in job.keys(): + if not isinstance(job['path'], str): + error_msgs.append(f"Invalid job for host {task_vars['ansible_host']} : path must be of type string") + elif not isinstance(job['command_to_pipe'], str): + error_msgs.append(f"Invalid job for host {task_vars['ansible_host']} : command_to_pipe must be of type string") + # a pre_command is an optional string + if 'pre_command' in job.keys(): + if not isinstance(job['pre_command'], str): + error_msgs.append(f"Invalid job for host {task_vars['ansible_host']} : pre_command must be of type string") + # a post_command is an optional string + if 'post_command' in job.keys(): + if not isinstance(job['post_command'], str): + error_msgs.append(f"Invalid job for host {task_vars['ansible_host']} : post_command must be of type string") + # exclude is an optional list of paths + if 'exclude' in job.keys(): + if not isinstance(job['exclude'], list): + error_msgs.append(f"Invalid job for host {task_vars['ansible_host']} : exclude must be of type list") + else: + for path in job['exclude']: + if not isinstance(path, str): + error_msgs.append(f"Invalid job for host {task_vars['ansible_host']} : exclude must be a list of strings") + # a borg client needs prune arguments + if not 'borg_prune_arguments' in task_vars: + error_msgs.append(f"No borg_jobs defined for host {task_vars['ansible_host']} while it has a borg_server configured") + elif not isinstance(task_vars['borg_prune_arguments'], str): + error_msgs.append(f"The borg_prune_arguments variable must be of type string for host {task_vars['ansible_host']}") + + ### Results compilation ############################################## + if error_msgs != []: + result['msg'] = ' ; '.join(error_msgs) + result['failed'] = True + + return result diff --git a/tasks/main.yml b/tasks/main.yml index daa0836..8832443 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,4 +1,6 @@ --- +- action: borg_validate + - action: borg_init - import_tasks: common.yml |