Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

rename

  • Loading branch information...
commit d4e5c418036b7ad7575d41d79efa8a516c8cf01d 0 parents
@russellhaering russellhaering authored
Showing with 30,075 additions and 0 deletions.
  1. +1 −0  .gitignore
  2. +203 −0 LICENSE.txt
  3. +111 −0 README.md
  4. +29 −0 bin/dreadnot
  5. +119 −0 lib/dreadnot.js
  6. +47 −0 lib/entry.js
  7. +28 −0 lib/plugins.js
  8. +105 −0 lib/plugins/email.js
  9. +73 −0 lib/plugins/irc.js
  10. +526 −0 lib/stack.js
  11. +189 −0 lib/util/buildbot.js
  12. +124 −0 lib/util/git.js
  13. +69 −0 lib/util/knife.js
  14. +74 −0 lib/util/misc.js
  15. +60 −0 lib/util/path_setup.js
  16. +128 −0 lib/util/sprintf.js
  17. +35 −0 lib/web/api.js
  18. +147 −0 lib/web/app.js
  19. +155 −0 lib/web/auth.js
  20. +182 −0 lib/web/handlers.js
  21. +22 −0 lib/web/middleware/index.js
  22. +59 −0 lib/web/middleware/logger.js
  23. +27 −0 lib/web/middleware/redirect.js
  24. +26 −0 lib/web/middleware/webhandler.js
  25. +63 −0 lib/web/view_helpers.js
  26. +19 −0 lib/web/views/deployment.jade
  27. +40 −0 lib/web/views/deployments.jade
  28. +36 −0 lib/web/views/layout.jade
  29. +19 −0 lib/web/views/login.jade
  30. +29 −0 lib/web/views/stacks.jade
  31. +1 −0  node_modules/.bin/express
  32. +1 −0  node_modules/.bin/jade
  33. +9 −0 node_modules/async/.gitmodules
  34. +19 −0 node_modules/async/LICENSE
  35. +21 −0 node_modules/async/Makefile
  36. +980 −0 node_modules/async/README.md
  37. +70 −0 node_modules/async/deps/nodeunit.css
  38. +1,966 −0 node_modules/async/deps/nodeunit.js
  39. +1 −0  node_modules/async/dist/async.min.js
  40. +3 −0  node_modules/async/index.js
  41. +686 −0 node_modules/async/lib/async.js
  42. +4 −0 node_modules/async/nodelint.cfg
  43. +16 −0 node_modules/async/package.json
  44. +1,541 −0 node_modules/async/test/test-async.js
  45. +24 −0 node_modules/async/test/test.html
  46. +11 −0 node_modules/connect/.npmignore
  47. +24 −0 node_modules/connect/LICENSE
  48. +2 −0  node_modules/connect/index.js
  49. +81 −0 node_modules/connect/lib/cache.js
  50. +106 −0 node_modules/connect/lib/connect.js
  51. +217 −0 node_modules/connect/lib/http.js
  52. +47 −0 node_modules/connect/lib/https.js
  53. +46 −0 node_modules/connect/lib/index.js
  54. +93 −0 node_modules/connect/lib/middleware/basicAuth.js
  55. +94 −0 node_modules/connect/lib/middleware/bodyParser.js
  56. +163 −0 node_modules/connect/lib/middleware/compiler.js
  57. +46 −0 node_modules/connect/lib/middleware/cookieParser.js
  58. +105 −0 node_modules/connect/lib/middleware/csrf.js
  59. +222 −0 node_modules/connect/lib/middleware/directory.js
  60. +100 −0 node_modules/connect/lib/middleware/errorHandler.js
  61. +76 −0 node_modules/connect/lib/middleware/favicon.js
  62. +82 −0 node_modules/connect/lib/middleware/limit.js
  63. +299 −0 node_modules/connect/lib/middleware/logger.js
  64. +38 −0 node_modules/connect/lib/middleware/methodOverride.js
  65. +100 −0 node_modules/connect/lib/middleware/profiler.js
  66. +40 −0 node_modules/connect/lib/middleware/query.js
  67. +34 −0 node_modules/connect/lib/middleware/responseTime.js
  68. +379 −0 node_modules/connect/lib/middleware/router.js
  69. +346 −0 node_modules/connect/lib/middleware/session.js
  70. +126 −0 node_modules/connect/lib/middleware/session/cookie.js
  71. +131 −0 node_modules/connect/lib/middleware/session/memory.js
  72. +137 −0 node_modules/connect/lib/middleware/session/session.js
  73. +87 −0 node_modules/connect/lib/middleware/session/store.js
  74. +225 −0 node_modules/connect/lib/middleware/static.js
  75. +175 −0 node_modules/connect/lib/middleware/staticCache.js
  76. +44 −0 node_modules/connect/lib/middleware/vhost.js
  77. +79 −0 node_modules/connect/lib/patch.js
  78. +75 −0 node_modules/connect/lib/public/directory.html
  79. +13 −0 node_modules/connect/lib/public/error.html
  80. BIN  node_modules/connect/lib/public/favicon.ico
  81. BIN  node_modules/connect/lib/public/icons/page.png
  82. BIN  node_modules/connect/lib/public/icons/page_add.png
  83. BIN  node_modules/connect/lib/public/icons/page_attach.png
  84. BIN  node_modules/connect/lib/public/icons/page_code.png
  85. BIN  node_modules/connect/lib/public/icons/page_copy.png
  86. BIN  node_modules/connect/lib/public/icons/page_delete.png
  87. BIN  node_modules/connect/lib/public/icons/page_edit.png
  88. BIN  node_modules/connect/lib/public/icons/page_error.png
  89. BIN  node_modules/connect/lib/public/icons/page_excel.png
  90. BIN  node_modules/connect/lib/public/icons/page_find.png
  91. BIN  node_modules/connect/lib/public/icons/page_gear.png
  92. BIN  node_modules/connect/lib/public/icons/page_go.png
  93. BIN  node_modules/connect/lib/public/icons/page_green.png
  94. BIN  node_modules/connect/lib/public/icons/page_key.png
  95. BIN  node_modules/connect/lib/public/icons/page_lightning.png
  96. BIN  node_modules/connect/lib/public/icons/page_link.png
  97. BIN  node_modules/connect/lib/public/icons/page_paintbrush.png
  98. BIN  node_modules/connect/lib/public/icons/page_paste.png
  99. BIN  node_modules/connect/lib/public/icons/page_red.png
  100. BIN  node_modules/connect/lib/public/icons/page_refresh.png
  101. BIN  node_modules/connect/lib/public/icons/page_save.png
  102. BIN  node_modules/connect/lib/public/icons/page_white.png
  103. BIN  node_modules/connect/lib/public/icons/page_white_acrobat.png
  104. BIN  node_modules/connect/lib/public/icons/page_white_actionscript.png
  105. BIN  node_modules/connect/lib/public/icons/page_white_add.png
  106. BIN  node_modules/connect/lib/public/icons/page_white_c.png
  107. BIN  node_modules/connect/lib/public/icons/page_white_camera.png
  108. BIN  node_modules/connect/lib/public/icons/page_white_cd.png
  109. BIN  node_modules/connect/lib/public/icons/page_white_code.png
  110. BIN  node_modules/connect/lib/public/icons/page_white_code_red.png
  111. BIN  node_modules/connect/lib/public/icons/page_white_coldfusion.png
  112. BIN  node_modules/connect/lib/public/icons/page_white_compressed.png
  113. BIN  node_modules/connect/lib/public/icons/page_white_copy.png
  114. BIN  node_modules/connect/lib/public/icons/page_white_cplusplus.png
  115. BIN  node_modules/connect/lib/public/icons/page_white_csharp.png
  116. BIN  node_modules/connect/lib/public/icons/page_white_cup.png
  117. BIN  node_modules/connect/lib/public/icons/page_white_database.png
  118. BIN  node_modules/connect/lib/public/icons/page_white_delete.png
  119. BIN  node_modules/connect/lib/public/icons/page_white_dvd.png
  120. BIN  node_modules/connect/lib/public/icons/page_white_edit.png
  121. BIN  node_modules/connect/lib/public/icons/page_white_error.png
  122. BIN  node_modules/connect/lib/public/icons/page_white_excel.png
  123. BIN  node_modules/connect/lib/public/icons/page_white_find.png
  124. BIN  node_modules/connect/lib/public/icons/page_white_flash.png
  125. BIN  node_modules/connect/lib/public/icons/page_white_freehand.png
  126. BIN  node_modules/connect/lib/public/icons/page_white_gear.png
  127. BIN  node_modules/connect/lib/public/icons/page_white_get.png
  128. BIN  node_modules/connect/lib/public/icons/page_white_go.png
  129. BIN  node_modules/connect/lib/public/icons/page_white_h.png
  130. BIN  node_modules/connect/lib/public/icons/page_white_horizontal.png
  131. BIN  node_modules/connect/lib/public/icons/page_white_key.png
  132. BIN  node_modules/connect/lib/public/icons/page_white_lightning.png
  133. BIN  node_modules/connect/lib/public/icons/page_white_link.png
  134. BIN  node_modules/connect/lib/public/icons/page_white_magnify.png
  135. BIN  node_modules/connect/lib/public/icons/page_white_medal.png
  136. BIN  node_modules/connect/lib/public/icons/page_white_office.png
  137. BIN  node_modules/connect/lib/public/icons/page_white_paint.png
  138. BIN  node_modules/connect/lib/public/icons/page_white_paintbrush.png
  139. BIN  node_modules/connect/lib/public/icons/page_white_paste.png
  140. BIN  node_modules/connect/lib/public/icons/page_white_php.png
  141. BIN  node_modules/connect/lib/public/icons/page_white_picture.png
  142. BIN  node_modules/connect/lib/public/icons/page_white_powerpoint.png
  143. BIN  node_modules/connect/lib/public/icons/page_white_put.png
  144. BIN  node_modules/connect/lib/public/icons/page_white_ruby.png
  145. BIN  node_modules/connect/lib/public/icons/page_white_stack.png
  146. BIN  node_modules/connect/lib/public/icons/page_white_star.png
  147. BIN  node_modules/connect/lib/public/icons/page_white_swoosh.png
  148. BIN  node_modules/connect/lib/public/icons/page_white_text.png
  149. BIN  node_modules/connect/lib/public/icons/page_white_text_width.png
  150. BIN  node_modules/connect/lib/public/icons/page_white_tux.png
  151. BIN  node_modules/connect/lib/public/icons/page_white_vector.png
  152. BIN  node_modules/connect/lib/public/icons/page_white_visualstudio.png
  153. BIN  node_modules/connect/lib/public/icons/page_white_width.png
  154. BIN  node_modules/connect/lib/public/icons/page_white_word.png
  155. BIN  node_modules/connect/lib/public/icons/page_white_world.png
  156. BIN  node_modules/connect/lib/public/icons/page_white_wrench.png
  157. BIN  node_modules/connect/lib/public/icons/page_white_zip.png
  158. BIN  node_modules/connect/lib/public/icons/page_word.png
  159. BIN  node_modules/connect/lib/public/icons/page_world.png
  160. +141 −0 node_modules/connect/lib/public/style.css
  161. +451 −0 node_modules/connect/lib/utils.js
  162. +19 −0 node_modules/connect/node_modules/mime/LICENSE
  163. +50 −0 node_modules/connect/node_modules/mime/README.md
  164. +92 −0 node_modules/connect/node_modules/mime/mime.js
  165. +22 −0 node_modules/connect/node_modules/mime/package.json
  166. +79 −0 node_modules/connect/node_modules/mime/test.js
  167. +1,479 −0 node_modules/connect/node_modules/mime/types/mime.types
  168. +43 −0 node_modules/connect/node_modules/mime/types/node.types
  169. +1 −0  node_modules/connect/node_modules/qs/.gitignore
  170. +6 −0 node_modules/connect/node_modules/qs/.gitmodules
  171. +63 −0 node_modules/connect/node_modules/qs/History.md
  172. +5 −0 node_modules/connect/node_modules/qs/Makefile
  173. +47 −0 node_modules/connect/node_modules/qs/Readme.md
  174. +17 −0 node_modules/connect/node_modules/qs/benchmark.js
  175. +48 −0 node_modules/connect/node_modules/qs/examples.js
  176. +2 −0  node_modules/connect/node_modules/qs/index.js
  177. +262 −0 node_modules/connect/node_modules/qs/lib/querystring.js
  178. +16 −0 node_modules/connect/node_modules/qs/package.json
  179. +2 −0  node_modules/connect/node_modules/qs/test/mocha.opts
  180. +155 −0 node_modules/connect/node_modules/qs/test/parse.js
  181. +95 −0 node_modules/connect/node_modules/qs/test/stringify.js
  182. +24 −0 node_modules/connect/package.json
  183. +11 −0 node_modules/connect/test.js
  184. +2 −0  node_modules/emailjs/.gitignore
  185. +25 −0 node_modules/emailjs/LICENSE
  186. +137 −0 node_modules/emailjs/Readme.md
  187. +3 −0  node_modules/emailjs/email.js
  188. +23 −0 node_modules/emailjs/package.json
  189. +416 −0 node_modules/emailjs/smtp/address.js
  190. +173 −0 node_modules/emailjs/smtp/client.js
  191. +16 −0 node_modules/emailjs/smtp/error.js
  192. +360 −0 node_modules/emailjs/smtp/message.js
  193. +76 −0 node_modules/emailjs/smtp/response.js
  194. +568 −0 node_modules/emailjs/smtp/smtp.js
  195. +64 −0 node_modules/emailjs/smtp/tls.js
  196. BIN  node_modules/emailjs/test/attachments/The Last Question - Isaac Asimov.pdf
  197. +779 −0 node_modules/emailjs/test/attachments/smtp.html
  198. +16 −0 node_modules/emailjs/test/config.js.empty
  199. +130 −0 node_modules/emailjs/test/run.js
  200. +63 −0 node_modules/emailjs/test/tests.js
  201. +7 −0 node_modules/express/.npmignore
  202. +755 −0 node_modules/express/History.md
  203. +22 −0 node_modules/express/LICENSE
  204. +35 −0 node_modules/express/Makefile
  205. +143 −0 node_modules/express/Readme.md
  206. +418 −0 node_modules/express/bin/express
  207. +2 −0  node_modules/express/index.js
  208. +79 −0 node_modules/express/lib/express.js
  209. +583 −0 node_modules/express/lib/http.js
  210. +52 −0 node_modules/express/lib/https.js
  211. +321 −0 node_modules/express/lib/request.js
  212. +460 −0 node_modules/express/lib/response.js
  213. +53 −0 node_modules/express/lib/router/collection.js
  214. +398 −0 node_modules/express/lib/router/index.js
  215. +70 −0 node_modules/express/lib/router/methods.js
  216. +88 −0 node_modules/express/lib/router/route.js
  217. +152 −0 node_modules/express/lib/utils.js
  218. +457 −0 node_modules/express/lib/view.js
  219. +40 −0 node_modules/express/lib/view/partial.js
  220. +210 −0 node_modules/express/lib/view/view.js
  221. +19 −0 node_modules/express/node_modules/mime/LICENSE
  222. +50 −0 node_modules/express/node_modules/mime/README.md
  223. +92 −0 node_modules/express/node_modules/mime/mime.js
  224. +22 −0 node_modules/express/node_modules/mime/package.json
  225. +79 −0 node_modules/express/node_modules/mime/test.js
  226. +1,479 −0 node_modules/express/node_modules/mime/types/mime.types
  227. +43 −0 node_modules/express/node_modules/mime/types/node.types
  228. +2 −0  node_modules/express/node_modules/mkdirp/.gitignore
  229. +2 −0  node_modules/express/node_modules/mkdirp/.gitignore.orig
  230. +5 −0 node_modules/express/node_modules/mkdirp/.gitignore.rej
  231. +21 −0 node_modules/express/node_modules/mkdirp/LICENSE
  232. +21 −0 node_modules/express/node_modules/mkdirp/README.markdown
  233. +6 −0 node_modules/express/node_modules/mkdirp/examples/pow.js
  234. +6 −0 node_modules/express/node_modules/mkdirp/examples/pow.js.orig
  235. +19 −0 node_modules/express/node_modules/mkdirp/examples/pow.js.rej
  236. +20 −0 node_modules/express/node_modules/mkdirp/index.js
  237. +23 −0 node_modules/express/node_modules/mkdirp/package.json
  238. +28 −0 node_modules/express/node_modules/mkdirp/test/mkdirp.js
  239. +41 −0 node_modules/express/node_modules/mkdirp/test/race.js
  240. +32 −0 node_modules/express/node_modules/mkdirp/test/rel.js
  241. +1 −0  node_modules/express/node_modules/qs/.gitignore
  242. +6 −0 node_modules/express/node_modules/qs/.gitmodules
  243. +63 −0 node_modules/express/node_modules/qs/History.md
  244. +5 −0 node_modules/express/node_modules/qs/Makefile
  245. +47 −0 node_modules/express/node_modules/qs/Readme.md
  246. +17 −0 node_modules/express/node_modules/qs/benchmark.js
  247. +48 −0 node_modules/express/node_modules/qs/examples.js
  248. +2 −0  node_modules/express/node_modules/qs/index.js
  249. +262 −0 node_modules/express/node_modules/qs/lib/querystring.js
  250. +16 −0 node_modules/express/node_modules/qs/package.json
  251. +2 −0  node_modules/express/node_modules/qs/test/mocha.opts
  252. +155 −0 node_modules/express/node_modules/qs/test/parse.js
  253. +95 −0 node_modules/express/node_modules/qs/test/stringify.js
  254. +39 −0 node_modules/express/package.json
  255. +17 −0 node_modules/express/testing/index.js
  256. +1 −0  node_modules/express/testing/views/users.jade
  257. +2 −0  node_modules/irc/.gitignore
  258. +674 −0 node_modules/irc/COPYING
  259. +86 −0 node_modules/irc/README.rst
  260. +10 −0 node_modules/irc/a.js
  261. +394 −0 node_modules/irc/docs/API.rst
  262. +130 −0 node_modules/irc/docs/Makefile
  263. +216 −0 node_modules/irc/docs/conf.py
  264. +13 −0 node_modules/irc/docs/index.rst
  265. +170 −0 node_modules/irc/docs/make.bat
  266. +51 −0 node_modules/irc/example/bot.js
  267. +65 −0 node_modules/irc/example/secure.js
  268. +486 −0 node_modules/irc/lib/codes.js
  269. +27 −0 node_modules/irc/lib/colors.js
  270. +751 −0 node_modules/irc/lib/irc.js
  271. +21 −0 node_modules/irc/package.json
  272. +29 −0 node_modules/irc/test.js
  273. +4 −0 node_modules/jade/.gitignore
  274. +21 −0 node_modules/jade/.gitmodules
  275. +4 −0 node_modules/jade/.npmignore
  276. +474 −0 node_modules/jade/History.md
  277. +22 −0 node_modules/jade/LICENSE
  278. +39 −0 node_modules/jade/Makefile
  279. +966 −0 node_modules/jade/Readme.md
  280. +125 −0 node_modules/jade/bin/jade
  281. +8 −0 node_modules/jade/examples/attributes.jade
  282. +11 −0 node_modules/jade/examples/attributes.js
  283. +13 −0 node_modules/jade/examples/code.jade
  284. +16 −0 node_modules/jade/examples/code.js
  285. +5 −0 node_modules/jade/examples/csrf.jade
  286. +42 −0 node_modules/jade/examples/csrf.js
  287. +5 −0 node_modules/jade/examples/dynamicscript.jade
  288. +20 −0 node_modules/jade/examples/dynamicscript.js
  289. +3 −0  node_modules/jade/examples/each.jade
  290. +16 −0 node_modules/jade/examples/each.js
  291. +10 −0 node_modules/jade/examples/extend-layout.jade
  292. +11 −0 node_modules/jade/examples/extend.jade
  293. +18 −0 node_modules/jade/examples/extend.js
  294. +29 −0 node_modules/jade/examples/form.jade
  295. +18 −0 node_modules/jade/examples/form.js
  296. +7 −0 node_modules/jade/examples/includes.jade
  297. +11 −0 node_modules/jade/examples/includes.js
  298. +2 −0  node_modules/jade/examples/includes/foot.jade
  299. +6 −0 node_modules/jade/examples/includes/head.jade
  300. +2 −0  node_modules/jade/examples/includes/scripts.jade
Sorry, we could not display the entire diff because too many files (1,553) changed.
1  .gitignore
@@ -0,0 +1 @@
+local_settings.js
203 LICENSE.txt
@@ -0,0 +1,203 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
111 README.md
@@ -0,0 +1,111 @@
+# Dreadnot - deploy without dread
+
+dreadnot is a 'one click' deploy tool written in nodejs.
+
+## Configuration
+
+Dreadnot loads its configuration from a javascript file:
+
+```javascript
+exports.config = {
+ // The name of this Dreadnot instance, used for display
+ name: 'Example Dreadnot',
+
+ // Each Dreadnot instance supports one environment such as 'dev', 'staging'
+ // or 'production'
+ env: 'staging',
+
+ // The data root Dreadnot will use
+ data_root: '/var/dreadnot',
+
+ // Dreadnot uses an htpasswd file (with support for md5 and sha1) for auth
+ htpasswd_file: '/etc/dreadnot/htpasswd',
+
+ // Each stack represents a code base that should be deployed to one or more regions
+ stacks: {
+
+ // For a stack named 'webapp', there should be a 'webapp.js' file in the
+ // stacks directory
+ webapp: {
+ // What branch to look in for the latest revision of the code base
+ tip: 'master',
+
+ // How long to cache the latest revision of the code base
+ tip_ttl: 120 * 1000,
+
+ // What regions this stack should be deployed to
+ regions: ['ord1'],
+
+ // Stacks should implement dryrun for testing
+ dryrun: true
+ }
+ },
+
+ // The GitHub organization you provide is used to build URLs for your stacks
+ github: {
+ organization: 'racker'
+ },
+
+ // Plugins provide optional functionality such as notifications. Any plugins
+ // that are not configured won't be used.
+ plugins: {
+
+ // An IRC notification plugin
+ irc: {
+ nick: 'staging-dreadnot',
+ channels: {'irc.freenode.net': ['#public-channel', '#private-channel pass']}
+ },
+
+ // An email notification plugin
+ email: {
+ server: {
+ user: 'staging-dreadnot@example.com',
+ password: '',
+ host: 'smtp.example.com',
+ ssl: true
+ },
+ to: 'systems@example.com',
+ from: 'staging-dreadnot@example.com'
+ }
+ }
+};
+```
+
+## Stacks
+
+Dreadnot looks in a directory (by default `./stacks`, but this can be changed
+from the command line) for "stack files". A stack file is simply a javascript
+file that exports
+
+* A `get_deployedRevision` function which takes an object containing
+ `environment` and `region` fields, and a callback taking `(err,
+ deployed_revision)`.
+* A `targets` hash that maps target names to lists of task names. Currently,
+ the only supported target is `deploy` which defaults to
+ `['task_preDeploy', 'task_deploy', 'task_postDeploy']`.
+* One or more "task functions" whose names are prefixed with `task_`. Each
+ task function takes:
+ 1. A "stack" object. The most useful fields on the stack are `stackConfig`
+ which contains the config for this particular stack, and `config` which
+ contains the global config.
+ 2. A "baton" object. Each task executed during a run of a given target
+ receives the same baton object. By default, it contains a `log` field
+ with methods such as `debug`, `info`, and `error` that can be used to
+ log output to deployment log and web view.
+ 3. An "args" hash with `dryrun`, `environment`, `region`, `revision` and
+ `user`, each of which is a string.
+ 4. A "callback" function that should be called without arguments on
+ completion, or with a single error object if an error occurs.
+
+
+## Running Dreadnot
+
+Dreadnot takes a number of options on the command line. The defaults are:
+
+```
+ dreadnot -c ./local_settings.js -s ./stacks -p 8000
+
+```
+
+This will start dreadnot with the specified config file and stack directories,
+listening on port 8000.
29 bin/dreadnot
@@ -0,0 +1,29 @@
+#!/usr/bin/env node
+
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var pathSetup = require('../lib/util/path_setup').pathSetup;
+pathSetup();
+
+var entry = require('entry');
+
+function main() {
+ entry.run();
+}
+
+main();
119 lib/dreadnot.js
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var events = require('events');
+var util = require('util');
+var path = require('path');
+
+var async = require('async');
+var log = require('logmagic').local('dreadnot');
+
+var sprintf = require('./util/sprintf');
+
+var Stack = require('./stack').Stack;
+
+function NotFoundError(message) {
+ this.name = 'NotFoundError';
+ this.message = message;
+ Error.captureStackTrace(this, NotFoundError);
+};
+
+util.inherits(NotFoundError, Error);
+
+
+
+function Deployinator(config, stackdir) {
+ log.info('using config', config);
+ this.config = config;
+ this.stackdir = stackdir;
+ this._stacks = {};
+ this.emitter = new events.EventEmitter();
+}
+
+
+Deployinator.prototype._getStack = function(name) {
+ if (!this._stacks[name]) {
+ this._stacks[name] = new Stack(name, this, this.config);
+ }
+
+ return this._stacks[name];
+};
+
+
+Deployinator.prototype.emit = function(id, data) {
+ this.emitter.emit(id, data);
+};
+
+
+Deployinator.prototype.getStack = function(name, callback) {
+ var stack = this._getStack(name);
+ if (!stack) {
+ callback(new NotFoundError('Stack not found'));
+ } else {
+ callback(null, stack);
+ }
+};
+
+
+Deployinator.prototype.init = function(callback) {
+ var self = this,
+ stackNames = Object.keys(this.config.stacks);
+
+ async.forEach(stackNames, function(stackName, callback) {
+ self._getStack(stackName).init(callback);
+ }, callback);
+};
+
+
+Deployinator.prototype.getName = function() {
+ return this.config.name;
+};
+
+
+Deployinator.prototype.getSummary = function(callback) {
+ callback(null, {name: this.config.env});
+};
+
+
+Deployinator.prototype.getDetails = function(callback) {
+ var self = this,
+ stackNames = Object.keys(this.config.stacks);
+
+ async.map(stackNames, function(stackName, callback) {
+ self._getStack(stackName).getDetails(callback);
+ },
+
+ function(err, summaries) {
+ callback(err, {
+ name: self.config.env,
+ stacks: summaries
+ });
+ });
+};
+
+
+Deployinator.prototype.runningStatus = function(callback) {
+ var self = this,
+ stackNames = Object.keys(this.config.stacks);
+
+ async.map(stackNames, function(stackName, callback) {
+ self._getStack(stackName).runningStatus(callback);
+ }, callback);
+};
+
+
+exports.Deployinator = Deployinator;
47 lib/entry.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var log = require('logmagic').local('entry');
+var optimist = require('optimist')
+
+var webApp = require('./web/app');
+var Deployinator = require('./dreadnot').Deployinator;
+var plugins = require('./plugins');
+
+exports.run = function() {
+ var argv, config, d;
+
+ optimist = optimist.usage('Usage: $0 -p [port] -c [/path/to/settings.js] -s [/path/to/stack/directory/]');
+ optimist = optimist['default']('p', 8000);
+ optimisc = optimist['default']('c', '../local_settings');
+ optimisc = optimist['default']('s', './stacks');
+ argv = optimist.argv;
+
+ config = require(argv.c).config;
+ d = new Deployinator(config, argv.s);
+
+ d.init(function(err) {
+ if (err) {
+ log.error('error initializing dreadnot', {err: err});
+ process.exit(1);
+ return;
+ }
+
+ plugins.run(d);
+ webApp.run(argv.p, d);
+ });
+};
28 lib/plugins.js
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var sprintf = require('./util/sprintf');
+
+
+exports.run = function(dreadnot) {
+ var plugins = dreadnot.config.plugins || {},
+ k;
+
+ for (k in plugins) {
+ require(sprintf('./plugins/%s', k)).run(dreadnot);
+ }
+};
105 lib/plugins/email.js
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var async = require('async');
+var email = require('emailjs');
+var log = require('logmagic').local('plugins.email');
+
+var helpers = require('web/view_helpers');
+var sprintf = require('util/sprintf');
+
+var BEG_SUBJ_FMT = 'Deployment #%s of %s to %s:%s started';
+var BEG_TEXT_FMT = [
+ 'user: %s',
+ 'started: %s',
+ 'revisions: %s -> %s',
+ 'diff link: %s'
+];
+
+var END_SUBJ_FMT = 'Deployment #%s of %s to %s:%s: %s';
+var END_TEXT_FMT = [
+ 'user: %s',
+ 'started: %s',
+ 'revisions: %s -> %s',
+ 'diff link: %s',
+ 'log:',
+ '',
+ '%s'
+];
+
+
+exports.run = function(dreadnot) {
+ var config = dreadnot.config.plugins.email;
+
+ dreadnot.emitter.on('deployments', function(deployment) {
+ var endPath = ['stacks', deployment.stack, 'regions', deployment.region, 'deployments', deployment.deployment, 'end'].join('.'),
+ logPath = ['stacks', deployment.stack, 'regions', deployment.region, 'deployments', deployment.deployment, 'log'].join('.'),
+ logEvents = [],
+ server, message;
+
+ server = email.server.connect(config.server);
+
+ message = email.message.create({
+ to: config.to,
+ from: config.from,
+ subject: sprintf(BEG_SUBJ_FMT, deployment.deployment, deployment.stack, dreadnot.config.env, deployment.region),
+ text: sprintf(BEG_TEXT_FMT.join('\n'),
+ deployment.user,
+ new Date(deployment.time).toUTCString(),
+ helpers.trimRevision(deployment.from_revision), helpers.trimRevision(deployment.to_revision),
+ helpers.ghDiffUrl(deployment.github_href, deployment.from_revision, deployment.to_revision))
+ });
+
+ server.send(message, function(err) {
+ if (err) {
+ log.error('error sending deployment email', {
+ deployment: deployment,
+ err: err
+ });
+ }
+ });
+
+ dreadnot.emitter.on(logPath, function(logEvent) {
+ logEvents.push(logEvent);
+ });
+
+ dreadnot.emitter.once(endPath, function(success) {
+ var endMessage = email.message.create({
+ to: config.to,
+ from: config.from,
+ subject: sprintf(END_SUBJ_FMT, deployment.deployment, deployment.stack, dreadnot.config.env, deployment.region,
+ success ? 'SUCCESS': 'FAILURE'),
+ text: sprintf(END_TEXT_FMT.join('\n'),
+ deployment.user,
+ new Date(deployment.time).toUTCString(),
+ helpers.trimRevision(deployment.from_revision), helpers.trimRevision(deployment.to_revision),
+ helpers.ghDiffUrl(deployment.github_href, deployment.from_revision, deployment.to_revision),
+ logEvents.map(function(logEvent) {
+ return logEvent.msg
+ }).join('\n'))
+ });
+
+ server.send(endMessage, function(err) {
+ log.error('error sending deployment end email', {
+ deployment: deployment,
+ success: success,
+ err: err
+ });
+ });
+ });
+ });
+};
73 lib/plugins/irc.js
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var async = require('async');
+var irc = require('irc');
+
+var sprintf = require('util/sprintf');
+
+exports.run = function(dreadnot) {
+ var clients = {},
+ joined = {},
+ config = dreadnot.config.plugins.irc;
+
+ async.forEach(Object.keys(config.channels), function(network, callback) {
+ var client = new irc.Client(network, config.nick);
+
+ client.on('registered', function() {
+ var i;
+
+ clients[network] = client;
+ joined[network] = [];
+
+ client.on('join', function(channel) {
+ joined[network].push(channel);
+ });
+
+ for (i = 0; i < config.channels[network].length; i++) {
+ clients[network].join(config.channels[network][i]);
+ }
+ });
+ });
+
+ dreadnot.emitter.on('deployments', function(deployment) {
+ var msg = sprintf('%s is deploying %s to %s:%s', deployment.user, deployment.stack, dreadnot.config.env, deployment.region),
+ endPath = ['stacks', deployment.stack, 'regions', deployment.region, 'deployments', deployment.deployment, 'end'].join('.'),
+ network, i;
+ onEach(clients, joined, function(client, channel) {
+ client.notice(channel, msg);
+ });
+
+ dreadnot.emitter.once(endPath, function(success) {
+ var endMsg = sprintf('deployment #%s of %s to %s:%s %s', deployment.deployment, deployment.stack, dreadnot.config.env,
+ deployment.region, success ? 'succeeded' : 'failed');
+ onEach(clients, joined, function(client, channel) {
+ client.notice(channel, endMsg);
+ });
+ });
+ });
+};
+
+
+
+function onEach(clients, joined, fn) {
+ for (network in joined) {
+ for (i = 0; i < joined[network].length; i++) {
+ fn(clients[network], joined[network][i]);
+ }
+ }
+}
526 lib/stack.js
@@ -0,0 +1,526 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var events = require('events');
+var fs = require('fs');
+var path = require('path');
+
+var async = require('async');
+var logmagic = require('logmagic');
+var mkdirp = require('mkdirp');
+
+var misc = require('./util/misc');
+var git = require('./util/git');
+var sprintf = require('./util/sprintf');
+
+var DEFAULT_TARGETS = {
+ 'deploy': ['task_predeploy', 'task_deploy', 'task_postdeploy']
+};
+
+
+var PAGE_SIZE = 10;
+
+
+
+/**
+ * Stack Constructor.
+ * @constructor
+ * @param {String} name The name of this stack.
+ * @param {Object} config The global config.
+ */
+function Stack(name, dreadnot, config) {
+ var self = this,
+ logName = sprintf('deploy.stack.%s', name),
+ sinkName = sprintf('stack.%s', name);
+
+ this.name = name;
+ this.dreadnot = dreadnot;
+ this.module = require(path.join(path.resolve(dreadnot.stackdir), name));
+ this.config = config;
+ this.stackConfig = config.stacks[name];
+ this.log = logmagic.local(logName);
+ this.logRoot = path.join(config.data_root, 'logs', name);
+ this.newestDeployments = {};
+ this.locked = false;
+ this.current = null;
+ this._cache = {};
+ this._waiting = {};
+
+ logmagic.registerSink(sinkName, function(moduleName, lvl, msg, obj) {
+ var both = moduleName.split('.').slice(-2),
+ logPath = ['regions', both[0], 'deployments', both[1], 'log'].join('.');
+ self.emit(logPath, {
+ lvl: lvl,
+ msg: msg,
+ obj: obj
+ });
+ });
+ logmagic.route(sprintf('%s.*', logName), logmagic.INFO, sinkName);
+}
+
+
+Stack.prototype.init = function(callback) {
+ var self = this;
+
+ async.forEach(this.stackConfig.regions, function(region, callback) {
+ self.log.debug('ensuring region log directory', {
+ stack: self.name,
+ region: region
+ });
+ async.series([
+ mkdirp.bind(null, path.join(self.logRoot, region), 0755),
+
+ function(callback) {
+ self._findNewestRegionDeployment(region, function(err, number) {
+ self.newestDeployments[region] = number;
+ callback(err);
+ });
+ }
+ ], callback);
+ }, callback);
+};
+
+
+Stack.prototype.emit = function(id, data) {
+ this.dreadnot.emit(sprintf('stacks.%s.%s', this.name, id), data);
+};
+
+
+Stack.prototype._getCached = function(name, ttl, getter, callback) {
+ var self = this,
+ now = Date.now(),
+ both = this._cache[name];
+
+ if (this._waiting[name] !== undefined) {
+ // Cache is being refreshed, wait for it
+ this._waiting[name].push(callback);
+ } else if (!both || now > both[0]) {
+ // Cache needs refresh
+ this._waiting[name] = [callback];
+
+ getter(function(err, val) {
+ if (err) {
+ delete self._cache[name];
+ self._waiting[name].forEach(function(callback) {
+ callback(err);
+ });
+ } else {
+ self._cache[name] = [Date.now() + ttl, val];
+ self._waiting[name].forEach(function(callback) {
+ callback(null, val);
+ });
+ }
+ delete self._waiting[name];
+ });
+ } else {
+ // Cache is fresh
+ callback(null, both[1]);
+ }
+};
+
+
+Stack.prototype.getRepoUrl = function() {
+ return sprintf('git@github.com:%s/%s.git', this.config.github.organization, this.name);
+};
+
+
+Stack.prototype.getGitHubBaseUrl = function() {
+ return sprintf('https://github.com/%s/%s', this.config.github.organization, this.name);
+};
+
+
+Stack.prototype.getGitHubCommitUrl = function(rev) {
+ return sprintf('%s/commit/%s', this.getGitHubBaseUrl(), rev);
+};
+
+Stack.prototype.getGitHubDiffUrl = function(revFrom, revTo) {
+ return sprintf('%s/compare/%s...%s', this.getGitHubBaseUrl(), revFrom, revTo);
+};
+
+
+/**
+ * Get all the Targets for the stack.
+ * @return {Array} All the tasks for a given stack.
+ */
+Stack.prototype.getTarget = function(name) {
+ return this.module.targets[name] || DEFAULT_TARGETS[name];
+};
+
+
+/**
+ * Run a given target.
+ * @param {String} name The target to run.
+ * @param {String} region The region to deploy to.
+ * @param {String} revision The revision to deploy.
+ * @param {String} user The name of the user responsible.
+ * @param {Function} callback Completion callback(err).
+ */
+Stack.prototype.run = function(name, region, revision, user, finalCallback) {
+ var self = this,
+ target = self.getTarget(name),
+ baton = {},
+ args = {
+ dryrun: this.stackConfig.dryrun,
+ environment: this.config.env,
+ region: region,
+ revision: revision,
+ user: user
+ },
+ start, number, tasks;
+
+ if (this.newestDeployments[region] === undefined) {
+ finalCallback(new Error('No such region'));
+ return;
+ }
+
+ if (target === undefined) {
+ finalCallback(new Error('No such target'));
+ return;
+ }
+
+ if (this.locked) {
+ finalCallback(new Error('Stack is locked'));
+ return;
+ }
+
+ this.locked = region;
+ number = ++this.newestDeployments[region];
+ baton.log = logmagic.local(sprintf('deploy.stack.%s.%s.%s', this.name, region, number));
+
+ tasks = target.map(function(taskName) {
+ return function(callback) {
+ baton.log.infof('executing task ${task}', {
+ task: taskName
+ });
+ self.module[taskName](self, baton, args, callback);
+ };
+ });
+
+ async.waterfall([
+ self.getRegionSummary.bind(self, region),
+
+ function run(summary, callback) {
+ start = Date.now();
+ self.current = {
+ name: number,
+ stackName: self.name,
+ region: region,
+ environment: self.config.env,
+ from_revision: summary.deployed_revision,
+ to_revision: revision,
+ time: start,
+ user: user,
+ finished: false,
+ success: false,
+ log: []
+ },
+ epath = ['stacks', self.name, 'regions', region, 'deployments', number, 'log'].join('.');
+
+ function onLog(item) {
+ self.current.log.push(item);
+ }
+
+ self.dreadnot.emitter.on(epath, onLog);
+
+ self.dreadnot.emit('deployments', {
+ user: user,
+ stack: self.name,
+ stackName: self.name,
+ region: region,
+ environment: self.config.env,
+ deployment: number,
+ from_revision: summary.deployed_revision,
+ to_revision: revision,
+ github_href: self.getGitHubBaseUrl(),
+ time: start
+ });
+
+ baton.log.info(sprintf('Starting deployment %s of target \'%s\'', number, name));
+
+ async.series(tasks, function(err) {
+ var seconds = (Date.now() - start) / 1000;
+
+ if (err) {
+ baton.log.error(sprintf('Target \'%s\' FAILED in %ss', name, seconds), err);
+ } else {
+ self.current.success = true;
+ baton.log.info(sprintf('Target \'%s\' SUCCESS in %ss', name, seconds));
+ }
+
+ self.emit(['regions', region, 'deployments', number, 'end'].join('.'), self.current.success);
+
+ self.dreadnot.emitter.removeListener(epath, onLog);
+ self.current.finished = true;
+
+ callback();
+ });
+
+ // Don't return to the user until the summary is generated and the
+ // deployment is started
+ finalCallback(null, number);
+ },
+
+ function(callback) {
+ var logPath = path.join(self.logRoot, region, sprintf('%s.json', number));
+ fs.writeFile(logPath, JSON.stringify(self.current, null, 4), callback);
+ }
+ ],
+
+ function(err) {
+ self.locked = false;
+ self.current = null;
+ });
+};
+
+
+/**
+ * Retrieve the deployed version
+ * @param {String} region The region to check.
+ * @param {Function} callback A callback fired with (err, rev).
+ */
+Stack.prototype.getDeployedRevision = function(region, callback) {
+ var args = {
+ environment: this.config.env,
+ region: region
+ };
+ this.module.get_deployedRevision.call(this, args, callback);
+};
+
+
+Stack.prototype.getSummary = function(callback) {
+ var self = this,
+ getter = git.getLatestRevision.bind(git, this.getRepoUrl(), this.stackConfig.tip);
+
+ this._getCached('latest_revision', this.stackConfig.tip_ttl, getter, function(err, rev) {
+ callback(err, err ? null : {
+ name: self.name,
+ github_href: self.getGitHubBaseUrl(),
+ latest_revision: rev
+ });
+ });
+};
+
+
+Stack.prototype.getDetails = function(callback) {
+ var self = this;
+
+ async.parallel([
+ self.getSummary.bind(self),
+
+ self.dreadnot.getSummary.bind(self.dreadnot),
+
+ function getRegionSummaries(callback) {
+ async.map(self.stackConfig.regions, self.getRegionSummary.bind(self), callback);
+ }
+ ],
+
+ function(err, results) {
+ var summary;
+ if (err) {
+ callback(err);
+ } else {
+ summary = results[0];
+ summary.env = results[1];
+ summary.regions = results[2];
+ callback(null, summary);
+ }
+ });
+};
+
+
+/**
+ * Find the newest deployment for a region.
+ */
+Stack.prototype._findNewestRegionDeployment = function(region, callback) {
+ var logRoot = path.join(this.logRoot, region);
+
+ fs.readdir(logRoot, function(err, files) {
+ var i, max = 0;
+
+ if (err) {
+ callback(err);
+ } else {
+ for (i = 0; i < files.length; i++) {
+ if (files[i].match(/^\d+\.json$/)) {
+ max = Math.max(max, parseInt(files[i].split('.')[0]));
+ }
+ }
+ callback(null, max);
+ }
+ });
+};
+
+
+Stack.prototype.getRegionSummary = function(region, callback) {
+ var self = this;
+
+ async.parallel([
+ self.getDeployedRevision.bind(self, region),
+
+ function getLatestDeployment(callback) {
+ if (self.newestDeployments[region] === 0) {
+ callback(null, null);
+ } else {
+ self.getDeploymentSummary(region, self.newestDeployments[region], callback);
+ }
+ }
+ ],
+
+ function(err, results) {
+ if (err) {
+ callback(err);
+ } else {
+ callback(null, {
+ name: region,
+ deployed_revision: results[0],
+ latest_deployment: results[1]
+ });
+ }
+ });
+};
+
+
+Stack.prototype.getRegionDetails = function(region, page, callback) {
+ var self = this,
+ newest = this.newestDeployments[region],
+ numbers = [],
+ i;
+
+ if (newest === undefined) {
+ callback(new Error('Region not found'));
+ return;
+ }
+
+ for (i = newest; i > Math.max(newest - PAGE_SIZE, 0); i--) {
+ numbers.push(i);
+ }
+
+ async.parallel([
+ self.getRegionSummary.bind(self, region),
+
+ self.getSummary.bind(self),
+
+ self.dreadnot.getSummary.bind(self.dreadnot),
+
+ function getDeployments(callback) {
+ async.map(numbers, self.getDeploymentSummary.bind(self, region), callback);
+ }
+ ],
+
+ function(err, results) {
+ var details;
+
+ if (err) {
+ callback(err);
+ } else {
+ details = results[0];
+ details.stack = results[1];
+ details.env = results[2];
+ details.deployments = results[3];
+ callback(null, details);
+ }
+ });
+};
+
+
+Stack.prototype.getDeploymentSummary = function(region, number, callback) {
+ var self = this,
+ summary = {},
+ k;
+
+ if (this.newestDeployments[region] === undefined) {
+ callback(new Error('Region not found'));
+ return;
+ }
+
+ if (number > this.newestDeployments[region]) {
+ callback(new Error('Deployment not found'));
+ return;
+ }
+
+ if (this.locked === region) {
+ for (k in this.current) {
+ if (this.current.hasOwnProperty(k)) {
+ summary[k] = this.current[k];
+ }
+ }
+ callback(null, summary);
+ } else {
+ fs.readFile(path.join(this.logRoot, region, sprintf('%s.json', number)), 'utf8', function(err, data) {
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ try {
+ summary = JSON.parse(data);
+ } catch (e) {
+ callback(e);
+ return;
+ }
+
+ callback(null, summary);
+ });
+ }
+};
+
+
+Stack.prototype.getDeploymentDetails = function(region, number, callback) {
+ var self = this;
+
+ async.parallel([
+ self.getDeploymentSummary.bind(self, region, number),
+
+ self.getRegionSummary.bind(self, region),
+
+ self.getSummary.bind(self),
+
+ self.dreadnot.getSummary.bind(self.dreadnot),
+
+ // Logs are mixed into the deployment "summary", no need to retrieve them separately
+ ],
+
+ function(err, results) {
+ var details;
+
+ if (err) {
+ callback(err);
+ } else {
+ details = results[0];
+ details.region = results[1];
+ details.stack = results[2];
+ details.env = results[3];
+ callback(null, details);
+ }
+ });
+};
+
+
+Stack.prototype.runningStatus = function(callback) {
+ if (this.current) {
+ var info = misc.merge({}, this.current),
+ diffUrl = this.getGitHubDiffUrl(info.from_revision, info.to_revision);
+ delete info.log;
+ callback(null, { running: true, info: info, diffUrl: diffUrl });
+ } else {
+ callback(null, { running: false });
+ }
+};
+
+
+/** Export Stack */
+exports.Stack = Stack;
189 lib/util/buildbot.js
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var url = require('url');
+var querystring = require('querystring');
+
+var log = require('logmagic').local('buildbot');
+var request = require('request');
+
+var sprintf = require('./sprintf');
+
+
+/**
+ * BuildBot Client.
+ * @constructor
+ * @param {Object} options The options.
+ */
+function BuildBot(options) {
+ var parsed = url.parse(options.url);
+
+ // Inject auth into the URL
+ delete parsed.host;
+ parsed['auth'] = sprintf('%s:%s', options.username, options.password);
+
+ this._url = url.format(parsed);
+ this._options = options;
+}
+
+
+/**
+ * Initiate build on buildbot
+ * @param {String} builder The builder to force the build on.
+ * @param {String} revision The revision to build.
+ * @param {Function} callback The completion callback(err).
+ */
+BuildBot.prototype.build = function(builder, revision, callback) {
+ var self = this,
+ reqOpts = {
+ method: 'POST',
+ uri: sprintf('%s/builders/%s/force', this._url, builder),
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: querystring.stringify({
+ username: this._options.username,
+ revision: revision
+ })
+ };
+
+ log.info('forcing build', {
+ builder: builder,
+ revision: revision
+ });
+
+ request(reqOpts, function(err, response, body) {
+ if (err) {
+ callback(err);
+ } else if (response.statusCode !== 302) {
+ callback(new Error(sprintf('Received %s from %s', response.statusCode, self._options.url)));
+ } else {
+ callback(null, body);
+ }
+ });
+};
+
+
+/**
+ * Find the oldest build with a given revision.
+ */
+BuildBot.prototype._findOldestBuild = function(builds, revision) {
+ var i, j, number, numbers, build, properties;
+
+ // Get numbers of builds from oldest to newest
+ numbers = Object.keys(builds).map(function(numstr) {
+ return parseInt(numstr);
+ }).sort(function(a, b) {
+ return a - b;
+ });
+
+ for (i = 0; i < numbers.length; i++) {
+ number = numbers[i].toString();
+
+ if (builds.hasOwnProperty(number)) {
+ build = builds[number];
+ properties = build.properties;
+
+ for (j = 0; j < properties.length; j++) {
+ if (properties[j][0] === 'got_revision' && properties[j][1] === revision) {
+ return build;
+ }
+ }
+ }
+ }
+};
+
+
+/**
+ * Get a build for a specified revision.
+ * @param {String} revision The revision to search for.
+ * @param {Function} callback A callback fired with (err, build).
+ */
+BuildBot.prototype.getRevision = function(builder, revision, callback) {
+ var self = this,
+ selects = [],
+ search, i;
+
+ for (i = 1; i <= this._options.num_builds; i++) {
+ selects.push(sprintf('-%s', i));
+ }
+
+ search = querystring.stringify({select: selects});
+
+ log.info('searching for revision', {
+ builder: builder,
+ revision: revision
+ });
+
+ request.get(sprintf('%s/json/builders/%s/builds?%s', this._url, builder, search), function(err, response, body) {
+ if (err) {
+ callback(err);
+ } else {
+ try {
+ body = JSON.parse(body);
+ } catch (e) {
+ callback(e);
+ return;
+ }
+
+ callback(null, self._findOldestBuild(body, revision));
+ }
+ });
+};
+
+
+/**
+ *
+ */
+BuildBot.prototype.ensureRevisionBuilt = function(builder, revision, callback) {
+ var self = this,
+ attempts = 0;
+
+ function attempt() {
+ attempts++;
+
+ if (attempts > self._options.retries) {
+ callback(new Error(sprintf('No build found after %s attempts', self._options.retries)));
+ return;
+ }
+
+ self.getRevision(builder, revision, function(err, build) {
+ if (err) {
+ callback(err);
+ } else if (!build) {
+ self.build(builder, revision, function(err) {
+ if (err) {
+ callback(err);
+ } else {
+ setTimeout(attempt, self._options.delay);
+ }
+ });
+ } else if (build.steps[build.steps.length - 1].isFinished) {
+ callback(null, build);
+ } else {
+ setTimeout(attempt, self._options.delay);
+ }
+ });
+ }
+
+ attempt();
+};
+
+
+
+/** BuildBot Class */
+exports.BuildBot = BuildBot;
124 lib/util/git.js
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var fs = require('fs');
+var path = require('path');
+
+var async = require('async');
+var mkdirp = require('mkdirp');
+
+var misc = require('./misc');
+var sprintf = require('./sprintf');
+
+
+function git(args, callback) {
+ args = ['git'].concat(args);
+ misc.spawn(args, callback);
+}
+
+
+function gitd(repo, args, callback) {
+ args = [sprintf('--git-dir=%s/.git', repo), sprintf('--work-tree=%s', repo)].concat(args);
+ git(args, callback);
+}
+
+
+exports.refresh = function(repo, url, callback) {
+ path.exists(repo, function(exists) {
+ if (exists) {
+ async.series([
+ exports.fetch.bind(null, repo),
+ exports.merge.bind(null, repo, 'origin/master')
+ ], callback);
+ } else {
+ exports.clone(repo, url, callback);
+ }
+ });
+};
+
+
+exports.hasChanges = function(repo, callback) {
+ // When this succeeds but there are changes, it silently exits with code 1
+ gitd(repo, ['diff-index', '--quiet', 'HEAD', '--'], function(err) {
+ if (err) {
+ if (err.message.indexOf('\nstderr:\n\nstdout:\n') < 0) {
+ callback(err);
+ } else {
+ callback(null, true);
+ }
+ } else {
+ callback(null, false);
+ }
+ });
+};
+
+
+exports.add = function(repo, addPath, callback) {
+ gitd(repo, ['add', addPath], callback);
+};
+
+
+exports.commit = function(repo, message, author, callback) {
+ gitd(repo, ['commit', '-m', message, sprintf('--author=%s', author)], callback);
+};
+
+
+exports.push = function(repo, callback) {
+ gitd(repo, ['push'], callback);
+};
+
+
+exports.resetHard = function(repo, ref, callback) {
+ gitd(repo, ['reset', '--hard', ref], callback);
+};
+
+
+exports.merge = function(repo, ref, callback) {
+ gitd(repo, ['merge', ref], callback);
+};
+
+
+exports.fetch = function(repo, callback) {
+ gitd(repo, ['fetch'], callback);
+};
+
+
+exports.clone = function(repo, url, callback) {
+ async.series([
+ mkdirp.bind(null, path.dirname(repo), 0755),
+ git.bind(null, ['clone', url, repo])
+ ],
+ function(err) {
+ callback(err);
+ });
+};
+
+
+exports.getLatestRevision = function(url, ref, callback) {
+ git(['ls-remote', url, ref], function(err, stdout) {
+ if (err) {
+ callback(err);
+ } else {
+ callback(null, stdout.split('\t')[0]);
+ }
+ });
+};
+
+
+exports.trimRevision = function(rev) {
+ return rev.slice(0, 7);
+};
69 lib/util/knife.js
@@ -0,0 +1,69 @@
+var fs = require('fs');
+
+var async = require('async');
+
+var misc = require('./misc');
+var sprintf = require('./sprintf');
+
+
+function knife(args) {
+ return function(callback) {
+ args = ['/usr/bin/knife'].concat(args);
+ misc.spawn(args, function(err, data) {
+ var payload;
+
+ if (err) {
+ callback(err);
+ return;
+ }
+
+ if (args.indexOf('json') >= 0) {
+ try {
+ payload = JSON.parse(data);
+ } catch (e) {
+ callback(e);
+ return;
+ }
+ }
+
+ callback(null, payload);
+ });
+ };
+}
+
+
+function knifeSearch(index, query, callback) {
+ knife(['search', index, query, '-F', 'json'])(callback);
+}
+
+
+function dataBagGet(bagName, item, callback) {
+ knife(['data', 'bag', 'show', '-F', 'json', bagName, item])(callback);
+}
+
+
+function dataBagSet(bagName, obj, callback) {
+ var filename = sprintf('/tmp/databag-%s.json', misc.randstr(8));
+ async.series([
+ fs.writeFile.bind(null, filename, JSON.stringify(obj)),
+ knife(['data', 'bag', 'from', 'file', bagName, filename]).bind(null),
+ fs.unlink.bind(null, filename)
+ ], callback);
+}
+
+
+/** Export Data Bag */
+exports.dataBag = {
+ 'set': dataBagSet,
+ 'get': dataBagGet
+};
+
+
+/** Export Search */
+exports.search = knifeSearch;
+
+
+/** Export Node */
+exports.node = {
+ 'list': knife(['node', 'list', '-F', 'json'])
+};
74 lib/util/misc.js
@@ -0,0 +1,74 @@
+var sprintf = require('./sprintf');
+var log = require('logmagic').local('deploy.helpers.misc');
+var spawn = require('child_process').spawn;
+
+
+/** Spawn a subprocess and issue the callback.
+ * @param {Array} cmd Command and Arguments.
+ * @param {Function} callback Completion callback(err, resultStdoutString).
+ */
+exports.spawn = function(cmd, callback) {
+ var args,
+ proc,
+ resultString = '',
+ errString = '',
+ cmdStr;
+
+ // Copy command so we don't override the parent
+ cmd = cmd.slice();
+ args = cmd.splice(1);
+
+ cmdStr = sprintf('%s %s', cmd[0], args.join(' '));
+ log.info('Executing Command', cmdStr);
+
+ proc = spawn(cmd[0], args);
+ proc.stdout.on('data', function(data) {
+ resultString += data;
+ });
+ proc.stderr.on('data', function(data) {
+ errString += data;
+ });
+ proc.on('exit', function(code) {
+ if (code) {
+ callback(new Error(sprintf('Failed command %s:\nstderr:\n%s\nstdout:\n%s', cmdStr, errString, resultString)));
+ } else {
+ callback(null, resultString);
+ }
+ });
+};
+
+
+exports.randstr = function(len) {
+ var chars, r, x;
+
+ chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+ r = [];
+
+ for (x = 0; x < len; x++) {
+ r.push(chars[exports.getRandomInt(0, chars.length - 1)]);
+ }
+
+ return r.join('');
+}
+
+
+exports.getRandomInt = function(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+};
+
+
+exports.merge = function(a, b) {
+ var c = {}, attrname;
+
+ for (attrname in a) {
+ if (a.hasOwnProperty(attrname)) {
+ c[attrname] = a[attrname];
+ }
+ }
+ for (attrname in b) {
+ if (b.hasOwnProperty(attrname)) {
+ c[attrname] = b[attrname];
+ }
+ }
+ return c;
+};
60 lib/util/path_setup.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var path = require('path');
+var fs = require('fs');
+
+
+/**
+ * This detects if the entry file is a symlinked path, and tries to
+ * insert the correct lib into the require.paths for eveyrthing to work
+ * correctly.
+ *
+ * @param {String} filePath Path to a file. If specified, it is joined with
+ * '../' and '../lib/cast' and both of those paths
+ * are inserted at the begining of the require.paths
+ * array.
+ */
+exports.pathSetup = function(filePath) {
+ var stat, root, p, orig;
+
+ p = filePath || path.join(__filename, '../');
+
+ stat = fs.lstatSync(p);
+ if (stat.isSymbolicLink()) {
+ p = fs.readlinkSync(p);
+ }
+
+ root = path.dirname(p);
+
+
+ // In node <=0.4, you could manipulate require.paths manually, now we need to hack
+ // the enviroment variable and re-init the module paths to make it work.
+ // require.paths.unshift(root);
+
+ orig = process.env.NODE_PATH ? process.env.NODE_PATH : '';
+
+ if (filePath) {
+ // in-place path
+ process.env.NODE_PATH = path.join(root, '../lib') + ':' + orig;
+ }
+ else {
+ process.env.NODE_PATH = root + ':' + orig;
+ }
+
+ require('module')._initPaths();
+};
128 lib/util/sprintf.js
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var cache = {};
+
+
+// Do any others need escaping?
+var TO_ESCAPE = {
+ '\'': '\\\'',
+ '\n': '\\n'
+};
+
+
+function populate(formatter) {
+ // TODO: In theory we only need to do this traverse once, but we have to
+ // handle escaping, etc, so its not especially clean
+ if (formatter.indexOf('%(') >= 0) {
+ populateNamed(formatter);
+ } else {
+ populatePositional(formatter);
+ }
+}
+
+
+function populateNamed(formatter) {
+ var i, type, key,
+ prev = 0,
+ builder = 'return \'';
+
+ for (i = 0; i < formatter.length; i++) {
+ if (formatter[i] === '%' && formatter[i + 1] === '(') {
+ key = formatter.slice(i + 2, i + 2 + formatter.slice(i + 2).indexOf(')'));
+ type = formatter[i + key.length + 3];
+ switch (type) {
+ case 's':
+ builder += formatter.slice(prev, i) + '\' + arguments[1][\'' + key + '\'] + \'';
+ i += key.length + 3;
+ prev = i + 1
+ break;
+ case 'j':
+ builder += formatter.slice(prev, i) + '\' + JSON.stringify(arguments[1][\'' + key + '\']) + \'';
+ i += key.length + 3;
+ prev = i + 1
+ break;
+ }
+ } else if (TO_ESCAPE[formatter[i]]) {
+ builder += formatter.slice(prev, i) + TO_ESCAPE[formatter[i]];
+ prev = i + 1;
+ }
+ }
+
+ builder += formatter.slice(prev) + '\';';
+ cache[formatter] = new Function(builder);
+}
+
+
+function populatePositional(formatter) {
+ var i, type,
+ prev = 0,
+ arg = 1,
+ builder = 'return \'';
+
+ for (i = 0; i < formatter.length; i++) {
+ if (formatter[i] === '%') {
+ type = formatter[i + 1];
+
+ switch (type) {
+ case 's':
+ builder += formatter.slice(prev, i) + '\' + arguments[' + arg + '] + \'';
+ prev = i + 2;
+ arg++;
+ break;
+ case 'j':
+ builder += formatter.slice(prev, i) + '\' + JSON.stringify(arguments[' + arg + ']) + \'';
+ prev = i + 2;
+ arg++;
+ break;
+ case '%':
+ builder += formatter.slice(prev, i + 1);
+ prev = i + 2;
+ i++;
+ break;
+ }
+
+
+ } else if (TO_ESCAPE[formatter[i]]) {
+ builder += formatter.slice(prev, i) + TO_ESCAPE[formatter[i]];
+ prev = i + 1;
+ }
+ }
+
+ builder += formatter.slice(prev) + '\';';
+ cache[formatter] = new Function(builder);
+}
+
+
+/**
+ * A fast version of sprintf(), which currently only supports the %s and %j.
+ * This caches a formatting function for each format string that is used, so
+ * you should only use this sprintf() will be called many times with a single
+ * format string and a limited number of format strings will ever be used (in
+ * general this means that format strings should be string literals).
+ *
+ * @param {String} formatter A format string.
+ * @param {...String} var_args Values that will be formatted by %s and %j.
+ * @return {String} The formatted output.
+ */
+module.exports = function(formatter, var_args) {
+ if (!cache[formatter]) {
+ populate(formatter);
+ }
+
+ return cache[formatter].apply(null, arguments);
+};
35 lib/web/api.js
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var express = require('express');
+
+function apiStatus(req, res) {
+ req.webHandlers._dreadnot.runningStatus(function(err, stat) {
+ if (err) {
+ return;
+ }
+ res.write(JSON.stringify(stat));
+ res.end();
+ });
+};
+
+exports.register = function() {
+ var app = express.createServer();
+ app.use(express.basicAuth('rax', 'montana'));
+ app.get('/status', apiStatus);
+ return app;
+};
147 lib/web/app.js
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2011 Rackspace
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+var path = require('path');
+
+var connect = require('connect');
+var express = require('express');
+var socketio = require('socket.io');
+var log = require('logmagic').local('web.app');
+
+var misc = require('util/misc');
+
+var api = require('./api');
+var handlers = require('./handlers');
+var middleware = require('./middleware');
+