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
@@ -28,7 +28,6 @@ describe Chef::Cookbook::SyntaxCheck do
28
28
  Chef::Log.logger = Logger.new(StringIO.new)
29
29
  Chef::Log.level = :warn # suppress "Syntax OK" messages
30
30
 
31
-
32
31
  @attr_files = %w{default.rb smokey.rb}.map { |f| File.join(cookbook_path, 'attributes', f) }
33
32
  @defn_files = %w{client.rb server.rb}.map { |f| File.join(cookbook_path, 'definitions', f)}
34
33
  @recipes = %w{default.rb gigantor.rb one.rb}.map { |f| File.join(cookbook_path, 'recipes', f) }
@@ -0,0 +1,100 @@
1
+ #
2
+ # Author:: Bryan McLellan <btm@loftninjas.org>
3
+ # Copyright:: Copyright (c) 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/dsl/reboot_pending"
20
+ require "spec_helper"
21
+
22
+ describe Chef::DSL::RebootPending do
23
+ describe "reboot_pending?" do
24
+ describe "in isoloation" do
25
+ let(:recipe) { Object.new.extend(Chef::DSL::RebootPending) }
26
+
27
+ before do
28
+ recipe.stub(:platform?).and_return(false)
29
+ end
30
+
31
+ context "platform is windows" do
32
+ before do
33
+ recipe.stub(:platform?).with('windows').and_return(true)
34
+ recipe.stub(:registry_key_exists?).and_return(false)
35
+ recipe.stub(:registry_value_exists?).and_return(false)
36
+ end
37
+
38
+ it 'should return true if "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations" exists' do
39
+ recipe.stub(:registry_value_exists?).with('HKLM\SYSTEM\CurrentControlSet\Control\Session Manager', { :name => 'PendingFileRenameOperations' }).and_return(true)
40
+ expect(recipe.reboot_pending?).to be_true
41
+ end
42
+
43
+ it 'should return true if "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" exists' do
44
+ recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired').and_return(true)
45
+ expect(recipe.reboot_pending?).to be_true
46
+ end
47
+
48
+ it 'should return true if key "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired" exists' do
49
+ recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootRequired').and_return(true)
50
+ expect(recipe.reboot_pending?).to be_true
51
+ end
52
+
53
+ it 'should return true if value "HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile" contains specific data' do
54
+ recipe.stub(:registry_key_exists?).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(true)
55
+ recipe.stub(:registry_get_values).with('HKLM\SOFTWARE\Microsoft\Updates\UpdateExeVolatile').and_return(
56
+ [{:name => "Flags", :type => :dword, :data => 3}])
57
+ expect(recipe.reboot_pending?).to be_true
58
+ end
59
+ end
60
+
61
+ context "platform is ubuntu" do
62
+ before do
63
+ recipe.stub(:platform?).with('ubuntu').and_return(true)
64
+ end
65
+
66
+ it 'should return true if /var/run/reboot-required exists' do
67
+ File.stub(:exists?).with('/var/run/reboot-required').and_return(true)
68
+ expect(recipe.reboot_pending?).to be_true
69
+ end
70
+
71
+ it 'should return false if /var/run/reboot-required does not exist' do
72
+ File.stub(:exists?).with('/var/run/reboot-required').and_return(false)
73
+ expect(recipe.reboot_pending?).to be_false
74
+ end
75
+ end
76
+
77
+ context "platform is not supported" do
78
+ it 'should raise an exception' do
79
+ recipe.stub_chain(:node, :[]).with(:platform).and_return('msdos')
80
+ expect { recipe.reboot_pending? }.to raise_error(Chef::Exceptions::UnsupportedPlatform)
81
+ end
82
+ end
83
+ end # describe in isolation
84
+
85
+ describe "in a recipe" do
86
+ it "responds to reboot_pending?" do
87
+ # Chef::Recipe.new(cookbook_name, recipe_name, run_context(node, cookbook_collection, events))
88
+ recipe = Chef::Recipe.new(nil,nil,Chef::RunContext.new(Chef::Node.new, {}, nil))
89
+ expect(recipe).to respond_to(:reboot_pending?)
90
+ end
91
+ end # describe in a recipe
92
+
93
+ describe "in a resource" do
94
+ it "responds to reboot_pending?" do
95
+ resource = Chef::Resource::new("Crackerjack::Timing", nil)
96
+ expect(resource).to respond_to(:reboot_pending?)
97
+ end
98
+ end # describe in a resource
99
+ end
100
+ end
@@ -25,7 +25,9 @@ describe Chef::Knife::ClientCreate do
25
25
  Chef::Config[:node_name] = "webmonkey.example.com"
26
26
  @knife = Chef::Knife::ClientCreate.new
27
27
  @knife.config = {
28
- :file => nil
28
+ :file => nil,
29
+ :admin => false,
30
+ :validator => false
29
31
  }
30
32
  @knife.name_args = [ "adam" ]
31
33
  @client = Chef::ApiClient.new
@@ -49,6 +51,16 @@ describe Chef::Knife::ClientCreate do
49
51
  @knife.run
50
52
  end
51
53
 
54
+ it "by default it is not an admin" do
55
+ @client.should_receive(:admin).with(false)
56
+ @knife.run
57
+ end
58
+
59
+ it "by default it is not a validator" do
60
+ @client.should_receive(:validator).with(false)
61
+ @knife.run
62
+ end
63
+
52
64
  it "should allow you to edit the data" do
53
65
  @knife.should_receive(:edit_data).with(@client)
54
66
  @knife.run
@@ -70,5 +82,21 @@ describe Chef::Knife::ClientCreate do
70
82
  end
71
83
  end
72
84
 
85
+ describe "with -a or --admin" do
86
+ it "should create an admin client" do
87
+ @knife.config[:admin] = true
88
+ @client.should_receive(:admin).with(true)
89
+ @knife.run
90
+ end
91
+ end
92
+
93
+ describe "with --validator" do
94
+ it "should create an validator client" do
95
+ @knife.config[:validator] = true
96
+ @client.should_receive(:validator).with(true)
97
+ @knife.run
98
+ end
99
+ end
100
+
73
101
  end
74
102
  end
@@ -21,12 +21,16 @@ require 'spec_helper'
21
21
  describe Chef::Knife::ClientDelete do
22
22
  before(:each) do
23
23
  @knife = Chef::Knife::ClientDelete.new
24
+ # defaults
25
+ @knife.config = {
26
+ :force => false
27
+ }
24
28
  @knife.name_args = [ 'adam' ]
25
29
  end
26
30
 
27
31
  describe 'run' do
28
32
  it 'should delete the client' do
29
- @knife.should_receive(:delete_object).with(Chef::ApiClient, 'adam')
33
+ @knife.should_receive(:delete_object).with(Chef::ApiClient, 'adam', 'client')
30
34
  @knife.run
31
35
  end
32
36
 
@@ -37,4 +41,43 @@ describe Chef::Knife::ClientDelete do
37
41
  lambda { @knife.run }.should raise_error(SystemExit)
38
42
  end
39
43
  end
44
+
45
+ describe 'with a validator' do
46
+ before(:each) do
47
+ Chef::Knife::UI.stub(:confirm).and_return(true)
48
+ @knife.stub(:confirm).and_return(true)
49
+ @client = Chef::ApiClient.new
50
+ Chef::ApiClient.should_receive(:load).and_return(@client)
51
+ end
52
+
53
+ it 'should delete non-validator client if --force is not set' do
54
+ @knife.config[:force] = false
55
+ @client.should_receive(:destroy).and_return(@client)
56
+ @knife.should_receive(:msg)
57
+
58
+ @knife.run
59
+ end
60
+
61
+ it 'should delete non-validator client if --force is set' do
62
+ @knife.config[:force] = true
63
+ @client.should_receive(:destroy).and_return(@client)
64
+ @knife.should_receive(:msg)
65
+
66
+ @knife.run
67
+ end
68
+
69
+ it 'should not delete validator client if --force is not set' do
70
+ @client.validator(true)
71
+ @knife.ui.should_receive(:fatal)
72
+ lambda { @knife.run}.should raise_error(SystemExit)
73
+ end
74
+
75
+ it 'should delete validator client if --force is set' do
76
+ @knife.config[:force] = true
77
+ @client.should_receive(:destroy).and_return(@client)
78
+ @knife.should_receive(:msg)
79
+
80
+ @knife.run
81
+ end
82
+ end
40
83
  end
@@ -22,6 +22,7 @@ module KnifeSpecs
22
22
  end
23
23
 
24
24
  require 'spec_helper'
25
+ require 'uri'
25
26
 
26
27
  describe Chef::Knife do
27
28
  before(:each) do
@@ -141,6 +142,60 @@ describe Chef::Knife do
141
142
 
142
143
  end
143
144
 
145
+ describe "the headers include X-Remote-Request-Id" do
146
+
147
+ let(:headers) {{"Accept"=>"application/json",
148
+ "Accept-Encoding"=>"gzip;q=1.0,deflate;q=0.6,identity;q=0.3",
149
+ 'X-Chef-Version' => Chef::VERSION,
150
+ "Host"=>"api.opscode.piab:443",
151
+ "X-REMOTE-REQUEST-ID"=>request_id}}
152
+
153
+ let(:request_id) {"1234"}
154
+
155
+ let(:request_mock) { {} }
156
+
157
+ let(:rest) do
158
+ Net::HTTP.stub(:new).and_return(http_client)
159
+ Chef::RequestID.instance.stub(:request_id).and_return(request_id)
160
+ Chef::Config.stub(:chef_server_url).and_return("https://api.opscode.piab")
161
+ command = Chef::Knife.run(%w{test yourself})
162
+ rest = command.noauth_rest
163
+ rest
164
+ end
165
+
166
+ let!(:http_client) do
167
+ http_client = Net::HTTP.new(url.host, url.port)
168
+ http_client.stub(:request).and_yield(http_response).and_return(http_response)
169
+ http_client
170
+ end
171
+
172
+ let(:url) { URI.parse("https://api.opscode.piab") }
173
+
174
+ let(:http_response) do
175
+ http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req")
176
+ http_response.stub(:read_body)
177
+ http_response.stub(:body).and_return(body)
178
+ http_response["Content-Length"] = body.bytesize.to_s
179
+ http_response
180
+ end
181
+
182
+ let(:body) { "ninja" }
183
+
184
+ before(:each) do
185
+ Chef::Config[:chef_server_url] = "https://api.opscode.piab"
186
+ if KnifeSpecs.const_defined?(:TestYourself)
187
+ KnifeSpecs.send :remove_const, :TestYourself
188
+ end
189
+ Kernel.load(File.join(CHEF_SPEC_DATA, 'knife_subcommand', 'test_yourself.rb'))
190
+ Chef::Knife.subcommands.each { |name, klass| Chef::Knife.subcommands.delete(name) unless klass.kind_of?(Class) }
191
+ end
192
+
193
+ it "confirms that the headers include X-Remote-Request-Id" do
194
+ Net::HTTP::Get.should_receive(:new).with("/monkey", headers).and_return(request_mock)
195
+ rest.get_rest("monkey")
196
+ end
197
+ end
198
+
144
199
  describe "when running a command" do
145
200
  before(:each) do
146
201
  if KnifeSpecs.const_defined?(:TestYourself)
@@ -488,6 +488,13 @@ describe Chef::Node::Attribute do
488
488
  end
489
489
  end
490
490
 
491
+ describe "dup" do
492
+ it "array can be duped even if some elements can't" do
493
+ @attributes.default[:foo] = %w[foo bar baz] + Array(1..3) + [nil, true, false, [ "el", 0, nil ] ]
494
+ @attributes.default[:foo].dup
495
+ end
496
+ end
497
+
491
498
  describe "has_key?" do
492
499
  it "should return true if an attribute exists" do
493
500
  @attributes.has_key?("music").should == true
@@ -86,7 +86,7 @@ end
86
86
  describe Chef::Node::ImmutableArray do
87
87
 
88
88
  before do
89
- @immutable_array = Chef::Node::ImmutableArray.new(%w[foo bar baz])
89
+ @immutable_array = Chef::Node::ImmutableArray.new(%w[foo bar baz] + Array(1..3) + [nil, true, false, [ "el", 0, nil ] ])
90
90
  end
91
91
 
92
92
  ##
@@ -130,6 +130,10 @@ describe Chef::Node::ImmutableArray do
130
130
  end
131
131
  end
132
132
 
133
+ it "can be duped even if some elements can't" do
134
+ @immutable_array.dup
135
+ end
136
+
133
137
  it "returns a mutable version of itself when duped" do
134
138
  mutable = @immutable_array.dup
135
139
  mutable[0] = :value
@@ -96,6 +96,11 @@ describe Chef::Provider::User do
96
96
  @provider.compare_group.should be_false
97
97
  end
98
98
 
99
+ it "should coerce an integer to a string for comparison" do
100
+ @current_resource.stub!(:gid).and_return("500")
101
+ @provider.compare_group.should be_false
102
+ end
103
+
99
104
  it "should return false if append is true and the group member(s) already exists" do
100
105
  @current_resource.members << "extra_user"
101
106
  @new_resource.stub(:append).and_return(true)
@@ -53,38 +53,264 @@ describe Chef::Provider::Ifconfig::Debian do
53
53
 
54
54
  let(:config_filename_ifcfg) { "/etc/network/interfaces.d/ifcfg-#{new_resource.device}" }
55
55
 
56
- describe "generate_config for action_add" do
56
+ describe "generate_config" do
57
57
 
58
- let(:config_file_ifaces) { StringIO.new }
58
+ context "when writing a file" do
59
+ let(:config_file_ifcfg) { StringIO.new }
59
60
 
60
- let(:config_file_ifcfg) { StringIO.new }
61
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
61
62
 
62
- before do
63
- expect(FileUtils).to receive(:cp)
64
- expect(File).to receive(:open).with(config_filename_ifaces).and_return(StringIO.new)
65
- expect(File).to receive(:open).with(config_filename_ifaces, "w").and_yield(config_file_ifaces)
66
- expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
67
- expect(File).to receive(:exist?).with(config_filename_ifaces).and_return(true)
68
- end
63
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
64
+
65
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
66
+
67
+ before do
68
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
69
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
70
+ expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
71
+ end
72
+
73
+ it "should write a network-script" do
74
+ provider.run_action(:add)
75
+ expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/)
76
+ expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/)
77
+ expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/)
78
+ end
79
+
80
+ context "when the interface_dot_d directory does not exist" do
81
+ before do
82
+ FileUtils.rmdir tempdir_path
83
+ expect(File.exists?(tempdir_path)).to be_false
84
+ end
85
+
86
+ it "should create the /etc/network/interfaces.d directory" do
87
+ provider.run_action(:add)
88
+ expect(File.exists?(tempdir_path)).to be_true
89
+ expect(File.directory?(tempdir_path)).to be_true
90
+ end
69
91
 
70
- it "should create network-scripts directory" do
71
- expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(false)
72
- expect(Dir).to receive(:mkdir).with(File.dirname(config_filename_ifcfg))
73
- provider.run_action(:add)
92
+ it "should mark the resource as updated" do
93
+ provider.run_action(:add)
94
+ expect(new_resource.updated_by_last_action?).to be_true
95
+ end
96
+ end
97
+
98
+ context "when the interface_dot_d directory exists" do
99
+ before do
100
+ expect(File.exists?(tempdir_path)).to be_true
101
+ end
102
+
103
+ it "should still mark the resource as updated (we still write a file to it)" do
104
+ provider.run_action(:add)
105
+ expect(new_resource.updated_by_last_action?).to be_true
106
+ end
107
+ end
74
108
  end
75
109
 
76
- it "should write configure network-scripts directory" do
77
- expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(true)
78
- provider.run_action(:add)
79
- expect(config_file_ifaces.string).to match(/^\s*source\s+\/etc\/network\/interfaces[.]d\/[*]\s*$/)
110
+ context "when the file is up-to-date" do
111
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
112
+
113
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
114
+
115
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
116
+
117
+ before do
118
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
119
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
120
+ config_file_ifcfg = StringIO.new(<<-EOF
121
+ iface eth0 inet static
122
+ address 10.0.0.1
123
+ netmask 255.255.254.0
124
+ EOF
125
+ )
126
+ expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg)
127
+ expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist
128
+ end
129
+
130
+ context "when the /etc/network/interfaces file has the source line" do
131
+ let(:expected_string) do
132
+ <<-EOF
133
+ a line
134
+ source #{tempdir_path}/*
135
+ another line
136
+ EOF
137
+ end
138
+
139
+ before do
140
+ tempfile.write(expected_string)
141
+ tempfile.close
142
+ end
143
+
144
+ it "should preserve all the contents" do
145
+ provider.run_action(:add)
146
+ expect(IO.read(tempfile.path)).to eq(expected_string)
147
+ end
148
+
149
+ it "should not mark the resource as updated" do
150
+ provider.run_action(:add)
151
+ pending "superclass ifconfig provider is not idempotent"
152
+ expect(new_resource.updated_by_last_action?).to be_false
153
+ end
154
+ end
155
+
156
+ context "when the /etc/network/interfaces file does not have the source line" do
157
+ let(:expected_string) do
158
+ <<-EOF
159
+ a line
160
+ another line
161
+ source #{tempdir_path}/*
162
+ EOF
163
+ end
164
+
165
+ before do
166
+ tempfile.write("a line\nanother line\n")
167
+ tempfile.close
168
+ end
169
+
170
+ it "should preserve the original contents and add the source line" do
171
+ provider.run_action(:add)
172
+ expect(IO.read(tempfile.path)).to eq(expected_string)
173
+ end
174
+
175
+ it "should mark the resource as updated" do
176
+ provider.run_action(:add)
177
+ expect(new_resource.updated_by_last_action?).to be_true
178
+ end
179
+ end
80
180
  end
81
181
 
82
- it "should write a network-script" do
83
- expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(true)
84
- provider.run_action(:add)
85
- expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/)
86
- expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/)
87
- expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/)
182
+ describe "when running under why run" do
183
+
184
+ before do
185
+ Chef::Config[:why_run] = true
186
+ end
187
+
188
+ after do
189
+ Chef::Config[:why_run] = false
190
+ end
191
+
192
+ context "when writing a file" do
193
+ let(:config_file_ifcfg) { StringIO.new }
194
+
195
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
196
+
197
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
198
+
199
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
200
+
201
+ before do
202
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
203
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
204
+ expect(File).not_to receive(:new).with(config_filename_ifcfg, "w")
205
+ end
206
+
207
+ it "should write a network-script" do
208
+ provider.run_action(:add)
209
+ expect(config_file_ifcfg.string).not_to match(/^iface eth0 inet static\s*$/)
210
+ expect(config_file_ifcfg.string).not_to match(/^\s+address 10\.0\.0\.1\s*$/)
211
+ expect(config_file_ifcfg.string).not_to match(/^\s+netmask 255\.255\.254\.0\s*$/)
212
+ end
213
+
214
+ context "when the interface_dot_d directory does not exist" do
215
+ before do
216
+ FileUtils.rmdir tempdir_path
217
+ expect(File.exists?(tempdir_path)).to be_false
218
+ end
219
+
220
+ it "should not create the /etc/network/interfaces.d directory" do
221
+ provider.run_action(:add)
222
+ expect(File.exists?(tempdir_path)).not_to be_true
223
+ end
224
+
225
+ it "should mark the resource as updated" do
226
+ provider.run_action(:add)
227
+ expect(new_resource.updated_by_last_action?).to be_true
228
+ end
229
+ end
230
+
231
+ context "when the interface_dot_d directory exists" do
232
+ before do
233
+ expect(File.exists?(tempdir_path)).to be_true
234
+ end
235
+
236
+ it "should still mark the resource as updated (we still write a file to it)" do
237
+ provider.run_action(:add)
238
+ expect(new_resource.updated_by_last_action?).to be_true
239
+ end
240
+ end
241
+ end
242
+
243
+ context "when the file is up-to-date" do
244
+ let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") }
245
+
246
+ let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") }
247
+
248
+ let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" }
249
+
250
+ before do
251
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path)
252
+ stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path)
253
+ config_file_ifcfg = StringIO.new(<<-EOF
254
+ iface eth0 inet static
255
+ address 10.0.0.1
256
+ netmask 255.255.254.0
257
+ EOF
258
+ )
259
+ expect(File).not_to receive(:new).with(config_filename_ifcfg, "w")
260
+ expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist
261
+ end
262
+
263
+ context "when the /etc/network/interfaces file has the source line" do
264
+ let(:expected_string) do
265
+ <<-EOF
266
+ a line
267
+ source #{tempdir_path}/*
268
+ another line
269
+ EOF
270
+ end
271
+
272
+ before do
273
+ tempfile.write(expected_string)
274
+ tempfile.close
275
+ end
276
+
277
+ it "should preserve all the contents" do
278
+ provider.run_action(:add)
279
+ expect(IO.read(tempfile.path)).to eq(expected_string)
280
+ end
281
+
282
+ it "should not mark the resource as updated" do
283
+ provider.run_action(:add)
284
+ pending "superclass ifconfig provider is not idempotent"
285
+ expect(new_resource.updated_by_last_action?).to be_false
286
+ end
287
+ end
288
+
289
+ context "when the /etc/network/interfaces file does not have the source line" do
290
+ let(:expected_string) do
291
+ <<-EOF
292
+ a line
293
+ another line
294
+ source #{tempdir_path}/*
295
+ EOF
296
+ end
297
+
298
+ before do
299
+ tempfile.write("a line\nanother line\n")
300
+ tempfile.close
301
+ end
302
+
303
+ it "should preserve the original contents and not add the source line" do
304
+ provider.run_action(:add)
305
+ expect(IO.read(tempfile.path)).to eq("a line\nanother line\n")
306
+ end
307
+
308
+ it "should mark the resource as updated" do
309
+ provider.run_action(:add)
310
+ expect(new_resource.updated_by_last_action?).to be_true
311
+ end
312
+ end
313
+ end
88
314
  end
89
315
  end
90
316
 
@@ -98,4 +324,5 @@ describe Chef::Provider::Ifconfig::Debian do
98
324
  provider.run_action(:delete)
99
325
  end
100
326
  end
327
+
101
328
  end