diff --git a/lib/puppet/functions/create_ini_settings.rb b/lib/puppet/functions/create_ini_settings.rb index 8cd5ab3..ba6b156 100644 --- a/lib/puppet/functions/create_ini_settings.rb +++ b/lib/puppet/functions/create_ini_settings.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + # @summary DEPRECATED. Use the namespaced function [`inifile::create_ini_settings`](#inifilecreate_ini_settings) instead. Puppet::Functions.create_function(:create_ini_settings) do dispatch :deprecation_gen do repeated_param 'Any', :args end def deprecation_gen(*args) call_function('deprecation', 'create_ini_settings', 'This method is deprecated, please use inifile::create_ini_settings instead.') call_function('inifile::create_ini_settings', *args) end end diff --git a/lib/puppet/functions/inifile/create_ini_settings.rb b/lib/puppet/functions/inifile/create_ini_settings.rb index 7a045fc..072e3a7 100644 --- a/lib/puppet/functions/inifile/create_ini_settings.rb +++ b/lib/puppet/functions/inifile/create_ini_settings.rb @@ -1,37 +1,39 @@ +# frozen_string_literal: true + # @summary This function is used to create a set of ini_setting resources from a hash Puppet::Functions.create_function(:'inifile::create_ini_settings') do # @param settings # A hash of settings you want to create ini_setting resources from # @param defaults # A hash of defaults you would like to use in the ini_setting resources dispatch :default_impl do param 'Hash', :settings optional_param 'Hash', :defaults end def default_impl(settings, defaults = {}) resources = settings.keys.each_with_object({}) do |section, res| unless settings[section].is_a?(Hash) raise(Puppet::ParseError, _('create_ini_settings(): Section %{section} must contain a Hash') % { section: section }) end path = defaults.merge(settings)['path'] raise Puppet::ParseError, _('create_ini_settings(): must pass the path parameter to the Ini_setting resource!') unless path settings[section].each do |setting, value| res["#{path} [#{section}] #{setting}"] = { 'ensure' => 'present', 'section' => section, 'setting' => setting, }.merge(if value.is_a?(Hash) value else { 'value' => value } end) end end call_function('create_resources', 'ini_setting', resources, defaults) end end diff --git a/lib/puppet/provider/ini_setting/ruby.rb b/lib/puppet/provider/ini_setting/ruby.rb index 08c51ff..8d2dc44 100644 --- a/lib/puppet/provider/ini_setting/ruby.rb +++ b/lib/puppet/provider/ini_setting/ruby.rb @@ -1,150 +1,152 @@ +# frozen_string_literal: true + require File.expand_path('../../../util/ini_file', __FILE__) Puppet::Type.type(:ini_setting).provide(:ruby) do def self.instances desc ' Creates new ini_setting file, a specific config file with a provider that uses this as its parent and implements the method self.file_path, and that will provide the value for the path to the ini file.' raise(Puppet::Error, 'Ini_settings only support collecting instances when a file path is hard coded') unless respond_to?(:file_path) # figure out what to do about the seperator ini_file = Puppet::Util::IniFile.new(file_path, '=') resources = [] ini_file.section_names.each do |section_name| ini_file.get_settings(section_name).each do |setting, value| resources.push( new( name: namevar(section_name, setting), value: value, ensure: :present, ), ) end end resources end def self.namevar(section_name, setting) setting.nil? ? section_name : "#{section_name}/#{setting}" end def exists? setting.nil? && ini_file.section_names.include?(section) || !ini_file.get_value(section, setting).nil? if ini_file.section?(section) !ini_file.get_value(section, setting).nil? elsif resource.parameters.keys.include?(:force_new_section_creation) && !resource[:force_new_section_creation] # for backwards compatibility, if a user is using their own ini_setting # types but does not have this parameter, we need to fall back to the # previous functionality which was to create the section. Anyone # wishing to leverage this setting must define it in their provider # type. See comments on # https://github.com/puppetlabs/puppetlabs-inifile/pull/286 resource[:ensure] = :absent resource[:force_new_section_creation] elsif resource.parameters.keys.include?(:force_new_section_creation) && resource[:force_new_section_creation] !resource[:force_new_section_creation] else false end end def create if setting.nil? && resource[:value].nil? ini_file.set_value(section) else ini_file.set_value(section, setting, separator, resource[:value]) end ini_file.save @ini_file = nil end def destroy ini_file.remove_setting(section, setting) ini_file.save @ini_file = nil end def value ini_file.get_value(section, setting) end def value=(_value) if setting.nil? && resource[:value].nil? ini_file.set_value(section) else ini_file.set_value(section, setting, separator, resource[:value]) end ini_file.save end def section # this method is here so that it can be overridden by a child provider resource[:section] end def setting # this method is here so that it can be overridden by a child provider resource[:setting] end def file_path # this method is here to support purging and sub-classing. # if a user creates a type and subclasses our provider and provides a # 'file_path' method, then they don't have to specify the # path as a parameter for every ini_setting declaration. # This implementation allows us to support that while still # falling back to the parameter value when necessary. if self.class.respond_to?(:file_path) self.class.file_path else resource[:path] end end def separator if resource.class.validattr?(:key_val_separator) resource[:key_val_separator] || '=' else '=' end end def section_prefix if resource.class.validattr?(:section_prefix) resource[:section_prefix] || '[' else '[' end end def section_suffix if resource.class.validattr?(:section_suffix) resource[:section_suffix] || ']' else ']' end end def indent_char if resource.class.validattr?(:indent_char) resource[:indent_char] || ' ' else ' ' end end def indent_width if resource.class.validattr?(:indent_width) resource[:indent_width] || nil else nil end end private def ini_file @ini_file ||= Puppet::Util::IniFile.new(file_path, separator, section_prefix, section_suffix, indent_char, indent_width) end end diff --git a/lib/puppet/provider/ini_subsetting/ruby.rb b/lib/puppet/provider/ini_subsetting/ruby.rb index 3c32a29..0f6b772 100644 --- a/lib/puppet/provider/ini_subsetting/ruby.rb +++ b/lib/puppet/provider/ini_subsetting/ruby.rb @@ -1,94 +1,96 @@ +# frozen_string_literal: true + require File.expand_path('../../../util/ini_file', __FILE__) require File.expand_path('../../../util/setting_value', __FILE__) Puppet::Type.type(:ini_subsetting).provide(:ruby) do desc ' Creates new ini_subsetting file, a specific config file with a provider that uses this as its parent and implements the method self.file_path, and that will provide the value for the path to the ini file.' def exists? setting_value.get_subsetting_value(subsetting, resource[:use_exact_match]) end def create setting_value.add_subsetting( subsetting, resource[:value], resource[:use_exact_match], resource[:insert_type], resource[:insert_value] ) ini_file.set_value(section, setting, key_val_separator, setting_value.get_value) ini_file.save @ini_file = nil @setting_value = nil end def destroy setting_value.remove_subsetting(subsetting, resource[:use_exact_match]) if setting_value.get_value.empty? && resource[:delete_if_empty] ini_file.remove_setting(section, setting) else ini_file.set_value(section, setting, key_val_separator, setting_value.get_value) end ini_file.save @ini_file = nil @setting_value = nil end def value setting_value.get_subsetting_value(subsetting) end def value=(value) setting_value.add_subsetting( subsetting, value, resource[:use_exact_match], resource[:insert_type], resource[:insert_value] ) ini_file.set_value(section, setting, key_val_separator, setting_value.get_value) ini_file.save end def section resource[:section] end def setting resource[:setting] end def subsetting resource[:subsetting] end def subsetting_separator resource[:subsetting_separator] end def file_path resource[:path] end def key_val_separator resource[:key_val_separator] || '=' end def subsetting_key_val_separator resource[:subsetting_key_val_separator] || '' end def quote_char resource[:quote_char] end private def ini_file @ini_file ||= Puppet::Util::IniFile.new(file_path, key_val_separator) end def setting_value @setting_value ||= Puppet::Util::SettingValue.new( ini_file.get_value(section, setting), subsetting_separator, quote_char, subsetting_key_val_separator ) end end diff --git a/lib/puppet/type/ini_setting.rb b/lib/puppet/type/ini_setting.rb index 8eb8280..4d1dfb9 100644 --- a/lib/puppet/type/ini_setting.rb +++ b/lib/puppet/type/ini_setting.rb @@ -1,155 +1,157 @@ +# frozen_string_literal: true + require 'digest/md5' require 'puppet/parameter/boolean' Puppet::Type.newtype(:ini_setting) do desc 'ini_settings is used to manage a single setting in an INI file' ensurable do desc 'Ensurable method handles modeling creation. It creates an ensure property' newvalue(:present) do provider.create end newvalue(:absent) do provider.destroy end def insync?(current) if @resource[:refreshonly] true else current == should end end defaultto :present end def munge_boolean_md5(value) case value when true, :true, 'true', :yes, 'yes' :true when false, :false, 'false', :no, 'no' :false when :md5, 'md5' :md5 else raise(_('expected a boolean value or :md5')) end 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.' defaultto('') end newparam(:setting) do desc 'The name of the setting to be defined.' munge do |value| if value.match?(%r{(^\s|\s$)}) Puppet.warn('Settings should not have spaces in the value, we are going to strip the whitespace') end value.strip end end newparam(:force_new_section_creation, boolean: true, parent: Puppet::Parameter::Boolean) do desc 'Create setting only if the section exists' defaultto(true) end newparam(:path) do desc 'The ini file Puppet will ensure contains the specified setting.' validate do |value| unless Puppet::Util.absolute_path?(value) raise(Puppet::Error, _("File paths must be fully qualified, not '%{value}'") % { value: value }) end end end newparam(:show_diff) do desc 'Whether to display differences when the setting changes.' defaultto :true newvalues(:true, :md5, :false) munge do |value| @resource.munge_boolean_md5(value) end end newparam(:key_val_separator) do desc 'The separator string to use between each setting name and value.' defaultto(' = ') end newproperty(:value) do desc 'The value of the setting to be defined.' munge do |value| if ([true, false].include? value) || value.is_a?(Numeric) value.to_s else value.strip.to_s end end def should_to_s(newvalue) if @resource[:show_diff] == :true && Puppet[:show_diff] newvalue elsif @resource[:show_diff] == :md5 && Puppet[:show_diff] '{md5}' + Digest::MD5.hexdigest(newvalue.to_s) else '[redacted sensitive information]' end end def is_to_s(value) # rubocop:disable Style/PredicateName : Changing breaks the code (./.bundle/gems/gems/puppet-5.3.3-universal-darwin/lib/puppet/parameter.rb:525:in `to_s') should_to_s(value) end def insync?(current) if @resource[:refreshonly] true else current == should end end end newparam(:section_prefix) do desc 'The prefix to the section name\'s header.' defaultto('[') end newparam(:section_suffix) do desc 'The suffix to the section name\'s header.' defaultto(']') end newparam(:indent_char) do desc 'The character to indent new settings with.' defaultto(' ') end newparam(:indent_width) do desc 'The number of indent_chars to use to indent a new setting.' end newparam(:refreshonly, boolean: true, parent: Puppet::Parameter::Boolean) do desc 'A flag indicating whether or not the ini_setting should be updated only when called as part of a refresh event' defaultto false end def refresh if self[:ensure] == :absent && self[:refreshonly] return provider.destroy end # update the value in the provider, which will save the value to the ini file provider.value = self[:value] if self[:refreshonly] end autorequire(:file) do Pathname.new(self[:path]).parent.to_s end end diff --git a/lib/puppet/type/ini_subsetting.rb b/lib/puppet/type/ini_subsetting.rb index 596607b..a2e10d4 100644 --- a/lib/puppet/type/ini_subsetting.rb +++ b/lib/puppet/type/ini_subsetting.rb @@ -1,132 +1,134 @@ +# frozen_string_literal: true + require 'digest/md5' Puppet::Type.newtype(:ini_subsetting) do desc 'ini_subsettings is used to manage multiple values in a setting in an INI file' ensurable do desc 'Ensurable method handles modeling creation. It creates an ensure property' defaultvalues defaultto :present end def munge_boolean_md5(value) case value when true, :true, 'true', :yes, 'yes' :true when false, :false, 'false', :no, 'no' :false when :md5, 'md5' :md5 else raise(_('expected a boolean value or :md5')) end 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.' defaultto('') end newparam(:setting) do desc 'The name of the setting to be defined.' end newparam(:subsetting) do desc 'The name of the subsetting to be defined.' end newparam(:subsetting_separator) do desc 'The separator string between subsettings. Defaults to the empty string.' defaultto(' ') end newparam(:subsetting_key_val_separator) do desc 'The separator string between the subsetting name and its value. Defaults to the empty string.' defaultto('') end newparam(:path) do desc 'The ini file Puppet will ensure contains the specified setting.' validate do |value| unless Puppet::Util.absolute_path?(value) raise(Puppet::Error, _("File paths must be fully qualified, not '%{value}'") % { value: value }) end end end newparam(:show_diff) do desc 'Whether to display differences when the setting changes.' defaultto :true newvalues(:true, :md5, :false) munge do |value| @resource.munge_boolean_md5(value) end end newparam(:key_val_separator) do desc 'The separator string to use between each setting name and value.' defaultto(' = ') end newparam(:quote_char) do desc "The character used to quote the entire value of the setting. Valid values are '', '\"' and \"'\"" defaultto('') validate do |value| unless value.match?(%r{^["']?$}) raise Puppet::Error, _(%q(:quote_char valid values are '', '"' and "'")) end end end newparam(:use_exact_match) do desc 'Set to true if your subsettings don\'t have values and you want to use exact matches to determine if the subsetting exists.' newvalues(:true, :false) defaultto(:false) end newproperty(:value) do desc 'The value of the subsetting to be defined.' def should_to_s(newvalue) if @resource[:show_diff] == :true && Puppet[:show_diff] newvalue elsif @resource[:show_diff] == :md5 && Puppet[:show_diff] '{md5}' + Digest::MD5.hexdigest(newvalue.to_s) else '[redacted sensitive information]' end end def is_to_s(value) # rubocop:disable Style/PredicateName : Changing breaks the code (./.bundle/gems/gems/puppet-5.3.3-universal-darwin/lib/puppet/parameter.rb:525:in `to_s') should_to_s(value) end end newparam(:insert_type) do desc <<-eof Where the new subsetting item should be inserted * :start - insert at the beginning of the line. * :end - insert at the end of the line (default). * :before - insert before the specified element if possible. * :after - insert after the specified element if possible. * :index - insert at the specified index number. eof newvalues(:start, :end, :before, :after, :index) defaultto(:end) end newparam(:insert_value) do desc 'The value for the insert types which require one.' end newparam(:delete_if_empty) do desc 'Set to true to delete the parent setting when the subsetting is empty instead of writing an empty string' newvalues(:true, :false) defaultto(:false) end end diff --git a/lib/puppet/util/external_iterator.rb b/lib/puppet/util/external_iterator.rb index 812f70b..0a6b235 100644 --- a/lib/puppet/util/external_iterator.rb +++ b/lib/puppet/util/external_iterator.rb @@ -1,30 +1,32 @@ +# frozen_string_literal: true + module Puppet::Util # # external_iterator.rb # class ExternalIterator def initialize(coll) @coll = coll @cur_index = -1 end def next @cur_index += 1 item_at(@cur_index) end def peek item_at(@cur_index + 1) end private def item_at(index) if @coll.length > index [@coll[index], index] else [nil, nil] end end end end diff --git a/lib/puppet/util/ini_file.rb b/lib/puppet/util/ini_file.rb index 7a5321b..c992fdd 100644 --- a/lib/puppet/util/ini_file.rb +++ b/lib/puppet/util/ini_file.rb @@ -1,347 +1,349 @@ +# frozen_string_literal: true + require File.expand_path('../external_iterator', __FILE__) require File.expand_path('../ini_file/section', __FILE__) module Puppet::Util # # ini_file.rb # class IniFile def initialize(path, key_val_separator = ' = ', section_prefix = '[', section_suffix = ']', indent_char = ' ', indent_width = nil) k_v_s = (key_val_separator =~ %r{^\s+$}) ? ' ' : key_val_separator.strip @section_prefix = section_prefix @section_suffix = section_suffix @indent_char = indent_char @indent_width = indent_width ? indent_width.to_i : nil @section_regex = section_regex @setting_regex = %r{^(\s*)([^#;\s]|[^#;\s].*?[^\s#{k_v_s}])(\s*#{k_v_s}[ \t]*)(.*)\s*$} @commented_setting_regex = %r{^(\s*)[#;]+(\s*)(.*?[^\s#{k_v_s}])(\s*#{k_v_s}[ \t]*)(.*)\s*$} @path = path @key_val_separator = key_val_separator @section_names = [] @sections_hash = {} parse_file end def section_regex # Only put in prefix/suffix if they exist # Also, if the prefix is '', the negated # set match should be a match all instead. r_string = '^\s*' r_string += Regexp.escape(@section_prefix) r_string += '(' if @section_prefix != '' r_string += '[^' r_string += Regexp.escape(@section_prefix) r_string += ']' else r_string += '.' end r_string += '*)' r_string += Regexp.escape(@section_suffix) r_string += '\s*$' %r{#{r_string}} end attr_reader :section_names def get_settings(section_name) section = @sections_hash[section_name] section.setting_names.each_with_object({}) do |setting, result| result[setting] = section.get_value(setting) end end def section?(section_name) @sections_hash.key?(section_name) end def get_value(section_name, setting) @sections_hash[section_name].get_value(setting) if @sections_hash.key?(section_name) end - def set_value(*args) # rubocop:disable Style/AccessorMethodName : Recomended alternative is a common value name + def set_value(*args) # rubocop:disable Naming/AccessorMethodName : Recomended alternative is a common value name case args.size when 1 section_name = args[0] when 3 # Backwards compatible set_value function, See MODULES-5172 (section_name, setting, value) = args when 4 (section_name, setting, separator, value) = args end complete_setting = { setting: setting, separator: separator, value: value, } unless @sections_hash.key?(section_name) add_section(Section.new(section_name, nil, nil, nil, nil)) end section = @sections_hash[section_name] if section.existing_setting?(setting) update_line(section, setting, value) section.update_existing_setting(setting, value) elsif find_commented_setting(section, setting) # So, this stanza is a bit of a hack. What we're trying # to do here is this: for settings that don't already # exist, we want to take a quick peek to see if there # is a commented-out version of them in the section. # If so, we'd prefer to add the setting directly after # the commented line, rather than at the end of the section. # If we get here then we found a commented line, so we # call "insert_inline_setting_line" to update the lines array insert_inline_setting_line(find_commented_setting(section, setting), section, complete_setting) # Then, we need to tell the setting object that we hacked # in an inline setting section.insert_inline_setting(setting, value) # Finally, we need to update all of the start/end line # numbers for all of the sections *after* the one that # was modified. section_index = @section_names.index(section_name) increment_section_line_numbers(section_index + 1) elsif !setting.nil? || !value.nil? section.set_additional_setting(setting, value) end end def remove_setting(section_name, setting) section = @sections_hash[section_name] return unless section.existing_setting?(setting) # If the setting is found, we have some work to do. # First, we remove the line from our array of lines: remove_line(section, setting) # Then, we need to tell the setting object to remove # the setting from its state: section.remove_existing_setting(setting) # Finally, we need to update all of the start/end line # numbers for all of the sections *after* the one that # was modified. section_index = @section_names.index(section_name) decrement_section_line_numbers(section_index + 1) return unless section.empty? # By convention, it's time to remove this newly emptied out section lines.delete_at(section.start_line) decrement_section_line_numbers(section_index + 1) @section_names.delete_at(section_index) @sections_hash.delete(section.name) end def save global_empty = @sections_hash[''].empty? && @sections_hash[''].additional_settings.empty? File.open(@path, 'w') do |fh| @section_names.each_index do |index| name = @section_names[index] section = @sections_hash[name] # We need a buffer to cache lines that are only whitespace whitespace_buffer = [] if section.new_section? && !section.global? if index == 1 && !global_empty || index > 1 fh.puts('') end fh.puts("#{@section_prefix}#{section.name}#{@section_suffix}") end unless section.new_section? # write all of the pre-existing lines (section.start_line..section.end_line).each do |line_num| line = lines[line_num] # We buffer any lines that are only whitespace so that # if they are at the end of a section, we can insert # any new settings *before* the final chunk of whitespace # lines. if line.match?(%r{^\s*$}) whitespace_buffer << line else # If we get here, we've found a non-whitespace line. # We'll flush any cached whitespace lines before we # write it. flush_buffer_to_file(whitespace_buffer, fh) fh.puts(line) end end end # write new settings, if there are any section.additional_settings.each_pair do |key, value| fh.puts("#{@indent_char * (@indent_width || section.indentation || 0)}#{key}#{@key_val_separator}#{value}") end if !whitespace_buffer.empty? flush_buffer_to_file(whitespace_buffer, fh) elsif section.new_section? && !section.additional_settings.empty? && (index < @section_names.length - 1) # We get here if there were no blank lines at the end of the # section. # # If we are adding a new section with a new setting, # and if there are more sections that come after this one, # we'll write one blank line just so that there is a little # whitespace between the sections. # if (section.end_line.nil? && fh.puts('') 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 # We always create a "global" section at the beginning of the file, for # anything that appears before the first named section. section = read_section('', 0, line_iter) add_section(section) 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 = {} end_line_num = start_line min_indentation = nil empty = true loop do line, line_num = line_iter.peek if line_num.nil? || @section_regex.match(line) # the global section always exists, even when it's empty; # when it's empty, we must be sure it's thought of as new, # which is signalled with a nil ending line end_line_num = nil if name == '' && empty return Section.new(name, start_line, end_line_num, settings, min_indentation) end if (match = @setting_regex.match(line)) settings[match[2]] = match[4] indentation = match[1].length min_indentation = [indentation, min_indentation || indentation].min end end_line_num = line_num empty = false line_iter.next end end def update_line(section, setting, value) (section.start_line..section.end_line).each do |line_num| next unless (match = @setting_regex.match(lines[line_num])) if match[2] == setting lines[line_num] = "#{match[1]}#{match[2]}#{match[3]}#{value}" end end end def remove_line(section, setting) (section.start_line..section.end_line).each do |line_num| next unless (match = @setting_regex.match(lines[line_num])) if match[2] == setting lines.delete_at(line_num) 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) # rubocop:disable Lint/IneffectiveAccessModifier : Attempting to change breaks tests # 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.file?(path) ? File.readlines(path) : [] end # This utility method scans through the lines for a section looking for # commented-out versions of a setting. It returns `nil` if it doesn't # find one. If it does find one, then it returns a hash containing # two keys: # # :line_num - the line number that contains the commented version # of the setting # :match - the ruby regular expression match object, which can # be used to mimic the whitespace from the comment line def find_commented_setting(section, setting) return nil if section.new_section? (section.start_line..section.end_line).each do |line_num| next unless (match = @commented_setting_regex.match(lines[line_num])) if match[3] == setting return { match: match, line_num: line_num } end end nil end # This utility method is for inserting a line into the existing # lines array. The `result` argument is expected to be in the # format of the return value of `find_commented_setting`. def insert_inline_setting_line(result, section, complete_setting) line_num = result[:line_num] s = complete_setting lines.insert(line_num + 1, "#{@indent_char * (@indent_width || section.indentation || 0)}#{s[:setting]}#{s[:separator]}#{s[:value]}") end # Utility method; given a section index (index into the @section_names # array), decrement the start/end line numbers for that section and all # all of the other sections that appear *after* the specified section. def decrement_section_line_numbers(section_index) @section_names[section_index..(@section_names.length - 1)].each do |name| section = @sections_hash[name] section.decrement_line_nums end end # Utility method; given a section index (index into the @section_names # array), increment the start/end line numbers for that section and all # all of the other sections that appear *after* the specified section. def increment_section_line_numbers(section_index) @section_names[section_index..(@section_names.length - 1)].each do |name| section = @sections_hash[name] section.increment_line_nums end end def flush_buffer_to_file(buffer, fh) return if buffer.empty? buffer.each { |l| fh.puts(l) } buffer.clear end end end diff --git a/lib/puppet/util/ini_file/section.rb b/lib/puppet/util/ini_file/section.rb index 9da0e9c..75ea544 100644 --- a/lib/puppet/util/ini_file/section.rb +++ b/lib/puppet/util/ini_file/section.rb @@ -1,93 +1,95 @@ +# frozen_string_literal: true + class Puppet::Util::IniFile # # section.rb # class Section # Some implementation details: # # * `name` will be set to the empty string for the 'global' section. # * there will always be a 'global' section, with a `start_line` of 0, # but if the file actually begins with a real section header on # the first line, then the 'global' section will have an # `end_line` of `nil`. # * `start_line` and `end_line` will be set to `nil` for a new non-global # section. def initialize(name, start_line, end_line, settings, indentation) @name = name @start_line = start_line @end_line = end_line @existing_settings = settings.nil? ? {} : settings @additional_settings = {} @indentation = indentation end attr_reader :name, :start_line, :end_line, :additional_settings, :indentation def global? @name == '' end def new_section? # a new section (global or named) will always have `end_line` # set to `nil` @end_line.nil? end def setting_names @existing_settings.keys | @additional_settings.keys end def get_value(setting_name) @existing_settings[setting_name] || @additional_settings[setting_name] end def existing_setting?(setting_name) @existing_settings.key?(setting_name) end # the global section is empty whenever it's new; # other sections are empty when they have no lines def empty? global? ? new_section? : start_line == end_line end def update_existing_setting(setting_name, value) @existing_settings[setting_name] = value end def remove_existing_setting(setting_name) @end_line -= 1 if @existing_settings.delete(setting_name) && @end_line end # This is a hacky method; it's basically called when we need to insert # a new setting but we don't want it to appear at the very end of the # section. Instead we hack it into the existing settings list and # increment our end_line number--this assumes that the caller (`ini_file`) # is doing some babysitting w/rt the other sections and the actual data # of the lines. def insert_inline_setting(setting_name, value) @existing_settings[setting_name] = value @end_line += 1 if @end_line end def set_additional_setting(setting_name, value) @additional_settings[setting_name] = value end # Decrement the start and end line numbers for the section (if they are # defined); this is intended to be called when a setting is removed # from a section that comes before this section in the ini file. def decrement_line_nums @start_line -= 1 if @start_line @end_line -= 1 if @end_line end # Increment the start and end line numbers for the section (if they are # defined); this is intended to be called when an inline setting is added # to a section that comes before this section in the ini file. def increment_line_nums @start_line += 1 if @start_line @end_line += 1 if @end_line end end end diff --git a/lib/puppet/util/setting_value.rb b/lib/puppet/util/setting_value.rb index 26f5517..66dec4f 100644 --- a/lib/puppet/util/setting_value.rb +++ b/lib/puppet/util/setting_value.rb @@ -1,182 +1,184 @@ +# frozen_string_literal: true + module Puppet::Util # This class can work with a list of subsettings inside # an ini file setting string to add, remove, extract and set their values. class SettingValue # The constructor method # @param setting_value [String] The initial setting value # @param subsetting_separator [String] The character is used to separate # subsettings in the setting_value string. # @param default_quote_char [String] Quote the setting string with this character. def initialize(setting_value, subsetting_separator = ' ', default_quote_char = '', key_val_separator = '') @setting_value = setting_value @subsetting_separator = subsetting_separator @quote_char = default_quote_char @key_val_separator = key_val_separator @subsetting_items = [] return unless @setting_value unquoted, quote_char = unquote_setting_value(setting_value) @quote_char = quote_char unless quote_char.empty? # an item can contain escaped separator @subsetting_items = unquoted.scan(Regexp.new("(?:(?:[^\\#{@subsetting_separator}]|\\.)+)")) @subsetting_items.map! { |item| item.strip } end # If the setting value is quoted, the quotes are # removed and the unquoted string and the quoting # character are returned. # @param setting_value [String] The input value # @return [Array] The unquoted string and the quoting character def unquote_setting_value(setting_value) quote_char = '' if setting_value.start_with?('"') && setting_value.end_with?('"') quote_char = '"' elsif setting_value.start_with?("'") && setting_value.end_with?("'") quote_char = "'" end unquoted = if quote_char != '' setting_value[1, setting_value.length - 2] else setting_value end [unquoted, quote_char] end # Get the resulting setting value by joining all the # subsettings, separator and quote characters. # @return [String] - def get_value # rubocop:disable Style/AccessorMethodName : Unable to alter without breaking code + def get_value # rubocop:disable Naming/AccessorMethodName : Unable to alter without breaking code value = @subsetting_items.join @subsetting_separator @quote_char + value + @quote_char end # Get the value of the given subsetting item. # If the exact match is used the value will be true # if the item is found. # @param subsetting [String] The name of the subsetting to add. # @param use_exact_match [:true,:false] Should the full name match be used? # @return [nil,true,String] def get_subsetting_value(subsetting, use_exact_match = :false) index = find_subsetting(subsetting, use_exact_match) # the item is not found in the list return nil unless index # the exact match is set and the item is found, the value should be true return true if use_exact_match == :true item = @subsetting_items[index] item[(subsetting.length + @key_val_separator.length)..-1] end # Add a new subsetting item to the list of existing items # if such item is not already there. # @param subsetting [String] The name of the subsetting to add. # @param subsetting_value [String] The value of the subsetting. # It will be appended to the name. # @param use_exact_match [:true,:false] Should the full name match be used? # @param [Symbol] insert_type # @param [String,Integer] insert_value # @return [Array] The resulting subsettings list. def add_subsetting(subsetting, subsetting_value, use_exact_match = :false, insert_type = :end, insert_value = nil) index = find_subsetting(subsetting, use_exact_match) # update the existing values if the subsetting is found in the list return update_subsetting(subsetting, subsetting_value, use_exact_match) if index new_item = item_value(subsetting, subsetting_value) case insert_type when :start @subsetting_items.unshift(new_item) when :end @subsetting_items.push(new_item) when :before before_index = find_subsetting(insert_value, use_exact_match) if before_index @subsetting_items.insert(before_index, new_item) else @subsetting_items.push(new_item) end when :after after_index = find_subsetting(insert_value, use_exact_match) if after_index @subsetting_items.insert(after_index + 1, new_item) else @subsetting_items.push(new_item) end when :index before_index = insert_value.to_i before_index = @subsetting_items.length if before_index > @subsetting_items.length @subsetting_items.insert(before_index, new_item) else @subsetting_items.push(new_item) end @subsetting_items end # Update all matching items in the settings list to the new values. # @param subsetting [String] The name of the subsetting to add. # @param subsetting_value [String] The value of the subsetting. # @param use_exact_match [:true,:false] Should the full name match be used? # @return [Array] The resulting subsettings list. def update_subsetting(subsetting, subsetting_value, use_exact_match = :false) new_item = item_value(subsetting, subsetting_value) @subsetting_items.map! do |item| if match_subsetting?(item, subsetting, use_exact_match) new_item else item end end end # Find the first subsetting item matching the given name, # or, if the exact match is set, equal to the given name # and return its array index value. Returns nil if not found. # @param subsetting [String] The name of the subsetting to search. # @param use_exact_match [:true,:false] Look for the full string match? # @return [Integer, nil] def find_subsetting(subsetting, use_exact_match = :false) @subsetting_items.index do |item| match_subsetting?(item, subsetting, use_exact_match) end end # Check if the subsetting item matches the given name. # If the exact match is set the entire item is matched, # and only the item name and separator string if not. # @param item [String] The item value to check against the subsetting name. # @param subsetting [String] The subsetting name. # @param use_exact_match [:true,:false] Look for the full string match? # @return [true,false] def match_subsetting?(item, subsetting, use_exact_match = :false) if use_exact_match == :true item.eql?(subsetting) else item.start_with?(subsetting + @key_val_separator) end end # Remove all the subsetting items that match # the given subsetting name. # @param subsetting [String] The subsetting name to remove. # @param use_exact_match [:true,:false] Look for the full string match? # @return [Array] The resulting subsettings list. def remove_subsetting(subsetting, use_exact_match = :false) @subsetting_items.delete_if do |item| match_subsetting?(item, subsetting, use_exact_match) end end # The actual value of the subsetting item. # It's built from the subsetting name, its value and the separator # string if present. # @param subsetting [String] The subsetting name # @param subsetting_value [String] The value of the subsetting # @return [String] def item_value(subsetting, subsetting_value) (subsetting || '') + (@key_val_separator || '') + (subsetting_value || '') end end end diff --git a/spec/acceptance/ini_setting_spec.rb b/spec/acceptance/ini_setting_spec.rb index 2dd5857..28e077f 100644 --- a/spec/acceptance/ini_setting_spec.rb +++ b/spec/acceptance/ini_setting_spec.rb @@ -1,356 +1,358 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'ini_setting resource' do basedir = setup_test_directory after :all do run_shell("rm #{basedir}/*.ini", expect_failures: true) end shared_examples 'has_content' do |path, pp, content| before :all do run_shell("rm #{path}", expect_failures: true) end it 'applies the manifest twice' do idempotent_apply(pp) end describe file(path) do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.to match content } end end end shared_examples 'has_error' do |path, pp, error| before :all do run_shell("rm #{path}", expect_failures: true) end it 'applies the manifest and gets a failure message' do expect(apply_manifest(pp, expect_failures: true).stderr).to match(error) end describe file(path) do it { is_expected.not_to be_file } end end context 'ensure parameter => present for global and section' do pp = <<-EOS ini_setting { 'ensure => present for section': ensure => present, path => "#{basedir}/ini_setting.ini", section => 'one', setting => 'two', value => 'three', } ini_setting { 'ensure => present for global': ensure => present, path => "#{basedir}/ini_setting.ini", section => '', setting => 'four', value => 'five', } EOS it_behaves_like 'has_content', "#{basedir}/ini_setting.ini", pp, %r{four = five\R\R\[one\]\Rtwo = three} end context 'ensure parameter => absent for key/value' do before :all do ipp = <<-MANIFEST file { '#{basedir}/ini_setting.ini': content => "four = five \n [one] \n two = three", force => true, } MANIFEST apply_manifest(ipp) end pp = <<-EOS ini_setting { 'ensure => absent for key/value': ensure => absent, path => "#{basedir}/ini_setting.ini", section => 'one', setting => 'two', value => 'three', } EOS it 'applies the manifest twice' do idempotent_apply(pp) end describe file("#{basedir}/ini_setting.ini") do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.to match %r{four = five} } it { is_expected.not_to match %r{\[one\]} } it { is_expected.not_to match %r{two = three} } end end end context 'ensure parameter => absent for global' do before :all do ipp = <<-MANIFEST file { '#{basedir}/ini_setting.ini': content => "four = five\n [one]\n two = three", force => true, } MANIFEST apply_manifest(ipp) end after :all do run_shell("rm #{basedir}/ini_setting.ini", expect_failures: true) end pp = <<-EOS ini_setting { 'ensure => absent for global': ensure => absent, path => "#{basedir}/ini_setting.ini", section => '', setting => 'four', value => 'five', } EOS it 'applies the manifest twice' do idempotent_apply(pp) end describe file("#{basedir}/ini_setting.ini") do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.not_to match %r{four = five} } it { is_expected.to match %r{\[one\]} } it { is_expected.to match %r{two = three} } end end end describe 'path parameter' do context 'path => foo' do pp = <<-EOS ini_setting { 'path => foo': ensure => present, section => 'one', setting => 'two', value => 'three', path => 'foo', } EOS it_behaves_like 'has_error', 'foo', pp, %r{must be fully qualified} end end describe 'show_diff parameter and logging:' do setup_puppet_config_file [{ value: 'initial_value', matcher: 'created', show_diff: true }, { value: 'public_value', matcher: %r{initial_value.*public_value}, show_diff: true }, { value: 'secret_value', matcher: %r{redacted sensitive information.*redacted sensitive information}, show_diff: false }, { value: 'md5_value', matcher: %r{\{md5\}881671aa2bbc680bc530c4353125052b.*\{md5\}ed0903a7fa5de7886ca1a7a9ad06cf51}, show_diff: :md5 }].each do |i| pp = <<-EOS ini_setting { 'test_show_diff': ensure => present, section => 'test', setting => 'something', value => '#{i[:value]}', path => "#{basedir}/test_show_diff.ini", show_diff => #{i[:show_diff]} } EOS context "show_diff => #{i[:show_diff]}" do res = apply_manifest(pp, expect_changes: true) it 'applies manifest and expects changed value to be logged in proper form' do expect(res.stdout).to match(i[:matcher]) end it 'applies manifest and expects changed value to be logged in proper form #optional test' do expect(res.stdout).not_to match(i[:value]) unless i[:show_diff] == true end end end end describe 'values with spaces in the value part at the beginning or at the end' do pp = <<-EOS ini_setting { 'path => foo': ensure => present, section => 'one', setting => 'two', value => ' 123', path => '#{basedir}/ini_setting.ini', } EOS it_behaves_like 'has_content', "#{basedir}/ini_setting.ini", pp, %r{\[one\]\Rtwo = 123} end describe 'refreshonly' do before :each do ipp = <<-MANIFEST file { '#{basedir}/ini_setting.ini': content => "[section1]\n valueinsection1 = 123\", force => true, } MANIFEST apply_manifest(ipp) end after :each do run_shell("rm #{basedir}/ini_setting.ini", expect_failures: true) end context 'when event is triggered' do context 'update setting value' do let(:update_value_manifest) do <<-EOS notify { foo: notify => Ini_Setting['updateSetting'], } ini_setting { "updateSetting": ensure => present, path => "#{basedir}/ini_setting.ini", section => 'section1', setting => 'valueinsection1', value => "newValue", refreshonly => true, } EOS end before(:each) do apply_manifest(update_value_manifest, expect_changes: true) end describe file("#{basedir}/ini_setting.ini") do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.to match %r{valueinsection1 = newValue} } end end end context 'remove setting value' do let(:remove_setting_manifest) do <<-EOS notify { foo: notify => Ini_Setting['removeSetting'], } ini_setting { "removeSetting": ensure => absent, path => "#{basedir}/ini_setting.ini", section => 'section1', setting => 'valueinsection1', refreshonly => true, } EOS end before(:each) do apply_manifest(remove_setting_manifest, expect_changes: true) end describe file("#{basedir}/ini_setting.ini") do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.to be_empty } end end end end context 'when not receiving an event' do context 'does not update setting' do let(:does_not_update_value_manifest) do <<-EOS file { "#{basedir}/ini_setting.ini": ensure => present, notify => Ini_Setting['updateSetting'], } ini_setting { "updateSetting": ensure => present, path => "#{basedir}/ini_setting.ini", section => 'section1', setting => 'valueinsection1', value => "newValue", refreshonly => true, } EOS end before(:each) do apply_manifest(does_not_update_value_manifest, expect_changes: false) end describe file("#{basedir}/ini_setting.ini") do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.to match %r{valueinsection1 = 123} } it { is_expected.to match %r{\[section1\]} } end end end context 'does not remove setting' do let(:does_not_remove_setting_manifest) do <<-EOS file { "#{basedir}/ini_setting.ini": ensure => present, notify => Ini_Setting['removeSetting'], } ini_setting { "removeSetting": ensure => absent, path => "#{basedir}/ini_setting.ini", section => 'section1', setting => 'valueinsection1', refreshonly => true, } EOS end before(:each) do apply_manifest(does_not_remove_setting_manifest, expect_changes: false) end describe file("#{basedir}/ini_setting.ini") do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.not_to be_empty } it { is_expected.to match %r{valueinsection1 = 123} } it { is_expected.to match %r{\[section1\]} } end end end end end end diff --git a/spec/acceptance/ini_subsetting_spec.rb b/spec/acceptance/ini_subsetting_spec.rb index 2465d4d..ac426fc 100644 --- a/spec/acceptance/ini_subsetting_spec.rb +++ b/spec/acceptance/ini_subsetting_spec.rb @@ -1,289 +1,291 @@ +# frozen_string_literal: true + require 'spec_helper_acceptance' describe 'ini_subsetting resource' do basedir = setup_test_directory after :all do run_shell("rm #{basedir}/*.ini", expect_failures: true) end shared_examples 'has_content' do |path, pp, content| before :all do run_shell("rm #{path}", expect_failures: true) end it 'applies the manifest twice' do idempotent_apply(pp) end describe file(path) do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.to match content } end end end shared_examples 'has_error' do |path, pp, error| before :all do run_shell("rm #{path}", expect_failures: true) end it 'applies the manifest and gets a failure message' do expect(apply_manifest(pp, expect_failures: true).stderr).to match(error) end describe file(path) do it { is_expected.not_to be_file } end end describe 'ensure, section, setting, subsetting, & value parameters => present with subsections' do pp = <<-EOS ini_subsetting { 'ensure => present for alpha': ensure => present, path => "#{basedir}/ini_subsetting.ini", section => 'one', setting => 'key', subsetting => 'alpha', value => 'bet', } ini_subsetting { 'ensure => present for beta': ensure => present, path => "#{basedir}/ini_subsetting.ini", section => 'one', setting => 'key', subsetting => 'beta', value => 'trons', require => Ini_subsetting['ensure => present for alpha'], } EOS describe file("#{basedir}/ini_subsetting.ini") do it_behaves_like 'has_content', "#{basedir}/ini_subsetting.ini", pp, %r{\[one\]\Rkey = alphabet betatrons} end end describe 'ensure => absent' do pp = <<-EOS ini_subsetting { 'ensure => absent for subsetting': ensure => absent, path => "#{basedir}/ini_subsetting.ini", section => 'one', setting => 'key', subsetting => 'alpha', } EOS it 'applies the manifest twice' do idempotent_apply(pp) end describe file("#{basedir}/ini_subsetting.ini") do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.to match %r{\[one\]} } it { is_expected.to match %r{key = betatrons} } it { is_expected.not_to match %r{alphabet} } end end end describe 'delete_if_empty => true' do pp = <<-EOS ini_subsetting { 'ensure => absent for alpha': ensure => absent, path => "#{basedir}/ini_subsetting.ini", section => 'one', setting => 'key', subsetting => 'alpha', delete_if_empty => true, } ini_subsetting { 'ensure => absent for beta': ensure => absent, path => "#{basedir}/ini_subsetting.ini", section => 'one', setting => 'key', subsetting => 'beta', delete_if_empty => true, } EOS it 'applies the manifest twice' do run_shell("echo -e [one]\\\\nkey = alphabet betatrons > #{basedir}/ini_subsetting.ini", expect_failures: true) idempotent_apply(pp) end describe file("#{basedir}/ini_subsetting.ini") do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.not_to match %r{\[one\]} } it { is_expected.not_to match %r{key =} } it { is_expected.not_to match %r{alphabet} } it { is_expected.not_to match %r{betatrons} } end end end describe 'quote_char' do { ['-Xmx'] => %r{args=""}, ['-Xmx', '256m'] => %r{args=-Xmx256m}, ['-Xmx', '512m'] => %r{args="-Xmx512m"}, ['-Xms', '256m'] => %r{args="-Xmx256m -Xms256m"}, }.each do |parameter, content| context %(with '#{parameter.first}' #{(parameter.length > 1) ? '=> \'' << parameter[1] << '\'' : 'absent'} makes '#{content}') do path = File.join(basedir, 'ini_subsetting.ini') before :all do ipp = <<-MANIFEST file { '#{path}': content => "[java]\nargs=-Xmx256m", force => true, } MANIFEST apply_manifest(ipp) end after :all do run_shell("cat #{path}", expect_failures: true) run_shell("rm #{path}", expect_failures: true) end pp = <<-EOS ini_subsetting { '#{parameter.first}': ensure => #{(parameter.length > 1) ? 'present' : 'absent'}, path => '#{path}', section => 'java', setting => 'args', quote_char => '"', subsetting => '#{parameter.first}', value => '#{(parameter.length > 1) ? parameter[1] : ''}' } EOS it 'applies the manifest twice' do idempotent_apply(pp) end describe file("#{basedir}/ini_subsetting.ini") do it { is_expected.to be_file } describe '#content' do subject { super().content } it { is_expected.to match content } end end end end end describe 'show_diff parameter and logging:' do setup_puppet_config_file [{ value: 'initial_value', matcher: 'created', show_diff: true }, { value: 'public_value', matcher: %r{initial_value.*public_value}, show_diff: true }, { value: 'secret_value', matcher: %r{redacted sensitive information.*redacted sensitive information}, show_diff: false }, { value: 'md5_value', matcher: %r{\{md5\}881671aa2bbc680bc530c4353125052b.*\{md5\}ed0903a7fa5de7886ca1a7a9ad06cf51}, show_diff: :md5 }].each do |i| pp = <<-EOS ini_subsetting { 'test_show_diff': ensure => present, section => 'test', setting => 'something', subsetting => 'xxx', value => '#{i[:value]}', path => "#{basedir}/test_show_diff.ini", show_diff => #{i[:show_diff]} } EOS context "show_diff => #{i[:show_diff]}" do res = apply_manifest(pp, expect_changes: true) it 'applies manifest and expects changed value to be logged in proper form' do expect(res.stdout).to match(i[:matcher]) end it 'applies manifest and expects changed value to be logged in proper form #optional test' do expect(res.stdout).not_to match(i[:value]) unless i[:show_diff] == true end end end end describe 'insert types:' do [ { insert_type: :start, content: %r{d a b c}, }, { insert_type: :end, content: %r{a b c d}, }, { insert_type: :before, insert_value: 'c', content: %r{a b d c}, }, { insert_type: :after, insert_value: 'a', content: %r{a d b c}, }, { insert_type: :index, insert_value: 2, content: %r{a b d c}, }, ].each do |params| context "with '#{params[:insert_type]}' makes '#{params[:content]}'" do pp = <<-EOS ini_subsetting { "a": ensure => present, section => 'one', setting => 'two', subsetting => 'a', path => "#{basedir}/insert_types.ini", } -> ini_subsetting { "b": ensure => present, section => 'one', setting => 'two', subsetting => 'b', path => "#{basedir}/insert_types.ini", } -> ini_subsetting { "c": ensure => present, section => 'one', setting => 'two', subsetting => 'c', path => "#{basedir}/insert_types.ini", } -> ini_subsetting { "insert makes #{params[:content]}": ensure => present, section => 'one', setting => 'two', subsetting => 'd', path => "#{basedir}/insert_types.ini", insert_type => '#{params[:insert_type]}', insert_value => '#{params[:insert_value]}', } EOS it_behaves_like 'has_content', "#{basedir}/insert_types.ini", pp, params[:content] end end end end diff --git a/spec/classes/create_ini_settings_test_spec.rb b/spec/classes/create_ini_settings_test_spec.rb index c008044..5f5555b 100644 --- a/spec/classes/create_ini_settings_test_spec.rb +++ b/spec/classes/create_ini_settings_test_spec.rb @@ -1,25 +1,27 @@ +# frozen_string_literal: true + require 'spec_helper' # end-to-end test of the create_init_settings function describe 'create_ini_settings_test' do it { is_expected.to have_ini_setting_resource_count(3) } it { is_expected.to contain_ini_setting('/tmp/foo.ini [section1] setting1').with( ensure: 'present', section: 'section1', setting: 'setting1', value: 'val1', path: '/tmp/foo.ini' ) } it { is_expected.to contain_ini_setting('/tmp/foo.ini [section2] setting2').with( ensure: 'present', section: 'section2', setting: 'setting2', value: 'val2', path: '/tmp/foo.ini' ) } it { is_expected.to contain_ini_setting('/tmp/foo.ini [section2] setting3').with( ensure: 'absent', section: 'section2', setting: 'setting3', path: '/tmp/foo.ini' ) } end diff --git a/spec/classes/create_multiple_ini_settings_spec.rb b/spec/classes/create_multiple_ini_settings_spec.rb index 6ed90fa..db79744 100644 --- a/spec/classes/create_multiple_ini_settings_spec.rb +++ b/spec/classes/create_multiple_ini_settings_spec.rb @@ -1,41 +1,43 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'create_multiple_ini_settings' do context 'on a non-Windows platform', if: !Puppet::Util::Platform.windows? do let(:facts) do { 'os' => { 'family' => 'RedHat', 'release' => { 'major' => '7', 'minor' => '1', 'full' => '7.1.1503' } } } end it { is_expected.to compile } it { is_expected.to have_ini_setting_resource_count(2) } it { is_expected.to contain_ini_setting('/tmp/foo.ini [section1] setting1').with( ensure: 'present', section: 'section1', setting: 'setting1', value: 'value1', path: '/tmp/foo.ini', ) } it { is_expected.to contain_ini_setting('/tmp/foo.ini [section1] setting2').with( ensure: 'absent', section: 'section1', setting: 'setting2', path: '/tmp/foo.ini', ) } end context 'on a Windows platform', if: Puppet::Util::Platform.windows? do let(:facts) do { 'os' => { 'family' => 'windows' } } end it { is_expected.to compile } end end diff --git a/spec/classes/inherit_test1_spec.rb b/spec/classes/inherit_test1_spec.rb index f6f3244..500810b 100644 --- a/spec/classes/inherit_test1_spec.rb +++ b/spec/classes/inherit_test1_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require 'spec_helper' # We can't really test much here, apart from the type roundtrips though the # parser OK. describe 'inherit_test1' do it { is_expected.to contain_inherit_ini_setting('valid_type').with('value' => 'true') } end diff --git a/spec/functions/create_ini_settings_spec.rb b/spec/functions/create_ini_settings_spec.rb index 9140e0b..9431911 100644 --- a/spec/functions/create_ini_settings_spec.rb +++ b/spec/functions/create_ini_settings_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'create_ini_settings' do it_behaves_like 'create_ini_settings function' end diff --git a/spec/functions/inifile_create_ini_settings_spec.rb b/spec/functions/inifile_create_ini_settings_spec.rb index 174a691..5916998 100644 --- a/spec/functions/inifile_create_ini_settings_spec.rb +++ b/spec/functions/inifile_create_ini_settings_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + require 'spec_helper' describe 'inifile::create_ini_settings' do it_behaves_like 'create_ini_settings function' end diff --git a/spec/spec_helper_local.rb b/spec/spec_helper_local.rb index c726167..0d9188c 100644 --- a/spec/spec_helper_local.rb +++ b/spec/spec_helper_local.rb @@ -1,37 +1,39 @@ +# frozen_string_literal: true + if ENV['COVERAGE'] == 'yes' require 'simplecov' require 'simplecov-console' require 'codecov' SimpleCov.formatters = [ SimpleCov::Formatter::HTMLFormatter, SimpleCov::Formatter::Console, SimpleCov::Formatter::Codecov, ] SimpleCov.start do track_files 'lib/**/*.rb' add_filter '/spec' # do not track vendored files add_filter '/vendor' add_filter '/.vendor' # do not track gitignored files # this adds about 4 seconds to the coverage check # this could definitely be optimized add_filter do |f| # system returns true if exit status is 0, which with git-check-ignore means file is ignored system("git check-ignore --quiet #{f.filename}") end end end shared_examples 'create_ini_settings function' do it { is_expected.not_to eq(nil) } it { is_expected.to run.with_params({}) } it { is_expected.to run.with_params({}, {}) } it { is_expected.to run.with_params('section' => { 'setting' => 'value' }).and_raise_error(Puppet::ParseError, %r{must pass the path parameter}) } it { is_expected.to run.with_params(1 => 2).and_raise_error(Puppet::ParseError, %r{Section 1 must contain a Hash}) } end diff --git a/spec/unit/puppet/provider/ini_setting/inheritance_spec.rb b/spec/unit/puppet/provider/ini_setting/inheritance_spec.rb index d3b8760..0f22485 100644 --- a/spec/unit/puppet/provider/ini_setting/inheritance_spec.rb +++ b/spec/unit/puppet/provider/ini_setting/inheritance_spec.rb @@ -1,61 +1,63 @@ +# frozen_string_literal: true + require 'spec_helper' # This is a reduced version of ruby_spec.rb just to ensure we can subclass as # documented $LOAD_PATH << './spec/fixtures/inherit_ini_setting/lib' describe Puppet::Type.type(:inherit_ini_setting).provider(:ini_setting) do include PuppetlabsSpec::Files let(:tmpfile) { tmpfilename('inherit_ini_setting_test') } def validate_file(expected_content, tmpfile) expect(File.read(tmpfile)).to eq(expected_content) end before :each do File.open(tmpfile, 'w') do |fh| fh.write(orig_content) end end context 'when calling instances' do let(:orig_content) { '' } it 'parses nothing when the file is empty' do allow(described_class).to receive(:file_path).and_return(tmpfile) expect(described_class.instances).to eq([]) end context 'when the file has contents' do let(:orig_content) do <<-EOS # A comment red = blue green = purple EOS end it 'parses the results' do allow(described_class).to receive(:file_path).and_return(tmpfile) instances = described_class.instances expect(instances.size).to eq(2) # inherited version of namevar flattens the names names = instances.map do |instance| instance.instance_variable_get(:@property_hash)[:name] end # rubocop:disable Style/BlockDelimiters expect(names.sort).to eq(['green', 'red']) end end end context 'when ensuring that a setting is present' do let(:orig_content) { '' } it 'adds a value to the file' do allow(described_class).to receive(:file_path).and_return(tmpfile) resource = Puppet::Type::Inherit_ini_setting.new(setting: 'set_this', value: 'to_that') provider = described_class.new(resource) provider.create validate_file("set_this=to_that\n", tmpfile) end end end diff --git a/spec/unit/puppet/provider/ini_setting/ruby_spec.rb b/spec/unit/puppet/provider/ini_setting/ruby_spec.rb index 414eb85..d1f448e 100644 --- a/spec/unit/puppet/provider/ini_setting/ruby_spec.rb +++ b/spec/unit/puppet/provider/ini_setting/ruby_spec.rb @@ -1,1588 +1,1590 @@ +# frozen_string_literal: true + require 'spec_helper' require 'puppet' provider_class = Puppet::Type.type(:ini_setting).provider(:ruby) describe provider_class do include PuppetlabsSpec::Files let(:tmpfile) { tmpfilename('ini_setting_test') } let(:emptyfile) { tmpfilename('ini_setting_test_empty') } let(:common_params) do { title: 'ini_setting_ensure_present_test', path: tmpfile, section: 'section2', } end def validate_file(expected_content, tmpfile) expect(File.read(tmpfile)).to eq(expected_content) end before :each do File.open(tmpfile, 'w') do |fh| fh.write(orig_content) end File.open(emptyfile, 'w') do |fh| fh.write('') end end context 'when calling instances' do let :orig_content do '' end it 'fails when file path is not set' do expect { provider_class.instances }.to raise_error(Puppet::Error, 'Ini_settings only support collecting instances when a file path is hard coded') end context 'when file path is set by a child class' do child_one = Class.new(provider_class) do def self.file_path emptyfile end end it 'returns [] when file is empty' do expect(child_one).to receive(:file_path).and_return(emptyfile) expect(child_one.instances).to eq([]) end child_two = Class.new(provider_class) do def self.file_path '/some/file/path' end end it 'overrides the provider instances file_path' do resource = Puppet::Type::Ini_setting.new(common_params) provider = child_two.new(resource) expect(provider.file_path).to eq('/some/file/path') end end context 'when file has contecnts' do let(:orig_content) do <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment EOS end it 'is able to parse the results' do child_three = Class.new(provider_class) do def self.file_path '/some/file/path' end end expect(child_three).to receive(:file_path).exactly(2).times.and_return(tmpfile) expect(child_three.instances.size).to eq(7) expected_array = [ { name: 'section1/foo', value: 'foovalue' }, { name: 'section1/bar', value: 'barvalue' }, { name: 'section1/main', value: 'true' }, { name: 'section2/foo', value: 'foovalue2' }, { name: 'section2/baz', value: 'bazvalue' }, { name: 'section2/url', value: 'http://192.168.1.1:8080' }, { name: 'section:sub/subby', value: 'bar' }, ] real_array = [] ensure_array = [] child_three.instances.each do |x| prop_hash = x.instance_variable_get(:@property_hash) ensure_value = prop_hash.delete(:ensure) ensure_array.push(ensure_value) real_array.push(prop_hash) end expect(ensure_array.uniq).to eq([:present]) expect((real_array - expected_array) && (expected_array - real_array)).to eq([]) end # rubocop:enable RSpec/ExampleLength # rubocop:enable RSpec/MultipleExpectations end end context 'when ensuring that a setting is present' do let(:orig_content) do <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS end expected_content_one = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 yahoo = yippee [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'adds a missing setting to the correct section' do resource = Puppet::Type::Ini_setting.new(common_params.merge(setting: 'yahoo', value: 'yippee')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple yahoo = yippee EOS it 'adds a missing setting to the correct section with pre/suffix' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'nonstandard', setting: 'yahoo', value: 'yippee', section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_two, tmpfile) end expected_content_three = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple indented = weirdly EOS it 'adds a missing setting to the correct section with indent_char' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'nonstandard', setting: 'indented', value: 'weirdly', section_prefix: '-', section_suffix: '-', indent_char: "\t")) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_three, tmpfile) end expected_content_four = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple indented = weirdly EOS it 'adds a missing setting to the correct section indented by indent_char * indent_width' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'nonstandard', setting: 'indented', value: 'weirdly', section_prefix: '-', section_suffix: '-', indent_char: "\t", indent_width: 4)) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_four, tmpfile) end expected_content_five = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple indented = weirdly EOS it 'treats a string indent_width as an integer' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'nonstandard', setting: 'indented', value: 'weirdly', section_prefix: '-', section_suffix: '-', indent_char: "\t", indent_width: '4')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_five, tmpfile) end expected_content_six = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple yahoo = yippee EOS it 'adds a missing setting to the correct section with colon' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:sub', setting: 'yahoo', value: 'yippee')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_six, tmpfile) end expected_content_seven = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue2 url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'modifies an existing setting with a different value' do resource = Puppet::Type::Ini_setting.new(common_params.merge(setting: 'baz', value: 'bazvalue2')) provider = described_class.new(resource) expect(provider).to be_exists provider.value = 'bazvalue2' validate_file(expected_content_seven, tmpfile) end expected_content_eight = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = false [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'modifies an existing setting with a different boolean value' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'main', value: false)) provider = described_class.new(resource) expect(provider.exists?).to be true transaction = instance_double('transaction', persistence: true) expect(Puppet::Transaction::ResourceHarness.new(transaction).evaluate(provider.resource).out_of_sync).to eq(true) validate_file(expected_content_eight, tmpfile) end # rubocop:enable RSpec/MultipleExpectations : Unable to reduce without altering test expected_content_nine = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = orange EOS it 'modifies an existing setting with pre/suffix with a different value' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'nonstandard', setting: 'shoes', value: 'orange', section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider.exists?).to be true provider.value = 'orange' validate_file(expected_content_nine, tmpfile) end expected_content_ten = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=foo #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'modifies an existing setting with a different value - with colon in section' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:sub', setting: 'subby', value: 'foo')) provider = described_class.new(resource) expect(provider.value).to eq('bar') provider.value = 'foo' validate_file(expected_content_ten, tmpfile) end expected_content_eleven = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.0.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'is able to handle settings with non alphanumbering settings' do resource = Puppet::Type::Ini_setting.new(common_params.merge(setting: 'url', value: 'http://192.168.0.1:8080')) provider = described_class.new(resource) expect(provider.value).to eq('http://192.168.1.1:8080') provider.value = 'http://192.168.0.1:8080' validate_file(expected_content_eleven, tmpfile) end expected_content_twelve = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = http://192.168.0.1:8080 EOS it 'is able to handle settings with pre/suffix with non alphanumbering settings' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'nonstandard', setting: 'shoes', value: 'http://192.168.0.1:8080', section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider.value).to eq('purple') provider.value = 'http://192.168.0.1:8080' validate_file(expected_content_twelve, tmpfile) end it 'recognizes 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) expect(provider.exists?).to be true end it 'recognizes an existing setting with pre/suffix with the specified value' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'nonstandard', setting: 'shoes', value: 'purple', section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider.exists?).to be true end expected_content_thirteen = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple [section3] huzzah = shazaam EOS it 'adds 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) expect(provider.exists?).to be false provider.create validate_file(expected_content_thirteen, tmpfile) end expected_content_fourteen = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple -section3- huzzah = shazaam EOS it 'adds a new section with pre/suffix if the section does not exist' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section3', setting: 'huzzah', value: 'shazaam', section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider).not_to be_exists provider.create validate_file(expected_content_fourteen, tmpfile) end expected_content_fifteen = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple [section:subsection] huzzah = shazaam EOS it 'adds a new section if the section does not exist - with colon' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:subsection', setting: 'huzzah', value: 'shazaam')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_fifteen, tmpfile) end expected_content_sixteen = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple -section:subsection- huzzah = shazaam EOS it 'adds a new section with pre/suffix if the section does not exist - with colon' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:subsection', setting: 'huzzah', value: 'shazaam', section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_sixteen, tmpfile) end it 'adds a new empty section' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(orig_content + "\n[section]\n", tmpfile) end it 'is able to handle variables of any type' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'main', value: true)) provider = described_class.new(resource) expect(provider.value).to eq('true') end end context 'when no sections exist' do let(:orig_content) do '' end validate_zero = "[section]\n" it 'adds an empty section' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section', path: emptyfile)) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(validate_zero, emptyfile) end validate_one = '[section1] setting1 = hellowworld ' it 'adds a new section' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'setting1', value: 'hellowworld', path: emptyfile)) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(validate_one, emptyfile) end validate_two = '-section1- setting1 = hellowworld ' it 'adds a new section with pre/suffix' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'setting1', value: 'hellowworld', path: emptyfile, section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(validate_two, emptyfile) end validate_three = '[section:subsection] setting1 = hellowworld ' it 'adds a new section with colon' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:subsection', setting: 'setting1', value: 'hellowworld', path: emptyfile)) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(validate_three, emptyfile) end validate_four = '-section:subsection- setting1 = hellowworld ' it 'adds a new section with pre/suffix with colon' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:subsection', setting: 'setting1', value: 'hellowworld', path: emptyfile, section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(validate_four, emptyfile) end end context 'when only an empty section exists' do let(:orig_content) do "[section]\n" end it 'adds a new setting' do expected = orig_content { 'section' => { 'first' => 1 } }.each_pair do |section, settings| settings.each_pair do |setting, value| resource = Puppet::Type::Ini_setting.new(common_params.merge(section: section, setting: setting, value: value)) provider = described_class.new(resource) expect(provider.exists?).to be false # byebug provider.create expected += "#{setting} = #{value}\n" end end validate_file(expected, tmpfile) end end context 'when dealing with a global section' do let(:orig_content) do <<-EOS # This is a comment foo=blah [section2] foo = http://192.168.1.1:8080 ; yet another comment EOS end expected_content_one = <<-EOS # This is a comment foo=blah bar = yippee [section2] foo = http://192.168.1.1:8080 ; yet another comment EOS it "adds a missing setting if it doesn't exist" do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: '', setting: 'bar', value: 'yippee')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS # This is a comment foo=yippee [section2] foo = http://192.168.1.1:8080 ; yet another comment EOS it 'modifies an existing setting with a different value' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: '', setting: 'foo', value: 'yippee')) provider = described_class.new(resource) expect(provider.value).to eq('blah') provider.value = 'yippee' validate_file(expected_content_two, tmpfile) end it 'recognizes an existing setting with the specified value' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: '', setting: 'foo', value: 'blah')) provider = described_class.new(resource) expect(provider.exists?).to be true end end context 'when the first line of the file is a section' do let(:orig_content) do <<-EOS [section2] foo = http://192.168.1.1:8080 EOS end expected_content_one = <<-EOS foo = yippee [section2] foo = http://192.168.1.1:8080 EOS it 'is able to add a global setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: '', setting: 'foo', value: 'yippee')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS [section2] foo = yippee EOS it 'modifies an existing setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'foo', value: 'yippee')) provider = described_class.new(resource) expect(provider.value).to eq('http://192.168.1.1:8080') provider.value = 'yippee' validate_file(expected_content_two, tmpfile) end expected_content_three = <<-EOS [section2] foo = http://192.168.1.1:8080 bar = baz EOS it 'adds a new setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'bar', value: 'baz')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_three, tmpfile) end end context 'when overriding the separator' do let(:orig_content) do <<-EOS [section2] foo=bar EOS end expected_content_one = <<-EOS [section2] foo=yippee EOS it 'modifies an existing setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'foo', value: 'yippee', key_val_separator: '=')) provider = described_class.new(resource) expect(provider.value).to eq('bar') provider.value = 'yippee' validate_file(expected_content_one, tmpfile) end end context 'when overriding the separator to something other than =' do let(:orig_content) do <<-EOS [section2] foo: bar EOS end expected_content_one = <<-EOS [section2] foo: yippee EOS it 'modifies an existing setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'foo', value: 'yippee', key_val_separator: ': ')) provider = described_class.new(resource) expect(provider.value).to eq('bar') provider.value = 'yippee' validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS [section2] foo: bar bar: baz EOS it 'adds a new setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'bar', value: 'baz', key_val_separator: ': ')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_two, tmpfile) end end context 'when overriding the separator to a space' do let(:orig_content) do <<-EOS [section2] foo bar EOS end expected_content_one = <<-EOS [section2] foo yippee EOS it 'modifies an existing setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'foo', value: 'yippee', key_val_separator: ' ')) provider = described_class.new(resource) expect(provider.value).to eq('bar') provider.value = 'yippee' validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS [section2] foo bar bar baz EOS it 'adds a new setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'bar', value: 'baz', key_val_separator: ' ')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_two, tmpfile) end end context 'when ensuring that a setting is absent' do let(:orig_content) do <<-EOS [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section3] # com = ment uncom = ment [section4] uncom = ment [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS end expected_content_one = <<-EOS [section1] ; This is also a comment bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section3] # com = ment uncom = ment [section4] uncom = ment [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'removes a setting that exists' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'foo', ensure: 'absent')) provider = described_class.new(resource) expect(provider.exists?).to be true provider.destroy validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section3] # com = ment uncom = ment [section4] uncom = ment [section:sub] subby=bar #another comment ; yet another comment EOS it 'removes a setting with pre/suffix that exists' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'nonstandard', setting: 'shoes', ensure: 'absent', section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider.exists?).to be true provider.destroy validate_file(expected_content_two, tmpfile) end expected_content_three = <<-EOS [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section3] # com = ment uncom = ment [section4] uncom = ment [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'does nothing for a setting that does not exist' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:sub', setting: 'foo', ensure: 'absent')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.destroy validate_file(expected_content_three, tmpfile) end expected_content_four = <<-EOS [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section3] # com = ment uncom = ment [section4] uncom = ment [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'does nothing for a setting with pre/suffix that does not exist' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'nonstandard', setting: 'foo', ensure: 'absent', section_prefix: '-', section_suffix: '-')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.destroy validate_file(expected_content_four, tmpfile) end expected_content_five = <<-EOS [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section3] # com = ment [section4] uncom = ment [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'does not remove a section when the last uncommented setting is removed if there are comments' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section3', setting: 'uncom', ensure: 'absent')) provider = described_class.new(resource) expect(provider.exists?).to be true provider.destroy validate_file(expected_content_five, tmpfile) end expected_content_six = <<-EOS [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section3] # com = ment uncom = ment [section:sub] subby=bar #another comment ; yet another comment -nonstandard- shoes = purple EOS it 'removes the section when removing the last line in the section' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section4', setting: 'uncom', ensure: 'absent')) provider = described_class.new(resource) expect(provider.exists?).to be true provider.destroy validate_file(expected_content_six, tmpfile) end end context 'when dealing with indentation in sections' do let(:orig_content) do <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment fleezy = flam ; yet another comment EOS end expected_content_one = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true yahoo = yippee [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment fleezy = flam ; yet another comment EOS it 'adds a missing setting at the correct indentation when the header is aligned' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'yahoo', value: 'yippee')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue2 main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment fleezy = flam ; yet another comment EOS it 'updates an existing setting at the correct indentation when the header is aligned' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'bar', value: 'barvalue2')) provider = described_class.new(resource) expect(provider.exists?).to be true provider.create validate_file(expected_content_two, tmpfile) end expected_content_three = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 yahoo = yippee [section:sub] subby=bar #another comment fleezy = flam ; yet another comment EOS it 'adds a missing setting at the correct indentation when the header is not aligned' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'yahoo', value: 'yippee')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_three, tmpfile) end expected_content_four = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue2 url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment fleezy = flam ; yet another comment EOS it 'updates an existing setting at the correct indentation when the header is not aligned' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'baz', value: 'bazvalue2')) provider = described_class.new(resource) expect(provider.exists?).to be true provider.create validate_file(expected_content_four, tmpfile) end expected_content_five = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment fleezy = flam ; yet another comment yahoo = yippee EOS it 'adds a missing setting at the min indentation when the section is not aligned' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:sub', setting: 'yahoo', value: 'yippee')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_five, tmpfile) end expected_content_six = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment fleezy = flam2 ; yet another comment EOS it 'updates an existing setting at the previous indentation when the section is not aligned' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:sub', setting: 'fleezy', value: 'flam2')) provider = described_class.new(resource) expect(provider.exists?).to be true provider.create validate_file(expected_content_six, tmpfile) end expected_content_seven = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue main = true [section2] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment fleezy = flam2 ; yet another comment EOS it 'updates an existing setting at the previous indentation regardless of indent_char and indent_width settings' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section:sub', setting: 'fleezy', value: 'flam2', indent_char: 'ignore this', indent_width: 10)) provider = described_class.new(resource) expect(provider.exists?).to be true provider.create validate_file(expected_content_seven, tmpfile) end end context 'when dealing settings that have a commented version present' do let(:orig_content) do <<-EOS [section1] # foo=foovalue bar=barvalue foo = foovalue2 [section2] # foo = foovalue ;bar=barvalue blah = blah #baz= EOS end expected_content_eight = <<-EOS [section1] # foo=foovalue bar=barvalue foo = foovalue2 [section2] # foo = foovalue foo = foo3 ;bar=barvalue blah = blah #baz= EOS it 'adds a new setting below a commented version of that setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'foo', value: 'foo3')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_eight, tmpfile) end expected_content_nine = <<-EOS [section1] # foo=foovalue bar=barvalue foo = foo3 [section2] # foo = foovalue ;bar=barvalue blah = blah #baz= EOS it 'updates an existing setting in place, even if there is a commented version of that setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'foo', value: 'foo3')) provider = described_class.new(resource) expect(provider.exists?).to be true provider.create validate_file(expected_content_nine, tmpfile) end expected_content_ten = <<-EOS [section1] # foo=foovalue bar=barvalue foo = foovalue2 [section2] # foo = foovalue ;bar=barvalue bar = bar3 blah = blah #baz= EOS it 'adds a new setting below a commented version of that setting, respecting semicolons as comments' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'bar', value: 'bar3')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_ten, tmpfile) end expected_content_four = <<-EOS [section1] # foo=foovalue bar=barvalue foo = foovalue2 [section2] # foo = foovalue ;bar=barvalue blah = blah #baz= baz = bazvalue EOS it 'adds a new setting below an empty commented version of that setting' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section2', setting: 'baz', value: 'bazvalue')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_four, tmpfile) end context 'when a section only contains comments' do let(:orig_content) do <<-EOS [section1] # foo=foovalue # bar=bar2 EOS end expected_content_one = <<-EOS [section1] # foo=foovalue foo = foovalue2 # bar=bar2 EOS it 'is able to add a new setting when a section contains only comments' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'foo', value: 'foovalue2')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS [section1] # foo=foovalue # bar=bar2 bar = barvalue2 EOS it 'is able to add a new setting when it matches a commented out line other than the first one' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section1', setting: 'bar', value: 'barvalue2')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_two, tmpfile) end end context 'when sections have spaces and dashes' do let(:orig_content) do <<-EOS # This is a comment [section - one] ; This is also a comment foo=foovalue bar = barvalue main = true [section - two] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 [section:sub] subby=bar #another comment ; yet another comment EOS end expected_content_one = <<-EOS # This is a comment [section - one] ; This is also a comment foo=foovalue bar = barvalue main = true [section - two] foo= foovalue2 baz=bazvalue url = http://192.168.1.1:8080 yahoo = yippee [section:sub] subby=bar #another comment ; yet another comment EOS it 'adds a missing setting to the correct section' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'section - two', setting: 'yahoo', value: 'yippee')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_one, tmpfile) end end end context 'when sections have spaces and quotations' do let(:orig_content) do <<-EOS [branch "main"] remote = origin merge = refs/heads/main [alias] to-deploy = log --merges --grep='pull request' --format='%s (%cN)' origin/production..origin/main [branch "production"] remote = origin merge = refs/heads/production EOS end expected_content_one = <<-EOS [branch "main"] remote = origin merge = refs/heads/main [alias] to-deploy = log --merges --grep='pull request' --format='%s (%cN)' origin/production..origin/main foo = bar [branch "production"] remote = origin merge = refs/heads/production EOS it 'adds a missing setting to the correct section' do resource = Puppet::Type::Ini_setting.new(common_params.merge(section: 'alias', setting: 'foo', value: 'bar')) provider = described_class.new(resource) expect(provider.exists?).to be false provider.create validate_file(expected_content_one, tmpfile) end end # rubocop:enable Layout/IndentHeredoc end diff --git a/spec/unit/puppet/provider/ini_subsetting/ruby_spec.rb b/spec/unit/puppet/provider/ini_subsetting/ruby_spec.rb index 18f22f7..8c732ca 100644 --- a/spec/unit/puppet/provider/ini_subsetting/ruby_spec.rb +++ b/spec/unit/puppet/provider/ini_subsetting/ruby_spec.rb @@ -1,361 +1,363 @@ +# frozen_string_literal: true + require 'spec_helper' require 'puppet' provider_class = Puppet::Type.type(:ini_subsetting).provider(:ruby) describe provider_class do include PuppetlabsSpec::Files let(:tmpfile) { tmpfilename('ini_setting_test') } def validate_file(expected_content, tmpfile) expect(File.read(tmpfile)).to eq expected_content end before :each do File.open(tmpfile, 'w') do |fh| fh.write(orig_content) end end context 'when ensuring that a subsetting is present' do let(:common_params) do { title: 'ini_setting_ensure_present_test', path: tmpfile, section: '', key_val_separator: '=', setting: 'JAVA_ARGS', } end let(:orig_content) do <<-EOS JAVA_ARGS="-Xmx192m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof" EOS end expected_content_one = <<-EOS JAVA_ARGS="-Xmx192m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof -Xms128m" EOS it 'adds a missing subsetting' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-Xms', value: '128m')) provider = described_class.new(resource) expect(provider.exists?).to be_nil provider.create validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS JAVA_ARGS="-Xms128m -Xmx192m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof" EOS it 'adds a missing subsetting element at the beginning of the line' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-Xms', value: '128m', insert_type: :start)) provider = described_class.new(resource) expect(provider.exists?).to be_nil provider.create validate_file(expected_content_two, tmpfile) end expected_content_three = <<-EOS JAVA_ARGS="-Xmx192m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof -Xms128m" EOS it 'adds a missing subsetting element at the end of the line' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-Xms', value: '128m', insert_type: :end)) provider = described_class.new(resource) expect(provider.exists?).to be_nil provider.create validate_file(expected_content_three, tmpfile) end expected_content_four = <<-EOS JAVA_ARGS="-Xmx192m -Xms128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof" EOS it 'adds a missing subsetting element after the given item' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-Xms', value: '128m', insert_type: :after, insert_value: '-Xmx')) provider = described_class.new(resource) expect(provider.exists?).to be_nil provider.create validate_file(expected_content_four, tmpfile) end expected_content_five = <<-EOS JAVA_ARGS="-Xms128m -Xmx192m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof" EOS it 'adds a missing subsetting element before the given item' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-Xms', value: '128m', insert_type: :before, insert_value: '-Xmx')) provider = described_class.new(resource) expect(provider.exists?).to be_nil provider.create validate_file(expected_content_five, tmpfile) end expected_content_six = <<-EOS JAVA_ARGS="-Xmx192m -Xms128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof" EOS it 'adds a missing subsetting element at the given index' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-Xms', value: '128m', insert_type: :index, insert_value: '1')) provider = described_class.new(resource) expect(provider.exists?).to be_nil provider.create validate_file(expected_content_six, tmpfile) end expected_content_seven = <<-EOS JAVA_ARGS="-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof" EOS it 'removes an existing subsetting' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-Xmx')) provider = described_class.new(resource) expect(provider.exists?).to eq '192m' provider.destroy validate_file(expected_content_seven, tmpfile) end expected_content_eight = <<-EOS JAVA_ARGS="-Xmx192m" EOS it 'is able to remove several subsettings with the same name' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-XX')) provider = described_class.new(resource) expect(provider.exists?).to eq ':+HeapDumpOnOutOfMemoryError' provider.destroy validate_file(expected_content_eight, tmpfile) end expected_content_nine = <<-EOS JAVA_ARGS="-Xmx256m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof" EOS it 'modifies an existing subsetting' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-Xmx', value: '256m')) provider = described_class.new(resource) expect(provider.exists?).to eq '192m' provider.value = '256m' validate_file(expected_content_nine, tmpfile) end expected_content_ten = <<-EOS JAVA_ARGS="-Xmx192m -XXtest -XXtest" EOS it 'is able to modify several subsettings with the same name' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: '-XX', value: 'test')) provider = described_class.new(resource) expect(provider.exists?).to eq ':+HeapDumpOnOutOfMemoryError' provider.value = 'test' validate_file(expected_content_ten, tmpfile) end end context 'when working with subsettings in files with unquoted settings values' do let(:common_params) do { title: 'ini_setting_ensure_present_test', path: tmpfile, section: 'main', setting: 'reports', } end let(:orig_content) do <<-EOS [main] reports = http,foo EOS end expected_content_one = <<-EOS [main] reports = foo EOS it 'removes an existing subsetting' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: 'http', subsetting_separator: ',')) provider = described_class.new(resource) expect(provider.exists?).to eq '' provider.destroy validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS [main] reports = http,foo,puppetdb EOS it "adds a new subsetting when the 'parent' setting already exists" do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: 'puppetdb', subsetting_separator: ',')) provider = described_class.new(resource) expect(provider.exists?).to be_nil provider.value = '' validate_file(expected_content_two, tmpfile) end expected_content_three = <<-EOS [main] reports = http,foo somenewsetting = puppetdb EOS it "adds a new subsetting when the 'parent' setting does not already exist" do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(setting: 'somenewsetting', subsetting: 'puppetdb', subsetting_separator: ',')) provider = described_class.new(resource) expect(provider.exists?).to be_nil provider.value = '' validate_file(expected_content_three, tmpfile) end end context 'when working with subsettings in files with use_exact_match' do let(:common_params) do { title: 'ini_setting_ensure_present_test', path: tmpfile, section: 'main', setting: 'reports', use_exact_match: true, } end let(:orig_content) do <<-EOS [main] reports = http,foo EOS end expected_content_one = <<-EOS [main] reports = http,foo,fo EOS it "adds a new subsetting when the 'parent' setting already exists" do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: 'fo', subsetting_separator: ',')) provider = described_class.new(resource) provider.value = '' validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS [main] reports = http,foo EOS it 'does not remove substring subsettings' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: 'fo', subsetting_separator: ',')) provider = described_class.new(resource) provider.value = '' provider.destroy validate_file(expected_content_two, tmpfile) end end context 'when working with subsettings in files with subsetting_key_val_separator' do let(:common_params) do { title: 'ini_setting_ensure_present_test', path: tmpfile, section: 'main', setting: 'reports', subsetting_separator: ',', subsetting_key_val_separator: ':', } end let(:orig_content) do <<-EOS [main] reports = a:1,b:2 EOS end expected_content_one = <<-EOS [main] reports = a:1,b:2,c:3 EOS it "adds a new subsetting when the 'parent' setting already exists" do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: 'c', value: '3')) provider = described_class.new(resource) provider.value = '3' validate_file(expected_content_one, tmpfile) end expected_content_two = <<-EOS [main] reports = a:1,b:2 somenewsetting = c:3 EOS it "adds a new subsetting when the 'parent' setting does not already exist" do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: 'c', value: '3', setting: 'somenewsetting')) provider = described_class.new(resource) expect(provider.exists?).to be_nil provider.value = '3' validate_file(expected_content_two, tmpfile) end expected_content_three = <<-EOS [main] reports = a:1 EOS it 'is able to remove the existing subsetting' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: 'b')) provider = described_class.new(resource) expect(provider.exists?).to eq '2' provider.destroy validate_file(expected_content_three, tmpfile) end expected_content_four = <<-EOS [main] reports = a:1,b:5 EOS it 'is able to modify the existing subsetting' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(subsetting: 'b', value: '5')) provider = described_class.new(resource) expect(provider.exists?).to eq '2' provider.value = '5' validate_file(expected_content_four, tmpfile) end end context 'when delete_if_empty is toggled to true' do let(:common_params) do { title: 'ini_setting_delete_if_empty_test', path: tmpfile, section: 'main', delete_if_empty: true, } end let(:orig_content) do <<-EOS [main] reports = http something = else EOS end expected_content_one = <<-EOS [main] something = else EOS expected_content_two = '' it 'removes the subsetting when the it is empty' do resource = Puppet::Type::Ini_subsetting.new(common_params.merge(setting: 'reports', subsetting: 'http', subsetting_separator: ',')) provider = described_class.new(resource) provider.destroy validate_file(expected_content_one, tmpfile) resource = Puppet::Type::Ini_subsetting.new(common_params.merge(setting: 'something', subsetting: 'else', subsetting_separator: ',')) provider = described_class.new(resource) provider.destroy validate_file(expected_content_two, tmpfile) end end end diff --git a/spec/unit/puppet/type/ini_setting_spec.rb b/spec/unit/puppet/type/ini_setting_spec.rb index c4a717a..00b3adf 100644 --- a/spec/unit/puppet/type/ini_setting_spec.rb +++ b/spec/unit/puppet/type/ini_setting_spec.rb @@ -1,171 +1,173 @@ +# frozen_string_literal: true + require 'spec_helper' ini_setting = Puppet::Type.type(:ini_setting) describe ini_setting do describe 'path validation' do subject { -> { described_class.new(name: 'foo', path: path) } } context 'on posix platforms' do before(:each) do Puppet.features.stub(:posix?) { true } Puppet.features.stub(:microsoft_windows?) { false } Puppet::Util::Platform.stub(:windows?) { false } end context 'with an absolute path' do let(:path) { '/absolute/path' } it { is_expected.not_to raise_exception } end context 'with a relative path' do let(:path) { 'relative/path' } it { is_expected.to raise_exception } end end context 'on windows platforms' do before(:each) do Puppet.features.stub(:posix?) { false } Puppet.features.stub(:microsoft_windows?) { true } Puppet::Util::Platform.stub(:windows?) { true } end context 'with an absolute path with front slashes' do let(:path) { 'c:/absolute/path' } it { is_expected.not_to raise_exception } end context 'with an absolute path with backslashes' do let(:path) { 'c:\absolute\path' } it { is_expected.not_to raise_exception } end context 'with an absolute path with mixed slashes' do let(:path) { 'c:/absolute\path' } it { is_expected.not_to raise_exception } end context 'with a relative path with front slashes' do let(:path) { 'relative/path' } it { is_expected.to raise_exception } end context 'with a relative path with back slashes' do let(:path) { 'relative\path' } it { is_expected.to raise_exception } end end # rubocop:enable RSpec/NestedGroups end [true, false].product([true, false, 'true', 'false', 'md5', :md5]).each do |cfg, param| describe "when Puppet[:show_diff] is #{cfg} and show_diff => #{param}" do before(:each) do Puppet[:show_diff] = cfg end let(:value) { described_class.new(name: 'foo', value: 'whatever', show_diff: param).property(:value) } if cfg && [true, 'true'].include?(param) it 'displays diff' do expect(value.change_to_s('not_secret', 'at_all')).to include('not_secret', 'at_all') end it 'tells current value' do expect(value.is_to_s('not_secret_at_all')).to eq('not_secret_at_all') end it 'tells new value' do expect(value.should_to_s('not_secret_at_all')).to eq('not_secret_at_all') end elsif cfg && ['md5', :md5].include?(param) it 'tells correct md5 hashes for multiple values' do expect(value.change_to_s('not_at', 'all_secret')).to include('6edef0c4f5ec664feff6ca6fbc290970', '1660308ab156754fa09af0e8dc2c6629') end it 'does not tell singular value one' do expect(value.change_to_s('not_at #', 'all_secret')).not_to include('not_at') end it 'does not tell singular value two' do expect(value.change_to_s('not_at', 'all_secret')).not_to include('all_secret') end it 'tells md5 of current value' do expect(value.is_to_s('not_at_all_secret')).to eq('{md5}858b46aee11b780b8f5c8853668efc05') end it 'does not tell the current value' do expect(value.is_to_s('not_at_all_secret')).not_to include('not_secret_at_all') end it 'tells md5 of new value' do expect(value.should_to_s('not_at_all_secret')).to eq('{md5}858b46aee11b780b8f5c8853668efc05') end it 'does not tell the new value' do expect(value.should_to_s('not_at_all_secret')).not_to include('not_secret_at_all') end else it 'tells redaction warning in place of actual values' do expect(value.change_to_s('at_all', 'not_secret')).to include('[redacted sensitive information]') end it 'does not tell actual value one' do expect(value.change_to_s('at_all', 'not_secret')).not_to include('not_secret') end it 'does not tell actual value two' do expect(value.change_to_s('at_all', 'not_secret')).not_to include('at_all') end it 'tells redaction warning in place of current value' do expect(value.is_to_s('not_secret_at_all')).to eq('[redacted sensitive information]') end it 'does not tell current value' do expect(value.is_to_s('not_secret_at_all')).not_to include('not_secret_at_all') end it 'tells redaction warning in place of new value' do expect(value.should_to_s('not_secret_at_all')).to eq('[redacted sensitive information]') end it 'does not tell new value' do expect(value.should_to_s('not_secret_at_all')).not_to include('not_secret_at_all') end end end end describe 'when parent of :path is in the catalog' do ['posix', 'windows'].each do |platform| context "on #{platform} platforms" do before(:each) do Puppet.features.stub(:posix?) { platform == 'posix' } Puppet.features.stub(:microsoft_windows?) { platform == 'windows' } Puppet::Util::Platform.stub(:windows?) { platform == 'windows' } end let(:file_path) { (platform == 'posix') ? '/tmp' : 'c:/tmp' } let(:file_resource) { Puppet::Type.type(:file).new(name: file_path) } let(:ini_setting_resource) { described_class.new(name: 'foo', path: "#{file_path}/foo.ini") } let(:auto_req) do catalog = Puppet::Resource::Catalog.new catalog.add_resource(file_resource) catalog.add_resource(ini_setting_resource) ini_setting_resource.autorequire end it 'creates relationship' do expect(auto_req.size).to be 1 end it 'links to ini_setting resource' do expect(auto_req[0].target).to eq(ini_setting_resource) end it 'autorequires parent directory' do expect(auto_req[0].source).to eq(file_resource) end end end end end diff --git a/spec/unit/puppet/type/ini_subetting_spec.rb b/spec/unit/puppet/type/ini_subetting_spec.rb index 088d775..d32de89 100644 --- a/spec/unit/puppet/type/ini_subetting_spec.rb +++ b/spec/unit/puppet/type/ini_subetting_spec.rb @@ -1,131 +1,133 @@ +# frozen_string_literal: true + require 'spec_helper' ini_subsetting = Puppet::Type.type(:ini_subsetting) describe ini_subsetting do describe 'quote_char validation' do subject { -> { described_class.new(name: 'foo', path: path, quote_char: quote_char) } } context 'on posix platforms' do let(:path) { '/absolute/path' } let(:quote_char) { '\”' } it { is_expected.to raise_exception } end end describe 'path validation' do subject { -> { described_class.new(name: 'foo', path: path) } } context 'on posix platforms' do before(:each) do Puppet.features.stub(:posix?) { true } Puppet.features.stub(:microsoft_windows?) { false } Puppet::Util::Platform.stub(:windows?) { false } end context 'with an absolute path' do let(:path) { '/absolute/path' } it { is_expected.not_to raise_exception } end context 'with a relative path' do let(:path) { 'relative/path' } it { is_expected.to raise_exception } end end context 'on windows platforms' do before(:each) do Puppet.features.stub(:posix?) { false } Puppet.features.stub(:microsoft_windows?) { true } Puppet::Util::Platform.stub(:windows?) { true } end context 'with an absolute path with front slashes' do let(:path) { 'c:/absolute/path' } it { is_expected.not_to raise_exception } end context 'with a relative path with back slashes' do let(:path) { 'relative\path' } it { is_expected.to raise_exception } end end end [true, false].product([true, false, :md5]).each do |cfg, param| describe "when Puppet[:show_diff] is #{cfg} and show_diff => #{param}" do before(:each) do Puppet[:show_diff] = cfg end let(:value) { described_class.new(name: 'foo', value: 'whatever', show_diff: param).property(:value) } if cfg && param == true it 'displays diff' do expect(value.change_to_s('not_secret', 'at_all')).to include('not_secret', 'at_all') end it 'tells current value' do expect(value.is_to_s('not_secret_at_all')).to eq('not_secret_at_all') end it 'tells new value' do expect(value.should_to_s('not_secret_at_all')).to eq('not_secret_at_all') end elsif cfg && param == :md5 it 'tells correct md5 hashes for multiple values' do expect(value.change_to_s('not_secret', 'at_all')).to include('e9e8db547f8960ef32dbc34029735564', '46cd73a9509ba78c39f05faf078a8cbe') end it 'does not tell singular value one' do expect(value.change_to_s('not_secret', 'at_all')).not_to include('not_secret') end it 'does not tell singular value two' do expect(value.change_to_s('not_secret', 'at_all')).not_to include('at_all') end it 'tells md5 of current value' do expect(value.is_to_s('not_secret_at_all')).to eq('{md5}218fde79f501b8ab8d212f1059bb857f') end it 'does not tell the current value' do expect(value.is_to_s('not_secret_at_all')).not_to include('not_secret_at_all') end it 'tells md5 of new value' do expect(value.should_to_s('not_secret_at_all')).not_to include('not_secret_at_all') end it 'does not tell the new value' do expect(value.should_to_s('not_secret_at_all')).to eq('{md5}218fde79f501b8ab8d212f1059bb857f') end else it 'tells redaction warning in place of actual values' do expect(value.change_to_s('not_at', 'all_secret')).to include('[redacted sensitive information]') end it 'does not tell actual value one' do expect(value.change_to_s('not_at', 'all_secret')).not_to include('not_at') end it 'does not tell actual value two' do expect(value.change_to_s('not_at', 'all_secret')).not_to include('all_secret') end it 'tells redaction warning in place of current value' do expect(value.is_to_s('not_at_all_secret')).to eq('[redacted sensitive information]') end it 'does not tell current value' do expect(value.is_to_s('not_at_all_secret')).not_to include('not_at_all_secret') end it 'tells redaction warning in place of new value' do expect(value.should_to_s('not_at_all_secret')).to eq('[redacted sensitive information]') end it 'does not tell new value' do expect(value.should_to_s('not_at_all_secret')).not_to include('not_at_all_secret') end end end end end diff --git a/spec/unit/puppet/util/external_iterator_spec.rb b/spec/unit/puppet/util/external_iterator_spec.rb index e19e8a3..4773d83 100644 --- a/spec/unit/puppet/util/external_iterator_spec.rb +++ b/spec/unit/puppet/util/external_iterator_spec.rb @@ -1,36 +1,38 @@ +# frozen_string_literal: true + require 'spec_helper' require 'puppet/util/external_iterator' describe Puppet::Util::ExternalIterator do subject_class = nil expected_values = nil before(:each) do subject_class = described_class.new(['a', 'b', 'c']) expected_values = [['a', 0], ['b', 1], ['c', 2]] end context '#next' do it 'iterates over the items' do expected_values.each do |expected_pair| expect(subject_class.next).to eq(expected_pair) end end end context '#peek' do it 'returns the 0th item repeatedly' do (0..2).each do |_i| expect(subject_class.peek).to eq(expected_values[0]) end end it 'does not advance the iterator, but should reflect calls to #next' do expected_values.each do |expected_pair| expect(subject_class.peek).to eq(expected_pair) expect(subject_class.peek).to eq(expected_pair) expect(subject_class.next).to eq(expected_pair) end end end end diff --git a/spec/unit/puppet/util/ini_file_spec.rb b/spec/unit/puppet/util/ini_file_spec.rb index bb4ceda..efd5122 100644 --- a/spec/unit/puppet/util/ini_file_spec.rb +++ b/spec/unit/puppet/util/ini_file_spec.rb @@ -1,316 +1,318 @@ +# frozen_string_literal: true + require 'spec_helper' require 'stringio' require 'puppet/util/ini_file' describe Puppet::Util::IniFile do subject(:ini_sub) { described_class.new('/my/ini/file/path') } before :each do allow(File).to receive(:file?).with('/my/ini/file/path') { true } allow(described_class).to receive(:readlines).once.with('/my/ini/file/path') do sample_content end end context 'when parsing a file' do let(:sample_content) do template = <<-EOS # This is a comment [section1] ; This is also a comment foo=foovalue bar = barvalue baz = [section2] foo= foovalue2 baz=bazvalue ; commented = out setting #another comment ; yet another comment zot = multi word value xyzzy['thing1']['thing2']=xyzzyvalue l=git log EOS template.split("\n") end it 'parses the correct number of sections' do # there is always a "global" section, so our count should be 3. expect(ini_sub.section_names.length).to eq(3) end it 'parses the correct section_names' do # there should always be a "global" section named "" at the beginning of the list expect(ini_sub.section_names).to eq(['', 'section1', 'section2']) end it 'exposes settings for sections #section1' do expect(ini_sub.get_settings('section1')).to eq('bar' => 'barvalue', 'baz' => '', 'foo' => 'foovalue') end it 'exposes settings for sections #section2' do expect(ini_sub.get_settings('section2')).to eq('baz' => 'bazvalue', 'foo' => 'foovalue2', 'l' => 'git log', "xyzzy['thing1']['thing2']" => 'xyzzyvalue', 'zot' => 'multi word value') end end context 'when parsing a file whose first line is a section' do let(:sample_content) do template = <<-EOS [section1] ; This is a comment foo=foovalue EOS template.split("\n") end it 'parses the correct number of sections' do # there is always a "global" section, so our count should be 2. expect(ini_sub.section_names.length).to eq(2) end it 'parses the correct section_names' do # there should always be a "global" section named "" at the beginning of the list expect(ini_sub.section_names).to eq(['', 'section1']) end it 'exposes settings for sections' do expect(ini_sub.get_value('section1', 'foo')).to eq('foovalue') end end context "when parsing a file with a 'global' section" do let(:sample_content) do template = <<-EOS foo = bar [section1] ; This is a comment foo=foovalue EOS template.split("\n") end it 'parses the correct number of sections' do # there is always a "global" section, so our count should be 2. expect(ini_sub.section_names.length).to eq(2) end it 'parses the correct section_names' do # there should always be a "global" section named "" at the beginning of the list expect(ini_sub.section_names).to eq(['', 'section1']) end it 'exposes settings for sections #bar' do expect(ini_sub.get_value('', 'foo')).to eq('bar') end it 'exposes settings for sections #foovalue' do expect(ini_sub.get_value('section1', 'foo')).to eq('foovalue') end end context 'when updating a file with existing empty values' do let(:sample_content) do template = <<-EOS [section1] foo= #bar= #xyzzy['thing1']['thing2']='xyzzyvalue' EOS template.split("\n") end # rubocop:disable RSpec/ExpectInHook before :each do expect(ini_sub.get_value('section1', 'far')).to eq(nil) expect(ini_sub.get_value('section1', 'bar')).to eq(nil) expect(ini_sub.get_value('section1', "xyzzy['thing1']['thing2']")).to eq(nil) end # rubocop:enable RSpec/ExpectInHook it 'properlies update uncommented values' do ini_sub.set_value('section1', 'foo', ' = ', 'foovalue') expect(ini_sub.get_value('section1', 'foo')).to eq('foovalue') end it 'properlies update uncommented values without separator' do ini_sub.set_value('section1', 'foo', 'foovalue') expect(ini_sub.get_value('section1', 'foo')).to eq('foovalue') end it 'properlies update commented value' do ini_sub.set_value('section1', 'bar', ' = ', 'barvalue') expect(ini_sub.get_value('section1', 'bar')).to eq('barvalue') end it 'properlies update commented values' do ini_sub.set_value('section1', "xyzzy['thing1']['thing2']", ' = ', 'xyzzyvalue') expect(ini_sub.get_value('section1', "xyzzy['thing1']['thing2']")).to eq('xyzzyvalue') end it 'properlies update commented value without separator' do ini_sub.set_value('section1', 'bar', 'barvalue') expect(ini_sub.get_value('section1', 'bar')).to eq('barvalue') end it 'properlies update commented values without separator' do ini_sub.set_value('section1', "xyzzy['thing1']['thing2']", 'xyzzyvalue') expect(ini_sub.get_value('section1', "xyzzy['thing1']['thing2']")).to eq('xyzzyvalue') end it 'properlies add new empty values' do ini_sub.set_value('section1', 'baz', ' = ', 'bazvalue') expect(ini_sub.get_value('section1', 'baz')).to eq('bazvalue') end it 'adds new empty values without separator' do ini_sub.set_value('section1', 'baz', 'bazvalue') expect(ini_sub.get_value('section1', 'baz')).to eq('bazvalue') end end context 'the file has quotation marks in its section names' do let(:sample_content) do template = <<-EOS [branch "main"] remote = origin merge = refs/heads/main [alias] to-deploy = log --merges --grep='pull request' --format='%s (%cN)' origin/production..origin/main [branch "production"] remote = origin merge = refs/heads/production EOS template.split("\n") end it 'parses the sections' do expect(ini_sub.section_names).to match_array ['', 'branch "main"', 'alias', 'branch "production"'] end end context 'Samba INI file with dollars in section names' do let(:sample_content) do template = <<-EOS [global] workgroup = FELLOWSHIP ; ... idmap config * : backend = tdb [printers] comment = All Printers ; ... browseable = No [print$] comment = Printer Drivers path = /var/lib/samba/printers [Shares] path = /home/shares read only = No guest ok = Yes EOS template.split("\n") end it 'parses the correct section_names' do expect(ini_sub.section_names).to match_array ['', 'global', 'printers', 'print$', 'Shares'] end end context 'section names with forward slashes in them' do let(:sample_content) do template = <<-EOS [monitor:///var/log/*.log] disabled = test_value EOS template.split("\n") end it 'parses the correct section_names' do expect(ini_sub.section_names).to match_array [ '', 'monitor:///var/log/*.log', ] end end context 'KDE Configuration with braces in setting names' do let(:sample_content) do template = <<-EOS [khotkeys] _k_friendly_name=khotkeys {5465e8c7-d608-4493-a48f-b99d99fdb508}=Print,none,PrintScreen {d03619b6-9b3c-48cc-9d9c-a2aadb485550}=Search,none,Search EOS template.split("\n") end it 'exposes settings for sections #print' do expect(ini_sub.get_value('khotkeys', '{5465e8c7-d608-4493-a48f-b99d99fdb508}')).to eq('Print,none,PrintScreen') end it 'exposes settings for sections #search' do expect(ini_sub.get_value('khotkeys', '{d03619b6-9b3c-48cc-9d9c-a2aadb485550}')).to eq('Search,none,Search') end end context 'Configuration with colons in setting names' do let(:sample_content) do template = <<-EOS [Drive names] A:=5.25" Floppy B:=3.5" Floppy C:=Winchester EOS template.split("\n") end it 'exposes settings for sections #A' do expect(ini_sub.get_value('Drive names', 'A:')).to eq '5.25" Floppy' end it 'exposes settings for sections #B' do expect(ini_sub.get_value('Drive names', 'B:')).to eq '3.5" Floppy' end it 'exposes settings for sections #C' do expect(ini_sub.get_value('Drive names', 'C:')).to eq 'Winchester' end end context 'Configuration with spaces in setting names' do let(:sample_content) do template = <<-EOS [global] # log files split per-machine: log file = /var/log/samba/log.%m kerberos method = system keytab passdb backend = tdbsam security = ads EOS template.split("\n") end it 'exposes settings for sections #log' do expect(ini_sub.get_value('global', 'log file')).to eq '/var/log/samba/log.%m' end it 'exposes settings for sections #kerberos' do expect(ini_sub.get_value('global', 'kerberos method')).to eq 'system keytab' end it 'exposes settings for sections #passdb' do expect(ini_sub.get_value('global', 'passdb backend')).to eq 'tdbsam' end it 'exposes settings for sections #security' do expect(ini_sub.get_value('global', 'security')).to eq 'ads' end end end diff --git a/spec/unit/puppet/util/setting_value_spec.rb b/spec/unit/puppet/util/setting_value_spec.rb index e2937db..b830123 100644 --- a/spec/unit/puppet/util/setting_value_spec.rb +++ b/spec/unit/puppet/util/setting_value_spec.rb @@ -1,120 +1,122 @@ +# frozen_string_literal: true + require 'spec_helper' require 'puppet/util/setting_value' describe Puppet::Util::SettingValue do describe 'space subsetting separator' do INIT_VALUE_SPACE = '"-Xmx192m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof"'.freeze let(:setting_value) { described_class.new(INIT_VALUE_SPACE, ' ') } it 'gets the original value' do expect(setting_value.get_value).to eq(INIT_VALUE_SPACE) end it 'gets the correct value' do expect(setting_value.get_subsetting_value('-Xmx')).to eq('192m') end it 'adds a new value #correct' do setting_value.add_subsetting('-Xms', '256m') expect(setting_value.get_subsetting_value('-Xms')).to eq('256m') end it 'adds a new value #original' do setting_value.add_subsetting('-Xms', '256m') expect(setting_value.get_value).to eq(INIT_VALUE_SPACE[0, INIT_VALUE_SPACE.length - 1] + ' -Xms256m"') end it 'changes existing value' do setting_value.add_subsetting('-Xmx', '512m') expect(setting_value.get_subsetting_value('-Xmx')).to eq('512m') end it 'removes existing value' do setting_value.remove_subsetting('-Xmx') expect(setting_value.get_subsetting_value('-Xmx')).to eq(nil) end end describe 'comma subsetting separator' do INIT_VALUE_COMMA = '"-Xmx192m,-XX:+HeapDumpOnOutOfMemoryError,-XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof"'.freeze let(:setting_value) { described_class.new(INIT_VALUE_COMMA, ',') } it 'gets the original value' do expect(setting_value.get_value).to eq(INIT_VALUE_COMMA) end it 'gets the correct value' do expect(setting_value.get_subsetting_value('-Xmx')).to eq('192m') end it 'adds a new value #actual' do setting_value.add_subsetting('-Xms', '256m') expect(setting_value.get_subsetting_value('-Xms')).to eq('256m') end it 'adds a new value #original' do setting_value.add_subsetting('-Xms', '256m') expect(setting_value.get_value).to eq(INIT_VALUE_COMMA[0, INIT_VALUE_COMMA.length - 1] + ',-Xms256m"') end it 'changes existing value' do setting_value.add_subsetting('-Xmx', '512m') expect(setting_value.get_subsetting_value('-Xmx')).to eq('512m') end it 'removes existing value' do setting_value.remove_subsetting('-Xmx') expect(setting_value.get_subsetting_value('-Xmx')).to eq(nil) end end describe 'quote_char parameter' do QUOTE_CHAR = '"'.freeze INIT_VALUE_UNQUOTED = '-Xmx192m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/pe-puppetdb/puppetdb-oom.hprof'.freeze it 'gets quoted empty string if original value was empty' do setting_value = described_class.new(nil, ' ', QUOTE_CHAR) expect(setting_value.get_value).to eq(QUOTE_CHAR * 2) end it 'quotes the setting when adding a value #actual' do setting_value = described_class.new(INIT_VALUE_UNQUOTED, ' ', QUOTE_CHAR) setting_value.add_subsetting('-Xms', '256m') expect(setting_value.get_subsetting_value('-Xms')).to eq('256m') end it 'quotes the setting when adding a value #original' do setting_value = described_class.new(INIT_VALUE_UNQUOTED, ' ', QUOTE_CHAR) setting_value.add_subsetting('-Xms', '256m') expect(setting_value.get_value).to eq(QUOTE_CHAR + INIT_VALUE_UNQUOTED + ' -Xms256m' + QUOTE_CHAR) end it 'quotes the setting when changing an existing value #value' do setting_value = described_class.new(INIT_VALUE_UNQUOTED, ' ', QUOTE_CHAR) setting_value.add_subsetting('-Xmx', '512m') expect(setting_value.get_subsetting_value('-Xmx')).to eq('512m') end it 'quotes the setting when changing an existing value #quotes' do setting_value = described_class.new(INIT_VALUE_UNQUOTED, ' ', QUOTE_CHAR) setting_value.add_subsetting('-Xmx', '512m') expect(setting_value.get_value).to match(%r{^#{Regexp.quote(QUOTE_CHAR)}.*#{Regexp.quote(QUOTE_CHAR)}$}) end it 'quotes the setting when removing an existing value #value' do setting_value = described_class.new(INIT_VALUE_UNQUOTED, ' ', QUOTE_CHAR) setting_value.remove_subsetting('-Xmx') expect(setting_value.get_subsetting_value('-Xmx')).to eq(nil) end end it 'quotes the setting when removing an existing value #quotes' do setting_value = described_class.new(INIT_VALUE_UNQUOTED, ' ', QUOTE_CHAR) setting_value.remove_subsetting('-Xmx') expect(setting_value.get_value).to match(%r{^#{Regexp.quote(QUOTE_CHAR)}.*#{Regexp.quote(QUOTE_CHAR)}$}) end end