Skip to content

Commit fd331f1

Browse files
committed
Add new ini_section type
In order to explicitly manage sections of an ini file regardless of their content, we introduce a new ini_section type. This make it possible to remove a whole section of an ini file, regardless of its content: ```puppet ini_section { 'remove puppet.conf agent section': ensure => abent, path => '/etc/puppetlabs/puppet/puppet.conf', section => 'agent', } ``` Just like the ini_setting type, ini_section can be subclassed: ```puppet puppet_config { 'remove agent section': ensure => abent, section => 'agent', } ``` Fixes #524
1 parent 085e4a9 commit fd331f1

File tree

9 files changed

+637
-5
lines changed

9 files changed

+637
-5
lines changed
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# frozen_string_literal: true
2+
3+
require File.expand_path('../../util/ini_file', __dir__)
4+
5+
Puppet::Type.type(:ini_section).provide(:ruby) do
6+
def self.instances
7+
desc '
8+
Creates new ini_section file, a specific config file with a provider that uses
9+
this as its parent and implements the method
10+
self.file_path, and that will provide the value for the path to the
11+
ini file.'
12+
raise(Puppet::Error, 'Ini_section only support collecting instances when a file path is hard coded') unless respond_to?(:file_path)
13+
14+
# figure out what to do about the seperator
15+
ini_file = Puppet::Util::IniFile.new(file_path, '=')
16+
resources = []
17+
ini_file.section_names.each do |section_name|
18+
next if section_name.empty?
19+
resources.push(
20+
new(
21+
name: namevar(section_name),
22+
ensure: :present,
23+
),
24+
)
25+
end
26+
resources
27+
end
28+
29+
def self.namevar(section_name)
30+
section_name
31+
end
32+
33+
def exists?
34+
ini_file.section_names.include?(section)
35+
end
36+
37+
def create
38+
ini_file.set_value(section)
39+
ini_file.save
40+
@ini_file = nil
41+
end
42+
43+
def destroy
44+
ini_file.remove_section(section)
45+
ini_file.save
46+
@ini_file = nil
47+
end
48+
49+
def file_path
50+
# this method is here to support purging and sub-classing.
51+
# if a user creates a type and subclasses our provider and provides a
52+
# 'file_path' method, then they don't have to specify the
53+
# path as a parameter for every ini_section declaration.
54+
# This implementation allows us to support that while still
55+
# falling back to the parameter value when necessary.
56+
if self.class.respond_to?(:file_path)
57+
self.class.file_path
58+
else
59+
resource[:path]
60+
end
61+
end
62+
63+
def section
64+
# this method is here so that it can be overridden by a child provider
65+
resource[:section]
66+
end
67+
68+
def section_prefix
69+
if resource.class.validattr?(:section_prefix)
70+
resource[:section_prefix] || '['
71+
else
72+
'['
73+
end
74+
end
75+
76+
def section_suffix
77+
if resource.class.validattr?(:section_suffix)
78+
resource[:section_suffix] || ']'
79+
else
80+
']'
81+
end
82+
end
83+
84+
private
85+
86+
def ini_file
87+
@ini_file ||= Puppet::Util::IniFile.new(file_path, '=', section_prefix, section_suffix, ' ', 2)
88+
end
89+
end

lib/puppet/type/ini_section.rb

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# frozen_string_literal: true
2+
3+
Puppet::Type.newtype(:ini_section) do
4+
desc 'ini_section is used to manage a single section in an INI file'
5+
ensurable do
6+
desc 'Ensurable method handles modeling creation. It creates an ensure property'
7+
newvalue(:present) do
8+
provider.create
9+
end
10+
newvalue(:absent) do
11+
provider.destroy
12+
end
13+
def insync?(current)
14+
if @resource[:refreshonly]
15+
true
16+
else
17+
current == should
18+
end
19+
end
20+
defaultto :present
21+
end
22+
23+
newparam(:name, namevar: true) do
24+
desc 'An arbitrary name used as the identity of the resource.'
25+
end
26+
27+
newparam(:section) do
28+
desc 'The name of the section in the ini file which should be managed.'
29+
defaultto('')
30+
end
31+
32+
newparam(:path) do
33+
desc 'The ini file Puppet will ensure contains the specified section.'
34+
validate do |value|
35+
raise(Puppet::Error, _("File paths must be fully qualified, not '%{value}'") % { value: value }) unless Puppet::Util.absolute_path?(value)
36+
end
37+
end
38+
39+
newparam(:section_prefix) do
40+
desc 'The prefix to the section name\'s header.'
41+
defaultto('[')
42+
end
43+
44+
newparam(:section_suffix) do
45+
desc 'The suffix to the section name\'s header.'
46+
defaultto(']')
47+
end
48+
49+
autorequire(:file) do
50+
Pathname.new(self[:path]).parent.to_s
51+
end
52+
end

lib/puppet/util/ini_file.rb

+15-2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ def remove_setting(section_name, setting)
124124
decrement_section_line_numbers(section_index + 1)
125125
end
126126

127+
def remove_section(section_name)
128+
section = @sections_hash[section_name]
129+
return unless section
130+
131+
lines.replace(lines[0..(section.start_line - 1)] + lines[(section.end_line + 1)..-1])
132+
133+
section_index = @section_names.index(section.name)
134+
decrement_section_line_numbers(section_index + 1, amount: section.length)
135+
136+
@section_names.delete_at(section_index)
137+
@sections_hash.delete(section.name)
138+
end
139+
127140
def save
128141
global_empty = @sections_hash[''].empty? && @sections_hash[''].additional_settings.empty?
129142
File.open(@path, 'w') do |fh|
@@ -300,10 +313,10 @@ def insert_inline_setting_line(result, section, complete_setting)
300313
# Utility method; given a section index (index into the @section_names
301314
# array), decrement the start/end line numbers for that section and all
302315
# all of the other sections that appear *after* the specified section.
303-
def decrement_section_line_numbers(section_index)
316+
def decrement_section_line_numbers(section_index, amount: 1)
304317
@section_names[section_index..(@section_names.length - 1)].each do |name|
305318
section = @sections_hash[name]
306-
section.decrement_line_nums
319+
section.decrement_line_nums(amount)
307320
end
308321
end
309322

lib/puppet/util/ini_file/section.rb

+7-3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ def empty?
5353
global? ? new_section? : start_line == end_line
5454
end
5555

56+
def length
57+
end_line - start_line + 1
58+
end
59+
5660
def update_existing_setting(setting_name, value)
5761
@existing_settings[setting_name] = value
5862
end
@@ -79,9 +83,9 @@ def set_additional_setting(setting_name, value)
7983
# Decrement the start and end line numbers for the section (if they are
8084
# defined); this is intended to be called when a setting is removed
8185
# from a section that comes before this section in the ini file.
82-
def decrement_line_nums
83-
@start_line -= 1 if @start_line
84-
@end_line -= 1 if @end_line
86+
def decrement_line_nums(amount)
87+
@start_line -= amount if @start_line
88+
@end_line -= amount if @end_line
8589
end
8690

8791
# Increment the start and end line numbers for the section (if they are
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Puppet::Type.type(:inherit_ini_section).provide(
2+
:ini_section,
3+
parent: Puppet::Type.type(:ini_section).provider(:ruby),
4+
) do
5+
def self.namevar(section)
6+
section
7+
end
8+
9+
def self.file_path
10+
File.expand_path(File.dirname(__FILE__) + '/../../../../../../tmp/inherit_inifile.cfg')
11+
end
12+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Puppet::Type.newtype(:inherit_ini_section) do
2+
ensurable
3+
newparam(:section, namevar: true)
4+
newproperty(:value)
5+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
# This is a reduced version of ruby_spec.rb just to ensure we can subclass as
6+
# documented
7+
$LOAD_PATH << './spec/fixtures/inherit_ini_section/lib'
8+
9+
describe Puppet::Type.type(:inherit_ini_section).provider(:ini_section) do
10+
include PuppetlabsSpec::Files
11+
12+
let(:tmpfile) { tmpfilename('inherit_ini_section_test') }
13+
14+
def validate_file(expected_content, tmpfile)
15+
expect(File.read(tmpfile)).to eq(expected_content)
16+
end
17+
18+
before :each do
19+
File.write(tmpfile, orig_content)
20+
end
21+
22+
context 'when calling instances' do
23+
let(:orig_content) { '' }
24+
25+
it 'parses nothing when the file is empty' do
26+
allow(described_class).to receive(:file_path).and_return(tmpfile)
27+
expect(described_class.instances).to eq([])
28+
end
29+
30+
context 'when the file has contents' do
31+
let(:orig_content) do
32+
<<-INIFILE
33+
[red]
34+
# A comment
35+
red = blue
36+
[green]
37+
green = purple
38+
INIFILE
39+
end
40+
41+
it 'parses the results' do
42+
allow(described_class).to receive(:file_path).and_return(tmpfile)
43+
instances = described_class.instances
44+
expect(instances.size).to eq(2)
45+
# inherited version of namevar flattens the names
46+
names = instances.map do |instance| instance.instance_variable_get(:@property_hash)[:name] end # rubocop:disable Style/BlockDelimiters
47+
expect(names.sort).to eq(['green', 'red'])
48+
end
49+
end
50+
end
51+
52+
context 'when ensuring that a setting is present' do
53+
let(:orig_content) { '' }
54+
55+
it 'adds a value to the file' do
56+
allow(described_class).to receive(:file_path).and_return(tmpfile)
57+
resource = Puppet::Type::Inherit_ini_section.new(section: 'set_this')
58+
provider = described_class.new(resource)
59+
provider.create
60+
expect(validate_file("[set_this]\n", tmpfile)).to be_truthy
61+
end
62+
end
63+
end

0 commit comments

Comments
 (0)