diff options
author | Adam Jacob <adam@hjksolutions.com> | 2008-03-23 20:33:06 -0700 |
---|---|---|
committer | Adam Jacob <adam@hjksolutions.com> | 2008-03-23 20:33:06 -0700 |
commit | c74068fffb387955cff8b32c2c984c782ee1e0a0 (patch) | |
tree | bac2087b4d045a2a5f36450fdc10d04202bf3961 | |
parent | 90d083563df3fc997b8cd3790f3b0b95d0855461 (diff) | |
download | chef-c74068fffb387955cff8b32c2c984c782ee1e0a0.tar.gz |
Adding a File provider
-rw-r--r-- | README.txt | 2 | ||||
-rw-r--r-- | lib/chef/provider.rb | 33 | ||||
-rw-r--r-- | lib/chef/provider/file.rb | 103 | ||||
-rw-r--r-- | lib/chef/resource/file.rb | 19 | ||||
-rw-r--r-- | spec/data/cookbooks/openldap/recipes/gigantor.rb | 2 | ||||
-rw-r--r-- | spec/data/recipes/test.rb | 2 | ||||
-rw-r--r-- | spec/data/seattle.txt | 1 | ||||
-rw-r--r-- | spec/unit/provider/file_spec.rb | 169 | ||||
-rw-r--r-- | spec/unit/provider_spec.rb | 46 | ||||
-rw-r--r-- | spec/unit/recipe_spec.rb | 2 | ||||
-rw-r--r-- | spec/unit/resource/file_spec.rb | 23 |
11 files changed, 376 insertions, 26 deletions
diff --git a/README.txt b/README.txt index 2216d88244..57ba1c4ea9 100644 --- a/README.txt +++ b/README.txt @@ -10,7 +10,7 @@ I'm in ur netwerk, cookin up yer servers. :) == FEATURES/PROBLEMS: -* Uses a Ruby DSL in place of the puppet language +* Uses a Ruby pidgin language in place of the puppet language * Drastically simpler implementation == SYNOPSIS: diff --git a/lib/chef/provider.rb b/lib/chef/provider.rb new file mode 100644 index 0000000000..d296b63227 --- /dev/null +++ b/lib/chef/provider.rb @@ -0,0 +1,33 @@ +# +# 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 + + attr_accessor :node, :new_resource, :current_resource + + def initialize(node, new_resource) + @node = node + @new_resource = new_resource + @current_resource = nil + end + + end +end
\ No newline at end of file diff --git a/lib/chef/provider/file.rb b/lib/chef/provider/file.rb new file mode 100644 index 0000000000..e59fd8bb7d --- /dev/null +++ b/lib/chef/provider/file.rb @@ -0,0 +1,103 @@ +# +# 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 'digest/md5' +require 'etc' + +class Chef + class Provider + class File < Chef::Provider + def load_current_resource + @current_resource = Chef::Resource::File.new(@new_resource.name) + @current_resource.path(@new_resource.path) + if ::File.exist?(@current_resource.path) && ::File.readable?(@current_resource.path) + cstats = ::File.stat(@current_resource.path) + @current_resource.owner(cstats.uid) + @current_resource.group(cstats.gid) + @current_resource.mode("%o" % (cstats.mode & 007777)) + end + @current_resource + end + + def checksum + digest = Digest::MD5.new + IO.foreach(@current_resource.path) do |line| + digest << line + end + @current_resource.checksum(digest.hexdigest) + end + + # Compare the ownership of a file. Returns true if they are the same, false if they are not. + def compare_owner + case @new_resource.owner + when /^\d+$/, Integer + @set_user_id = @new_resource.owner.to_i + @set_user_id == @current_resource.owner + else + # This raises an ArugmentError if you can't find the user + user_info = Etc.getpwnam(@new_resource.owner) + @set_user_id = user_info.uid + @set_user_id == @current_resource.owner + end + end + + # Set the ownership on the file, assuming it is not set correctly already. + def set_owner + unless compare_owner + ::File.chown(@set_user_id, nil, @new_resource.path) + end + end + + # Compares the group of a file. Returns true if they are the same, false if they are not. + def compare_group + case @new_resource.group + when /^\d+$/, Integer + @set_group_id = @new_resource.group.to_i + @set_group_id == @current_resource.group + else + group_info = Etc.getpwnam(@new_resource.group) + @set_group_id = group_info.gid + @set_group_id == @current_resource.group + end + end + + def set_group + unless compare_group + ::File.chown(nil, @set_group_id, @new_resource.path) + end + end + + def action_create + unless ::File.exists?(@new_resource.path) + ::File.open(@new_resource.path, "w+") { |f| } + end + set_owner + set_group + end + + def action_delete + if ::File.exists?(@new_resource.path) && ::File.writable?(@new_resource.path) + ::File.delete(@new_resource.path) + end + end + + end + end +end
\ No newline at end of file diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb index 0a2edb56a5..3132aa5a1a 100644 --- a/lib/chef/resource/file.rb +++ b/lib/chef/resource/file.rb @@ -21,14 +21,13 @@ class Chef class Resource class File < Chef::Resource - attr_reader :backup, :checksum, :insure, :group, :mode, :owner, :path - + def initialize(name, collection=nil, config=nil) @resource_name = :file super(name, collection, config) @path = name @backup = true - @checksum = "md5sum" + @action = "create" end def backup(arg=nil) @@ -45,21 +44,21 @@ class Chef def checksum(arg=nil) set_if_args(@checksum, arg) do case arg - when "md5sum", "mtime" + when /^[a-zA-Z0-9]{32}$/ # md5sum @checksum = arg else - raise ArgumentError, "checksum must be md5sum or mtime!" + raise ArgumentError, "checksum must be an md5sum!" end end end - def insure(arg=nil) - set_if_args(@insure, arg) do + def action(arg=nil) + set_if_args(@action, arg) do case arg - when "absent", "present" - @insure = arg + when "create", "delete" + @action = arg else - raise ArgumentError, "insure must be absent or present!" + raise ArgumentError, "action must be create or delete!" end end end diff --git a/spec/data/cookbooks/openldap/recipes/gigantor.rb b/spec/data/cookbooks/openldap/recipes/gigantor.rb index 4ebbaeb22d..b450eca7cd 100644 --- a/spec/data/cookbooks/openldap/recipes/gigantor.rb +++ b/spec/data/cookbooks/openldap/recipes/gigantor.rb @@ -1,3 +1,3 @@ cat "blanket" do pretty_kitty false -end
\ No newline at end of file +end diff --git a/spec/data/recipes/test.rb b/spec/data/recipes/test.rb index ce79f7109f..1c94b917f0 100644 --- a/spec/data/recipes/test.rb +++ b/spec/data/recipes/test.rb @@ -1,6 +1,6 @@ file "/etc/nsswitch.conf" do - insure "present" + action "create" owner "root" group "root" mode 0644 diff --git a/spec/data/seattle.txt b/spec/data/seattle.txt new file mode 100644 index 0000000000..19f6290939 --- /dev/null +++ b/spec/data/seattle.txt @@ -0,0 +1 @@ +Seattle is a great town. Next time you visit, you should buy me a beer.
\ No newline at end of file diff --git a/spec/unit/provider/file_spec.rb b/spec/unit/provider/file_spec.rb new file mode 100644 index 0000000000..b31a4d7ef1 --- /dev/null +++ b/spec/unit/provider/file_spec.rb @@ -0,0 +1,169 @@ +# +# 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.join(File.dirname(__FILE__), "..", "..", "spec_helper") + +describe Chef::Provider::File do + before(:each) do + @resource = Chef::Resource::File.new("seattle") + @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt")) + @node = Chef::Node.new + @node.name "latte" + @provider = Chef::Provider::File.new(@node, @resource) + end + + it "should return a Chef::Provider::File" do + @provider.should be_a_kind_of(Chef::Provider::File) + end + + it "should store the resource passed to new as new_resource" do + @provider.new_resource.should eql(@resource) + end + + it "should store the node passed to new as node" do + @provider.node.should eql(@node) + end + + it "should load a current resource based on the one specified at construction" do + @provider.load_current_resource + @provider.current_resource.should be_a_kind_of(Chef::Resource::File) + @provider.current_resource.name.should eql(@resource.name) + @provider.current_resource.path.should eql(@resource.path) + @provider.current_resource.owner.should_not eql(nil) + @provider.current_resource.group.should_not eql(nil) + @provider.current_resource.mode.should_not eql(nil) + end + + it "should load a mostly blank current resource if the file specified in new_resource doesn't exist/isn't readable" do + resource = Chef::Resource::File.new("seattle") + resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "woot.txt")) + node = Chef::Node.new + node.name "latte" + provider = Chef::Provider::File.new(node, resource) + provider.load_current_resource + provider.current_resource.should be_a_kind_of(Chef::Resource::File) + provider.current_resource.name.should eql(resource.name) + provider.current_resource.path.should eql(resource.path) + provider.current_resource.owner.should eql(nil) + provider.current_resource.group.should eql(nil) + provider.current_resource.mode.should eql(nil) + end + + it "should load the correct value for owner of the current resource" do + stats = File.stat(@resource.path) + @provider.load_current_resource + @provider.current_resource.owner.should eql(stats.uid) + end + + it "should load an md5 sum for an existing file" do + @provider.load_current_resource + @provider.current_resource.checksum("8d6152c7d62ea9188eda596c4d31e732") + end + + it "should compare the current owner with the requested owner" do + @provider.load_current_resource + @provider.new_resource.stub!(:owner).and_return("adam") + Etc.stub!(:getpwnam).and_return( + OpenStruct.new( + :name => "adam", + :passwd => "foo", + :uid => 501, + :gid => 501, + :gecos => "Adam Jacob", + :dir => "/Users/adam", + :shell => "/bin/zsh", + :change => "0", + :uclass => "", + :expire => 0 + ) + ) + @provider.current_resource.owner(501) + @provider.compare_owner.should eql(true) + + @provider.current_resource.owner(777) + @provider.compare_owner.should eql(false) + + @provider.new_resource.stub!(:owner).and_return(501) + @provider.current_resource.owner(501) + @provider.compare_owner.should eql(true) + + @provider.new_resource.stub!(:owner).and_return("501") + @provider.current_resource.owner(501) + @provider.compare_owner.should eql(true) + end + + it "should set the ownership on the file to the requested owner" do + @provider.load_current_resource + @provider.new_resource.stub!(:owner).and_return(9982398) + File.stub!(:chown).and_return(1) + File.should_receive(:chown).with(9982398, nil, @provider.current_resource.path) + lambda { @provider.set_owner }.should_not raise_error + end + + it "should raise an exception if you are not root and try to change ownership" do + @provider.load_current_resource + @provider.new_resource.stub!(:owner).and_return(0) + if Process.uid != 0 + lambda { @provider.set_owner }.should raise_error + end + end + + it "should set the group on the file to the requested group" do + @provider.load_current_resource + @provider.new_resource.stub!(:group).and_return(9982398) + File.stub!(:chown).and_return(1) + File.should_receive(:chown).with(nil, 9982398, @provider.current_resource.path) + lambda { @provider.set_group }.should_not raise_error + end + + it "should raise an exception if you are not root and try to change ownership" do + @provider.load_current_resource + @provider.new_resource.stub!(:group).and_return(0) + if Process.uid != 0 + lambda { @provider.set_group }.should raise_error + end + end + + it "should create the file if it is missing, then set the attributes on action_create" do + @provider.load_current_resource + @provider.new_resource.stub!(:owner).and_return(9982398) + @provider.new_resource.stub!(:group).and_return(9982398) + @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo") + File.stub!(:chown).and_return(1) + File.should_receive(:chown).with(nil, 9982398, @provider.new_resource.path) + File.stub!(:chown).and_return(1) + File.should_receive(:chown).with(9982398, nil, @provider.new_resource.path) + File.stub!(:open).and_return(1) + File.should_receive(:open).with(@provider.new_resource.path, "w+") + @provider.action_create + end + + it "should delete the file if it exists and is writable on action_delete" do + @provider.load_current_resource + @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo") + File.should_receive("exists?").with(@provider.new_resource.path).and_return(true) + File.should_receive("writable?").with(@provider.new_resource.path).and_return(true) + File.should_receive(:delete).with(@provider.new_resource.path).and_return(true) + @provider.action_delete + end + +end
\ No newline at end of file diff --git a/spec/unit/provider_spec.rb b/spec/unit/provider_spec.rb new file mode 100644 index 0000000000..51980cbe99 --- /dev/null +++ b/spec/unit/provider_spec.rb @@ -0,0 +1,46 @@ +# +# 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.join(File.dirname(__FILE__), "..", "spec_helper") + +describe Chef::Provider do + before(:each) do + @resource = Chef::Resource.new("funk") + @node = Chef::Node.new + @node.name "latte" + @provider = Chef::Provider.new(@node, @resource) + end + + it "should return a Chef::Provider" do + @provider.should be_a_kind_of(Chef::Provider) + end + + it "should store the resource passed to new as new_resource" do + @provider.new_resource.should eql(@resource) + end + + it "should store the node passed to new as node" do + @provider.node.should eql(@node) + end + + it "should have nil for current_resource by default" do + @provider.current_resource.should eql(nil) + end +end
\ No newline at end of file diff --git a/spec/unit/recipe_spec.rb b/spec/unit/recipe_spec.rb index 11080a2d82..e02973d73c 100644 --- a/spec/unit/recipe_spec.rb +++ b/spec/unit/recipe_spec.rb @@ -114,7 +114,7 @@ CODE @recipe.from_file(File.join(File.dirname(__FILE__), "..", "data", "recipes", "test.rb")) res = @recipe.resources(:file => "/etc/nsswitch.conf") res.name.should eql("/etc/nsswitch.conf") - res.insure.should eql("present") + res.action.should eql("create") res.owner.should eql("root") res.group.should eql("root") res.mode.should eql(0644) diff --git a/spec/unit/resource/file_spec.rb b/spec/unit/resource/file_spec.rb index 85aaac1311..474c58f0d1 100644 --- a/spec/unit/resource/file_spec.rb +++ b/spec/unit/resource/file_spec.rb @@ -34,6 +34,10 @@ describe Chef::Resource::File 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 be set to back up files by default" do @resource.backup.should eql(true) end @@ -45,20 +49,15 @@ describe Chef::Resource::File do lambda { @resource.backup "blues" }.should raise_error(ArgumentError) end - it "should use the md5sum for checking changes by default" do - @resource.checksum.should eql("md5sum") - end - - it "should accept md5sum or mtime for checksum" do - lambda { @resource.checksum "md5sum" }.should_not raise_error(ArgumentError) - lambda { @resource.checksum "mtime" }.should_not raise_error(ArgumentError) - lambda { @resource.checksum "blues" }.should raise_error(ArgumentError) + it "should accept an md5sum for checksum" do + lambda { @resource.checksum "bfda9e7a13afb123433667c2c7801d11" }.should_not raise_error(ArgumentError) + lambda { @resource.checksum "monkey!" }.should raise_error(ArgumentError) end - it "should accept absent or present for insure" do - lambda { @resource.insure "absent" }.should_not raise_error(ArgumentError) - lambda { @resource.insure "present" }.should_not raise_error(ArgumentError) - lambda { @resource.insure "blues" }.should raise_error(ArgumentError) + 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 accept a group name or id for group" do |