Package starcluster :: Module cli
[hide private]
[frames] | no frames]

Source Code for Module starcluster.cli

  1  #!/usr/bin/env python 
  2  """ 
  3  StarCluster Command Line Interface: 
  4   
  5  starcluster [global-opts] action [action-opts] [<action-args> ...] 
  6  """ 
  7  import os 
  8  import sys 
  9  import shlex 
 10  import socket 
 11  import optparse 
 12  import platform 
 13  import traceback 
 14   
 15  from boto.exception import BotoServerError, EC2ResponseError, S3ResponseError 
 16   
 17  from starcluster import config 
 18  from starcluster import static 
 19  from starcluster import logger 
 20  from starcluster import commands 
 21  from starcluster import exception 
 22  from starcluster import optcomplete 
 23  from starcluster.logger import log, console, session 
 24  from starcluster import __version__ 
 25   
 26  __description__ = """ 
 27  StarCluster - (http://web.mit.edu/starcluster) (v. %s) 
 28  Software Tools for Academics and Researchers (STAR) 
 29  Please submit bug reports to starcluster@mit.edu 
 30  """ % __version__ 
31 32 33 -class StarClusterCLI(object):
34 """ 35 StarCluster Command Line Interface 36 """
37 - def __init__(self):
38 self._gparser = None 39 self.subcmds_map = {}
40 41 @property
42 - def gparser(self):
43 if not self._gparser: 44 self._gparser = self.create_global_parser() 45 return self._gparser
46
47 - def print_header(self):
48 print __description__.replace('\n', '', 1)
49
50 - def parse_subcommands(self, gparser=None):
51 """ 52 Parse global arguments, find subcommand from list of subcommand 53 objects, parse local subcommand arguments and return a tuple of 54 global options, selected command object, command options, and 55 command arguments. 56 57 Call execute() on the command object to run. The command object has 58 members 'gopts' and 'opts' set for global and command options 59 respectively, you don't need to call execute with those but you could 60 if you wanted to. 61 """ 62 gparser = gparser or self.gparser 63 # parse global options. 64 gopts, args = gparser.parse_args() 65 if not args: 66 gparser.print_help() 67 raise SystemExit("\nError: you must specify an action.") 68 # set debug level if specified 69 if gopts.DEBUG: 70 console.setLevel(logger.DEBUG) 71 # load StarClusterConfig into global options 72 try: 73 cfg = config.StarClusterConfig(gopts.CONFIG) 74 cfg.load() 75 except exception.ConfigNotFound, e: 76 log.error(e.msg) 77 e.display_options() 78 sys.exit(1) 79 except exception.ConfigError, e: 80 log.error(e.msg) 81 sys.exit(1) 82 gopts.CONFIG = cfg 83 # Parse command arguments and invoke command. 84 subcmdname, subargs = args[0], args[1:] 85 try: 86 sc = self.subcmds_map[subcmdname] 87 lparser = optparse.OptionParser(sc.__doc__.strip()) 88 sc.gopts = gopts 89 sc.parser = lparser 90 sc.gparser = gparser 91 sc.subcmds_map = self.subcmds_map 92 sc.addopts(lparser) 93 sc.opts, subsubargs = lparser.parse_args(subargs) 94 except KeyError: 95 raise SystemExit("Error: invalid command '%s'" % subcmdname) 96 return gopts, sc, sc.opts, subsubargs
97
98 - def create_global_parser(self, subcmds=None, no_usage=False, 99 add_help=True):
100 if no_usage: 101 gparser = optparse.OptionParser(usage=optparse.SUPPRESS_USAGE, 102 add_help_option=add_help) 103 else: 104 gparser = optparse.OptionParser(__doc__.strip(), 105 version=__version__, 106 add_help_option=add_help) 107 # Build map of name -> command and docstring. 108 cmds_header = 'Available Commands:' 109 gparser.usage += '\n\n%s\n' % cmds_header 110 gparser.usage += '%s\n' % ('-' * len(cmds_header)) 111 gparser.usage += "NOTE: Pass --help to any command for a list of " 112 gparser.usage += 'its options and detailed usage information\n\n' 113 subcmds = subcmds or commands.all_cmds 114 for sc in subcmds: 115 helptxt = sc.__doc__.splitlines()[3].strip() 116 gparser.usage += '- %s: %s\n' % (', '.join(sc.names), helptxt) 117 for n in sc.names: 118 assert n not in self.subcmds_map 119 self.subcmds_map[n] = sc 120 gparser.add_option("-d", "--debug", dest="DEBUG", 121 action="store_true", default=False, 122 help="print debug messages (useful for " 123 "diagnosing problems)") 124 gparser.add_option("-c", "--config", dest="CONFIG", action="store", 125 metavar="FILE", 126 help="use alternate config file (default: %s)" % 127 static.STARCLUSTER_CFG_FILE) 128 gparser.add_option("-r", "--region", dest="REGION", action="store", 129 help="specify a region to use (default: us-east-1)") 130 gparser.disable_interspersed_args() 131 return gparser
132
133 - def __write_module_version(self, modname, fp):
134 """ 135 Write module version information to a file 136 """ 137 try: 138 mod = __import__(modname) 139 fp.write("%s: %s\n" % (mod.__name__, mod.__version__)) 140 except Exception, e: 141 print "error getting version for '%s' module: %s" % (modname, e)
142
143 - def bug_found(self):
144 """ 145 Builds a crash-report when StarCluster encounters an unhandled 146 exception. Report includes system info, python version, dependency 147 versions, and a full debug log and stack-trace of the crash. 148 """ 149 dashes = '-' * 10 150 header = dashes + ' %s ' + dashes + '\n' 151 crashfile = open(static.CRASH_FILE, 'w') 152 crashfile.write(header % "CRASH DETAILS") 153 crashfile.write(session.stream.getvalue()) 154 crashfile.write(header % "SYSTEM INFO") 155 crashfile.write("StarCluster: %s\n" % __version__) 156 crashfile.write("Python: %s\n" % sys.version.replace('\n', ' ')) 157 crashfile.write("Platform: %s\n" % platform.platform()) 158 dependencies = ['boto', 'paramiko', 'Crypto', 'jinja2', 'decorator'] 159 for dep in dependencies: 160 self.__write_module_version(dep, crashfile) 161 crashfile.close() 162 log.error("Oops! Looks like you've found a bug in StarCluster") 163 log.error("Crash report written to: %s" % static.CRASH_FILE) 164 log.error("Please remove any sensitive data from the crash report") 165 log.error("and submit it to starcluster@mit.edu") 166 sys.exit(1)
167
168 - def get_global_opts(self):
169 """ 170 Parse and return global options. This method will silently return None 171 if any errors are encountered during parsing. 172 """ 173 gparser = self.create_global_parser(no_usage=True, add_help=False) 174 try: 175 sys.stdout = open(os.devnull, 'w') 176 sys.stderr = open(os.devnull, 'w') 177 gopts, _ = gparser.parse_args() 178 return gopts 179 except SystemExit: 180 pass 181 finally: 182 sys.stdout = sys.__stdout__ 183 sys.stderr = sys.__stderr__
184
185 - def is_completion_active(self):
186 return 'OPTPARSE_AUTO_COMPLETE' in os.environ
187
188 - def _init_completion(self):
189 """ 190 Restore original sys.argv from COMP_LINE in the case that starcluster 191 is being called by Bash/ZSH for completion options. Bash/ZSH will 192 simply call 'starcluster' with COMP_LINE environment variable set to 193 the current (partial) argv for completion. 194 195 StarCluster's Bash/ZSH completion code needs to read the global config 196 option in case an alternate config is specified at the command line 197 when completing options. StarCluster's comletion code uses the config 198 to generate completion options. Setting sys.argv to $COMP_LINE in this 199 case allows the global option parser to be used to extract the global 200 -c option (if specified) and load the proper config in the completion 201 code. 202 """ 203 if 'COMP_LINE' in os.environ: 204 newargv = shlex.split(os.environ.get('COMP_LINE')) 205 for i, arg in enumerate(newargv): 206 arg = os.path.expanduser(arg) 207 newargv[i] = os.path.expandvars(arg) 208 sys.argv = newargv
209
210 - def handle_completion(self):
211 if self.is_completion_active(): 212 gparser = self.create_global_parser(no_usage=True, add_help=False) 213 # set sys.path to COMP_LINE if it exists 214 self._init_completion() 215 # fetch the global options 216 gopts = self.get_global_opts() 217 # try to load StarClusterConfig into global options 218 if gopts: 219 try: 220 cfg = config.StarClusterConfig(gopts.CONFIG) 221 cfg.load() 222 except exception.ConfigError: 223 cfg = None 224 gopts.CONFIG = cfg 225 scmap = {} 226 for sc in commands.all_cmds: 227 sc.gopts = gopts 228 for n in sc.names: 229 scmap[n] = sc 230 listcter = optcomplete.ListCompleter(scmap.keys()) 231 subcter = optcomplete.NoneCompleter() 232 optcomplete.autocomplete(gparser, listcter, None, subcter, 233 subcommands=scmap) 234 sys.exit(1)
235
236 - def main(self):
237 """ 238 StarCluster main 239 """ 240 # Handle Bash/ZSH completion if necessary 241 self.handle_completion() 242 # Show StarCluster header 243 self.print_header() 244 # Parse subcommand options and args 245 gopts, sc, opts, args = self.parse_subcommands() 246 if args and args[0] == 'help': 247 # make 'help' subcommand act like --help option 248 sc.parser.print_help() 249 sys.exit(0) 250 # run the subcommand and handle exceptions 251 try: 252 sc.execute(args) 253 except (EC2ResponseError, S3ResponseError, BotoServerError), e: 254 log.error("%s: %s" % (e.error_code, e.error_message)) 255 sys.exit(1) 256 except socket.error, e: 257 log.error("Unable to connect: %s" % e) 258 log.error("Check your internet connection?") 259 sys.exit(1) 260 except exception.ThreadPoolException, e: 261 if not gopts.DEBUG: 262 e.print_excs() 263 log.debug(e.format_excs()) 264 print 265 self.bug_found() 266 except exception.ClusterDoesNotExist, e: 267 cm = gopts.CONFIG.get_cluster_manager() 268 cls = cm.get_clusters() 269 log.error(e.msg) 270 if cls: 271 taglist = ', '.join([c.cluster_tag for c in cls]) 272 active_clusters = "(active clusters: %s)" % taglist 273 log.error(active_clusters) 274 sys.exit(1) 275 except exception.BaseException, e: 276 log.error(e.msg, extra={'__textwrap__': True}) 277 sys.exit(1) 278 except SystemExit, e: 279 # re-raise SystemExit to avoid the bug-catcher below 280 raise e 281 except Exception, e: 282 if not gopts.DEBUG: 283 traceback.print_exc() 284 log.debug(traceback.format_exc()) 285 print 286 self.bug_found()
287
288 289 -def warn_debug_file_moved():
290 old_file = os.path.join(static.TMP_DIR, 'starcluster-debug-%s.log' % 291 static.CURRENT_USER) 292 if os.path.exists(old_file): 293 stars = '*' * 50 294 log.warn(stars) 295 log.warn("The default log file location is now:") 296 log.warn("") 297 log.warn(static.DEBUG_FILE) 298 log.warn("") 299 log.warn("Please delete or move the old log file located at:") 300 log.warn("") 301 log.warn(old_file) 302 log.warn(stars)
303
304 305 -def main():
306 static.create_sc_config_dirs() 307 logger.configure_sc_logging() 308 warn_debug_file_moved() 309 StarClusterCLI().main()
310 311 if __name__ == '__main__': 312 try: 313 main() 314 except KeyboardInterrupt: 315 print "Interrupted, exiting." 316