summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChris Price <chris@puppetlabs.com>2012-07-28 21:59:54 -0700
committerChris Price <chris@puppetlabs.com>2012-07-28 21:59:54 -0700
commit9c76b6af1200c71e7ae72e0e2f349919a3081738 (patch)
treee3dc5ca1824f1fcf74c08e78d649eed394408a78
parent91273a5a2b0c88e7129908406fd0933a0d88ef11 (diff)
downloadpuppet-inifile-9c76b6af1200c71e7ae72e0e2f349919a3081738.tar.gz
puppet-inifile-9c76b6af1200c71e7ae72e0e2f349919a3081738.tar.bz2
First (basic) working version of ini_setting provider
-rw-r--r--lib/puppet/provider/ini_setting/ruby.rb19
-rw-r--r--lib/puppet/type/ini_setting.rb26
-rw-r--r--lib/puppet/util/external_iterator.rb24
-rw-r--r--lib/puppet/util/ini_file.rb132
-rw-r--r--lib/puppet/util/ini_file/section.rb34
-rw-r--r--spec/spec_helper.rb8
-rw-r--r--spec/unit/puppet/provider/ini_setting/ruby_spec.rb186
-rw-r--r--spec/unit/puppet/util/ini_file_spec.rb47
8 files changed, 341 insertions, 135 deletions
diff --git a/lib/puppet/provider/ini_setting/ruby.rb b/lib/puppet/provider/ini_setting/ruby.rb
index e69de29..e44a107 100644
--- a/lib/puppet/provider/ini_setting/ruby.rb
+++ b/lib/puppet/provider/ini_setting/ruby.rb
@@ -0,0 +1,19 @@
+require 'puppet/util/ini_file'
+
+Puppet::Type.type(:ini_setting).provide(:ruby) do
+ def exists?
+ ini_file.get_value(resource[:section], resource[:setting]) == resource[:value]
+ end
+
+ def create
+ ini_file.set_value(resource[:section], resource[:setting], resource[:value])
+ ini_file.save
+ @ini_file = nil
+ end
+
+
+ private
+ def ini_file
+ @ini_file ||= Puppet::Util::IniFile.new(resource[:path])
+ end
+end
diff --git a/lib/puppet/type/ini_setting.rb b/lib/puppet/type/ini_setting.rb
index fb7f6e2..9af47c1 100644
--- a/lib/puppet/type/ini_setting.rb
+++ b/lib/puppet/type/ini_setting.rb
@@ -4,4 +4,30 @@ Puppet::Type.newtype(:ini_setting) do
defaultvalues
defaultto :present
end
+
+ newparam(:name, :namevar => true) do
+ desc 'An arbitrary name used as the identity of the resource.'
+ end
+
+ newparam(:section) do
+ desc 'The name of the section in the ini file in which the setting should be defined.'
+ end
+
+ newparam(:setting) do
+ desc 'The name of the setting to be defined.'
+ end
+
+ newparam(:value) do
+ desc 'The value of the setting to be defined.'
+ end
+
+ newparam(:path) do
+ desc 'The ini file Puppet will ensure contains the specified setting.'
+ validate do |value|
+ unless (Puppet.features.posix? and value =~ /^\//) or (Puppet.features.microsoft_windows? and (value =~ /^.:\// or value =~ /^\/\/[^\/]+\/[^\/]+/))
+ raise(Puppet::Error, "File paths must be fully qualified, not '#{value}'")
+ end
+ end
+ end
+
end \ No newline at end of file
diff --git a/lib/puppet/util/external_iterator.rb b/lib/puppet/util/external_iterator.rb
new file mode 100644
index 0000000..67b3375
--- /dev/null
+++ b/lib/puppet/util/external_iterator.rb
@@ -0,0 +1,24 @@
+module Puppet
+module Util
+ class ExternalIterator
+ def initialize(coll)
+ @coll = coll
+ @cur_index = 0
+ end
+
+ def next
+ @cur_index = @cur_index + 1
+ item_at(@cur_index)
+ end
+
+ def peek
+ item_at(@cur_index + 1)
+ end
+
+ private
+ def item_at(index)
+ [@coll[index], index]
+ end
+ end
+end
+end
diff --git a/lib/puppet/util/ini_file.rb b/lib/puppet/util/ini_file.rb
new file mode 100644
index 0000000..75d8a9f
--- /dev/null
+++ b/lib/puppet/util/ini_file.rb
@@ -0,0 +1,132 @@
+require 'puppet/util/external_iterator'
+require 'puppet/util/ini_file/section'
+
+module Puppet
+module Util
+ class IniFile
+
+ SECTION_REGEX = /^\s*\[([\w\d\.]+)\]\s*$/
+ SETTING_REGEX = /^\s*([\w\d\.]+)\s*=\s*([\w\d\.]+)\s*$/
+
+ def initialize(path)
+ @path = path
+ @section_names = []
+ @sections_hash = {}
+
+ parse_file
+ end
+
+ def section_names
+ @section_names
+ end
+
+ def get_value(section_name, setting)
+ if (@sections_hash.has_key?(section_name))
+ @sections_hash[section_name].get_value(setting)
+ end
+ end
+
+ def set_value(section_name, setting, value)
+ unless (@sections_hash.has_key?(section_name))
+ add_section(Section.new(section_name, nil, nil, nil))
+ end
+
+ section = @sections_hash[section_name]
+ if (section.has_existing_setting?(setting))
+ update_line(section, setting, value)
+ section.update_existing_setting(setting, value)
+ else
+ section.set_additional_setting(setting, value)
+ end
+ end
+
+ def save
+ File.open(@path, 'w') do |fh|
+ first_section = @sections_hash[@section_names[0]]
+ (0..first_section.start_line - 1).each do |line_num|
+ fh.puts(lines[line_num])
+ end
+
+ @section_names.each do |name|
+ section = @sections_hash[name]
+
+ if (section.start_line.nil?)
+ fh.puts("\n[#{section.name}]")
+ else
+ (section.start_line..section.end_line).each do |line_num|
+ fh.puts(lines[line_num])
+ end
+ end
+
+ section.additional_settings.each_pair do |key, value|
+ fh.puts("#{key} = #{value}")
+ end
+ end
+ end
+ end
+
+
+ private
+ def add_section(section)
+ @sections_hash[section.name] = section
+ @section_names << section.name
+ end
+
+ def parse_file
+ line_iter = create_line_iter
+ line, line_num = line_iter.next
+ while line
+ if (match = SECTION_REGEX.match(line))
+ section = read_section(match[1], line_num, line_iter)
+ add_section(section)
+ end
+ line, line_num = line_iter.next
+ end
+ end
+
+ def read_section(name, start_line, line_iter)
+ settings = {}
+ while true
+ line, line_num = line_iter.peek
+ if (line.nil? or match = SECTION_REGEX.match(line))
+ return Section.new(name, start_line, line_num - 1, settings)
+ elsif (match = SETTING_REGEX.match(line))
+ settings[match[1]] = match[2]
+ end
+
+ line_iter.next
+ end
+ end
+
+ def update_line(section, setting, value)
+ (section.start_line..section.end_line).each do |line_num|
+ if (match = SETTING_REGEX.match(lines[line_num]))
+ if (match[1] == setting)
+ lines[line_num] = "#{setting} = #{value}"
+ end
+ end
+ end
+ end
+
+ def create_line_iter
+ ExternalIterator.new(lines)
+ end
+
+ def lines
+ @lines ||= IniFile.readlines(@path)
+ end
+
+ # This is mostly here because it makes testing easier--we don't have
+ # to try to stub any methods on File.
+ def self.readlines(path)
+ # If this type is ever used with very large files, we should
+ # write this in a different way, using a temp
+ # file; for now assuming that this type is only used on
+ # small-ish config files that can fit into memory without
+ # too much trouble.
+ File.readlines(path)
+ end
+
+ end
+end
+end \ No newline at end of file
diff --git a/lib/puppet/util/ini_file/section.rb b/lib/puppet/util/ini_file/section.rb
new file mode 100644
index 0000000..39f2959
--- /dev/null
+++ b/lib/puppet/util/ini_file/section.rb
@@ -0,0 +1,34 @@
+module Puppet
+module Util
+class IniFile
+ class Section
+ def initialize(name, start_line, end_line, settings)
+ @name = name
+ @start_line = start_line
+ @end_line = end_line
+ @existing_settings = settings.nil? ? {} : settings
+ @additional_settings = {}
+ end
+
+ attr_reader :name, :start_line, :end_line, :additional_settings
+
+ def get_value(setting_name)
+ @existing_settings[setting_name] || @additional_settings[setting_name]
+ end
+
+ def has_existing_setting?(setting_name)
+ @existing_settings.has_key?(setting_name)
+ end
+
+ def update_existing_setting(setting_name, value)
+ @existing_settings[setting_name] = value
+ end
+
+ def set_additional_setting(setting_name, value)
+ @additional_settings[setting_name] = value
+ end
+
+ end
+end
+end
+end \ No newline at end of file
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 3ded441..ddbcd6e 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,4 +1,8 @@
-dir = File.expand_path(File.dirname(__FILE__))
-$LOAD_PATH.unshift File.join(dir, 'lib')
+gem 'rspec', '>=2.0.0'
+require 'rspec/expectations'
+
require 'puppetlabs_spec_helper/puppetlabs_spec_helper'
+
+require 'puppetlabs_spec_helper/puppetlabs_spec/files'
+
diff --git a/spec/unit/puppet/provider/ini_setting/ruby_spec.rb b/spec/unit/puppet/provider/ini_setting/ruby_spec.rb
index 7eb4c6d..91d3050 100644
--- a/spec/unit/puppet/provider/ini_setting/ruby_spec.rb
+++ b/spec/unit/puppet/provider/ini_setting/ruby_spec.rb
@@ -5,7 +5,6 @@ provider_class = Puppet::Type.type(:ini_setting).provider(:ruby)
describe provider_class do
include PuppetlabsSpec::Files
-
let(:tmpfile) { tmpfilename("ini_setting_test") }
let(:orig_content) {
<<-EOS
@@ -38,20 +37,15 @@ baz=bazvalue
context "when ensuring that a setting is present" do
let(:common_params) { {
:title => 'ini_setting_ensure_present_test',
- :file => tmpfile,
+ :path => tmpfile,
:section => 'section2',
} }
it "should add a missing setting to the correct section" do
- puts "common params (#{common_params.class}:"
- require 'pp'
- pp common_params
resource = Puppet::Type::Ini_setting.new(common_params.merge(
:setting => 'yahoo', :value => 'yippee'))
- puts "parse title..."
- pp resource.parse_title
provider = described_class.new(resource)
- provider.exists?.should be_nil
+ provider.exists?.should == false
provider.create
validate_file(<<-EOS
# This is a comment
@@ -72,133 +66,59 @@ yahoo = yippee
end
it "should modify an existing setting with a different value" do
- fail
+ resource = Puppet::Type::Ini_setting.new(common_params.merge(
+ :setting => 'baz', :value => 'bazvalue2'))
+ provider = described_class.new(resource)
+ provider.exists?.should == false
+ provider.create
+ validate_file(<<-EOS
+# This is a comment
+[section1]
+; This is also a comment
+foo=foovalue
+
+bar = barvalue
+[section2]
+
+foo= foovalue2
+baz = bazvalue2
+ #another comment
+ ; yet another comment
+ EOS
+ )
end
- it "should recognize an existing setting with the specified value and leave it intact" do
- fail
+ it "should recognize an existing setting with the specified value" do
+ resource = Puppet::Type::Ini_setting.new(common_params.merge(
+ :setting => 'baz', :value => 'bazvalue'))
+ provider = described_class.new(resource)
+ provider.exists?.should == true
+ end
+
+ it "should add a new section if the section does not exist" do
+ resource = Puppet::Type::Ini_setting.new(common_params.merge(
+ :section => "section3", :setting => 'huzzah', :value => 'shazaam'))
+ provider = described_class.new(resource)
+ provider.exists?.should == false
+ provider.create
+ validate_file(<<-EOS
+# This is a comment
+[section1]
+; This is also a comment
+foo=foovalue
+
+bar = barvalue
+[section2]
+
+foo= foovalue2
+baz=bazvalue
+ #another comment
+ ; yet another comment
+
+[section3]
+huzzah = shazaam
+ EOS
+ )
end
end
- #it "should pass" do
- # File.read(@tmpfile).should == orig_content
- #end
-
- #context "when adding" do
- # before :each do
- # #tmp = tmpfilename
- # #
- # #@resource = Puppet::Type::File_line.new(
- # # {:name => 'foo', :path => @tmpfile, :line => 'foo'}
- # #)
- # #@provider = provider_class.new(@resource)
- # end
- # it 'should detect if the line exists in the file' do
- # File.open(@tmpfile, 'w') do |fh|
- # fh.write('foo')
- # end
- # @provider.exists?.should be_true
- # end
- # it 'should detect if the line does not exist in the file' do
- # File.open(@tmpfile, 'w') do |fh|
- # fh.write('foo1')
- # end
- # @provider.exists?.should be_nil
- # end
- # it 'should append to an existing file when creating' do
- # @provider.create
- # File.read(@tmpfile).chomp.should == 'foo'
- # end
- #end
- #
- #context "when matching" do
- # before :each do
- # # TODO: these should be ported over to use the PuppetLabs spec_helper
- # # file fixtures once the following pull request has been merged:
- # # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files
- # tmp = Tempfile.new('tmp')
- # @tmpfile = tmp.path
- # tmp.close!
- # @resource = Puppet::Type::File_line.new(
- # {
- # :name => 'foo',
- # :path => @tmpfile,
- # :line => 'foo = bar',
- # :match => '^foo\s*=.*$',
- # }
- # )
- # @provider = provider_class.new(@resource)
- # end
- #
- # it 'should raise an error if more than one line matches, and should not have modified the file' do
- # File.open(@tmpfile, 'w') do |fh|
- # fh.write("foo1\nfoo=blah\nfoo2\nfoo=baz")
- # end
- # @provider.exists?.should be_nil
- # expect { @provider.create }.to raise_error(Puppet::Error, /More than one line.*matches/)
- # File.read(@tmpfile).should eql("foo1\nfoo=blah\nfoo2\nfoo=baz")
- # end
- #
- # it 'should replace a line that matches' do
- # File.open(@tmpfile, 'w') do |fh|
- # fh.write("foo1\nfoo=blah\nfoo2")
- # end
- # @provider.exists?.should be_nil
- # @provider.create
- # File.read(@tmpfile).chomp.should eql("foo1\nfoo = bar\nfoo2")
- # end
- # it 'should add a new line if no lines match' do
- # File.open(@tmpfile, 'w') do |fh|
- # fh.write("foo1\nfoo2")
- # end
- # @provider.exists?.should be_nil
- # @provider.create
- # File.read(@tmpfile).should eql("foo1\nfoo2\nfoo = bar\n")
- # end
- # it 'should do nothing if the exact line already exists' do
- # File.open(@tmpfile, 'w') do |fh|
- # fh.write("foo1\nfoo = bar\nfoo2")
- # end
- # @provider.exists?.should be_true
- # @provider.create
- # File.read(@tmpfile).chomp.should eql("foo1\nfoo = bar\nfoo2")
- # end
- #end
- #
- #context "when removing" do
- # before :each do
- # # TODO: these should be ported over to use the PuppetLabs spec_helper
- # # file fixtures once the following pull request has been merged:
- # # https://github.com/puppetlabs/puppetlabs-stdlib/pull/73/files
- # tmp = Tempfile.new('tmp')
- # @tmpfile = tmp.path
- # tmp.close!
- # @resource = Puppet::Type::File_line.new(
- # {:name => 'foo', :path => @tmpfile, :line => 'foo', :ensure => 'absent' }
- # )
- # @provider = provider_class.new(@resource)
- # end
- # it 'should remove the line if it exists' do
- # File.open(@tmpfile, 'w') do |fh|
- # fh.write("foo1\nfoo\nfoo2")
- # end
- # @provider.destroy
- # File.read(@tmpfile).should eql("foo1\nfoo2")
- # end
- #
- # it 'should remove the line without touching the last new line' do
- # File.open(@tmpfile, 'w') do |fh|
- # fh.write("foo1\nfoo\nfoo2\n")
- # end
- # @provider.destroy
- # File.read(@tmpfile).should eql("foo1\nfoo2\n")
- # end
- #
- # it 'should remove any occurence of the line' do
- # File.open(@tmpfile, 'w') do |fh|
- # fh.write("foo1\nfoo\nfoo2\nfoo\nfoo")
- # end
- # @provider.destroy
- # File.read(@tmpfile).should eql("foo1\nfoo2\n")
- # end
- #end
end
diff --git a/spec/unit/puppet/util/ini_file_spec.rb b/spec/unit/puppet/util/ini_file_spec.rb
new file mode 100644
index 0000000..7e7458a
--- /dev/null
+++ b/spec/unit/puppet/util/ini_file_spec.rb
@@ -0,0 +1,47 @@
+require 'spec_helper'
+require 'puppet/util/ini_file'
+
+describe Puppet::Util::IniFile do
+ context "when parsing a file" do
+ let(:subject) { Puppet::Util::IniFile.new("/my/ini/file/path") }
+ let(:sample_content) {
+ template = <<-EOS
+# This is a comment
+[section1]
+; This is also a comment
+foo=foovalue
+
+bar = barvalue
+[section2]
+
+foo= foovalue2
+baz=bazvalue
+ #another comment
+ ; yet another comment
+ EOS
+ template.split("\n")
+ }
+
+ before :each do
+ described_class.should_receive(:readlines).once.with("/my/ini/file/path") do
+ sample_content
+ end
+ end
+
+ it "should parse the correct number of sections" do
+ subject.section_names.length.should == 2
+ end
+
+ it "should parse the correct section_names" do
+ subject.section_names.should == ["section1", "section2"]
+ end
+
+ it "should expose settings for sections" do
+ subject.get_value("section1", "foo").should == "foovalue"
+ subject.get_value("section1", "bar").should == "barvalue"
+ subject.get_value("section2", "foo").should == "foovalue2"
+ subject.get_value("section2", "baz").should == "bazvalue"
+ end
+
+ end
+end \ No newline at end of file