puppetlabs-inifile icon indicating copy to clipboard operation
puppetlabs-inifile copied to clipboard

Dangling section headers persist in INI files when sections contain whitespace

Open peytonw1 opened this issue 1 year ago • 3 comments

Describe the Bug

When removing the only setting in a section, the section header still remains when the section contains whitespace. As a result, trailing section headers (i.e. sections without any settings) will persist in INI files like below and eventually cause unnecessary clutter:

# cat /tmp/test.ini
[section1]

[section2]

Expected Behavior

We would expect to see section headers to be removed when we are removing the last and only setting in a section.

Steps to Reproduce

Here is an example of a section with a singular setting, in which the empty section header still remains upon ensuring the setting is absent:

  1. We created two files (/tmp/test.ini, /tmp/test_remove_setting.pp) as shown below, in which we aimed to remove setting1 and the [section1] header:
# cat /tmp/test.ini
[section1]
setting1 = value1

[section2]
setting2 = value2
# cat /tmp/test_remove_setting.pp
ini_setting { 'remove setting1':
  ensure  => 'absent',
  path    => '/tmp/test.ini',
  section => 'section1',
  setting => 'setting1',
}
  1. Applied Puppet:
# puppet apply /tmp/test_remove_setting.pp
Notice: Compiled catalog for <redacted> in environment production in 0.09 seconds
Notice: /Stage[main]/Main/Ini_setting[remove setting1]/ensure: removed
Notice: Applied catalog in 0.69 seconds
  1. Observe that while setting1 was removed, the [section1] header still persisted:
# cat /tmp/test.ini
[section1]
[section2]
setting2 = value2

However, when removing the newline after setting1, both the setting AND section header are removed correctly:

  1. Modified test.ini to not include a newline after the first setting:
# cat /tmp/test.ini
[section1]
setting1 = value1
[section2]
setting2 = value2
  1. Applied Puppet:
# puppet apply /tmp/test_remove_setting.pp
Notice: Compiled catalog for <redacted> in environment production in 0.04 seconds
Notice: /Stage[main]/Main/Ini_setting[remove setting1]/ensure: removed
Notice: Applied catalog in 0.28 seconds
  1. Observe that both setting1 and the [section1] header are removed as expected:
# cat /tmp/test.ini
[section2]
setting2 = value2

Environment

  • Version: 7.14.0
  • Platform: Ubuntu 20.04.1

Additional Context

We also stepped through the puppetlabs-inifile code with pry to observe the IniFile::Section object in the reproduced problem (steps 1-3 above). Initially, we saw that section1 consisted of @start_line=0 and @end_line=2.

[4] pry(#<Puppet::Util::IniFile>)> section
=> #<Puppet::Util::IniFile::Section:<redacted> @additional_settings={}, @end_line=2, @existing_settings={"setting1"=>"value1"}, @indentation=0, @name="section1", @start_line=0>

The start and end lines correspond to the lines with the [section1] header and the newline before the [section2] header, respectively, in the test.ini file:

[5] pry(#<Puppet::Util::IniFile>)> @lines
=> ["[section1]\n", "setting1 = value1\n", "\n", "[section2]\n", "setting2 = value2\n", "\n"]

After removing setting1, we observed in remove_setting() that the section was not registered as empty.

[11] pry(#<Puppet::Util::IniFile>)> next

From: /opt/puppetlabs/puppet/cache/lib/puppet/util/ini_file.rb:137 Puppet::Util::IniFile#remove_setting:

    120: def remove_setting(section_name, setting)
    121:   section = @sections_hash[section_name]
    122:   return unless section.existing_setting?(setting)
    123:   # If the setting is found, we have some work to do.
    124:   # First, we remove the line from our array of lines:
    125:   remove_line(section, setting)
    126:
    127:   # Then, we need to tell the setting object to remove
    128:   # the setting from its state:
    129:   section.remove_existing_setting(setting)
    130:
    131:   # Finally, we need to update all of the start/end line
    132:   # numbers for all of the sections *after* the one that
    133:   # was modified.
    134:   section_index = @section_names.index(section_name)
    135:   decrement_section_line_numbers(section_index + 1)
    136:
 => 137:   return unless section.empty?
    138:   # By convention, it's time to remove this newly emptied out section
    139:   lines.delete_at(section.start_line)
    140:   decrement_section_line_numbers(section_index + 1)
    141:   @section_names.delete_at(section_index)
    142:   @sections_hash.delete(section.name)
    143: end
[12] pry(#<Puppet::Util::IniFile>)> section.empty?
=> false

However, looking at the final state of the Puppet::Util::IniFile::Section object, we saw that section1 now had @start_line=0 and @end_line=1.

[11] pry(#<Puppet::Util::IniFile>)> section
=> #<Puppet::Util::IniFile::Section:<redacted> @additional_settings={}, @end_line=1, @existing_settings={}, @indentation=0, @name="section1", @start_line=0>
[13] pry(#<Puppet::Util::IniFile>)> @lines
=> ["[section1]\n", "\n", "[section2]\n", "setting2 = value2\n", "\n"]

Furthermore, in the section.rb code here, the section.empty? function returns true if the section's start_line == end_line. Thus, this shows that sections containing whitespace are not getting removed correctly, as the code counts the whitespace in the start and end lines to determine if the section is empty.

peytonw1 avatar Oct 16 '23 02:10 peytonw1

We would expect to see section headers to be removed when we are removing the last and only setting in a section.

As discussed here, I think that auto-pruning empty sections is beyond the scope of the ini_setting type, and that removal should rather be explicitly requested with a dedicated ini_section type.

My proposal would be to remove the auto-removing of empty sections from ini_setting and introduce a new ini_section type that can be used to add / remove sections regardless of their content:

ini_section { 'section1':
  ensure => absent,
}

@peytonw1 would this allow you to do what you want?

smortex avatar Mar 31 '24 23:03 smortex

@smortex yes that would, thanks!

peytonw1 avatar Apr 02 '24 18:04 peytonw1

@peytonw1 cool! There was some discussion here in case you missed it, with links to PR that fix the issue: https://github.com/puppetlabs/puppetlabs-inifile/pull/532#issuecomment-2030671985

smortex avatar Apr 02 '24 19:04 smortex