This PR disallows modification of jobs received from pillar with `schedule` execution module.

 Function `config.merge` is no longer used to obtain schedule data, in favor for explicit merging of opts['schedule'] and opts['pillar']['schedule'], so that job source can be find out.

 This PR reverts some changes made in #36437 and takes different approach to the problem highlighted there.
This commit is contained in:
Vyacheslav Spiridonov 2017-03-15 13:15:28 +03:00
parent 86f7195e0e
commit 14d71918b2
2 changed files with 88 additions and 93 deletions

View file

@ -1822,17 +1822,17 @@ class Minion(MinionBase):
elif func == 'add':
self.schedule.add_job(schedule, persist)
elif func == 'modify':
self.schedule.modify_job(name, schedule, persist, where)
self.schedule.modify_job(name, schedule, persist)
elif func == 'enable':
self.schedule.enable_schedule()
elif func == 'disable':
self.schedule.disable_schedule()
elif func == 'enable_job':
self.schedule.enable_job(name, persist, where)
self.schedule.enable_job(name, persist)
elif func == 'run_job':
self.schedule.run_job(name)
elif func == 'disable_job':
self.schedule.disable_job(name, persist, where)
self.schedule.disable_job(name, persist)
elif func == 'reload':
self.schedule.reload(schedule)
elif func == 'list':

View file

@ -423,15 +423,28 @@ class Schedule(object):
def option(self, opt):
'''
Return the schedule data structure
Return options merged from config and pillar
'''
if 'config.merge' in self.functions:
return self.functions['config.merge'](opt, {}, omit_master=True)
return self.opts.get(opt, {})
def _get_schedule(self,
include_opts=True,
include_pillar=True):
'''
Return the schedule data structure
'''
schedule = {}
if include_pillar and 'schedule' in self.opts['pillar']:
schedule.update(self.opts['pillar']['schedule'])
if include_opts:
schedule.update(self.opts['schedule'])
return schedule
def persist(self):
'''
Persist the modified schedule into <<configdir>>/minion.d/_schedule.conf
Persist the modified schedule into <<configdir>>/<<default_include>>/_schedule.conf
'''
config_dir = self.opts.get('conf_dir', None)
if config_dir is None and 'conf_file' in self.opts:
@ -453,33 +466,27 @@ class Schedule(object):
with salt.utils.fopen(schedule_conf, 'wb+') as fp_:
fp_.write(
salt.utils.to_bytes(
yaml.dump({'schedule': self.option('schedule')})
yaml.dump({'schedule': self._get_schedule(include_pillar=False)})
)
)
except (IOError, OSError):
log.error('Failed to persist the updated schedule',
exc_info_on_loglevel=logging.DEBUG)
def delete_job(self, name, persist=True, where=None):
def delete_job(self, name, persist=True):
'''
Deletes a job from the scheduler.
Deletes a job from the scheduler. Ignore jobs from pillar
'''
if where is None or where != 'pillar':
# ensure job exists, then delete it
schedule = self.option('schedule')
if name in schedule:
del schedule[name]
else:
# If job is in pillar, delete it there too
if 'schedule' in self.opts['pillar']:
if name in self.opts['pillar']['schedule']:
del self.opts['pillar']['schedule'][name]
schedule = self.opts['pillar']['schedule']
log.warning('Pillar schedule deleted. Pillar refresh recommended. Run saltutil.refresh_pillar.')
# ensure job exists, then delete it
if name in self.opts['schedule']:
del self.opts['schedule'][name]
elif name in self._get_schedule(include_opts=False):
log.warning('Cannot delete job {0}, '
'it`s in the pillar!'.format(name))
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': schedule},
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_delete_complete')
# remove from self.intervals
@ -489,28 +496,22 @@ class Schedule(object):
if persist:
self.persist()
def delete_job_prefix(self, name, persist=True, where=None):
def delete_job_prefix(self, name, persist=True):
'''
Deletes a job from the scheduler.
Deletes a job from the scheduler. Ignores jobs from pillar
'''
if where is None or where != 'pillar':
# ensure job exists, then delete it
schedule = self.option('schedule')
for job in list(schedule.keys()):
if job.startswith(name):
del schedule[job]
else:
# If job is in pillar, delete it there too
if 'schedule' in self.opts['pillar']:
for job in list(self.opts['pillar']['schedule'].keys()):
if job.startswith(name):
del self.opts['pillar']['schedule'][job]
schedule = self.opts['pillar']['schedule']
log.warning('Pillar schedule deleted. Pillar refresh recommended. Run saltutil.refresh_pillar.')
# ensure job exists, then delete it
for job in list(self.opts['schedule'].keys()):
if job.startswith(name):
del self.opts['schedule'][job]
for job in self._get_schedule(include_opts=False).keys():
if job.startswith(name):
log.warning('Cannot delete job {0}, '
'it`s in the pillar!'.format(job))
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': schedule},
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_delete_complete')
# remove from self.intervals
@ -543,77 +544,80 @@ class Schedule(object):
new_job = next(six.iterkeys(data))
schedule = self.option('schedule')
if new_job in schedule:
if new_job in self._get_schedule(include_opts=False):
log.warning('Cannot update job {0}, '
'it`s in the pillar!'.format(new_job))
elif new_job in self.opts['schedule']:
log.info('Updating job settings for scheduled '
'job: {0}'.format(new_job))
self.opts['schedule'].update(data)
else:
log.info('Added new job {0} to scheduler'.format(new_job))
schedule.update(data)
self.opts['schedule'].update(data)
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': schedule},
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_add_complete')
if persist:
self.persist()
def enable_job(self, name, persist=True, where=None):
def enable_job(self, name, persist=True):
'''
Enable a job in the scheduler.
Enable a job in the scheduler. Ignores jobs from pillar
'''
if where == 'pillar':
self.opts['pillar']['schedule'][name]['enabled'] = True
schedule = self.opts['pillar']['schedule']
else:
schedule = self.option('schedule')
schedule[name]['enabled'] = True
# ensure job exists, then enable it
if name in self.opts['schedule']:
self.opts['schedule'][name]['enabled'] = True
log.info('Enabling job {0} in scheduler'.format(name))
elif name in self._get_schedule(include_opts=False):
log.warning('Cannot modify job {0}, '
'it`s in the pillar!'.format(name))
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': schedule},
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_enabled_job_complete')
log.info('Enabling job {0} in scheduler'.format(name))
if persist:
self.persist()
def disable_job(self, name, persist=True, where=None):
def disable_job(self, name, persist=True):
'''
Disable a job in the scheduler.
Disable a job in the scheduler. Ignores jobs from pillar
'''
if where == 'pillar':
self.opts['pillar']['schedule'][name]['enabled'] = False
schedule = self.opts['pillar']['schedule']
else:
schedule = self.option('schedule')
schedule[name]['enabled'] = False
# ensure job exists, then disable it
if name in self.opts['schedule']:
self.opts['schedule'][name]['enabled'] = False
log.info('Disabling job {0} in scheduler'.format(name))
elif name in self._get_schedule(include_opts=False):
log.warning('Cannot modify job {0}, '
'it`s in the pillar!'.format(name))
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': schedule},
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_disabled_job_complete')
log.info('Disabling job {0} in scheduler'.format(name))
if persist:
self.persist()
def modify_job(self, name, schedule, persist=True, where=None):
def modify_job(self, name, schedule, persist=True):
'''
Modify a job in the scheduler.
Modify a job in the scheduler. Ignores jobs from pillar
'''
if where == 'pillar':
if name in self.opts['pillar']['schedule']:
self.delete_job(name, persist, where=where)
self.opts['pillar']['schedule'][name] = schedule
else:
if name in self.option('schedule'):
self.delete_job(name, persist, where=where)
self.option('schedule')[name] = schedule
# ensure job exists, then replace it
if name in self.opts['schedule']:
self.delete_job(name, persist)
elif name in self._get_schedule(include_opts=False):
log.warning('Cannot modify job {0}, '
'it`s in the pillar!'.format(name))
return
self.opts['schedule'][name] = schedule
if persist:
self.persist()
@ -622,10 +626,7 @@ class Schedule(object):
'''
Run a schedule job now
'''
schedule = self.option('schedule')
if 'schedule' in self.opts['pillar']:
schedule.update(self.opts['pillar']['schedule'])
data = schedule[name]
data = self._get_schedule().get(name, {})
if 'function' in data:
func = data['function']
@ -669,24 +670,22 @@ class Schedule(object):
'''
Enable the scheduler.
'''
schedule = self.option('schedule')
schedule['enabled'] = True
self.opts['schedule']['enabled'] = True
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': schedule},
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_enabled_complete')
def disable_schedule(self):
'''
Disable the scheduler.
'''
schedule = self.option('schedule')
schedule['enabled'] = False
self.opts['schedule']['enabled'] = False
# Fire the complete event back along with updated list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
evt.fire_event({'complete': True, 'schedule': schedule},
evt.fire_event({'complete': True, 'schedule': self._get_schedule()},
tag='/salt/minion/minion_schedule_disabled_complete')
def reload(self, schedule):
@ -704,16 +703,12 @@ class Schedule(object):
'''
List the current schedule items
'''
schedule = {}
if where == 'pillar':
if 'schedule' in self.opts['pillar']:
schedule.update(self.opts['pillar']['schedule'])
schedule = self._get_schedule(include_opts=False)
elif where == 'opts':
schedule.update(self.option('schedule'))
schedule = self._get_schedule(include_pillar=False)
else:
schedule.update(self.option('schedule'))
if 'schedule' in self.opts['pillar']:
schedule.update(self.opts['pillar']['schedule'])
schedule = self._get_schedule()
# Fire the complete event back along with the list of schedule
evt = salt.utils.event.get_event('minion', opts=self.opts, listen=False)
@ -918,7 +913,7 @@ class Schedule(object):
'''
Evaluate and execute the schedule
'''
schedule = self.option('schedule')
schedule = self._get_schedule()
if not isinstance(schedule, dict):
raise ValueError('Schedule must be of type dict.')
if 'enabled' in schedule and not schedule['enabled']: