summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2008-08-18 21:50:59 -0500
committerLuke Kanies <luke@madstop.com>2008-08-20 12:13:25 -0500
commita5ab52c628cae7ac9ed5ca1bd5de779944840802 (patch)
treed2c0db0f2859efaa25225fba82c40da94623d910
parent4f275b669b0336c6032204bfa9694fa6522eb267 (diff)
downloadpuppet-a5ab52c628cae7ac9ed5ca1bd5de779944840802.tar.gz
puppet-a5ab52c628cae7ac9ed5ca1bd5de779944840802.tar.xz
puppet-a5ab52c628cae7ac9ed5ca1bd5de779944840802.zip
Adding files temporarily, since I've decided this work is a dead-end.
I'm merely adding these so that they're in the history if I decided to look at them again. Signed-off-by: Luke Kanies <luke@madstop.com>
-rw-r--r--lib/puppet/provider/newfile/default.rb89
-rw-r--r--lib/puppet/type/newfile.rb38
-rw-r--r--lib/puppet/util/state_machine.rb52
-rwxr-xr-xspec/unit/provider/newfile/default.rb181
-rw-r--r--spec/unit/type/newfile.rb118
-rwxr-xr-xspec/unit/util/state_machine.rb59
6 files changed, 537 insertions, 0 deletions
diff --git a/lib/puppet/provider/newfile/default.rb b/lib/puppet/provider/newfile/default.rb
new file mode 100644
index 000000000..4da98718f
--- /dev/null
+++ b/lib/puppet/provider/newfile/default.rb
@@ -0,0 +1,89 @@
+Puppet::Type.type(:newfile).provide(:default) do
+ # Remove the file.
+ def destroy
+ end
+
+ # Does the file currently exist?
+ def exist?
+ ! stat.nil?
+ end
+
+ def content
+ return :absent unless exist?
+ begin
+ File.read(name)
+ rescue => detail
+ fail "Could not read %s: %s" % [name, detail]
+ end
+ end
+
+ def content=(value)
+ File.open(name, "w") { |f| f.print value }
+ end
+
+ def flush
+ @stat = nil
+ end
+
+ def group
+ return :absent unless exist?
+ stat.gid
+ end
+
+ def group=(value)
+ File.chown(nil, value, name)
+ end
+
+ def mkdir
+ begin
+ Dir.mkdir(name)
+ rescue Errno::ENOENT
+ fail "Cannot create %s; parent directory does not exist" % name
+ rescue => detail
+ fail "Could not create directory %s: %s" % [name, detail]
+ end
+ end
+
+ def mkfile
+ end
+
+ def mklink
+ end
+
+ def mode
+ return :absent unless exist?
+ stat.mode & 007777
+ end
+
+ def mode=(value)
+ File.chmod(value, name)
+ end
+
+ def owner
+ return :absent unless exist?
+ stat.uid
+ end
+
+ def owner=(value)
+ File.chown(value, nil, name)
+ end
+
+ def type
+ return :absent unless exist?
+ stat.ftype
+ end
+
+ private
+
+ def stat
+ unless defined?(@stat) and @stat
+ begin
+ @stat = File.stat(name)
+ # Map the property names to the stat values, yo.
+ rescue Errno::ENOENT
+ @stat = nil
+ end
+ end
+ @stat
+ end
+end
diff --git a/lib/puppet/type/newfile.rb b/lib/puppet/type/newfile.rb
new file mode 100644
index 000000000..e8763748d
--- /dev/null
+++ b/lib/puppet/type/newfile.rb
@@ -0,0 +1,38 @@
+Puppet::Type.newtype(:newfile) do
+ newparam(:path, :namevar => true) do
+ desc "The file path."
+ end
+
+ newproperty(:ensure) do
+ desc "What type the file should be."
+
+ newvalue(:absent) { provider.destroy }
+ newvalue(:file) { provider.mkfile }
+ newvalue(:directory) {provider.mkdir }
+ newvalue(:link) {provider.mklink}
+
+ def retrieve
+ provider.type
+ end
+ end
+
+ newproperty(:content) do
+ desc "The file content."
+ end
+
+ newproperty(:owner) do
+ desc "The file owner."
+ end
+
+ newproperty(:group) do
+ desc "The file group."
+ end
+
+ newproperty(:mode) do
+ desc "The file mode."
+ end
+
+ newproperty(:type) do
+ desc "The read-only file type."
+ end
+end
diff --git a/lib/puppet/util/state_machine.rb b/lib/puppet/util/state_machine.rb
new file mode 100644
index 000000000..9329df3ce
--- /dev/null
+++ b/lib/puppet/util/state_machine.rb
@@ -0,0 +1,52 @@
+require 'puppet/util'
+
+# A simple class for defining simple state machines. You define
+# the state transitions, which translate to the states a given property
+# can be in and the methods used to transition between those states.
+#
+# == Example
+#
+# newproperty(:ensure) do
+#
+# :absent => :present (:create)
+#
+# statemachine.new(:create => {:absent => :present}, :destroy => {:present => :absent})
+#
+# mach.transition(:absent => :present).with :create
+# mach.transition(:present => :absent).with :destroy
+#
+# mach.transition :create => {:absent => :present}, :destroy => {:present => :absent}
+
+require 'puppet/simple_graph'
+require 'puppet/relationship'
+
+class Puppet::Util::StateMachine
+ class Transition < Puppet::Relationship
+ end
+
+ def initialize(&block)
+ @graph = Puppet::SimpleGraph.new
+ @docs = {}
+
+ instance_eval(&block) if block
+ end
+
+ # Define a state, with docs.
+ def state(name, docs)
+ @docs[name] = docs
+ @graph.add_vertex(name)
+ end
+
+ # Check whether a state is defined.
+ def state?(name)
+ @graph.vertex?(name)
+ end
+
+ def transition(from, to)
+ raise ArgumentError, "Unknown starting state %s" % from unless state?(from)
+ raise ArgumentError, "Unknown ending state %s" % to unless state?(to)
+ raise ArgumentError, "Transition %s => %s already exists" % [from, to] if @graph.edge?(from, to)
+ transition = Transition.new(from, to)
+ @graph.add_edge(transition)
+ end
+end
diff --git a/spec/unit/provider/newfile/default.rb b/spec/unit/provider/newfile/default.rb
new file mode 100755
index 000000000..4261e5599
--- /dev/null
+++ b/spec/unit/provider/newfile/default.rb
@@ -0,0 +1,181 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../../spec_helper'
+
+provider = Puppet::Type.type(:newfile).provider(:default)
+
+describe provider do
+ %w{content owner group mode}.each do |attr|
+ it "should be able to determine the '#{attr}'" do
+ provider.new({}).should respond_to(attr)
+ end
+
+ it "should be able to set the '#{attr}'" do
+ provider.new({}).should respond_to(attr + "=")
+ end
+ end
+
+ it "should be able to determine the 'type'" do
+ provider.new({}).should respond_to(:type)
+ end
+
+ it "should be able to tell if the file exists" do
+ provider.new({}).should respond_to(:exist?)
+ end
+
+ it "should be able to create a file" do
+ provider.new({}).should respond_to(:mkfile)
+ end
+
+ it "should be able to create a directory" do
+ provider.new({}).should respond_to(:mkdir)
+ end
+
+ it "should be able to create a symlink" do
+ provider.new({}).should respond_to(:mklink)
+ end
+
+ it "should be able to destroy the file" do
+ provider.new({}).should respond_to(:destroy)
+ end
+
+ describe "when retrieving current state" do
+ before do
+ @file = provider.new :name => "/foo/bar"
+ end
+
+ it "should not stat() the file more than once per transaction" do
+ stat = mock('stat')
+ File.expects(:stat).with("/foo/bar").returns stat
+ @file.exist?
+ @file.exist?
+ end
+
+ it "should remove the cached stat after it has been flushed" do
+ File.expects(:stat).times(2).with("/foo/bar").returns "foo"
+ @file.exist?
+ @file.flush
+ @file.exist?
+ end
+
+ describe "and the file does not exist" do
+ before do
+ File.stubs(:stat).with("/foo/bar").returns nil
+ end
+
+ it "should correctly detect the file's absence" do
+ @file.should_not be_exist
+ end
+
+ it "should consider the type to be absent" do
+ @file.type.should == :absent
+ end
+
+ it "should consider the owner to be absent" do
+ @file.owner.should == :absent
+ end
+
+ it "should consider the group to be absent" do
+ @file.group.should == :absent
+ end
+
+ it "should consider the mode to be absent" do
+ @file.mode.should == :absent
+ end
+
+ it "should consider the content to be absent" do
+ @file.content.should == :absent
+ end
+ end
+
+ describe "and the file exists" do
+ before do
+ @stat = mock 'stat'
+ File.stubs(:stat).with("/foo/bar").returns @stat
+ end
+
+ it "should correctly detect the file's presence" do
+ @file.should be_exist
+ end
+
+ it "should return the filetype as the type" do
+ @stat.expects(:ftype).returns "file"
+ @file.type.should == "file"
+ end
+
+ it "should return the Stat UID as the owner" do
+ @stat.expects(:uid).returns 50
+ @file.owner.should == 50
+ end
+
+ it "should return the Stat GID as the group" do
+ @stat.expects(:gid).returns 50
+ @file.group.should == 50
+ end
+
+ it "should return the Stat mode, shifted accordingly, as the mode" do
+ @stat.expects(:mode).returns 16877
+ @file.mode.should == 493 # 755 in decimal
+ end
+
+ it "should return the contents of the file as the content" do
+ File.expects(:read).with("/foo/bar").returns "fooness"
+ @file.content.should == "fooness"
+ end
+
+ it "should fail appropriately when reading the file's content fails" do
+ File.expects(:read).with("/foo/bar").raises RuntimeError
+ lambda { @file.content }.should raise_error(Puppet::Error)
+ end
+ end
+ end
+
+ describe "when creating the file" do
+ before do
+ @file = provider.new :name => "/foo/bar"
+ end
+
+ it "should create a directory when asked" do
+ Dir.expects(:mkdir).with("/foo/bar")
+ @file.mkdir
+ end
+
+ it "should indicate a problem with the parent directory if Errno::ENOENT is thrown when creating the directory" do
+ Dir.expects(:mkdir).with("/foo/bar").raises Errno::ENOENT
+ lambda { @file.mkdir }.should raise_error(Puppet::Error)
+ end
+
+ it "should fail helpfully if a different problem creating the directory is encountered" do
+ Dir.expects(:mkdir).with("/foo/bar").raises RuntimeError
+ lambda { @file.mkdir }.should raise_error(Puppet::Error)
+ end
+ end
+
+ describe "when changing the file" do
+ before do
+ @file = provider.new :name => "/foo/bar"
+ end
+
+ it "should chown the file to the provided UID when setting the owner" do
+ File.expects(:chown).with(50, nil, "/foo/bar")
+ @file.owner = 50
+ end
+
+ it "should chown the file to the provided GID when setting the group" do
+ File.expects(:chown).with(nil, 50, "/foo/bar")
+ @file.group = 50
+ end
+
+ it "should chmod the file to the provided mode when setting the mode" do
+ File.expects(:chmod).with(493, "/foo/bar") # decimal, not octal
+ @file.mode = 493
+ end
+
+ it "should write the provided contents to the file when the content is set" do
+ fh = mock 'fh'
+ File.expects(:open).with("/foo/bar", "w").yields fh
+ fh.expects(:print).with "foo"
+ @file.content = "foo"
+ end
+ end
+end
diff --git a/spec/unit/type/newfile.rb b/spec/unit/type/newfile.rb
new file mode 100644
index 000000000..6929d51c3
--- /dev/null
+++ b/spec/unit/type/newfile.rb
@@ -0,0 +1,118 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+type = Puppet::Type.type(:newfile)
+
+describe type do
+ describe "when validating attributes" do
+ %w{path}.each do |attr|
+ it "should have a '#{attr}' parameter" do
+ Puppet::Type.type(:newfile).attrtype(attr.intern).should == :param
+ end
+ end
+
+ %w{content ensure owner group mode type}.each do |attr|
+ it "should have a '#{attr}' property" do
+ Puppet::Type.type(:newfile).attrtype(attr.intern).should == :property
+ end
+ end
+
+ it "should have its 'path' attribute set as its namevar" do
+ Puppet::Type.type(:newfile).namevar.should == :path
+ end
+ end
+
+ describe "when validating 'ensure'" do
+ before do
+ @file = type.create :path => "/foo/bar"
+ end
+
+ it "should support 'absent' as a value" do
+ lambda { @file[:ensure] = :absent }.should_not raise_error
+ end
+
+ it "should support 'file' as a value" do
+ lambda { @file[:ensure] = :file }.should_not raise_error
+ end
+
+ it "should support 'directory' as a value" do
+ lambda { @file[:ensure] = :directory }.should_not raise_error
+ end
+
+ it "should support 'link' as a value" do
+ lambda { @file[:ensure] = :link }.should_not raise_error
+ end
+
+ it "should not support other values" do
+ lambda { @file[:ensure] = :foo }.should raise_error(Puppet::Error)
+ end
+ end
+
+ describe "when managing the file's type" do
+ before do
+ @file = type.create :path => "/foo/bar", :ensure => :absent
+
+ # We need a catalog to do our actual work; this kinda blows.
+ @catalog = Puppet::Node::Catalog.new
+ @catalog.add_resource @file
+ Puppet::Util::Log.newdestination :console
+ end
+
+ it "should use the provider's :type method to determine the current file type" do
+ @file.provider.expects(:type).returns :file
+ @file.retrieve
+ end
+
+ it "should use 'destroy' to remove the file" do
+ @file.provider.stubs(:type).returns :file
+ @file.provider.expects(:destroy)
+ @catalog.apply
+ end
+
+ it "should use 'mkfile' to create a file when the file is absent" do
+ @file[:ensure] = :file
+ @file.provider.stubs(:type).returns :absent
+ @file.provider.expects(:mkfile)
+ @catalog.apply
+ end
+
+ it "should destroy the file and then use 'mkfile' to create a file when the file is present" do
+ @file[:ensure] = :file
+ @file.provider.stubs(:type).returns :directory
+ @file.provider.expects(:destroy)
+ @file.provider.expects(:mkfile)
+ @catalog.apply
+ end
+
+ it "should use 'mkdir' to make a directory when the file is absent" do
+ @file[:ensure] = :directory
+ @file.provider.stubs(:type).returns :absent
+ @file.provider.expects(:mkdir)
+ @catalog.apply
+ end
+
+ it "should destroy the file then use 'mkdir' to make a directory when the file is present" do
+ @file[:ensure] = :directory
+ @file.provider.stubs(:type).returns :file
+ @file.provider.expects(:destroy)
+ @file.provider.expects(:mkdir)
+ @catalog.apply
+ end
+
+ it "should use 'mklink' to make a link when the file is absent" do
+ @file[:ensure] = :link
+ @file.provider.stubs(:type).returns :absent
+ @file.provider.expects(:mklink)
+ @catalog.apply
+ end
+
+ it "should destroy the file then use 'mklink' to make a link when the file is present" do
+ @file[:ensure] = :link
+ @file.provider.stubs(:type).returns :file
+ @file.provider.expects(:destroy)
+ @file.provider.expects(:mklink)
+ @catalog.apply
+ end
+ end
+end
diff --git a/spec/unit/util/state_machine.rb b/spec/unit/util/state_machine.rb
new file mode 100755
index 000000000..e8ffc629d
--- /dev/null
+++ b/spec/unit/util/state_machine.rb
@@ -0,0 +1,59 @@
+#!/usr/bin/env ruby
+
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'puppet/util/state_machine'
+
+describe Puppet::Util::StateMachine do
+ before do
+ @class = Puppet::Util::StateMachine
+ @machine = @class.new
+ end
+
+ it "should instance_eval any provided block" do
+ f = @class.new { state(:foo, "foo") }
+ f.should be_state(:foo)
+ end
+
+ it "should be able to declare states" do
+ @machine.should respond_to(:state)
+ end
+
+ it "should require documentation when declaring states" do
+ lambda { @machine.state(:foo) }.should raise_error(ArgumentError)
+ end
+
+ it "should be able to detect when states are set" do
+ @machine.state(:foo, "bar")
+ @machine.should be_state(:foo)
+ end
+
+ it "should be able to declare transitions" do
+ @machine.should respond_to(:transition)
+ end
+
+ describe "when adding transitions" do
+ it "should fail if the starting state of a transition is unknown" do
+ @machine.state(:end, "foo")
+ lambda { @machine.transition(:start, :end) }.should raise_error(ArgumentError)
+ end
+
+ it "should fail if the ending state of a transition is unknown" do
+ @machine.state(:start, "foo")
+ lambda { @machine.transition(:start, :end) }.should raise_error(ArgumentError)
+ end
+
+ it "should fail if an equivalent transition already exists" do
+ @machine.state(:start, "foo")
+ @machine.state(:end, "foo")
+ @machine.transition(:start, :end)
+ lambda { @machine.transition(:start, :end) }.should raise_error(ArgumentError)
+ end
+ end
+
+ describe "when making a transition" do
+ it "should require the initial state"
+
+ it "should return all of the transitions to be made"
+ end
+end