diff options
-rw-r--r-- | lib/chef/provider/ifconfig/debian.rb | 27 | ||||
-rw-r--r-- | spec/unit/provider/ifconfig/debian_spec.rb | 275 |
2 files changed, 270 insertions, 32 deletions
diff --git a/lib/chef/provider/ifconfig/debian.rb b/lib/chef/provider/ifconfig/debian.rb index 821f4fe924..7589971143 100644 --- a/lib/chef/provider/ifconfig/debian.rb +++ b/lib/chef/provider/ifconfig/debian.rb @@ -24,6 +24,9 @@ class Chef class Ifconfig class Debian < Chef::Provider::Ifconfig + INTERFACES_FILE = "/etc/network/interfaces" + INTERFACES_DOT_D_DIR = "/etc/network/interfaces.d" + def initialize(new_resource, run_context) super(new_resource, run_context) @config_template = %{ @@ -46,22 +49,30 @@ iface <%= @new_resource.device %> inet static <% end %> <% end %> } - @config_path = "/etc/network/interfaces.d/ifcfg-#{@new_resource.device}" + @config_path = "#{INTERFACES_DOT_D_DIR}/ifcfg-#{@new_resource.device}" end def generate_config - check_interfaces_config + enforce_interfaces_dot_d_sanity super end protected - def check_interfaces_config - converge_by ('modify configuration file : /etc/network/interfaces') do - Dir.mkdir('/etc/network/interfaces.d') unless ::File.directory?('/etc/network/interfaces.d') - conf = Chef::Util::FileEdit.new('/etc/network/interfaces') - conf.insert_line_if_no_match('^\s*source\s+/etc/network/interfaces[.]d/[*]\s*$', 'source /etc/network/interfaces.d/*') - conf.write_file + def enforce_interfaces_dot_d_sanity + # create /etc/network/interfaces.d via dir resource (to get reporting, etc) + dir = Chef::Resource::Directory.new(INTERFACES_DOT_D_DIR, run_context) + dir.run_action(:create) + new_resource.updated_by_last_action(true) if dir.updated_by_last_action? + # roll our own file_edit resource, this will not get reported until we have a file_edit resource + interfaces_dot_d_for_regexp = INTERFACES_DOT_D_DIR.gsub(/\./, '\.') # escape dots for the regexp + regexp = %r{^\s*source\s+#{interfaces_dot_d_for_regexp}/\*\s*$} + unless ::File.exists?(INTERFACES_FILE) && regexp.match(IO.read(INTERFACES_FILE)) + converge_by("modifying #{INTERFACES_FILE} to source #{INTERFACES_DOT_D_DIR}") do + conf = Chef::Util::FileEdit.new(INTERFACES_FILE) + conf.insert_line_if_no_match(regexp, "source #{INTERFACES_DOT_D_DIR}/*") + conf.write_file + end end end diff --git a/spec/unit/provider/ifconfig/debian_spec.rb b/spec/unit/provider/ifconfig/debian_spec.rb index c2e2d1bfd1..c6a37fdd5b 100644 --- a/spec/unit/provider/ifconfig/debian_spec.rb +++ b/spec/unit/provider/ifconfig/debian_spec.rb @@ -53,38 +53,264 @@ describe Chef::Provider::Ifconfig::Debian do let(:config_filename_ifcfg) { "/etc/network/interfaces.d/ifcfg-#{new_resource.device}" } - describe "generate_config for action_add" do + describe "generate_config" do - let(:config_file_ifaces) { StringIO.new } + context "when writing a file" do + let(:config_file_ifcfg) { StringIO.new } - let(:config_file_ifcfg) { StringIO.new } + let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } - before do - expect(FileUtils).to receive(:cp) - expect(File).to receive(:open).with(config_filename_ifaces).and_return(StringIO.new) - expect(File).to receive(:open).with(config_filename_ifaces, "w").and_yield(config_file_ifaces) - expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg) - expect(File).to receive(:exist?).with(config_filename_ifaces).and_return(true) - end + let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } + + let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } + + before do + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) + expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg) + end + + it "should write a network-script" do + provider.run_action(:add) + expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/) + expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/) + expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/) + end + + context "when the interface_dot_d directory does not exist" do + before do + FileUtils.rmdir tempdir_path + expect(File.exists?(tempdir_path)).to be_false + end + + it "should create the /etc/network/interfaces.d directory" do + provider.run_action(:add) + expect(File.exists?(tempdir_path)).to be_true + expect(File.directory?(tempdir_path)).to be_true + end - it "should create network-scripts directory" do - expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(false) - expect(Dir).to receive(:mkdir).with(File.dirname(config_filename_ifcfg)) - provider.run_action(:add) + it "should mark the resource as updated" do + provider.run_action(:add) + expect(new_resource.updated_by_last_action?).to be_true + end + end + + context "when the interface_dot_d directory exists" do + before do + expect(File.exists?(tempdir_path)).to be_true + end + + it "should still mark the resource as updated (we still write a file to it)" do + provider.run_action(:add) + expect(new_resource.updated_by_last_action?).to be_true + end + end end - it "should write configure network-scripts directory" do - expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(true) - provider.run_action(:add) - expect(config_file_ifaces.string).to match(/^\s*source\s+\/etc\/network\/interfaces[.]d\/[*]\s*$/) + context "when the file is up-to-date" do + let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } + + let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } + + let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } + + before do + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) + config_file_ifcfg = StringIO.new(<<-EOF +iface eth0 inet static + address 10.0.0.1 + netmask 255.255.254.0 +EOF + ) + expect(File).to receive(:new).with(config_filename_ifcfg, "w").and_return(config_file_ifcfg) + expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist + end + + context "when the /etc/network/interfaces file has the source line" do + let(:expected_string) do + <<-EOF +a line +source #{tempdir_path}/* +another line +EOF + end + + before do + tempfile.write(expected_string) + tempfile.close + end + + it "should preserve all the contents" do + provider.run_action(:add) + expect(IO.read(tempfile.path)).to eq(expected_string) + end + + it "should not mark the resource as updated" do + provider.run_action(:add) + pending "superclass ifconfig provider is not idempotent" + expect(new_resource.updated_by_last_action?).to be_false + end + end + + context "when the /etc/network/interfaces file does not have the source line" do + let(:expected_string) do + <<-EOF +a line +another line +source #{tempdir_path}/* +EOF + end + + before do + tempfile.write("a line\nanother line\n") + tempfile.close + end + + it "should preserve the original contents and add the source line" do + provider.run_action(:add) + expect(IO.read(tempfile.path)).to eq(expected_string) + end + + it "should mark the resource as updated" do + provider.run_action(:add) + expect(new_resource.updated_by_last_action?).to be_true + end + end end - it "should write a network-script" do - expect(File).to receive(:directory?).with(File.dirname(config_filename_ifcfg)).and_return(true) - provider.run_action(:add) - expect(config_file_ifcfg.string).to match(/^iface eth0 inet static\s*$/) - expect(config_file_ifcfg.string).to match(/^\s+address 10\.0\.0\.1\s*$/) - expect(config_file_ifcfg.string).to match(/^\s+netmask 255\.255\.254\.0\s*$/) + describe "when running under why run" do + + before do + Chef::Config[:why_run] = true + end + + after do + Chef::Config[:why_run] = false + end + + context "when writing a file" do + let(:config_file_ifcfg) { StringIO.new } + + let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } + + let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } + + let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } + + before do + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) + expect(File).not_to receive(:new).with(config_filename_ifcfg, "w") + end + + it "should write a network-script" do + provider.run_action(:add) + expect(config_file_ifcfg.string).not_to match(/^iface eth0 inet static\s*$/) + expect(config_file_ifcfg.string).not_to match(/^\s+address 10\.0\.0\.1\s*$/) + expect(config_file_ifcfg.string).not_to match(/^\s+netmask 255\.255\.254\.0\s*$/) + end + + context "when the interface_dot_d directory does not exist" do + before do + FileUtils.rmdir tempdir_path + expect(File.exists?(tempdir_path)).to be_false + end + + it "should not create the /etc/network/interfaces.d directory" do + provider.run_action(:add) + expect(File.exists?(tempdir_path)).not_to be_true + end + + it "should mark the resource as updated" do + provider.run_action(:add) + expect(new_resource.updated_by_last_action?).to be_true + end + end + + context "when the interface_dot_d directory exists" do + before do + expect(File.exists?(tempdir_path)).to be_true + end + + it "should still mark the resource as updated (we still write a file to it)" do + provider.run_action(:add) + expect(new_resource.updated_by_last_action?).to be_true + end + end + end + + context "when the file is up-to-date" do + let(:tempfile) { Tempfile.new("rspec-chef-ifconfig-debian") } + + let(:tempdir_path) { Dir.mktmpdir("rspec-chef-ifconfig-debian-dir") } + + let(:config_filename_ifcfg) { "#{tempdir_path}/ifcfg-#{new_resource.device}" } + + before do + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_FILE", tempfile.path) + stub_const("Chef::Provider::Ifconfig::Debian::INTERFACES_DOT_D_DIR", tempdir_path) + config_file_ifcfg = StringIO.new(<<-EOF +iface eth0 inet static + address 10.0.0.1 + netmask 255.255.254.0 + EOF + ) + expect(File).not_to receive(:new).with(config_filename_ifcfg, "w") + expect(File.exists?(tempdir_path)).to be_true # since the file exists, the enclosing dir must also exist + end + + context "when the /etc/network/interfaces file has the source line" do + let(:expected_string) do + <<-EOF +a line +source #{tempdir_path}/* +another line + EOF + end + + before do + tempfile.write(expected_string) + tempfile.close + end + + it "should preserve all the contents" do + provider.run_action(:add) + expect(IO.read(tempfile.path)).to eq(expected_string) + end + + it "should not mark the resource as updated" do + provider.run_action(:add) + pending "superclass ifconfig provider is not idempotent" + expect(new_resource.updated_by_last_action?).to be_false + end + end + + context "when the /etc/network/interfaces file does not have the source line" do + let(:expected_string) do + <<-EOF +a line +another line +source #{tempdir_path}/* + EOF + end + + before do + tempfile.write("a line\nanother line\n") + tempfile.close + end + + it "should preserve the original contents and not add the source line" do + provider.run_action(:add) + expect(IO.read(tempfile.path)).to eq("a line\nanother line\n") + end + + it "should mark the resource as updated" do + provider.run_action(:add) + expect(new_resource.updated_by_last_action?).to be_true + end + end + end end end @@ -98,4 +324,5 @@ describe Chef::Provider::Ifconfig::Debian do provider.run_action(:delete) end end + end |