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