PageRenderTime 60ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/silverlining/runner.py

https://bitbucket.org/ianb/silverlining/
Python | 486 lines | 475 code | 4 blank | 7 comment | 19 complexity | b687884a42385084c6a5cb2229da7330 MD5 | raw file
Possible License(s): GPL-2.0
  1. """silverlining interface
  2. This implements the command-line interface of silverlining. It includes
  3. all the commands and their options, though the implementation of the
  4. commands are in ``silverlining.commands.*``
  5. """
  6. import os
  7. import sys
  8. import argparse
  9. from cmdutils.arg import add_verbose, create_logger
  10. from cmdutils import CommandError
  11. from silverlining import createconf
  12. from silverlining.config import Config
  13. ## Following is a HUGE BLOCK of argument definitions:
  14. description = """\
  15. Runs a variety of deployment-related commands
  16. """
  17. parser = argparse.ArgumentParser(
  18. description=description)
  19. parser.add_argument(
  20. '-p', '--provider',
  21. metavar='NAME',
  22. help="The [provider:NAME] section from ~/.silverlining.conf to use (default [provider:default])",
  23. default="default")
  24. parser.add_argument(
  25. '-y', '--yes',
  26. action='store_true',
  27. help="Answer yes to any questions")
  28. parser.add_argument(
  29. '--debug-libcloud', metavar='FILENAME',
  30. help="Write any libcloud interactions (HTTP request log)")
  31. add_verbose(parser, add_log=True)
  32. subcommands = parser.add_subparsers(dest="command")
  33. parser_list_images = subcommands.add_parser(
  34. 'list-images', help="List all images available")
  35. parser_list_sizes = subcommands.add_parser(
  36. 'list-sizes', help="List all sizes available")
  37. parser_list_nodes = subcommands.add_parser(
  38. 'list-nodes', help="List all active nodes")
  39. parser_destroy = subcommands.add_parser(
  40. 'destroy-node', help="Destroy a node")
  41. parser_destroy.add_argument(
  42. 'nodes', nargs='+',
  43. metavar='HOSTNAME',
  44. help="The hostname(s) of the node to destroy")
  45. parser_create = subcommands.add_parser(
  46. 'create-node', help="Create a new node")
  47. parser_create.add_argument(
  48. 'node',
  49. metavar='HOSTNAME',
  50. help="The hostname of the node to create")
  51. parser_create.add_argument(
  52. '--image',
  53. default='name *lucid*',
  54. metavar="IMAGE",
  55. help='Image to use, can be "id 10", and can contain wildcards like "name *karmic*". '
  56. 'Default is "name *karmic*" which will select an Ubuntu Karmic image.')
  57. parser_create.add_argument(
  58. '--size',
  59. metavar="SIZE",
  60. default="ram 256",
  61. help='Size to use, can be "id 1", "ram 256" etc')
  62. parser_create.add_argument(
  63. '--setup-node', action='store_true',
  64. help="Wait for the node to be created (this means the command just "
  65. "sits for a couple minutes) and then set up the server. It is suggested "
  66. "you also use --yes with this option.")
  67. parser_create.add_argument(
  68. '--wait', action='store_true',
  69. help="Wait for the node to be created, but don't setup (like "
  70. "--setup-node that just quits before it actually sets up the node)")
  71. parser_create.add_argument(
  72. '--if-not-exists', action='store_true',
  73. dest='if_not_exists',
  74. help="Only create the node if it does not exist (will check the cloud "
  75. "providers server list)")
  76. parser_setup = subcommands.add_parser(
  77. 'setup-node', help="Setup a new (fresh Ubuntu Jaunty install) server")
  78. parser_setup.add_argument(
  79. 'node',
  80. metavar='HOSTNAME',
  81. help="The hostname of the node to setup")
  82. parser_clean = subcommands.add_parser(
  83. 'clean-node', help="Clean unused application instances on a node")
  84. parser_clean.add_argument(
  85. 'node', nargs='?',
  86. metavar='HOSTNAME',
  87. help="Node to clean instances from")
  88. parser_clean.add_argument(
  89. '-n', '--simulate',
  90. action='store_true',
  91. help="Don't actually clean anything, just show what would be done")
  92. parser_update = subcommands.add_parser(
  93. 'update', help="Update/deploy an application")
  94. parser_update.add_argument(
  95. 'dir',
  96. help="The directory to upload to the server")
  97. parser_update.add_argument(
  98. 'location', nargs='?',
  99. metavar="HOSTNAME[/PATH]",
  100. help="Place to upload to (will default to the default_location "
  101. "setting in app.ini)")
  102. parser_update.add_argument(
  103. '--debug-single-process',
  104. action='store_true',
  105. help="Install as a 'debug' application, running in a single process with "
  106. "threads, so the application can be used with weberror or other debug "
  107. "tools.")
  108. parser_update.add_argument(
  109. '--name',
  110. metavar="NAME",
  111. help="'Name' of the site; defaults to the app_name setting in app.ini")
  112. parser_update.add_argument(
  113. '--node',
  114. metavar='NODE_HOSTNAME',
  115. help="The hostname of the node to upload to")
  116. parser_update.add_argument(
  117. '--clear',
  118. action='store_true',
  119. help='Clear the database on update (clears all database, files, etc!)')
  120. parser_update.add_argument(
  121. '--config',
  122. metavar='CONFIG_DIR',
  123. help="Configuration to use for this deployment of the application")
  124. parser_update.add_argument(
  125. '--noetchosts', help="Do not try to update /etc/hosts",
  126. dest='update_etc_hosts', action='store_const', const=False, default=True)
  127. parser_init = subcommands.add_parser(
  128. 'init', help="Create a new application file layout")
  129. parser_init.add_argument(
  130. 'dir',
  131. metavar='DIR',
  132. help="A directory to initialize")
  133. parser_init.add_argument(
  134. '-f', '--force',
  135. action='store_true',
  136. help="Overwrite files even if they already exist")
  137. parser_init.add_argument(
  138. '--distribute',
  139. action='store_true',
  140. help="Use Distribute (instead of Setuptools)")
  141. parser_init.add_argument(
  142. '--config',
  143. action='store_true',
  144. help="Put in a sample Paste Deploy config.ini")
  145. parser_init.add_argument(
  146. '--main',
  147. action='store_true',
  148. help="Put in a sample main.py")
  149. parser_serve = subcommands.add_parser(
  150. 'serve', help="Serve up an application for development")
  151. parser_serve.add_argument(
  152. 'dir',
  153. metavar='APP_DIR',
  154. help='Directory holding app')
  155. parser_serve.add_argument(
  156. '--port',
  157. metavar='PORT',
  158. default='8080',
  159. help='Port to serve on (default 8080)')
  160. parser_serve.add_argument(
  161. '--host',
  162. metavar='IP/INTERFACE',
  163. default='127.0.0.1',
  164. help='Host/IP/interface to serve on (127.0.0.1 is private, 0.0.0.0 is public)')
  165. parser_serve.add_argument(
  166. '--config',
  167. metavar='CONFIG_DIR',
  168. help="Configuration to use for the application")
  169. ## We can't handle "silver run" well with a subparser, because there's
  170. ## a bug in subparsers that they can't ignore arguments they don't
  171. ## understand. Because there will be arguments passed to the remote
  172. ## command we need that, so instead we create an entirely separate
  173. ## parser, and we'll do a little checking to see if the run command is
  174. ## given:
  175. parser_run = argparse.ArgumentParser(
  176. add_help=False,
  177. description="""\
  178. Run a command for an application; this runs a script in bin/ on the
  179. remote server.
  180. Use it like:
  181. silver run LOCATION import-something --option-for-import-something
  182. Note any arguments that point to existing files or directories will
  183. cause those files/directories to be uploaded, and substituted with the
  184. location of the remote name.
  185. This will run *Python* scripts in your application's bin/ directory,
  186. in another directory (e.g., silver run LOCATION src/myapp/script.py),
  187. or a global command (e.g., ipython).
  188. Use 'silver run LOCATION python' to get an interactive prompt on the
  189. remote server.
  190. """)
  191. parser_run.add_argument(
  192. '--node',
  193. metavar="NODE_HOSTNAME",
  194. help="Node to run command on")
  195. parser_run.add_argument(
  196. '-p', '--provider',
  197. metavar='NAME',
  198. help="The [provider:NAME] section from ~/.silverlining.conf to use (default [provider:default])",
  199. default="default")
  200. parser_run.add_argument(
  201. '-y', '--yes',
  202. action='store_true',
  203. help="Answer yes to any questions")
  204. parser_run.add_argument(
  205. '-i', '--interactive',
  206. action='store_true',
  207. help=("Tells ssh to force pseudo-tty allocation. Useful when what you're"
  208. " running is a shell of some sort"))
  209. #add_verbose(parser_run, add_log=True)
  210. parser_run.add_argument(
  211. 'location',
  212. help="Location where the application is running")
  213. parser_run.add_argument(
  214. 'script',
  215. help="script (in bin/) to run")
  216. parser_run.add_argument(
  217. '--user', metavar='USERNAME',
  218. default="www-data",
  219. help="The user to run the command as; default is www-data. "
  220. "Other options are www-mgr and root")
  221. parser_query = subcommands.add_parser(
  222. 'query', help="See what apps are on a node")
  223. parser_query.add_argument(
  224. 'node',
  225. metavar='HOSTNAME',
  226. help="Node to query")
  227. parser_query.add_argument(
  228. 'app-name', nargs='*',
  229. help="The app_name or hostname to query (wildcards allowed)")
  230. parser_activate = subcommands.add_parser(
  231. 'activate', help="Activate a site instance for a given domain")
  232. parser_activate.add_argument(
  233. '--node',
  234. metavar="NODE_HOSTNAME",
  235. help="Node to act on")
  236. parser_activate.add_argument(
  237. 'location',
  238. help="The hostname/path to act on")
  239. parser_activate.add_argument(
  240. 'instance_name',
  241. help="The instance name to activate (can also be \"prev\")")
  242. parser_deactivate = subcommands.add_parser(
  243. 'deactivate', help="Deactivate a site (leaving it dangling)")
  244. parser_deactivate.add_argument(
  245. '--node',
  246. metavar="NODE_HOSTNAME",
  247. help="Node to act on")
  248. parser_deactivate.add_argument(
  249. 'locations', nargs='+',
  250. help="The hostname/path to act on; if you give more than one, "
  251. "they must all be on the same node. Can be a wildcard.")
  252. parser_deactivate.add_argument(
  253. '--keep-prev', action='store_true',
  254. help="Keep the prev.* host activate (by default it is deleted)")
  255. # This is a mock for use with -h:
  256. parser_run_mock = subcommands.add_parser(
  257. 'run', help="Run a command on a remote host")
  258. parser_run_mock.add_argument(
  259. 'host',
  260. help="Host where the application is running")
  261. parser_run_mock.add_argument(
  262. 'script',
  263. help="script (in bin/) to run")
  264. parser_run_mock.add_argument(
  265. '--user', metavar='USERNAME',
  266. default="www-data",
  267. help="The user to run the command as; default is www-data. "
  268. "Other options are www-mgr and root")
  269. parser_backup = subcommands.add_parser(
  270. 'backup', help="Backup a deployed application's data")
  271. parser_backup.add_argument(
  272. 'location', metavar="LOCATION",
  273. help="The location to back up (i.e., the server and application)")
  274. parser_backup.add_argument(
  275. 'destination', metavar='DESTINATION',
  276. help="Destination to back up to. Can be a local file, remote:filename, "
  277. "site://location, ssh://hostname/location rsync://hostname/location")
  278. parser_restore = subcommands.add_parser(
  279. 'restore', help="Restore a backup")
  280. parser_restore.add_argument(
  281. 'backup', metavar="BACKUP",
  282. help="The backup to restore (file or archive)")
  283. parser_restore.add_argument(
  284. 'location', metavar="LOCATION",
  285. help="Location (hostname[/path]) to restore to")
  286. parser_clear = subcommands.add_parser(
  287. 'clear', help="Clear the data from an app")
  288. parser_clear.add_argument(
  289. 'location', metavar="LOCATION",
  290. help="Location (hostname[/path]) to clear")
  291. parser_diff = subcommands.add_parser(
  292. 'diff', help="Diff a local app and a deployed app")
  293. parser_diff.add_argument(
  294. 'dir', metavar='DIR',
  295. help="The application to diff")
  296. parser_diff.add_argument(
  297. 'location', metavar='HOST', nargs='?',
  298. help="the host to diff with")
  299. parser_diff.add_argument(
  300. '--app-name', metavar='NAME',
  301. help="A name besides app.ini:app_name")
  302. parser_diff.add_argument(
  303. '--instance-name', metavar='APP_NAME.DATE_VERSION',
  304. help="Diff with a specific instance (not the active one)")
  305. parser_create_config = subcommands.add_parser(
  306. 'create-config', help="Create configuration for an application")
  307. parser_create_config.add_argument(
  308. 'dir', metavar='DIR',
  309. help="The application to generate configuration for")
  310. parser_create_config.add_argument(
  311. 'output', nargs='?', metavar='DIR',
  312. help="The place to put the new configuration")
  313. parser_create_config.add_argument(
  314. 'variable', nargs='*',
  315. help="Assign a variable to use for generating the configuration")
  316. parser_create_config.add_argument(
  317. '--info', action='store_true',
  318. help="Show information about how the configuration is created")
  319. parser_disable = subcommands.add_parser(
  320. 'disable', help="Temporarily disable an application")
  321. parser_enable = subcommands.add_parser(
  322. 'enable', help="Re-enable a disabled application")
  323. for subparser in (parser_disable, parser_enable):
  324. group = subparser.add_mutually_exclusive_group(required=True)
  325. group.add_argument('--by-name', metavar="APPNAME",
  326. help="Identify the application by its name")
  327. group.add_argument('--by-location', metavar="LOCATION",
  328. help="Identify the application by its location")
  329. subparser.add_argument('--node', metavar="NODE_HOSTNAME",
  330. help="Node to act on")
  331. for subparser in subcommands._name_parser_map.values():
  332. subparser.add_argument(
  333. '-p', '--provider',
  334. metavar='NAME',
  335. help="The [provider:NAME] section from ~/.silverlining.conf to use (default [provider:default])",
  336. default="default")
  337. subparser.add_argument(
  338. '-y', '--yes',
  339. action='store_true',
  340. help="Answer yes to any questions")
  341. add_verbose(subparser, add_log=True)
  342. def catch_error(func):
  343. """Catch CommandError and turn it into an error message"""
  344. def decorated(*args, **kw):
  345. try:
  346. return func(*args, **kw)
  347. except CommandError, e:
  348. print e
  349. sys.exit(2)
  350. return decorated
  351. @catch_error
  352. def main():
  353. if not os.path.exists(createconf.silverlining_conf):
  354. print "%s doesn't exists; let's create it" % createconf.silverlining_conf
  355. createconf.create_conf()
  356. return
  357. if len(sys.argv) > 1 and sys.argv[1] == 'run':
  358. # Special case for silver run:
  359. if sys.argv[2:] in [['-h'], ['--help']]:
  360. # Don't generally catch this, as you might want to pass this option
  361. print parser_run.format_help()
  362. return 0
  363. args, unknown_args = parser_run.parse_known_args(sys.argv[2:])
  364. args.unknown_args = unknown_args
  365. args.command = 'run'
  366. else:
  367. args = parser.parse_args()
  368. create_logger(args)
  369. config = Config.from_config_file(
  370. createconf.silverlining_conf, 'provider:'+args.provider,
  371. args)
  372. name = args.command.replace('-', '_')
  373. mod_name = 'silverlining.commands.%s' % name
  374. __import__(mod_name)
  375. mod = sys.modules[mod_name]
  376. func = getattr(mod, 'command_%s' % name)
  377. return func(config)
  378. if __name__ == '__main__':
  379. main()