microwave 0.1004.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 (206) hide show
  1. data/LICENSE +201 -0
  2. data/README.rdoc +102 -0
  3. data/bin/chef-solo +25 -0
  4. data/lib/chef.rb +41 -0
  5. data/lib/chef/application.rb +147 -0
  6. data/lib/chef/application/solo.rb +181 -0
  7. data/lib/chef/applications.rb +1 -0
  8. data/lib/chef/checksum.rb +83 -0
  9. data/lib/chef/checksum_cache.rb +189 -0
  10. data/lib/chef/client.rb +325 -0
  11. data/lib/chef/config.rb +152 -0
  12. data/lib/chef/cookbook/chefignore.rb +66 -0
  13. data/lib/chef/cookbook/cookbook_collection.rb +45 -0
  14. data/lib/chef/cookbook/cookbook_version_loader.rb +173 -0
  15. data/lib/chef/cookbook/file_system_file_vendor.rb +56 -0
  16. data/lib/chef/cookbook/file_vendor.rb +48 -0
  17. data/lib/chef/cookbook/metadata.rb +629 -0
  18. data/lib/chef/cookbook/syntax_check.rb +136 -0
  19. data/lib/chef/cookbook_loader.rb +121 -0
  20. data/lib/chef/cookbook_version.rb +786 -0
  21. data/lib/chef/cookbook_version_selector.rb +151 -0
  22. data/lib/chef/environment.rb +267 -0
  23. data/lib/chef/exceptions.rb +150 -0
  24. data/lib/chef/file_access_control.rb +144 -0
  25. data/lib/chef/file_cache.rb +218 -0
  26. data/lib/chef/handler.rb +206 -0
  27. data/lib/chef/handler/error_report.rb +33 -0
  28. data/lib/chef/handler/json_file.rb +58 -0
  29. data/lib/chef/json_compat.rb +52 -0
  30. data/lib/chef/log.rb +39 -0
  31. data/lib/chef/mash.rb +211 -0
  32. data/lib/chef/mixin/check_helper.rb +31 -0
  33. data/lib/chef/mixin/checksum.rb +32 -0
  34. data/lib/chef/mixin/command.rb +221 -0
  35. data/lib/chef/mixin/command/unix.rb +215 -0
  36. data/lib/chef/mixin/command/windows.rb +76 -0
  37. data/lib/chef/mixin/convert_to_class_name.rb +63 -0
  38. data/lib/chef/mixin/create_path.rb +57 -0
  39. data/lib/chef/mixin/deep_merge.rb +225 -0
  40. data/lib/chef/mixin/deprecation.rb +65 -0
  41. data/lib/chef/mixin/from_file.rb +50 -0
  42. data/lib/chef/mixin/get_source_from_package.rb +42 -0
  43. data/lib/chef/mixin/language.rb +125 -0
  44. data/lib/chef/mixin/language_include_attribute.rb +61 -0
  45. data/lib/chef/mixin/language_include_recipe.rb +52 -0
  46. data/lib/chef/mixin/params_validate.rb +225 -0
  47. data/lib/chef/mixin/recipe_definition_dsl_core.rb +78 -0
  48. data/lib/chef/mixin/shell_out.rb +41 -0
  49. data/lib/chef/mixin/template.rb +95 -0
  50. data/lib/chef/mixin/xml_escape.rb +140 -0
  51. data/lib/chef/mixins.rb +15 -0
  52. data/lib/chef/monkey_patches/dir.rb +36 -0
  53. data/lib/chef/monkey_patches/numeric.rb +15 -0
  54. data/lib/chef/monkey_patches/object.rb +9 -0
  55. data/lib/chef/monkey_patches/regexp.rb +34 -0
  56. data/lib/chef/monkey_patches/string.rb +49 -0
  57. data/lib/chef/monkey_patches/tempfile.rb +64 -0
  58. data/lib/chef/nil_argument.rb +3 -0
  59. data/lib/chef/node.rb +469 -0
  60. data/lib/chef/node/attribute.rb +487 -0
  61. data/lib/chef/platform.rb +409 -0
  62. data/lib/chef/provider.rb +124 -0
  63. data/lib/chef/provider/breakpoint.rb +31 -0
  64. data/lib/chef/provider/cookbook_file.rb +100 -0
  65. data/lib/chef/provider/cron.rb +186 -0
  66. data/lib/chef/provider/cron/solaris.rb +195 -0
  67. data/lib/chef/provider/deploy.rb +343 -0
  68. data/lib/chef/provider/deploy/revision.rb +80 -0
  69. data/lib/chef/provider/deploy/timestamped.rb +33 -0
  70. data/lib/chef/provider/directory.rb +72 -0
  71. data/lib/chef/provider/env.rb +152 -0
  72. data/lib/chef/provider/env/windows.rb +75 -0
  73. data/lib/chef/provider/erl_call.rb +101 -0
  74. data/lib/chef/provider/execute.rb +66 -0
  75. data/lib/chef/provider/file.rb +222 -0
  76. data/lib/chef/provider/git.rb +243 -0
  77. data/lib/chef/provider/group.rb +133 -0
  78. data/lib/chef/provider/group/aix.rb +70 -0
  79. data/lib/chef/provider/group/dscl.rb +121 -0
  80. data/lib/chef/provider/group/gpasswd.rb +53 -0
  81. data/lib/chef/provider/group/groupadd.rb +81 -0
  82. data/lib/chef/provider/group/pw.rb +84 -0
  83. data/lib/chef/provider/group/suse.rb +53 -0
  84. data/lib/chef/provider/group/usermod.rb +57 -0
  85. data/lib/chef/provider/group/windows.rb +79 -0
  86. data/lib/chef/provider/ifconfig.rb +134 -0
  87. data/lib/chef/provider/link.rb +164 -0
  88. data/lib/chef/provider/log.rb +54 -0
  89. data/lib/chef/provider/mdadm.rb +91 -0
  90. data/lib/chef/provider/mount.rb +114 -0
  91. data/lib/chef/provider/mount/mount.rb +240 -0
  92. data/lib/chef/provider/mount/windows.rb +81 -0
  93. data/lib/chef/provider/ohai.rb +42 -0
  94. data/lib/chef/provider/package.rb +163 -0
  95. data/lib/chef/provider/package/apt.rb +135 -0
  96. data/lib/chef/provider/package/dpkg.rb +115 -0
  97. data/lib/chef/provider/package/easy_install.rb +136 -0
  98. data/lib/chef/provider/package/freebsd.rb +125 -0
  99. data/lib/chef/provider/package/macports.rb +105 -0
  100. data/lib/chef/provider/package/pacman.rb +101 -0
  101. data/lib/chef/provider/package/portage.rb +135 -0
  102. data/lib/chef/provider/package/rpm.rb +104 -0
  103. data/lib/chef/provider/package/rubygems.rb +465 -0
  104. data/lib/chef/provider/package/solaris.rb +130 -0
  105. data/lib/chef/provider/package/yum-dump.py +286 -0
  106. data/lib/chef/provider/package/yum.rb +1128 -0
  107. data/lib/chef/provider/package/zypper.rb +144 -0
  108. data/lib/chef/provider/remote_directory.rb +137 -0
  109. data/lib/chef/provider/route.rb +193 -0
  110. data/lib/chef/provider/ruby_block.rb +34 -0
  111. data/lib/chef/provider/script.rb +55 -0
  112. data/lib/chef/provider/service.rb +122 -0
  113. data/lib/chef/provider/service/arch.rb +116 -0
  114. data/lib/chef/provider/service/debian.rb +130 -0
  115. data/lib/chef/provider/service/freebsd.rb +154 -0
  116. data/lib/chef/provider/service/gentoo.rb +53 -0
  117. data/lib/chef/provider/service/init.rb +71 -0
  118. data/lib/chef/provider/service/insserv.rb +52 -0
  119. data/lib/chef/provider/service/redhat.rb +60 -0
  120. data/lib/chef/provider/service/simple.rb +120 -0
  121. data/lib/chef/provider/service/solaris.rb +85 -0
  122. data/lib/chef/provider/service/systemd.rb +102 -0
  123. data/lib/chef/provider/service/upstart.rb +198 -0
  124. data/lib/chef/provider/service/windows.rb +146 -0
  125. data/lib/chef/provider/subversion.rb +197 -0
  126. data/lib/chef/provider/template.rb +104 -0
  127. data/lib/chef/provider/user.rb +186 -0
  128. data/lib/chef/provider/user/dscl.rb +280 -0
  129. data/lib/chef/provider/user/pw.rb +113 -0
  130. data/lib/chef/provider/user/useradd.rb +137 -0
  131. data/lib/chef/provider/user/windows.rb +124 -0
  132. data/lib/chef/providers.rb +93 -0
  133. data/lib/chef/recipe.rb +129 -0
  134. data/lib/chef/resource.rb +584 -0
  135. data/lib/chef/resource/apt_package.rb +34 -0
  136. data/lib/chef/resource/bash.rb +33 -0
  137. data/lib/chef/resource/breakpoint.rb +35 -0
  138. data/lib/chef/resource/cookbook_file.rb +45 -0
  139. data/lib/chef/resource/cron.rb +188 -0
  140. data/lib/chef/resource/csh.rb +33 -0
  141. data/lib/chef/resource/deploy.rb +380 -0
  142. data/lib/chef/resource/deploy_revision.rb +40 -0
  143. data/lib/chef/resource/directory.rb +89 -0
  144. data/lib/chef/resource/dpkg_package.rb +34 -0
  145. data/lib/chef/resource/easy_install_package.rb +57 -0
  146. data/lib/chef/resource/env.rb +58 -0
  147. data/lib/chef/resource/erl_call.rb +83 -0
  148. data/lib/chef/resource/execute.rb +127 -0
  149. data/lib/chef/resource/file.rb +112 -0
  150. data/lib/chef/resource/freebsd_package.rb +35 -0
  151. data/lib/chef/resource/gem_package.rb +53 -0
  152. data/lib/chef/resource/git.rb +46 -0
  153. data/lib/chef/resource/group.rb +70 -0
  154. data/lib/chef/resource/ifconfig.rb +134 -0
  155. data/lib/chef/resource/link.rb +105 -0
  156. data/lib/chef/resource/log.rb +62 -0
  157. data/lib/chef/resource/macports_package.rb +29 -0
  158. data/lib/chef/resource/mdadm.rb +82 -0
  159. data/lib/chef/resource/mount.rb +134 -0
  160. data/lib/chef/resource/ohai.rb +40 -0
  161. data/lib/chef/resource/package.rb +80 -0
  162. data/lib/chef/resource/pacman_package.rb +33 -0
  163. data/lib/chef/resource/perl.rb +33 -0
  164. data/lib/chef/resource/portage_package.rb +33 -0
  165. data/lib/chef/resource/python.rb +33 -0
  166. data/lib/chef/resource/remote_directory.rb +109 -0
  167. data/lib/chef/resource/route.rb +135 -0
  168. data/lib/chef/resource/rpm_package.rb +34 -0
  169. data/lib/chef/resource/ruby.rb +33 -0
  170. data/lib/chef/resource/ruby_block.rb +40 -0
  171. data/lib/chef/resource/scm.rb +147 -0
  172. data/lib/chef/resource/script.rb +60 -0
  173. data/lib/chef/resource/service.rb +160 -0
  174. data/lib/chef/resource/solaris_package.rb +36 -0
  175. data/lib/chef/resource/subversion.rb +36 -0
  176. data/lib/chef/resource/template.rb +69 -0
  177. data/lib/chef/resource/timestamped_deploy.rb +31 -0
  178. data/lib/chef/resource/user.rb +130 -0
  179. data/lib/chef/resource/yum_package.rb +63 -0
  180. data/lib/chef/resource_collection.rb +217 -0
  181. data/lib/chef/resource_collection/stepable_iterator.rb +124 -0
  182. data/lib/chef/resource_definition.rb +67 -0
  183. data/lib/chef/resource_definition_list.rb +38 -0
  184. data/lib/chef/resources.rb +62 -0
  185. data/lib/chef/role.rb +225 -0
  186. data/lib/chef/run_context.rb +126 -0
  187. data/lib/chef/run_list.rb +161 -0
  188. data/lib/chef/run_list/run_list_expansion.rb +159 -0
  189. data/lib/chef/run_list/run_list_item.rb +92 -0
  190. data/lib/chef/run_list/versioned_recipe_list.rb +68 -0
  191. data/lib/chef/run_status.rb +121 -0
  192. data/lib/chef/runner.rb +105 -0
  193. data/lib/chef/shell_out.rb +250 -0
  194. data/lib/chef/shell_out/unix.rb +223 -0
  195. data/lib/chef/shell_out/windows.rb +98 -0
  196. data/lib/chef/tasks/chef_repo.rake +330 -0
  197. data/lib/chef/util/file_edit.rb +122 -0
  198. data/lib/chef/util/windows.rb +56 -0
  199. data/lib/chef/util/windows/net_group.rb +101 -0
  200. data/lib/chef/util/windows/net_use.rb +121 -0
  201. data/lib/chef/util/windows/net_user.rb +198 -0
  202. data/lib/chef/util/windows/volume.rb +59 -0
  203. data/lib/chef/version.rb +23 -0
  204. data/lib/chef/version_class.rb +70 -0
  205. data/lib/chef/version_constraint.rb +116 -0
  206. metadata +493 -0
@@ -0,0 +1,136 @@
1
+ #
2
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
3
+ # Copyright:: Copyright (c) 2010 Opscode, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ require 'chef/checksum_cache'
20
+ require 'chef/mixin/shell_out'
21
+
22
+ class Chef
23
+ class Cookbook
24
+ # == Chef::Cookbook::SyntaxCheck
25
+ # Encapsulates the process of validating the ruby syntax of files in Chef
26
+ # cookbooks.
27
+ class SyntaxCheck
28
+ include Chef::Mixin::ShellOut
29
+
30
+ attr_reader :cookbook_path
31
+
32
+ # Creates a new SyntaxCheck given the +cookbook_name+ and a +cookbook_path+.
33
+ # If no +cookbook_path+ is given, +Chef::Config.cookbook_path+ is used.
34
+ def self.for_cookbook(cookbook_name, cookbook_path=nil)
35
+ cookbook_path ||= Chef::Config.cookbook_path
36
+ unless cookbook_path
37
+ raise ArgumentError, "Cannot find cookbook #{cookbook_name} unless Chef::Config.cookbook_path is set or an explicit cookbook path is given"
38
+ end
39
+ new(File.join(cookbook_path, cookbook_name.to_s))
40
+ end
41
+
42
+ # Create a new SyntaxCheck object
43
+ # === Arguments
44
+ # cookbook_path::: the (on disk) path to the cookbook
45
+ def initialize(cookbook_path)
46
+ @cookbook_path = cookbook_path
47
+ end
48
+
49
+ def cache
50
+ Chef::ChecksumCache.instance
51
+ end
52
+
53
+ def ruby_files
54
+ Dir[File.join(cookbook_path, '**', '*.rb')]
55
+ end
56
+
57
+ def untested_ruby_files
58
+ ruby_files.reject do |file|
59
+ if validated?(file)
60
+ Chef::Log.debug("Ruby file #{file} is unchanged, skipping syntax check")
61
+ true
62
+ else
63
+ false
64
+ end
65
+ end
66
+ end
67
+
68
+ def template_files
69
+ Dir[File.join(cookbook_path, '**', '*.erb')]
70
+ end
71
+
72
+ def untested_template_files
73
+ template_files.reject do |file|
74
+ if validated?(file)
75
+ Chef::Log.debug("Template #{file} is unchanged, skipping syntax check")
76
+ true
77
+ else
78
+ false
79
+ end
80
+ end
81
+ end
82
+
83
+ def validated?(file)
84
+ !!cache.lookup_checksum(cache_key(file), File.stat(file))
85
+ end
86
+
87
+ def validated(file)
88
+ cache.generate_checksum(cache_key(file), file, File.stat(file))
89
+ end
90
+
91
+ def cache_key(file)
92
+ @cache_keys ||= {}
93
+ @cache_keys[file] ||= cache.generate_key(file, "chef-test")
94
+ end
95
+
96
+ def validate_ruby_files
97
+ untested_ruby_files.each do |ruby_file|
98
+ return false unless validate_ruby_file(ruby_file)
99
+ validated(ruby_file)
100
+ end
101
+ end
102
+
103
+ def validate_templates
104
+ untested_template_files.each do |template|
105
+ return false unless validate_template(template)
106
+ validated(template)
107
+ end
108
+ end
109
+
110
+ def validate_template(erb_file)
111
+ Chef::Log.debug("Testing template #{erb_file} for syntax errors...")
112
+ result = shell_out("sh -c 'erubis -x #{erb_file} | ruby -c'")
113
+ result.error!
114
+ true
115
+ rescue Chef::Exceptions::ShellCommandFailed
116
+ file_relative_path = erb_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
117
+ Chef::Log.fatal("Erb template #{file_relative_path} has a syntax error:")
118
+ result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
119
+ false
120
+ end
121
+
122
+ def validate_ruby_file(ruby_file)
123
+ Chef::Log.debug("Testing #{ruby_file} for syntax errors...")
124
+ result = shell_out("ruby -c #{ruby_file}")
125
+ result.error!
126
+ true
127
+ rescue Chef::Exceptions::ShellCommandFailed
128
+ file_relative_path = ruby_file[/^#{Regexp.escape(cookbook_path+File::Separator)}(.*)/, 1]
129
+ Chef::Log.fatal("Cookbook file #{file_relative_path} has a ruby syntax error:")
130
+ result.stderr.each_line { |l| Chef::Log.fatal(l.chomp) }
131
+ false
132
+ end
133
+
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,121 @@
1
+ #
2
+ # Author:: Adam Jacob (<adam@opscode.com>)
3
+ # Author:: Christopher Walters (<cw@opscode.com>)
4
+ # Author:: Daniel DeLeo (<dan@kallistec.com>)
5
+ # Copyright:: Copyright (c) 2008 Opscode, Inc.
6
+ # Copyright:: Copyright (c) 2009 Daniel DeLeo
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/config'
22
+ require 'chef/exceptions'
23
+ require 'chef/cookbook/cookbook_version_loader'
24
+ require 'chef/cookbook_version'
25
+ require 'chef/cookbook/chefignore'
26
+ require 'chef/cookbook/metadata'
27
+
28
+ class Chef
29
+ class CookbookLoader
30
+
31
+ attr_accessor :metadata
32
+ attr_reader :cookbooks_by_name
33
+ attr_reader :merged_cookbooks
34
+ attr_reader :cookbook_paths
35
+
36
+ include Enumerable
37
+
38
+ def initialize(*repo_paths)
39
+ @repo_paths = repo_paths.flatten
40
+ raise ArgumentError, "You must specify at least one cookbook repo path" if @repo_paths.empty?
41
+ @cookbooks_by_name = Mash.new
42
+ @loaded_cookbooks = {}
43
+ @metadata = Mash.new
44
+ @cookbooks_paths = Hash.new {|h,k| h[k] = []} # for deprecation warnings
45
+
46
+ # Used to track which cookbooks appear in multiple places in the cookbook repos
47
+ # and are merged in to a single cookbook by file shadowing. This behavior is
48
+ # deprecated, so users of this class may issue warnings to the user by checking
49
+ # this variable
50
+ @merged_cookbooks = []
51
+
52
+ load_cookbooks
53
+ end
54
+
55
+ def merged_cookbook_paths # for deprecation warnings
56
+ merged_cookbook_paths = {}
57
+ @merged_cookbooks.each {|c| merged_cookbook_paths[c] = @cookbooks_paths[c]}
58
+ merged_cookbook_paths
59
+ end
60
+
61
+ def load_cookbooks
62
+ cookbook_settings = Hash.new
63
+ @repo_paths.each do |repo_path|
64
+ repo_path = File.expand_path(repo_path)
65
+ chefignore = Cookbook::Chefignore.new(repo_path)
66
+ Dir[File.join(repo_path, "*")].each do |cookbook_path|
67
+ next unless File.directory?(cookbook_path)
68
+ loader = Cookbook::CookbookVersionLoader.new(cookbook_path, chefignore)
69
+ loader.load_cookbooks
70
+ next if loader.empty?
71
+ @cookbooks_paths[loader.cookbook_name] << cookbook_path # for deprecation warnings
72
+ if @loaded_cookbooks.key?(loader.cookbook_name)
73
+ @merged_cookbooks << loader.cookbook_name # for deprecation warnings
74
+ @loaded_cookbooks[loader.cookbook_name].merge!(loader)
75
+ else
76
+ @loaded_cookbooks[loader.cookbook_name] = loader
77
+ end
78
+ end
79
+ end
80
+
81
+ @loaded_cookbooks.each do |cookbook, loader|
82
+ cookbook_version = loader.cookbook_version
83
+ @cookbooks_by_name[cookbook] = cookbook_version
84
+ @metadata[cookbook] = cookbook_version.metadata
85
+ end
86
+ @cookbooks_by_name
87
+ end
88
+
89
+ def [](cookbook)
90
+ if @cookbooks_by_name.has_key?(cookbook.to_sym)
91
+ @cookbooks_by_name[cookbook.to_sym]
92
+ else
93
+ raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (http://wiki.opscode.com/display/chef/Metadata)"
94
+ end
95
+ end
96
+
97
+ alias :fetch :[]
98
+
99
+ def has_key?(cookbook_name)
100
+ @cookbooks_by_name.has_key?(cookbook_name)
101
+ end
102
+ alias :cookbook_exists? :has_key?
103
+ alias :key? :has_key?
104
+
105
+ def each
106
+ @cookbooks_by_name.keys.sort { |a,b| a.to_s <=> b.to_s }.each do |cname|
107
+ yield(cname, @cookbooks_by_name[cname])
108
+ end
109
+ end
110
+
111
+ def cookbook_names
112
+ @cookbooks_by_name.keys.sort
113
+ end
114
+
115
+ def values
116
+ @cookbooks_by_name.values
117
+ end
118
+ alias :cookbooks :values
119
+
120
+ end
121
+ end
@@ -0,0 +1,786 @@
1
+ # Author:: Adam Jacob (<adam@opscode.com>)
2
+ # Author:: Nuo Yan (<nuo@opscode.com>)
3
+ # Author:: Christopher Walters (<cw@opscode.com>)
4
+ # Author:: Tim Hinderliter (<tim@opscode.com>)
5
+ # Author:: Seth Falcon (<seth@opscode.com>)
6
+ # Author:: Daniel DeLeo (<dan@opscode.com>)
7
+ # Copyright:: Copyright 2008-2011 Opscode, Inc.
8
+ # License:: Apache License, Version 2.0
9
+ #
10
+ # Licensed under the Apache License, Version 2.0 (the "License");
11
+ # you may not use this file except in compliance with the License.
12
+ # You may obtain a copy of the License at
13
+ #
14
+ # http://www.apache.org/licenses/LICENSE-2.0
15
+ #
16
+ # Unless required by applicable law or agreed to in writing, software
17
+ # distributed under the License is distributed on an "AS IS" BASIS,
18
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ # See the License for the specific language governing permissions and
20
+ # limitations under the License.
21
+
22
+ require 'chef/log'
23
+ require 'chef/client'
24
+ require 'chef/node'
25
+ require 'chef/resource_definition_list'
26
+ require 'chef/recipe'
27
+ require 'chef/cookbook/file_vendor'
28
+ require 'chef/checksum'
29
+ require 'chef/cookbook/metadata'
30
+ require 'chef/version_class'
31
+
32
+ class Chef
33
+
34
+ #== Chef::MinimalCookbookVersion
35
+ # MinimalCookbookVersion is a duck type of CookbookVersion, used
36
+ # internally by Chef Server as an optimization when determining the
37
+ # optimal cookbook set for a chef-client.
38
+ #
39
+ # MinimalCookbookVersion objects contain only enough information to
40
+ # solve the cookbook collection for a given run list. They *do not*
41
+ # contain enough information to generate the response.
42
+ #
43
+ # See also: Chef::CookbookVersionSelector
44
+ class MinimalCookbookVersion
45
+
46
+ include Comparable
47
+
48
+ ID = "id".freeze
49
+ NAME = 'name'.freeze
50
+ KEY = 'key'.freeze
51
+ VERSION = 'version'.freeze
52
+ VALUE = 'value'.freeze
53
+ DEPS = 'deps'.freeze
54
+
55
+ DEPENDENCIES = 'dependencies'.freeze
56
+
57
+ attr_reader :name
58
+ attr_reader :version
59
+ attr_reader :deps
60
+
61
+ def initialize(params)
62
+ @name = params[KEY]
63
+ @version = params[VALUE][VERSION]
64
+ @deps = params[VALUE][DEPS]
65
+ end
66
+
67
+ # Returns the Cookbook::MinimalMetadata object for this cookbook
68
+ # version.
69
+ def metadata
70
+ @metadata ||= Cookbook::MinimalMetadata.new(@name, DEPENDENCIES => @deps)
71
+ end
72
+
73
+ def legit_version
74
+ @legit_version ||= Chef::Version.new(@version)
75
+ end
76
+
77
+ def <=>(o)
78
+ raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != o.name
79
+ raise "Unexpected comparison to #{o}" unless o.respond_to?(:legit_version)
80
+ legit_version <=> o.legit_version
81
+ end
82
+ end
83
+
84
+ # == Chef::CookbookVersion
85
+ # CookbookVersion is a model object encapsulating the data about a Chef
86
+ # cookbook. Chef supports maintaining multiple versions of a cookbook on a
87
+ # single server; each version is represented by a distinct instance of this
88
+ # class.
89
+ #--
90
+ # TODO: timh/cw: 5-24-2010: mutators for files (e.g., recipe_filenames=,
91
+ # recipe_filenames.insert) should dirty the manifest so it gets regenerated.
92
+ class CookbookVersion
93
+ include Comparable
94
+
95
+ COOKBOOK_SEGMENTS = [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates, :root_files ]
96
+
97
+ attr_accessor :root_dir
98
+ attr_accessor :definition_filenames
99
+ attr_accessor :template_filenames
100
+ attr_accessor :file_filenames
101
+ attr_accessor :library_filenames
102
+ attr_accessor :resource_filenames
103
+ attr_accessor :provider_filenames
104
+ attr_accessor :root_filenames
105
+ attr_accessor :name
106
+ attr_accessor :metadata
107
+ attr_accessor :metadata_filenames
108
+ attr_accessor :status
109
+
110
+ # attribute_filenames also has a setter that has non-default
111
+ # functionality.
112
+ attr_reader :attribute_filenames
113
+
114
+ # recipe_filenames also has a setter that has non-default
115
+ # functionality.
116
+ attr_reader :recipe_filenames
117
+
118
+ attr_reader :recipe_filenames_by_name
119
+ attr_reader :attribute_filenames_by_short_filename
120
+
121
+ # This is the one and only method that knows how cookbook files'
122
+ # checksums are generated.
123
+ def self.checksum_cookbook_file(filepath)
124
+ Chef::ChecksumCache.generate_md5_checksum_for_file(filepath)
125
+ rescue Errno::ENOENT
126
+ Chef::Log.debug("File #{filepath} does not exist, so there is no checksum to generate")
127
+ nil
128
+ end
129
+
130
+ # Keep track of the filenames that we use in both eager cookbook
131
+ # downloading (during sync_cookbooks) and lazy (during the run
132
+ # itself, through FileVendor). After the run is over, clean up the
133
+ # cache.
134
+ def self.valid_cache_entries
135
+ @valid_cache_entries ||= {}
136
+ end
137
+
138
+ def self.reset_cache_validity
139
+ @valid_cache_entries = nil
140
+ end
141
+
142
+ def self.cache
143
+ Chef::FileCache
144
+ end
145
+
146
+ # Setup a notification to clear the valid_cache_entries when a Chef client
147
+ # run starts
148
+ Chef::Client.when_run_starts do |run_status|
149
+ reset_cache_validity
150
+ end
151
+
152
+ # Synchronizes all the cookbooks from the chef-server.
153
+ #
154
+ # === Returns
155
+ # true:: Always returns true
156
+ def self.sync_cookbooks(cookbook_hash)
157
+ Chef::Log.info("Loading cookbooks [#{cookbook_hash.keys.sort.join(', ')}]")
158
+ Chef::Log.debug("Cookbooks detail: #{cookbook_hash.inspect}")
159
+
160
+ clear_obsoleted_cookbooks(cookbook_hash)
161
+
162
+ # Synchronize each of the node's cookbooks, and add to the
163
+ # valid_cache_entries hash.
164
+ cookbook_hash.values.each do |cookbook|
165
+ sync_cookbook_file_cache(cookbook)
166
+ end
167
+
168
+ true
169
+ end
170
+
171
+ # Iterates over cached cookbooks' files, removing files belonging to
172
+ # cookbooks that don't appear in +cookbook_hash+
173
+ def self.clear_obsoleted_cookbooks(cookbook_hash)
174
+ # Remove all cookbooks no longer relevant to this node
175
+ cache.find(File.join(%w{cookbooks ** *})).each do |cache_file|
176
+ cache_file =~ /^cookbooks\/([^\/]+)\//
177
+ unless cookbook_hash.has_key?($1)
178
+ Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.")
179
+ cache.delete(cache_file)
180
+ end
181
+ end
182
+ end
183
+
184
+ # Update the file caches for a given cache segment. Takes a segment name
185
+ # and a hash that matches one of the cookbooks/_attribute_files style
186
+ # remote file listings.
187
+ #
188
+ # === Parameters
189
+ # cookbook<Chef::Cookbook>:: The cookbook to update
190
+ # valid_cache_entries<Hash>:: Out-param; Added to this hash are the files that
191
+ # were referred to by this cookbook
192
+ def self.sync_cookbook_file_cache(cookbook)
193
+ Chef::Log.debug("Synchronizing cookbook #{cookbook.name}")
194
+
195
+ # files and templates are lazily loaded, and will be done later.
196
+ eager_segments = COOKBOOK_SEGMENTS.dup
197
+ eager_segments.delete(:files)
198
+ eager_segments.delete(:templates)
199
+
200
+ eager_segments.each do |segment|
201
+ segment_filenames = Array.new
202
+ cookbook.manifest[segment].each do |manifest_record|
203
+ # segment = cookbook segment
204
+ # remote_list = list of file hashes
205
+ #
206
+ # We need the list of known good attribute files, so we can delete any that are
207
+ # just laying about.
208
+
209
+ cache_filename = File.join("cookbooks", cookbook.name, manifest_record['path'])
210
+ valid_cache_entries[cache_filename] = true
211
+
212
+ current_checksum = nil
213
+ if cache.has_key?(cache_filename)
214
+ current_checksum = checksum_cookbook_file(cache.load(cache_filename, false))
215
+ end
216
+
217
+ # If the checksums are different between on-disk (current) and on-server
218
+ # (remote, per manifest), do the update. This will also execute if there
219
+ # is no current checksum.
220
+ if current_checksum != manifest_record['checksum']
221
+ raw_file = chef_server_rest.get_rest(manifest_record[:url], true)
222
+
223
+ Chef::Log.info("Storing updated #{cache_filename} in the cache.")
224
+ cache.move_to(raw_file.path, cache_filename)
225
+ else
226
+ Chef::Log.debug("Not storing #{cache_filename}, as the cache is up to date.")
227
+ end
228
+
229
+ # make the segment filenames a full path.
230
+ full_path_cache_filename = cache.load(cache_filename, false)
231
+ segment_filenames << full_path_cache_filename
232
+ end
233
+
234
+ # replace segment filenames with a full-path one.
235
+ if segment.to_sym == :recipes
236
+ cookbook.recipe_filenames = segment_filenames
237
+ elsif segment.to_sym == :attributes
238
+ cookbook.attribute_filenames = segment_filenames
239
+ else
240
+ cookbook.segment_filenames(segment).replace(segment_filenames)
241
+ end
242
+ end
243
+ end
244
+
245
+ def self.cleanup_file_cache
246
+ end
247
+
248
+ # Register a notification to cleanup unused files from cookbooks
249
+ Chef::Client.when_run_completes_successfully do |run_status|
250
+ cleanup_file_cache
251
+ end
252
+
253
+ # Creates a new Chef::CookbookVersion object.
254
+ #
255
+ # === Returns
256
+ # object<Chef::CookbookVersion>:: Duh. :)
257
+ def initialize(name)
258
+ @name = name
259
+ @frozen = false
260
+ @attribute_filenames = Array.new
261
+ @definition_filenames = Array.new
262
+ @template_filenames = Array.new
263
+ @file_filenames = Array.new
264
+ @recipe_filenames = Array.new
265
+ @recipe_filenames_by_name = Hash.new
266
+ @library_filenames = Array.new
267
+ @resource_filenames = Array.new
268
+ @provider_filenames = Array.new
269
+ @metadata_filenames = Array.new
270
+ @root_dir = nil
271
+ @root_filenames = Array.new
272
+ @status = :ready
273
+ @manifest = nil
274
+ @file_vendor = nil
275
+ @metadata = Chef::Cookbook::Metadata.new
276
+ end
277
+
278
+ def version
279
+ metadata.version
280
+ end
281
+
282
+ # Indicates if this version is frozen or not. Freezing a coobkook version
283
+ # indicates that a new cookbook with the same name and version number
284
+ # shoule
285
+ def frozen_version?
286
+ @frozen
287
+ end
288
+
289
+ def freeze_version
290
+ @frozen = true
291
+ end
292
+
293
+ def version=(new_version)
294
+ manifest["version"] = new_version
295
+ metadata.version(new_version)
296
+ end
297
+
298
+ # A manifest is a Mash that maps segment names to arrays of manifest
299
+ # records (see #preferred_manifest_record for format of manifest records),
300
+ # as well as describing cookbook metadata. The manifest follows a form
301
+ # like the following:
302
+ #
303
+ # {
304
+ # :cookbook_name = "apache2",
305
+ # :version = "1.0",
306
+ # :name = "Apache 2"
307
+ # :metadata = ???TODO: timh/cw: 5-24-2010: describe this format,
308
+ #
309
+ # :files => [
310
+ # {
311
+ # :name => "afile.rb",
312
+ # :path => "files/ubuntu-9.10/afile.rb",
313
+ # :checksum => "2222",
314
+ # :specificity => "ubuntu-9.10"
315
+ # },
316
+ # ],
317
+ # :templates => [ manifest_record1, ... ],
318
+ # ...
319
+ # }
320
+ def manifest
321
+ unless @manifest
322
+ generate_manifest
323
+ end
324
+ @manifest
325
+ end
326
+
327
+ def manifest=(new_manifest)
328
+ @manifest = Mash.new new_manifest
329
+ @checksums = extract_checksums_from_manifest(@manifest)
330
+ @manifest_records_by_path = extract_manifest_records_by_path(@manifest)
331
+
332
+ COOKBOOK_SEGMENTS.each do |segment|
333
+ next unless @manifest.has_key?(segment)
334
+ filenames = @manifest[segment].map{|manifest_record| manifest_record['name']}
335
+
336
+ if segment == :recipes
337
+ self.recipe_filenames = filenames
338
+ elsif segment == :attributes
339
+ self.attribute_filenames = filenames
340
+ else
341
+ segment_filenames(segment).clear
342
+ filenames.each { |filename| segment_filenames(segment) << filename }
343
+ end
344
+ end
345
+ end
346
+
347
+ # Returns a hash of checksums to either nil or the on disk path (which is
348
+ # done by generate_manifest).
349
+ def checksums
350
+ unless @checksums
351
+ generate_manifest
352
+ end
353
+ @checksums
354
+ end
355
+
356
+ def manifest_records_by_path
357
+ @manifest_records_by_path || generate_manifest
358
+ @manifest_records_by_path
359
+ end
360
+
361
+ def full_name
362
+ "#{name}-#{version}"
363
+ end
364
+
365
+ def attribute_filenames=(*filenames)
366
+ @attribute_filenames = filenames.flatten
367
+ @attribute_filenames_by_short_filename = filenames_by_name(attribute_filenames)
368
+ attribute_filenames
369
+ end
370
+
371
+ ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
372
+ alias :attribute_files :attribute_filenames
373
+ alias :attribute_files= :attribute_filenames=
374
+
375
+ # Return recipe names in the form of cookbook_name::recipe_name
376
+ def fully_qualified_recipe_names
377
+ results = Array.new
378
+ recipe_filenames_by_name.each_key do |rname|
379
+ results << "#{name}::#{rname}"
380
+ end
381
+ results
382
+ end
383
+
384
+ def recipe_filenames=(*filenames)
385
+ @recipe_filenames = filenames.flatten
386
+ @recipe_filenames_by_name = filenames_by_name(recipe_filenames)
387
+ recipe_filenames
388
+ end
389
+
390
+ ## BACKCOMPAT/DEPRECATED - Remove these and fix breakage before release [DAN - 5/20/2010]##
391
+ alias :recipe_files :recipe_filenames
392
+ alias :recipe_files= :recipe_filenames=
393
+
394
+ # called from DSL
395
+ def load_recipe(recipe_name, run_context)
396
+ unless recipe_filenames_by_name.has_key?(recipe_name)
397
+ raise ArgumentError, "Cannot find a recipe matching #{recipe_name} in cookbook #{name}"
398
+ end
399
+
400
+ Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{name}")
401
+ recipe = Chef::Recipe.new(name, recipe_name, run_context)
402
+ recipe_filename = recipe_filenames_by_name[recipe_name]
403
+
404
+ unless recipe_filename
405
+ raise Chef::Exceptions::RecipeNotFound, "could not find recipe #{recipe_name} for cookbook #{name}"
406
+ end
407
+
408
+ recipe.from_file(recipe_filename)
409
+ recipe
410
+ end
411
+
412
+ def segment_filenames(segment)
413
+ unless COOKBOOK_SEGMENTS.include?(segment)
414
+ raise ArgumentError, "invalid segment #{segment}: must be one of #{COOKBOOK_SEGMENTS.join(', ')}"
415
+ end
416
+
417
+ case segment.to_sym
418
+ when :resources
419
+ @resource_filenames
420
+ when :providers
421
+ @provider_filenames
422
+ when :recipes
423
+ @recipe_filenames
424
+ when :libraries
425
+ @library_filenames
426
+ when :definitions
427
+ @definition_filenames
428
+ when :attributes
429
+ @attribute_filenames
430
+ when :files
431
+ @file_filenames
432
+ when :templates
433
+ @template_filenames
434
+ when :root_files
435
+ @root_filenames
436
+ end
437
+ end
438
+
439
+ # Determine the most specific manifest record for the given
440
+ # segment/filename, given information in the node. Throws
441
+ # FileNotFound if there is no such segment and filename in the
442
+ # manifest.
443
+ #
444
+ # A manifest record is a Mash that follows the following form:
445
+ # {
446
+ # :name => "example.rb",
447
+ # :path => "files/default/example.rb",
448
+ # :specificity => "default",
449
+ # :checksum => "1234"
450
+ # }
451
+ def preferred_manifest_record(node, segment, filename)
452
+ preferences = preferences_for_path(node, segment, filename)
453
+
454
+ # ensure that we generate the manifest, which will also generate
455
+ # @manifest_records_by_path
456
+ manifest
457
+
458
+ # in order of prefernce, look for the filename in the manifest
459
+ found_pref = preferences.find {|preferred_filename| @manifest_records_by_path[preferred_filename] }
460
+ if found_pref
461
+ @manifest_records_by_path[found_pref]
462
+ else
463
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} does not contain file #{segment}/#{filename}"
464
+ end
465
+ end
466
+
467
+ def preferred_filename_on_disk_location(node, segment, filename, current_filepath=nil)
468
+ manifest_record = preferred_manifest_record(node, segment, filename)
469
+ if current_filepath && (manifest_record['checksum'] == self.class.checksum_cookbook_file(current_filepath))
470
+ nil
471
+ else
472
+ file_vendor.get_filename(manifest_record['path'])
473
+ end
474
+ end
475
+
476
+ def relative_filenames_in_preferred_directory(node, segment, dirname)
477
+ preferences = preferences_for_path(node, segment, dirname)
478
+ filenames_by_pref = Hash.new
479
+ preferences.each { |pref| filenames_by_pref[pref] = Array.new }
480
+
481
+ manifest[segment].each do |manifest_record|
482
+ manifest_record_path = manifest_record[:path]
483
+
484
+ # find the NON SPECIFIC filenames, but prefer them by filespecificity.
485
+ # For example, if we have a file:
486
+ # 'files/default/somedir/somefile.conf' we only keep
487
+ # 'somedir/somefile.conf'. If there is also
488
+ # 'files/$hostspecific/somedir/otherfiles' that matches the requested
489
+ # hostname specificity, that directory will win, as it is more specific.
490
+ #
491
+ # This is clearly ugly b/c the use case is for remote directory, where
492
+ # we're just going to make cookbook_files out of these and make the
493
+ # cookbook find them by filespecificity again. but it's the shortest
494
+ # path to "success" for now.
495
+ if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
496
+ specificity_dirname = $1
497
+ non_specific_path = manifest_record_path[/#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)}\/(.+)$/, 1]
498
+ # Record the specificity_dirname only if it's in the list of
499
+ # valid preferences
500
+ if filenames_by_pref[specificity_dirname]
501
+ filenames_by_pref[specificity_dirname] << non_specific_path
502
+ end
503
+ end
504
+ end
505
+
506
+ best_pref = preferences.find { |pref| !filenames_by_pref[pref].empty? }
507
+
508
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
509
+
510
+ filenames_by_pref[best_pref]
511
+
512
+ end
513
+
514
+ # Determine the manifest records from the most specific directory
515
+ # for the given node. See #preferred_manifest_record for a
516
+ # description of entries of the returned Array.
517
+ def preferred_manifest_records_for_directory(node, segment, dirname)
518
+ preferences = preferences_for_path(node, segment, dirname)
519
+ records_by_pref = Hash.new
520
+ preferences.each { |pref| records_by_pref[pref] = Array.new }
521
+
522
+ manifest[segment].each do |manifest_record|
523
+ manifest_record_path = manifest_record[:path]
524
+
525
+ # extract the preference part from the path.
526
+ if manifest_record_path =~ /(#{Regexp.escape(segment.to_s)}\/[^\/]+\/#{Regexp.escape(dirname)})\/.+$/
527
+ # Note the specificy_dirname includes the segment and
528
+ # dirname argument as above, which is what
529
+ # preferences_for_path returns. It could be
530
+ # "files/ubuntu-9.10/dirname", for example.
531
+ specificity_dirname = $1
532
+
533
+ # Record the specificity_dirname only if it's in the list of
534
+ # valid preferences
535
+ if records_by_pref[specificity_dirname]
536
+ records_by_pref[specificity_dirname] << manifest_record
537
+ end
538
+ end
539
+ end
540
+
541
+ best_pref = preferences.find { |pref| !records_by_pref[pref].empty? }
542
+
543
+ raise Chef::Exceptions::FileNotFound, "cookbook #{name} has no directory #{segment}/#{dirname}" unless best_pref
544
+
545
+ records_by_pref[best_pref]
546
+ end
547
+
548
+
549
+ # Given a node, segment and path (filename or directory name),
550
+ # return the priority-ordered list of preference locations to
551
+ # look.
552
+ def preferences_for_path(node, segment, path)
553
+ # only files and templates can be platform-specific
554
+ if segment.to_sym == :files || segment.to_sym == :templates
555
+ begin
556
+ platform, version = Chef::Platform.find_platform_and_version(node)
557
+ rescue ArgumentError => e
558
+ # Skip platform/version if they were not found by find_platform_and_version
559
+ if e.message =~ /Cannot find a (?:platform|version)/
560
+ platform = "/unknown_platform/"
561
+ version = "/unknown_platform_version/"
562
+ else
563
+ raise
564
+ end
565
+ end
566
+
567
+ fqdn = node[:fqdn]
568
+
569
+ # Most specific to least specific places to find the path
570
+ [
571
+ File.join(segment.to_s, "host-#{fqdn}", path),
572
+ File.join(segment.to_s, "#{platform}-#{version}", path),
573
+ File.join(segment.to_s, platform.to_s, path),
574
+ File.join(segment.to_s, "default", path)
575
+ ]
576
+ else
577
+ [File.join(segment, path)]
578
+ end
579
+ end
580
+ private :preferences_for_path
581
+
582
+ def to_hash
583
+ result = manifest.dup
584
+ result['frozen?'] = frozen_version?
585
+ result['chef_type'] = 'cookbook_version'
586
+ result.to_hash
587
+ end
588
+
589
+ def to_json(*a)
590
+ result = self.to_hash
591
+ result['json_class'] = self.class.name
592
+ result.to_json(*a)
593
+ end
594
+
595
+ def self.json_create(o)
596
+ cookbook_version = new(o["cookbook_name"])
597
+ # We want the Chef::Cookbook::Metadata object to always be inflated
598
+ cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"])
599
+ cookbook_version.manifest = o
600
+
601
+ # We don't need the following step when we decide to stop supporting deprecated operators in the metadata (e.g. <<, >>)
602
+ cookbook_version.manifest["metadata"] = JSON.parse(cookbook_version.metadata.to_json)
603
+
604
+ cookbook_version.freeze_version if o["frozen?"]
605
+ cookbook_version
606
+ end
607
+
608
+ def generate_manifest_with_urls(&url_generator)
609
+ rendered_manifest = manifest.dup
610
+ COOKBOOK_SEGMENTS.each do |segment|
611
+ if rendered_manifest.has_key?(segment)
612
+ rendered_manifest[segment].each do |manifest_record|
613
+ url_options = { :cookbook_name => name.to_s, :cookbook_version => version, :checksum => manifest_record["checksum"] }
614
+ manifest_record["url"] = url_generator.call(url_options)
615
+ end
616
+ end
617
+ end
618
+ rendered_manifest
619
+ end
620
+
621
+ def metadata_json_file
622
+ File.join(root_dir, "metadata.json")
623
+ end
624
+
625
+ def metadata_rb_file
626
+ File.join(root_dir, "metadata.rb")
627
+ end
628
+
629
+ def reload_metadata!
630
+ if File.exists?(metadata_json_file)
631
+ metadata.from_json(IO.read(metadata_json_file))
632
+ end
633
+ end
634
+
635
+ def destroy
636
+ chef_server_rest.delete_rest("cookbooks/#{name}/#{version}")
637
+ self
638
+ end
639
+
640
+ def self.load(name, version="_latest")
641
+ version = "_latest" if version == "latest"
642
+ chef_server_rest.get_rest("cookbooks/#{name}/#{version}")
643
+ end
644
+
645
+ def self.list
646
+ chef_server_rest.get_rest('cookbooks')
647
+ end
648
+
649
+ ##
650
+ # Given a +cookbook_name+, get a list of all versions that exist on the
651
+ # server.
652
+ # ===Returns
653
+ # [String]:: Array of cookbook versions, which are strings like 'x.y.z'
654
+ # nil:: if the cookbook doesn't exist. an error will also be logged.
655
+ def self.available_versions(cookbook_name)
656
+ chef_server_rest.get_rest("cookbooks/#{cookbook_name}")[cookbook_name]["versions"].map do |cb|
657
+ cb["version"]
658
+ end
659
+ rescue Net::HTTPServerException => e
660
+ if e.to_s =~ /^404/
661
+ Chef::Log.error("Cannot find a cookbook named #{cookbook_name}")
662
+ nil
663
+ else
664
+ raise
665
+ end
666
+ end
667
+
668
+ # Get the newest version of all cookbooks
669
+ def self.latest_cookbooks
670
+ chef_server_rest.get_rest('cookbooks/_latest')
671
+ end
672
+
673
+ def <=>(o)
674
+ raise Chef::Exceptions::CookbookVersionNameMismatch if self.name != o.name
675
+ # FIXME: can we change the interface to the Metadata class such
676
+ # that metadata.version returns a Chef::Version instance instead
677
+ # of a string?
678
+ Chef::Version.new(self.version) <=> Chef::Version.new(o.version)
679
+ end
680
+
681
+ private
682
+
683
+ # For each filename, produce a mapping of base filename (i.e. recipe name
684
+ # or attribute file) to on disk location
685
+ def filenames_by_name(filenames)
686
+ filenames.select{|filename| filename =~ /\.rb$/}.inject({}){|memo, filename| memo[File.basename(filename, '.rb')] = filename ; memo }
687
+ end
688
+
689
+ # See #manifest for a description of the manifest return value.
690
+ # See #preferred_manifest_record for a description an individual manifest record.
691
+ def generate_manifest
692
+ manifest = Mash.new({
693
+ :recipes => Array.new,
694
+ :definitions => Array.new,
695
+ :libraries => Array.new,
696
+ :attributes => Array.new,
697
+ :files => Array.new,
698
+ :templates => Array.new,
699
+ :resources => Array.new,
700
+ :providers => Array.new,
701
+ :root_files => Array.new
702
+ })
703
+ checksums_to_on_disk_paths = {}
704
+
705
+ COOKBOOK_SEGMENTS.each do |segment|
706
+ segment_filenames(segment).each do |segment_file|
707
+ next if File.directory?(segment_file)
708
+
709
+ file_name = nil
710
+ path = nil
711
+ specificity = "default"
712
+
713
+ if segment == :root_files
714
+ matcher = segment_file.match(".+/#{Regexp.escape(name.to_s)}/(.+)")
715
+ file_name = matcher[1]
716
+ path = file_name
717
+ elsif segment == :templates || segment == :files
718
+ matcher = segment_file.match("/#{Regexp.escape(name.to_s)}/(#{Regexp.escape(segment.to_s)}/(.+?)/(.+))")
719
+ unless matcher
720
+ Chef::Log.debug("Skipping file #{segment_file}, as it isn't in any of the proper directories (platform-version, platform or default)")
721
+ Chef::Log.debug("You probably need to move #{segment_file} into the 'default' sub-directory")
722
+ next
723
+ end
724
+ path = matcher[1]
725
+ specificity = matcher[2]
726
+ file_name = matcher[3]
727
+ else
728
+ matcher = segment_file.match("/#{Regexp.escape(name.to_s)}/(#{Regexp.escape(segment.to_s)}/(.+))")
729
+ path = matcher[1]
730
+ file_name = matcher[2]
731
+ end
732
+
733
+ csum = self.class.checksum_cookbook_file(segment_file)
734
+ checksums_to_on_disk_paths[csum] = segment_file
735
+ rs = Mash.new({
736
+ :name => file_name,
737
+ :path => path,
738
+ :checksum => csum
739
+ })
740
+ rs[:specificity] = specificity
741
+
742
+ manifest[segment] << rs
743
+ end
744
+ end
745
+
746
+ manifest[:cookbook_name] = name.to_s
747
+ manifest[:metadata] = metadata
748
+ manifest[:version] = metadata.version
749
+ manifest[:name] = full_name
750
+
751
+ @checksums = checksums_to_on_disk_paths
752
+ @manifest = manifest
753
+ @manifest_records_by_path = extract_manifest_records_by_path(manifest)
754
+ end
755
+
756
+ def file_vendor
757
+ unless @file_vendor
758
+ @file_vendor = Chef::Cookbook::FileVendor.create_from_manifest(manifest)
759
+ end
760
+ @file_vendor
761
+ end
762
+
763
+ def extract_checksums_from_manifest(manifest)
764
+ checksums = {}
765
+ COOKBOOK_SEGMENTS.each do |segment|
766
+ next unless manifest.has_key?(segment)
767
+ manifest[segment].each do |manifest_record|
768
+ checksums[manifest_record[:checksum]] = nil
769
+ end
770
+ end
771
+ checksums
772
+ end
773
+
774
+ def extract_manifest_records_by_path(manifest)
775
+ manifest_records_by_path = {}
776
+ COOKBOOK_SEGMENTS.each do |segment|
777
+ next unless manifest.has_key?(segment)
778
+ manifest[segment].each do |manifest_record|
779
+ manifest_records_by_path[manifest_record[:path]] = manifest_record
780
+ end
781
+ end
782
+ manifest_records_by_path
783
+ end
784
+
785
+ end
786
+ end