diff --git a/REFERENCE.md b/REFERENCE.md
index 9b5ffc7197..a9609ff4a5 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -1527,6 +1527,7 @@ 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,6 +1561,14 @@ 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.
@@ -4211,6 +4220,12 @@ 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`
@@ -4219,20 +4234,26 @@ The basic property that the resource should be in.
Default value: `present`
-##### `target`
-
-The path to postgresql.conf
-
##### `value`
+Valid values: `%r{^\S(.*\S)?$}`
+
The value to set for this parameter.
#### Parameters
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`
@@ -4240,13 +4261,19 @@ Valid values: `%r{^[\w.]+$}`
namevar
-The postgresql parameter name to manage.
+A unique title for the resource.
##### `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
deleted file mode 100644
index 8918769cab..0000000000
--- a/lib/puppet/provider/postgresql_conf/parsed.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# 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
new file mode 100644
index 0000000000..63b87478d1
--- /dev/null
+++ b/lib/puppet/provider/postgresql_conf/ruby.rb
@@ -0,0 +1,167 @@
+# 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 c014ac0fe8..432f5aa877 100644
--- a/lib/puppet/type/postgresql_conf.rb
+++ b/lib/puppet/type/postgresql_conf.rb
@@ -2,28 +2,40 @@
Puppet::Type.newtype(:postgresql_conf) do
@doc = 'This type allows puppet to manage postgresql.conf parameters.'
-
ensurable
newparam(:name) do
- desc 'The postgresql parameter name to manage.'
- isnamevar
+ desc 'A unique title for the resource.'
+ newvalues(%r{^[\w.]+$})
+ end
+ newparam(:key) do
+ desc 'The Postgresql parameter to manage.'
newvalues(%r{^[\w.]+$})
end
newproperty(:value) do
desc 'The value to set for this parameter.'
- end
+ newvalues(%r{^\S(.*\S)?$})
- 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
+ munge do |value|
+ if value.to_i.to_s == value
+ value.to_i
+ elsif value.to_f.to_s == value
+ value.to_f
else
- nil
+ value
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 65cd68315c..d17b844a18 100644
--- a/manifests/server/config_entry.pp
+++ b/manifests/server/config_entry.pp
@@ -4,12 +4,14 @@
# @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
+ 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,
) {
# Those are the variables that are marked as "(change requires restart)"
# on postgresql.conf. Items are ordered as on postgresql.conf.
@@ -85,8 +87,9 @@
postgresql_conf { $name:
ensure => $ensure,
target => $path,
- name => $key,
+ key => $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
deleted file mode 100644
index 7f6fdaef05..0000000000
--- a/spec/unit/provider/postgresql_conf/parsed_spec.rb
+++ /dev/null
@@ -1,151 +0,0 @@
-# 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
new file mode 100644
index 0000000000..11800b0fc7
--- /dev/null
+++ b/spec/unit/provider/postgresql_conf/ruby_spec.rb
@@ -0,0 +1,60 @@
+# 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 179c369740..9ce4269bfa 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].each do |param|
+ [:name, :provider, :target].each do |param|
it "has a #{param} parameter" do
expect(described_class.attrtype(param)).to eq(:param)
end
end
- [:value, :target].each do |property|
+ [:value, :comment].each do |property|
it "has a #{property} property" do
expect(described_class.attrtype(property)).to eq(:property)
end