summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Jacob <adam@hjksolutions.com>2008-05-03 15:11:10 -0700
committerAdam Jacob <adam@hjksolutions.com>2008-05-03 15:11:10 -0700
commit2f1c4c662c3b1d999258796152273eac7b7b9ae8 (patch)
tree3a15963d57a53f4089adba070b22de75e7337f6e
parent4320cdb02e32147bf589d23c3b1ce02a2eba7731 (diff)
downloadchef-2f1c4c662c3b1d999258796152273eac7b7b9ae8.tar.gz
Adding link support
-rw-r--r--examples/config/cookbooks/tempfile/recipes/default.rb20
-rw-r--r--lib/chef/provider/link.rb73
-rw-r--r--lib/chef/resource/link.rb62
-rw-r--r--spec/unit/provider/link_spec.rb149
-rw-r--r--spec/unit/resource/link_spec.rb79
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