main.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775
  1. """
  2. This module contains Fab's `main` method plus related subroutines.
  3. `main` is executed as the command line ``fab`` program and takes care of
  4. parsing options and commands, loading the user settings file, loading a
  5. fabfile, and executing the commands given.
  6. The other callables defined in this module are internal only. Anything useful
  7. to individuals leveraging Fabric as a library, should be kept elsewhere.
  8. """
  9. import getpass
  10. import inspect
  11. from operator import isMappingType
  12. from optparse import OptionParser
  13. import os
  14. import sys
  15. import types
  16. # For checking callables against the API, & easy mocking
  17. from fabric import api, state, colors
  18. from fabric.contrib import console, files, project
  19. from fabric.network import disconnect_all, ssh
  20. from fabric.state import env_options
  21. from fabric.tasks import Task, execute, get_task_details
  22. from fabric.task_utils import _Dict, crawl
  23. from fabric.utils import abort, indent, warn, _pty_size
  24. # One-time calculation of "all internal callables" to avoid doing this on every
  25. # check of a given fabfile callable (in is_classic_task()).
  26. _modules = [api, project, files, console, colors]
  27. _internals = reduce(lambda x, y: x + filter(callable, vars(y).values()),
  28. _modules,
  29. []
  30. )
  31. # Module recursion cache
  32. class _ModuleCache(object):
  33. """
  34. Set-like object operating on modules and storing __name__s internally.
  35. """
  36. def __init__(self):
  37. self.cache = set()
  38. def __contains__(self, value):
  39. return value.__name__ in self.cache
  40. def add(self, value):
  41. return self.cache.add(value.__name__)
  42. def clear(self):
  43. return self.cache.clear()
  44. _seen = _ModuleCache()
  45. def load_settings(path):
  46. """
  47. Take given file path and return dictionary of any key=value pairs found.
  48. Usage docs are in sites/docs/usage/fab.rst, in "Settings files."
  49. """
  50. if os.path.exists(path):
  51. comments = lambda s: s and not s.startswith("#")
  52. settings = filter(comments, open(path, 'r'))
  53. return dict((k.strip(), v.strip()) for k, _, v in
  54. [s.partition('=') for s in settings])
  55. # Handle nonexistent or empty settings file
  56. return {}
  57. def _is_package(path):
  58. """
  59. Is the given path a Python package?
  60. """
  61. _exists = lambda s: os.path.exists(os.path.join(path, s))
  62. return (
  63. os.path.isdir(path)
  64. and (_exists('__init__.py') or _exists('__init__.pyc'))
  65. )
  66. def find_fabfile(names=None):
  67. """
  68. Attempt to locate a fabfile, either explicitly or by searching parent dirs.
  69. Usage docs are in sites/docs/usage/fabfiles.rst, in "Fabfile discovery."
  70. """
  71. # Obtain env value if not given specifically
  72. if names is None:
  73. names = [state.env.fabfile]
  74. # Create .py version if necessary
  75. if not names[0].endswith('.py'):
  76. names += [names[0] + '.py']
  77. # Does the name contain path elements?
  78. if os.path.dirname(names[0]):
  79. # If so, expand home-directory markers and test for existence
  80. for name in names:
  81. expanded = os.path.expanduser(name)
  82. if os.path.exists(expanded):
  83. if name.endswith('.py') or _is_package(expanded):
  84. return os.path.abspath(expanded)
  85. else:
  86. # Otherwise, start in cwd and work downwards towards filesystem root
  87. path = '.'
  88. # Stop before falling off root of filesystem (should be platform
  89. # agnostic)
  90. while os.path.split(os.path.abspath(path))[1]:
  91. for name in names:
  92. joined = os.path.join(path, name)
  93. if os.path.exists(joined):
  94. if name.endswith('.py') or _is_package(joined):
  95. return os.path.abspath(joined)
  96. path = os.path.join('..', path)
  97. # Implicit 'return None' if nothing was found
  98. def is_classic_task(tup):
  99. """
  100. Takes (name, object) tuple, returns True if it's a non-Fab public callable.
  101. """
  102. name, func = tup
  103. try:
  104. is_classic = (
  105. callable(func)
  106. and (func not in _internals)
  107. and not name.startswith('_')
  108. and not (inspect.isclass(func) and issubclass(func, Exception))
  109. )
  110. # Handle poorly behaved __eq__ implementations
  111. except (ValueError, TypeError):
  112. is_classic = False
  113. return is_classic
  114. def load_fabfile(path, importer=None):
  115. """
  116. Import given fabfile path and return (docstring, callables).
  117. Specifically, the fabfile's ``__doc__`` attribute (a string) and a
  118. dictionary of ``{'name': callable}`` containing all callables which pass
  119. the "is a Fabric task" test.
  120. """
  121. if importer is None:
  122. importer = __import__
  123. # Get directory and fabfile name
  124. directory, fabfile = os.path.split(path)
  125. # If the directory isn't in the PYTHONPATH, add it so our import will work
  126. added_to_path = False
  127. index = None
  128. if directory not in sys.path:
  129. sys.path.insert(0, directory)
  130. added_to_path = True
  131. # If the directory IS in the PYTHONPATH, move it to the front temporarily,
  132. # otherwise other fabfiles -- like Fabric's own -- may scoop the intended
  133. # one.
  134. else:
  135. i = sys.path.index(directory)
  136. if i != 0:
  137. # Store index for later restoration
  138. index = i
  139. # Add to front, then remove from original position
  140. sys.path.insert(0, directory)
  141. del sys.path[i + 1]
  142. # Perform the import (trimming off the .py)
  143. imported = importer(os.path.splitext(fabfile)[0])
  144. # Remove directory from path if we added it ourselves (just to be neat)
  145. if added_to_path:
  146. del sys.path[0]
  147. # Put back in original index if we moved it
  148. if index is not None:
  149. sys.path.insert(index + 1, directory)
  150. del sys.path[0]
  151. # Actually load tasks
  152. docstring, new_style, classic, default = load_tasks_from_module(imported)
  153. tasks = new_style if state.env.new_style_tasks else classic
  154. # Clean up after ourselves
  155. _seen.clear()
  156. return docstring, tasks, default
  157. def load_tasks_from_module(imported):
  158. """
  159. Handles loading all of the tasks for a given `imported` module
  160. """
  161. # Obey the use of <module>.__all__ if it is present
  162. imported_vars = vars(imported)
  163. if "__all__" in imported_vars:
  164. imported_vars = [(name, imported_vars[name]) for name in \
  165. imported_vars if name in imported_vars["__all__"]]
  166. else:
  167. imported_vars = imported_vars.items()
  168. # Return a two-tuple value. First is the documentation, second is a
  169. # dictionary of callables only (and don't include Fab operations or
  170. # underscored callables)
  171. new_style, classic, default = extract_tasks(imported_vars)
  172. return imported.__doc__, new_style, classic, default
  173. def extract_tasks(imported_vars):
  174. """
  175. Handle extracting tasks from a given list of variables
  176. """
  177. new_style_tasks = _Dict()
  178. classic_tasks = {}
  179. default_task = None
  180. if 'new_style_tasks' not in state.env:
  181. state.env.new_style_tasks = False
  182. for tup in imported_vars:
  183. name, obj = tup
  184. if is_task_object(obj):
  185. state.env.new_style_tasks = True
  186. # Use instance.name if defined
  187. if obj.name and obj.name != 'undefined':
  188. new_style_tasks[obj.name] = obj
  189. else:
  190. obj.name = name
  191. new_style_tasks[name] = obj
  192. # Handle aliasing
  193. if obj.aliases is not None:
  194. for alias in obj.aliases:
  195. new_style_tasks[alias] = obj
  196. # Handle defaults
  197. if obj.is_default:
  198. default_task = obj
  199. elif is_classic_task(tup):
  200. classic_tasks[name] = obj
  201. elif is_task_module(obj):
  202. docs, newstyle, classic, default = load_tasks_from_module(obj)
  203. for task_name, task in newstyle.items():
  204. if name not in new_style_tasks:
  205. new_style_tasks[name] = _Dict()
  206. new_style_tasks[name][task_name] = task
  207. if default is not None:
  208. new_style_tasks[name].default = default
  209. return new_style_tasks, classic_tasks, default_task
  210. def is_task_module(a):
  211. """
  212. Determine if the provided value is a task module
  213. """
  214. #return (type(a) is types.ModuleType and
  215. # any(map(is_task_object, vars(a).values())))
  216. if isinstance(a, types.ModuleType) and a not in _seen:
  217. # Flag module as seen
  218. _seen.add(a)
  219. # Signal that we need to check it out
  220. return True
  221. def is_task_object(a):
  222. """
  223. Determine if the provided value is a ``Task`` object.
  224. This returning True signals that all tasks within the fabfile
  225. module must be Task objects.
  226. """
  227. return isinstance(a, Task) and a.use_task_objects
  228. def parse_options():
  229. """
  230. Handle command-line options with optparse.OptionParser.
  231. Return list of arguments, largely for use in `parse_arguments`.
  232. """
  233. #
  234. # Initialize
  235. #
  236. parser = OptionParser(
  237. usage=("fab [options] <command>"
  238. "[:arg1,arg2=val2,host=foo,hosts='h1;h2',...] ..."))
  239. #
  240. # Define options that don't become `env` vars (typically ones which cause
  241. # Fabric to do something other than its normal execution, such as
  242. # --version)
  243. #
  244. # Display info about a specific command
  245. parser.add_option('-d', '--display',
  246. metavar='NAME',
  247. help="print detailed info about command NAME"
  248. )
  249. # Control behavior of --list
  250. LIST_FORMAT_OPTIONS = ('short', 'normal', 'nested')
  251. parser.add_option('-F', '--list-format',
  252. choices=LIST_FORMAT_OPTIONS,
  253. default='normal',
  254. metavar='FORMAT',
  255. help="formats --list, choices: %s" % ", ".join(LIST_FORMAT_OPTIONS)
  256. )
  257. parser.add_option('-I', '--initial-password-prompt',
  258. action='store_true',
  259. default=False,
  260. help="Force password prompt up-front"
  261. )
  262. parser.add_option('--initial-sudo-password-prompt',
  263. action='store_true',
  264. default=False,
  265. help="Force sudo password prompt up-front"
  266. )
  267. # List Fab commands found in loaded fabfiles/source files
  268. parser.add_option('-l', '--list',
  269. action='store_true',
  270. dest='list_commands',
  271. default=False,
  272. help="print list of possible commands and exit"
  273. )
  274. # Allow setting of arbitrary env vars at runtime.
  275. parser.add_option('--set',
  276. metavar="KEY=VALUE,...",
  277. dest='env_settings',
  278. default="",
  279. help="comma separated KEY=VALUE pairs to set Fab env vars"
  280. )
  281. # Like --list, but text processing friendly
  282. parser.add_option('--shortlist',
  283. action='store_true',
  284. dest='shortlist',
  285. default=False,
  286. help="alias for -F short --list"
  287. )
  288. # Version number (optparse gives you --version but we have to do it
  289. # ourselves to get -V too. sigh)
  290. parser.add_option('-V', '--version',
  291. action='store_true',
  292. dest='show_version',
  293. default=False,
  294. help="show program's version number and exit"
  295. )
  296. #
  297. # Add in options which are also destined to show up as `env` vars.
  298. #
  299. for option in env_options:
  300. parser.add_option(option)
  301. #
  302. # Finalize
  303. #
  304. # Return three-tuple of parser + the output from parse_args (opt obj, args)
  305. opts, args = parser.parse_args()
  306. return parser, opts, args
  307. def _is_task(name, value):
  308. """
  309. Is the object a task as opposed to e.g. a dict or int?
  310. """
  311. return is_classic_task((name, value)) or is_task_object(value)
  312. def _sift_tasks(mapping):
  313. tasks, collections = [], []
  314. for name, value in mapping.iteritems():
  315. if _is_task(name, value):
  316. tasks.append(name)
  317. elif isMappingType(value):
  318. collections.append(name)
  319. tasks = sorted(tasks)
  320. collections = sorted(collections)
  321. return tasks, collections
  322. def _task_names(mapping):
  323. """
  324. Flatten & sort task names in a breadth-first fashion.
  325. Tasks are always listed before submodules at the same level, but within
  326. those two groups, sorting is alphabetical.
  327. """
  328. tasks, collections = _sift_tasks(mapping)
  329. for collection in collections:
  330. module = mapping[collection]
  331. if hasattr(module, 'default'):
  332. tasks.append(collection)
  333. join = lambda x: ".".join((collection, x))
  334. tasks.extend(map(join, _task_names(module)))
  335. return tasks
  336. def _print_docstring(docstrings, name):
  337. if not docstrings:
  338. return False
  339. docstring = crawl(name, state.commands).__doc__
  340. if isinstance(docstring, basestring):
  341. return docstring
  342. def _normal_list(docstrings=True):
  343. result = []
  344. task_names = _task_names(state.commands)
  345. # Want separator between name, description to be straight col
  346. max_len = reduce(lambda a, b: max(a, len(b)), task_names, 0)
  347. sep = ' '
  348. trail = '...'
  349. max_width = _pty_size()[1] - 1 - len(trail)
  350. for name in task_names:
  351. output = None
  352. docstring = _print_docstring(docstrings, name)
  353. if docstring:
  354. lines = filter(None, docstring.splitlines())
  355. first_line = lines[0].strip()
  356. # Truncate it if it's longer than N chars
  357. size = max_width - (max_len + len(sep) + len(trail))
  358. if len(first_line) > size:
  359. first_line = first_line[:size] + trail
  360. output = name.ljust(max_len) + sep + first_line
  361. # Or nothing (so just the name)
  362. else:
  363. output = name
  364. result.append(indent(output))
  365. return result
  366. def _nested_list(mapping, level=1):
  367. result = []
  368. tasks, collections = _sift_tasks(mapping)
  369. # Tasks come first
  370. result.extend(map(lambda x: indent(x, spaces=level * 4), tasks))
  371. for collection in collections:
  372. module = mapping[collection]
  373. # Section/module "header"
  374. result.append(indent(collection + ":", spaces=level * 4))
  375. # Recurse
  376. result.extend(_nested_list(module, level + 1))
  377. return result
  378. COMMANDS_HEADER = "Available commands"
  379. NESTED_REMINDER = " (remember to call as module.[...].task)"
  380. def list_commands(docstring, format_):
  381. """
  382. Print all found commands/tasks, then exit. Invoked with ``-l/--list.``
  383. If ``docstring`` is non-empty, it will be printed before the task list.
  384. ``format_`` should conform to the options specified in
  385. ``LIST_FORMAT_OPTIONS``, e.g. ``"short"``, ``"normal"``.
  386. """
  387. # Short-circuit with simple short output
  388. if format_ == "short":
  389. return _task_names(state.commands)
  390. # Otherwise, handle more verbose modes
  391. result = []
  392. # Docstring at top, if applicable
  393. if docstring:
  394. trailer = "\n" if not docstring.endswith("\n") else ""
  395. result.append(docstring + trailer)
  396. header = COMMANDS_HEADER
  397. if format_ == "nested":
  398. header += NESTED_REMINDER
  399. result.append(header + ":\n")
  400. c = _normal_list() if format_ == "normal" else _nested_list(state.commands)
  401. result.extend(c)
  402. return result
  403. def display_command(name):
  404. """
  405. Print command function's docstring, then exit. Invoked with -d/--display.
  406. """
  407. # Sanity check
  408. command = crawl(name, state.commands)
  409. if command is None:
  410. msg = "Task '%s' does not appear to exist. Valid task names:\n%s"
  411. abort(msg % (name, "\n".join(_normal_list(False))))
  412. # Print out nicely presented docstring if found
  413. if hasattr(command, '__details__'):
  414. task_details = command.__details__()
  415. else:
  416. task_details = get_task_details(command)
  417. if task_details:
  418. print("Displaying detailed information for task '%s':" % name)
  419. print('')
  420. print(indent(task_details, strip=True))
  421. print('')
  422. # Or print notice if not
  423. else:
  424. print("No detailed information available for task '%s':" % name)
  425. sys.exit(0)
  426. def _escape_split(sep, argstr):
  427. """
  428. Allows for escaping of the separator: e.g. task:arg='foo\, bar'
  429. It should be noted that the way bash et. al. do command line parsing, those
  430. single quotes are required.
  431. """
  432. escaped_sep = r'\%s' % sep
  433. if escaped_sep not in argstr:
  434. return argstr.split(sep)
  435. before, _, after = argstr.partition(escaped_sep)
  436. startlist = before.split(sep) # a regular split is fine here
  437. unfinished = startlist[-1]
  438. startlist = startlist[:-1]
  439. # recurse because there may be more escaped separators
  440. endlist = _escape_split(sep, after)
  441. # finish building the escaped value. we use endlist[0] becaue the first
  442. # part of the string sent in recursion is the rest of the escaped value.
  443. unfinished += sep + endlist[0]
  444. return startlist + [unfinished] + endlist[1:] # put together all the parts
  445. def parse_arguments(arguments):
  446. """
  447. Parse string list into list of tuples: command, args, kwargs, hosts, roles.
  448. See sites/docs/usage/fab.rst, section on "per-task arguments" for details.
  449. """
  450. cmds = []
  451. for cmd in arguments:
  452. args = []
  453. kwargs = {}
  454. hosts = []
  455. roles = []
  456. exclude_hosts = []
  457. if ':' in cmd:
  458. cmd, argstr = cmd.split(':', 1)
  459. for pair in _escape_split(',', argstr):
  460. result = _escape_split('=', pair)
  461. if len(result) > 1:
  462. k, v = result
  463. # Catch, interpret host/hosts/role/roles/exclude_hosts
  464. # kwargs
  465. if k in ['host', 'hosts', 'role', 'roles', 'exclude_hosts']:
  466. if k == 'host':
  467. hosts = [v.strip()]
  468. elif k == 'hosts':
  469. hosts = [x.strip() for x in v.split(';')]
  470. elif k == 'role':
  471. roles = [v.strip()]
  472. elif k == 'roles':
  473. roles = [x.strip() for x in v.split(';')]
  474. elif k == 'exclude_hosts':
  475. exclude_hosts = [x.strip() for x in v.split(';')]
  476. # Otherwise, record as usual
  477. else:
  478. kwargs[k] = v
  479. else:
  480. args.append(result[0])
  481. cmds.append((cmd, args, kwargs, hosts, roles, exclude_hosts))
  482. return cmds
  483. def parse_remainder(arguments):
  484. """
  485. Merge list of "remainder arguments" into a single command string.
  486. """
  487. return ' '.join(arguments)
  488. def update_output_levels(show, hide):
  489. """
  490. Update state.output values as per given comma-separated list of key names.
  491. For example, ``update_output_levels(show='debug,warnings')`` is
  492. functionally equivalent to ``state.output['debug'] = True ;
  493. state.output['warnings'] = True``. Conversely, anything given to ``hide``
  494. sets the values to ``False``.
  495. """
  496. if show:
  497. for key in show.split(','):
  498. state.output[key] = True
  499. if hide:
  500. for key in hide.split(','):
  501. state.output[key] = False
  502. def show_commands(docstring, format, code=0):
  503. print("\n".join(list_commands(docstring, format)))
  504. sys.exit(code)
  505. def main(fabfile_locations=None):
  506. """
  507. Main command-line execution loop.
  508. """
  509. try:
  510. # Parse command line options
  511. parser, options, arguments = parse_options()
  512. # Handle regular args vs -- args
  513. arguments = parser.largs
  514. remainder_arguments = parser.rargs
  515. # Allow setting of arbitrary env keys.
  516. # This comes *before* the "specific" env_options so that those may
  517. # override these ones. Specific should override generic, if somebody
  518. # was silly enough to specify the same key in both places.
  519. # E.g. "fab --set shell=foo --shell=bar" should have env.shell set to
  520. # 'bar', not 'foo'.
  521. for pair in _escape_split(',', options.env_settings):
  522. pair = _escape_split('=', pair)
  523. # "--set x" => set env.x to True
  524. # "--set x=" => set env.x to ""
  525. key = pair[0]
  526. value = True
  527. if len(pair) == 2:
  528. value = pair[1]
  529. state.env[key] = value
  530. # Update env with any overridden option values
  531. # NOTE: This needs to remain the first thing that occurs
  532. # post-parsing, since so many things hinge on the values in env.
  533. for option in env_options:
  534. state.env[option.dest] = getattr(options, option.dest)
  535. # Handle --hosts, --roles, --exclude-hosts (comma separated string =>
  536. # list)
  537. for key in ['hosts', 'roles', 'exclude_hosts']:
  538. if key in state.env and isinstance(state.env[key], basestring):
  539. state.env[key] = state.env[key].split(',')
  540. # Feed the env.tasks : tasks that are asked to be executed.
  541. state.env['tasks'] = arguments
  542. # Handle output control level show/hide
  543. update_output_levels(show=options.show, hide=options.hide)
  544. # Handle version number option
  545. if options.show_version:
  546. print("Fabric %s" % state.env.version)
  547. print("Paramiko %s" % ssh.__version__)
  548. sys.exit(0)
  549. # Load settings from user settings file, into shared env dict.
  550. state.env.update(load_settings(state.env.rcfile))
  551. # Find local fabfile path or abort
  552. fabfile = find_fabfile(fabfile_locations)
  553. if not fabfile and not remainder_arguments:
  554. abort("""Couldn't find any fabfiles!
  555. Remember that -f can be used to specify fabfile path, and use -h for help.""")
  556. # Store absolute path to fabfile in case anyone needs it
  557. state.env.real_fabfile = fabfile
  558. # Load fabfile (which calls its module-level code, including
  559. # tweaks to env values) and put its commands in the shared commands
  560. # dict
  561. default = None
  562. if fabfile:
  563. docstring, callables, default = load_fabfile(fabfile)
  564. state.commands.update(callables)
  565. # Handle case where we were called bare, i.e. just "fab", and print
  566. # a help message.
  567. actions = (options.list_commands, options.shortlist, options.display,
  568. arguments, remainder_arguments, default)
  569. if not any(actions):
  570. parser.print_help()
  571. sys.exit(1)
  572. # Abort if no commands found
  573. if not state.commands and not remainder_arguments:
  574. abort("Fabfile didn't contain any commands!")
  575. # Now that we're settled on a fabfile, inform user.
  576. if state.output.debug:
  577. if fabfile:
  578. print("Using fabfile '%s'" % fabfile)
  579. else:
  580. print("No fabfile loaded -- remainder command only")
  581. # Shortlist is now just an alias for the "short" list format;
  582. # it overrides use of --list-format if somebody were to specify both
  583. if options.shortlist:
  584. options.list_format = 'short'
  585. options.list_commands = True
  586. # List available commands
  587. if options.list_commands:
  588. show_commands(docstring, options.list_format)
  589. # Handle show (command-specific help) option
  590. if options.display:
  591. display_command(options.display)
  592. # If user didn't specify any commands to run, show help
  593. if not (arguments or remainder_arguments or default):
  594. parser.print_help()
  595. sys.exit(0) # Or should it exit with error (1)?
  596. # Parse arguments into commands to run (plus args/kwargs/hosts)
  597. commands_to_run = parse_arguments(arguments)
  598. # Parse remainders into a faux "command" to execute
  599. remainder_command = parse_remainder(remainder_arguments)
  600. # Figure out if any specified task names are invalid
  601. unknown_commands = []
  602. for tup in commands_to_run:
  603. if crawl(tup[0], state.commands) is None:
  604. unknown_commands.append(tup[0])
  605. # Abort if any unknown commands were specified
  606. if unknown_commands and not state.env.get('skip_unknown_tasks', False):
  607. warn("Command(s) not found:\n%s" \
  608. % indent(unknown_commands))
  609. show_commands(None, options.list_format, 1)
  610. # Generate remainder command and insert into commands, commands_to_run
  611. if remainder_command:
  612. r = '<remainder>'
  613. state.commands[r] = lambda: api.run(remainder_command)
  614. commands_to_run.append((r, [], {}, [], [], []))
  615. # Ditto for a default, if found
  616. if not commands_to_run and default:
  617. commands_to_run.append((default.name, [], {}, [], [], []))
  618. # Initial password prompt, if requested
  619. if options.initial_password_prompt:
  620. prompt = "Initial value for env.password: "
  621. state.env.password = getpass.getpass(prompt)
  622. # Ditto sudo_password
  623. if options.initial_sudo_password_prompt:
  624. prompt = "Initial value for env.sudo_password: "
  625. state.env.sudo_password = getpass.getpass(prompt)
  626. if state.output.debug:
  627. names = ", ".join(x[0] for x in commands_to_run)
  628. print("Commands to run: %s" % names)
  629. # At this point all commands must exist, so execute them in order.
  630. for name, args, kwargs, arg_hosts, arg_roles, arg_exclude_hosts in commands_to_run:
  631. execute(
  632. name,
  633. hosts=arg_hosts,
  634. roles=arg_roles,
  635. exclude_hosts=arg_exclude_hosts,
  636. *args, **kwargs
  637. )
  638. # If we got here, no errors occurred, so print a final note.
  639. if state.output.status:
  640. print("\nDone.")
  641. except SystemExit:
  642. # a number of internal functions might raise this one.
  643. raise
  644. except KeyboardInterrupt:
  645. if state.output.status:
  646. sys.stderr.write("\nStopped.\n")
  647. sys.exit(1)
  648. except:
  649. sys.excepthook(*sys.exc_info())
  650. # we might leave stale threads if we don't explicitly exit()
  651. sys.exit(1)
  652. finally:
  653. disconnect_all()
  654. sys.exit(0)