Current File : //usr/lib/python3/dist-packages/sos/report/plugins/charmed_postgresql.py |
# This file is part of the sos project: https://github.com/sosreport/sos
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# version 2 of the GNU General Public License.
#
# See the LICENSE file in the source distribution for further information.
import os
import yaml
from sos.report.plugins import Plugin, UbuntuPlugin
SNAP_COMMON_PATH = "/var/snap/charmed-postgresql/common"
SNAP_CURRENT_PATH = "/var/snap/charmed-postgresql/current"
PATHS = {
"POSTGRESQL_CONF": SNAP_COMMON_PATH + "/var/lib/postgresql",
"POSTGRESQL_LOGS": SNAP_COMMON_PATH + "/var/log/postgresql",
"PATRONI_CONF": SNAP_CURRENT_PATH + "/etc/patroni",
"PATRONI_LOGS": SNAP_COMMON_PATH + "/var/log/patroni",
"PGBACKREST_CONF": SNAP_CURRENT_PATH + "/etc/pgbackrest",
"PGBACKREST_LOGS": SNAP_COMMON_PATH + "/var/log/pgbackrest",
"PGBOUNCER_CONF": SNAP_CURRENT_PATH + "/etc/pgbouncer",
"PGBOUNCER_LOGS": SNAP_COMMON_PATH + "/var/log/pgbouncer",
}
PATRONI_CONFIG_FILE = f"{PATHS['PATRONI_CONF']}/patroni.yaml"
RUNAS = "snap_daemon"
PSQL = "charmed-postgresql.psql"
PATRONICTL = "charmed-postgresql.patronictl"
class CharmedPostgreSQL(Plugin, UbuntuPlugin):
short_desc = "Charmed PostgreSQL"
plugin_name = "charmed_postgresql"
packages = ('charmed-postgresql',)
@property
def patronictl_args(self) -> str:
return f"--config-file {PATRONI_CONFIG_FILE}"
@property
def psql_args(self) -> str:
return (f"-U {self.postgresql_username} "
f"-h {self.postgresql_host} "
f"-p {self.postgresql_port} "
r"-d postgres -P pager=off")
def setup(self):
# --- FILE EXCLUSIONS ---
# Keys and certificates
self.add_forbidden_path([
f"{PATHS['PATRONI_CONF']}/*.pem",
f"{PATHS['PGBOUNCER_CONF']}/*.pem",
])
# --- FILE INCLUSIONS ---
self.add_copy_spec([
f"{PATHS['POSTGRESQL_CONF']}/*.conf*",
f"{PATHS['POSTGRESQL_LOGS']}",
f"{PATHS['PATRONI_CONF']}/*.y*ml",
f"{PATHS['PATRONI_LOGS']}",
f"{PATHS['PGBACKREST_CONF']}",
f"{PATHS['PGBACKREST_LOGS']}",
f"{PATHS['PGBOUNCER_CONF']}",
f"{PATHS['PGBOUNCER_LOGS']}",
])
# --- SNAP LOGS ---
self.add_journal("snap.charmed-postgresql.*")
# --- SNAP INFO ---
self.add_cmd_output(
"snap info charmed-postgresql",
suggest_filename="snap-info",
)
# Read and parse patroni config, finish setup if there are errors
try:
with open(PATRONI_CONFIG_FILE, encoding='utf-8') as f:
patroni_config = yaml.safe_load(f)
self.patroni_cluster_name = patroni_config["scope"]
postgresql = patroni_config["postgresql"]
host_port = postgresql["connect_address"].split(":")
self.postgresql_host, self.postgresql_port = host_port
authentication = postgresql["authentication"]
superuser = authentication["superuser"]
self.postgresql_username = superuser["username"]
self.postgresql_password = superuser["password"]
except (OSError, yaml.YAMLError, TypeError,
ValueError, KeyError, AttributeError) as error:
self._log_warn("Can not run additional commands due to "
"an error on opening or parsing the config file "
f"{PATRONI_CONFIG_FILE}: "
f'{error}')
return
# --- TOPOLOGY ---
self.add_cmd_output(
(f"{PATRONICTL} {self.patronictl_args} "
f"topology {self.patroni_cluster_name}"),
suggest_filename="patroni-topology",
runas=RUNAS,
)
# --- HISTORY ---
self.add_cmd_output(
(f"{PATRONICTL} {self.patronictl_args} "
f"history {self.patroni_cluster_name}"),
suggest_filename="patroni-history",
runas=RUNAS,
)
# --- DCS CONFIGS ---
self.add_cmd_output(
(f"{PATRONICTL} {self.patronictl_args} "
f"show-config {self.patroni_cluster_name}"),
suggest_filename="patroni-dcs-config",
runas=RUNAS,
)
# ADD DB PASSWORD TO ENVIRONMENT
os.environ["PGPASSWORD"] = self.postgresql_password
# --- DATABASES ---
self.add_cmd_output(
(f"{PSQL} {self.psql_args} "
r"-c '\l+'"),
suggest_filename="postgresql-databases",
runas=RUNAS,
)
# --- USERS ---
self.add_cmd_output(
(f"{PSQL} {self.psql_args} "
r"-c '\duS+'"),
suggest_filename="postgresql-users",
runas=RUNAS,
)
# --- TABLES ---
self.add_cmd_output(
(f"{PSQL} {self.psql_args} "
r"-c '\dtS+'"),
suggest_filename="postgresql-tables",
runas=RUNAS,
)
def postproc(self):
# REMOVE DB PASSWORD FROM ENVIRONMENT
if "PGPASSWORD" in os.environ:
del os.environ["PGPASSWORD"]
# --- SCRUB PASSWORDS ---
# Match lines containing password: and
# followed by anything which may be enclosed with "
self.do_path_regex_sub(
f"{PATHS['PATRONI_CONF']}/*",
r'(password: )"?.*"?',
r'\1"*********"',
)
# https://pgbackrest.org/configuration.html#section-repository/option-repo-s3-key
# https://pgbackrest.org/configuration.html#section-repository/option-repo-s3-key-secret
self.do_path_regex_sub(
f"{PATHS['PGBACKREST_CONF']}/pgbackrest.conf",
r'(.*s3-key.*=).*',
r'\1*********',
)
# https://www.pgbouncer.org/config.html#authentication-file-format
self.do_path_regex_sub(
f"{PATHS['PGBOUNCER_CONF']}/pgbouncer/userlist.txt",
r'(".*" )".*"',
r'\1"*********"',
)