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.
- data/LICENSE +201 -0
- data/README.rdoc +102 -0
- data/bin/chef-solo +25 -0
- data/lib/chef.rb +41 -0
- data/lib/chef/application.rb +147 -0
- data/lib/chef/application/solo.rb +181 -0
- data/lib/chef/applications.rb +1 -0
- data/lib/chef/checksum.rb +83 -0
- data/lib/chef/checksum_cache.rb +189 -0
- data/lib/chef/client.rb +325 -0
- data/lib/chef/config.rb +152 -0
- data/lib/chef/cookbook/chefignore.rb +66 -0
- data/lib/chef/cookbook/cookbook_collection.rb +45 -0
- data/lib/chef/cookbook/cookbook_version_loader.rb +173 -0
- data/lib/chef/cookbook/file_system_file_vendor.rb +56 -0
- data/lib/chef/cookbook/file_vendor.rb +48 -0
- data/lib/chef/cookbook/metadata.rb +629 -0
- data/lib/chef/cookbook/syntax_check.rb +136 -0
- data/lib/chef/cookbook_loader.rb +121 -0
- data/lib/chef/cookbook_version.rb +786 -0
- data/lib/chef/cookbook_version_selector.rb +151 -0
- data/lib/chef/environment.rb +267 -0
- data/lib/chef/exceptions.rb +150 -0
- data/lib/chef/file_access_control.rb +144 -0
- data/lib/chef/file_cache.rb +218 -0
- data/lib/chef/handler.rb +206 -0
- data/lib/chef/handler/error_report.rb +33 -0
- data/lib/chef/handler/json_file.rb +58 -0
- data/lib/chef/json_compat.rb +52 -0
- data/lib/chef/log.rb +39 -0
- data/lib/chef/mash.rb +211 -0
- data/lib/chef/mixin/check_helper.rb +31 -0
- data/lib/chef/mixin/checksum.rb +32 -0
- data/lib/chef/mixin/command.rb +221 -0
- data/lib/chef/mixin/command/unix.rb +215 -0
- data/lib/chef/mixin/command/windows.rb +76 -0
- data/lib/chef/mixin/convert_to_class_name.rb +63 -0
- data/lib/chef/mixin/create_path.rb +57 -0
- data/lib/chef/mixin/deep_merge.rb +225 -0
- data/lib/chef/mixin/deprecation.rb +65 -0
- data/lib/chef/mixin/from_file.rb +50 -0
- data/lib/chef/mixin/get_source_from_package.rb +42 -0
- data/lib/chef/mixin/language.rb +125 -0
- data/lib/chef/mixin/language_include_attribute.rb +61 -0
- data/lib/chef/mixin/language_include_recipe.rb +52 -0
- data/lib/chef/mixin/params_validate.rb +225 -0
- data/lib/chef/mixin/recipe_definition_dsl_core.rb +78 -0
- data/lib/chef/mixin/shell_out.rb +41 -0
- data/lib/chef/mixin/template.rb +95 -0
- data/lib/chef/mixin/xml_escape.rb +140 -0
- data/lib/chef/mixins.rb +15 -0
- data/lib/chef/monkey_patches/dir.rb +36 -0
- data/lib/chef/monkey_patches/numeric.rb +15 -0
- data/lib/chef/monkey_patches/object.rb +9 -0
- data/lib/chef/monkey_patches/regexp.rb +34 -0
- data/lib/chef/monkey_patches/string.rb +49 -0
- data/lib/chef/monkey_patches/tempfile.rb +64 -0
- data/lib/chef/nil_argument.rb +3 -0
- data/lib/chef/node.rb +469 -0
- data/lib/chef/node/attribute.rb +487 -0
- data/lib/chef/platform.rb +409 -0
- data/lib/chef/provider.rb +124 -0
- data/lib/chef/provider/breakpoint.rb +31 -0
- data/lib/chef/provider/cookbook_file.rb +100 -0
- data/lib/chef/provider/cron.rb +186 -0
- data/lib/chef/provider/cron/solaris.rb +195 -0
- data/lib/chef/provider/deploy.rb +343 -0
- data/lib/chef/provider/deploy/revision.rb +80 -0
- data/lib/chef/provider/deploy/timestamped.rb +33 -0
- data/lib/chef/provider/directory.rb +72 -0
- data/lib/chef/provider/env.rb +152 -0
- data/lib/chef/provider/env/windows.rb +75 -0
- data/lib/chef/provider/erl_call.rb +101 -0
- data/lib/chef/provider/execute.rb +66 -0
- data/lib/chef/provider/file.rb +222 -0
- data/lib/chef/provider/git.rb +243 -0
- data/lib/chef/provider/group.rb +133 -0
- data/lib/chef/provider/group/aix.rb +70 -0
- data/lib/chef/provider/group/dscl.rb +121 -0
- data/lib/chef/provider/group/gpasswd.rb +53 -0
- data/lib/chef/provider/group/groupadd.rb +81 -0
- data/lib/chef/provider/group/pw.rb +84 -0
- data/lib/chef/provider/group/suse.rb +53 -0
- data/lib/chef/provider/group/usermod.rb +57 -0
- data/lib/chef/provider/group/windows.rb +79 -0
- data/lib/chef/provider/ifconfig.rb +134 -0
- data/lib/chef/provider/link.rb +164 -0
- data/lib/chef/provider/log.rb +54 -0
- data/lib/chef/provider/mdadm.rb +91 -0
- data/lib/chef/provider/mount.rb +114 -0
- data/lib/chef/provider/mount/mount.rb +240 -0
- data/lib/chef/provider/mount/windows.rb +81 -0
- data/lib/chef/provider/ohai.rb +42 -0
- data/lib/chef/provider/package.rb +163 -0
- data/lib/chef/provider/package/apt.rb +135 -0
- data/lib/chef/provider/package/dpkg.rb +115 -0
- data/lib/chef/provider/package/easy_install.rb +136 -0
- data/lib/chef/provider/package/freebsd.rb +125 -0
- data/lib/chef/provider/package/macports.rb +105 -0
- data/lib/chef/provider/package/pacman.rb +101 -0
- data/lib/chef/provider/package/portage.rb +135 -0
- data/lib/chef/provider/package/rpm.rb +104 -0
- data/lib/chef/provider/package/rubygems.rb +465 -0
- data/lib/chef/provider/package/solaris.rb +130 -0
- data/lib/chef/provider/package/yum-dump.py +286 -0
- data/lib/chef/provider/package/yum.rb +1128 -0
- data/lib/chef/provider/package/zypper.rb +144 -0
- data/lib/chef/provider/remote_directory.rb +137 -0
- data/lib/chef/provider/route.rb +193 -0
- data/lib/chef/provider/ruby_block.rb +34 -0
- data/lib/chef/provider/script.rb +55 -0
- data/lib/chef/provider/service.rb +122 -0
- data/lib/chef/provider/service/arch.rb +116 -0
- data/lib/chef/provider/service/debian.rb +130 -0
- data/lib/chef/provider/service/freebsd.rb +154 -0
- data/lib/chef/provider/service/gentoo.rb +53 -0
- data/lib/chef/provider/service/init.rb +71 -0
- data/lib/chef/provider/service/insserv.rb +52 -0
- data/lib/chef/provider/service/redhat.rb +60 -0
- data/lib/chef/provider/service/simple.rb +120 -0
- data/lib/chef/provider/service/solaris.rb +85 -0
- data/lib/chef/provider/service/systemd.rb +102 -0
- data/lib/chef/provider/service/upstart.rb +198 -0
- data/lib/chef/provider/service/windows.rb +146 -0
- data/lib/chef/provider/subversion.rb +197 -0
- data/lib/chef/provider/template.rb +104 -0
- data/lib/chef/provider/user.rb +186 -0
- data/lib/chef/provider/user/dscl.rb +280 -0
- data/lib/chef/provider/user/pw.rb +113 -0
- data/lib/chef/provider/user/useradd.rb +137 -0
- data/lib/chef/provider/user/windows.rb +124 -0
- data/lib/chef/providers.rb +93 -0
- data/lib/chef/recipe.rb +129 -0
- data/lib/chef/resource.rb +584 -0
- data/lib/chef/resource/apt_package.rb +34 -0
- data/lib/chef/resource/bash.rb +33 -0
- data/lib/chef/resource/breakpoint.rb +35 -0
- data/lib/chef/resource/cookbook_file.rb +45 -0
- data/lib/chef/resource/cron.rb +188 -0
- data/lib/chef/resource/csh.rb +33 -0
- data/lib/chef/resource/deploy.rb +380 -0
- data/lib/chef/resource/deploy_revision.rb +40 -0
- data/lib/chef/resource/directory.rb +89 -0
- data/lib/chef/resource/dpkg_package.rb +34 -0
- data/lib/chef/resource/easy_install_package.rb +57 -0
- data/lib/chef/resource/env.rb +58 -0
- data/lib/chef/resource/erl_call.rb +83 -0
- data/lib/chef/resource/execute.rb +127 -0
- data/lib/chef/resource/file.rb +112 -0
- data/lib/chef/resource/freebsd_package.rb +35 -0
- data/lib/chef/resource/gem_package.rb +53 -0
- data/lib/chef/resource/git.rb +46 -0
- data/lib/chef/resource/group.rb +70 -0
- data/lib/chef/resource/ifconfig.rb +134 -0
- data/lib/chef/resource/link.rb +105 -0
- data/lib/chef/resource/log.rb +62 -0
- data/lib/chef/resource/macports_package.rb +29 -0
- data/lib/chef/resource/mdadm.rb +82 -0
- data/lib/chef/resource/mount.rb +134 -0
- data/lib/chef/resource/ohai.rb +40 -0
- data/lib/chef/resource/package.rb +80 -0
- data/lib/chef/resource/pacman_package.rb +33 -0
- data/lib/chef/resource/perl.rb +33 -0
- data/lib/chef/resource/portage_package.rb +33 -0
- data/lib/chef/resource/python.rb +33 -0
- data/lib/chef/resource/remote_directory.rb +109 -0
- data/lib/chef/resource/route.rb +135 -0
- data/lib/chef/resource/rpm_package.rb +34 -0
- data/lib/chef/resource/ruby.rb +33 -0
- data/lib/chef/resource/ruby_block.rb +40 -0
- data/lib/chef/resource/scm.rb +147 -0
- data/lib/chef/resource/script.rb +60 -0
- data/lib/chef/resource/service.rb +160 -0
- data/lib/chef/resource/solaris_package.rb +36 -0
- data/lib/chef/resource/subversion.rb +36 -0
- data/lib/chef/resource/template.rb +69 -0
- data/lib/chef/resource/timestamped_deploy.rb +31 -0
- data/lib/chef/resource/user.rb +130 -0
- data/lib/chef/resource/yum_package.rb +63 -0
- data/lib/chef/resource_collection.rb +217 -0
- data/lib/chef/resource_collection/stepable_iterator.rb +124 -0
- data/lib/chef/resource_definition.rb +67 -0
- data/lib/chef/resource_definition_list.rb +38 -0
- data/lib/chef/resources.rb +62 -0
- data/lib/chef/role.rb +225 -0
- data/lib/chef/run_context.rb +126 -0
- data/lib/chef/run_list.rb +161 -0
- data/lib/chef/run_list/run_list_expansion.rb +159 -0
- data/lib/chef/run_list/run_list_item.rb +92 -0
- data/lib/chef/run_list/versioned_recipe_list.rb +68 -0
- data/lib/chef/run_status.rb +121 -0
- data/lib/chef/runner.rb +105 -0
- data/lib/chef/shell_out.rb +250 -0
- data/lib/chef/shell_out/unix.rb +223 -0
- data/lib/chef/shell_out/windows.rb +98 -0
- data/lib/chef/tasks/chef_repo.rake +330 -0
- data/lib/chef/util/file_edit.rb +122 -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_use.rb +121 -0
- data/lib/chef/util/windows/net_user.rb +198 -0
- data/lib/chef/util/windows/volume.rb +59 -0
- data/lib/chef/version.rb +23 -0
- data/lib/chef/version_class.rb +70 -0
- data/lib/chef/version_constraint.rb +116 -0
- 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
|