state.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. """
  2. Internal shared-state variables such as config settings and host lists.
  3. """
  4. import os
  5. import sys
  6. from optparse import make_option
  7. from fabric.network import HostConnectionCache, ssh
  8. from fabric.version import get_version
  9. from fabric.utils import _AliasDict, _AttributeDict
  10. #
  11. # Win32 flag
  12. #
  13. # Impacts a handful of platform specific behaviors. Note that Cygwin's Python
  14. # is actually close enough to "real" UNIXes that it doesn't need (or want!) to
  15. # use PyWin32 -- so we only test for literal Win32 setups (vanilla Python,
  16. # ActiveState etc) here.
  17. win32 = (sys.platform == 'win32')
  18. #
  19. # Environment dictionary - support structures
  20. #
  21. # By default, if the user (including code using Fabric as a library) doesn't
  22. # set the username, we obtain the currently running username and use that.
  23. def _get_system_username():
  24. """
  25. Obtain name of current system user, which will be default connection user.
  26. """
  27. import getpass
  28. username = None
  29. try:
  30. username = getpass.getuser()
  31. # getpass.getuser supported on both Unix and Windows systems.
  32. # getpass.getuser may call pwd.getpwuid which in turns may raise KeyError
  33. # if it cannot find a username for the given UID, e.g. on ep.io
  34. # and similar "non VPS" style services. Rather than error out, just keep
  35. # the 'default' username to None. Can check for this value later if needed.
  36. except KeyError:
  37. pass
  38. except ImportError:
  39. if win32:
  40. import win32api
  41. import win32security # noqa
  42. import win32profile # noqa
  43. username = win32api.GetUserName()
  44. return username
  45. def _rc_path():
  46. """
  47. Return platform-specific default file path for $HOME/.fabricrc.
  48. """
  49. rc_file = '.fabricrc'
  50. rc_path = '~/' + rc_file
  51. expanded_rc_path = os.path.expanduser(rc_path)
  52. if expanded_rc_path == rc_path and win32:
  53. from win32com.shell.shell import SHGetSpecialFolderPath
  54. from win32com.shell.shellcon import CSIDL_PROFILE
  55. expanded_rc_path = "%s/%s" % (
  56. SHGetSpecialFolderPath(0, CSIDL_PROFILE),
  57. rc_file
  58. )
  59. return expanded_rc_path
  60. default_port = '22' # hurr durr
  61. default_ssh_config_path = os.path.join(os.path.expanduser('~'), '.ssh', 'config')
  62. # Options/settings which exist both as environment keys and which can be set on
  63. # the command line, are defined here. When used via `fab` they will be added to
  64. # the optparse parser, and either way they are added to `env` below (i.e. the
  65. # 'dest' value becomes the environment key and the value, the env value).
  66. #
  67. # Keep in mind that optparse changes hyphens to underscores when automatically
  68. # deriving the `dest` name, e.g. `--reject-unknown-hosts` becomes
  69. # `reject_unknown_hosts`.
  70. #
  71. # Furthermore, *always* specify some sort of default to avoid ending up with
  72. # optparse.NO_DEFAULT (currently a two-tuple)! In general, None is a better
  73. # default than ''.
  74. #
  75. # User-facing documentation for these are kept in sites/docs/env.rst.
  76. env_options = [
  77. make_option('-a', '--no_agent',
  78. action='store_true',
  79. default=False,
  80. help="don't use the running SSH agent"
  81. ),
  82. make_option('-A', '--forward-agent',
  83. action='store_true',
  84. default=False,
  85. help="forward local agent to remote end"
  86. ),
  87. make_option('--abort-on-prompts',
  88. action='store_true',
  89. default=False,
  90. help="abort instead of prompting (for password, host, etc)"
  91. ),
  92. make_option('-c', '--config',
  93. dest='rcfile',
  94. default=_rc_path(),
  95. metavar='PATH',
  96. help="specify location of config file to use"
  97. ),
  98. make_option('--colorize-errors',
  99. action='store_true',
  100. default=False,
  101. help="Color error output",
  102. ),
  103. make_option('-D', '--disable-known-hosts',
  104. action='store_true',
  105. default=False,
  106. help="do not load user known_hosts file"
  107. ),
  108. make_option('-e', '--eagerly-disconnect',
  109. action='store_true',
  110. default=False,
  111. help="disconnect from hosts as soon as possible"
  112. ),
  113. make_option('-f', '--fabfile',
  114. default='fabfile',
  115. metavar='PATH',
  116. help="python module file to import, e.g. '../other.py'"
  117. ),
  118. make_option('-g', '--gateway',
  119. default=None,
  120. metavar='HOST',
  121. help="gateway host to connect through"
  122. ),
  123. make_option('--gss-auth',
  124. action='store_true',
  125. default=None,
  126. help="Use GSS-API authentication"
  127. ),
  128. make_option('--gss-deleg',
  129. action='store_true',
  130. default=None,
  131. help="Delegate GSS-API client credentials or not"
  132. ),
  133. make_option('--gss-kex',
  134. action='store_true',
  135. default=None,
  136. help="Perform GSS-API Key Exchange and user authentication"
  137. ),
  138. make_option('--hide',
  139. metavar='LEVELS',
  140. help="comma-separated list of output levels to hide"
  141. ),
  142. make_option('-H', '--hosts',
  143. default=[],
  144. help="comma-separated list of hosts to operate on"
  145. ),
  146. make_option('-i',
  147. action='append',
  148. dest='key_filename',
  149. metavar='PATH',
  150. default=None,
  151. help="path to SSH private key file. May be repeated."
  152. ),
  153. make_option('-k', '--no-keys',
  154. action='store_true',
  155. default=False,
  156. help="don't load private key files from ~/.ssh/"
  157. ),
  158. make_option('--keepalive',
  159. dest='keepalive',
  160. type=int,
  161. default=0,
  162. metavar="N",
  163. help="enables a keepalive every N seconds"
  164. ),
  165. make_option('--linewise',
  166. action='store_true',
  167. default=False,
  168. help="print line-by-line instead of byte-by-byte"
  169. ),
  170. make_option('-n', '--connection-attempts',
  171. type='int',
  172. metavar='M',
  173. dest='connection_attempts',
  174. default=1,
  175. help="make M attempts to connect before giving up"
  176. ),
  177. make_option('--no-pty',
  178. dest='always_use_pty',
  179. action='store_false',
  180. default=True,
  181. help="do not use pseudo-terminal in run/sudo"
  182. ),
  183. make_option('-p', '--password',
  184. default=None,
  185. help="password for use with authentication and/or sudo"
  186. ),
  187. make_option('-P', '--parallel',
  188. dest='parallel',
  189. action='store_true',
  190. default=False,
  191. help="default to parallel execution method"
  192. ),
  193. make_option('--port',
  194. default=default_port,
  195. help="SSH connection port"
  196. ),
  197. make_option('-r', '--reject-unknown-hosts',
  198. action='store_true',
  199. default=False,
  200. help="reject unknown hosts"
  201. ),
  202. make_option('--sudo-password',
  203. default=None,
  204. help="password for use with sudo only",
  205. ),
  206. make_option('--system-known-hosts',
  207. default=None,
  208. help="load system known_hosts file before reading user known_hosts"
  209. ),
  210. make_option('-R', '--roles',
  211. default=[],
  212. help="comma-separated list of roles to operate on"
  213. ),
  214. make_option('-s', '--shell',
  215. default='/bin/bash -l -c',
  216. help="specify a new shell, defaults to '/bin/bash -l -c'"
  217. ),
  218. make_option('--show',
  219. metavar='LEVELS',
  220. help="comma-separated list of output levels to show"
  221. ),
  222. make_option('--skip-bad-hosts',
  223. action="store_true",
  224. default=False,
  225. help="skip over hosts that can't be reached"
  226. ),
  227. make_option('--skip-unknown-tasks',
  228. action="store_true",
  229. default=False,
  230. help="skip over unknown tasks"
  231. ),
  232. make_option('--ssh-config-path',
  233. default=default_ssh_config_path,
  234. metavar='PATH',
  235. help="Path to SSH config file"
  236. ),
  237. make_option('-t', '--timeout',
  238. type='int',
  239. default=10,
  240. metavar="N",
  241. help="set connection timeout to N seconds"
  242. ),
  243. make_option('-T', '--command-timeout',
  244. dest='command_timeout',
  245. type='int',
  246. default=None,
  247. metavar="N",
  248. help="set remote command timeout to N seconds"
  249. ),
  250. make_option('-u', '--user',
  251. default=_get_system_username(),
  252. help="username to use when connecting to remote hosts"
  253. ),
  254. make_option('-w', '--warn-only',
  255. action='store_true',
  256. default=False,
  257. help="warn, instead of abort, when commands fail"
  258. ),
  259. make_option('-x', '--exclude-hosts',
  260. default=[],
  261. metavar='HOSTS',
  262. help="comma-separated list of hosts to exclude"
  263. ),
  264. make_option('-z', '--pool-size',
  265. dest='pool_size',
  266. type='int',
  267. metavar='INT',
  268. default=0,
  269. help="number of concurrent processes to use in parallel mode",
  270. ),
  271. ]
  272. #
  273. # Environment dictionary - actual dictionary object
  274. #
  275. # Global environment dict. Currently a catchall for everything: config settings
  276. # such as global deep/broad mode, host lists, username etc.
  277. # Most default values are specified in `env_options` above, in the interests of
  278. # preserving DRY: anything in here is generally not settable via the command
  279. # line.
  280. env = _AttributeDict({
  281. 'abort_exception': None,
  282. 'again_prompt': 'Sorry, try again.',
  283. 'all_hosts': [],
  284. 'combine_stderr': True,
  285. 'colorize_errors': False,
  286. 'command': None,
  287. 'command_prefixes': [],
  288. 'cwd': '', # Must be empty string, not None, for concatenation purposes
  289. 'dedupe_hosts': True,
  290. 'default_port': default_port,
  291. 'eagerly_disconnect': False,
  292. 'echo_stdin': True,
  293. 'effective_roles': [],
  294. 'exclude_hosts': [],
  295. 'gateway': None,
  296. 'gss_auth': None,
  297. 'gss_deleg': None,
  298. 'gss_kex': None,
  299. 'host': None,
  300. 'host_string': None,
  301. 'lcwd': '', # Must be empty string, not None, for concatenation purposes
  302. 'local_user': _get_system_username(),
  303. 'output_prefix': True,
  304. 'passwords': {},
  305. 'path': '',
  306. 'path_behavior': 'append',
  307. 'port': default_port,
  308. 'real_fabfile': None,
  309. 'remote_interrupt': None,
  310. 'roles': [],
  311. 'roledefs': {},
  312. 'shell_env': {},
  313. 'skip_bad_hosts': False,
  314. 'skip_unknown_tasks': False,
  315. 'ssh_config_path': default_ssh_config_path,
  316. 'sudo_passwords': {},
  317. 'ok_ret_codes': [0], # a list of return codes that indicate success
  318. # -S so sudo accepts passwd via stdin, -p with our known-value prompt for
  319. # later detection (thus %s -- gets filled with env.sudo_prompt at runtime)
  320. 'sudo_prefix': "sudo -S -p '%(sudo_prompt)s' ",
  321. 'sudo_prompt': 'sudo password:',
  322. 'sudo_user': None,
  323. 'tasks': [],
  324. 'prompts': {},
  325. 'use_exceptions_for': {'network': False},
  326. 'use_shell': True,
  327. 'use_ssh_config': False,
  328. 'user': None,
  329. 'version': get_version('short')
  330. })
  331. # Fill in exceptions settings
  332. exceptions = ['network']
  333. exception_dict = {}
  334. for e in exceptions:
  335. exception_dict[e] = False
  336. env.use_exceptions_for = _AliasDict(exception_dict,
  337. aliases={'everything': exceptions})
  338. # Add in option defaults
  339. for option in env_options:
  340. env[option.dest] = option.default
  341. #
  342. # Command dictionary
  343. #
  344. # Keys are the command/function names, values are the callables themselves.
  345. # This is filled in when main() runs.
  346. commands = {}
  347. #
  348. # Host connection dict/cache
  349. #
  350. connections = HostConnectionCache()
  351. def _open_session():
  352. return connections[env.host_string].get_transport().open_session()
  353. def default_channel():
  354. """
  355. Return a channel object based on ``env.host_string``.
  356. """
  357. try:
  358. chan = _open_session()
  359. except ssh.SSHException, err:
  360. if str(err) == 'SSH session not active':
  361. connections[env.host_string].close()
  362. del connections[env.host_string]
  363. chan = _open_session()
  364. else:
  365. raise
  366. chan.settimeout(0.1)
  367. chan.input_enabled = True
  368. return chan
  369. #
  370. # Output controls
  371. #
  372. # Keys are "levels" or "groups" of output, values are always boolean,
  373. # determining whether output falling into the given group is printed or not
  374. # printed.
  375. #
  376. # By default, everything except 'debug' is printed, as this is what the average
  377. # user, and new users, are most likely to expect.
  378. #
  379. # See docs/usage.rst for details on what these levels mean.
  380. output = _AliasDict({
  381. 'status': True,
  382. 'aborts': True,
  383. 'warnings': True,
  384. 'running': True,
  385. 'stdout': True,
  386. 'stderr': True,
  387. 'exceptions': False,
  388. 'debug': False,
  389. 'user': True
  390. }, aliases={
  391. 'everything': ['warnings', 'running', 'user', 'output', 'exceptions'],
  392. 'output': ['stdout', 'stderr'],
  393. 'commands': ['stdout', 'running']
  394. })