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

Source Code for Module starcluster.cli

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