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.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +6 -6
  3. data/README.md +1 -1
  4. data/lib/chef/api_client.rb +1 -3
  5. data/lib/chef/application.rb +2 -1
  6. data/lib/chef/application/client.rb +11 -1
  7. data/lib/chef/client.rb +24 -9
  8. data/lib/chef/cookbook/syntax_check.rb +107 -6
  9. data/lib/chef/dsl/reboot_pending.rb +61 -0
  10. data/lib/chef/exceptions.rb +12 -1
  11. data/lib/chef/formatters/error_descriptor.rb +1 -1
  12. data/lib/chef/http/remote_request_id.rb +46 -0
  13. data/lib/chef/knife/bootstrap.rb +1 -1
  14. data/lib/chef/knife/bootstrap/README.md +12 -0
  15. data/lib/chef/knife/bootstrap/chef-full.erb +3 -0
  16. data/lib/chef/knife/client_create.rb +6 -0
  17. data/lib/chef/knife/client_delete.rb +15 -1
  18. data/lib/chef/knife/raw.rb +1 -0
  19. data/lib/chef/node.rb +1 -1
  20. data/lib/chef/node/attribute_collections.rb +8 -1
  21. data/lib/chef/node/immutable_collections.rb +8 -1
  22. data/lib/chef/provider/deploy.rb +1 -1
  23. data/lib/chef/provider/group.rb +1 -1
  24. data/lib/chef/provider/ifconfig/debian.rb +19 -8
  25. data/lib/chef/provider/ohai.rb +6 -5
  26. data/lib/chef/provider/service/macosx.rb +68 -14
  27. data/lib/chef/recipe.rb +2 -0
  28. data/lib/chef/request_id.rb +37 -0
  29. data/lib/chef/resource.rb +2 -0
  30. data/lib/chef/resource_reporter.rb +7 -4
  31. data/lib/chef/rest.rb +5 -1
  32. data/lib/chef/run_status.rb +4 -1
  33. data/lib/chef/server_api.rb +3 -1
  34. data/lib/chef/version.rb +2 -2
  35. data/spec/functional/dsl/reboot_pending_spec.rb +118 -0
  36. data/spec/functional/resource/base.rb +1 -3
  37. data/spec/functional/resource/deploy_revision_spec.rb +192 -1
  38. data/spec/functional/resource/git_spec.rb +1 -1
  39. data/spec/functional/resource/ohai_spec.rb +65 -0
  40. data/spec/functional/resource/registry_spec.rb +4 -5
  41. data/spec/integration/client/client_spec.rb +14 -0
  42. data/spec/spec_helper.rb +1 -2
  43. data/spec/support/shared/functional/windows_script.rb +1 -2
  44. data/spec/unit/api_client_spec.rb +46 -0
  45. data/spec/unit/client_spec.rb +345 -229
  46. data/spec/unit/cookbook/syntax_check_spec.rb +0 -1
  47. data/spec/unit/dsl/reboot_pending_spec.rb +100 -0
  48. data/spec/unit/knife/client_create_spec.rb +29 -1
  49. data/spec/unit/knife/client_delete_spec.rb +44 -1
  50. data/spec/unit/knife_spec.rb +55 -0
  51. data/spec/unit/node/attribute_spec.rb +7 -0
  52. data/spec/unit/node/immutable_collections_spec.rb +5 -1
  53. data/spec/unit/provider/group_spec.rb +5 -0
  54. data/spec/unit/provider/ifconfig/debian_spec.rb +251 -24
  55. data/spec/unit/provider/ohai_spec.rb +2 -3
  56. data/spec/unit/provider/service/macosx_spec.rb +29 -11
  57. data/spec/unit/resource_reporter_spec.rb +1 -1
  58. data/spec/unit/rest_spec.rb +38 -13
  59. metadata +151 -194
@@ -92,7 +92,7 @@ E
92
92
 
93
93
  before(:all) do
94
94
  @ohai = Ohai::System.new
95
- @ohai.require_plugin("os")
95
+ @ohai.all_plugins("os")
96
96
  end
97
97
 
98
98
  context "working with pathes with special characters" do
@@ -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
- lambda{@new_resource.run_action(:create)}.should_not raise_error
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
- lambda{@new_resource.run_action(:create_if_missing)}.should_not raise_error
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
@@ -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.require_plugin("os")
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.require_plugin("os")
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"
@@ -24,29 +24,54 @@ require 'chef/run_context'
24
24
  require 'chef/rest'
25
25
  require 'rbconfig'
26
26
 
27
- shared_examples_for Chef::Client do
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
- @hostname = "hostname"
33
- @fqdn = "hostname.example.org"
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
- @client.node_name
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
- @client.node_name
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
- @client.formatters_for_run.should == [[:doc]]
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
- @client.formatters_for_run.should == [[:null]]
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
- @client.formatters_for_run.should == [[:null]]
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
- @client.formatters_for_run.should == [[:doc]]
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
- @client.formatters_for_run.should == [[:min, nil]]
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 = @client.configure_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 = @client.configure_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
- it "should identify the node and run ohai, then register the client" do
171
- mock_chef_rest_for_node = double("Chef::REST (node)")
172
- mock_chef_rest_for_cookbook_sync = double("Chef::REST (cookbook sync)")
173
- mock_chef_rest_for_node_save = double("Chef::REST (node save)")
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
- if(Chef::Config[:client_fork] && !windows?)
225
- require 'stringio'
226
- if(Chef::Config[:pipe_node])
227
- pipe_sim = StringIO.new
228
- pipe_sim.should_receive(:close).exactly(4).and_return(nil)
229
- res = ''
230
- pipe_sim.should_receive(:puts) do |string|
231
- res.replace(string)
232
- end
233
- pipe_sim.should_receive(:gets).and_return(res)
234
- IO.should_receive(:pipe).and_return([pipe_sim, pipe_sim])
235
- IO.should_receive(:select).and_return(true)
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
- proc_ret = Class.new.new
238
- proc_ret.should_receive(:success?).and_return(true)
239
- Process.should_receive(:waitpid2).and_return([1, proc_ret])
240
- @client.should_receive(:exit).and_return(nil)
241
- @client.should_receive(:fork) do |&block|
242
- block.call
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
- # This is what we're testing.
247
- @client.run
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
- if(!Chef::Config[:client_fork] || Chef::Config[:pipe_node])
250
- @node.automatic_attrs[:platform].should == "example-platform"
251
- @node.automatic_attrs[:platform_version].should == "example-platform-1.0"
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
- @client = Chef::Client.new
264
- @client.stub(:load_node).and_raise(Exception)
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
- @client.should_receive(:fork) do |&block|
410
+ client.should_receive(:fork) do |&block|
268
411
  block.call
269
412
  end
270
413
  end
271
- lambda { @client.run }.should raise_error(Exception)
414
+ lambda { client.run }.should raise_error(Exception)
272
415
  end
416
+ end
273
417
 
274
- describe "when notifying other objects of the status of the chef run" do
275
- before do
276
- Chef::Client.clear_notifications
277
- Chef::Node.stub(:find_or_create).and_return(@node)
278
- @node.stub(:save)
279
- @client.load_node
280
- @client.build_node
281
- end
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
- @client.run_started
291
- notified.should be_true
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
- it "notifies observers that the run has completed successfully" do
295
- notified = false
296
- Chef::Client.when_run_completes_successfully do |run_status|
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
- @client.run_completed_successfully
302
- notified.should be_true
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
- it "notifies observers that the run failed" do
306
- notified = false
307
- Chef::Client.when_run_fails do |run_status|
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
- @client.run_failed
313
- notified.should be_true
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
- @node.run_list << "role[role_containing_cookbook1]"
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
- @node[:roles].should be_nil
333
- @node[:recipes].should be_nil
475
+ node[:roles].should be_nil
476
+ node[:recipes].should be_nil
334
477
 
335
- @client.policy_builder.stub(:node).and_return(@node)
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
- @client.build_node.should == @node
481
+ client.build_node.should == node
339
482
 
340
483
  # check post-conditions.
341
- @node[:roles].should_not be_nil
342
- @node[:roles].length.should == 1
343
- @node[:roles].should include("role_containing_cookbook1")
344
- @node[:recipes].should_not be_nil
345
- @node[:recipes].length.should == 1
346
- @node[:recipes].should include("cookbook1")
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
- @client.should_not_receive(:has_admin_privileges?)
362
- @client.do_windows_admin_check
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
- @client.should_receive(:has_admin_privileges?)
373
- @client.do_windows_admin_check
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
- @client.should_receive(:has_admin_privileges?).and_return(true)
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
- @client.do_windows_admin_check
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
- @client.do_windows_admin_check.should_not raise_error
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
- @client.should_receive(:has_admin_privileges?).and_return(false)
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
- @client.do_windows_admin_check
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
- @client.do_windows_admin_check.should_not raise_error
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
- @client.send(:assert_cookbook_path_not_empty, nil)
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
- end
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
- describe Chef::Client do
474
- Chef::Config[:client_fork] = false
475
- it_behaves_like Chef::Client
476
- end
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
- describe "Chef::Client Forked" do
479
- before do
480
- Chef::Config[:client_fork] = true
481
- end
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
- it_behaves_like Chef::Client
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
+