chef 11.10.4 → 11.12.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +6 -6
- data/README.md +1 -1
- data/lib/chef/api_client.rb +1 -3
- data/lib/chef/application.rb +2 -1
- data/lib/chef/application/client.rb +11 -1
- data/lib/chef/client.rb +24 -9
- data/lib/chef/cookbook/syntax_check.rb +107 -6
- data/lib/chef/dsl/reboot_pending.rb +61 -0
- data/lib/chef/exceptions.rb +12 -1
- data/lib/chef/formatters/error_descriptor.rb +1 -1
- data/lib/chef/http/remote_request_id.rb +46 -0
- data/lib/chef/knife/bootstrap.rb +1 -1
- data/lib/chef/knife/bootstrap/README.md +12 -0
- data/lib/chef/knife/bootstrap/chef-full.erb +3 -0
- data/lib/chef/knife/client_create.rb +6 -0
- data/lib/chef/knife/client_delete.rb +15 -1
- data/lib/chef/knife/raw.rb +1 -0
- data/lib/chef/node.rb +1 -1
- data/lib/chef/node/attribute_collections.rb +8 -1
- data/lib/chef/node/immutable_collections.rb +8 -1
- data/lib/chef/provider/deploy.rb +1 -1
- data/lib/chef/provider/group.rb +1 -1
- data/lib/chef/provider/ifconfig/debian.rb +19 -8
- data/lib/chef/provider/ohai.rb +6 -5
- data/lib/chef/provider/service/macosx.rb +68 -14
- data/lib/chef/recipe.rb +2 -0
- data/lib/chef/request_id.rb +37 -0
- data/lib/chef/resource.rb +2 -0
- data/lib/chef/resource_reporter.rb +7 -4
- data/lib/chef/rest.rb +5 -1
- data/lib/chef/run_status.rb +4 -1
- data/lib/chef/server_api.rb +3 -1
- data/lib/chef/version.rb +2 -2
- data/spec/functional/dsl/reboot_pending_spec.rb +118 -0
- data/spec/functional/resource/base.rb +1 -3
- data/spec/functional/resource/deploy_revision_spec.rb +192 -1
- data/spec/functional/resource/git_spec.rb +1 -1
- data/spec/functional/resource/ohai_spec.rb +65 -0
- data/spec/functional/resource/registry_spec.rb +4 -5
- data/spec/integration/client/client_spec.rb +14 -0
- data/spec/spec_helper.rb +1 -2
- data/spec/support/shared/functional/windows_script.rb +1 -2
- data/spec/unit/api_client_spec.rb +46 -0
- data/spec/unit/client_spec.rb +345 -229
- data/spec/unit/cookbook/syntax_check_spec.rb +0 -1
- data/spec/unit/dsl/reboot_pending_spec.rb +100 -0
- data/spec/unit/knife/client_create_spec.rb +29 -1
- data/spec/unit/knife/client_delete_spec.rb +44 -1
- data/spec/unit/knife_spec.rb +55 -0
- data/spec/unit/node/attribute_spec.rb +7 -0
- data/spec/unit/node/immutable_collections_spec.rb +5 -1
- data/spec/unit/provider/group_spec.rb +5 -0
- data/spec/unit/provider/ifconfig/debian_spec.rb +251 -24
- data/spec/unit/provider/ohai_spec.rb +2 -3
- data/spec/unit/provider/service/macosx_spec.rb +29 -11
- data/spec/unit/resource_reporter_spec.rb +1 -1
- data/spec/unit/rest_spec.rb +38 -13
- metadata +151 -194
@@ -0,0 +1,65 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Serdar Sutay (<serdar@opscode.com>)
|
3
|
+
# Copyright:: Copyright (c) 2014 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 'spec_helper'
|
20
|
+
|
21
|
+
describe Chef::Resource::Ohai do
|
22
|
+
let(:ohai) {
|
23
|
+
o = Ohai::System.new
|
24
|
+
o.all_plugins
|
25
|
+
o
|
26
|
+
}
|
27
|
+
|
28
|
+
let(:node) { Chef::Node.new }
|
29
|
+
|
30
|
+
let(:run_context) {
|
31
|
+
node.default[:platform] = ohai[:platform]
|
32
|
+
node.default[:platform_version] = ohai[:platform_version]
|
33
|
+
events = Chef::EventDispatch::Dispatcher.new
|
34
|
+
Chef::RunContext.new(node, {}, events)
|
35
|
+
}
|
36
|
+
|
37
|
+
shared_examples_for "reloaded :uptime" do
|
38
|
+
it "should reload :uptime" do
|
39
|
+
initial_uptime = ohai[:uptime]
|
40
|
+
|
41
|
+
# Sleep for a second so the uptime gets updated.
|
42
|
+
sleep 1
|
43
|
+
|
44
|
+
ohai_resource.run_action(:reload)
|
45
|
+
node[:uptime].should_not == initial_uptime
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "when reloading all plugins" do
|
50
|
+
let(:ohai_resource) { Chef::Resource::Ohai.new("reload all", run_context)}
|
51
|
+
|
52
|
+
it_behaves_like "reloaded :uptime"
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "when reloading only uptime" do
|
56
|
+
let(:ohai_resource) {
|
57
|
+
r = Chef::Resource::Ohai.new("reload all", run_context)
|
58
|
+
r.plugin("uptime")
|
59
|
+
r
|
60
|
+
}
|
61
|
+
|
62
|
+
|
63
|
+
it_behaves_like "reloaded :uptime"
|
64
|
+
end
|
65
|
+
end
|
@@ -118,10 +118,10 @@ describe Chef::Resource::RegistryKey, :windows_only do
|
|
118
118
|
|
119
119
|
@resource_reporter = Chef::ResourceReporter.new(@rest_client)
|
120
120
|
@events.register(@resource_reporter)
|
121
|
-
@run_id = @resource_reporter.run_id
|
122
121
|
@run_status = Chef::RunStatus.new(@node, @events)
|
123
|
-
|
124
122
|
@resource_reporter.run_started(@run_status)
|
123
|
+
@run_id = @resource_reporter.run_id
|
124
|
+
|
125
125
|
|
126
126
|
@new_resource.cookbook_name = "monkey"
|
127
127
|
@cookbook_version = double("Cookbook::Version", :version => "1.2.3")
|
@@ -265,7 +265,7 @@ describe Chef::Resource::RegistryKey, :windows_only do
|
|
265
265
|
@new_resource.key(reg_child + '\Slitheen\Raxicoricofallapatorius')
|
266
266
|
@new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
|
267
267
|
@new_resource.recursive(false)
|
268
|
-
|
268
|
+
@new_resource.run_action(:create) # should not raise_error
|
269
269
|
@registry.key_exists?(reg_child + '\Slitheen').should == false
|
270
270
|
@registry.key_exists?(reg_child + '\Slitheen\Raxicoricofallapatorius').should == false
|
271
271
|
end
|
@@ -376,7 +376,7 @@ describe Chef::Resource::RegistryKey, :windows_only do
|
|
376
376
|
@new_resource.key(reg_child + '\Zygons\Zygor')
|
377
377
|
@new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
|
378
378
|
@new_resource.recursive(false)
|
379
|
-
|
379
|
+
@new_resource.run_action(:create_if_missing) # should not raise_error
|
380
380
|
@registry.key_exists?(reg_child + '\Zygons').should == false
|
381
381
|
@registry.key_exists?(reg_child + '\Zygons\Zygor').should == false
|
382
382
|
end
|
@@ -547,7 +547,6 @@ describe Chef::Resource::RegistryKey, :windows_only do
|
|
547
547
|
@new_resource.values([{:name=>"BriskWalk",:type=>:string,:data=>"is good for health"}])
|
548
548
|
@new_resource.recursive(false)
|
549
549
|
@new_resource.run_action(:delete_key)
|
550
|
-
@new_resource.should_not raise_error
|
551
550
|
end
|
552
551
|
it "does nothing if the action is delete_key" do
|
553
552
|
@new_resource.key(reg_parent + '\OpscodeWhyRun')
|
@@ -215,5 +215,19 @@ EOM
|
|
215
215
|
result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default' -z", :cwd => chef_dir)
|
216
216
|
result.error!
|
217
217
|
end
|
218
|
+
|
219
|
+
it "should complete with success when setting the run list with -r" do
|
220
|
+
file 'config/client.rb', <<EOM
|
221
|
+
chef_server_url 'http://omg.com/blah'
|
222
|
+
cookbook_path "#{path_to('cookbooks')}"
|
223
|
+
EOM
|
224
|
+
|
225
|
+
result = shell_out("#{chef_client} -c \"#{path_to('config/client.rb')}\" -r 'x::default' -z", :cwd => chef_dir)
|
226
|
+
result.stdout.should_not include("Overridden Run List")
|
227
|
+
result.stdout.should include("Run List is [recipe[x::default]]")
|
228
|
+
#puts result.stdout
|
229
|
+
result.error!
|
230
|
+
end
|
231
|
+
|
218
232
|
end
|
219
233
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -87,8 +87,7 @@ Dir["spec/support/**/*.rb"].
|
|
87
87
|
each { |f| require f }
|
88
88
|
|
89
89
|
OHAI_SYSTEM = Ohai::System.new
|
90
|
-
OHAI_SYSTEM.
|
91
|
-
OHAI_SYSTEM.require_plugin("platform")
|
90
|
+
OHAI_SYSTEM.all_plugins("platform")
|
92
91
|
TEST_PLATFORM = OHAI_SYSTEM["platform"].dup.freeze
|
93
92
|
TEST_PLATFORM_VERSION = OHAI_SYSTEM["platform_version"].dup.freeze
|
94
93
|
|
@@ -23,8 +23,7 @@ shared_context Chef::Resource::WindowsScript do
|
|
23
23
|
before(:all) do
|
24
24
|
|
25
25
|
ohai_reader = Ohai::System.new
|
26
|
-
ohai_reader.
|
27
|
-
ohai_reader.require_plugin("windows::platform")
|
26
|
+
ohai_reader.all_plugins("platform")
|
28
27
|
|
29
28
|
new_node = Chef::Node.new
|
30
29
|
new_node.consume_external_attrs(ohai_reader.data,{})
|
@@ -164,6 +164,52 @@ describe Chef::ApiClient do
|
|
164
164
|
|
165
165
|
end
|
166
166
|
|
167
|
+
describe "when loading from JSON" do
|
168
|
+
before do
|
169
|
+
end
|
170
|
+
|
171
|
+
before(:each) do
|
172
|
+
client = {
|
173
|
+
"name" => "black",
|
174
|
+
"clientname" => "black",
|
175
|
+
"public_key" => "crowes",
|
176
|
+
"private_key" => "monkeypants",
|
177
|
+
"admin" => true,
|
178
|
+
"validator" => true,
|
179
|
+
"json_class" => "Chef::ApiClient"
|
180
|
+
}
|
181
|
+
@http_client = double("Chef::REST mock")
|
182
|
+
Chef::REST.stub(:new).and_return(@http_client)
|
183
|
+
@http_client.should_receive(:get).with("clients/black").and_return(client)
|
184
|
+
@client = Chef::ApiClient.load(client['name'])
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should deserialize to a Chef::ApiClient object" do
|
188
|
+
@client.should be_a_kind_of(Chef::ApiClient)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "preserves the name" do
|
192
|
+
@client.name.should == "black"
|
193
|
+
end
|
194
|
+
|
195
|
+
it "preserves the public key" do
|
196
|
+
@client.public_key.should == "crowes"
|
197
|
+
end
|
198
|
+
|
199
|
+
it "preserves the admin status" do
|
200
|
+
@client.admin.should be_a_kind_of(Chef::TrueClass)
|
201
|
+
end
|
202
|
+
|
203
|
+
it "preserves the 'validator' status" do
|
204
|
+
@client.validator.should be_a_kind_of(Chef::TrueClass)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "includes the private key if present" do
|
208
|
+
@client.private_key.should == "monkeypants"
|
209
|
+
end
|
210
|
+
|
211
|
+
end
|
212
|
+
|
167
213
|
describe "with correctly configured API credentials" do
|
168
214
|
before do
|
169
215
|
Chef::Config[:node_name] = "silent-bob"
|
data/spec/unit/client_spec.rb
CHANGED
@@ -24,29 +24,54 @@ require 'chef/run_context'
|
|
24
24
|
require 'chef/rest'
|
25
25
|
require 'rbconfig'
|
26
26
|
|
27
|
-
|
27
|
+
describe Chef::Client do
|
28
|
+
|
29
|
+
let(:hostname) { "hostname" }
|
30
|
+
let(:machinename) { "machinename.example.org" }
|
31
|
+
let(:fqdn) { "hostname.example.org" }
|
32
|
+
|
33
|
+
let(:ohai_data) do
|
34
|
+
{ :fqdn => fqdn,
|
35
|
+
:hostname => hostname,
|
36
|
+
:machinename => machinename,
|
37
|
+
:platform => 'example-platform',
|
38
|
+
:platform_version => 'example-platform-1.0',
|
39
|
+
:data => {}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
let(:ohai_system) do
|
44
|
+
ohai_system = double( "Ohai::System",
|
45
|
+
:all_plugins => true,
|
46
|
+
:data => ohai_data)
|
47
|
+
ohai_system.stub(:[]) do |key|
|
48
|
+
ohai_data[key]
|
49
|
+
end
|
50
|
+
ohai_system
|
51
|
+
end
|
52
|
+
|
53
|
+
let(:node) do
|
54
|
+
Chef::Node.new.tap do |n|
|
55
|
+
n.name(fqdn)
|
56
|
+
n.chef_environment("_default")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
let(:json_attribs) { nil }
|
61
|
+
let(:client_opts) { {} }
|
62
|
+
|
63
|
+
let(:client) do
|
64
|
+
Chef::Client.new(json_attribs, client_opts).tap do |c|
|
65
|
+
c.node = node
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
28
69
|
before do
|
29
70
|
Chef::Log.logger = Logger.new(StringIO.new)
|
30
71
|
|
31
72
|
# Node/Ohai data
|
32
|
-
|
33
|
-
|
34
|
-
Chef::Config[:node_name] = @fqdn
|
35
|
-
ohai_data = { :fqdn => @fqdn,
|
36
|
-
:hostname => @hostname,
|
37
|
-
:platform => 'example-platform',
|
38
|
-
:platform_version => 'example-platform-1.0',
|
39
|
-
:data => {} }
|
40
|
-
ohai_data.stub(:all_plugins).and_return(true)
|
41
|
-
ohai_data.stub(:data).and_return(ohai_data)
|
42
|
-
Ohai::System.stub(:new).and_return(ohai_data)
|
43
|
-
|
44
|
-
@node = Chef::Node.new
|
45
|
-
@node.name(@fqdn)
|
46
|
-
@node.chef_environment("_default")
|
47
|
-
|
48
|
-
@client = Chef::Client.new
|
49
|
-
@client.node = @node
|
73
|
+
#Chef::Config[:node_name] = fqdn
|
74
|
+
Ohai::System.stub(:new).and_return(ohai_system)
|
50
75
|
end
|
51
76
|
|
52
77
|
describe "authentication protocol selection" do
|
@@ -58,7 +83,7 @@ shared_examples_for Chef::Client do
|
|
58
83
|
it "does not force the authentication protocol to 1.1" do
|
59
84
|
Chef::Config[:node_name] = ("f" * 90)
|
60
85
|
# ugly that this happens as a side effect of a getter :(
|
61
|
-
|
86
|
+
client.node_name
|
62
87
|
Chef::Config[:authentication_protocol_version].should == "1.0"
|
63
88
|
end
|
64
89
|
end
|
@@ -67,7 +92,7 @@ shared_examples_for Chef::Client do
|
|
67
92
|
it "sets the authentication protocol to version 1.1" do
|
68
93
|
Chef::Config[:node_name] = ("f" * 91)
|
69
94
|
# ugly that this happens as a side effect of a getter :(
|
70
|
-
|
95
|
+
client.node_name
|
71
96
|
Chef::Config[:authentication_protocol_version].should == "1.1"
|
72
97
|
end
|
73
98
|
end
|
@@ -75,9 +100,6 @@ shared_examples_for Chef::Client do
|
|
75
100
|
|
76
101
|
describe "configuring output formatters" do
|
77
102
|
context "when no formatter has been configured" do
|
78
|
-
before do
|
79
|
-
@client = Chef::Client.new
|
80
|
-
end
|
81
103
|
|
82
104
|
context "and STDOUT is a TTY" do
|
83
105
|
before do
|
@@ -85,7 +107,7 @@ shared_examples_for Chef::Client do
|
|
85
107
|
end
|
86
108
|
|
87
109
|
it "configures the :doc formatter" do
|
88
|
-
|
110
|
+
client.formatters_for_run.should == [[:doc]]
|
89
111
|
end
|
90
112
|
|
91
113
|
context "and force_logger is set" do
|
@@ -95,7 +117,7 @@ shared_examples_for Chef::Client do
|
|
95
117
|
|
96
118
|
it "configures the :null formatter" do
|
97
119
|
Chef::Config[:force_logger].should be_true
|
98
|
-
|
120
|
+
client.formatters_for_run.should == [[:null]]
|
99
121
|
end
|
100
122
|
|
101
123
|
end
|
@@ -108,7 +130,7 @@ shared_examples_for Chef::Client do
|
|
108
130
|
end
|
109
131
|
|
110
132
|
it "configures the :null formatter" do
|
111
|
-
|
133
|
+
client.formatters_for_run.should == [[:null]]
|
112
134
|
end
|
113
135
|
|
114
136
|
context "and force_formatter is set" do
|
@@ -116,7 +138,7 @@ shared_examples_for Chef::Client do
|
|
116
138
|
Chef::Config[:force_formatter] = true
|
117
139
|
end
|
118
140
|
it "it configures the :doc formatter" do
|
119
|
-
|
141
|
+
client.formatters_for_run.should == [[:doc]]
|
120
142
|
end
|
121
143
|
end
|
122
144
|
end
|
@@ -126,16 +148,15 @@ shared_examples_for Chef::Client do
|
|
126
148
|
context "when a formatter is configured" do
|
127
149
|
context "with no output path" do
|
128
150
|
before do
|
129
|
-
@client = Chef::Client.new
|
130
151
|
Chef::Config.add_formatter(:min)
|
131
152
|
end
|
132
153
|
|
133
154
|
it "does not configure a default formatter" do
|
134
|
-
|
155
|
+
client.formatters_for_run.should == [[:min, nil]]
|
135
156
|
end
|
136
157
|
|
137
158
|
it "configures the formatter for STDOUT/STDERR" do
|
138
|
-
configured_formatters =
|
159
|
+
configured_formatters = client.configure_formatters
|
139
160
|
min_formatter = configured_formatters[0]
|
140
161
|
min_formatter.output.out.should == STDOUT
|
141
162
|
min_formatter.output.err.should == STDERR
|
@@ -144,7 +165,6 @@ shared_examples_for Chef::Client do
|
|
144
165
|
|
145
166
|
context "with an output path" do
|
146
167
|
before do
|
147
|
-
@client = Chef::Client.new
|
148
168
|
@tmpout = Tempfile.open("rspec-for-client-formatter-selection-#{Process.pid}")
|
149
169
|
Chef::Config.add_formatter(:min, @tmpout.path)
|
150
170
|
end
|
@@ -155,7 +175,7 @@ shared_examples_for Chef::Client do
|
|
155
175
|
end
|
156
176
|
|
157
177
|
it "configures the formatter for the file path" do
|
158
|
-
configured_formatters =
|
178
|
+
configured_formatters = client.configure_formatters
|
159
179
|
min_formatter = configured_formatters[0]
|
160
180
|
min_formatter.output.out.path.should == @tmpout.path
|
161
181
|
min_formatter.output.err.path.should == @tmpout.path
|
@@ -165,93 +185,216 @@ shared_examples_for Chef::Client do
|
|
165
185
|
end
|
166
186
|
end
|
167
187
|
|
168
|
-
describe "run" do
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
mock_chef_runner = double("Chef::Runner")
|
175
|
-
|
176
|
-
# --Client.register
|
177
|
-
# Make sure Client#register thinks the client key doesn't
|
178
|
-
# exist, so it tries to register and create one.
|
179
|
-
File.should_receive(:exists?).with(Chef::Config[:client_key]).exactly(1).times.and_return(false)
|
180
|
-
|
181
|
-
# Client.register will register with the validation client name.
|
182
|
-
Chef::ApiClient::Registration.any_instance.should_receive(:run)
|
183
|
-
# Client.register will then turn around create another
|
184
|
-
# Chef::REST object, this time with the client key it got from the
|
185
|
-
# previous step.
|
186
|
-
Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url], @fqdn, Chef::Config[:client_key]).exactly(1).and_return(mock_chef_rest_for_node)
|
187
|
-
|
188
|
-
# --Client#build_node
|
189
|
-
# looks up the node, which we will return, then later saves it.
|
190
|
-
Chef::Node.should_receive(:find_or_create).with(@fqdn).and_return(@node)
|
191
|
-
|
192
|
-
# --ResourceReporter#node_load_completed
|
193
|
-
# gets a run id from the server for storing resource history
|
194
|
-
# (has its own tests, so stubbing it here.)
|
195
|
-
Chef::ResourceReporter.any_instance.should_receive(:node_load_completed)
|
196
|
-
|
197
|
-
# --ResourceReporter#run_completed
|
198
|
-
# updates the server with the resource history
|
199
|
-
# (has its own tests, so stubbing it here.)
|
200
|
-
Chef::ResourceReporter.any_instance.should_receive(:run_completed)
|
201
|
-
# --Client#setup_run_context
|
202
|
-
# ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
|
203
|
-
#
|
204
|
-
Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
|
205
|
-
Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(mock_chef_rest_for_cookbook_sync)
|
206
|
-
mock_chef_rest_for_cookbook_sync.should_receive(:post).with("environments/_default/cookbook_versions", {:run_list => []}).and_return({})
|
207
|
-
|
208
|
-
# --Client#converge
|
209
|
-
Chef::Runner.should_receive(:new).and_return(mock_chef_runner)
|
210
|
-
mock_chef_runner.should_receive(:converge).and_return(true)
|
211
|
-
|
212
|
-
# --Client#save_updated_node
|
213
|
-
Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(mock_chef_rest_for_node_save)
|
214
|
-
mock_chef_rest_for_node_save.should_receive(:put_rest).with("nodes/#{@fqdn}", @node).and_return(true)
|
215
|
-
|
216
|
-
Chef::RunLock.any_instance.should_receive(:acquire)
|
217
|
-
Chef::RunLock.any_instance.should_receive(:save_pid)
|
218
|
-
Chef::RunLock.any_instance.should_receive(:release)
|
219
|
-
|
220
|
-
# Post conditions: check that node has been filled in correctly
|
221
|
-
@client.should_receive(:run_started)
|
222
|
-
@client.should_receive(:run_completed_successfully)
|
188
|
+
describe "a full client run" do
|
189
|
+
shared_examples_for "a successful client run" do
|
190
|
+
let(:http_node_load) { double("Chef::REST (node)") }
|
191
|
+
let(:http_cookbook_sync) { double("Chef::REST (cookbook sync)") }
|
192
|
+
let(:http_node_save) { double("Chef::REST (node save)") }
|
193
|
+
let(:runner) { double("Chef::Runner") }
|
223
194
|
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
195
|
+
let(:api_client_exists?) { false }
|
196
|
+
|
197
|
+
let(:stdout) { StringIO.new }
|
198
|
+
let(:stderr) { StringIO.new }
|
199
|
+
|
200
|
+
let(:enable_fork) { false }
|
201
|
+
|
202
|
+
def stub_for_register
|
203
|
+
# --Client.register
|
204
|
+
# Make sure Client#register thinks the client key doesn't
|
205
|
+
# exist, so it tries to register and create one.
|
206
|
+
File.should_receive(:exists?).with(Chef::Config[:client_key]).exactly(1).times.and_return(api_client_exists?)
|
207
|
+
|
208
|
+
unless api_client_exists?
|
209
|
+
# Client.register will register with the validation client name.
|
210
|
+
Chef::ApiClient::Registration.any_instance.should_receive(:run)
|
236
211
|
end
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
212
|
+
end
|
213
|
+
|
214
|
+
def stub_for_node_load
|
215
|
+
# Client.register will then turn around create another
|
216
|
+
# Chef::REST object, this time with the client key it got from the
|
217
|
+
# previous step.
|
218
|
+
Chef::REST.should_receive(:new).
|
219
|
+
with(Chef::Config[:chef_server_url], fqdn, Chef::Config[:client_key]).
|
220
|
+
exactly(1).
|
221
|
+
and_return(http_node_load)
|
222
|
+
|
223
|
+
# --Client#build_node
|
224
|
+
# looks up the node, which we will return, then later saves it.
|
225
|
+
Chef::Node.should_receive(:find_or_create).with(fqdn).and_return(node)
|
226
|
+
|
227
|
+
# --ResourceReporter#node_load_completed
|
228
|
+
# gets a run id from the server for storing resource history
|
229
|
+
# (has its own tests, so stubbing it here.)
|
230
|
+
Chef::ResourceReporter.any_instance.should_receive(:node_load_completed)
|
231
|
+
end
|
232
|
+
|
233
|
+
def stub_for_sync_cookbooks
|
234
|
+
# --Client#setup_run_context
|
235
|
+
# ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
|
236
|
+
#
|
237
|
+
Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
|
238
|
+
Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
|
239
|
+
http_cookbook_sync.should_receive(:post).
|
240
|
+
with("environments/_default/cookbook_versions", {:run_list => []}).
|
241
|
+
and_return({})
|
242
|
+
end
|
243
|
+
|
244
|
+
def stub_for_converge
|
245
|
+
# --Client#converge
|
246
|
+
Chef::Runner.should_receive(:new).and_return(runner)
|
247
|
+
runner.should_receive(:converge).and_return(true)
|
248
|
+
|
249
|
+
# --ResourceReporter#run_completed
|
250
|
+
# updates the server with the resource history
|
251
|
+
# (has its own tests, so stubbing it here.)
|
252
|
+
Chef::ResourceReporter.any_instance.should_receive(:run_completed)
|
253
|
+
end
|
254
|
+
|
255
|
+
def stub_for_node_save
|
256
|
+
# --Client#save_updated_node
|
257
|
+
Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_node_save)
|
258
|
+
http_node_save.should_receive(:put_rest).with("nodes/#{fqdn}", node).and_return(true)
|
259
|
+
end
|
260
|
+
|
261
|
+
def stub_for_run
|
262
|
+
Chef::RunLock.any_instance.should_receive(:acquire)
|
263
|
+
Chef::RunLock.any_instance.should_receive(:save_pid)
|
264
|
+
Chef::RunLock.any_instance.should_receive(:release)
|
265
|
+
|
266
|
+
# Post conditions: check that node has been filled in correctly
|
267
|
+
client.should_receive(:run_started)
|
268
|
+
client.should_receive(:run_completed_successfully)
|
269
|
+
end
|
270
|
+
|
271
|
+
before do
|
272
|
+
Chef::Config[:client_fork] = enable_fork
|
273
|
+
|
274
|
+
stub_const("Chef::Client::STDOUT_FD", stdout)
|
275
|
+
stub_const("Chef::Client::STDERR_FD", stderr)
|
276
|
+
|
277
|
+
stub_for_register
|
278
|
+
stub_for_node_load
|
279
|
+
stub_for_sync_cookbooks
|
280
|
+
stub_for_converge
|
281
|
+
stub_for_node_save
|
282
|
+
stub_for_run
|
283
|
+
end
|
284
|
+
|
285
|
+
it "runs ohai, sets up authentication, loads node state, synchronizes policy, and converges" do
|
286
|
+
# This is what we're testing.
|
287
|
+
client.run
|
288
|
+
|
289
|
+
# fork is stubbed, so we can see the outcome of the run
|
290
|
+
node.automatic_attrs[:platform].should == "example-platform"
|
291
|
+
node.automatic_attrs[:platform_version].should == "example-platform-1.0"
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
describe "when running chef-client without fork" do
|
297
|
+
|
298
|
+
include_examples "a successful client run"
|
299
|
+
end
|
300
|
+
|
301
|
+
describe "when running chef-client with forking enabled", :unix_only do
|
302
|
+
include_examples "a successful client run" do
|
303
|
+
let(:process_status) do
|
304
|
+
double("Process::Status")
|
305
|
+
end
|
306
|
+
|
307
|
+
let(:enable_fork) { true }
|
308
|
+
|
309
|
+
before do
|
310
|
+
Process.should_receive(:waitpid2).and_return([1, process_status])
|
311
|
+
|
312
|
+
process_status.should_receive(:success?).and_return(true)
|
313
|
+
client.should_receive(:exit).and_return(nil)
|
314
|
+
client.should_receive(:fork).and_yield
|
243
315
|
end
|
244
316
|
end
|
245
317
|
|
246
|
-
|
247
|
-
|
318
|
+
end
|
319
|
+
|
320
|
+
describe "when the client key already exists" do
|
321
|
+
|
322
|
+
let(:api_client_exists?) { true }
|
323
|
+
|
324
|
+
include_examples "a successful client run"
|
325
|
+
end
|
326
|
+
|
327
|
+
describe "when an override run list is given" do
|
328
|
+
let(:client_opts) { {:override_runlist => "recipe[override_recipe]"} }
|
248
329
|
|
249
|
-
|
250
|
-
|
251
|
-
|
330
|
+
it "should permit spaces in overriding run list" do
|
331
|
+
Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
|
332
|
+
end
|
333
|
+
|
334
|
+
describe "when running the client" do
|
335
|
+
include_examples "a successful client run" do
|
336
|
+
|
337
|
+
before do
|
338
|
+
# Client will try to compile and run override_recipe
|
339
|
+
Chef::RunContext::CookbookCompiler.any_instance.should_receive(:compile)
|
340
|
+
end
|
341
|
+
|
342
|
+
def stub_for_sync_cookbooks
|
343
|
+
# --Client#setup_run_context
|
344
|
+
# ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
|
345
|
+
#
|
346
|
+
Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
|
347
|
+
Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
|
348
|
+
http_cookbook_sync.should_receive(:post).
|
349
|
+
with("environments/_default/cookbook_versions", {:run_list => ["override_recipe"]}).
|
350
|
+
and_return({})
|
351
|
+
end
|
352
|
+
|
353
|
+
def stub_for_node_save
|
354
|
+
# Expect NO node save
|
355
|
+
node.should_not_receive(:save)
|
356
|
+
end
|
357
|
+
end
|
252
358
|
end
|
253
359
|
end
|
254
360
|
|
361
|
+
describe "when a permanent run list is passed as an option" do
|
362
|
+
|
363
|
+
include_examples "a successful client run" do
|
364
|
+
|
365
|
+
let(:new_runlist) { "recipe[new_run_list_recipe]" }
|
366
|
+
let(:client_opts) { {:runlist => new_runlist} }
|
367
|
+
|
368
|
+
def stub_for_sync_cookbooks
|
369
|
+
# --Client#setup_run_context
|
370
|
+
# ---Client#sync_cookbooks -- downloads the list of cookbooks to sync
|
371
|
+
#
|
372
|
+
Chef::CookbookSynchronizer.any_instance.should_receive(:sync_cookbooks)
|
373
|
+
Chef::REST.should_receive(:new).with(Chef::Config[:chef_server_url]).and_return(http_cookbook_sync)
|
374
|
+
http_cookbook_sync.should_receive(:post).
|
375
|
+
with("environments/_default/cookbook_versions", {:run_list => ["new_run_list_recipe"]}).
|
376
|
+
and_return({})
|
377
|
+
end
|
378
|
+
|
379
|
+
before do
|
380
|
+
# Client will try to compile and run the new_run_list_recipe, but we
|
381
|
+
# do not create a fixture for this.
|
382
|
+
Chef::RunContext::CookbookCompiler.any_instance.should_receive(:compile)
|
383
|
+
end
|
384
|
+
|
385
|
+
it "sets the new run list on the node" do
|
386
|
+
client.run
|
387
|
+
node.run_list.should == Chef::RunList.new(new_runlist)
|
388
|
+
end
|
389
|
+
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
describe "when handling run failures" do
|
397
|
+
|
255
398
|
it "should remove the run_lock on failure of #load_node" do
|
256
399
|
@run_lock = double("Chef::RunLock", :acquire => true)
|
257
400
|
Chef::RunLock.stub(:new).and_return(@run_lock)
|
@@ -260,64 +403,64 @@ shared_examples_for Chef::Client do
|
|
260
403
|
Chef::EventDispatch::Dispatcher.stub(:new).and_return(@events)
|
261
404
|
|
262
405
|
# @events is created on Chef::Client.new, so we need to recreate it after mocking
|
263
|
-
|
264
|
-
|
406
|
+
client = Chef::Client.new
|
407
|
+
client.stub(:load_node).and_raise(Exception)
|
265
408
|
@run_lock.should_receive(:release)
|
266
409
|
if(Chef::Config[:client_fork] && !windows?)
|
267
|
-
|
410
|
+
client.should_receive(:fork) do |&block|
|
268
411
|
block.call
|
269
412
|
end
|
270
413
|
end
|
271
|
-
lambda {
|
414
|
+
lambda { client.run }.should raise_error(Exception)
|
272
415
|
end
|
416
|
+
end
|
273
417
|
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
it "notifies observers that the run has started" do
|
284
|
-
notified = false
|
285
|
-
Chef::Client.when_run_starts do |run_status|
|
286
|
-
run_status.node.should == @node
|
287
|
-
notified = true
|
288
|
-
end
|
418
|
+
describe "when notifying other objects of the status of the chef run" do
|
419
|
+
before do
|
420
|
+
Chef::Client.clear_notifications
|
421
|
+
Chef::Node.stub(:find_or_create).and_return(node)
|
422
|
+
node.stub(:save)
|
423
|
+
client.load_node
|
424
|
+
client.build_node
|
425
|
+
end
|
289
426
|
|
290
|
-
|
291
|
-
|
427
|
+
it "notifies observers that the run has started" do
|
428
|
+
notified = false
|
429
|
+
Chef::Client.when_run_starts do |run_status|
|
430
|
+
run_status.node.should == node
|
431
|
+
notified = true
|
292
432
|
end
|
293
433
|
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
run_status.node.should == @node
|
298
|
-
notified = true
|
299
|
-
end
|
434
|
+
client.run_started
|
435
|
+
notified.should be_true
|
436
|
+
end
|
300
437
|
|
301
|
-
|
302
|
-
|
438
|
+
it "notifies observers that the run has completed successfully" do
|
439
|
+
notified = false
|
440
|
+
Chef::Client.when_run_completes_successfully do |run_status|
|
441
|
+
run_status.node.should == node
|
442
|
+
notified = true
|
303
443
|
end
|
304
444
|
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
run_status.node.should == @node
|
309
|
-
notified = true
|
310
|
-
end
|
445
|
+
client.run_completed_successfully
|
446
|
+
notified.should be_true
|
447
|
+
end
|
311
448
|
|
312
|
-
|
313
|
-
|
449
|
+
it "notifies observers that the run failed" do
|
450
|
+
notified = false
|
451
|
+
Chef::Client.when_run_fails do |run_status|
|
452
|
+
run_status.node.should == node
|
453
|
+
notified = true
|
314
454
|
end
|
455
|
+
|
456
|
+
client.run_failed
|
457
|
+
notified.should be_true
|
315
458
|
end
|
316
459
|
end
|
317
460
|
|
318
461
|
describe "build_node" do
|
319
462
|
it "should expand the roles and recipes for the node" do
|
320
|
-
|
463
|
+
node.run_list << "role[role_containing_cookbook1]"
|
321
464
|
role_containing_cookbook1 = Chef::Role.new
|
322
465
|
role_containing_cookbook1.name("role_containing_cookbook1")
|
323
466
|
role_containing_cookbook1.run_list << "cookbook1"
|
@@ -329,37 +472,33 @@ shared_examples_for Chef::Client do
|
|
329
472
|
Chef::REST.should_receive(:new).and_return(mock_chef_rest)
|
330
473
|
|
331
474
|
# check pre-conditions.
|
332
|
-
|
333
|
-
|
475
|
+
node[:roles].should be_nil
|
476
|
+
node[:recipes].should be_nil
|
334
477
|
|
335
|
-
|
478
|
+
client.policy_builder.stub(:node).and_return(node)
|
336
479
|
|
337
480
|
# chefspec and possibly others use the return value of this method
|
338
|
-
|
481
|
+
client.build_node.should == node
|
339
482
|
|
340
483
|
# check post-conditions.
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
484
|
+
node[:roles].should_not be_nil
|
485
|
+
node[:roles].length.should == 1
|
486
|
+
node[:roles].should include("role_containing_cookbook1")
|
487
|
+
node[:recipes].should_not be_nil
|
488
|
+
node[:recipes].length.should == 1
|
489
|
+
node[:recipes].should include("cookbook1")
|
347
490
|
end
|
348
491
|
end
|
349
492
|
|
350
493
|
describe "windows_admin_check" do
|
351
|
-
before do
|
352
|
-
@client = Chef::Client.new
|
353
|
-
end
|
354
|
-
|
355
494
|
context "platform is not windows" do
|
356
495
|
before do
|
357
496
|
Chef::Platform.stub(:windows?).and_return(false)
|
358
497
|
end
|
359
498
|
|
360
499
|
it "shouldn't be called" do
|
361
|
-
|
362
|
-
|
500
|
+
client.should_not_receive(:has_admin_privileges?)
|
501
|
+
client.do_windows_admin_check
|
363
502
|
end
|
364
503
|
end
|
365
504
|
|
@@ -369,91 +508,46 @@ shared_examples_for Chef::Client do
|
|
369
508
|
end
|
370
509
|
|
371
510
|
it "should be called" do
|
372
|
-
|
373
|
-
|
511
|
+
client.should_receive(:has_admin_privileges?)
|
512
|
+
client.do_windows_admin_check
|
374
513
|
end
|
375
514
|
|
376
515
|
context "admin privileges exist" do
|
377
516
|
before do
|
378
|
-
|
517
|
+
client.should_receive(:has_admin_privileges?).and_return(true)
|
379
518
|
end
|
380
519
|
|
381
520
|
it "should not log a warning message" do
|
382
521
|
Chef::Log.should_not_receive(:warn)
|
383
|
-
|
522
|
+
client.do_windows_admin_check
|
384
523
|
end
|
385
524
|
|
386
525
|
context "fatal admin check is configured" do
|
387
526
|
it "should not raise an exception" do
|
388
|
-
|
527
|
+
client.do_windows_admin_check #should not raise
|
389
528
|
end
|
390
529
|
end
|
391
530
|
end
|
392
531
|
|
393
532
|
context "admin privileges doesn't exist" do
|
394
533
|
before do
|
395
|
-
|
534
|
+
client.should_receive(:has_admin_privileges?).and_return(false)
|
396
535
|
end
|
397
536
|
|
398
537
|
it "should log a warning message" do
|
399
538
|
Chef::Log.should_receive(:warn)
|
400
|
-
|
539
|
+
client.do_windows_admin_check
|
401
540
|
end
|
402
541
|
|
403
542
|
context "fatal admin check is configured" do
|
404
543
|
it "should raise an exception" do
|
405
|
-
|
544
|
+
client.do_windows_admin_check # should not raise
|
406
545
|
end
|
407
546
|
end
|
408
547
|
end
|
409
548
|
end
|
410
549
|
end
|
411
550
|
|
412
|
-
describe "when a run list override is provided" do
|
413
|
-
before do
|
414
|
-
@node = Chef::Node.new
|
415
|
-
@node.name(@fqdn)
|
416
|
-
@node.chef_environment("_default")
|
417
|
-
@node.automatic_attrs[:platform] = "example-platform"
|
418
|
-
@node.automatic_attrs[:platform_version] = "example-platform-1.0"
|
419
|
-
end
|
420
|
-
|
421
|
-
it "should permit spaces in overriding run list" do
|
422
|
-
@client = Chef::Client.new(nil, :override_runlist => 'role[a], role[b]')
|
423
|
-
end
|
424
|
-
|
425
|
-
it "should override the run list and skip the final node save" do
|
426
|
-
@client = Chef::Client.new(nil, :override_runlist => 'role[test_role]')
|
427
|
-
@client.node = @node
|
428
|
-
|
429
|
-
@node.run_list << "role[role_containing_cookbook1]"
|
430
|
-
|
431
|
-
override_role = Chef::Role.new
|
432
|
-
override_role.name 'test_role'
|
433
|
-
override_role.run_list << 'cookbook1'
|
434
|
-
|
435
|
-
original_runlist = @node.run_list.dup
|
436
|
-
|
437
|
-
mock_chef_rest = double("Chef::REST")
|
438
|
-
mock_chef_rest.should_receive(:get_rest).with("roles/test_role").and_return(override_role)
|
439
|
-
Chef::REST.should_receive(:new).and_return(mock_chef_rest)
|
440
|
-
|
441
|
-
@node.should_not_receive(:save)
|
442
|
-
|
443
|
-
@client.policy_builder.stub(:node).and_return(@node)
|
444
|
-
@client.policy_builder.build_node
|
445
|
-
|
446
|
-
@node[:roles].should_not be_nil
|
447
|
-
@node[:roles].should eql(['test_role'])
|
448
|
-
@node[:recipes].should eql(['cookbook1'])
|
449
|
-
|
450
|
-
@client.save_updated_node
|
451
|
-
|
452
|
-
@node.run_list.should == original_runlist
|
453
|
-
|
454
|
-
end
|
455
|
-
end
|
456
|
-
|
457
551
|
describe "assert_cookbook_path_not_empty" do
|
458
552
|
before do
|
459
553
|
Chef::Config[:solo] = true
|
@@ -462,24 +556,46 @@ shared_examples_for Chef::Client do
|
|
462
556
|
context "when any directory of cookbook_path contains no cookbook" do
|
463
557
|
it "raises CookbookNotFound error" do
|
464
558
|
expect do
|
465
|
-
|
559
|
+
client.send(:assert_cookbook_path_not_empty, nil)
|
466
560
|
end.to raise_error(Chef::Exceptions::CookbookNotFound, 'None of the cookbook paths set in Chef::Config[:cookbook_path], ["/path/to/invalid/cookbook_path"], contain any cookbooks')
|
467
561
|
end
|
468
562
|
end
|
469
563
|
end
|
470
564
|
|
471
|
-
|
565
|
+
describe "setting node name" do
|
566
|
+
context "when machinename, hostname and fqdn are all set" do
|
567
|
+
it "favors the fqdn" do
|
568
|
+
expect(client.node_name).to eql(fqdn)
|
569
|
+
end
|
570
|
+
end
|
472
571
|
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
572
|
+
context "when fqdn is missing" do
|
573
|
+
# ohai 7 should always have machinename == return of hostname
|
574
|
+
let(:fqdn) { nil }
|
575
|
+
it "favors the machinename" do
|
576
|
+
expect(client.node_name).to eql(machinename)
|
577
|
+
end
|
578
|
+
end
|
477
579
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
580
|
+
context "when fqdn and machinename are missing" do
|
581
|
+
# ohai 6 will not have machinename, return the short hostname
|
582
|
+
let(:fqdn) { nil }
|
583
|
+
let(:machinename) { nil }
|
584
|
+
it "falls back to hostname" do
|
585
|
+
expect(client.node_name).to eql(hostname)
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
context "when they're all missing" do
|
590
|
+
let(:machinename) { nil }
|
591
|
+
let(:hostname) { nil }
|
592
|
+
let(:fqdn) { nil }
|
482
593
|
|
483
|
-
|
594
|
+
it "throws an exception" do
|
595
|
+
expect { client.node_name }.to raise_error(Chef::Exceptions::CannotDetermineNodeName)
|
596
|
+
end
|
597
|
+
end
|
484
598
|
|
599
|
+
end
|
485
600
|
end
|
601
|
+
|