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.
- data/bin/shef +1 -0
- data/distro/common/man/man1/chef-server-webui.1 +106 -0
- data/distro/common/man/man1/chef-server.1 +0 -1
- data/distro/common/man/man1/chef-solr-indexer.1 +55 -0
- data/distro/common/man/man1/chef-solr.1 +55 -0
- data/distro/common/man/man8/chef-client.8 +4 -2
- data/distro/common/man/man8/chef-solo.8 +1 -2
- data/distro/common/man/man8/chef-solr-rebuild.8 +37 -0
- data/distro/common/man/man8/knife.8 +668 -266
- data/distro/common/man/man8/shef.8 +45 -0
- data/distro/common/markdown/README +3 -0
- data/distro/common/markdown/knife.mkd +520 -0
- data/distro/debian/etc/default/chef-client +4 -0
- data/distro/debian/etc/default/chef-server +6 -0
- data/distro/debian/etc/default/chef-server-webui +6 -0
- data/distro/debian/etc/default/chef-solr +4 -0
- data/distro/debian/etc/default/chef-solr-indexer +4 -0
- data/distro/debian/etc/init.d/chef-client +41 -41
- data/distro/debian/etc/init.d/chef-server +10 -10
- data/distro/debian/etc/init.d/chef-server-webui +121 -0
- data/distro/debian/etc/init.d/chef-solr +177 -0
- data/distro/debian/etc/init.d/chef-solr-indexer +176 -0
- data/distro/redhat/etc/init.d/chef-client +76 -48
- data/distro/redhat/etc/init.d/chef-server +85 -51
- data/distro/redhat/etc/init.d/chef-server-webui +85 -51
- data/distro/redhat/etc/init.d/chef-solr +77 -49
- data/distro/redhat/etc/init.d/chef-solr-indexer +77 -48
- data/distro/redhat/etc/logrotate.d/chef-client +8 -0
- data/distro/redhat/etc/logrotate.d/chef-server +8 -0
- data/distro/redhat/etc/logrotate.d/chef-server-webui +8 -0
- data/distro/redhat/etc/logrotate.d/chef-solr +8 -0
- data/distro/redhat/etc/logrotate.d/chef-solr-indexer +8 -0
- data/distro/redhat/etc/sysconfig/chef-client +9 -4
- data/distro/redhat/etc/sysconfig/chef-server +10 -6
- data/distro/redhat/etc/sysconfig/chef-server-webui +10 -6
- data/distro/redhat/etc/sysconfig/chef-solr +3 -4
- data/distro/redhat/etc/sysconfig/chef-solr-indexer +3 -3
- data/lib/chef.rb +16 -5
- data/lib/chef/application/knife.rb +2 -2
- data/lib/chef/application/solo.rb +1 -7
- data/lib/chef/cache/checksum.rb +12 -5
- data/lib/chef/cache/file_cache_by_checksum.rb +52 -0
- data/lib/chef/checksum.rb +115 -0
- data/lib/chef/client.rb +193 -185
- data/lib/chef/config.rb +9 -1
- data/lib/chef/cookbook/cookbook_collection.rb +43 -0
- data/lib/chef/cookbook/file_system_file_vendor.rb +53 -0
- data/lib/chef/cookbook/file_vendor.rb +47 -0
- data/lib/chef/cookbook/metadata.rb +34 -35
- data/lib/chef/cookbook/metadata/version.rb +1 -1
- data/lib/chef/cookbook_loader.rb +70 -45
- data/lib/chef/cookbook_version.rb +760 -0
- data/lib/chef/couchdb.rb +8 -5
- data/lib/chef/data_bag_item.rb +5 -5
- data/lib/chef/exceptions.rb +10 -0
- data/lib/chef/file_access_control.rb +134 -0
- data/lib/chef/handler.rb +62 -0
- data/lib/chef/handler/json_file.rb +47 -0
- data/lib/chef/knife.rb +14 -2
- data/lib/chef/knife/bootstrap.rb +126 -0
- data/lib/chef/knife/cookbook_bulk_delete.rb +1 -1
- data/lib/chef/knife/cookbook_delete.rb +4 -4
- data/lib/chef/knife/cookbook_download.rb +57 -26
- data/lib/chef/knife/cookbook_metadata.rb +2 -2
- data/lib/chef/knife/cookbook_show.rb +30 -11
- data/lib/chef/knife/cookbook_upload.rb +113 -86
- data/lib/chef/knife/ec2_server_create.rb +146 -0
- data/lib/chef/knife/ec2_server_delete.rb +84 -0
- data/lib/chef/knife/ec2_server_list.rb +82 -0
- data/lib/chef/knife/status.rb +51 -0
- data/lib/chef/mixin/language_include_attribute.rb +16 -11
- data/lib/chef/mixin/language_include_recipe.rb +15 -16
- data/lib/chef/mixin/recipe_definition_dsl_core.rb +17 -20
- data/lib/chef/mixin/shell_out.rb +38 -0
- data/lib/chef/mixins.rb +1 -1
- data/lib/chef/node.rb +190 -63
- data/lib/chef/node/attribute.rb +92 -78
- data/lib/chef/platform.rb +24 -4
- data/lib/chef/provider.rb +28 -10
- data/lib/chef/provider/breakpoint.rb +2 -2
- data/lib/chef/provider/cookbook_file.rb +96 -0
- data/lib/chef/provider/cron.rb +2 -2
- data/lib/chef/provider/deploy.rb +12 -10
- data/lib/chef/provider/env.rb +152 -0
- data/lib/chef/provider/env/windows.rb +75 -0
- data/lib/chef/provider/file.rb +10 -14
- data/lib/chef/provider/group.rb +15 -2
- data/lib/chef/provider/group/dscl.rb +17 -25
- data/lib/chef/provider/group/gpasswd.rb +6 -3
- data/lib/chef/provider/group/pw.rb +3 -7
- data/lib/chef/provider/group/windows.rb +79 -0
- data/lib/chef/provider/link.rb +4 -5
- data/lib/chef/provider/mdadm.rb +25 -18
- data/lib/chef/provider/mount/mount.rb +28 -27
- data/lib/chef/provider/package.rb +35 -35
- data/lib/chef/provider/package/dpkg.rb +13 -10
- data/lib/chef/provider/package/easy_install.rb +6 -6
- data/lib/chef/provider/package/freebsd.rb +17 -51
- data/lib/chef/provider/package/rpm.rb +1 -1
- data/lib/chef/provider/package/rubygems.rb +391 -74
- data/lib/chef/provider/package/yum.rb +2 -2
- data/lib/chef/provider/package/zypper.rb +2 -1
- data/lib/chef/provider/remote_directory.rb +60 -83
- data/lib/chef/provider/remote_file.rb +17 -66
- data/lib/chef/provider/script.rb +20 -9
- data/lib/chef/provider/service.rb +23 -30
- data/lib/chef/provider/service/arch.rb +3 -3
- data/lib/chef/provider/service/debian.rb +22 -17
- data/lib/chef/provider/service/freebsd.rb +4 -4
- data/lib/chef/provider/service/init.rb +2 -2
- data/lib/chef/provider/service/redhat.rb +14 -16
- data/lib/chef/provider/service/simple.rb +7 -3
- data/lib/chef/provider/service/solaris.rb +85 -0
- data/lib/chef/provider/service/upstart.rb +12 -7
- data/lib/chef/provider/service/windows.rb +2 -2
- data/lib/chef/provider/template.rb +133 -118
- data/lib/chef/provider/user.rb +34 -17
- data/lib/chef/provider/user/dscl.rb +117 -114
- data/lib/chef/provider/user/windows.rb +124 -0
- data/lib/chef/providers.rb +7 -0
- data/lib/chef/recipe.rb +39 -20
- data/lib/chef/resource.rb +47 -52
- data/lib/chef/resource/apt_package.rb +4 -4
- data/lib/chef/resource/bash.rb +4 -4
- data/lib/chef/resource/cookbook_file.rb +45 -0
- data/lib/chef/resource/cron.rb +3 -3
- data/lib/chef/resource/csh.rb +4 -4
- data/lib/chef/resource/deploy.rb +3 -3
- data/lib/chef/resource/directory.rb +4 -4
- data/lib/chef/resource/dpkg_package.rb +4 -4
- data/lib/chef/resource/easy_install_package.rb +3 -3
- data/lib/chef/resource/env.rb +58 -0
- data/lib/chef/resource/erl_call.rb +3 -3
- data/lib/chef/resource/execute.rb +3 -3
- data/lib/chef/resource/file.rb +3 -3
- data/lib/chef/resource/freebsd_package.rb +3 -3
- data/lib/chef/resource/gem_package.rb +17 -9
- data/lib/chef/resource/git.rb +3 -3
- data/lib/chef/resource/group.rb +3 -3
- data/lib/chef/resource/http_request.rb +4 -4
- data/lib/chef/resource/ifconfig.rb +3 -3
- data/lib/chef/resource/link.rb +3 -3
- data/lib/chef/resource/log.rb +2 -2
- data/lib/chef/resource/macports_package.rb +2 -2
- data/lib/chef/resource/mdadm.rb +3 -3
- data/lib/chef/resource/mount.rb +2 -2
- data/lib/chef/resource/package.rb +4 -4
- data/lib/chef/resource/pacman_package.rb +4 -4
- data/lib/chef/resource/perl.rb +4 -4
- data/lib/chef/resource/portage_package.rb +4 -4
- data/lib/chef/resource/python.rb +4 -4
- data/lib/chef/resource/remote_directory.rb +3 -3
- data/lib/chef/resource/remote_file.rb +26 -3
- data/lib/chef/resource/route.rb +3 -3
- data/lib/chef/resource/ruby.rb +3 -3
- data/lib/chef/resource/ruby_block.rb +3 -2
- data/lib/chef/resource/scm.rb +7 -5
- data/lib/chef/resource/script.rb +4 -4
- data/lib/chef/resource/service.rb +3 -3
- data/lib/chef/resource/subversion.rb +4 -2
- data/lib/chef/resource/template.rb +3 -3
- data/lib/chef/resource/user.rb +3 -3
- data/lib/chef/resource/yum_package.rb +3 -3
- data/lib/chef/resource_collection.rb +9 -5
- data/lib/chef/resources.rb +2 -0
- data/lib/chef/rest.rb +4 -0
- data/lib/chef/role.rb +2 -0
- data/lib/chef/run_context.rb +108 -0
- data/lib/chef/run_list.rb +75 -98
- data/lib/chef/run_list/run_list_expansion.rb +156 -0
- data/lib/chef/run_list/run_list_item.rb +71 -0
- data/lib/chef/runner.rb +58 -61
- data/lib/chef/sandbox.rb +147 -0
- data/lib/chef/shef.rb +4 -3
- data/lib/chef/shef/ext.rb +12 -4
- data/lib/chef/shef/shef_session.rb +27 -23
- data/lib/chef/shell_out.rb +375 -0
- data/lib/chef/util/windows.rb +56 -0
- data/lib/chef/util/windows/net_group.rb +101 -0
- data/lib/chef/util/windows/net_user.rb +198 -0
- data/lib/chef/version.rb +20 -0
- metadata +112 -22
- data/lib/chef/compile.rb +0 -158
- data/lib/chef/cookbook.rb +0 -201
- 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
|