[stable-1] Various backports from community.postgres (#1789)

* postgresql modules: various backports from community.postgresql

* Add postgresql_set community/postgresql/pull/52 backport

* Fix

* Update plugins/modules/database/postgresql/postgresql_set.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update plugins/modules/database/postgresql/postgresql_set.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update changelogs/fragments/1-community-postgresql_backports.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Andrew Klychkov 2021-02-11 20:54:16 +03:00 committed by GitHub
parent 91acc44c34
commit 3a2e614071
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 296 additions and 32 deletions

View file

@ -926,7 +926,7 @@ class PgClusterInfo(object):
raw = raw.split()[1].split('.')
self.pg_info["version"] = dict(
major=int(raw[0]),
minor=int(raw[1]),
minor=int(raw[1].rstrip(',')),
)
def get_recovery_state(self):

View file

@ -116,7 +116,7 @@ class PgPing(object):
raw = raw.split()[1].split('.')
self.version = dict(
major=int(raw[0]),
minor=int(raw[1]),
minor=int(raw[1].rstrip(',')),
)

View file

@ -171,6 +171,27 @@ EXAMPLES = r'''
search_path:
- app1
- public
# If you use a variable in positional_args / named_args that can
# be undefined and you wish to set it as NULL, the constructions like
# "{{ my_var if (my_var is defined) else none | default(none) }}"
# will not work as expected substituting an empty string instead of NULL.
# If possible, we suggest to use Ansible's DEFAULT_JINJA2_NATIVE configuration
# (https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-jinja2-native).
# Enabling it fixes this problem. If you cannot enable it, the following workaround
# can be used.
# You should precheck such a value and define it as NULL when undefined.
# For example:
- name: When undefined, set to NULL
set_fact:
my_var: NULL
when: my_var is undefined
# Then:
- name: Insert a value using positional arguments
community.postgresql.postgresql_query:
query: INSERT INTO test_table (col1) VALUES (%s)
positional_args:
- '{{ my_var }}'
'''
RETURN = r'''
@ -222,6 +243,9 @@ rowcount:
sample: 5
'''
import datetime
import decimal
try:
from psycopg2 import ProgrammingError as Psycopg2ProgrammingError
from psycopg2.extras import DictCursor
@ -389,8 +413,20 @@ def main():
if cursor.rowcount > 0:
rowcount += cursor.rowcount
query_result = []
try:
query_result = [dict(row) for row in cursor.fetchall()]
for row in cursor.fetchall():
# Ansible engine does not support decimals.
# An explicit conversion is required on the module's side
row = dict(row)
for (key, val) in iteritems(row):
if isinstance(val, decimal.Decimal):
row[key] = float(val)
elif isinstance(val, datetime.timedelta):
row[key] = str(val)
query_result.append(row)
except Psycopg2ProgrammingError as e:
if to_native(e) == 'no results to fetch':

View file

@ -180,7 +180,7 @@ from ansible.module_utils._text import to_native
PG_REQ_VER = 90400
# To allow to set value like 1mb instead of 1MB, etc:
POSSIBLE_SIZE_UNITS = ("mb", "gb", "tb")
LOWERCASE_SIZE_UNITS = ("mb", "gb", "tb")
# ===========================================
# PostgreSQL module specific support methods.
@ -199,6 +199,11 @@ def param_get(cursor, module, name):
except Exception as e:
module.fail_json(msg="Unable to get %s value due to : %s" % (name, to_native(e)))
if not info:
module.fail_json(msg="No such parameter: %s. "
"Please check its spelling or presence in your PostgreSQL version "
"(https://www.postgresql.org/docs/current/runtime-config.html)" % name)
raw_val = info[0][1]
unit = info[0][2]
context = info[0][3]
@ -233,32 +238,55 @@ def pretty_to_bytes(pretty_val):
# if the value contains 'B', 'kB', 'MB', 'GB', 'TB'.
# Otherwise it returns the passed argument.
val_in_bytes = None
if 'kB' in pretty_val:
num_part = int(''.join(d for d in pretty_val if d.isdigit()))
val_in_bytes = num_part * 1024
elif 'MB' in pretty_val.upper():
num_part = int(''.join(d for d in pretty_val if d.isdigit()))
val_in_bytes = num_part * 1024 * 1024
elif 'GB' in pretty_val.upper():
num_part = int(''.join(d for d in pretty_val if d.isdigit()))
val_in_bytes = num_part * 1024 * 1024 * 1024
elif 'TB' in pretty_val.upper():
num_part = int(''.join(d for d in pretty_val if d.isdigit()))
val_in_bytes = num_part * 1024 * 1024 * 1024 * 1024
elif 'B' in pretty_val.upper():
num_part = int(''.join(d for d in pretty_val if d.isdigit()))
val_in_bytes = num_part
else:
# It's sometimes possible to have an empty values
if not pretty_val:
return pretty_val
return val_in_bytes
# If the first char is not a digit, it does not make sense
# to parse further, so just return a passed value
if not pretty_val[0].isdigit():
return pretty_val
# If the last char is not an alphabetical symbol, it means that
# it does not contain any suffixes, so no sense to parse further
if not pretty_val[-1].isalpha():
return pretty_val
# Extract digits
num_part = []
for c in pretty_val:
# When we reach the first non-digit element,
# e.g. in 1024kB, stop iterating
if not c.isdigit():
break
else:
num_part.append(c)
num_part = ''.join(num_part)
val_in_bytes = None
if len(pretty_val) >= 2:
if 'kB' in pretty_val[-2:]:
val_in_bytes = num_part * 1024
elif 'MB' in pretty_val[-2:]:
val_in_bytes = num_part * 1024 * 1024
elif 'GB' in pretty_val[-2:]:
val_in_bytes = num_part * 1024 * 1024 * 1024
elif 'TB' in pretty_val[-2:]:
val_in_bytes = num_part * 1024 * 1024 * 1024 * 1024
# For cases like "1B"
if not val_in_bytes and 'B' in pretty_val[-1]:
val_in_bytes = num_part
if val_in_bytes is not None:
return val_in_bytes
else:
return pretty_val
def param_set(cursor, module, name, value, context):
@ -308,11 +336,14 @@ def main():
# Check input for potentially dangerous elements:
check_input(module, name, value, session_role)
# Allow to pass values like 1mb instead of 1MB, etc:
if value:
for unit in POSSIBLE_SIZE_UNITS:
if value[:-2].isdigit() and unit in value[-2:]:
value = value.upper()
# Convert a value like 1mb (Postgres does not support) to 1MB, etc:
if len(value) > 2 and value[:-2].isdigit() and value[-2:] in LOWERCASE_SIZE_UNITS:
value = value.upper()
# Convert a value like 1b (Postgres does not support) to 1B:
elif len(value) > 1 and ('b' in value[-1] and value[:-1].isdigit()):
value = value.upper()
if value is not None and reset:
module.fail_json(msg="%s: value and reset params are mutually exclusive" % name)

View file

@ -107,6 +107,7 @@ options:
no_password_changes:
description:
- If C(yes), does not inspect the database for password changes.
If the user already exists, skips all password related checks.
Useful when C(pg_authid) is not accessible (such as in AWS RDS).
Otherwise, makes password changes as necessary.
default: no
@ -156,6 +157,10 @@ notes:
On the previous versions the whole hashed string is used as a password.
- 'Working with SCRAM-SHA-256-hashed passwords, be sure you use the I(environment:) variable
C(PGOPTIONS: "-c password_encryption=scram-sha-256") (see the provided example).'
- On some systems (such as AWS RDS), C(pg_authid) is not accessible, thus, the module cannot compare
the current and desired C(password). In this case, the module assumes that the passwords are
different and changes it reporting that the state has been changed.
To skip all password related checks for existing users, use I(no_password_changes=yes).
- Supports ``check_mode``.
seealso:
- module: community.general.postgresql_privs