1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
# 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.'
# The function parses the postgresql.conf and figures out which active settings exist in a config file and returns an array of hashes
#
def parse_config
# 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(resource[:target]).with_index(1) do |line, line_number|
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 PostgreSQL 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 [Array] lines of the parsed postgresql configuration file
def write_config(lines)
lines = delete_header(lines)
lines = add_header(lines)
File.write(resource[:target], lines.join)
end
# check, if resource exists in postgresql.conf file
def exists?
select = parse_config.select { |hash| hash[:key] == resource[:key] }
raise Puppet::Error, "found multiple config items of #{resource[:key]}, 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]}}
lines = File.readlines(resource[:target])
lines.delete_if do |entry|
entry.match?(entry_regex)
end
write_config(lines)
end
# create resource if it does not exists
def create
lines = File.readlines(resource[:target])
new_line = line(key: resource[:key], value: resource[:value], comment: resource[:comment])
lines.push(new_line)
write_config(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)
lines = File.readlines(resource[:target])
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(lines)
end
# setter - set comment of a resource
def comment=(_comment)
lines = File.readlines(resource[:target])
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(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
|