diff --git a/REFERENCE.md b/REFERENCE.md
index 7d7b672790..9b460e543e 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -1526,7 +1526,6 @@ The following parameters are available in the `postgresql::server::config_entry`
* [`key`](#-postgresql--server--config_entry--key)
* [`value`](#-postgresql--server--config_entry--value)
* [`path`](#-postgresql--server--config_entry--path)
-* [`comment`](#-postgresql--server--config_entry--comment)
##### `ensure`
@@ -1560,14 +1559,6 @@ Path for postgresql.conf
Default value: `$postgresql::server::postgresql_conf_path`
-##### `comment`
-
-Data type: `Optional[String[1]]`
-
-Defines the comment for the setting. The # is added by default.
-
-Default value: `undef`
-
### `postgresql::server::database`
Define for creating a database.
@@ -4309,12 +4300,6 @@ This type allows puppet to manage postgresql.conf parameters.
The following properties are available in the `postgresql_conf` type.
-##### `comment`
-
-Valid values: `%r{^[\w\W]+$}`
-
-The comment to set for this parameter.
-
##### `ensure`
Valid values: `present`, `absent`
@@ -4323,9 +4308,11 @@ The basic property that the resource should be in.
Default value: `present`
-##### `value`
+##### `target`
-Valid values: `%r{^\S(.*\S)?$}`
+The path to postgresql.conf
+
+##### `value`
The value to set for this parameter.
@@ -4333,16 +4320,8 @@ The value to set for this parameter.
The following parameters are available in the `postgresql_conf` type.
-* [`key`](#-postgresql_conf--key)
* [`name`](#-postgresql_conf--name)
* [`provider`](#-postgresql_conf--provider)
-* [`target`](#-postgresql_conf--target)
-
-##### `key`
-
-Valid values: `%r{^[\w.]+$}`
-
-The Postgresql parameter to manage.
##### `name`
@@ -4350,19 +4329,13 @@ Valid values: `%r{^[\w.]+$}`
namevar
-A unique title for the resource.
+The postgresql parameter name to manage.
##### `provider`
The specific backend to use for this `postgresql_conf` resource. You will seldom need to specify this --- Puppet will
usually discover the appropriate provider for your platform.
-##### `target`
-
-Valid values: `%r{^/\S+[a-z0-9(/)-]*\w+.conf$}`
-
-The path to the postgresql config file
-
### `postgresql_conn_validator`
Verify that a connection can be successfully established between a node
diff --git a/lib/puppet/provider/postgresql_conf/parsed.rb b/lib/puppet/provider/postgresql_conf/parsed.rb
new file mode 100644
index 0000000000..8918769cab
--- /dev/null
+++ b/lib/puppet/provider/postgresql_conf/parsed.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'puppet/provider/parsedfile'
+
+Puppet::Type.type(:postgresql_conf).provide(
+ :parsed,
+ parent: Puppet::Provider::ParsedFile,
+ default_target: '/etc/postgresql.conf',
+ filetype: :flat,
+) do
+ desc 'Set key/values in postgresql.conf.'
+
+ text_line :comment, match: %r{^\s*#}
+ text_line :blank, match: %r{^\s*$}
+
+ record_line :parsed,
+ fields: ['name', 'value', 'comment'],
+ optional: ['comment'],
+ match: %r{^\s*([\w.]+)\s*=?\s*(.*?)(?:\s*#\s*(.*))?\s*$},
+ to_line: proc { |h|
+ # simple string and numeric values don't need to be enclosed in quotes
+ val = if h[:value].is_a?(Numeric)
+ h[:value].to_s
+ elsif h[:value].is_a?(Array)
+ # multiple listen_addresses specified as a string containing a comma-speparated list
+ h[:value].join(', ')
+ else
+ h[:value]
+ end
+ dontneedquote = val.match(%r{^(\d+.?\d+|\w+)$})
+ dontneedequal = h[:name].match(%r{^(include|include_if_exists)$}i)
+
+ str = h[:name].downcase # normalize case
+ str += dontneedequal ? ' ' : ' = '
+ str += "'" unless dontneedquote && !dontneedequal
+ str += val
+ str += "'" unless dontneedquote && !dontneedequal
+ str += " # #{h[:comment]}" unless h[:comment].nil? || h[:comment] == :absent
+ str
+ },
+ post_parse: proc { |h|
+ h[:name].downcase! # normalize case
+ h[:value].gsub!(%r{(^'|'$)}, '') # strip out quotes
+ }
+end
diff --git a/lib/puppet/provider/postgresql_conf/ruby.rb b/lib/puppet/provider/postgresql_conf/ruby.rb
deleted file mode 100644
index 63b87478d1..0000000000
--- a/lib/puppet/provider/postgresql_conf/ruby.rb
+++ /dev/null
@@ -1,167 +0,0 @@
-# frozen_string_literal: true
-
-# This provider is used to manage postgresql.conf files
-# It uses ruby to parse the config file and
-# to add, remove or modify settings.
-#
-# The provider is able to parse postgresql.conf files with the following format:
-# key = value # comment
-
-Puppet::Type.type(:postgresql_conf).provide(:ruby) do
- desc 'Set keys, values and comments in a postgresql config file.'
- confine kernel: 'Linux'
-
- # The function pareses the postgresql.conf and figures out which active settings exist in a config file and returns an array of hashes
- #
- def parse_config
- # open the config file
- file = File.open(resource[:target])
- # regex to match active keys, values and comments
- active_values_regex = %r{^\s*(?[\w.]+)\s*=?\s*(?.*?)(?:\s*#\s*(?.*))?\s*$}
- # empty array to be filled with hashes
- active_settings = []
- # iterate the file and construct a hash for every matching/active setting
- # the hash is pushed to the array and the array is returned
- File.foreach(file).with_index do |line, index|
- line_number = index + 1
- matches = line.match(active_values_regex)
- if matches
- value = if matches[:value].to_i.to_s == matches[:value]
- matches[:value].to_i
- elsif matches[:value].to_f.to_s == matches[:value]
- matches[:value].to_f
- else
- matches[:value].delete("'")
- end
- attributes_hash = { line_number: line_number, key: matches[:key], ensure: 'present', value: value, comment: matches[:comment] }
- active_settings.push(attributes_hash)
- end
- end
- Puppet.debug("DEBUG: parse_config Active Settings found in Postgreql config file: #{active_settings}")
- active_settings
- end
-
- # Deletes an existing header from a parsed postgresql.conf configuration file
- #
- # @param [Array] lines of the parsed postgresql configuration file
- def delete_header(lines)
- header_regex = %r{^# HEADER:.*}
- lines.delete_if do |entry|
- entry.match?(header_regex)
- end
- end
-
- # Adds a header to a parsed postgresql.conf configuration file, after all other changes are made
- #
- # @param [Array] lines of the parsed postgresql configuration file
- def add_header(lines)
- timestamp = Time.now.strftime('%F %T %z')
- header = ["# HEADER: This file was autogenerated at #{timestamp}\n",
- "# HEADER: by puppet. While it can still be managed manually, it\n",
- "# HEADER: is definitely not recommended.\n"]
- header + lines
- end
-
- # This function writes the config file, it removes the old header, adds a new one and writes the file
- #
- # @param [File] the file object of the postgresql configuration file
- # @param [Array] lines of the parsed postgresql configuration file
- def write_config(file, lines)
- lines = delete_header(lines)
- lines = add_header(lines)
- File.write(file, lines.join)
- end
-
- # check, if resource exists in postgresql.conf file
- def exists?
- select = parse_config.select { |hash| hash[:key] == resource[:key] }
- raise ParserError, "found multiple config items of #{resource[:key]} found, please fix this" if select.length > 1
- return false if select.empty?
-
- @result = select.first
- Puppet.debug("DEBUG: exists? @result: #{@result}")
- true
- end
-
- # remove resource if exists and is set to absent
- def destroy
- entry_regex = %r{#{resource[:key]}.*=.*#{resource[:value]}}
- file = File.open(resource[:target])
- lines = File.readlines(file)
-
- lines.delete_if do |entry|
- entry.match?(entry_regex)
- end
- write_config(file, lines)
- end
-
- # create resource if it does not exists
- def create
- file = File.open(resource[:target])
- lines = File.readlines(file)
- new_line = line(key: resource[:key], value: resource[:value], comment: resource[:comment])
-
- lines.push(new_line)
- write_config(file, lines)
- end
-
- # getter - get value of a resource
- def value
- @result[:value]
- end
-
- # getter - get comment of a resource
- def comment
- @result[:comment]
- end
-
- # setter - set value of a resource
- def value=(_value)
- file = File.open(resource[:target])
- lines = File.readlines(file)
- active_values_regex = %r{^\s*(?[\w.]+)\s*=?\s*(?.*?)(?:\s*#\s*(?.*))?\s*$}
- new_line = line(key: resource[:key], value: resource[:value], comment: resource[:comment])
-
- lines.each_with_index do |line, index|
- matches = line.to_s.match(active_values_regex)
- lines[index] = new_line if matches && (matches[:key] == resource[:key] && matches[:value] != resource[:value])
- end
- write_config(file, lines)
- end
-
- # setter - set comment of a resource
- def comment=(_comment)
- file = File.open(resource[:target])
- lines = File.readlines(file)
- active_values_regex = %r{^\s*(?[\w.]+)\s*=?\s*(?.*?)(?:\s*#\s*(?.*))?\s*$}
- new_line = line(key: resource[:key], value: resource[:value], comment: resource[:comment])
-
- lines.each_with_index do |line, index|
- matches = line.to_s.match(active_values_regex)
- lines[index] = new_line if matches && (matches[:key] == resource[:key] && matches[:comment] != resource[:comment])
- end
- write_config(file, lines)
- end
-
- private
-
- # Takes elements for a postgresql.conf configuration line and formats them properly
- #
- # @param [String] key postgresql.conf configuration option
- # @param [String] value the value for the configuration option
- # @param [String] comment optional comment that will be added at the end of the line
- # @return [String] line the whole line for the config file, with \n
- def line(key: '', value: '', comment: nil)
- value = value.to_s if value.is_a?(Numeric)
- dontneedquote = value.match(%r{^(\d+.?\d+|\w+)$})
- dontneedequal = key.match(%r{^(include|include_if_exists)$}i)
- line = key.downcase # normalize case
- line += dontneedequal ? ' ' : ' = '
- line += "'" unless dontneedquote && !dontneedequal
- line += value
- line += "'" unless dontneedquote && !dontneedequal
- line += " # #{comment}" unless comment.nil? || comment == :absent
- line += "\n"
- line
- end
-end
diff --git a/lib/puppet/type/postgresql_conf.rb b/lib/puppet/type/postgresql_conf.rb
index 432f5aa877..c014ac0fe8 100644
--- a/lib/puppet/type/postgresql_conf.rb
+++ b/lib/puppet/type/postgresql_conf.rb
@@ -2,40 +2,28 @@
Puppet::Type.newtype(:postgresql_conf) do
@doc = 'This type allows puppet to manage postgresql.conf parameters.'
+
ensurable
newparam(:name) do
- desc 'A unique title for the resource.'
- newvalues(%r{^[\w.]+$})
- end
+ desc 'The postgresql parameter name to manage.'
+ isnamevar
- newparam(:key) do
- desc 'The Postgresql parameter to manage.'
newvalues(%r{^[\w.]+$})
end
newproperty(:value) do
desc 'The value to set for this parameter.'
- newvalues(%r{^\S(.*\S)?$})
+ end
- munge do |value|
- if value.to_i.to_s == value
- value.to_i
- elsif value.to_f.to_s == value
- value.to_f
+ newproperty(:target) do
+ desc 'The path to postgresql.conf'
+ defaultto do
+ if @resource.class.defaultprovider.ancestors.include?(Puppet::Provider::ParsedFile)
+ @resource.class.defaultprovider.default_target
else
- value
+ nil
end
end
end
-
- newproperty(:comment) do
- desc 'The comment to set for this parameter.'
- newvalues(%r{^[\w\W]+$})
- end
-
- newparam(:target) do
- desc 'The path to the postgresql config file'
- newvalues(%r{^/\S+[a-z0-9(/)-]*\w+.conf$})
- end
end
diff --git a/manifests/server/config_entry.pp b/manifests/server/config_entry.pp
index d17b844a18..65cd68315c 100644
--- a/manifests/server/config_entry.pp
+++ b/manifests/server/config_entry.pp
@@ -4,14 +4,12 @@
# @param key Defines the key/name for the setting. Defaults to $name
# @param value Defines the value for the setting.
# @param path Path for postgresql.conf
-# @param comment Defines the comment for the setting. The # is added by default.
#
define postgresql::server::config_entry (
- Enum['present', 'absent'] $ensure = 'present',
- String[1] $key = $name,
- Optional[Variant[String[1], Numeric, Array[String[1]]]] $value = undef,
- Stdlib::Absolutepath $path = $postgresql::server::postgresql_conf_path,
- Optional[String[1]] $comment = undef,
+ Enum['present', 'absent'] $ensure = 'present',
+ String[1] $key = $name,
+ Optional[Variant[String[1], Numeric, Array[String[1]]]] $value = undef,
+ Stdlib::Absolutepath $path = $postgresql::server::postgresql_conf_path
) {
# Those are the variables that are marked as "(change requires restart)"
# on postgresql.conf. Items are ordered as on postgresql.conf.
@@ -87,9 +85,8 @@
postgresql_conf { $name:
ensure => $ensure,
target => $path,
- key => $key,
+ name => $key,
value => $value,
- comment => $comment,
require => Class['postgresql::server::initdb'],
}
}
diff --git a/spec/unit/provider/postgresql_conf/parsed_spec.rb b/spec/unit/provider/postgresql_conf/parsed_spec.rb
new file mode 100644
index 0000000000..7f6fdaef05
--- /dev/null
+++ b/spec/unit/provider/postgresql_conf/parsed_spec.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'tempfile'
+
+provider_class = Puppet::Type.type(:postgresql_conf).provider(:parsed)
+
+describe provider_class do
+ let(:title) { 'postgresql_conf' }
+ let(:provider) do
+ conf_class = Puppet::Type.type(:postgresql_conf)
+ provider = conf_class.provider(:parsed)
+ conffile = tmpfilename('postgresql.conf')
+ allow_any_instance_of(provider).to receive(:target).and_return conffile # rubocop:disable RSpec/AnyInstance
+ provider
+ end
+
+ after :each do
+ provider.initvars
+ end
+
+ describe 'simple configuration that should be allowed' do
+ it 'parses a simple ini line' do
+ expect(provider.parse_line("listen_addreses = '*'")).to eq(
+ name: 'listen_addreses', value: '*', comment: nil, record_type: :parsed,
+ )
+ end
+
+ it 'parses a simple ini line (2)' do
+ expect(provider.parse_line(" listen_addreses = '*'")).to eq(
+ name: 'listen_addreses', value: '*', comment: nil, record_type: :parsed,
+ )
+ end
+
+ it 'parses a simple ini line (3)' do
+ expect(provider.parse_line("listen_addreses = '*' # dont mind me")).to eq(
+ name: 'listen_addreses', value: '*', comment: 'dont mind me', record_type: :parsed,
+ )
+ end
+
+ it 'parses a comment' do
+ expect(provider.parse_line('# dont mind me')).to eq(
+ line: '# dont mind me', record_type: :comment,
+ )
+ end
+
+ it 'parses a comment (2)' do
+ expect(provider.parse_line(" \t# dont mind me")).to eq(
+ line: " \t# dont mind me", record_type: :comment,
+ )
+ end
+
+ it 'allows includes' do
+ expect(provider.parse_line('include puppetextra')).to eq(
+ name: 'include', value: 'puppetextra', comment: nil, record_type: :parsed,
+ )
+ end
+
+ it 'allows numbers through without quotes' do
+ expect(provider.parse_line('wal_keep_segments = 32')).to eq(
+ name: 'wal_keep_segments', value: '32', comment: nil, record_type: :parsed,
+ )
+ end
+
+ it 'allows blanks through' do
+ expect(provider.parse_line('')).to eq(
+ line: '', record_type: :blank,
+ )
+ end
+
+ it 'parses keys with dots' do
+ expect(provider.parse_line('auto_explain.log_min_duration = 1ms')).to eq(
+ name: 'auto_explain.log_min_duration', value: '1ms', comment: nil, record_type: :parsed,
+ )
+ end
+ end
+
+ describe 'configuration that should be set' do
+ it 'sets comment lines' do
+ expect(provider.to_line(line: '# dont mind me', record_type: :comment)).to eq(
+ '# dont mind me',
+ )
+ end
+
+ it 'sets blank lines' do
+ expect(provider.to_line(line: '', record_type: :blank)).to eq(
+ '',
+ )
+ end
+
+ it 'sets simple configuration' do
+ expect(provider.to_line(name: 'listen_addresses', value: '*', comment: nil, record_type: :parsed)).to eq(
+ "listen_addresses = '*'",
+ )
+ end
+
+ it 'sets simple configuration with period in name' do
+ expect(provider.to_line(name: 'auto_explain.log_min_duration', value: '100ms', comment: nil, record_type: :parsed)).to eq(
+ 'auto_explain.log_min_duration = 100ms',
+ )
+ end
+
+ it 'sets simple configuration even with comments' do
+ expect(provider.to_line(name: 'listen_addresses', value: '*', comment: 'dont mind me', record_type: :parsed)).to eq(
+ "listen_addresses = '*' # dont mind me",
+ )
+ end
+
+ it 'quotes includes' do
+ expect(provider.to_line(name: 'include', value: 'puppetextra', comment: nil, record_type: :parsed)).to eq(
+ "include 'puppetextra'",
+ )
+ end
+
+ it 'quotes multiple words' do
+ expect(provider.to_line(name: 'archive_command', value: 'rsync up', comment: nil, record_type: :parsed)).to eq(
+ "archive_command = 'rsync up'",
+ )
+ end
+
+ it 'does not quote numbers' do
+ expect(provider.to_line(name: 'wal_segments', value: '32', comment: nil, record_type: :parsed)).to eq(
+ 'wal_segments = 32',
+ )
+ end
+
+ it 'allows numbers' do
+ expect(provider.to_line(name: 'integer', value: 42, comment: nil, record_type: :parsed)).to eq(
+ 'integer = 42',
+ )
+ end
+
+ it 'allows floats' do
+ expect(provider.to_line(name: 'float', value: 2.71828182845, comment: nil, record_type: :parsed)).to eq(
+ 'float = 2.71828182845',
+ )
+ end
+
+ it 'quotes single string address' do
+ expect(provider.to_line(name: 'listen_addresses', value: '0.0.0.0', comment: nil, record_type: :parsed)).to eq(
+ "listen_addresses = '0.0.0.0'",
+ )
+ end
+
+ it 'quotes an array of addresses' do
+ expect(provider.to_line(name: 'listen_addresses', value: ['0.0.0.0', '127.0.0.1'], comment: nil, record_type: :parsed)).to eq(
+ "listen_addresses = '0.0.0.0, 127.0.0.1'",
+ )
+ end
+ end
+end
diff --git a/spec/unit/provider/postgresql_conf/ruby_spec.rb b/spec/unit/provider/postgresql_conf/ruby_spec.rb
deleted file mode 100644
index 11800b0fc7..0000000000
--- a/spec/unit/provider/postgresql_conf/ruby_spec.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-provider_class = Puppet::Type.type(:postgresql_conf).provider(:ruby)
-
-describe provider_class do
- let(:resource) { Puppet::Type.type(:postgresql_conf).new(name: 'foo', value: 'bar') }
- let(:provider) { resource.provider }
-
- before(:each) do
- allow(provider).to receive(:file_path).and_return('/tmp/foo')
- allow(provider).to receive(:read_file).and_return('foo = bar')
- allow(provider).to receive(:write_file).and_return(true)
- end
- # rubocop:enable RSpec/ReceiveMessages
-
- it 'has a method parse_config' do
- expect(provider).to respond_to(:parse_config)
- end
-
- it 'has a method delete_header' do
- expect(provider).to respond_to(:delete_header)
- end
-
- it 'has a method add_header' do
- expect(provider).to respond_to(:add_header)
- end
-
- it 'has a method exists?' do
- expect(provider).to respond_to(:exists?)
- end
-
- it 'has a method create' do
- expect(provider).to respond_to(:create)
- end
-
- it 'has a method destroy' do
- expect(provider).to respond_to(:destroy)
- end
-
- it 'has a method value' do
- expect(provider).to respond_to(:value)
- end
-
- it 'has a method value=' do
- expect(provider).to respond_to(:value=)
- end
-
- it 'has a method comment' do
- expect(provider).to respond_to(:comment)
- end
-
- it 'has a method comment=' do
- expect(provider).to respond_to(:comment=)
- end
-
- it 'is an instance of the Provider Ruby' do
- expect(provider).to be_an_instance_of Puppet::Type::Postgresql_conf::ProviderRuby
- end
-end
diff --git a/spec/unit/type/postgresql_conf_spec.rb b/spec/unit/type/postgresql_conf_spec.rb
index 9ce4269bfa..179c369740 100644
--- a/spec/unit/type/postgresql_conf_spec.rb
+++ b/spec/unit/type/postgresql_conf_spec.rb
@@ -24,13 +24,13 @@
end
describe 'when validating attributes' do
- [:name, :provider, :target].each do |param|
+ [:name, :provider].each do |param|
it "has a #{param} parameter" do
expect(described_class.attrtype(param)).to eq(:param)
end
end
- [:value, :comment].each do |property|
+ [:value, :target].each do |property|
it "has a #{property} property" do
expect(described_class.attrtype(property)).to eq(:property)
end