Server IP : 85.214.239.14 / Your IP : 18.119.142.107 Web Server : Apache/2.4.62 (Debian) System : Linux h2886529.stratoserver.net 4.9.0 #1 SMP Tue Jan 9 19:45:01 MSK 2024 x86_64 User : www-data ( 33) PHP Version : 7.4.18 Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare, MySQL : OFF | cURL : OFF | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /lib/python3/dist-packages/ansible_collections/awx/awx/plugins/lookup/ |
Upload File : |
# (c) 2020 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type DOCUMENTATION = """ name: schedule_rruleset author: John Westcott IV (@john-westcott-iv) short_description: Generate an rruleset string requirements: - pytz - python-dateutil >= 2.7.0 description: - Returns a string based on criteria which represents an rrule options: _terms: description: - The start date of the ruleset - Used for all frequencies - Format should be YYYY-MM-DD [HH:MM:SS] required: True type: str timezone: description: - The timezone to use for this rule - Used for all frequencies - Format should be as US/Eastern - Defaults to America/New_York type: str rules: description: - Array of rules in the rruleset type: list elements: dict required: True suboptions: frequency: description: - The frequency of the schedule - none - Run this schedule once - minute - Run this schedule every x minutes - hour - Run this schedule every x hours - day - Run this schedule every x days - week - Run this schedule weekly - month - Run this schedule monthly required: True choices: ['none', 'minute', 'hour', 'day', 'week', 'month'] interval: description: - The repetition in months, weeks, days hours or minutes - Used for all types except none type: int end_on: description: - How to end this schedule - If this is not defined, this schedule will never end - If this is a positive integer, this schedule will end after this number of occurrences - If this is a date in the format YYYY-MM-DD [HH:MM:SS], this schedule ends after this date - Used for all types except none type: str bysetpos: description: - Specify an occurrence number, corresponding to the nth occurrence of the rule inside the frequency period. - A comma-separated list of positions (first, second, third, forth or last) type: string bymonth: description: - The months this schedule will run on - A comma-separated list which can contain values 0-12 type: string bymonthday: description: - The day of the month this schedule will run on - A comma-separated list which can contain values 0-31 type: string byyearday: description: - The year day numbers to run this schedule on - A comma-separated list which can contain values 0-366 type: string byweekno: description: - The week numbers to run this schedule on - A comma-separated list which can contain values as described in ISO8601 type: string byweekday: description: - The days to run this schedule on - A comma-separated list which can contain values sunday, monday, tuesday, wednesday, thursday, friday type: string byhour: description: - The hours to run this schedule on - A comma-separated list which can contain values 0-23 type: string byminute: description: - The minutes to run this schedule on - A comma-separated list which can contain values 0-59 type: string include: description: - If this rule should be included (RRULE) or excluded (EXRULE) type: bool default: True """ EXAMPLES = """ - name: Create a ruleset for everyday except Sundays set_fact: complex_rule: "{{ query(awx.awx.schedule_rruleset, '2022-04-30 10:30:45', rules=rrules, timezone='UTC' ) }}" vars: rrules: - frequency: 'day' interval: 1 - frequency: 'day' interval: 1 byweekday: 'sunday' include: False """ RETURN = """ _raw: description: - String in the rrule format type: string """ import re from ansible.module_utils.six import raise_from from ansible.plugins.lookup import LookupBase from ansible.errors import AnsibleError from datetime import datetime try: import pytz from dateutil import rrule except ImportError as imp_exc: LIBRARY_IMPORT_ERROR = imp_exc else: LIBRARY_IMPORT_ERROR = None class LookupModule(LookupBase): # plugin constructor def __init__(self, *args, **kwargs): if LIBRARY_IMPORT_ERROR: raise_from(AnsibleError('{0}'.format(LIBRARY_IMPORT_ERROR)), LIBRARY_IMPORT_ERROR) super().__init__(*args, **kwargs) self.frequencies = { 'none': rrule.DAILY, 'minute': rrule.MINUTELY, 'hour': rrule.HOURLY, 'day': rrule.DAILY, 'week': rrule.WEEKLY, 'month': rrule.MONTHLY, } self.weekdays = { 'monday': rrule.MO, 'tuesday': rrule.TU, 'wednesday': rrule.WE, 'thursday': rrule.TH, 'friday': rrule.FR, 'saturday': rrule.SA, 'sunday': rrule.SU, } self.set_positions = { 'first': 1, 'second': 2, 'third': 3, 'fourth': 4, 'last': -1, } @staticmethod def parse_date_time(date_string): try: return datetime.strptime(date_string, '%Y-%m-%d %H:%M:%S') except ValueError: return datetime.strptime(date_string, '%Y-%m-%d') def process_integer(self, field_name, rule, min_value, max_value, rule_number): # We are going to tolerate multiple types of input here: # something: 1 - A single integer # something: "1" - A single str # something: "1,2,3" - A comma separated string of ints # something: "1, 2,3" - A comma separated string of ints (with spaces) # something: ["1", "2", "3"] - A list of strings # something: [1,2,3] - A list of ints return_values = [] # If they give us a single int, lets make it a list of ints if isinstance(rule[field_name], int): rule[field_name] = [rule[field_name]] # If its not a list, we need to split it into a list if not isinstance(rule[field_name], list): rule[field_name] = rule[field_name].split(',') for value in rule[field_name]: # If they have a list of strs we want to strip the str incase its space delineated if isinstance(value, str): value = value.strip() # If value happens to be an int (from a list of ints) we need to coerce it into a str for the re.match if not re.match(r"^\d+$", str(value)) or int(value) < min_value or int(value) > max_value: raise AnsibleError('In rule {0} {1} must be between {2} and {3}'.format(rule_number, field_name, min_value, max_value)) return_values.append(int(value)) return return_values def process_list(self, field_name, rule, valid_list, rule_number): return_values = [] # If its not a list, we need to split it into a list if not isinstance(rule[field_name], list): rule[field_name] = rule[field_name].split(',') for value in rule[field_name]: value = value.strip() if value not in valid_list: raise AnsibleError('In rule {0} {1} must only contain values in {2}'.format(rule_number, field_name, ', '.join(valid_list.keys()))) return_values.append(valid_list[value]) return return_values def run(self, terms, variables=None, **kwargs): if len(terms) != 1: raise AnsibleError('You may only pass one schedule type in at a time') # Validate the start date try: start_date = LookupModule.parse_date_time(terms[0]) except Exception as e: raise_from(AnsibleError('The start date must be in the format YYYY-MM-DD [HH:MM:SS]'), e) if not kwargs.get('rules', None): raise AnsibleError('You must include rules to be in the ruleset via the rules parameter') # All frequencies can use a timezone but rrule can't support the format that AWX uses. # So we will do a string manip here if we need to timezone = 'America/New_York' if 'timezone' in kwargs: if kwargs['timezone'] not in pytz.all_timezones: raise AnsibleError('Timezone parameter is not valid') timezone = kwargs['timezone'] rules = [] got_at_least_one_rule = False for rule_index in range(0, len(kwargs['rules'])): rule = kwargs['rules'][rule_index] rule_number = rule_index + 1 valid_options = [ "frequency", "interval", "end_on", "bysetpos", "bymonth", "bymonthday", "byyearday", "byweekno", "byweekday", "byhour", "byminute", "include", ] invalid_options = list(set(rule.keys()) - set(valid_options)) if invalid_options: raise AnsibleError('Rule {0} has invalid options: {1}'.format(rule_number, ', '.join(invalid_options))) frequency = rule.get('frequency', None) if not frequency: raise AnsibleError("Rule {0} is missing a frequency".format(rule_number)) if frequency not in self.frequencies: raise AnsibleError('Frequency of rule {0} is invalid {1}'.format(rule_number, frequency)) rrule_kwargs = { 'freq': self.frequencies[frequency], 'interval': rule.get('interval', 1), 'dtstart': start_date, } # If we are a none frequency we don't need anything else if frequency == 'none': rrule_kwargs['count'] = 1 else: # All non-none frequencies can have an end_on option if 'end_on' in rule: end_on = rule['end_on'] if re.match(r'^\d+$', end_on): rrule_kwargs['count'] = end_on else: try: rrule_kwargs['until'] = LookupModule.parse_date_time(end_on) except Exception as e: raise_from( AnsibleError('In rule {0} end_on must either be an integer or in the format YYYY-MM-DD [HH:MM:SS]'.format(rule_number)), e ) if 'bysetpos' in rule: rrule_kwargs['bysetpos'] = self.process_list('bysetpos', rule, self.set_positions, rule_number) if 'bymonth' in rule: rrule_kwargs['bymonth'] = self.process_integer('bymonth', rule, 1, 12, rule_number) if 'bymonthday' in rule: rrule_kwargs['bymonthday'] = self.process_integer('bymonthday', rule, 1, 31, rule_number) if 'byyearday' in rule: rrule_kwargs['byyearday'] = self.process_integer('byyearday', rule, 1, 366, rule_number) # 366 for leap years if 'byweekno' in rule: rrule_kwargs['byweekno'] = self.process_integer('byweekno', rule, 1, 52, rule_number) if 'byweekday' in rule: rrule_kwargs['byweekday'] = self.process_list('byweekday', rule, self.weekdays, rule_number) if 'byhour' in rule: rrule_kwargs['byhour'] = self.process_integer('byhour', rule, 0, 23, rule_number) if 'byminute' in rule: rrule_kwargs['byminute'] = self.process_integer('byminute', rule, 0, 59, rule_number) try: generated_rule = str(rrule.rrule(**rrule_kwargs)) except Exception as e: raise_from(AnsibleError('Failed to parse rrule for rule {0} {1}: {2}'.format(rule_number, str(rrule_kwargs), e)), e) # AWX requires an interval. rrule will not add interval if it's set to 1 if rule.get('interval', 1) == 1: generated_rule = "{0};INTERVAL=1".format(generated_rule) if rule_index == 0: # rrule puts a \n in the rule instead of a space and can't handle timezones generated_rule = generated_rule.replace('\n', ' ').replace('DTSTART:', 'DTSTART;TZID={0}:'.format(timezone)) else: # Only the first rule needs the dtstart in a ruleset so remaining rules we can split at \n generated_rule = generated_rule.split('\n')[1] # If we are an exclude rule we need to flip from an rrule to an ex rule if not rule.get('include', True): generated_rule = generated_rule.replace('RRULE', 'EXRULE') else: got_at_least_one_rule = True rules.append(generated_rule) if not got_at_least_one_rule: raise AnsibleError("A ruleset must contain at least one RRULE") rruleset_str = ' '.join(rules) # For a sanity check lets make sure our rule can parse. Not sure how we can test this though try: rules = rrule.rrulestr(rruleset_str) except Exception as e: raise_from(AnsibleError("Failed to parse generated rule set via rruleset {0}".format(e)), e) # return self.get_rrule(frequency, kwargs) return rruleset_str