1
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__
34 """
35 StarCluster Command Line Interface
36 """
40
41 @property
46
49
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
64 gopts, args = gparser.parse_args()
65 if not args:
66 gparser.print_help()
67 raise SystemExit("\nError: you must specify an action.")
68
69 if gopts.DEBUG:
70 console.setLevel(logger.DEBUG)
71
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
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
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
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
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
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
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
186 return 'OPTPARSE_AUTO_COMPLETE' in os.environ
187
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
235
237 """
238 StarCluster main
239 """
240
241 self.handle_completion()
242
243 self.print_header()
244
245 gopts, sc, opts, args = self.parse_subcommands()
246 if args and args[0] == 'help':
247
248 sc.parser.print_help()
249 sys.exit(0)
250
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
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
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
310
311 if __name__ == '__main__':
312 try:
313 main()
314 except KeyboardInterrupt:
315 print "Interrupted, exiting."
316