Package lamson :: Module commands
[hide private]
[frames] | no frames]

Source Code for Module lamson.commands

  1  """ 
  2  Implements the Lamson command line tool's commands, which are run 
  3  by the lamson.args module dynamically.  Each command has it's 
  4  actual user displayed command line documentation as the __doc__ 
  5  string. 
  6   
  7  You will notice that all of the command functions in this module 
  8  end in _command.  This is not required by the lamson.args module 
  9  but it is the default.  You could easily use any other suffix, or 
 10  none at all. 
 11   
 12  This is done to disambiguate the command that it implements 
 13  so that your command line tools do not clash with Python's 
 14  reserved words and built-ins.  With this design you can have a 
 15  list_command without clashing with list(). 
 16   
 17  You will also notice that commands which take trailing positional 
 18  arguments give a TRAILING=[] or TRAILING=None (if it's required). 
 19  This is done instead of *args because we need to use None to indicate 
 20  that this command requires positional arguments.  TRAILING=[] is  
 21  like saying they are optional (but expected), and TRAILING=None is 
 22  like saying they are required.  You can't (afaik) do TRAILING=None 
 23  with *args. 
 24   
 25  See lamson.args for more details. 
 26  """ 
 27   
 28  from lamson import server, args, utils, mail, routing, queue, encoding 
 29  from pkg_resources import resource_stream 
 30  from zipfile import ZipFile 
 31  import glob 
 32  import lamson 
 33  import os 
 34  import signal 
 35  import sys 
 36  import time 
 37  import mailbox 
 38  import email 
 39   
40 -def log_command(port=8825, host='127.0.0.1', chroot=False, 41 chdir=".", uid=False, gid=False, umask=False, pid="./run/log.pid", 42 FORCE=False):
43 """ 44 Runs a logging only server on the given hosts and port. It logs 45 each message it receives and also stores it to the run/queue 46 so that you can make sure it was received in testing. 47 48 lamson log -port 8825 -host 127.0.0.1 \\ 49 -pid ./run/log.pid -chroot False \\ 50 -chdir "." -umask False -uid False -gid False \\ 51 -FORCE False 52 53 If you specify a uid/gid then this means you want to first change to 54 root, set everything up, and then drop to that UID/GID combination. 55 This is typically so you can bind to port 25 and then become "safe" 56 to continue operating as a non-root user. 57 58 If you give one or the other, this it will just change to that 59 uid or gid without doing the priv drop operation. 60 """ 61 loader = lambda: utils.make_fake_settings(host, port) 62 utils.start_server(pid, FORCE, chroot, chdir, uid, gid, umask, loader)
63 64
65 -def send_command(port=8825, host='127.0.0.1', username=False, password=False, 66 ssl=False, starttls=False, debug=1, sender=None, to=None, 67 subject=None, body=None, attach=False):
68 """ 69 Sends an email to someone as a test message. 70 See the sendmail command for a sendmail replacement. 71 72 lamson send -port 8825 -host 127.0.0.1 -debug 1 \\ 73 -sender EMAIL -to EMAIL -subject STR -body STR -attach False' 74 75 There is also a username, password, and starttls option for those 76 who need it. 77 """ 78 message = mail.MailResponse(From=sender, 79 To=to, 80 Subject=subject, 81 Body=body) 82 if attach: 83 message.attach(attach) 84 85 if username == False: 86 username = None 87 if password == False: 88 password = None 89 90 relay = server.Relay(host, port=port, username=username, password=password, 91 ssl=ssl, starttls=starttls, debug=debug) 92 relay.deliver(message)
93 94
95 -def sendmail_command(port=8825, host='127.0.0.1', debug=0, TRAILING=None):
96 """ 97 Used as a testing sendmail replacement for use in programs 98 like mutt as an MTA. It reads the email to send on the stdin 99 and then delivers it based on the port and host settings. 100 101 lamson sendmail -port 8825 -host 127.0.0.1 -debug 0 -- [recipients] 102 """ 103 relay = server.Relay(host, port=port, 104 debug=debug) 105 data = sys.stdin.read() 106 msg = mail.MailRequest(None, TRAILING, None, data) 107 relay.deliver(msg)
108 109 110 111
112 -def start_command(pid='./run/smtp.pid', FORCE=False, chroot=False, chdir=".", 113 boot="config.boot", uid=False, gid=False, umask=False):
114 """ 115 Runs a lamson server out of the current directory: 116 117 lamson start -pid ./run/smtp.pid -FORCE False -chroot False -chdir "." \\ 118 -umask False -uid False -gid False -boot config.boot 119 """ 120 loader = lambda: utils.import_settings(True, from_dir=os.getcwd(), boot_module=boot) 121 utils.start_server(pid, FORCE, chroot, chdir, uid, gid, umask, loader)
122 123
124 -def stop_command(pid='./run/smtp.pid', KILL=False, ALL=False):
125 """ 126 Stops a running lamson server. Give -KILL True to have it 127 stopped violently. The PID file is removed after the 128 signal is sent. Give -ALL the name of a run directory and 129 it will stop all pid files it finds there. 130 131 lamson stop -pid ./run/smtp.pid -KILL False -ALL False 132 """ 133 pid_files = [] 134 135 if ALL: 136 pid_files = glob.glob(ALL + "/*.pid") 137 else: 138 pid_files = [pid] 139 140 if not os.path.exists(pid): 141 print "PID file %s doesn't exist, maybe Lamson isn't running?" % pid 142 sys.exit(1) 143 return # for unit tests mocking sys.exit 144 145 print "Stopping processes with the following PID files: %s" % pid_files 146 147 for pid_f in pid_files: 148 pid = open(pid_f).readline() 149 150 print "Attempting to stop lamson at pid %d" % int(pid) 151 152 try: 153 if KILL: 154 os.kill(int(pid), signal.SIGKILL) 155 else: 156 os.kill(int(pid), signal.SIGHUP) 157 158 os.unlink(pid_f) 159 os.unlink(pid_f + ".lock") 160 except OSError, exc: 161 print "ERROR stopping Lamson on PID %d: %s" % (int(pid), exc)
162 163
164 -def restart_command(**options):
165 """ 166 Simply attempts a stop and then a start command. All options for both 167 apply to restart. See stop and start for options available. 168 """ 169 170 stop_command(**options) 171 time.sleep(2) 172 start_command(**options)
173 174
175 -def status_command(pid='./run/smtp.pid'):
176 """ 177 Prints out status information about lamson useful for finding out if it's 178 running and where. 179 180 lamson status -pid ./run/smtp.pid 181 """ 182 if os.path.exists(pid): 183 pid = open(pid).readline() 184 print "Lamson running with PID %d" % int(pid) 185 else: 186 print "Lamson not running."
187 188
189 -def help_command(**options):
190 """ 191 Prints out help for the commands. 192 193 lamson help 194 195 You can get help for one command with: 196 197 lamson help -for STR 198 """ 199 if "for" in options: 200 help_text = args.help_for_command(lamson.commands, options['for']) 201 if help_text: 202 print help_text 203 else: 204 args.invalid_command_message(lamson.commands, exit_on_error=True) 205 else: 206 print "Available commands:\n" 207 print ", ".join(args.available_commands(lamson.commands)) 208 print "\nUse lamson help -for <command> to find out more."
209 210
211 -def queue_command(pop=False, get=False, keys=False, remove=False, count=False, 212 clear=False, name="run/queue"):
213 """ 214 Let's you do most of the operations available to a queue. 215 216 lamson queue (-pop | -get | -remove | -count | -clear | -keys) -name run/queue 217 """ 218 print "Using queue: %r" % name 219 220 inq = queue.Queue(name) 221 222 if pop: 223 key, msg = inq.pop() 224 if key: 225 print "KEY: ", key 226 print msg 227 elif get: 228 print inq.get(get) 229 elif remove: 230 inq.remove(remove) 231 elif count: 232 print "Queue %s contains %d messages" % (name, inq.count()) 233 elif clear: 234 inq.clear() 235 elif keys: 236 print "\n".join(inq.keys()) 237 else: 238 print "Give something to do. Try lamson help -for queue to find out what." 239 sys.exit(1) 240 return # for unit tests mocking sys.exit
241 242
243 -def routes_command(TRAILING=['config.testing'], path=os.getcwd(), test=""):
244 """ 245 Prints out valuable information about an application's routing configuration 246 after everything is loaded and ready to go. Helps debug problems with 247 messages not getting to your handlers. Path has the search paths you want 248 separated by a ':' character, and it's added to the sys.path. 249 250 lamson routes -path $PWD -- config.testing -test "" 251 252 It defaults to running your config.testing to load the routes. 253 If you want it to run the config.boot then give that instead: 254 255 lamson routes -- config.boot 256 257 You can also test a potential target by doing -test EMAIL. 258 259 """ 260 modules = TRAILING 261 sys.path += path.split(':') 262 test_case_matches = [] 263 264 for module in modules: 265 __import__(module, globals(), locals()) 266 267 print "Routing ORDER: ", routing.Router.ORDER 268 print "Routing TABLE: \n---" 269 for format in routing.Router.REGISTERED: 270 print "%r: " % format, 271 regex, functions = routing.Router.REGISTERED[format] 272 for func in functions: 273 print "%s.%s " % (func.__module__, func.__name__), 274 match = regex.match(test) 275 if test and match: 276 test_case_matches.append((format, func, match)) 277 278 print "\n---" 279 280 if test_case_matches: 281 print "\nTEST address %r matches:" % test 282 for format, func, match in test_case_matches: 283 print " %r %s.%s" % (format, func.__module__, func.__name__) 284 print " - %r" % (match.groupdict()) 285 elif test: 286 print "\nTEST address %r didn't match anything." % test
287 288 289
290 -def gen_command(project=None, FORCE=False):
291 """ 292 Generates various useful things for you to get you started. 293 294 lamson gen -project STR -FORCE False 295 """ 296 project = project 297 298 if os.path.exists(project) and not FORCE: 299 print "Project %s exists, delete it first." % project 300 sys.exit(1) 301 return 302 303 prototype = ZipFile(resource_stream(__name__, 'data/prototype.zip')) 304 # looks like the very handy ZipFile.extractall is only in python 2.6 305 306 if not os.path.exists(project): 307 os.makedirs(project) 308 309 files = prototype.namelist() 310 311 for gen_f in files: 312 if str(gen_f).endswith('/'): 313 target = os.path.join(project, gen_f) 314 if not os.path.exists(target): 315 print "mkdir: %s" % target 316 os.makedirs(target) 317 else: 318 target = os.path.join(project, gen_f) 319 if os.path.exists(target): 320 continue 321 322 print "copy: %s" % target 323 out = open(target, 'w') 324 out.write(prototype.read(gen_f)) 325 out.close()
326 327
328 -def web_command(basedir=".", port=8888, host='127.0.0.1'):
329 """ 330 Starts a very simple files only web server for easy testing of applications 331 that need to make some HTML files as the result of their operation. 332 If you need more than this then use a real web server. 333 334 lamson web -basedir "." -port 8888 -host '127.0.0.1' 335 336 This command doesn't exit so you can view the logs it prints out. 337 """ 338 from BaseHTTPServer import HTTPServer 339 from SimpleHTTPServer import SimpleHTTPRequestHandler 340 341 os.chdir(basedir) 342 web = HTTPServer((host, port), SimpleHTTPRequestHandler) 343 print "Starting server on %s:%d out of directory %r" % ( 344 host, port, basedir) 345 web.serve_forever()
346 347
348 -def cleanse_command(input=None, output=None):
349 """ 350 Uses Lamson mail cleansing and canonicalization system to take an 351 input maildir (or mbox) and replicate the email over into another 352 maildir. It's used mostly for testing and cleaning. 353 """ 354 error_count = 0 355 356 try: 357 inbox = mailbox.mbox(input) 358 except: 359 inbox = mailbox.Maildir(input, factory=None) 360 361 outbox = mailbox.Maildir(output) 362 363 for msg in inbox: 364 try: 365 mail = encoding.from_message(msg) 366 outbox.add(encoding.to_string(mail)) 367 except encoding.EncodingError, exc: 368 print "ERROR: ", exc 369 error_count += 1 370 371 outbox.close() 372 inbox.close() 373 374 print "TOTAL ERRORS:", error_count
375 376
377 -def blast_command(input=None, host='127.0.0.1', port=8823, debug=0):
378 """ 379 Given a maildir, this command will go through each email 380 and blast it at your server. It does nothing to the message, so 381 it will be real messages hitting your server, not cleansed ones. 382 """ 383 inbox = mailbox.Maildir(input) 384 relay = server.Relay(host, port=port, debug=debug) 385 386 for key in inbox.keys(): 387 msgfile = inbox.get_file(key) 388 msg = email.message_from_file(msgfile) 389 relay.deliver(msg)
390 391
392 -def version_command():
393 """ 394 Prints the version of Lamson, the reporitory revision, and the 395 file it came from. 396 """ 397 398 from lamson import version 399 400 print "Lamson-Version: ", version.VERSION['version'] 401 print "Repository-Revision:", version.VERSION['rev'][0] 402 print "Repository-Hash:", version.VERSION['rev'][1] 403 print "Version-File:", version.__file__ 404 print "" 405 print "Lamson is Copyright (C) Zed A. Shaw 2008-2009. Licensed GPLv3." 406 print "If you didn't get a copy of the LICENSE contact the author at:\n" 407 print " zedshaw@zedshaw.com" 408 print "" 409 print "Have fun."
410