Package pulp :: Package server :: Module crontab
[hide private]
[frames] | no frames]

Source Code for Module pulp.server.crontab

  1  # 
  2  # Copyright 2008, Martin Owens. 
  3  # 
  4  # This program is free software: you can redistribute it and/or modify 
  5  # it under the terms of the GNU General Public License as published by 
  6  # the Free Software Foundation, either version 3 of the License, or 
  7  # (at your option) any later version. 
  8  #  
  9  # This program is distributed in the hope that it will be useful, 
 10  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 11  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 12  # GNU General Public License for more details. 
 13  #  
 14  # You should have received a copy of the GNU General Public License 
 15  # along with this program.  If not, see <http://www.gnu.org/licenses/>. 
 16  # 
 17  # Rewritten from scratch, but based on the code from gnome-schedual by: 
 18  # - Philip Van Hoof <me at pvanhoof dot be> 
 19  # - Gaute Hope <eg at gaute dot vetsj dot com> 
 20  # - Kristof Vansant <de_lupus at pandora dot be> 
 21  # 
 22   
 23  """ 
 24  Example Use: 
 25   
 26  from crontab import CronTab 
 27   
 28  tab = CronTab() 
 29  cron = tab.new(command='/usr/bin/echo') 
 30   
 31  cron.minute().during(5,50).every(5) 
 32  cron.hour().every(4) 
 33   
 34  cron2 = tab.new(command='/foo/bar',comment='SomeID') 
 35  cron2.every_reboot() 
 36   
 37  list = tab.find_command('bar') 
 38  cron3 = list[0] 
 39  cron3.clear() 
 40  cron3.minute().every(1) 
 41   
 42  print unicode(tab.render()) 
 43   
 44  for cron4 in tab.find_command('echo'): 
 45      print cron4 
 46   
 47  for cron5 in tab: 
 48      print cron5 
 49   
 50  tab.remove_all('echo') 
 51   
 52  t.write() 
 53  """ 
 54   
 55  import os, re, sys 
 56  import tempfile 
 57   
 58  __version__ = '0.9.3' 
 59   
 60  CRONCMD = "/usr/bin/crontab" 
 61  ITEMREX = re.compile('^\s*([^@#\s]+)\s+([^@#\s]+)\s+([^@#\s]+)' + 
 62      '\s+([^@#\s]+)\s+([^@#\s]+)\s+([^#\n]*)(\s+#\s*([^\n]*)|$)') 
 63  SPECREX = re.compile('@(\w+)\s([^#\n]*)(\s+#\s*([^\n]*)|$)') 
 64  DEVNULL = ">/dev/null 2>&1" 
 65   
 66  MONTH_ENUM = [ 
 67      'jan', 'feb', 'mar', 'apr', 'may', 
 68      'jun', 'jul', 'aug', 'sep', 'oct', 
 69      'nov', 'dec', 
 70  ] 
 71  WEEK_ENUM  = [ 
 72      'sun', 'mon', 'tue', 'wed', 'thu', 
 73      'fri', 'sat', 'sun', 
 74  ] 
 75   
 76  SPECIALS = { 
 77      "reboot"  : '@reboot', 
 78      "hourly"  : '0 * * * *', 
 79      "daily"   : '0 0 * * *', 
 80      "weekly"  : '0 0 * * 0', 
 81      "monthly" : '0 0 1 * *', 
 82      "yearly"  : '0 0 1 1 *', 
 83      "annually": '0 0 1 1 *', 
 84      "midnight": '0 0 * * *' 
 85  } 
 86   
 87  S_INFO = [ 
 88      { 'name' : 'Minutes',      'max_v' : 59, 'min_v' : 0 }, 
 89      { 'name' : 'Hours',        'max_v' : 23, 'min_v' : 0 }, 
 90      { 'name' : 'Day of Month', 'max_v' : 31, 'min_v' : 1 }, 
 91      { 'name' : 'Month',        'max_v' : 12, 'min_v' : 1, 'enum' : MONTH_ENUM }, 
 92      { 'name' : 'Day of Week',  'max_v' : 7,  'min_v' : 0, 'enum' : WEEK_ENUM }, 
 93  ] 
 94   
95 -class CronTab(object):
96 """ 97 Crontab object which can access any time based cron using the standard. 98 99 user = Set the user of the crontab (defaults to $USER) 100 fake_tab = Don't set to crontab at all, set to testable fake tab variable. 101 """
102 - def __init__(self, user=None, fake_tab=None):
103 self.user = user 104 self.root = ( os.getuid() == 0 ) 105 self.lines = None 106 self.crons = None 107 self.fake = fake_tab 108 self.read()
109
110 - def read(self):
111 """ 112 Read in the crontab from the system into the object, called 113 automatically when listing or using the object. use for refresh. 114 """ 115 self.crons = [] 116 self.lines = [] 117 if self.fake: 118 lines = self.fake.split('\n') 119 else: 120 lines = os.popen(self._read_execute()).readlines() 121 for line in lines: 122 cron = CronItem(line) 123 if cron.is_valid(): 124 self.crons.append(cron) 125 self.lines.append(cron) 126 else: 127 self.lines.append(line.replace('\n',''))
128
129 - def write(self):
130 """Write the crontab to the system. Saves all information.""" 131 # Add to either the crontab or the fake tab. 132 if self.fake != None: 133 self.fake = self.render() 134 return 135 136 filed, path = tempfile.mkstemp() 137 fileh = os.fdopen(filed, 'w') 138 fileh.write(self.render()) 139 fileh.close() 140 # Add the entire crontab back to the user crontab 141 os.system(self._write_execute(path)) 142 os.unlink(path)
143
144 - def render(self):
145 """Render this crontab as it would be in the crontab.""" 146 crons = [] 147 for cron in self.lines: 148 if type(cron) == CronItem and not cron.is_valid(): 149 crons.append("# " + unicode(cron)) 150 sys.stderr.write( 151 "Ignoring invalid crontab line `%s`\n" % str(cron)) 152 continue 153 crons.append(unicode(cron)) 154 result = '\n'.join(crons) 155 156 # jdobies, June 17, 2010: Added length check below from original code 157 # to prevent an error when deleting the last cron entry in the file 158 if len(result) == 0 or result[-1] not in [ '\n', '\r' ]: 159 result += '\n' 160 return result
161
162 - def new(self, command='', comment=''):
163 """ 164 Create a new cron with a command and comment. 165 166 Returns the new CronItem object. 167 """ 168 item = CronItem(command=command, meta=comment) 169 self.crons.append(item) 170 self.lines.append(item) 171 return item
172
173 - def find_command(self, command):
174 """Return a list of crons using a command.""" 175 result = [] 176 for cron in self.crons: 177 if cron.command.match(command): 178 result.append(cron) 179 return result
180
181 - def remove_all(self, command):
182 """Removes all crons using the stated command.""" 183 l_value = self.find_command(command) 184 for c_value in l_value: 185 self.remove(c_value)
186
187 - def remove(self, item):
188 """Remove a selected cron from the crontab.""" 189 self.crons.remove(item) 190 self.lines.remove(item)
191
192 - def _read_execute(self):
193 """Returns the command line for reading a crontab""" 194 return "%s -l%s" % (CRONCMD, self._user_execute())
195
196 - def _write_execute(self, path):
197 """Return the command line for writing a crontab""" 198 return "%s %s%s" % (CRONCMD, path, self._user_execute())
199
200 - def _user_execute(self):
201 """User command switches to append to the read and write commands.""" 202 if self.user: 203 return ' -u %s' % str(self.user) 204 return ''
205
206 - def __iter__(self):
207 return self.crons.__iter__()
208
209 - def __unicode__(self):
210 return self.render()
211 212
213 -class CronItem(object):
214 """ 215 An item which objectifies a single line of a crontab and 216 May be considered to be a cron job object. 217 """
218 - def __init__(self, line=None, command='', meta=''):
219 self.command = CronCommand(unicode(command)) 220 self._meta = meta 221 self.valid = False 222 self.slices = [] 223 self.special = False 224 self.set_slices() 225 if line: 226 self.parse(line)
227
228 - def parse(self, line):
229 """Parse a cron line string and save the info as the objects.""" 230 result = ITEMREX.findall(line) 231 if result: 232 o_value = result[0] 233 self.command = CronCommand(o_value[5]) 234 self._meta = o_value[7] 235 self.set_slices( o_value ) 236 self.valid = True 237 elif line.find('@') < line.find('#') or line.find('#')==-1: 238 result = SPECREX.findall(line) 239 if result and SPECIALS.has_key(result[0][0]): 240 o_value = result[0] 241 self.command = CronCommand(o_value[1]) 242 self._meta = o_value[3] 243 value = SPECIALS[o_value[0]] 244 if value.find('@') != -1: 245 self.special = value 246 else: 247 self.set_slices( value.split(' ') ) 248 self.valid = True
249
250 - def set_slices(self, o_value=None):
251 """Set the values of this slice set""" 252 self.slices = [] 253 for i_value in range(0, 5): 254 if not o_value: 255 o_value = [None, None, None, None, None] 256 self.slices.append( 257 CronSlice(value=o_value[i_value], **S_INFO[i_value]))
258
259 - def is_valid(self):
260 """Return true if this slice set is valid""" 261 return self.valid
262
263 - def render(self):
264 """Render this set slice to a string""" 265 time = '' 266 if not self.special: 267 slices = [] 268 for i in range(0, 5): 269 slices.append(unicode(self.slices[i])) 270 time = ' '.join(slices) 271 if self.special or time in SPECIALS.values(): 272 if self.special: 273 time = self.special 274 else: 275 time = "@%s" % SPECIALS.keys()[SPECIALS.values().index(time)] 276 277 result = "%s %s" % (time, unicode(self.command)) 278 if self.meta(): 279 result += " # " + self.meta() 280 return result
281 282
283 - def meta(self, value=None):
284 """Return or set the meta value to replace the set values""" 285 if value: 286 self._meta = value 287 return self._meta
288
289 - def every_reboot(self):
290 """Set to every reboot instead of a time pattern""" 291 self.special = '@reboot'
292
293 - def clear(self):
294 """Clear the special and set values""" 295 self.special = None 296 for slice_v in self.slices: 297 slice_v.clear()
298
299 - def minute(self):
300 """Return the minute slice""" 301 return self.slices[0]
302
303 - def hour(self):
304 """Return the hour slice""" 305 return self.slices[1]
306
307 - def dom(self):
308 """Return the day-of-the month slice""" 309 return self.slices[2]
310
311 - def month(self):
312 """Return the month slice""" 313 return self.slices[3]
314
315 - def dow(self):
316 """Return the day of the week slice""" 317 return self.slices[4]
318
319 - def __str__(self):
320 return self.__unicode__()
321
322 - def __unicode__(self):
323 return self.render()
324 325
326 -class CronSlice(object):
327 """Cron slice object which shows a time pattern"""
328 - def __init__(self, name, min_v, max_v, enum=None, value=None):
329 self.name = name 330 self.min = min_v 331 self.max = max_v 332 self.enum = enum 333 self.parts = [] 334 self.value(value)
335
336 - def value(self, value=None):
337 """Return the value of the entire slice.""" 338 if value: 339 self.parts = [] 340 for part in value.split(','): 341 if part.find("/") > 0 or part.find("-") > 0 or part == '*': 342 self.parts.append( self.get_range( part ) ) 343 else: 344 if self.enum and part.lower() in self.enum: 345 part = self.enum.index(part.lower()) 346 try: 347 self.parts.append( int(part) ) 348 except: 349 raise ValueError( 350 'Unknown cron time part for %s: %s' % ( 351 self.name, part)) 352 return self.render()
353
354 - def render(self):
355 """Return the slice rendered as a crontab""" 356 result = [] 357 for part in self.parts: 358 result.append(unicode(part)) 359 if not result: 360 return '*' 361 return ','.join(result)
362
363 - def __str__(self):
364 return self.__unicode__()
365
366 - def __unicode__(self):
367 return self.render()
368
369 - def every(self, n_value):
370 """Set the every X units value""" 371 self.parts = [ self.get_range( '*/%d' % int(n_value) ) ]
372
373 - def on(self, *n_value):
374 """Set the on the time value.""" 375 self.parts += n_value
376
377 - def during(self, value_from, value_to):
378 """Set the During value, which sets a range""" 379 range_value = self.get_range( 380 "%s-%s" % (str(value_from), str(value_to))) 381 self.parts.append( range_value ) 382 return range_value
383
384 - def clear(self):
385 """clear the slice ready for new vaues""" 386 self.parts = []
387
388 - def get_range(self, range_value):
389 """Return a cron range for this slice""" 390 return CronRange( self, range_value )
391 392
393 -class CronRange(object):
394 """A range between one value and another for a time range."""
395 - def __init__(self, slice_value, range_value=None):
396 self.value_from = None 397 self.value_to = None 398 self.slice = slice_value 399 self.seq = 1 400 if not range_value: 401 range_value = '*' 402 self.parse(range_value)
403
404 - def parse(self, value):
405 """Parse a ranged value in a cronjob""" 406 if value.find('/') > 0: 407 value, self.seq = value.split('/') 408 if value.find('-') > 0: 409 from_val, to_val = value.split('-') 410 self.value_from = self.clean_value(from_val) 411 self.value_to = self.clean_value(to_val) 412 elif value == '*': 413 self.value_from = self.slice.min 414 self.value_to = self.slice.max 415 else: 416 raise ValueError, 'Unknown cron range value %s' % value
417
418 - def render(self):
419 """Render the ranged value for a cronjob""" 420 value = '*' 421 if self.value_from > self.slice.min or self.value_to < self.slice.max: 422 value = "%d-%d" % (int(self.value_from), int(self.value_to)) 423 if int(self.seq) != 1: 424 value += "/%d" % (int(self.seq)) 425 return value
426
427 - def clean_value(self, value):
428 """Return a cleaned value of the ranged value""" 429 if self.slice.enum and str(value).lower() in self.slice.enum: 430 value = self.slice.enum.index(str(value).lower()) 431 try: 432 value = int(value) 433 if value >= self.slice.min and value <= self.slice.max: 434 return value 435 except ValueError: 436 raise ValueError('Invalid range value %s' % str(value))
437
438 - def every(self, value):
439 """Set the sequence value for this range.""" 440 self.seq = int(value)
441
442 - def __str__(self):
443 return self.__unicode__()
444
445 - def __unicode__(self):
446 return self.render()
447 448
449 -class CronCommand(object):
450 """Reprisent a cron command as an object."""
451 - def __init__(self, line):
452 self._command = line
453
454 - def match(self, command):
455 """Match the command given""" 456 if command in self._command: 457 return True 458 return False
459
460 - def command(self):
461 """Return the command line""" 462 return self._command
463
464 - def __str__(self):
465 """Return a string as a value""" 466 return self.__unicode__()
467
468 - def __unicode__(self):
469 """Return unicode command line value""" 470 return self.command()
471