chef 0.8.16 → 0.9.0.a3

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of chef might be problematic. Click here for more details.

Files changed (185) hide show
  1. data/bin/shef +1 -0
  2. data/distro/common/man/man1/chef-server-webui.1 +106 -0
  3. data/distro/common/man/man1/chef-server.1 +0 -1
  4. data/distro/common/man/man1/chef-solr-indexer.1 +55 -0
  5. data/distro/common/man/man1/chef-solr.1 +55 -0
  6. data/distro/common/man/man8/chef-client.8 +4 -2
  7. data/distro/common/man/man8/chef-solo.8 +1 -2
  8. data/distro/common/man/man8/chef-solr-rebuild.8 +37 -0
  9. data/distro/common/man/man8/knife.8 +668 -266
  10. data/distro/common/man/man8/shef.8 +45 -0
  11. data/distro/common/markdown/README +3 -0
  12. data/distro/common/markdown/knife.mkd +520 -0
  13. data/distro/debian/etc/default/chef-client +4 -0
  14. data/distro/debian/etc/default/chef-server +6 -0
  15. data/distro/debian/etc/default/chef-server-webui +6 -0
  16. data/distro/debian/etc/default/chef-solr +4 -0
  17. data/distro/debian/etc/default/chef-solr-indexer +4 -0
  18. data/distro/debian/etc/init.d/chef-client +41 -41
  19. data/distro/debian/etc/init.d/chef-server +10 -10
  20. data/distro/debian/etc/init.d/chef-server-webui +121 -0
  21. data/distro/debian/etc/init.d/chef-solr +177 -0
  22. data/distro/debian/etc/init.d/chef-solr-indexer +176 -0
  23. data/distro/redhat/etc/init.d/chef-client +76 -48
  24. data/distro/redhat/etc/init.d/chef-server +85 -51
  25. data/distro/redhat/etc/init.d/chef-server-webui +85 -51
  26. data/distro/redhat/etc/init.d/chef-solr +77 -49
  27. data/distro/redhat/etc/init.d/chef-solr-indexer +77 -48
  28. data/distro/redhat/etc/logrotate.d/chef-client +8 -0
  29. data/distro/redhat/etc/logrotate.d/chef-server +8 -0
  30. data/distro/redhat/etc/logrotate.d/chef-server-webui +8 -0
  31. data/distro/redhat/etc/logrotate.d/chef-solr +8 -0
  32. data/distro/redhat/etc/logrotate.d/chef-solr-indexer +8 -0
  33. data/distro/redhat/etc/sysconfig/chef-client +9 -4
  34. data/distro/redhat/etc/sysconfig/chef-server +10 -6
  35. data/distro/redhat/etc/sysconfig/chef-server-webui +10 -6
  36. data/distro/redhat/etc/sysconfig/chef-solr +3 -4
  37. data/distro/redhat/etc/sysconfig/chef-solr-indexer +3 -3
  38. data/lib/chef.rb +16 -5
  39. data/lib/chef/application/knife.rb +2 -2
  40. data/lib/chef/application/solo.rb +1 -7
  41. data/lib/chef/cache/checksum.rb +12 -5
  42. data/lib/chef/cache/file_cache_by_checksum.rb +52 -0
  43. data/lib/chef/checksum.rb +115 -0
  44. data/lib/chef/client.rb +193 -185
  45. data/lib/chef/config.rb +9 -1
  46. data/lib/chef/cookbook/cookbook_collection.rb +43 -0
  47. data/lib/chef/cookbook/file_system_file_vendor.rb +53 -0
  48. data/lib/chef/cookbook/file_vendor.rb +47 -0
  49. data/lib/chef/cookbook/metadata.rb +34 -35
  50. data/lib/chef/cookbook/metadata/version.rb +1 -1
  51. data/lib/chef/cookbook_loader.rb +70 -45
  52. data/lib/chef/cookbook_version.rb +760 -0
  53. data/lib/chef/couchdb.rb +8 -5
  54. data/lib/chef/data_bag_item.rb +5 -5
  55. data/lib/chef/exceptions.rb +10 -0
  56. data/lib/chef/file_access_control.rb +134 -0
  57. data/lib/chef/handler.rb +62 -0
  58. data/lib/chef/handler/json_file.rb +47 -0
  59. data/lib/chef/knife.rb +14 -2
  60. data/lib/chef/knife/bootstrap.rb +126 -0
  61. data/lib/chef/knife/cookbook_bulk_delete.rb +1 -1
  62. data/lib/chef/knife/cookbook_delete.rb +4 -4
  63. data/lib/chef/knife/cookbook_download.rb +57 -26
  64. data/lib/chef/knife/cookbook_metadata.rb +2 -2
  65. data/lib/chef/knife/cookbook_show.rb +30 -11
  66. data/lib/chef/knife/cookbook_upload.rb +113 -86
  67. data/lib/chef/knife/ec2_server_create.rb +146 -0
  68. data/lib/chef/knife/ec2_server_delete.rb +84 -0
  69. data/lib/chef/knife/ec2_server_list.rb +82 -0
  70. data/lib/chef/knife/status.rb +51 -0
  71. data/lib/chef/mixin/language_include_attribute.rb +16 -11
  72. data/lib/chef/mixin/language_include_recipe.rb +15 -16
  73. data/lib/chef/mixin/recipe_definition_dsl_core.rb +17 -20
  74. data/lib/chef/mixin/shell_out.rb +38 -0
  75. data/lib/chef/mixins.rb +1 -1
  76. data/lib/chef/node.rb +190 -63
  77. data/lib/chef/node/attribute.rb +92 -78
  78. data/lib/chef/platform.rb +24 -4
  79. data/lib/chef/provider.rb +28 -10
  80. data/lib/chef/provider/breakpoint.rb +2 -2
  81. data/lib/chef/provider/cookbook_file.rb +96 -0
  82. data/lib/chef/provider/cron.rb +2 -2
  83. data/lib/chef/provider/deploy.rb +12 -10
  84. data/lib/chef/provider/env.rb +152 -0
  85. data/lib/chef/provider/env/windows.rb +75 -0
  86. data/lib/chef/provider/file.rb +10 -14
  87. data/lib/chef/provider/group.rb +15 -2
  88. data/lib/chef/provider/group/dscl.rb +17 -25
  89. data/lib/chef/provider/group/gpasswd.rb +6 -3
  90. data/lib/chef/provider/group/pw.rb +3 -7
  91. data/lib/chef/provider/group/windows.rb +79 -0
  92. data/lib/chef/provider/link.rb +4 -5
  93. data/lib/chef/provider/mdadm.rb +25 -18
  94. data/lib/chef/provider/mount/mount.rb +28 -27
  95. data/lib/chef/provider/package.rb +35 -35
  96. data/lib/chef/provider/package/dpkg.rb +13 -10
  97. data/lib/chef/provider/package/easy_install.rb +6 -6
  98. data/lib/chef/provider/package/freebsd.rb +17 -51
  99. data/lib/chef/provider/package/rpm.rb +1 -1
  100. data/lib/chef/provider/package/rubygems.rb +391 -74
  101. data/lib/chef/provider/package/yum.rb +2 -2
  102. data/lib/chef/provider/package/zypper.rb +2 -1
  103. data/lib/chef/provider/remote_directory.rb +60 -83
  104. data/lib/chef/provider/remote_file.rb +17 -66
  105. data/lib/chef/provider/script.rb +20 -9
  106. data/lib/chef/provider/service.rb +23 -30
  107. data/lib/chef/provider/service/arch.rb +3 -3
  108. data/lib/chef/provider/service/debian.rb +22 -17
  109. data/lib/chef/provider/service/freebsd.rb +4 -4
  110. data/lib/chef/provider/service/init.rb +2 -2
  111. data/lib/chef/provider/service/redhat.rb +14 -16
  112. data/lib/chef/provider/service/simple.rb +7 -3
  113. data/lib/chef/provider/service/solaris.rb +85 -0
  114. data/lib/chef/provider/service/upstart.rb +12 -7
  115. data/lib/chef/provider/service/windows.rb +2 -2
  116. data/lib/chef/provider/template.rb +133 -118
  117. data/lib/chef/provider/user.rb +34 -17
  118. data/lib/chef/provider/user/dscl.rb +117 -114
  119. data/lib/chef/provider/user/windows.rb +124 -0
  120. data/lib/chef/providers.rb +7 -0
  121. data/lib/chef/recipe.rb +39 -20
  122. data/lib/chef/resource.rb +47 -52
  123. data/lib/chef/resource/apt_package.rb +4 -4
  124. data/lib/chef/resource/bash.rb +4 -4
  125. data/lib/chef/resource/cookbook_file.rb +45 -0
  126. data/lib/chef/resource/cron.rb +3 -3
  127. data/lib/chef/resource/csh.rb +4 -4
  128. data/lib/chef/resource/deploy.rb +3 -3
  129. data/lib/chef/resource/directory.rb +4 -4
  130. data/lib/chef/resource/dpkg_package.rb +4 -4
  131. data/lib/chef/resource/easy_install_package.rb +3 -3
  132. data/lib/chef/resource/env.rb +58 -0
  133. data/lib/chef/resource/erl_call.rb +3 -3
  134. data/lib/chef/resource/execute.rb +3 -3
  135. data/lib/chef/resource/file.rb +3 -3
  136. data/lib/chef/resource/freebsd_package.rb +3 -3
  137. data/lib/chef/resource/gem_package.rb +17 -9
  138. data/lib/chef/resource/git.rb +3 -3
  139. data/lib/chef/resource/group.rb +3 -3
  140. data/lib/chef/resource/http_request.rb +4 -4
  141. data/lib/chef/resource/ifconfig.rb +3 -3
  142. data/lib/chef/resource/link.rb +3 -3
  143. data/lib/chef/resource/log.rb +2 -2
  144. data/lib/chef/resource/macports_package.rb +2 -2
  145. data/lib/chef/resource/mdadm.rb +3 -3
  146. data/lib/chef/resource/mount.rb +2 -2
  147. data/lib/chef/resource/package.rb +4 -4
  148. data/lib/chef/resource/pacman_package.rb +4 -4
  149. data/lib/chef/resource/perl.rb +4 -4
  150. data/lib/chef/resource/portage_package.rb +4 -4
  151. data/lib/chef/resource/python.rb +4 -4
  152. data/lib/chef/resource/remote_directory.rb +3 -3
  153. data/lib/chef/resource/remote_file.rb +26 -3
  154. data/lib/chef/resource/route.rb +3 -3
  155. data/lib/chef/resource/ruby.rb +3 -3
  156. data/lib/chef/resource/ruby_block.rb +3 -2
  157. data/lib/chef/resource/scm.rb +7 -5
  158. data/lib/chef/resource/script.rb +4 -4
  159. data/lib/chef/resource/service.rb +3 -3
  160. data/lib/chef/resource/subversion.rb +4 -2
  161. data/lib/chef/resource/template.rb +3 -3
  162. data/lib/chef/resource/user.rb +3 -3
  163. data/lib/chef/resource/yum_package.rb +3 -3
  164. data/lib/chef/resource_collection.rb +9 -5
  165. data/lib/chef/resources.rb +2 -0
  166. data/lib/chef/rest.rb +4 -0
  167. data/lib/chef/role.rb +2 -0
  168. data/lib/chef/run_context.rb +108 -0
  169. data/lib/chef/run_list.rb +75 -98
  170. data/lib/chef/run_list/run_list_expansion.rb +156 -0
  171. data/lib/chef/run_list/run_list_item.rb +71 -0
  172. data/lib/chef/runner.rb +58 -61
  173. data/lib/chef/sandbox.rb +147 -0
  174. data/lib/chef/shef.rb +4 -3
  175. data/lib/chef/shef/ext.rb +12 -4
  176. data/lib/chef/shef/shef_session.rb +27 -23
  177. data/lib/chef/shell_out.rb +375 -0
  178. data/lib/chef/util/windows.rb +56 -0
  179. data/lib/chef/util/windows/net_group.rb +101 -0
  180. data/lib/chef/util/windows/net_user.rb +198 -0
  181. data/lib/chef/version.rb +20 -0
  182. metadata +112 -22
  183. data/lib/chef/compile.rb +0 -158
  184. data/lib/chef/cookbook.rb +0 -201
  185. data/lib/chef/mixin/generate_url.rb +0 -58
@@ -0,0 +1,760 @@
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/node'
23
+ require 'chef/resource_definition_list'
24
+ require 'chef/recipe'
25
+ require 'chef/cookbook/file_vendor'
26
+
27
+ # TODO: timh/cw: 5-24-2010: mutators for files (e.g., recipe_filenames=,
28
+ # recipe_filenames.insert) should dirty the manifest so it gets regenerated.
29
+ class Chef
30
+ class CookbookVersion
31
+ include Chef::IndexQueue::Indexable
32
+
33
+ attr_accessor :definition_filenames, :template_filenames, :file_filenames,
34
+ :library_filenames, :resource_filenames, :provider_filenames, :root_filenames, :name,
35
+ :metadata, :metadata_filenames, :status, :couchdb_rev, :couchdb
36
+ attr_reader :couchdb_id
37
+ attr_reader :file_vendor
38
+
39
+ # attribute_filenames also has a setter that has non-default
40
+ # functionality.
41
+ attr_reader :attribute_filenames
42
+
43
+ # recipe_filenames also has a setter that has non-default
44
+ # functionality.
45
+ attr_reader :recipe_filenames
46
+
47
+ attr_reader :recipe_filenames_by_name
48
+ attr_reader :attribute_filenames_by_short_filename
49
+
50
+ COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]
51
+
52
+ DESIGN_DOCUMENT = {
53
+ "version" => 5,
54
+ "language" => "javascript",
55
+ "views" => {
56
+ "all" => {
57
+ "map" => <<-EOJS
58
+ function(doc) {
59
+ if (doc.chef_type == "cookbook_version") {
60
+ emit(doc.name, doc);
61
+ }
62
+ }
63
+ EOJS
64
+ },
65
+ "all_id" => {
66
+ "map" => <<-EOJS
67
+ function(doc) {
68
+ if (doc.chef_type == "cookbook_version") {
69
+ emit(doc.name, doc.name);
70
+ }
71
+ }
72
+ EOJS
73
+ },
74
+ "all_with_version" => {
75
+ "map" => <<-EOJS
76
+ function(doc) {
77
+ if (doc.chef_type == "cookbook_version") {
78
+ emit(doc.cookbook_name, doc.version);
79
+ }
80
+ }
81
+ EOJS
82
+ },
83
+ "all_latest_version" => {
84
+ "map" => %q@
85
+ function(doc) {
86
+ if (doc.chef_type == "cookbook_version") {
87
+ emit(doc.cookbook_name, doc.version);
88
+ }
89
+ }
90
+ @,
91
+ "reduce" => %q@
92
+ function(keys, values, rereduce) {
93
+ var result = null;
94
+
95
+ for (var idx in values) {
96
+ var value = values[idx];
97
+
98
+ if (idx == 0) {
99
+ result = value;
100
+ continue;
101
+ }
102
+
103
+ var valueParts = value[1].split('.').map(function(v) { return parseInt(v); });
104
+ var resultParts = result[1].split('.').map(function(v) { return parseInt(v); });
105
+
106
+ if (valueParts[0] != resultParts[0]) {
107
+ if (valueParts[0] > resultParts[0]) {
108
+ result = value;
109
+ }
110
+ }
111
+ else if (valueParts[1] != resultParts[1]) {
112
+ if (valueParts[1] > resultParts[1]) {
113
+ result = value;
114
+ }
115
+ }
116
+ else if (valueParts[2] != resultParts[2]) {
117
+ if (valueParts[2] > resultParts[2]) {
118
+ result = value;
119
+ }
120
+ }
121
+ }
122
+ return result;
123
+ }
124
+ @
125
+ },
126
+ }
127
+ }
128
+
129
+ # This is the one and only method that knows how cookbook files'
130
+ # checksums are generated.
131
+ def self.checksum_cookbook_file(filepath)
132
+ Chef::Cache::Checksum.generate_md5_checksum_for_file(filepath)
133
+ rescue Errno::ENOENT
134
+ Chef::Log.debug("File #{filepath} does not exist, so there is no checksum to generate")
135
+ nil
136
+ end
137
+
138
+ # Creates a new Chef::CookbookVersion object.
139
+ #
140
+ # === Returns
141
+ # object<Chef::CookbookVersion>:: Duh. :)
142
+ def initialize(name, couchdb=nil)
143
+ @name = name
144
+ @attribute_filenames = Array.new
145
+ @definition_filenames = Array.new
146
+ @template_filenames = Array.new
147
+ @file_filenames = Array.new
148
+ @recipe_filenames = Array.new
149
+ @recipe_filenames_by_name = Hash.new
150
+ @library_filenames = Array.new
151
+ @resource_filenames = Array.new
152
+ @provider_filenames = Array.new
153
+ @metadata_filenames = Array.new
154
+ @root_filenames = Array.new
155
+ @couchdb_id = nil
156
+ @couchdb = couchdb || Chef::CouchDB.new
157
+ @couchdb_rev = nil
158
+ @status = :ready
159
+ @manifest = nil
160
+ @file_vendor = nil
161
+ @metadata = {}
162
+ end
163
+
164
+ def version
165
+ metadata.version
166
+ end
167
+
168
+ def version=(new_version)
169
+ manifest["version"] = new_version
170
+ metadata.version(new_version)
171
+ end
172
+
173
+ # A manifest is a Mash that maps segment names to arrays of manifest
174
+ # records (see #preferred_manifest_record for format of manifest records),
175
+ # as well as describing cookbook metadata. The manifest follows a form
176
+ # like the following:
177
+ #
178
+ # {
179
+ # :cookbook_name = "apache2",
180
+ # :version = "1.0",
181
+ # :name = "Apache 2"
182
+ # :metadata = ???TODO: timh/cw: 5-24-2010: describe this format,
183
+ #
184
+ # :files => [
185
+ # {
186
+ # :name => "afile.rb",
187
+ # :path => "files/ubuntu-9.10/afile.rb",
188
+ # :checksum => "2222",
189
+ # :specificity => "ubuntu-9.10"
190
+ # },
191
+ # ],
192
+ # :templates => [ manifest_record1, ... ],
193
+ # ...
194
+ # }
195
+ def manifest
196
+ unless @manifest
197
+ generate_manifest
198
+ end
199
+ @manifest
200
+ end
201
+
202
+ def manifest=(new_manifest)
203
+ @manifest = Mash.new new_manifest
204
+ @checksums = extract_checksums_from_manifest(@manifest)
205
+ @manifest_records_by_path = extract_manifest_records_by_path(@manifest)
206
+
207
+ COOKBOOK_SEGMENTS.each do |segment|
208
+ next unless @manifest.has_key?(segment)
209
+ filenames = @manifest[segment].map{|manifest_record| manifest_record['name']}
210
+
211
+ if segment == :recipes
212
+ self.recipe_filenames = filenames
213
+ elsif segment == :attributes
214
+ self.attribute_filenames = filenames
215
+ else
216
+ segment_filenames(segment).clear
217
+ filenames.each { |filename| segment_filenames(segment) << filename }
218
+ end
219
+ end
220
+ end
221
+
222
+ # Returns a hash of checksums to either nil or the on disk path (which is
223
+ # done by generate_manifest).
224
+ def checksums
225
+ unless @checksums
226
+ generate_manifest
227
+ end
228
+ @checksums
229
+ end
230
+
231
+ def full_name
232
+ "#{name}-#{version}"
233
+ end
234
+
235
+ def attribute_filenames=(*filenames)
236
+ @attribute_filenames = filenames.flatten
237
+ @attribute_filenames_by_short_filename = filenames_by_name(attribute_filenames)
238
+ attribute_filenames
239
+ end
240
+
241
+ ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
242
+ alias :attribute_files :attribute_filenames
243
+ alias :attribute_files= :attribute_filenames=
244
+
245
+ # Return recipe names in the form of cookbook_name::recipe_name
246
+ def fully_qualified_recipe_names
247
+ results = Array.new
248
+ recipe_filenames_by_name.each_key do |rname|
249
+ results << "#{name}::#{rname}"
250
+ end
251
+ results
252
+ end
253
+
254
+ def recipe_filenames=(*filenames)
255
+ @recipe_filenames = filenames.flatten
256
+ @recipe_filenames_by_name = filenames_by_name(recipe_filenames)
257
+ recipe_filenames
258
+ end
259
+
260
+ ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
261
+ alias :recipe_files :recipe_filenames
262
+ alias :recipe_files= :recipe_filenames=
263
+
264
+ # called from DSL
265
+ def load_recipe(recipe_name, run_context)
266
+ unless recipe_filenames_by_name.has_key?(recipe_name)
267
+ raise ArgumentError, "Cannot find a recipe matching #{recipe_name} in cookbook #{name}"
268
+ end
269
+ Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{name}")
270
+ recipe = Chef::Recipe.new(name, recipe_name, run_context)
271
+ recipe_filename = recipe_filenames_by_name[recipe_name]
272
+ raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}" unless recipe_filename
273
+
274
+ recipe.from_file(recipe_filename)
275
+ recipe
276
+ end
277
+
278
+ def segment_filenames(segment)
279
+ raise ArgumentError, "invalid segment #{segment}: must be one of #{COOKBOOK_SEGMENTS.join(', ')}" unless COOKBOOK_SEGMENTS.include?(segment)
280
+
281
+ case segment.to_sym
282
+ when :resources
283
+ @resource_filenames
284
+ when :providers
285
+ @provider_filenames
286
+ when :recipes
287
+ @recipe_filenames
288
+ when :libraries
289
+ @library_filenames
290
+ when :definitions
291
+ @definition_filenames
292
+ when :attributes
293
+ @attribute_filenames
294
+ when :files
295
+ @file_filenames
296
+ when :templates
297
+ @template_filenames
298
+ when :root_files
299
+ @root_filenames
300
+ end
301
+ end
302
+
303
+ # Determine the most specific manifest record for the given
304
+ # segment/filename, given information in the node. Throws
305
+ # FileNotFound if there is no such segment and filename in the
306
+ # manifest.
307
+ #
308
+ # A manifest record is a Mash that follows the following form:
309
+ # {
310
+ # :name => "example.rb",
311
+ # :path => "files/default/example.rb",
312
+ # :specificity => "default",
313
+ # :checksum => "1234"
314
+ # }
315
+ def preferred_manifest_record(node, segment, filename)
316
+ preferences = preferences_for_path(node, segment, filename)
317
+
318
+ # ensure that we generate the manifest, which will also generate
319
+ # @manifest_records_by_path
320
+ manifest
321
+
322
+ # in order of prefernce, look for the filename in the manifest
323
+ found_pref = preferences.find {|preferred_filename| @manifest_records_by_path[preferred_filename] }
324
+ if found_pref
325
+ @manifest_records_by_path[found_pref]
326
+ else
327
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} does not contain file #{segment}/#{filename}"
328
+ end
329
+ end
330
+
331
+ def preferred_filename_on_disk_location(node, segment, filename, current_filepath=nil)
332
+ manifest_record = preferred_manifest_record(node, segment, filename)
333
+ if current_filepath && (manifest_record['checksum'] == self.class.checksum_cookbook_file(current_filepath))
334
+ nil
335
+ else
336
+ file_vendor.get_filename(manifest_record['path'])
337
+ end
338
+ end
339
+
340
+
341
+ def relative_filenames_in_preferred_directory(node, segment, dirname)
342
+ preferences = preferences_for_path(node, segment, dirname)
343
+ filenames_by_pref = Hash.new
344
+ preferences.each { |pref| filenames_by_pref[pref] = Array.new }
345
+
346
+ manifest[segment].each do |manifest_record|
347
+ manifest_record_path = manifest_record[:path]
348
+
349
+ # find the NON SPECIFIC filenames, but prefer them by filespecificity.
350
+ # For example, if we have a file:
351
+ # 'files/default/somedir/somefile.conf' we only keep
352
+ # 'somedir/somefile.conf'. If there is also
353
+ # 'files/$hostspecific/somedir/otherfiles' that matches the requested
354
+ # hostname specificity, that directory will win, as it is more specific.
355
+ #
356
+ # This is clearly ugly b/c the use case is for remote directory, where
357
+ # we're just going to make cookbook_files out of these and make the
358
+ # cookbook find them by filespecificity again. but it's the shortest
359
+ # path to "success" for now.
360
+ if manifest_record_path =~ /(#{segment}\/[^\/]+\/#{dirname})\/.+$/
361
+ specificity_dirname = $1
362
+ non_specific_path = manifest_record_path[/#{segment}\/[^\/]+\/#{dirname}\/(.+)$/, 1]
363
+ # Record the specificity_dirname only if it's in the list of
364
+ # valid preferences
365
+ if filenames_by_pref[specificity_dirname]
366
+ filenames_by_pref[specificity_dirname] << non_specific_path
367
+ end
368
+ end
369
+ end
370
+
371
+ best_pref = preferences.find { |pref| !filenames_by_pref[pref].empty? }
372
+
373
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
374
+
375
+ filenames_by_pref[best_pref]
376
+
377
+ end
378
+
379
+ # Determine the manifest records from the most specific directory
380
+ # for the given node. See #preferred_manifest_record for a
381
+ # description of entries of the returned Array.
382
+ def preferred_manifest_records_for_directory(node, segment, dirname)
383
+ preferences = preferences_for_path(node, segment, dirname)
384
+ records_by_pref = Hash.new
385
+ preferences.each { |pref| records_by_pref[pref] = Array.new }
386
+
387
+ manifest[segment].each do |manifest_record|
388
+ manifest_record_path = manifest_record[:path]
389
+
390
+ # extract the preference part from the path.
391
+ if manifest_record_path =~ /(#{segment}\/[^\/]+\/#{dirname})\/.+$/
392
+ # Note the specificy_dirname includes the segment and
393
+ # dirname argument as above, which is what
394
+ # preferences_for_path returns. It could be
395
+ # "files/ubuntu-9.10/dirname", for example.
396
+ specificity_dirname = $1
397
+
398
+ # Record the specificity_dirname only if it's in the list of
399
+ # valid preferences
400
+ if records_by_pref[specificity_dirname]
401
+ records_by_pref[specificity_dirname] << manifest_record
402
+ end
403
+ end
404
+ end
405
+
406
+ best_pref = preferences.find { |pref| !records_by_pref[pref].empty? }
407
+
408
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
409
+
410
+ records_by_pref[best_pref]
411
+ end
412
+
413
+ # Given a node, segment and path (filename or directory name),
414
+ # return the priority-ordered list of preference locations to
415
+ # look.
416
+ def preferences_for_path(node, segment, path)
417
+ # only files and templates can be platform-specific
418
+ if segment.to_sym == :files || segment.to_sym == :templates
419
+ begin
420
+ platform, version = Chef::Platform.find_platform_and_version(node)
421
+ rescue ArgumentError => e
422
+ # Skip platform/version if they were not found by find_platform_and_version
423
+ if e.message =~ /Cannot find a (platform|version)/
424
+ platform = "/unknown_platform/"
425
+ version = "/unknown_platform_version/"
426
+ else
427
+ raise
428
+ end
429
+ end
430
+
431
+ fqdn = node[:fqdn]
432
+
433
+ # Most specific to least specific places to find the path
434
+ [
435
+ File.join(segment.to_s, "host-#{fqdn}", path),
436
+ File.join(segment.to_s, "#{platform}-#{version}", path),
437
+ File.join(segment.to_s, platform.to_s, path),
438
+ File.join(segment.to_s, "default", path)
439
+ ]
440
+ else
441
+ [File.join(segment, path)]
442
+ end
443
+ end
444
+ private :preferences_for_path
445
+
446
+
447
+ def relative_filenames_in_preferred_directory(node, segment, dirname)
448
+ preferences = preferences_for_path(node, segment, dirname)
449
+ filenames_by_pref = Hash.new
450
+ preferences.each { |pref| filenames_by_pref[pref] = Array.new }
451
+
452
+ manifest[segment].each do |manifest_record|
453
+ manifest_record_path = manifest_record[:path]
454
+
455
+ # find the NON SPECIFIC filenames, but prefer them by filespecificity.
456
+ # For example, if we have a file:
457
+ # 'files/default/somedir/somefile.conf' we only keep
458
+ # 'somedir/somefile.conf'. If there is also
459
+ # 'files/$hostspecific/somedir/otherfiles' that matches the requested
460
+ # hostname specificity, that directory will win, as it is more specific.
461
+ #
462
+ # This is clearly ugly b/c the use case is for remote directory, where
463
+ # we're just going to make cookbook_files out of these and make the
464
+ # cookbook find them by filespecificity again. but it's the shortest
465
+ # path to "success" for now.
466
+ if manifest_record_path =~ /(#{segment}\/[^\/]+\/#{dirname})\/.+$/
467
+ specificity_dirname = $1
468
+ non_specific_path = manifest_record_path[/#{segment}\/[^\/]+\/#{dirname}\/(.+)$/, 1]
469
+ # Record the specificity_dirname only if it's in the list of
470
+ # valid preferences
471
+ if filenames_by_pref[specificity_dirname]
472
+ filenames_by_pref[specificity_dirname] << non_specific_path
473
+ end
474
+ end
475
+ end
476
+
477
+ best_pref = preferences.find { |pref| !filenames_by_pref[pref].empty? }
478
+
479
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
480
+
481
+ filenames_by_pref[best_pref]
482
+
483
+ end
484
+
485
+ # Determine the manifest records from the most specific directory
486
+ # for the given node. See #preferred_manifest_record for a
487
+ # description of entries of the returned Array.
488
+ def preferred_manifest_records_for_directory(node, segment, dirname)
489
+ preferences = preferences_for_path(node, segment, dirname)
490
+ records_by_pref = Hash.new
491
+ preferences.each { |pref| records_by_pref[pref] = Array.new }
492
+
493
+ manifest[segment].each do |manifest_record|
494
+ manifest_record_path = manifest_record[:path]
495
+
496
+ # extract the preference part from the path.
497
+ if manifest_record_path =~ /(#{segment}\/[^\/]+\/#{dirname})\/.+$/
498
+ # Note the specificy_dirname includes the segment and
499
+ # dirname argument as above, which is what
500
+ # preferences_for_path returns. It could be
501
+ # "files/ubuntu-9.10/dirname", for example.
502
+ specificity_dirname = $1
503
+
504
+ # Record the specificity_dirname only if it's in the list of
505
+ # valid preferences
506
+ if records_by_pref[specificity_dirname]
507
+ records_by_pref[specificity_dirname] << manifest_record
508
+ end
509
+ end
510
+ end
511
+
512
+ best_pref = preferences.find { |pref| !records_by_pref[pref].empty? }
513
+
514
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
515
+
516
+ records_by_pref[best_pref]
517
+ end
518
+
519
+ # Given a node, segment and path (filename or directory name),
520
+ # return the priority-ordered list of preference locations to
521
+ # look.
522
+ def preferences_for_path(node, segment, path)
523
+ # only files and templates can be platform-specific
524
+ if segment.to_sym == :files || segment.to_sym == :templates
525
+ begin
526
+ platform, version = Chef::Platform.find_platform_and_version(node)
527
+ rescue ArgumentError => e
528
+ # Skip platform/version if they were not found by find_platform_and_version
529
+ if e.message =~ /Cannot find a (platform|version)/
530
+ platform = "/unknown_platform/"
531
+ version = "/unknown_platform_version/"
532
+ else
533
+ raise
534
+ end
535
+ end
536
+
537
+ fqdn = node[:fqdn]
538
+
539
+ # Most specific to least specific places to find the path
540
+ [
541
+ File.join(segment.to_s, "host-#{fqdn}", path),
542
+ File.join(segment.to_s, "#{platform}-#{version}", path),
543
+ File.join(segment.to_s, platform.to_s, path),
544
+ File.join(segment.to_s, "default", path)
545
+ ]
546
+ else
547
+ [File.join(segment, path)]
548
+ end
549
+ end
550
+ private :preferences_for_path
551
+
552
+ def to_hash
553
+ result = manifest.dup
554
+ result['chef_type'] = 'cookbook_version'
555
+ result["_rev"] = couchdb_rev if couchdb_rev
556
+ result.to_hash
557
+ end
558
+
559
+ def to_json(*a)
560
+ result = self.to_hash
561
+ result['json_class'] = self.class.name
562
+ result.to_json(*a)
563
+ end
564
+
565
+ def self.json_create(o)
566
+ cookbook_version = new(o["cookbook_name"])
567
+ if o.has_key?('_rev')
568
+ cookbook_version.couchdb_rev = o["_rev"] if o.has_key?("_rev")
569
+ o.delete("_rev")
570
+ end
571
+ if o.has_key?("_id")
572
+ cookbook_version.couchdb_id = o["_id"] if o.has_key?("_id")
573
+ cookbook_version.index_id = cookbook_version.couchdb_id
574
+ o.delete("_id")
575
+ end
576
+ cookbook_version.manifest = o
577
+ # We want the Chef::Cookbook::Metadata object to always be inflated
578
+ cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"])
579
+ cookbook_version
580
+ end
581
+
582
+ def generate_manifest_with_urls(&url_generator)
583
+ rendered_manifest = manifest.dup
584
+ COOKBOOK_SEGMENTS.each do |segment|
585
+ if rendered_manifest.has_key?(segment)
586
+ rendered_manifest[segment].each do |manifest_record|
587
+ url_options = { :cookbook_name => name.to_s, :cookbook_version => version, :checksum => manifest_record["checksum"] }
588
+ manifest_record["url"] = url_generator.call(url_options)
589
+ end
590
+ end
591
+ end
592
+ rendered_manifest
593
+ end
594
+
595
+ ##
596
+ # REST API
597
+ ##
598
+ def chef_server_rest
599
+ Chef::REST.new(Chef::Config[:chef_server_url])
600
+ end
601
+
602
+ def save
603
+ chef_server_rest.put_rest("cookbooks/#{name}/#{version}", self)
604
+ self
605
+ end
606
+ alias :create :save
607
+
608
+ def destroy
609
+ chef_server_rest.delete_rest("cookbooks/#{name}/#{version}")
610
+ self
611
+ end
612
+
613
+ def self.load(name, version="_latest")
614
+ version = "_latest" if version == "latest"
615
+ Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("cookbooks/#{name}/#{version}")
616
+ end
617
+
618
+ ##
619
+ # Couchdb
620
+ ##
621
+
622
+ def self.cdb_by_name(cookbook_name, couchdb=nil)
623
+ cdb = couchdb || Chef::CouchDB.new
624
+ options = { :startkey => cookbook_name, :endkey => cookbook_name }
625
+ rs = cdb.get_view("cookbooks", "all_with_version", options)
626
+ rs["rows"].inject({}) { |memo, row| memo.has_key?(row["key"]) ? memo[row["key"]] << row["value"] : memo[row["key"]] = [ row["value"] ]; memo }
627
+ end
628
+
629
+ def self.create_design_document(couchdb=nil)
630
+ (couchdb || Chef::CouchDB.new).create_design_document("cookbooks", DESIGN_DOCUMENT)
631
+ end
632
+
633
+ def self.cdb_list(inflate=false, couchdb=nil)
634
+ rs = (couchdb || Chef::CouchDB.new).list("cookbooks", inflate)
635
+ lookup = (inflate ? "value" : "key")
636
+ rs["rows"].collect { |r| r[lookup] }
637
+ end
638
+
639
+ def self.cdb_load(name, version='latest', couchdb=nil)
640
+ cdb = couchdb || Chef::CouchDB.new
641
+ if version == "latest" || version == "_latest"
642
+ rs = cdb.get_view("cookbook_versions", "all_latest_version", :key => name, :descending => true, :group => true, :reduce => true)["rows"].first
643
+ cdb.load("cookbook_version", "#{rs["key"]}-#{rs["value"]}")
644
+ else
645
+ cdb.load("cookbook_version", "#{name}-#{version}")
646
+ end
647
+ end
648
+
649
+ def cdb_destroy
650
+ (couchdb || Chef::CouchDB.new).delete("cookbook_version", full_name, couchdb_rev)
651
+ end
652
+
653
+ def cdb_save
654
+ @couchdb_rev = couchdb.store("cookbook_version", full_name, self)["rev"]
655
+ end
656
+
657
+ def couchdb_id=(value)
658
+ @couchdb_id = value
659
+ @index_id = value
660
+ end
661
+
662
+ private
663
+
664
+ # For each filename, produce a mapping of base filename (i.e. recipe name
665
+ # or attribute file) to on disk location
666
+ def filenames_by_name(filenames)
667
+ filenames.select{|filename| filename =~ /\.rb$/}.inject({}){|memo, filename| memo[File.basename(filename, '.rb')] = filename ; memo }
668
+ end
669
+
670
+ # See #manifest for a description of the manifest return value.
671
+ # See #preferred_manifest_record for a description an individual manifest record.
672
+ def generate_manifest
673
+ manifest = Mash.new({
674
+ :recipes => Array.new,
675
+ :definitions => Array.new,
676
+ :libraries => Array.new,
677
+ :attributes => Array.new,
678
+ :files => Array.new,
679
+ :templates => Array.new,
680
+ :resources => Array.new,
681
+ :providers => Array.new,
682
+ :root_files => Array.new
683
+ })
684
+ checksums_to_on_disk_paths = {}
685
+
686
+ COOKBOOK_SEGMENTS.each do |segment|
687
+ segment_filenames(segment).each do |segment_file|
688
+ next if File.directory?(segment_file)
689
+
690
+ file_name = nil
691
+ path = nil
692
+ specificity = "default"
693
+
694
+ if segment == :root_files
695
+ matcher = segment_file.match("/#{name}/(.+)")
696
+ file_name = matcher[1]
697
+ path = file_name
698
+ elsif segment == :templates || segment == :files
699
+ matcher = segment_file.match("/#{name}/(#{segment}/(.+?)/(.+))")
700
+ unless matcher
701
+ Chef::Log.debug("Skipping file #{segment_file}, as it doesn't have a proper segment.")
702
+ next
703
+ end
704
+ path = matcher[1]
705
+ specificity = matcher[2]
706
+ file_name = matcher[3]
707
+ else
708
+ matcher = segment_file.match("/#{name}/(#{segment}/(.+))")
709
+ path = matcher[1]
710
+ file_name = matcher[2]
711
+ end
712
+
713
+ csum = self.class.checksum_cookbook_file(segment_file)
714
+ checksums_to_on_disk_paths[csum] = segment_file
715
+ rs = Mash.new({
716
+ :name => file_name,
717
+ :path => path,
718
+ :checksum => csum
719
+ })
720
+ rs[:specificity] = specificity
721
+
722
+ manifest[segment] << rs
723
+ end
724
+ end
725
+
726
+ manifest[:cookbook_name] = name.to_s
727
+ manifest[:metadata] = metadata
728
+ manifest[:version] = metadata.version
729
+ manifest[:name] = full_name
730
+
731
+ @checksums = checksums_to_on_disk_paths
732
+ @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(manifest)
733
+ @manifest = manifest
734
+ @manifest_records_by_path = extract_manifest_records_by_path(manifest)
735
+ end
736
+
737
+ def extract_checksums_from_manifest(manifest)
738
+ checksums = {}
739
+ COOKBOOK_SEGMENTS.each do |segment|
740
+ next unless manifest.has_key?(segment)
741
+ manifest[segment].each do |manifest_record|
742
+ checksums[manifest_record[:checksum]] = nil
743
+ end
744
+ end
745
+ checksums
746
+ end
747
+
748
+ def extract_manifest_records_by_path(manifest)
749
+ manifest_records_by_path = {}
750
+ COOKBOOK_SEGMENTS.each do |segment|
751
+ next unless manifest.has_key?(segment)
752
+ manifest[segment].each do |manifest_record|
753
+ manifest_records_by_path[manifest_record[:path]] = manifest_record
754
+ end
755
+ end
756
+ manifest_records_by_path
757
+ end
758
+
759
+ end
760
+ end