chef 11.10.0.alpha.1 → 11.10.0.rc.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +57 -36
- data/distro/common/html/chef-client.8.html +4 -4
- data/distro/common/html/chef-expander.8.html +4 -4
- data/distro/common/html/chef-expanderctl.8.html +4 -4
- data/distro/common/html/chef-server-webui.8.html +4 -4
- data/distro/common/html/chef-server.8.html +4 -4
- data/distro/common/html/chef-shell.1.html +4 -4
- data/distro/common/html/chef-solo.8.html +4 -4
- data/distro/common/html/chef-solr.8.html +5 -5
- data/distro/common/html/knife-bootstrap.1.html +4 -4
- data/distro/common/html/knife-client.1.html +4 -4
- data/distro/common/html/knife-configure.1.html +4 -4
- data/distro/common/html/knife-cookbook-site.1.html +4 -4
- data/distro/common/html/knife-cookbook.1.html +4 -4
- data/distro/common/html/knife-data-bag.1.html +4 -4
- data/distro/common/html/knife-environment.1.html +4 -4
- data/distro/common/html/knife-exec.1.html +4 -4
- data/distro/common/html/knife-index.1.html +4 -4
- data/distro/common/html/knife-node.1.html +4 -4
- data/distro/common/html/knife-role.1.html +4 -4
- data/distro/common/html/knife-search.1.html +4 -4
- data/distro/common/html/knife-ssh.1.html +4 -4
- data/distro/common/html/knife-status.1.html +4 -4
- data/distro/common/html/knife-tag.1.html +4 -4
- data/distro/common/html/knife.1.html +4 -4
- data/distro/common/man/man1/knife-bootstrap.1 +58 -64
- data/distro/common/man/man1/knife-client.1 +19 -22
- data/distro/common/man/man1/knife-configure.1 +37 -46
- data/distro/common/man/man1/knife-cookbook-site.1 +14 -17
- data/distro/common/man/man1/knife-cookbook.1 +15 -18
- data/distro/common/man/man1/knife-data-bag.1 +14 -17
- data/distro/common/man/man1/knife-delete.1 +38 -47
- data/distro/common/man/man1/knife-deps.1 +39 -48
- data/distro/common/man/man1/knife-diff.1 +43 -52
- data/distro/common/man/man1/knife-download.1 +47 -53
- data/distro/common/man/man1/knife-edit.1 +32 -41
- data/distro/common/man/man1/knife-environment.1 +14 -17
- data/distro/common/man/man1/knife-exec.1 +52 -61
- data/distro/common/man/man1/knife-index-rebuild.1 +1 -61
- data/distro/common/man/man1/knife-list.1 +47 -59
- data/distro/common/man/man1/knife-node.1 +15 -18
- data/distro/common/man/man1/knife-raw.1 +28 -46
- data/distro/common/man/man1/knife-recipe-list.1 +1 -61
- data/distro/common/man/man1/knife-role.1 +19 -25
- data/distro/common/man/man1/knife-search.1 +53 -62
- data/distro/common/man/man1/knife-show.1 +36 -28
- data/distro/common/man/man1/knife-ssh.1 +55 -61
- data/distro/common/man/man1/knife-status.1 +34 -43
- data/distro/common/man/man1/knife-tag.1 +14 -17
- data/distro/common/man/man1/knife-upload.1 +47 -56
- data/distro/common/man/man1/knife-user.1 +17 -20
- data/distro/common/man/man1/knife-xargs.1 +60 -69
- data/lib/chef/application.rb +3 -1
- data/lib/chef/application/windows_service.rb +0 -1
- data/lib/chef/client.rb +41 -152
- data/lib/chef/config.rb +19 -23
- data/lib/chef/data_bag.rb +1 -1
- data/lib/chef/data_bag_item.rb +1 -1
- data/lib/chef/exceptions.rb +8 -0
- data/lib/chef/formatters/doc.rb +15 -0
- data/lib/chef/formatters/error_inspectors/api_error_formatting.rb +2 -1
- data/lib/chef/http.rb +18 -8
- data/lib/chef/http/authenticator.rb +4 -0
- data/lib/chef/http/cookie_manager.rb +3 -0
- data/lib/chef/http/decompressor.rb +4 -0
- data/lib/chef/http/json_input.rb +4 -0
- data/lib/chef/http/json_output.rb +4 -0
- data/lib/chef/http/validate_content_length.rb +94 -0
- data/lib/chef/knife.rb +0 -1
- data/lib/chef/knife/configure.rb +6 -6
- data/lib/chef/knife/cookbook_create.rb +2 -2
- data/lib/chef/knife/core/subcommand_loader.rb +49 -3
- data/lib/chef/knife/ssh.rb +34 -4
- data/lib/chef/mixin/path_sanity.rb +1 -0
- data/lib/chef/monologger.rb +1 -2
- data/lib/chef/node.rb +7 -0
- data/lib/chef/policy_builder.rb +49 -0
- data/lib/chef/policy_builder/expand_node_object.rb +230 -0
- data/lib/chef/policy_builder/policyfile.rb +338 -0
- data/lib/chef/provider/file.rb +15 -5
- data/lib/chef/provider/group.rb +6 -2
- data/lib/chef/provider/group/windows.rb +12 -2
- data/lib/chef/provider/http_request.rb +3 -2
- data/lib/chef/provider/package.rb +1 -0
- data/lib/chef/provider/package/aix.rb +1 -1
- data/lib/chef/provider/service/debian.rb +7 -2
- data/lib/chef/resource/file.rb +8 -1
- data/lib/chef/resource/package.rb +9 -0
- data/lib/chef/resource/service.rb +0 -1
- data/lib/chef/rest.rb +2 -0
- data/lib/chef/run_context.rb +1 -1
- data/lib/chef/util/file_edit.rb +1 -1
- data/lib/chef/util/windows/net_group.rb +7 -6
- data/lib/chef/version.rb +1 -1
- data/lib/chef/win32/version.rb +31 -18
- data/spec/data/cookbooks/preseed/templates/default/preseed-template-variables.seed +1 -0
- data/spec/functional/resource/file_spec.rb +0 -1
- data/spec/functional/resource/group_spec.rb +96 -16
- data/spec/functional/resource/package_spec.rb +17 -0
- data/spec/functional/resource/user_spec.rb +2 -2
- data/spec/functional/win32/versions_spec.rb +39 -0
- data/spec/integration/client/client_spec.rb +27 -28
- data/spec/spec_helper.rb +2 -0
- data/spec/support/platform_helpers.rb +7 -1
- data/spec/support/shared/functional/file_resource.rb +83 -43
- data/spec/unit/application_spec.rb +7 -5
- data/spec/unit/client_spec.rb +10 -3
- data/spec/unit/config_spec.rb +0 -30
- data/spec/unit/cookbook_spec.rb +1 -0
- data/spec/unit/data_bag_item_spec.rb +8 -0
- data/spec/unit/data_bag_spec.rb +6 -0
- data/spec/unit/http_spec.rb +48 -0
- data/spec/unit/knife/core/subcommand_loader_spec.rb +77 -1
- data/spec/unit/knife/ssh_spec.rb +107 -0
- data/spec/unit/mixin/path_sanity_spec.rb +6 -0
- data/spec/unit/mixin/securable_spec.rb +77 -3
- data/spec/unit/monologger_spec.rb +45 -0
- data/spec/unit/node_spec.rb +16 -0
- data/spec/unit/policy_builder/expand_node_object_spec.rb +320 -0
- data/spec/unit/policy_builder/policyfile_spec.rb +399 -0
- data/spec/unit/policy_builder_spec.rb +26 -0
- data/spec/unit/provider/deploy_spec.rb +3 -0
- data/spec/unit/provider/group/windows_spec.rb +1 -0
- data/spec/unit/provider/http_request_spec.rb +23 -1
- data/spec/unit/provider/service/debian_service_spec.rb +50 -19
- data/spec/unit/recipe_spec.rb +4 -0
- data/spec/unit/resource/package_spec.rb +5 -0
- data/spec/unit/rest_spec.rb +375 -278
- data/spec/unit/run_context_spec.rb +4 -0
- metadata +96 -59
- checksums.yaml +0 -7
data/lib/chef/knife/ssh.rb
CHANGED
@@ -64,10 +64,14 @@ class Chef
|
|
64
64
|
:long => "--ssh-user USERNAME",
|
65
65
|
:description => "The ssh username"
|
66
66
|
|
67
|
-
option :
|
68
|
-
:short => "-P PASSWORD",
|
69
|
-
:long => "--ssh-password PASSWORD",
|
70
|
-
:description => "The ssh password"
|
67
|
+
option :ssh_password_ng,
|
68
|
+
:short => "-P [PASSWORD]",
|
69
|
+
:long => "--ssh-password [PASSWORD]",
|
70
|
+
:description => "The ssh password - will prompt if flag is specified but no password is given",
|
71
|
+
# default to a value that can not be a password (boolean)
|
72
|
+
# so we can effectively test if this parameter was specified
|
73
|
+
# without a vlaue
|
74
|
+
:default => false
|
71
75
|
|
72
76
|
option :ssh_port,
|
73
77
|
:short => "-p PORT",
|
@@ -432,6 +436,31 @@ class Chef
|
|
432
436
|
Chef::Config[:knife][:ssh_user])
|
433
437
|
end
|
434
438
|
|
439
|
+
# This is a bit overly complicated because of the way we want knife ssh to work with -P causing a password prompt for
|
440
|
+
# the user, but we have to be conscious that this code gets included in knife bootstrap and knife * server create as
|
441
|
+
# well. We want to change the semantics so that the default is false and 'nil' means -P without an argument on the
|
442
|
+
# command line. But the other utilities expect nil to be the default and we can't prompt in that case. So we effectively
|
443
|
+
# use ssh_password_ng to determine if we're coming from knife ssh or from the other utilities. The other utilties can
|
444
|
+
# also be patched to use ssh_password_ng easily as long they follow the convention that the default is false.
|
445
|
+
def configure_password
|
446
|
+
if config.has_key?(:ssh_password_ng) && config[:ssh_password_ng].nil?
|
447
|
+
# If the parameter is called on the command line with no value
|
448
|
+
# it will set :ssh_password_ng = nil
|
449
|
+
# This is where we want to trigger a prompt for password
|
450
|
+
config[:ssh_password] = get_password
|
451
|
+
else
|
452
|
+
# if ssh_password_ng is false then it has not been set at all, and we may be in knife ec2 and still
|
453
|
+
# using an old config[:ssh_password]. this is backwards compatibility. all knife cloud plugins should
|
454
|
+
# be updated to use ssh_password_ng with a default of false and ssh_password should be retired, (but
|
455
|
+
# we'll still need to use the ssh_password out of knife.rb if we find that).
|
456
|
+
ssh_password = config.has_key?(:ssh_password_ng) ? config[:ssh_password_ng] : config[:ssh_password]
|
457
|
+
# Otherwise, the password has either been specified on the command line,
|
458
|
+
# in knife.rb, or key based auth will be attempted
|
459
|
+
config[:ssh_password] = get_stripped_unfrozen_value(ssh_password ||
|
460
|
+
Chef::Config[:knife][:ssh_password])
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
435
464
|
def configure_identity_file
|
436
465
|
config[:identity_file] = get_stripped_unfrozen_value(config[:identity_file] ||
|
437
466
|
Chef::Config[:knife][:ssh_identity_file])
|
@@ -448,6 +477,7 @@ class Chef
|
|
448
477
|
|
449
478
|
configure_attribute
|
450
479
|
configure_user
|
480
|
+
configure_password
|
451
481
|
configure_identity_file
|
452
482
|
configure_gateway
|
453
483
|
configure_session
|
@@ -22,6 +22,7 @@ class Chef
|
|
22
22
|
|
23
23
|
def enforce_path_sanity(env=ENV)
|
24
24
|
if Chef::Config[:enforce_path_sanity]
|
25
|
+
env["PATH"] = "" if env["PATH"].nil?
|
25
26
|
path_separator = Chef::Platform.windows? ? ';' : ':'
|
26
27
|
existing_paths = env["PATH"].split(path_separator)
|
27
28
|
# ensure the Ruby and Gem bindirs are included
|
data/lib/chef/monologger.rb
CHANGED
@@ -48,9 +48,9 @@ class MonoLogger < Logger
|
|
48
48
|
@dev = log
|
49
49
|
else
|
50
50
|
@dev = open_logfile(log)
|
51
|
-
@dev.sync = true
|
52
51
|
@filename = log
|
53
52
|
end
|
53
|
+
@dev.sync = true
|
54
54
|
end
|
55
55
|
|
56
56
|
def write(message)
|
@@ -75,7 +75,6 @@ class MonoLogger < Logger
|
|
75
75
|
|
76
76
|
def create_logfile(filename)
|
77
77
|
logdev = open(filename, (File::WRONLY | File::APPEND | File::CREAT))
|
78
|
-
logdev.sync = true
|
79
78
|
add_log_header(logdev)
|
80
79
|
logdev
|
81
80
|
end
|
data/lib/chef/node.rb
CHANGED
@@ -247,6 +247,13 @@ class Chef
|
|
247
247
|
run_list.include?(recipe_name) || Array(self[:recipes]).include?(recipe_name)
|
248
248
|
end
|
249
249
|
|
250
|
+
# used by include_recipe to add recipes to the expanded run_list to be
|
251
|
+
# saved back to the node and be searchable
|
252
|
+
def loaded_recipe(cookbook, recipe)
|
253
|
+
fully_qualified_recipe = "#{cookbook}::#{recipe}"
|
254
|
+
automatic_attrs[:recipes] << fully_qualified_recipe unless Array(self[:recipes]).include?(fully_qualified_recipe)
|
255
|
+
end
|
256
|
+
|
250
257
|
# Returns true if this Node expects a given role, false if not.
|
251
258
|
def role?(role_name)
|
252
259
|
run_list.include?("role[#{role_name}]")
|
@@ -0,0 +1,49 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Daniel DeLeo (<dan@getchef.com>)
|
3
|
+
# Copyright:: Copyright 2008-2014 Chef Software, 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/policy_builder/expand_node_object'
|
20
|
+
require 'chef/policy_builder/policyfile'
|
21
|
+
|
22
|
+
class Chef
|
23
|
+
|
24
|
+
# PolicyBuilder contains classes that handles fetching policy from server or
|
25
|
+
# disk and resolving any indirection (e.g. expanding run_list).
|
26
|
+
#
|
27
|
+
# INPUTS
|
28
|
+
# * event stream object
|
29
|
+
# * node object/run_list
|
30
|
+
# * json_attribs
|
31
|
+
# * override_runlist
|
32
|
+
#
|
33
|
+
# OUTPUTS
|
34
|
+
# * mutated node object (implicit)
|
35
|
+
# * a new RunStatus (probably doesn't need to be here)
|
36
|
+
# * cookbooks sync'd to disk
|
37
|
+
# * cookbook_hash is stored in run_context
|
38
|
+
module PolicyBuilder
|
39
|
+
|
40
|
+
def self.strategy
|
41
|
+
if Chef::Config[:use_policyfile]
|
42
|
+
Policyfile
|
43
|
+
else
|
44
|
+
ExpandNodeObject
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
+
# Author:: Tim Hinderliter (<tim@opscode.com>)
|
4
|
+
# Author:: Christopher Walters (<cw@opscode.com>)
|
5
|
+
# Author:: Daniel DeLeo (<dan@getchef.com>)
|
6
|
+
# Copyright:: Copyright 2008-2014 Chef Software, 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
|
+
|
22
|
+
require 'chef/log'
|
23
|
+
require 'chef/rest'
|
24
|
+
require 'chef/run_context'
|
25
|
+
require 'chef/config'
|
26
|
+
require 'chef/node'
|
27
|
+
|
28
|
+
class Chef
|
29
|
+
module PolicyBuilder
|
30
|
+
|
31
|
+
# ExpandNodeObject is the "classic" policy builder implementation. It
|
32
|
+
# expands the run_list on a node object and then queries the chef-server
|
33
|
+
# to find the correct set of cookbooks, given version constraints of the
|
34
|
+
# node's environment.
|
35
|
+
class ExpandNodeObject
|
36
|
+
|
37
|
+
attr_reader :events
|
38
|
+
attr_reader :node
|
39
|
+
attr_reader :node_name
|
40
|
+
attr_reader :ohai_data
|
41
|
+
attr_reader :json_attribs
|
42
|
+
attr_reader :override_runlist
|
43
|
+
attr_reader :original_runlist
|
44
|
+
attr_reader :run_context
|
45
|
+
attr_reader :run_list_expansion
|
46
|
+
|
47
|
+
def initialize(node_name, ohai_data, json_attribs, override_runlist, events)
|
48
|
+
@node_name = node_name
|
49
|
+
@ohai_data = ohai_data
|
50
|
+
@json_attribs = json_attribs
|
51
|
+
@override_runlist = override_runlist
|
52
|
+
@events = events
|
53
|
+
|
54
|
+
@node = nil
|
55
|
+
@original_runlist = nil
|
56
|
+
@run_list_expansion = nil
|
57
|
+
end
|
58
|
+
|
59
|
+
def setup_run_context(specific_recipes=nil)
|
60
|
+
if Chef::Config[:solo]
|
61
|
+
Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, Chef::Config[:cookbook_path]) }
|
62
|
+
cl = Chef::CookbookLoader.new(Chef::Config[:cookbook_path])
|
63
|
+
cl.load_cookbooks
|
64
|
+
cookbook_collection = Chef::CookbookCollection.new(cl)
|
65
|
+
run_context = Chef::RunContext.new(node, cookbook_collection, @events)
|
66
|
+
else
|
67
|
+
Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::RemoteFileVendor.new(manifest, api_service) }
|
68
|
+
cookbook_hash = sync_cookbooks
|
69
|
+
cookbook_collection = Chef::CookbookCollection.new(cookbook_hash)
|
70
|
+
run_context = Chef::RunContext.new(node, cookbook_collection, @events)
|
71
|
+
end
|
72
|
+
|
73
|
+
# TODO: this is not the place for this. It should be in Runner or
|
74
|
+
# CookbookCompiler or something.
|
75
|
+
run_context.load(@run_list_expansion)
|
76
|
+
if specific_recipes
|
77
|
+
specific_recipes.each do |recipe_file|
|
78
|
+
run_context.load_recipe_file(recipe_file)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
run_context
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
# In client-server operation, loads the node state from the server. In
|
86
|
+
# chef-solo operation, builds a new node object.
|
87
|
+
def load_node
|
88
|
+
events.node_load_start(node_name, Chef::Config)
|
89
|
+
Chef::Log.debug("Building node object for #{node_name}")
|
90
|
+
|
91
|
+
if Chef::Config[:solo]
|
92
|
+
@node = Chef::Node.build(node_name)
|
93
|
+
else
|
94
|
+
@node = Chef::Node.find_or_create(node_name)
|
95
|
+
end
|
96
|
+
rescue Exception => e
|
97
|
+
# TODO: wrap this exception so useful error info can be given to the
|
98
|
+
# user.
|
99
|
+
events.node_load_failed(node_name, e, Chef::Config)
|
100
|
+
raise
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
# Applies environment, external JSON attributes, and override run list to
|
105
|
+
# the node, Then expands the run_list.
|
106
|
+
#
|
107
|
+
# === Returns
|
108
|
+
# node<Chef::Node>:: The modified node object. node is modified in place.
|
109
|
+
def build_node
|
110
|
+
# Allow user to override the environment of a node by specifying
|
111
|
+
# a config parameter.
|
112
|
+
if Chef::Config[:environment] && !Chef::Config[:environment].chop.empty?
|
113
|
+
node.chef_environment(Chef::Config[:environment])
|
114
|
+
end
|
115
|
+
|
116
|
+
# consume_external_attrs may add items to the run_list. Save the
|
117
|
+
# expanded run_list, which we will pass to the server later to
|
118
|
+
# determine which versions of cookbooks to use.
|
119
|
+
node.reset_defaults_and_overrides
|
120
|
+
node.consume_external_attrs(ohai_data, @json_attribs)
|
121
|
+
|
122
|
+
setup_run_list_override
|
123
|
+
|
124
|
+
expand_run_list
|
125
|
+
|
126
|
+
Chef::Log.info("Run List is [#{node.run_list}]")
|
127
|
+
Chef::Log.info("Run List expands to [#{@expanded_run_list_with_versions.join(', ')}]")
|
128
|
+
|
129
|
+
events.node_load_completed(node, @expanded_run_list_with_versions, Chef::Config)
|
130
|
+
|
131
|
+
node
|
132
|
+
end
|
133
|
+
|
134
|
+
# Expands the node's run list. Stores the run_list_expansion object for later use.
|
135
|
+
def expand_run_list
|
136
|
+
@run_list_expansion = if Chef::Config[:solo]
|
137
|
+
node.expand!('disk')
|
138
|
+
else
|
139
|
+
node.expand!('server')
|
140
|
+
end
|
141
|
+
|
142
|
+
# @run_list_expansion is a RunListExpansion.
|
143
|
+
#
|
144
|
+
# Convert @expanded_run_list, which is an
|
145
|
+
# Array of Hashes of the form
|
146
|
+
# {:name => NAME, :version_constraint => Chef::VersionConstraint },
|
147
|
+
# into @expanded_run_list_with_versions, an
|
148
|
+
# Array of Strings of the form
|
149
|
+
# "#{NAME}@#{VERSION}"
|
150
|
+
@expanded_run_list_with_versions = @run_list_expansion.recipes.with_version_constraints_strings
|
151
|
+
@run_list_expansion
|
152
|
+
rescue Exception => e
|
153
|
+
# TODO: wrap/munge exception with useful error output.
|
154
|
+
events.run_list_expand_failed(node, e)
|
155
|
+
raise
|
156
|
+
end
|
157
|
+
|
158
|
+
########################################
|
159
|
+
# Internal public API
|
160
|
+
########################################
|
161
|
+
|
162
|
+
# Sync_cookbooks eagerly loads all files except files and
|
163
|
+
# templates. It returns the cookbook_hash -- the return result
|
164
|
+
# from /environments/#{node.chef_environment}/cookbook_versions,
|
165
|
+
# which we will use for our run_context.
|
166
|
+
#
|
167
|
+
# === Returns
|
168
|
+
# Hash:: The hash of cookbooks with download URLs as given by the server
|
169
|
+
def sync_cookbooks
|
170
|
+
Chef::Log.debug("Synchronizing cookbooks")
|
171
|
+
|
172
|
+
begin
|
173
|
+
events.cookbook_resolution_start(@expanded_run_list_with_versions)
|
174
|
+
cookbook_hash = api_service.post("environments/#{node.chef_environment}/cookbook_versions",
|
175
|
+
{:run_list => @expanded_run_list_with_versions})
|
176
|
+
rescue Exception => e
|
177
|
+
# TODO: wrap/munge exception to provide helpful error output
|
178
|
+
events.cookbook_resolution_failed(@expanded_run_list_with_versions, e)
|
179
|
+
raise
|
180
|
+
else
|
181
|
+
events.cookbook_resolution_complete(cookbook_hash)
|
182
|
+
end
|
183
|
+
|
184
|
+
synchronizer = Chef::CookbookSynchronizer.new(cookbook_hash, events)
|
185
|
+
synchronizer.sync_cookbooks
|
186
|
+
|
187
|
+
# register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks
|
188
|
+
Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks")
|
189
|
+
|
190
|
+
cookbook_hash
|
191
|
+
end
|
192
|
+
|
193
|
+
def setup_run_list_override
|
194
|
+
runlist_override_sanity_check!
|
195
|
+
unless(override_runlist.empty?)
|
196
|
+
@original_runlist = node.run_list.run_list_items.dup
|
197
|
+
node.run_list(*override_runlist)
|
198
|
+
Chef::Log.warn "Run List override has been provided."
|
199
|
+
Chef::Log.warn "Original Run List: [#{original_runlist.join(', ')}]"
|
200
|
+
Chef::Log.warn "Overridden Run List: [#{node.run_list}]"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# Ensures runlist override contains RunListItem instances
|
205
|
+
def runlist_override_sanity_check!
|
206
|
+
# Convert to array and remove whitespace
|
207
|
+
if override_runlist.is_a?(String)
|
208
|
+
@override_runlist = override_runlist.split(',').map { |e| e.strip }
|
209
|
+
end
|
210
|
+
@override_runlist = [override_runlist].flatten.compact
|
211
|
+
override_runlist.map! do |item|
|
212
|
+
if(item.is_a?(Chef::RunList::RunListItem))
|
213
|
+
item
|
214
|
+
else
|
215
|
+
Chef::RunList::RunListItem.new(item)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def api_service
|
221
|
+
@api_service ||= Chef::REST.new(config[:chef_server_url])
|
222
|
+
end
|
223
|
+
|
224
|
+
def config
|
225
|
+
Chef::Config
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,338 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
+
# Author:: Tim Hinderliter (<tim@opscode.com>)
|
4
|
+
# Author:: Christopher Walters (<cw@opscode.com>)
|
5
|
+
# Author:: Daniel DeLeo (<dan@getchef.com>)
|
6
|
+
# Copyright:: Copyright 2008-2014 Chef Software, 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
|
+
|
22
|
+
require 'chef/log'
|
23
|
+
require 'chef/rest'
|
24
|
+
require 'chef/run_context'
|
25
|
+
require 'chef/config'
|
26
|
+
require 'chef/node'
|
27
|
+
|
28
|
+
class Chef
|
29
|
+
module PolicyBuilder
|
30
|
+
|
31
|
+
# Policyfile is an experimental policy builder implementation that gets run
|
32
|
+
# list and cookbook version information from a single document.
|
33
|
+
#
|
34
|
+
# == WARNING
|
35
|
+
# This implementation is experimental. It may be changed in incompatible
|
36
|
+
# ways in minor or even patch releases, or even abandoned altogether. If
|
37
|
+
# using this with other tools, you may be forced to upgrade those tools in
|
38
|
+
# lockstep with chef-client because of incompatible behavior changes.
|
39
|
+
#
|
40
|
+
# == Unsupported Options:
|
41
|
+
# * override_runlist:: This could potentially be integrated into the
|
42
|
+
# policyfile, or replaced with a similar feature that has different
|
43
|
+
# semantics.
|
44
|
+
# * specific_recipes:: put more design thought into this use case.
|
45
|
+
# * run_list in json_attribs:: would be ignored anyway, so it raises an error.
|
46
|
+
# * chef-solo:: not currently supported. Need more design thought around
|
47
|
+
# how this should work.
|
48
|
+
class Policyfile
|
49
|
+
|
50
|
+
class UnsupportedFeature < StandardError; end
|
51
|
+
|
52
|
+
class PolicyfileError < StandardError; end
|
53
|
+
|
54
|
+
RunListExpansionIsh = Struct.new(:recipes, :roles)
|
55
|
+
|
56
|
+
attr_reader :events
|
57
|
+
attr_reader :node
|
58
|
+
attr_reader :node_name
|
59
|
+
attr_reader :ohai_data
|
60
|
+
attr_reader :json_attribs
|
61
|
+
attr_reader :run_context
|
62
|
+
|
63
|
+
def initialize(node_name, ohai_data, json_attribs, override_runlist, events)
|
64
|
+
@node_name = node_name
|
65
|
+
@ohai_data = ohai_data
|
66
|
+
@json_attribs = json_attribs
|
67
|
+
@events = events
|
68
|
+
|
69
|
+
@node = nil
|
70
|
+
|
71
|
+
Chef::Log.warn("Using experimental Policyfile feature")
|
72
|
+
|
73
|
+
if Chef::Config[:solo]
|
74
|
+
raise UnsupportedFeature, "Policyfile does not support chef-solo at this time."
|
75
|
+
end
|
76
|
+
|
77
|
+
if override_runlist
|
78
|
+
raise UnsupportedFeature, "Policyfile does not support override run lists at this time"
|
79
|
+
end
|
80
|
+
|
81
|
+
if json_attribs && json_attribs.key?("run_list")
|
82
|
+
raise UnsupportedFeature, "Policyfile does not support setting the run_list in json data at this time"
|
83
|
+
end
|
84
|
+
|
85
|
+
if Chef::Config[:environment] && !Chef::Config[:environment].chop.empty?
|
86
|
+
raise UnsupportedFeature, "Policyfile does not work with Chef Environments"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
## API Compat ##
|
91
|
+
# Methods related to unsupported features
|
92
|
+
|
93
|
+
# Override run_list is not supported.
|
94
|
+
def original_runlist
|
95
|
+
nil
|
96
|
+
end
|
97
|
+
|
98
|
+
# Override run_list is not supported.
|
99
|
+
def override_runlist
|
100
|
+
nil
|
101
|
+
end
|
102
|
+
|
103
|
+
# Policyfile gives you the run_list already expanded, but users of this
|
104
|
+
# class may expect to get a run_list expansion compatible object by
|
105
|
+
# calling this method.
|
106
|
+
#
|
107
|
+
# === Returns
|
108
|
+
# RunListExpansionIsh:: A RunListExpansion duck type
|
109
|
+
def run_list_expansion
|
110
|
+
run_list_expansion_ish
|
111
|
+
end
|
112
|
+
|
113
|
+
## PolicyBuilder API ##
|
114
|
+
|
115
|
+
# Loads the node state from the server.
|
116
|
+
def load_node
|
117
|
+
events.node_load_start(node_name, Chef::Config)
|
118
|
+
Chef::Log.debug("Building node object for #{node_name}")
|
119
|
+
|
120
|
+
@node = Chef::Node.find_or_create(node_name)
|
121
|
+
validate_policyfile
|
122
|
+
node
|
123
|
+
rescue Exception => e
|
124
|
+
events.node_load_failed(node_name, e, Chef::Config)
|
125
|
+
raise
|
126
|
+
end
|
127
|
+
|
128
|
+
# Applies environment, external JSON attributes, and override run list to
|
129
|
+
# the node, Then expands the run_list.
|
130
|
+
#
|
131
|
+
# === Returns
|
132
|
+
# node<Chef::Node>:: The modified node object. node is modified in place.
|
133
|
+
def build_node
|
134
|
+
# consume_external_attrs may add items to the run_list. Save the
|
135
|
+
# expanded run_list, which we will pass to the server later to
|
136
|
+
# determine which versions of cookbooks to use.
|
137
|
+
node.reset_defaults_and_overrides
|
138
|
+
|
139
|
+
node.consume_external_attrs(ohai_data, json_attribs)
|
140
|
+
|
141
|
+
expand_run_list
|
142
|
+
apply_policyfile_attributes
|
143
|
+
|
144
|
+
Chef::Log.info("Run List is [#{run_list}]")
|
145
|
+
Chef::Log.info("Run List expands to [#{run_list_with_versions_for_display.join(', ')}]")
|
146
|
+
|
147
|
+
|
148
|
+
events.node_load_completed(node, run_list_with_versions_for_display, Chef::Config)
|
149
|
+
|
150
|
+
node
|
151
|
+
rescue Exception => e
|
152
|
+
events.node_load_failed(node_name, e, Chef::Config)
|
153
|
+
raise
|
154
|
+
end
|
155
|
+
|
156
|
+
def setup_run_context(specific_recipes=nil)
|
157
|
+
# TODO: This file vendor stuff is duplicated and initializing it with a
|
158
|
+
# block traps a reference to this object in a global context which will
|
159
|
+
# prevent it from getting GC'd. Simplify it.
|
160
|
+
Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::RemoteFileVendor.new(manifest, api_service) }
|
161
|
+
sync_cookbooks
|
162
|
+
cookbook_collection = Chef::CookbookCollection.new(cookbooks_to_sync)
|
163
|
+
run_context = Chef::RunContext.new(node, cookbook_collection, events)
|
164
|
+
|
165
|
+
run_context.load(run_list_expansion_ish)
|
166
|
+
|
167
|
+
run_context
|
168
|
+
end
|
169
|
+
|
170
|
+
def expand_run_list
|
171
|
+
node.run_list(run_list)
|
172
|
+
node.automatic_attrs[:roles] = []
|
173
|
+
node.automatic_attrs[:recipes] = run_list_expansion_ish.recipes
|
174
|
+
run_list_expansion_ish
|
175
|
+
end
|
176
|
+
|
177
|
+
## Internal Public API ##
|
178
|
+
|
179
|
+
def sync_cookbooks
|
180
|
+
Chef::Log.debug("Synchronizing cookbooks")
|
181
|
+
synchronizer = Chef::CookbookSynchronizer.new(cookbooks_to_sync, events)
|
182
|
+
synchronizer.sync_cookbooks
|
183
|
+
|
184
|
+
# register the file cache path in the cookbook path so that CookbookLoader actually picks up the synced cookbooks
|
185
|
+
Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks")
|
186
|
+
|
187
|
+
cookbooks_to_sync
|
188
|
+
end
|
189
|
+
|
190
|
+
|
191
|
+
def run_list_with_versions_for_display
|
192
|
+
run_list.map do |recipe_spec|
|
193
|
+
cookbook, recipe = parse_recipe_spec(recipe_spec)
|
194
|
+
lock_data = cookbook_lock_for(cookbook)
|
195
|
+
display = "#{cookbook}::#{recipe}@#{lock_data["version"]} (#{lock_data["identifier"][0...7]})"
|
196
|
+
display
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def run_list_expansion_ish
|
201
|
+
recipes = run_list.map do |recipe_spec|
|
202
|
+
cookbook, recipe = parse_recipe_spec(recipe_spec)
|
203
|
+
"#{cookbook}::#{recipe}"
|
204
|
+
end
|
205
|
+
RunListExpansionIsh.new(recipes, [])
|
206
|
+
end
|
207
|
+
|
208
|
+
def apply_policyfile_attributes
|
209
|
+
node.attributes.role_default = policy["default_attributes"]
|
210
|
+
node.attributes.role_override = policy["override_attributes"]
|
211
|
+
end
|
212
|
+
|
213
|
+
def parse_recipe_spec(recipe_spec)
|
214
|
+
rmatch = recipe_spec.match(/recipe\[([^:]+)::([^:]+)\]/)
|
215
|
+
if rmatch.nil?
|
216
|
+
raise PolicyfileError, "invalid recipe specification #{recipe_spec} in Policyfile from #{policyfile_location}"
|
217
|
+
else
|
218
|
+
[rmatch[1], rmatch[2]]
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def cookbook_lock_for(cookbook_name)
|
223
|
+
cookbook_locks[cookbook_name]
|
224
|
+
end
|
225
|
+
|
226
|
+
def run_list
|
227
|
+
policy["run_list"]
|
228
|
+
end
|
229
|
+
|
230
|
+
def policy
|
231
|
+
@policy ||= http_api.get(policyfile_location)
|
232
|
+
rescue Net::HTTPServerException => e
|
233
|
+
raise ConfigurationError, "Error loading policyfile from `#{policyfile_location}': #{e.class} - #{e.message}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def policyfile_location
|
237
|
+
"data/policyfiles/#{deployment_group}"
|
238
|
+
end
|
239
|
+
|
240
|
+
# Do some mimimal validation of the policyfile we fetched from the
|
241
|
+
# server. Compatibility mode relies on using data bags to store policy
|
242
|
+
# files; therefore no real validation will be performed server-side and
|
243
|
+
# we need to make additional checks to ensure the data will be formatted
|
244
|
+
# correctly.
|
245
|
+
def validate_policyfile
|
246
|
+
errors = []
|
247
|
+
unless run_list
|
248
|
+
errors << "Policyfile is missing run_list element"
|
249
|
+
end
|
250
|
+
unless policy.key?("cookbook_locks")
|
251
|
+
errors << "Policyfile is missing cookbook_locks element"
|
252
|
+
end
|
253
|
+
if run_list.kind_of?(Array)
|
254
|
+
run_list_errors = run_list.select do |maybe_recipe_spec|
|
255
|
+
validate_recipe_spec(maybe_recipe_spec)
|
256
|
+
end
|
257
|
+
errors += run_list_errors
|
258
|
+
else
|
259
|
+
errors << "Policyfile run_list is malformed, must be an array of `recipe[cb_name::recipe_name]` items: #{policy["run_list"]}"
|
260
|
+
end
|
261
|
+
|
262
|
+
unless errors.empty?
|
263
|
+
raise PolicyfileError, "Policyfile fetched from #{policyfile_location} was invalid:\n#{errors.join("\n")}"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def validate_recipe_spec(recipe_spec)
|
268
|
+
parse_recipe_spec(recipe_spec)
|
269
|
+
nil
|
270
|
+
rescue PolicyfileError => e
|
271
|
+
e.message
|
272
|
+
end
|
273
|
+
|
274
|
+
class ConfigurationError < StandardError; end
|
275
|
+
|
276
|
+
def deployment_group
|
277
|
+
Chef::Config[:deployment_group] or
|
278
|
+
raise ConfigurationError, "Setting `deployment_group` is not configured."
|
279
|
+
end
|
280
|
+
|
281
|
+
# Builds a 'cookbook_hash' map of the form
|
282
|
+
# "COOKBOOK_NAME" => "IDENTIFIER"
|
283
|
+
#
|
284
|
+
# This can be passed to a Chef::CookbookSynchronizer object to
|
285
|
+
# synchronize the cookbooks.
|
286
|
+
#
|
287
|
+
# TODO: Currently this makes N API calls to the server to get the
|
288
|
+
# cookbook objects. With server support (bulk API or the like), this
|
289
|
+
# should be reduced to a single call.
|
290
|
+
def cookbooks_to_sync
|
291
|
+
@cookbook_to_sync ||= begin
|
292
|
+
events.cookbook_resolution_start(run_list_with_versions_for_display)
|
293
|
+
|
294
|
+
cookbook_versions_by_name = cookbook_locks.inject({}) do |cb_map, (name, lock_data)|
|
295
|
+
cb_map[name] = manifest_for(name, lock_data)
|
296
|
+
cb_map
|
297
|
+
end
|
298
|
+
events.cookbook_resolution_complete(cookbook_versions_by_name)
|
299
|
+
|
300
|
+
cookbook_versions_by_name
|
301
|
+
end
|
302
|
+
rescue Exception => e
|
303
|
+
# TODO: wrap/munge exception to provide helpful error output
|
304
|
+
events.cookbook_resolution_failed(run_list_with_versions_for_display, e)
|
305
|
+
raise
|
306
|
+
end
|
307
|
+
|
308
|
+
# Fetches the CookbookVersion object for the given name and identifer
|
309
|
+
# specified in the lock_data.
|
310
|
+
# TODO: This only implements Chef 11 compatibility mode, which means that
|
311
|
+
# cookbooks are fetched by the "dotted_decimal_identifier": a
|
312
|
+
# representation of a SHA1 in the traditional x.y.z version format.
|
313
|
+
def manifest_for(cookbook_name, lock_data)
|
314
|
+
xyz_version = lock_data["dotted_decimal_identifier"]
|
315
|
+
http_api.get("cookbooks/#{cookbook_name}/#{xyz_version}")
|
316
|
+
rescue Exception => e
|
317
|
+
message = "Error loading cookbook #{cookbook_name} at version #{xyz_version}: #{e.class} - #{e.message}"
|
318
|
+
err = Chef::Exceptions::CookbookNotFound.new(message)
|
319
|
+
err.set_backtrace(e.backtrace)
|
320
|
+
raise err
|
321
|
+
end
|
322
|
+
|
323
|
+
def cookbook_locks
|
324
|
+
policy["cookbook_locks"]
|
325
|
+
end
|
326
|
+
|
327
|
+
def http_api
|
328
|
+
@api_service ||= Chef::REST.new(config[:chef_server_url])
|
329
|
+
end
|
330
|
+
|
331
|
+
def config
|
332
|
+
Chef::Config
|
333
|
+
end
|
334
|
+
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|