First (basic) working version of ini_setting provider
This commit is contained in:
parent
91273a5a2b
commit
9c76b6af12
8 changed files with 341 additions and 135 deletions
|
@ -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
|
|
@ -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
|
24
lib/puppet/util/external_iterator.rb
Normal file
24
lib/puppet/util/external_iterator.rb
Normal file
|
@ -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
|
132
lib/puppet/util/ini_file.rb
Normal file
132
lib/puppet/util/ini_file.rb
Normal file
|
@ -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
|
34
lib/puppet/util/ini_file/section.rb
Normal file
34
lib/puppet/util/ini_file/section.rb
Normal file
|
@ -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
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
47
spec/unit/puppet/util/ini_file_spec.rb
Normal file
47
spec/unit/puppet/util/ini_file_spec.rb
Normal file
|
@ -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
|
Loading…
Reference in a new issue