diff options
author | Adam Jacob <adam@hjksolutions.com> | 2008-05-03 15:11:10 -0700 |
---|---|---|
committer | Adam Jacob <adam@hjksolutions.com> | 2008-05-03 15:11:10 -0700 |
commit | 2f1c4c662c3b1d999258796152273eac7b7b9ae8 (patch) | |
tree | 3a15963d57a53f4089adba070b22de75e7337f6e | |
parent | 4320cdb02e32147bf589d23c3b1ce02a2eba7731 (diff) | |
download | chef-2f1c4c662c3b1d999258796152273eac7b7b9ae8.tar.gz |
Adding link support
-rw-r--r-- | examples/config/cookbooks/tempfile/recipes/default.rb | 20 | ||||
-rw-r--r-- | lib/chef/provider/link.rb | 73 | ||||
-rw-r--r-- | lib/chef/resource/link.rb | 62 | ||||
-rw-r--r-- | spec/unit/provider/link_spec.rb | 149 | ||||
-rw-r--r-- | spec/unit/resource/link_spec.rb | 79 |
5 files changed, 373 insertions, 10 deletions
diff --git a/examples/config/cookbooks/tempfile/recipes/default.rb b/examples/config/cookbooks/tempfile/recipes/default.rb index 79c4640921..4207b50b49 100644 --- a/examples/config/cookbooks/tempfile/recipes/default.rb +++ b/examples/config/cookbooks/tempfile/recipes/default.rb @@ -1,13 +1,13 @@ - file "/tmp/glen" do - owner "adam" - mode 0755 - action "create" - end +file "/tmp/glen" do + owner "adam" + mode 0755 + action "create" +end - directory "/tmp/marginal" do - owner "adam" - mode 0755 - action :create - end +directory "/tmp/marginal" do + owner "adam" + mode 0755 + action :create +end diff --git a/lib/chef/provider/link.rb b/lib/chef/provider/link.rb new file mode 100644 index 0000000000..4a1254de9b --- /dev/null +++ b/lib/chef/provider/link.rb @@ -0,0 +1,73 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +class Chef + class Provider + class Link < Chef::Provider + def load_current_resource + @current_resource = Chef::Resource::Link.new(@new_resource.name) + @current_resource.target_file(@new_resource.target_file) + @current_resource.link_type(@new_resource.link_type) + if @new_resource.link_type == :symbolic + if ::File.exists?(@current_resource.target_file) && ::File.symlink?(@current_resource.target_file) + @current_resource.source_file( + ::File.expand_path(::File.readlink(@current_resource.target_file)) + ) + else + @current_resource.source_file("") + end + elsif @new_resource.link_type == :hard + if ::File.exists?(@current_resource.target_file) && ::File.exists?(@new_resource.source_file) + if ::File.stat(@current_resource.target_file).ino == ::File.stat(@new_resource.source_file).ino + @current_resource.source_file(@new_resource.source_file) + else + @current_resource.source_file("") + end + else + @current_resource.source_file("") + end + end + @current_resource + end + + def action_create + if @current_resource.source_file != @new_resource.source_file + Chef::Log.info("Creating a #{@new_resource.link_type} link from #{@new_resource.source_file} -> #{@new_resource.target_file} for #{@new_resource}") + if @new_resource.link_type == :symbolic + ::File.symlink(@new_resource.source_file, @new_resource.target_file) + elsif @new_resource.link_type == :hard + ::File.link(@new_resource.source_file, @new_resource.target_file) + end + @new_resource.updated = true + end + end + + def action_delete + if ::File.exists?(@new_resource.target_file) && ::File.writable?(@new_resource.target_file) + Chef::Log.info("Deleting #{@new_resource} at #{@new_resource.target_file}") + ::File.delete(@new_resource.target_file) + @new_resource.updated = true + else + raise "Cannot delete #{@new_resource} at #{@new_resource_path}!" + end + end + end + end +end
\ No newline at end of file diff --git a/lib/chef/resource/link.rb b/lib/chef/resource/link.rb new file mode 100644 index 0000000000..743ef5ea73 --- /dev/null +++ b/lib/chef/resource/link.rb @@ -0,0 +1,62 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +class Chef + class Resource + class Link < Chef::Resource + + def initialize(name, collection=nil) + @resource_name = :link + super(name, collection) + @source_file = name + @action = :create + @link_type = :symbolic + @target_file = nil + @allowed_actions.push(:create, :delete) + end + + def source_file(arg=nil) + set_or_return( + :source_file, + arg, + :kind_of => String + ) + end + + def target_file(arg=nil) + set_or_return( + :target_file, + arg, + :kind_of => String + ) + end + + def link_type(arg=nil) + real_arg = arg.kind_of?(String) ? arg.to_sym : arg + set_or_return( + :link_type, + real_arg, + :equal_to => [ :symbolic, :hard ] + ) + end + + end + end +end
\ No newline at end of file diff --git a/spec/unit/provider/link_spec.rb b/spec/unit/provider/link_spec.rb new file mode 100644 index 0000000000..d6a4e40ddf --- /dev/null +++ b/spec/unit/provider/link_spec.rb @@ -0,0 +1,149 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +require 'ostruct' + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Provider::Link do + before(:each) do + @new_resource = mock("New Resource", :null_object => true) + @new_resource.stub!(:name).and_return("symlink") + @new_resource.stub!(:source_file).and_return("/tmp/fofile") + @new_resource.stub!(:target_file).and_return("/tmp/fofile-link") + @new_resource.stub!(:link_type).and_return(:symbolic) + @new_resource.stub!(:updated).and_return(false) + @node = Chef::Node.new + @node.name "latte" + @provider = Chef::Provider::Link.new(@node, @new_resource) + end + + it "should load the current resource based on the new resource" do + File.should_receive(:exists?).once.and_return(true) + File.should_receive(:symlink?).once.and_return(true) + File.should_receive(:readlink).once.and_return("/tmp/fofile") + @provider.load_current_resource + @provider.current_resource.name.should eql("symlink") + @provider.current_resource.source_file.should eql("/tmp/fofile") + @provider.current_resource.target_file.should eql("/tmp/fofile-link") + @provider.current_resource.link_type.should eql(:symbolic) + end + + it "should set the current resource's source_file to '' if the target_file doesn't exist" do + File.should_receive(:exists?).once.and_return(true) + File.should_receive(:symlink?).once.and_return(false) + @provider.load_current_resource + @provider.current_resource.source_file.should eql("") + end + + it "should load the current resource if it is a hard link" do + @new_resource.stub!(:link_type).and_return(:hard) + File.should_receive(:exists?).twice.and_return(true) + cstat = mock("stats", :null_object => true) + cstat.stub!(:ino).and_return(1) + File.should_receive(:stat).with("/tmp/fofile-link").and_return(cstat) + File.should_receive(:stat).with("/tmp/fofile").and_return(cstat) + @provider.load_current_resource + @provider.current_resource.name.should eql("symlink") + @provider.current_resource.source_file.should eql("/tmp/fofile") + @provider.current_resource.target_file.should eql("/tmp/fofile-link") + @provider.current_resource.link_type.should eql(:hard) + end + + it "should set the current resource's source_file to '' if the target_file doesn't exist" do + @new_resource.stub!(:link_type).and_return(:hard) + File.should_receive(:exists?).once.and_return(false) + @provider.load_current_resource + @provider.current_resource.source_file.should eql("") + end + + it "should set the current resource's source_file to '' if the two files arent hardlinked" do + @new_resource.stub!(:link_type).and_return(:hard) + File.stub!(:exists?).and_return(true) + cstat = mock("stats", :null_object => true) + cstat.stub!(:ino).and_return(0) + bstat = mock("stats", :null_object => true) + bstat.stub!(:ino).and_return(1) + File.should_receive(:stat).with("/tmp/fofile-link").and_return(cstat) + File.should_receive(:stat).with("/tmp/fofile").and_return(bstat) + @provider.load_current_resource + @provider.current_resource.source_file.should eql("") + end + + it "should create a new symlink on create, setting updated to true" do + load_mock_symlink_provider + @provider.current_resource.source_file("nil") + File.should_receive(:symlink).with(@new_resource.source_file, @new_resource.target_file).once.and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) + @provider.action_create + end + + it "should not create a new symlink on create if it already exists" do + load_mock_symlink_provider + File.should_not_receive(:symlink).with(@new_resource.source_file, @new_resource.target_file) + @provider.action_create + end + + it "should create a new hard link on create, setting updated to true" do + load_mock_hardlink_provider + @provider.current_resource.source_file("nil") + File.should_receive(:link).with(@new_resource.source_file, @new_resource.target_file).once.and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) + @provider.action_create + end + + it "should not create a new hard link on create if it already exists" do + load_mock_symlink_provider + File.should_not_receive(:link).with(@new_resource.source_file, @new_resource.target_file) + @provider.action_create + end + + it "should delete the link if it exists, and is writable with action_delete" do + load_mock_symlink_provider + File.should_receive(:exists?).once.and_return(true) + File.should_receive(:writable?).once.and_return(true) + File.should_receive(:delete).with(@new_resource.target_file).once.and_return(true) + @provider.action_delete + end + + it "should raise an exception if it cannot delete the link due to bad permissions" do + load_mock_symlink_provider + File.stub!(:exists?).and_return(true) + File.stub!(:writable?).and_return(false) + lambda { @provider.action_delete }.should raise_error(RuntimeError) + end + + def load_mock_symlink_provider + File.stub!(:exists?).and_return(true) + File.stub!(:symlink?).and_return(true) + File.stub!(:readlink).and_return("/tmp/fofile") + @provider.load_current_resource + end + + def load_mock_hardlink_provider + @new_resource.stub!(:link_type).and_return(:hard) + File.stub!(:exists?).twice.and_return(true) + cstat = mock("stats", :null_object => true) + cstat.stub!(:ino).and_return(1) + File.stub!(:stat).with("/tmp/fofile-link").and_return(cstat) + File.stub!(:stat).with("/tmp/fofile").and_return(cstat) + @provider.load_current_resource + end +end
\ No newline at end of file diff --git a/spec/unit/resource/link_spec.rb b/spec/unit/resource/link_spec.rb new file mode 100644 index 0000000000..3a47d672fe --- /dev/null +++ b/spec/unit/resource/link_spec.rb @@ -0,0 +1,79 @@ +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Resource::Link do + + before(:each) do + @resource = Chef::Resource::Link.new("fakey_fakerton") + end + + it "should create a new Chef::Resource::Link" do + @resource.should be_a_kind_of(Chef::Resource) + @resource.should be_a_kind_of(Chef::Resource::Link) + end + + it "should have a name" do + @resource.name.should eql("fakey_fakerton") + end + + it "should have a default action of 'create'" do + @resource.action.should eql(:create) + end + + it "should accept create or delete for action" do + lambda { @resource.action "create" }.should_not raise_error(ArgumentError) + lambda { @resource.action "delete" }.should_not raise_error(ArgumentError) + lambda { @resource.action "blues" }.should raise_error(ArgumentError) + end + + it "should use the object name as the source_file by default" do + @resource.source_file.should eql("fakey_fakerton") + end + + it "should accept a string as the source_file" do + lambda { @resource.source_file "/tmp" }.should_not raise_error(ArgumentError) + lambda { @resource.source_file Hash.new }.should raise_error(ArgumentError) + end + + it "should allow you to set a target_file" do + @resource.target_file "/tmp/foo" + @resource.target_file.should eql("/tmp/foo") + end + + it "should allow you to specify the link type" do + @resource.link_type "symbolic" + @resource.link_type.should eql(:symbolic) + end + + it "should default to a symbolic link" do + @resource.link_type.should eql(:symbolic) + end + + it "should accept a hard link_type" do + @resource.link_type :hard + @resource.link_type.should eql(:hard) + end + + it "should reject any other link_type but :hard and :symbolic" do + lambda { @resource.link_type "x-men" }.should raise_error(ArgumentError) + end + +end
\ No newline at end of file |