diff --git a/HISTORY b/HISTORY
index 36f33f48..9f2897e7 100644
--- a/HISTORY
+++ b/HISTORY
@@ -12,6 +12,8 @@
repmgr: add replication configuration file ownership check to
"standby switchover" (Ian)
repmgr: consolidate replication connection code (Ian)
+ repmgr: check permissions for "pg_promote()" and fall back to pg_ctl
+ if necessary (Ian)
5.0 2019-10-15
general: add PostgreSQL 12 support (Ian)
diff --git a/dbutils.c b/dbutils.c
index 273dcadb..ded07784 100644
--- a/dbutils.c
+++ b/dbutils.c
@@ -1723,6 +1723,43 @@ get_timeline_history(PGconn *repl_conn, TimeLineID tli)
/* user/role information functions */
/* =============================== */
+
+bool
+can_execute_pg_promote(PGconn *conn)
+{
+ PQExpBufferData query;
+ PGresult *res;
+ bool has_pg_promote= false;
+
+ /* pg_promote() available from PostgreSQL 12 */
+ if(PQserverVersion(conn) < 120000)
+ return false;
+
+ initPQExpBuffer(&query);
+ appendPQExpBufferStr(&query,
+ " SELECT pg_catalog.has_function_privilege( "
+ " CURRENT_USER, "
+ " 'pg_catalog.pg_promote(bool,int)', "
+ " 'execute' "
+ " )");
+
+ res = PQexec(conn, query.data);
+
+ if (PQresultStatus(res) != PGRES_TUPLES_OK)
+ {
+ log_db_error(conn, query.data,
+ _("can_execute_pg_promote(): unable to query user function privilege"));
+ }
+ else
+ {
+ has_pg_promote = atobool(PQgetvalue(res, 0, 0));
+ }
+ termPQExpBuffer(&query);
+
+ return has_pg_promote;
+}
+
+
bool
connection_has_pg_settings(PGconn *conn)
{
diff --git a/dbutils.h b/dbutils.h
index ad1f86c6..4923252b 100644
--- a/dbutils.h
+++ b/dbutils.h
@@ -441,6 +441,7 @@ uint64 system_identifier(PGconn *conn);
TimeLineHistoryEntry *get_timeline_history(PGconn *repl_conn, TimeLineID tli);
/* user/role information functions */
+bool can_execute_pg_promote(PGconn *conn);
bool connection_has_pg_settings(PGconn *conn);
bool is_replication_role(PGconn *conn, char *rolname);
bool is_superuser_connection(PGconn *conn, t_connection_user *userinfo);
diff --git a/doc/appendix-release-notes.xml b/doc/appendix-release-notes.xml
index 0f557683..1eefcdeb 100644
--- a/doc/appendix-release-notes.xml
+++ b/doc/appendix-release-notes.xml
@@ -88,6 +88,16 @@
+
+
+ repmgr standby promote
+ will check if the repmgr user has permission to execute
+ pg_promote() and fall back to pg_ctl promote if
+ necessary.
+
+
+
+
Fix situation where replication connections were not created correctly, which
diff --git a/doc/repmgr-standby-promote.xml b/doc/repmgr-standby-promote.xml
index 72be9595..4a26a3a2 100644
--- a/doc/repmgr-standby-promote.xml
+++ b/doc/repmgr-standby-promote.xml
@@ -106,20 +106,22 @@
User permission requirementspg_promote() (PostgreSQL 12)
- From PostgreSQL 12, &repmgr; uses the pg_promote() function to promote a standby
- to primary.
+ From PostgreSQL 12, &repmgr; will attempt to use the pg_promote() function
+ to promote a standby to primary.
- By default, execution of pg_promote() is restricted to superusers.
- If the repmgr use is not a superuser, execution permission for this
- function must be granted with e.g.:
-
+ By default, execution of pg_promote() is restricted to superusers.
+ If the repmgr user does not have permission to execute
+ pg_promote(), &repmgr; will fall back to using pg_ctl promote.
+
+
+
+ If the repmgr user is not a superuser, execution permission for this
+ function can be granted with e.g.:
+
GRANT EXECUTE ON FUNCTION pg_catalog.pg_promote TO repmgr
-
-
- A future &repmgr; release will relax this restriction by falling back to
- pg_ctl promote, as used for pre-PostgreSQL 12 versions.
-
+
+
diff --git a/repmgr-action-standby.c b/repmgr-action-standby.c
index 000b9070..201ba433 100644
--- a/repmgr-action-standby.c
+++ b/repmgr-action-standby.c
@@ -2476,45 +2476,62 @@ _do_standby_promote_internal(PGconn *conn)
* option so we can't be sure when or if the promotion completes. For now
* we'll poll the server until the default timeout (60 seconds)
*
- * For PostgreSQL 12+, use the pg_promote() function - note this is
- * experimental
+ * For PostgreSQL 12+, use the pg_promote() function, unless one of
+ * "service_promote_command" or "use_pg_ctl_promote" is set.
*/
- log_notice(_("promoting standby to primary"));
-
- if (PQserverVersion(conn) >= 120000)
{
- log_detail(_("promoting server \"%s\" (ID: %i) using pg_promote()"),
- local_node_record.node_name,
- local_node_record.node_id);
+ bool use_pg_promote = false;
- /*
- * We'll check for promotion success ourselves, but will abort
- * if some unrecoverable error prevented the function from being
- * executed.
- */
- if (!promote_standby(conn, false, 0))
+
+ if (PQserverVersion(conn) >= 120000)
{
- log_error(_("unable to promote server from standby to primary"));
- exit(ERR_PROMOTION_FAIL);
+ use_pg_promote = true;
+
+ if (can_execute_pg_promote(conn) == false)
+ {
+ use_pg_promote = false;
+ log_info(_("user \"%s\" does not have permission to execute \"pg_promote()\", falling back to \"pg_ctl promote\""),
+ PQuser(conn));
+ }
}
- }
- else
- {
- char script[MAXLEN];
- int r;
- get_server_action(ACTION_PROMOTE, script, (char *) data_dir);
+ log_notice(_("promoting standby to primary"));
- log_detail(_("promoting server \"%s\" (ID: %i) using \"%s\""),
- local_node_record.node_name,
- local_node_record.node_id,
- script);
-
- r = system(script);
- if (r != 0)
+ if (use_pg_promote == true)
{
- log_error(_("unable to promote server from standby to primary"));
- exit(ERR_PROMOTION_FAIL);
+ log_detail(_("promoting server \"%s\" (ID: %i) using pg_promote()"),
+ local_node_record.node_name,
+ local_node_record.node_id);
+
+ /*
+ * We'll check for promotion success ourselves, but will abort
+ * if some unrecoverable error prevented the function from being
+ * executed.
+ */
+ if (!promote_standby(conn, false, 0))
+ {
+ log_error(_("unable to promote server from standby to primary"));
+ exit(ERR_PROMOTION_FAIL);
+ }
+ }
+ else
+ {
+ char script[MAXLEN];
+ int r;
+
+ get_server_action(ACTION_PROMOTE, script, (char *) data_dir);
+
+ log_detail(_("promoting server \"%s\" (ID: %i) using \"%s\""),
+ local_node_record.node_name,
+ local_node_record.node_id,
+ script);
+
+ r = system(script);
+ if (r != 0)
+ {
+ log_error(_("unable to promote server from standby to primary"));
+ exit(ERR_PROMOTION_FAIL);
+ }
}
}