Skip to content

Commit

Permalink
New postgresql_conf provider, handles multiple configs
Browse files Browse the repository at this point in the history
update provider docs
  • Loading branch information
SimonHoenscheid committed Sep 27, 2023
1 parent f6ff7c2 commit 179472b
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 218 deletions.
37 changes: 32 additions & 5 deletions REFERENCE.md
Expand Up @@ -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)

##### <a name="-postgresql--server--config_entry--ensure"></a>`ensure`

Expand Down Expand Up @@ -1560,6 +1561,14 @@ Path for postgresql.conf

Default value: `$postgresql::server::postgresql_conf_path`

##### <a name="-postgresql--server--config_entry--comment"></a>`comment`

Data type: `Optional[String[1]]`

Defines the comment for the setting. The # is added by default.

Default value: `undef`

### <a name="postgresql--server--database"></a>`postgresql::server::database`

Define for creating a database.
Expand Down Expand Up @@ -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`
Expand All @@ -4219,34 +4234,46 @@ 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)

##### <a name="-postgresql_conf--key"></a>`key`

Valid values: `%r{^[\w.]+$}`

The Postgresql parameter to manage.

##### <a name="-postgresql_conf--name"></a>`name`

Valid values: `%r{^[\w.]+$}`

namevar

The postgresql parameter name to manage.
A unique title for the resource.

##### <a name="-postgresql_conf--provider"></a>`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.

##### <a name="-postgresql_conf--target"></a>`target`

Valid values: `%r{^/\S+[a-z0-9(/)-]*\w+.conf$}`

The path to the postgresql config file

### <a name="postgresql_conn_validator"></a>`postgresql_conn_validator`

Verify that a connection can be successfully established between a node
Expand Down
45 changes: 0 additions & 45 deletions lib/puppet/provider/postgresql_conf/parsed.rb

This file was deleted.

167 changes: 167 additions & 0 deletions 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'

This comment has been minimized.

Copy link
@smortex

smortex Nov 11, 2023

Collaborator

This seems to break the module on all non-linux OS:

root@agrajag /usr/home/romain # facter os
{
  architecture => "amd64",
  family => "FreeBSD",
  hardware => "amd64",
  name => "FreeBSD",
  release => {
    branch => "RC4",
    full => "14.0-RC4",
    major => "14",
    minor => "0"
  }
}
root@agrajag /usr/home/romain # puppet agent -t -E romain --tags Postgresql
[...]
Notice: /Stage[main]/Postgresql::Server::Service/Postgresql::Server::Instance::Service[main]/Anchor[postgresql::server::service::begin::main]: Dependency Postgresql_conf[port_for_instance_main] has failures: true
Notice: /Stage[main]/Postgresql::Server::Service/Postgresql::Server::Instance::Service[main]/Anchor[postgresql::server::service::begin::main]: Dependency Postgresql_conf[password_encryption_for_instance_main] has failures: true
Notice: /Stage[main]/Postgresql::Server::Service/Postgresql::Server::Instance::Service[main]/Anchor[postgresql::server::service::begin::main]: Dependency Postgresql_conf[data_directory_for_instance_main] has failures: true
[...]
Error: Could not find a suitable provider for postgresql_conf

# 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*(?<key>[\w.]+)\s*=?\s*(?<value>.*?)(?:\s*#\s*(?<comment>.*))?\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*(?<key>[\w.]+)\s*=?\s*(?<value>.*?)(?:\s*#\s*(?<comment>.*))?\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*(?<key>[\w.]+)\s*=?\s*(?<value>.*?)(?:\s*#\s*(?<comment>.*))?\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
32 changes: 22 additions & 10 deletions lib/puppet/type/postgresql_conf.rb
Expand Up @@ -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
13 changes: 8 additions & 5 deletions manifests/server/config_entry.pp
Expand Up @@ -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.
Expand Down Expand Up @@ -85,8 +87,9 @@
postgresql_conf { $name:
ensure => $ensure,
target => $path,
name => $key,
key => $key,
value => $value,
comment => $comment,
require => Class['postgresql::server::initdb'],
}
}

0 comments on commit 179472b

Please sign in to comment.