mbailey-chef 0.9.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (345) hide show
  1. data/LICENSE +201 -0
  2. data/README.rdoc +172 -0
  3. data/bin/chef-client +26 -0
  4. data/bin/chef-solo +25 -0
  5. data/bin/knife +26 -0
  6. data/bin/shef +34 -0
  7. data/distro/README +2 -0
  8. data/distro/common/man/man1/chef-indexer.1 +42 -0
  9. data/distro/common/man/man1/chef-server-webui.1 +106 -0
  10. data/distro/common/man/man1/chef-server.1 +107 -0
  11. data/distro/common/man/man1/chef-solr-indexer.1 +55 -0
  12. data/distro/common/man/man1/chef-solr.1 +55 -0
  13. data/distro/common/man/man8/chef-client.8 +63 -0
  14. data/distro/common/man/man8/chef-solo.8 +57 -0
  15. data/distro/common/man/man8/chef-solr-rebuild.8 +37 -0
  16. data/distro/common/man/man8/knife.8 +1349 -0
  17. data/distro/common/man/man8/shef.8 +45 -0
  18. data/distro/common/markdown/README +3 -0
  19. data/distro/common/markdown/knife.mkd +832 -0
  20. data/distro/debian/etc/default/chef-client +4 -0
  21. data/distro/debian/etc/default/chef-server +9 -0
  22. data/distro/debian/etc/default/chef-server-webui +9 -0
  23. data/distro/debian/etc/default/chef-solr +7 -0
  24. data/distro/debian/etc/default/chef-solr-indexer +7 -0
  25. data/distro/debian/etc/init/chef-client.conf +17 -0
  26. data/distro/debian/etc/init/chef-server-webui.conf +17 -0
  27. data/distro/debian/etc/init/chef-server.conf +17 -0
  28. data/distro/debian/etc/init/chef-solr-indexer.conf +17 -0
  29. data/distro/debian/etc/init/chef-solr.conf +17 -0
  30. data/distro/debian/etc/init.d/chef-client +175 -0
  31. data/distro/debian/etc/init.d/chef-server +122 -0
  32. data/distro/debian/etc/init.d/chef-server-webui +123 -0
  33. data/distro/debian/etc/init.d/chef-solr +177 -0
  34. data/distro/debian/etc/init.d/chef-solr-indexer +176 -0
  35. data/distro/redhat/etc/init.d/chef-client +106 -0
  36. data/distro/redhat/etc/init.d/chef-server +112 -0
  37. data/distro/redhat/etc/init.d/chef-server-webui +112 -0
  38. data/distro/redhat/etc/init.d/chef-solr +104 -0
  39. data/distro/redhat/etc/init.d/chef-solr-indexer +104 -0
  40. data/distro/redhat/etc/logrotate.d/chef-client +8 -0
  41. data/distro/redhat/etc/logrotate.d/chef-server +8 -0
  42. data/distro/redhat/etc/logrotate.d/chef-server-webui +8 -0
  43. data/distro/redhat/etc/logrotate.d/chef-solr +8 -0
  44. data/distro/redhat/etc/logrotate.d/chef-solr-indexer +8 -0
  45. data/distro/redhat/etc/sysconfig/chef-client +15 -0
  46. data/distro/redhat/etc/sysconfig/chef-server +14 -0
  47. data/distro/redhat/etc/sysconfig/chef-server-webui +14 -0
  48. data/distro/redhat/etc/sysconfig/chef-solr +8 -0
  49. data/distro/redhat/etc/sysconfig/chef-solr-indexer +7 -0
  50. data/lib/chef/api_client.rb +264 -0
  51. data/lib/chef/application/agent.rb +18 -0
  52. data/lib/chef/application/client.rb +246 -0
  53. data/lib/chef/application/knife.rb +171 -0
  54. data/lib/chef/application/solo.rb +215 -0
  55. data/lib/chef/application.rb +127 -0
  56. data/lib/chef/applications.rb +4 -0
  57. data/lib/chef/certificate.rb +194 -0
  58. data/lib/chef/checksum.rb +182 -0
  59. data/lib/chef/checksum_cache.rb +173 -0
  60. data/lib/chef/client.rb +304 -0
  61. data/lib/chef/config.rb +240 -0
  62. data/lib/chef/cookbook/cookbook_collection.rb +44 -0
  63. data/lib/chef/cookbook/file_system_file_vendor.rb +54 -0
  64. data/lib/chef/cookbook/file_vendor.rb +48 -0
  65. data/lib/chef/cookbook/metadata/version.rb +87 -0
  66. data/lib/chef/cookbook/metadata.rb +500 -0
  67. data/lib/chef/cookbook/remote_file_vendor.rb +87 -0
  68. data/lib/chef/cookbook/syntax_check.rb +136 -0
  69. data/lib/chef/cookbook_loader.rb +227 -0
  70. data/lib/chef/cookbook_site_streaming_uploader.rb +244 -0
  71. data/lib/chef/cookbook_uploader.rb +103 -0
  72. data/lib/chef/cookbook_version.rb +930 -0
  73. data/lib/chef/couchdb.rb +239 -0
  74. data/lib/chef/daemon.rb +172 -0
  75. data/lib/chef/data_bag.rb +215 -0
  76. data/lib/chef/data_bag_item.rb +228 -0
  77. data/lib/chef/exceptions.rb +66 -0
  78. data/lib/chef/file_access_control.rb +140 -0
  79. data/lib/chef/file_cache.rb +218 -0
  80. data/lib/chef/handler/json_file.rb +58 -0
  81. data/lib/chef/handler.rb +206 -0
  82. data/lib/chef/index_queue/amqp_client.rb +113 -0
  83. data/lib/chef/index_queue/consumer.rb +76 -0
  84. data/lib/chef/index_queue/indexable.rb +76 -0
  85. data/lib/chef/index_queue.rb +29 -0
  86. data/lib/chef/knife/bootstrap/archlinux-gems.erb +44 -0
  87. data/lib/chef/knife/bootstrap/centos5-gems.erb +41 -0
  88. data/lib/chef/knife/bootstrap/client-install.vbs +80 -0
  89. data/lib/chef/knife/bootstrap/fedora13-gems.erb +38 -0
  90. data/lib/chef/knife/bootstrap/ubuntu10.04-apt.erb +32 -0
  91. data/lib/chef/knife/bootstrap/ubuntu10.04-gems.erb +43 -0
  92. data/lib/chef/knife/bootstrap/windows-gems.erb +34 -0
  93. data/lib/chef/knife/bootstrap.rb +181 -0
  94. data/lib/chef/knife/client_bulk_delete.rb +40 -0
  95. data/lib/chef/knife/client_create.rb +70 -0
  96. data/lib/chef/knife/client_delete.rb +45 -0
  97. data/lib/chef/knife/client_edit.rb +45 -0
  98. data/lib/chef/knife/client_list.rb +40 -0
  99. data/lib/chef/knife/client_reregister.rb +56 -0
  100. data/lib/chef/knife/client_show.rb +50 -0
  101. data/lib/chef/knife/configure.rb +140 -0
  102. data/lib/chef/knife/configure_client.rb +52 -0
  103. data/lib/chef/knife/cookbook_bulk_delete.rb +58 -0
  104. data/lib/chef/knife/cookbook_create.rb +209 -0
  105. data/lib/chef/knife/cookbook_delete.rb +143 -0
  106. data/lib/chef/knife/cookbook_download.rb +130 -0
  107. data/lib/chef/knife/cookbook_list.rb +41 -0
  108. data/lib/chef/knife/cookbook_metadata.rb +82 -0
  109. data/lib/chef/knife/cookbook_metadata_from_file.rb +40 -0
  110. data/lib/chef/knife/cookbook_show.rb +98 -0
  111. data/lib/chef/knife/cookbook_site_download.rb +58 -0
  112. data/lib/chef/knife/cookbook_site_list.rb +56 -0
  113. data/lib/chef/knife/cookbook_site_search.rb +51 -0
  114. data/lib/chef/knife/cookbook_site_share.rb +109 -0
  115. data/lib/chef/knife/cookbook_site_show.rb +57 -0
  116. data/lib/chef/knife/cookbook_site_unshare.rb +52 -0
  117. data/lib/chef/knife/cookbook_site_vendor.rb +133 -0
  118. data/lib/chef/knife/cookbook_test.rb +82 -0
  119. data/lib/chef/knife/cookbook_upload.rb +95 -0
  120. data/lib/chef/knife/data_bag_create.rb +59 -0
  121. data/lib/chef/knife/data_bag_delete.rb +48 -0
  122. data/lib/chef/knife/data_bag_edit.rb +50 -0
  123. data/lib/chef/knife/data_bag_from_file.rb +44 -0
  124. data/lib/chef/knife/data_bag_list.rb +43 -0
  125. data/lib/chef/knife/data_bag_show.rb +41 -0
  126. data/lib/chef/knife/ec2_instance_data.rb +46 -0
  127. data/lib/chef/knife/ec2_server_create.rb +216 -0
  128. data/lib/chef/knife/ec2_server_delete.rb +90 -0
  129. data/lib/chef/knife/ec2_server_list.rb +88 -0
  130. data/lib/chef/knife/exec.rb +52 -0
  131. data/lib/chef/knife/index_rebuild.rb +51 -0
  132. data/lib/chef/knife/node_bulk_delete.rb +43 -0
  133. data/lib/chef/knife/node_create.rb +47 -0
  134. data/lib/chef/knife/node_delete.rb +44 -0
  135. data/lib/chef/knife/node_edit.rb +44 -0
  136. data/lib/chef/knife/node_from_file.rb +42 -0
  137. data/lib/chef/knife/node_list.rb +41 -0
  138. data/lib/chef/knife/node_run_list_add.rb +64 -0
  139. data/lib/chef/knife/node_run_list_remove.rb +45 -0
  140. data/lib/chef/knife/node_show.rb +54 -0
  141. data/lib/chef/knife/rackspace_server_create.rb +156 -0
  142. data/lib/chef/knife/rackspace_server_delete.rb +57 -0
  143. data/lib/chef/knife/rackspace_server_list.rb +59 -0
  144. data/lib/chef/knife/recipe_list.rb +32 -0
  145. data/lib/chef/knife/role_bulk_delete.rb +44 -0
  146. data/lib/chef/knife/role_create.rb +52 -0
  147. data/lib/chef/knife/role_delete.rb +44 -0
  148. data/lib/chef/knife/role_edit.rb +45 -0
  149. data/lib/chef/knife/role_from_file.rb +46 -0
  150. data/lib/chef/knife/role_list.rb +40 -0
  151. data/lib/chef/knife/role_show.rb +51 -0
  152. data/lib/chef/knife/search.rb +94 -0
  153. data/lib/chef/knife/slicehost_images_list.rb +53 -0
  154. data/lib/chef/knife/slicehost_server_create.rb +103 -0
  155. data/lib/chef/knife/slicehost_server_delete.rb +61 -0
  156. data/lib/chef/knife/slicehost_server_list.rb +64 -0
  157. data/lib/chef/knife/ssh.rb +328 -0
  158. data/lib/chef/knife/status.rb +87 -0
  159. data/lib/chef/knife/terremark_server_create.rb +152 -0
  160. data/lib/chef/knife/terremark_server_delete.rb +87 -0
  161. data/lib/chef/knife/terremark_server_list.rb +77 -0
  162. data/lib/chef/knife/windows_bootstrap.rb +154 -0
  163. data/lib/chef/knife.rb +522 -0
  164. data/lib/chef/log.rb +61 -0
  165. data/lib/chef/mixin/check_helper.rb +31 -0
  166. data/lib/chef/mixin/checksum.rb +32 -0
  167. data/lib/chef/mixin/command/unix.rb +215 -0
  168. data/lib/chef/mixin/command/windows.rb +72 -0
  169. data/lib/chef/mixin/command.rb +220 -0
  170. data/lib/chef/mixin/convert_to_class_name.rb +63 -0
  171. data/lib/chef/mixin/create_path.rb +56 -0
  172. data/lib/chef/mixin/deep_merge.rb +225 -0
  173. data/lib/chef/mixin/deprecation.rb +65 -0
  174. data/lib/chef/mixin/find_preferred_file.rb +92 -0
  175. data/lib/chef/mixin/from_file.rb +50 -0
  176. data/lib/chef/mixin/language.rb +156 -0
  177. data/lib/chef/mixin/language_include_attribute.rb +61 -0
  178. data/lib/chef/mixin/language_include_recipe.rb +52 -0
  179. data/lib/chef/mixin/params_validate.rb +225 -0
  180. data/lib/chef/mixin/recipe_definition_dsl_core.rb +81 -0
  181. data/lib/chef/mixin/shell_out.rb +38 -0
  182. data/lib/chef/mixin/template.rb +95 -0
  183. data/lib/chef/mixin/xml_escape.rb +140 -0
  184. data/lib/chef/mixins.rb +16 -0
  185. data/lib/chef/monkey_patches/dir.rb +36 -0
  186. data/lib/chef/monkey_patches/string.rb +28 -0
  187. data/lib/chef/monkey_patches/tempfile.rb +64 -0
  188. data/lib/chef/node/attribute.rb +465 -0
  189. data/lib/chef/node.rb +601 -0
  190. data/lib/chef/openid_registration.rb +187 -0
  191. data/lib/chef/platform.rb +371 -0
  192. data/lib/chef/provider/breakpoint.rb +36 -0
  193. data/lib/chef/provider/cookbook_file.rb +100 -0
  194. data/lib/chef/provider/cron/solaris.rb +195 -0
  195. data/lib/chef/provider/cron.rb +186 -0
  196. data/lib/chef/provider/deploy/revision.rb +73 -0
  197. data/lib/chef/provider/deploy/timestamped.rb +33 -0
  198. data/lib/chef/provider/deploy.rb +319 -0
  199. data/lib/chef/provider/directory.rb +72 -0
  200. data/lib/chef/provider/env/windows.rb +75 -0
  201. data/lib/chef/provider/env.rb +152 -0
  202. data/lib/chef/provider/erl_call.rb +72 -0
  203. data/lib/chef/provider/execute.rb +58 -0
  204. data/lib/chef/provider/file.rb +213 -0
  205. data/lib/chef/provider/git.rb +211 -0
  206. data/lib/chef/provider/group/dscl.rb +121 -0
  207. data/lib/chef/provider/group/gpasswd.rb +53 -0
  208. data/lib/chef/provider/group/groupadd.rb +78 -0
  209. data/lib/chef/provider/group/pw.rb +84 -0
  210. data/lib/chef/provider/group/usermod.rb +57 -0
  211. data/lib/chef/provider/group/windows.rb +79 -0
  212. data/lib/chef/provider/group.rb +133 -0
  213. data/lib/chef/provider/http_request.rb +122 -0
  214. data/lib/chef/provider/ifconfig.rb +132 -0
  215. data/lib/chef/provider/link.rb +161 -0
  216. data/lib/chef/provider/log.rb +54 -0
  217. data/lib/chef/provider/mdadm.rb +91 -0
  218. data/lib/chef/provider/mount/mount.rb +232 -0
  219. data/lib/chef/provider/mount/windows.rb +80 -0
  220. data/lib/chef/provider/mount.rb +117 -0
  221. data/lib/chef/provider/ohai.rb +41 -0
  222. data/lib/chef/provider/package/apt.rb +110 -0
  223. data/lib/chef/provider/package/dpkg.rb +112 -0
  224. data/lib/chef/provider/package/easy_install.rb +114 -0
  225. data/lib/chef/provider/package/freebsd.rb +123 -0
  226. data/lib/chef/provider/package/macports.rb +105 -0
  227. data/lib/chef/provider/package/pacman.rb +101 -0
  228. data/lib/chef/provider/package/portage.rb +124 -0
  229. data/lib/chef/provider/package/rpm.rb +101 -0
  230. data/lib/chef/provider/package/rubygems.rb +462 -0
  231. data/lib/chef/provider/package/solaris.rb +127 -0
  232. data/lib/chef/provider/package/yum-dump.py +128 -0
  233. data/lib/chef/provider/package/yum.rb +222 -0
  234. data/lib/chef/provider/package/zypper.rb +133 -0
  235. data/lib/chef/provider/package.rb +160 -0
  236. data/lib/chef/provider/remote_directory.rb +140 -0
  237. data/lib/chef/provider/remote_file.rb +120 -0
  238. data/lib/chef/provider/route.rb +195 -0
  239. data/lib/chef/provider/ruby_block.rb +33 -0
  240. data/lib/chef/provider/script.rb +55 -0
  241. data/lib/chef/provider/service/arch.rb +109 -0
  242. data/lib/chef/provider/service/debian.rb +105 -0
  243. data/lib/chef/provider/service/freebsd.rb +156 -0
  244. data/lib/chef/provider/service/gentoo.rb +54 -0
  245. data/lib/chef/provider/service/init.rb +71 -0
  246. data/lib/chef/provider/service/redhat.rb +60 -0
  247. data/lib/chef/provider/service/simple.rb +118 -0
  248. data/lib/chef/provider/service/solaris.rb +85 -0
  249. data/lib/chef/provider/service/upstart.rb +192 -0
  250. data/lib/chef/provider/service/windows.rb +129 -0
  251. data/lib/chef/provider/service.rb +128 -0
  252. data/lib/chef/provider/subversion.rb +159 -0
  253. data/lib/chef/provider/template.rb +105 -0
  254. data/lib/chef/provider/user/dscl.rb +280 -0
  255. data/lib/chef/provider/user/pw.rb +113 -0
  256. data/lib/chef/provider/user/useradd.rb +137 -0
  257. data/lib/chef/provider/user/windows.rb +124 -0
  258. data/lib/chef/provider/user.rb +187 -0
  259. data/lib/chef/provider.rb +124 -0
  260. data/lib/chef/providers.rb +91 -0
  261. data/lib/chef/recipe.rb +130 -0
  262. data/lib/chef/resource/apt_package.rb +34 -0
  263. data/lib/chef/resource/bash.rb +33 -0
  264. data/lib/chef/resource/breakpoint.rb +35 -0
  265. data/lib/chef/resource/cookbook_file.rb +45 -0
  266. data/lib/chef/resource/cron.rb +188 -0
  267. data/lib/chef/resource/csh.rb +33 -0
  268. data/lib/chef/resource/deploy.rb +371 -0
  269. data/lib/chef/resource/deploy_revision.rb +35 -0
  270. data/lib/chef/resource/directory.rb +76 -0
  271. data/lib/chef/resource/dpkg_package.rb +34 -0
  272. data/lib/chef/resource/easy_install_package.rb +41 -0
  273. data/lib/chef/resource/env.rb +58 -0
  274. data/lib/chef/resource/erl_call.rb +83 -0
  275. data/lib/chef/resource/execute.rb +127 -0
  276. data/lib/chef/resource/file.rb +92 -0
  277. data/lib/chef/resource/freebsd_package.rb +35 -0
  278. data/lib/chef/resource/gem_package.rb +49 -0
  279. data/lib/chef/resource/git.rb +36 -0
  280. data/lib/chef/resource/group.rb +70 -0
  281. data/lib/chef/resource/http_request.rb +61 -0
  282. data/lib/chef/resource/ifconfig.rb +134 -0
  283. data/lib/chef/resource/link.rb +78 -0
  284. data/lib/chef/resource/log.rb +62 -0
  285. data/lib/chef/resource/macports_package.rb +29 -0
  286. data/lib/chef/resource/mdadm.rb +82 -0
  287. data/lib/chef/resource/mount.rb +135 -0
  288. data/lib/chef/resource/ohai.rb +40 -0
  289. data/lib/chef/resource/package.rb +80 -0
  290. data/lib/chef/resource/pacman_package.rb +33 -0
  291. data/lib/chef/resource/perl.rb +33 -0
  292. data/lib/chef/resource/portage_package.rb +33 -0
  293. data/lib/chef/resource/python.rb +33 -0
  294. data/lib/chef/resource/remote_directory.rb +109 -0
  295. data/lib/chef/resource/remote_file.rb +83 -0
  296. data/lib/chef/resource/route.rb +135 -0
  297. data/lib/chef/resource/rpm_package.rb +34 -0
  298. data/lib/chef/resource/ruby.rb +33 -0
  299. data/lib/chef/resource/ruby_block.rb +40 -0
  300. data/lib/chef/resource/scm.rb +146 -0
  301. data/lib/chef/resource/script.rb +60 -0
  302. data/lib/chef/resource/service.rb +160 -0
  303. data/lib/chef/resource/solaris_package.rb +36 -0
  304. data/lib/chef/resource/subversion.rb +36 -0
  305. data/lib/chef/resource/template.rb +69 -0
  306. data/lib/chef/resource/timestamped_deploy.rb +31 -0
  307. data/lib/chef/resource/user.rb +130 -0
  308. data/lib/chef/resource/yum_package.rb +43 -0
  309. data/lib/chef/resource.rb +523 -0
  310. data/lib/chef/resource_collection/stepable_iterator.rb +124 -0
  311. data/lib/chef/resource_collection.rb +217 -0
  312. data/lib/chef/resource_definition.rb +67 -0
  313. data/lib/chef/resource_definition_list.rb +38 -0
  314. data/lib/chef/resources.rb +64 -0
  315. data/lib/chef/rest/auth_credentials.rb +78 -0
  316. data/lib/chef/rest/cookie_jar.rb +31 -0
  317. data/lib/chef/rest/rest_request.rb +188 -0
  318. data/lib/chef/rest.rb +394 -0
  319. data/lib/chef/role.rb +287 -0
  320. data/lib/chef/run_context.rb +110 -0
  321. data/lib/chef/run_list/run_list_expansion.rb +172 -0
  322. data/lib/chef/run_list/run_list_item.rb +78 -0
  323. data/lib/chef/run_list.rb +150 -0
  324. data/lib/chef/run_status.rb +121 -0
  325. data/lib/chef/runner.rb +107 -0
  326. data/lib/chef/sandbox.rb +153 -0
  327. data/lib/chef/search/query.rb +60 -0
  328. data/lib/chef/shef/ext.rb +568 -0
  329. data/lib/chef/shef/model_wrapper.rb +120 -0
  330. data/lib/chef/shef/shef_rest.rb +28 -0
  331. data/lib/chef/shef/shef_session.rb +271 -0
  332. data/lib/chef/shef.rb +325 -0
  333. data/lib/chef/shell_out.rb +413 -0
  334. data/lib/chef/streaming_cookbook_uploader.rb +201 -0
  335. data/lib/chef/tasks/chef_repo.rake +256 -0
  336. data/lib/chef/util/file_edit.rb +122 -0
  337. data/lib/chef/util/windows/net_group.rb +101 -0
  338. data/lib/chef/util/windows/net_use.rb +121 -0
  339. data/lib/chef/util/windows/net_user.rb +198 -0
  340. data/lib/chef/util/windows/volume.rb +59 -0
  341. data/lib/chef/util/windows.rb +56 -0
  342. data/lib/chef/version.rb +21 -0
  343. data/lib/chef/webui_user.rb +231 -0
  344. data/lib/chef.rb +39 -0
  345. metadata +533 -0
@@ -0,0 +1,244 @@
1
+ #
2
+ # Author:: Stanislav Vitvitskiy
3
+ # Author:: Nuo Yan (nuo@opscode.com)
4
+ # Author:: Christopher Walters (<cw@opscode.com>)
5
+ # Copyright:: Copyright (c) 2009, 2010 Opscode, Inc.
6
+ # License:: Apache License, Version 2.0
7
+ #
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+ #
20
+
21
+ require 'net/http'
22
+ require 'mixlib/authentication/signedheaderauth'
23
+ require 'openssl'
24
+
25
+ class Chef
26
+ # == Chef::CookbookSiteStreamingUploader
27
+ # A streaming multipart HTTP upload implementation. Used to upload cookbooks
28
+ # (in tarball form) to http://cookbooks.opscode.com
29
+ #
30
+ # inspired by http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html
31
+ class CookbookSiteStreamingUploader
32
+
33
+ DefaultHeaders = { 'accept' => 'application/json', 'x-chef-version' => ::Chef::VERSION }
34
+
35
+ class << self
36
+
37
+ def create_build_dir(cookbook)
38
+ tmp_cookbook_path = Tempfile.new("chef-#{cookbook.name}-build")
39
+ tmp_cookbook_path.close
40
+ tmp_cookbook_dir = tmp_cookbook_path.path
41
+ File.unlink(tmp_cookbook_dir)
42
+ FileUtils.mkdir_p(tmp_cookbook_dir)
43
+ Chef::Log.debug("Staging at #{tmp_cookbook_dir}")
44
+ checksums_to_on_disk_paths = cookbook.checksums
45
+ Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment|
46
+ cookbook.manifest[segment].each do |manifest_record|
47
+ path_in_cookbook = manifest_record[:path]
48
+ on_disk_path = checksums_to_on_disk_paths[manifest_record[:checksum]]
49
+ dest = File.join(tmp_cookbook_dir, cookbook.name.to_s, path_in_cookbook)
50
+ FileUtils.mkdir_p(File.dirname(dest))
51
+ Chef::Log.debug("Staging #{on_disk_path} to #{dest}")
52
+ FileUtils.cp(on_disk_path, dest)
53
+ end
54
+ end
55
+
56
+ # First, generate metadata
57
+ Chef::Log.debug("Generating metadata")
58
+ kcm = Chef::Knife::CookbookMetadata.new
59
+ kcm.config[:cookbook_path] = [ tmp_cookbook_dir ]
60
+ kcm.name_args = [ cookbook.name.to_s ]
61
+ kcm.run
62
+
63
+ tmp_cookbook_dir
64
+ end
65
+
66
+ def post(to_url, user_id, secret_key_filename, params = {}, headers = {})
67
+ make_request(:post, to_url, user_id, secret_key_filename, params, headers)
68
+ end
69
+
70
+ def put(to_url, user_id, secret_key_filename, params = {}, headers = {})
71
+ make_request(:put, to_url, user_id, secret_key_filename, params, headers)
72
+ end
73
+
74
+ def make_request(http_verb, to_url, user_id, secret_key_filename, params = {}, headers = {})
75
+ boundary = '----RubyMultipartClient' + rand(1000000).to_s + 'ZZZZZ'
76
+ parts = []
77
+ content_file = nil
78
+
79
+ timestamp = Time.now.utc.iso8601
80
+ secret_key = OpenSSL::PKey::RSA.new(File.read(secret_key_filename))
81
+
82
+ unless params.nil? || params.empty?
83
+ params.each do |key, value|
84
+ if value.kind_of?(File)
85
+ content_file = value
86
+ filepath = value.path
87
+ filename = File.basename(filepath)
88
+ parts << StringPart.new( "--" + boundary + "\r\n" +
89
+ "Content-Disposition: form-data; name=\"" + key.to_s + "\"; filename=\"" + filename + "\"\r\n" +
90
+ "Content-Type: application/octet-stream\r\n\r\n")
91
+ parts << StreamPart.new(value, File.size(filepath))
92
+ parts << StringPart.new("\r\n")
93
+ else
94
+ parts << StringPart.new( "--" + boundary + "\r\n" +
95
+ "Content-Disposition: form-data; name=\"" + key.to_s + "\"\r\n\r\n")
96
+ parts << StringPart.new(value.to_s + "\r\n")
97
+ end
98
+ end
99
+ parts << StringPart.new("--" + boundary + "--\r\n")
100
+ end
101
+
102
+ body_stream = MultipartStream.new(parts)
103
+
104
+ timestamp = Time.now.utc.iso8601
105
+
106
+ url = URI.parse(to_url)
107
+
108
+ Chef::Log.logger.debug("Signing: method: #{http_verb}, path: #{url.path}, file: #{content_file}, User-id: #{user_id}, Timestamp: #{timestamp}")
109
+
110
+ # We use the body for signing the request if the file parameter
111
+ # wasn't a valid file or wasn't included. Extract the body (with
112
+ # multi-part delimiters intact) to sign the request.
113
+ # TODO: tim: 2009-12-28: It'd be nice to remove this special case, and
114
+ # always hash the entire request body. In the file case it would just be
115
+ # expanded multipart text - the entire body of the POST.
116
+ content_body = parts.inject("") { |result,part| result + part.read(0, part.size) }
117
+ content_file.rewind if content_file # we consumed the file for the above operation, so rewind it.
118
+
119
+ signing_options = {
120
+ :http_method=>http_verb,
121
+ :path=>url.path,
122
+ :user_id=>user_id,
123
+ :timestamp=>timestamp}
124
+ (content_file && signing_options[:file] = content_file) || (signing_options[:body] = (content_body || ""))
125
+
126
+ headers.merge!(Mixlib::Authentication::SignedHeaderAuth.signing_object(signing_options).sign(secret_key))
127
+
128
+ content_file.rewind if content_file
129
+
130
+ # net/http doesn't like symbols for header keys, so we'll to_s each one just in case
131
+ headers = DefaultHeaders.merge(Hash[*headers.map{ |k,v| [k.to_s, v] }.flatten])
132
+
133
+ req = case http_verb
134
+ when :put
135
+ Net::HTTP::Put.new(url.path, headers)
136
+ when :post
137
+ Net::HTTP::Post.new(url.path, headers)
138
+ end
139
+ req.content_length = body_stream.size
140
+ req.content_type = 'multipart/form-data; boundary=' + boundary unless parts.empty?
141
+ req.body_stream = body_stream
142
+
143
+ http = Net::HTTP.new(url.host, url.port)
144
+ if url.scheme == "https"
145
+ http.use_ssl = true
146
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
147
+ end
148
+ res = http.request(req)
149
+ #res = http.start {|http_proc| http_proc.request(req) }
150
+
151
+ # alias status to code and to_s to body for test purposes
152
+ # TODO: stop the following madness!
153
+ class << res
154
+ alias :to_s :body
155
+
156
+ # BUGBUG this makes the response compatible with what respsonse_steps expects to test headers (response.headers[] -> response[])
157
+ def headers
158
+ self
159
+ end
160
+
161
+ def status
162
+ code.to_i
163
+ end
164
+ end
165
+ res
166
+ end
167
+
168
+ end
169
+
170
+ class StreamPart
171
+ def initialize(stream, size)
172
+ @stream, @size = stream, size
173
+ end
174
+
175
+ def size
176
+ @size
177
+ end
178
+
179
+ # read the specified amount from the stream
180
+ def read(offset, how_much)
181
+ @stream.read(how_much)
182
+ end
183
+ end
184
+
185
+ class StringPart
186
+ def initialize(str)
187
+ @str = str
188
+ end
189
+
190
+ def size
191
+ @str.length
192
+ end
193
+
194
+ # read the specified amount from the string startiung at the offset
195
+ def read(offset, how_much)
196
+ @str[offset, how_much]
197
+ end
198
+ end
199
+
200
+ class MultipartStream
201
+ def initialize(parts)
202
+ @parts = parts
203
+ @part_no = 0
204
+ @part_offset = 0
205
+ end
206
+
207
+ def size
208
+ @parts.inject(0) {|size, part| size + part.size}
209
+ end
210
+
211
+ def read(how_much)
212
+ return nil if @part_no >= @parts.size
213
+
214
+ how_much_current_part = @parts[@part_no].size - @part_offset
215
+
216
+ how_much_current_part = if how_much_current_part > how_much
217
+ how_much
218
+ else
219
+ how_much_current_part
220
+ end
221
+
222
+ how_much_next_part = how_much - how_much_current_part
223
+
224
+ current_part = @parts[@part_no].read(@part_offset, how_much_current_part)
225
+
226
+ # recurse into the next part if the current one was not large enough
227
+ if how_much_next_part > 0
228
+ @part_no += 1
229
+ @part_offset = 0
230
+ next_part = read(how_much_next_part)
231
+ current_part + if next_part
232
+ next_part
233
+ else
234
+ ''
235
+ end
236
+ else
237
+ @part_offset += how_much_current_part
238
+ current_part
239
+ end
240
+ end
241
+ end
242
+
243
+ end
244
+ end
@@ -0,0 +1,103 @@
1
+ require 'rest_client'
2
+ require 'chef/cookbook_loader'
3
+ require 'chef/checksum_cache'
4
+ require 'chef/sandbox'
5
+ require 'chef/cookbook_version'
6
+ require 'chef/cookbook/syntax_check'
7
+ require 'chef/cookbook/file_system_file_vendor'
8
+
9
+ class Chef
10
+ class CookbookUploader
11
+ class << self
12
+
13
+ def upload_cookbook(cookbook)
14
+ Chef::Log.info("Saving #{cookbook.name}")
15
+
16
+ rest = Chef::REST.new(Chef::Config[:chef_server_url])
17
+
18
+ # Syntax Check
19
+ validate_cookbook(cookbook)
20
+ # Generate metadata.json from metadata.rb
21
+ build_metadata(cookbook)
22
+
23
+ # generate checksums of cookbook files and create a sandbox
24
+ checksum_files = cookbook.checksums
25
+ checksums = checksum_files.inject({}){|memo,elt| memo[elt.first]=nil ; memo}
26
+ new_sandbox = rest.post_rest("sandboxes", { :checksums => checksums })
27
+
28
+ Chef::Log.info("Uploading files")
29
+ # upload the new checksums and commit the sandbox
30
+ new_sandbox['checksums'].each do |checksum, info|
31
+ if info['needs_upload'] == true
32
+ Chef::Log.info("Uploading #{checksum_files[checksum]} (checksum hex = #{checksum}) to #{info['url']}")
33
+
34
+ # Checksum is the hexadecimal representation of the md5,
35
+ # but we need the base64 encoding for the content-md5
36
+ # header
37
+ checksum64 = Base64.encode64([checksum].pack("H*")).strip
38
+ timestamp = Time.now.utc.iso8601
39
+ file_contents = File.read(checksum_files[checksum])
40
+ # TODO - 5/28/2010, cw: make signing and sending the request streaming
41
+ sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(
42
+ :http_method => :put,
43
+ :path => URI.parse(info['url']).path,
44
+ :body => file_contents,
45
+ :timestamp => timestamp,
46
+ :user_id => rest.client_name
47
+ )
48
+ headers = { 'content-type' => 'application/x-binary', 'content-md5' => checksum64, :accept => 'application/json' }
49
+ headers.merge!(sign_obj.sign(OpenSSL::PKey::RSA.new(rest.signing_key)))
50
+ begin
51
+ RestClient::Resource.new(info['url'], :headers=>headers, :timeout=>1800, :open_timeout=>1800).put(file_contents)
52
+ rescue RestClient::Exception => e
53
+ Chef::Log.error("Upload failed: #{e.message}\n#{e.response.body}")
54
+ raise
55
+ end
56
+ else
57
+ Chef::Log.debug("#{checksum_files[checksum]} has not changed")
58
+ end
59
+ end
60
+ sandbox_url = new_sandbox['uri']
61
+ Chef::Log.debug("Committing sandbox")
62
+ # Retry if S3 is claims a checksum doesn't exist (the eventual
63
+ # in eventual consistency)
64
+ retries = 0
65
+ begin
66
+ rest.put_rest(sandbox_url, {:is_completed => true})
67
+ rescue Net::HTTPServerException => e
68
+ if e.message =~ /^400/ && (retries += 1) <= 5
69
+ sleep 2
70
+ retry
71
+ else
72
+ raise
73
+ end
74
+ end
75
+ # files are uploaded, so save the manifest
76
+ cookbook.save
77
+ Chef::Log.info("Upload complete!")
78
+ end
79
+
80
+ def build_metadata(cookbook)
81
+ Chef::Log.debug("Generating metadata")
82
+ # FIXME: This knife command should be factored out into a
83
+ # library for use here
84
+ kcm = Chef::Knife::CookbookMetadata.new
85
+ kcm.config[:cookbook_path] = Chef::Config[:cookbook_path]
86
+ kcm.name_args = [ cookbook.name.to_s ]
87
+ kcm.run
88
+ cookbook.reload_metadata!
89
+ end
90
+
91
+ def validate_cookbook(cookbook)
92
+ syntax_checker = Chef::Cookbook::SyntaxCheck.for_cookbook(cookbook.name, @user_cookbook_path)
93
+ Chef::Log.info("Validating ruby files")
94
+ exit(1) unless syntax_checker.validate_ruby_files
95
+ Chef::Log.info("Validating templates")
96
+ exit(1) unless syntax_checker.validate_templates
97
+ Chef::Log.info("Syntax OK")
98
+ true
99
+ end
100
+
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,930 @@
1
+ #
2
+ # Author:: Adam Jacob (<adam@opscode.com>)
3
+ # Author:: Nuo Yan (<nuo@opscode.com>)
4
+ # Author:: Christopher Walters (<cw@opscode.com>)
5
+ # Author:: Tim Hinderliter (<tim@opscode.com>)
6
+ # Copyright:: Copyright (c) 2008-2010 Opscode, Inc.
7
+ # License:: Apache License, Version 2.0
8
+ #
9
+ # Licensed under the Apache License, Version 2.0 (the "License");
10
+ # you may not use this file except in compliance with the License.
11
+ # You may obtain a copy of the License at
12
+ #
13
+ # http://www.apache.org/licenses/LICENSE-2.0
14
+ #
15
+ # Unless required by applicable law or agreed to in writing, software
16
+ # distributed under the License is distributed on an "AS IS" BASIS,
17
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18
+ # See the License for the specific language governing permissions and
19
+ # limitations under the License.
20
+
21
+ require 'chef/log'
22
+ require 'chef/client'
23
+ require 'chef/node'
24
+ require 'chef/resource_definition_list'
25
+ require 'chef/recipe'
26
+ require 'chef/cookbook/file_vendor'
27
+
28
+ class Chef
29
+ # == Chef::CookbookVersion
30
+ # CookbookVersion is a model object encapsulating the data about a Chef
31
+ # cookbook. Chef supports maintaining multiple versions of a cookbook on a
32
+ # single server; each version is represented by a distinct instance of this
33
+ # class.
34
+ #--
35
+ # TODO: timh/cw: 5-24-2010: mutators for files (e.g., recipe_filenames=,
36
+ # recipe_filenames.insert) should dirty the manifest so it gets regenerated.
37
+ class CookbookVersion
38
+ include Chef::IndexQueue::Indexable
39
+
40
+ COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]
41
+
42
+ DESIGN_DOCUMENT = {
43
+ "version" => 7,
44
+ "language" => "javascript",
45
+ "views" => {
46
+ "all" => {
47
+ "map" => <<-EOJS
48
+ function(doc) {
49
+ if (doc.chef_type == "cookbook_version") {
50
+ emit(doc.name, doc);
51
+ }
52
+ }
53
+ EOJS
54
+ },
55
+ "all_id" => {
56
+ "map" => <<-EOJS
57
+ function(doc) {
58
+ if (doc.chef_type == "cookbook_version") {
59
+ emit(doc.name, doc.name);
60
+ }
61
+ }
62
+ EOJS
63
+ },
64
+ "all_with_version" => {
65
+ "map" => <<-EOJS
66
+ function(doc) {
67
+ if (doc.chef_type == "cookbook_version") {
68
+ emit(doc.cookbook_name, doc.version);
69
+ }
70
+ }
71
+ EOJS
72
+ },
73
+ "all_latest_version" => {
74
+ "map" => %q@
75
+ function(doc) {
76
+ if (doc.chef_type == "cookbook_version") {
77
+ emit(doc.cookbook_name, doc.version);
78
+ }
79
+ }
80
+ @,
81
+ "reduce" => %q@
82
+ function(keys, values, rereduce) {
83
+ var result = null;
84
+
85
+ for (var idx in values) {
86
+ var value = values[idx];
87
+
88
+ if (idx == 0) {
89
+ result = value;
90
+ continue;
91
+ }
92
+
93
+ var valueParts = value.split('.').map(function(v) { return parseInt(v); });
94
+ var resultParts = result.split('.').map(function(v) { return parseInt(v); });
95
+
96
+ if (valueParts[0] != resultParts[0]) {
97
+ if (valueParts[0] > resultParts[0]) {
98
+ result = value;
99
+ }
100
+ }
101
+ else if (valueParts[1] != resultParts[1]) {
102
+ if (valueParts[1] > resultParts[1]) {
103
+ result = value;
104
+ }
105
+ }
106
+ else if (valueParts[2] != resultParts[2]) {
107
+ if (valueParts[2] > resultParts[2]) {
108
+ result = value;
109
+ }
110
+ }
111
+ }
112
+ return result;
113
+ }
114
+ @
115
+ },
116
+ "all_latest_version_by_id" => {
117
+ "map" => %q@
118
+ function(doc) {
119
+ if (doc.chef_type == "cookbook_version") {
120
+ emit(doc.cookbook_name, {version: doc.version, id:doc._id});
121
+ }
122
+ }
123
+ @,
124
+ "reduce" => %q@
125
+ function(keys, values, rereduce) {
126
+ var result = null;
127
+
128
+ for (var idx in values) {
129
+ var value = values[idx];
130
+
131
+ if (idx == 0) {
132
+ result = value;
133
+ continue;
134
+ }
135
+
136
+ var valueParts = value.version.split('.').map(function(v) { return parseInt(v); });
137
+ var resultParts = result.version.split('.').map(function(v) { return parseInt(v); });
138
+
139
+ if (valueParts[0] != resultParts[0]) {
140
+ if (valueParts[0] > resultParts[0]) {
141
+ result = value;
142
+ }
143
+ }
144
+ else if (valueParts[1] != resultParts[1]) {
145
+ if (valueParts[1] > resultParts[1]) {
146
+ result = value;
147
+ }
148
+ }
149
+ else if (valueParts[2] != resultParts[2]) {
150
+ if (valueParts[2] > resultParts[2]) {
151
+ result = value;
152
+ }
153
+ }
154
+ }
155
+ return result;
156
+ }
157
+ @
158
+ },
159
+ }
160
+ }
161
+
162
+ attr_accessor :root_dir
163
+ attr_accessor :definition_filenames
164
+ attr_accessor :template_filenames
165
+ attr_accessor :file_filenames
166
+ attr_accessor :library_filenames
167
+ attr_accessor :resource_filenames
168
+ attr_accessor :provider_filenames
169
+ attr_accessor :root_filenames
170
+ attr_accessor :name
171
+ attr_accessor :metadata
172
+ attr_accessor :metadata_filenames
173
+ attr_accessor :status
174
+ attr_accessor :couchdb_rev
175
+ attr_accessor :couchdb
176
+
177
+ attr_reader :couchdb_id
178
+
179
+ # attribute_filenames also has a setter that has non-default
180
+ # functionality.
181
+ attr_reader :attribute_filenames
182
+
183
+ # recipe_filenames also has a setter that has non-default
184
+ # functionality.
185
+ attr_reader :recipe_filenames
186
+
187
+ attr_reader :recipe_filenames_by_name
188
+ attr_reader :attribute_filenames_by_short_filename
189
+
190
+ # This is the one and only method that knows how cookbook files'
191
+ # checksums are generated.
192
+ def self.checksum_cookbook_file(filepath)
193
+ Chef::ChecksumCache.generate_md5_checksum_for_file(filepath)
194
+ rescue Errno::ENOENT
195
+ Chef::Log.debug("File #{filepath} does not exist, so there is no checksum to generate")
196
+ nil
197
+ end
198
+
199
+ # Keep track of the filenames that we use in both eager cookbook
200
+ # downloading (during sync_cookbooks) and lazy (during the run
201
+ # itself, through FileVendor). After the run is over, clean up the
202
+ # cache.
203
+ def self.valid_cache_entries
204
+ @valid_cache_entries ||= {}
205
+ end
206
+
207
+ def self.reset_cache_validity
208
+ @valid_cache_entries = nil
209
+ end
210
+
211
+ def self.cache
212
+ Chef::FileCache
213
+ end
214
+
215
+ # Setup a notification to clear the valid_cache_entries when a Chef client
216
+ # run starts
217
+ Chef::Client.when_run_starts do |run_status|
218
+ reset_cache_validity
219
+ end
220
+
221
+ # Synchronizes all the cookbooks from the chef-server.
222
+ #
223
+ # === Returns
224
+ # true:: Always returns true
225
+ def self.sync_cookbooks(cookbook_hash)
226
+ Chef::Log.debug("Cookbooks to load: #{cookbook_hash.inspect}")
227
+
228
+ clear_obsoleted_cookbooks(cookbook_hash)
229
+
230
+ # Synchronize each of the node's cookbooks, and add to the
231
+ # valid_cache_entries hash.
232
+ cookbook_hash.values.each do |cookbook|
233
+ sync_cookbook_file_cache(cookbook)
234
+ end
235
+
236
+ true
237
+ end
238
+
239
+ # Iterates over cached cookbooks' files, removing files belonging to
240
+ # cookbooks that don't appear in +cookbook_hash+
241
+ def self.clear_obsoleted_cookbooks(cookbook_hash)
242
+ # Remove all cookbooks no longer relevant to this node
243
+ cache.find(File.join(%w{cookbooks ** *})).each do |cache_file|
244
+ cache_file =~ /^cookbooks\/([^\/]+)\//
245
+ unless cookbook_hash.has_key?($1)
246
+ Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.")
247
+ cache.delete(cache_file)
248
+ end
249
+ end
250
+ end
251
+
252
+ # Update the file caches for a given cache segment. Takes a segment name
253
+ # and a hash that matches one of the cookbooks/_attribute_files style
254
+ # remote file listings.
255
+ #
256
+ # === Parameters
257
+ # cookbook<Chef::Cookbook>:: The cookbook to update
258
+ # valid_cache_entries<Hash>:: Out-param; Added to this hash are the files that
259
+ # were referred to by this cookbook
260
+ def self.sync_cookbook_file_cache(cookbook)
261
+ Chef::Log.debug("Synchronizing cookbook #{cookbook.name}")
262
+
263
+ # files and templates are lazily loaded, and will be done later.
264
+ eager_segments = COOKBOOK_SEGMENTS.dup
265
+ eager_segments.delete(:files)
266
+ eager_segments.delete(:templates)
267
+
268
+ eager_segments.each do |segment|
269
+ segment_filenames = Array.new
270
+ cookbook.manifest[segment].each do |manifest_record|
271
+ # segment = cookbook segment
272
+ # remote_list = list of file hashes
273
+ #
274
+ # We need the list of known good attribute files, so we can delete any that are
275
+ # just laying about.
276
+
277
+ cache_filename = File.join("cookbooks", cookbook.name, manifest_record['path'])
278
+ valid_cache_entries[cache_filename] = true
279
+
280
+ current_checksum = nil
281
+ if cache.has_key?(cache_filename)
282
+ current_checksum = checksum_cookbook_file(cache.load(cache_filename, false))
283
+ end
284
+
285
+ # If the checksums are different between on-disk (current) and on-server
286
+ # (remote, per manifest), do the update. This will also execute if there
287
+ # is no current checksum.
288
+ if current_checksum != manifest_record['checksum']
289
+ raw_file = chef_server_rest.get_rest(manifest_record[:url], true)
290
+
291
+ Chef::Log.info("Storing updated #{cache_filename} in the cache.")
292
+ cache.move_to(raw_file.path, cache_filename)
293
+ else
294
+ Chef::Log.debug("Not storing #{cache_filename}, as the cache is up to date.")
295
+ end
296
+
297
+ # make the segment filenames a full path.
298
+ full_path_cache_filename = cache.load(cache_filename, false)
299
+ segment_filenames << full_path_cache_filename
300
+ end
301
+
302
+ # replace segment filenames with a full-path one.
303
+ if segment.to_sym == :recipes
304
+ cookbook.recipe_filenames = segment_filenames
305
+ elsif segment.to_sym == :attributes
306
+ cookbook.attribute_filenames = segment_filenames
307
+ else
308
+ cookbook.segment_filenames(segment).replace(segment_filenames)
309
+ end
310
+ end
311
+ end
312
+
313
+ def self.cleanup_file_cache
314
+ unless Chef::Config[:solo]
315
+ # Delete each file in the cache that we didn't encounter in the
316
+ # manifest.
317
+ cache.find(File.join(%w{cookbooks ** *})).each do |cache_filename|
318
+ unless valid_cache_entries[cache_filename]
319
+ Chef::Log.info("Removing #{cache_filename} from the cache; it is no longer on the server.")
320
+ cache.delete(cache_filename)
321
+ end
322
+ end
323
+ end
324
+ end
325
+
326
+ # Register a notification to cleanup unused files from cookbooks
327
+ Chef::Client.when_run_completes_successfully do |run_status|
328
+ cleanup_file_cache
329
+ end
330
+
331
+ # Creates a new Chef::CookbookVersion object.
332
+ #
333
+ # === Returns
334
+ # object<Chef::CookbookVersion>:: Duh. :)
335
+ def initialize(name, couchdb=nil)
336
+ @name = name
337
+ @attribute_filenames = Array.new
338
+ @definition_filenames = Array.new
339
+ @template_filenames = Array.new
340
+ @file_filenames = Array.new
341
+ @recipe_filenames = Array.new
342
+ @recipe_filenames_by_name = Hash.new
343
+ @library_filenames = Array.new
344
+ @resource_filenames = Array.new
345
+ @provider_filenames = Array.new
346
+ @metadata_filenames = Array.new
347
+ @root_dir = nil
348
+ @root_filenames = Array.new
349
+ @couchdb_id = nil
350
+ @couchdb = couchdb || Chef::CouchDB.new
351
+ @couchdb_rev = nil
352
+ @status = :ready
353
+ @manifest = nil
354
+ @file_vendor = nil
355
+ @metadata = Chef::Cookbook::Metadata.new
356
+ end
357
+
358
+ def version
359
+ metadata.version
360
+ end
361
+
362
+ def version=(new_version)
363
+ manifest["version"] = new_version
364
+ metadata.version(new_version)
365
+ end
366
+
367
+ # A manifest is a Mash that maps segment names to arrays of manifest
368
+ # records (see #preferred_manifest_record for format of manifest records),
369
+ # as well as describing cookbook metadata. The manifest follows a form
370
+ # like the following:
371
+ #
372
+ # {
373
+ # :cookbook_name = "apache2",
374
+ # :version = "1.0",
375
+ # :name = "Apache 2"
376
+ # :metadata = ???TODO: timh/cw: 5-24-2010: describe this format,
377
+ #
378
+ # :files => [
379
+ # {
380
+ # :name => "afile.rb",
381
+ # :path => "files/ubuntu-9.10/afile.rb",
382
+ # :checksum => "2222",
383
+ # :specificity => "ubuntu-9.10"
384
+ # },
385
+ # ],
386
+ # :templates => [ manifest_record1, ... ],
387
+ # ...
388
+ # }
389
+ def manifest
390
+ unless @manifest
391
+ generate_manifest
392
+ end
393
+ @manifest
394
+ end
395
+
396
+ def manifest=(new_manifest)
397
+ @manifest = Mash.new new_manifest
398
+ @checksums = extract_checksums_from_manifest(@manifest)
399
+ @manifest_records_by_path = extract_manifest_records_by_path(@manifest)
400
+
401
+ COOKBOOK_SEGMENTS.each do |segment|
402
+ next unless @manifest.has_key?(segment)
403
+ filenames = @manifest[segment].map{|manifest_record| manifest_record['name']}
404
+
405
+ if segment == :recipes
406
+ self.recipe_filenames = filenames
407
+ elsif segment == :attributes
408
+ self.attribute_filenames = filenames
409
+ else
410
+ segment_filenames(segment).clear
411
+ filenames.each { |filename| segment_filenames(segment) << filename }
412
+ end
413
+ end
414
+ end
415
+
416
+ # Returns a hash of checksums to either nil or the on disk path (which is
417
+ # done by generate_manifest).
418
+ def checksums
419
+ unless @checksums
420
+ generate_manifest
421
+ end
422
+ @checksums
423
+ end
424
+
425
+ def full_name
426
+ "#{name}-#{version}"
427
+ end
428
+
429
+ def attribute_filenames=(*filenames)
430
+ @attribute_filenames = filenames.flatten
431
+ @attribute_filenames_by_short_filename = filenames_by_name(attribute_filenames)
432
+ attribute_filenames
433
+ end
434
+
435
+ ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
436
+ alias :attribute_files :attribute_filenames
437
+ alias :attribute_files= :attribute_filenames=
438
+
439
+ # Return recipe names in the form of cookbook_name::recipe_name
440
+ def fully_qualified_recipe_names
441
+ results = Array.new
442
+ recipe_filenames_by_name.each_key do |rname|
443
+ results << "#{name}::#{rname}"
444
+ end
445
+ results
446
+ end
447
+
448
+ def recipe_filenames=(*filenames)
449
+ @recipe_filenames = filenames.flatten
450
+ @recipe_filenames_by_name = filenames_by_name(recipe_filenames)
451
+ recipe_filenames
452
+ end
453
+
454
+ ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
455
+ alias :recipe_files :recipe_filenames
456
+ alias :recipe_files= :recipe_filenames=
457
+
458
+ # called from DSL
459
+ def load_recipe(recipe_name, run_context)
460
+ unless recipe_filenames_by_name.has_key?(recipe_name)
461
+ raise ArgumentError, "Cannot find a recipe matching #{recipe_name} in cookbook #{name}"
462
+ end
463
+
464
+ Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{name}")
465
+ recipe = Chef::Recipe.new(name, recipe_name, run_context)
466
+ recipe_filename = recipe_filenames_by_name[recipe_name]
467
+
468
+ unless recipe_filename
469
+ raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}"
470
+ end
471
+
472
+ recipe.from_file(recipe_filename)
473
+ recipe
474
+ end
475
+
476
+ def segment_filenames(segment)
477
+ unless COOKBOOK_SEGMENTS.include?(segment)
478
+ raise ArgumentError, "invalid segment #{segment}: must be one of #{COOKBOOK_SEGMENTS.join(', ')}"
479
+ end
480
+
481
+ case segment.to_sym
482
+ when :resources
483
+ @resource_filenames
484
+ when :providers
485
+ @provider_filenames
486
+ when :recipes
487
+ @recipe_filenames
488
+ when :libraries
489
+ @library_filenames
490
+ when :definitions
491
+ @definition_filenames
492
+ when :attributes
493
+ @attribute_filenames
494
+ when :files
495
+ @file_filenames
496
+ when :templates
497
+ @template_filenames
498
+ when :root_files
499
+ @root_filenames
500
+ end
501
+ end
502
+
503
+ # Determine the most specific manifest record for the given
504
+ # segment/filename, given information in the node. Throws
505
+ # FileNotFound if there is no such segment and filename in the
506
+ # manifest.
507
+ #
508
+ # A manifest record is a Mash that follows the following form:
509
+ # {
510
+ # :name => "example.rb",
511
+ # :path => "files/default/example.rb",
512
+ # :specificity => "default",
513
+ # :checksum => "1234"
514
+ # }
515
+ def preferred_manifest_record(node, segment, filename)
516
+ preferences = preferences_for_path(node, segment, filename)
517
+
518
+ # ensure that we generate the manifest, which will also generate
519
+ # @manifest_records_by_path
520
+ manifest
521
+
522
+ # in order of prefernce, look for the filename in the manifest
523
+ found_pref = preferences.find {|preferred_filename| @manifest_records_by_path[preferred_filename] }
524
+ if found_pref
525
+ @manifest_records_by_path[found_pref]
526
+ else
527
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} does not contain file #{segment}/#{filename}"
528
+ end
529
+ end
530
+
531
+ def preferred_filename_on_disk_location(node, segment, filename, current_filepath=nil)
532
+ manifest_record = preferred_manifest_record(node, segment, filename)
533
+ if current_filepath && (manifest_record['checksum'] == self.class.checksum_cookbook_file(current_filepath))
534
+ nil
535
+ else
536
+ file_vendor.get_filename(manifest_record['path'])
537
+ end
538
+ end
539
+
540
+ def relative_filenames_in_preferred_directory(node, segment, dirname)
541
+ preferences = preferences_for_path(node, segment, dirname)
542
+ filenames_by_pref = Hash.new
543
+ preferences.each { |pref| filenames_by_pref[pref] = Array.new }
544
+
545
+ manifest[segment].each do |manifest_record|
546
+ manifest_record_path = manifest_record[:path]
547
+
548
+ # find the NON SPECIFIC filenames, but prefer them by filespecificity.
549
+ # For example, if we have a file:
550
+ # 'files/default/somedir/somefile.conf' we only keep
551
+ # 'somedir/somefile.conf'. If there is also
552
+ # 'files/$hostspecific/somedir/otherfiles' that matches the requested
553
+ # hostname specificity, that directory will win, as it is more specific.
554
+ #
555
+ # This is clearly ugly b/c the use case is for remote directory, where
556
+ # we're just going to make cookbook_files out of these and make the
557
+ # cookbook find them by filespecificity again. but it's the shortest
558
+ # path to "success" for now.
559
+ if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
560
+ specificity_dirname = $1
561
+ non_specific_path = manifest_record_path[/#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)}\/(.+)$/, 1]
562
+ # Record the specificity_dirname only if it's in the list of
563
+ # valid preferences
564
+ if filenames_by_pref[specificity_dirname]
565
+ filenames_by_pref[specificity_dirname] << non_specific_path
566
+ end
567
+ end
568
+ end
569
+
570
+ best_pref = preferences.find { |pref| !filenames_by_pref[pref].empty? }
571
+
572
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
573
+
574
+ filenames_by_pref[best_pref]
575
+
576
+ end
577
+
578
+ # Determine the manifest records from the most specific directory
579
+ # for the given node. See #preferred_manifest_record for a
580
+ # description of entries of the returned Array.
581
+ def preferred_manifest_records_for_directory(node, segment, dirname)
582
+ preferences = preferences_for_path(node, segment, dirname)
583
+ records_by_pref = Hash.new
584
+ preferences.each { |pref| records_by_pref[pref] = Array.new }
585
+
586
+ manifest[segment].each do |manifest_record|
587
+ manifest_record_path = manifest_record[:path]
588
+
589
+ # extract the preference part from the path.
590
+ if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
591
+ # Note the specificy_dirname includes the segment and
592
+ # dirname argument as above, which is what
593
+ # preferences_for_path returns. It could be
594
+ # "files/ubuntu-9.10/dirname", for example.
595
+ specificity_dirname = $1
596
+
597
+ # Record the specificity_dirname only if it's in the list of
598
+ # valid preferences
599
+ if records_by_pref[specificity_dirname]
600
+ records_by_pref[specificity_dirname] << manifest_record
601
+ end
602
+ end
603
+ end
604
+
605
+ best_pref = preferences.find { |pref| !records_by_pref[pref].empty? }
606
+
607
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
608
+
609
+ records_by_pref[best_pref]
610
+ end
611
+
612
+
613
+ # Given a node, segment and path (filename or directory name),
614
+ # return the priority-ordered list of preference locations to
615
+ # look.
616
+ def preferences_for_path(node, segment, path)
617
+ # only files and templates can be platform-specific
618
+ if segment.to_sym == :files || segment.to_sym == :templates
619
+ begin
620
+ platform, version = Chef::Platform.find_platform_and_version(node)
621
+ rescue ArgumentError => e
622
+ # Skip platform/version if they were not found by find_platform_and_version
623
+ if e.message =~ /Cannot find a (?:platform|version)/
624
+ platform = "/unknown_platform/"
625
+ version = "/unknown_platform_version/"
626
+ else
627
+ raise
628
+ end
629
+ end
630
+
631
+ fqdn = node[:fqdn]
632
+
633
+ # Most specific to least specific places to find the path
634
+ [
635
+ File.join(segment.to_s, "host-#{fqdn}", path),
636
+ File.join(segment.to_s, "#{platform}-#{version}", path),
637
+ File.join(segment.to_s, platform.to_s, path),
638
+ File.join(segment.to_s, "default", path)
639
+ ]
640
+ else
641
+ [File.join(segment, path)]
642
+ end
643
+ end
644
+ private :preferences_for_path
645
+
646
+ def to_hash
647
+ result = manifest.dup
648
+ result['chef_type'] = 'cookbook_version'
649
+ result["_rev"] = couchdb_rev if couchdb_rev
650
+ result.to_hash
651
+ end
652
+
653
+ def to_json(*a)
654
+ result = self.to_hash
655
+ result['json_class'] = self.class.name
656
+ result.to_json(*a)
657
+ end
658
+
659
+ def self.json_create(o)
660
+ cookbook_version = new(o["cookbook_name"])
661
+ if o.has_key?('_rev')
662
+ cookbook_version.couchdb_rev = o["_rev"] if o.has_key?("_rev")
663
+ o.delete("_rev")
664
+ end
665
+ if o.has_key?("_id")
666
+ cookbook_version.couchdb_id = o["_id"] if o.has_key?("_id")
667
+ cookbook_version.index_id = cookbook_version.couchdb_id
668
+ o.delete("_id")
669
+ end
670
+ cookbook_version.manifest = o
671
+ # We want the Chef::Cookbook::Metadata object to always be inflated
672
+ cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"])
673
+ cookbook_version
674
+ end
675
+
676
+ def generate_manifest_with_urls(&url_generator)
677
+ rendered_manifest = manifest.dup
678
+ COOKBOOK_SEGMENTS.each do |segment|
679
+ if rendered_manifest.has_key?(segment)
680
+ rendered_manifest[segment].each do |manifest_record|
681
+ url_options = { :cookbook_name => name.to_s, :cookbook_version => version, :checksum => manifest_record["checksum"] }
682
+ manifest_record["url"] = url_generator.call(url_options)
683
+ end
684
+ end
685
+ end
686
+ rendered_manifest
687
+ end
688
+
689
+ def metadata_json_file
690
+ File.join(root_dir, "metadata.json")
691
+ end
692
+
693
+ def metadata_rb_file
694
+ File.join(root_dir, "metadata.rb")
695
+ end
696
+
697
+ def reload_metadata!
698
+ if File.exists?(metadata_json_file)
699
+ metadata.from_json(IO.read(metadata_json_file))
700
+ end
701
+ end
702
+
703
+ ##
704
+ # REST API
705
+ ##
706
+ def self.chef_server_rest
707
+ Chef::REST.new(Chef::Config[:chef_server_url])
708
+ end
709
+
710
+ def chef_server_rest
711
+ self.class.chef_server_rest
712
+ end
713
+
714
+ def save
715
+ chef_server_rest.put_rest("cookbooks/#{name}/#{version}", self)
716
+ self
717
+ end
718
+ alias :create :save
719
+
720
+ def destroy
721
+ chef_server_rest.delete_rest("cookbooks/#{name}/#{version}")
722
+ self
723
+ end
724
+
725
+ def self.load(name, version="_latest")
726
+ version = "_latest" if version == "latest"
727
+ chef_server_rest.get_rest("cookbooks/#{name}/#{version}")
728
+ end
729
+
730
+ def self.list
731
+ chef_server_rest.get_rest('cookbooks')
732
+ end
733
+
734
+ ##
735
+ # Given a +cookbook_name+, get a list of all versions that exist on the
736
+ # server.
737
+ # ===Returns
738
+ # [String]:: Array of cookbook versions, which are strings like 'x.y.z'
739
+ # nil:: if the cookbook doesn't exist. an error will also be logged.
740
+ def self.available_versions(cookbook_name)
741
+ chef_server_rest.get_rest("cookbooks/#{cookbook_name}").values.flatten
742
+ rescue Net::HTTPServerException => e
743
+ if e.to_s =~ /^404/
744
+ Chef::Log.error("Cannot find a cookbook named #{cookbook_name}")
745
+ nil
746
+ else
747
+ raise
748
+ end
749
+ end
750
+
751
+ # Get the newest version of all cookbooks
752
+ def self.latest_cookbooks
753
+ chef_server_rest.get_rest('cookbooks/_latest')
754
+ end
755
+
756
+ ##
757
+ # Couchdb
758
+ ##
759
+
760
+ def self.cdb_by_name(cookbook_name, couchdb=nil)
761
+ cdb = (couchdb || Chef::CouchDB.new)
762
+ options = { :startkey => cookbook_name, :endkey => cookbook_name }
763
+ rs = cdb.get_view("cookbooks", "all_with_version", options)
764
+ rs["rows"].inject({}) { |memo, row| memo.has_key?(row["key"]) ? memo[row["key"]] << row["value"] : memo[row["key"]] = [ row["value"] ]; memo }
765
+ end
766
+
767
+ def self.create_design_document(couchdb=nil)
768
+ (couchdb || Chef::CouchDB.new).create_design_document("cookbooks", DESIGN_DOCUMENT)
769
+ end
770
+
771
+ def self.cdb_list_latest(inflate=false, couchdb=nil)
772
+ couchdb ||= Chef::CouchDB.new
773
+ if inflate
774
+ doc_ids = cdb_list_latest_ids.map {|i|i["id"]}
775
+ couchdb.bulk_get(doc_ids)
776
+ else
777
+ results = couchdb.get_view("cookbooks", "all_latest_version", :group=>true)["rows"]
778
+ results.inject({}) { |mapped, row| mapped[row["key"]] = row["value"]; mapped}
779
+ end
780
+ end
781
+
782
+ def self.cdb_list_latest_ids(inflate=false, couchdb=nil)
783
+ couchdb ||= Chef::CouchDB.new
784
+ results = couchdb.get_view("cookbooks", "all_latest_version_by_id", :group=>true)["rows"]
785
+ results.map { |name_and_id| name_and_id["value"]}
786
+ end
787
+
788
+ def self.cdb_list(inflate=false, couchdb=nil)
789
+ rs = (couchdb || Chef::CouchDB.new).list("cookbooks", inflate)
790
+ lookup = (inflate ? "value" : "key")
791
+ rs["rows"].collect { |r| r[lookup] }
792
+ end
793
+
794
+ def self.cdb_load(name, version='latest', couchdb=nil)
795
+ cdb = couchdb || Chef::CouchDB.new
796
+ if version == "latest" || version == "_latest"
797
+ rs = cdb.get_view("cookbooks", "all_latest_version", :key => name, :descending => true, :group => true, :reduce => true)["rows"].first
798
+ cdb.load("cookbook_version", "#{rs["key"]}-#{rs["value"]}")
799
+ else
800
+ cdb.load("cookbook_version", "#{name}-#{version}")
801
+ end
802
+ end
803
+
804
+ def cdb_destroy
805
+ (couchdb || Chef::CouchDB.new).delete("cookbook_version", full_name, couchdb_rev)
806
+ end
807
+
808
+ # Runs on Chef Server (API); deletes the cookbook from couchdb and also destroys associated
809
+ # checksum documents
810
+ def purge
811
+ checksums.keys.each do |checksum|
812
+ Chef::Checksum.cdb_load(checksum, couchdb).purge
813
+ end
814
+ cdb_destroy
815
+ end
816
+
817
+ def cdb_save
818
+ @couchdb_rev = couchdb.store("cookbook_version", full_name, self)["rev"]
819
+ end
820
+
821
+ def couchdb_id=(value)
822
+ @couchdb_id = value
823
+ @index_id = value
824
+ end
825
+
826
+ private
827
+
828
+ # For each filename, produce a mapping of base filename (i.e. recipe name
829
+ # or attribute file) to on disk location
830
+ def filenames_by_name(filenames)
831
+ filenames.select{|filename| filename =~ /\.rb$/}.inject({}){|memo, filename| memo[File.basename(filename, '.rb')] = filename ; memo }
832
+ end
833
+
834
+ # See #manifest for a description of the manifest return value.
835
+ # See #preferred_manifest_record for a description an individual manifest record.
836
+ def generate_manifest
837
+ manifest = Mash.new({
838
+ :recipes => Array.new,
839
+ :definitions => Array.new,
840
+ :libraries => Array.new,
841
+ :attributes => Array.new,
842
+ :files => Array.new,
843
+ :templates => Array.new,
844
+ :resources => Array.new,
845
+ :providers => Array.new,
846
+ :root_files => Array.new
847
+ })
848
+ checksums_to_on_disk_paths = {}
849
+
850
+ COOKBOOK_SEGMENTS.each do |segment|
851
+ segment_filenames(segment).each do |segment_file|
852
+ next if File.directory?(segment_file)
853
+
854
+ file_name = nil
855
+ path = nil
856
+ specificity = "default"
857
+
858
+ if segment == :root_files
859
+ matcher = segment_file.match(".+/#{Regexp.escape(name.to_s)}/(.+)")
860
+ file_name = matcher[1]
861
+ path = file_name
862
+ elsif segment == :templates || segment == :files
863
+ matcher = segment_file.match("/#{Regexp.escape(name.to_s)}/(#{Regexp.escape(segment.to_s)}/(.+?)/(.+))")
864
+ unless matcher
865
+ Chef::Log.debug("Skipping file #{segment_file}, as it doesn't have a proper segment.")
866
+ next
867
+ end
868
+ path = matcher[1]
869
+ specificity = matcher[2]
870
+ file_name = matcher[3]
871
+ else
872
+ matcher = segment_file.match("/#{Regexp.escape(name.to_s)}/(#{Regexp.escape(segment.to_s)}/(.+))")
873
+ path = matcher[1]
874
+ file_name = matcher[2]
875
+ end
876
+
877
+ csum = self.class.checksum_cookbook_file(segment_file)
878
+ checksums_to_on_disk_paths[csum] = segment_file
879
+ rs = Mash.new({
880
+ :name => file_name,
881
+ :path => path,
882
+ :checksum => csum
883
+ })
884
+ rs[:specificity] = specificity
885
+
886
+ manifest[segment] << rs
887
+ end
888
+ end
889
+
890
+ manifest[:cookbook_name] = name.to_s
891
+ manifest[:metadata] = metadata
892
+ manifest[:version] = metadata.version
893
+ manifest[:name] = full_name
894
+
895
+ @checksums = checksums_to_on_disk_paths
896
+ @manifest = manifest
897
+ @manifest_records_by_path = extract_manifest_records_by_path(manifest)
898
+ end
899
+
900
+ def file_vendor
901
+ unless @file_vendor
902
+ @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(manifest)
903
+ end
904
+ @file_vendor
905
+ end
906
+
907
+ def extract_checksums_from_manifest(manifest)
908
+ checksums = {}
909
+ COOKBOOK_SEGMENTS.each do |segment|
910
+ next unless manifest.has_key?(segment)
911
+ manifest[segment].each do |manifest_record|
912
+ checksums[manifest_record[:checksum]] = nil
913
+ end
914
+ end
915
+ checksums
916
+ end
917
+
918
+ def extract_manifest_records_by_path(manifest)
919
+ manifest_records_by_path = {}
920
+ COOKBOOK_SEGMENTS.each do |segment|
921
+ next unless manifest.has_key?(segment)
922
+ manifest[segment].each do |manifest_record|
923
+ manifest_records_by_path[manifest_record[:path]] = manifest_record
924
+ end
925
+ end
926
+ manifest_records_by_path
927
+ end
928
+
929
+ end
930
+ end