278 lines
7.9 KiB
Bash
Executable File
278 lines
7.9 KiB
Bash
Executable File
#!/bin/bash
|
|
#
|
|
# api_check.sh: Check API compatibility between two pg_stat_monitor branches.
|
|
#
|
|
# Usage: api_check.sh [source_branch] target_branch
|
|
# OR
|
|
# api_check.sh clean
|
|
#
|
|
# source_branch is optional, if omitted the script will use the current git branch
|
|
# as the source.
|
|
#
|
|
# If invoked as api_check.sh clean, it will remove all generated objects that could
|
|
# be used for further inspection.
|
|
#
|
|
# The script works by compiling the pg_stat_monitor extension on both source and
|
|
# target branches, then it (re)creates the extension on PostgreSQL, dump the
|
|
# database objects (views, functions) and compare the results.
|
|
#
|
|
# All the relevant log is output on console (stdout/1).
|
|
#
|
|
# Following exit codes are used:
|
|
# 0 (SUCCESS) API between branches hasn't changed.
|
|
# 1 (ERROR) Script failed to execute some step.
|
|
# 10 (API CHANGED) Target branch has either removed or modified some database
|
|
# object that was exported by the source branch.
|
|
#
|
|
#
|
|
# REQUIRES: Following environment variables must be properly set.
|
|
# PATH Must include path to the pg_config relative to the PostgreSQL instance to be used.
|
|
# PGDATA Must point to the database cluster directory to be used.
|
|
|
|
function usage() {
|
|
echo "[*] Usage: $0 [source_branch/commit_id] target_branch/commit_id"
|
|
echo "[*] OR"
|
|
echo "[*] Usage: $0 clean"
|
|
echo
|
|
echo "$0 checks for API compatibility between the source_branch and"\
|
|
"target_branch."
|
|
echo
|
|
echo "If invoked with clean argument, the script just removes all generated"\
|
|
"files that could be used for further inspection."
|
|
exit 1
|
|
}
|
|
|
|
# Check number of command line arguments.
|
|
[ $# -eq 0 ] &&
|
|
{
|
|
usage
|
|
}
|
|
|
|
[ "$1" = "-h" -o "$1" = "--help"] &&
|
|
{
|
|
usage
|
|
}
|
|
|
|
if [ "$1" = "clean" ]; then
|
|
rm -vf api_pgsmview.*
|
|
rm -vf api_pgsmerrview.*
|
|
rm -vf api_histogramfn.*
|
|
rm -vf api_resetfn.*
|
|
rm -vf api_reseterrfn.*
|
|
exit 0
|
|
fi
|
|
|
|
# Get absolute directory path to the script.
|
|
# https://stackoverflow.com/questions/4774054/reliable-way-for-a-bash-script-to-get-the-full-path-to-itself
|
|
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
|
|
# Change working dir to the project directory to be able to run git commands.
|
|
cd "${SCRIPTPATH}"
|
|
|
|
# Save original git branch.
|
|
ORIG_BRANCH="`git rev-parse --abbrev-ref HEAD`"
|
|
|
|
if [ $# -gt 1 ]; then
|
|
# Source git branch.
|
|
SOURCE_BRANCH="$1"
|
|
# Target branch to compare API with.
|
|
TARGET_BRANCH="$2"
|
|
# Check if source branch actually exists.
|
|
if ! git rev-parse --verify "${SOURCE_BRANCH}"; then
|
|
echo "[*] ERROR: Source branch do not exist: ${SOURCE_BRANCH}"
|
|
exit 1
|
|
fi
|
|
else
|
|
# Current git branch.
|
|
SOURCE_BRANCH="`git rev-parse --abbrev-ref HEAD`"
|
|
# Target branch to compare API with.
|
|
TARGET_BRANCH="$1"
|
|
fi
|
|
|
|
# Check that target branch actually exists.
|
|
if ! git rev-parse --verify "${TARGET_BRANCH}"; then
|
|
echo "[*] ERROR: Target branch do not exist: ${TARGET_BRANCH}"
|
|
exit 1
|
|
fi
|
|
|
|
# Check for pg_config, POSIX compatible way.
|
|
if ! command -v pg_config > /dev/null 2>&1; then
|
|
echo "[*] ERROR: Could not locate pg_config, please adjust PATH"
|
|
exit 1
|
|
fi
|
|
|
|
PG_BINDIR=`pg_config --bindir`
|
|
PGCTL="${PG_BINDIR}/pg_ctl"
|
|
PSQL="${PG_BINDIR}/psql"
|
|
|
|
[ ! -x "${PGCTL}" ] &&
|
|
{
|
|
echo "[*] ERROR: pg_ctl not found/no +x permission: ${PGCTL}"
|
|
exit 1
|
|
}
|
|
|
|
[ ! -x "${PSQL}" ] &&
|
|
{
|
|
echo "[*] ERROR: psql not found/no +x permission: ${PSQL}"
|
|
exit 1
|
|
}
|
|
|
|
[ -z "$PGDATA" ] &&
|
|
{
|
|
echo "[*] ERROR: PGDATA environment must be set and point to your\
|
|
PostgreSQL database directory"
|
|
exit 1
|
|
}
|
|
|
|
[ ! -d "$PGDATA" ] &&
|
|
{
|
|
echo "[*] ERROR: PGDATA directory not found: $PGDATA"
|
|
exit 1
|
|
}
|
|
|
|
# Remove temporary files when script exits.
|
|
function cleanup() {
|
|
rm -f err
|
|
if [ -f pgconf.orig ]; then
|
|
cp -f pgconf.orig "${PGDATA}/postgresql.conf"
|
|
rm -f pgconf.orig
|
|
fi
|
|
rm -f pgsm.conf
|
|
}
|
|
|
|
trap cleanup EXIT
|
|
|
|
PG_VER="`cat ${PGDATA}/PG_VERSION 2> /dev/null`"
|
|
|
|
# Make a copy of original postgresql.conf.
|
|
cp -f "${PGDATA}"/postgresql.conf ./pgconf.orig
|
|
# Remove any shared_preload_libraries configuration.
|
|
grep -v shared_preload_libraries pgconf.orig > pgsm.conf
|
|
# Add pg_stat_monitor extension to the config.
|
|
echo "shared_preload_libraries = 'pg_stat_monitor'" >> pgsm.conf
|
|
# Overwrite postgresql configuration.
|
|
cp pgsm.conf "${PGDATA}/postgresql.conf"
|
|
|
|
function restart_postgres() {
|
|
echo "[*] Restarting PostgreSQL ${PG_VER}..."
|
|
cd "$PGDATA"
|
|
if ! $PGCTL -D "$PGDATA" restart 2> err > /dev/null; then
|
|
echo "[*] ERROR: Failed to restart PostgreSQL ${PG_VER}: `cat err`"
|
|
exit 1
|
|
fi
|
|
cd -
|
|
}
|
|
|
|
# Dump information about views, functions, or any object used as API.
|
|
# 1. Compile and install pg_stat_monitor.
|
|
# 2. Restart PostgreSQL.
|
|
# 3. Create the extension.
|
|
# 4. Dump pg_stat_monitor views, functions, etc...
|
|
function dump_pgsm_api() {
|
|
BRANCH="$1"
|
|
echo "[*] Compiling pg_stat_monitor ($BRANCH)"
|
|
make USE_PGXS=1 clean
|
|
if ! make USE_PGXS=1 install; then
|
|
echo "[*] Compilation failed, aborting..."
|
|
exit 1
|
|
fi
|
|
restart_postgres
|
|
"$PSQL" -c 'DROP EXTENSION IF EXISTS pg_stat_monitor;' 2> /dev/null
|
|
"$PSQL" -c 'CREATE EXTENSION pg_stat_monitor;' 2> /dev/null
|
|
"$PSQL" -c '\d pg_stat_monitor' 2> /dev/null > api_pgsmview."${BRANCH}"
|
|
"$PSQL" -c '\d pg_stat_monitor_errors' 2> /dev/null > api_pgsmerrview."${BRANCH}"
|
|
"$PSQL" -c '\sf histogram' 2> /dev/null > api_histogramfn."${BRANCH}"
|
|
"$PSQL" -c '\sf pg_stat_monitor_reset' 2> /dev/null > api_resetfn."${BRANCH}"
|
|
"$PSQL" -c '\sf pg_stat_monitor_reset_errors' 2> /dev/null > api_reseterrfn."${BRANCH}"
|
|
}
|
|
|
|
# Checkout source branch.
|
|
# If source branch was not given on command line, use current
|
|
# git branch (skip checkout).
|
|
TMP_SRC_BRANCH=""
|
|
if [ $# -gt 1 ]; then
|
|
RANDSTR=`echo $RANDOM | md5sum | head -c 20`
|
|
TMP_SRC_BRANCH="source_${RANDSTR}_${SOURCE_BRANCH}"
|
|
echo "[*] Checking out ${SOURCE_BRANCH} on temporary branch ${TMP_SRC_BRANCH}"
|
|
|
|
if ! git checkout -b "${TMP_SRC_BRANCH}" "${SOURCE_BRANCH}" 2> /dev/null; then
|
|
echo "[*] ERROR: Failed to checkout branch ${SOURCE_BRANCH}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
echo "[*] Dumping API (${SOURCE_BRANCH})..."
|
|
dump_pgsm_api "${SOURCE_BRANCH}"
|
|
make USE_PGXS=1 clean
|
|
|
|
RANDSTR=`echo $RANDOM | md5sum | head -c 20`
|
|
TMP_DST_BRANCH="target_${RANDSTR}_${TARGET_BRANCH}"1
|
|
echo "[*] Checking out ${TARGET_BRANCH} on temporary branch ${TMP_DST_BRANCH}"
|
|
|
|
if ! git checkout -b "${TMP_DST_BRANCH}" "${TARGET_BRANCH}" 2> /dev/null;
|
|
then
|
|
echo "[*] ERROR: Failed to checkout branch ${TARGET_BRANCH}"
|
|
exit 1
|
|
fi
|
|
|
|
# Remove temporary source branch (if created).
|
|
[ -z "${TMP_SRC_BRANCH}"] || git branch -D "${TMP_SRC_BRANCH}"
|
|
|
|
echo "[*] Dumping API (${TARGET_BRANCH})..."
|
|
dump_pgsm_api "${TARGET_BRANCH}"
|
|
make USE_PGXS=1 clean
|
|
|
|
# Restore original branch and remove temporary one.
|
|
git checkout "${ORIG_BRANCH}"
|
|
git branch -D "${TMP_DST_BRANCH}"
|
|
|
|
echo
|
|
echo "[*] ------ API comparison results ------"
|
|
echo "[*]"
|
|
|
|
# Input files
|
|
INPUT=(api_pgsmview api_pgsmerrview api_histogramfn api_resetfn api_reseterrfn)
|
|
# Real API object names.
|
|
API_OBJECTS=("pg_stat_monitor" "pg_stat_monitor_errors" "histogram" "pg_stat_monitor_reset" "pg_stat_monitor_reset_errors")
|
|
|
|
STATUS=0
|
|
i=0
|
|
while [ $i -lt ${#API_OBJECTS[@]} ]; do
|
|
echo "[*] ${API_OBJECTS[$i]}:"
|
|
|
|
# Compute input file sizes.
|
|
fs0=$(stat -c '%s' ${INPUT[$i]}."${SOURCE_BRANCH}")
|
|
fs1=$(stat -c '%s' ${INPUT[$i]}."${TARGET_BRANCH}")
|
|
|
|
if [ $fs0 -eq 0 ]; then
|
|
echo -e "\tNot present in branch ${SOURCE_BRANCH}"
|
|
if [ $fs1 -gt 0 ]; then
|
|
echo -e "\tObject added on target branch ${TARGET_BRANCH}"
|
|
fi
|
|
fi
|
|
|
|
if [ $fs1 -eq 0 ]; then
|
|
if [ $fs0 -gt 0 ]; then
|
|
# Object was present in original but removed on target branch.
|
|
echo -e "\tRemoved on branch ${TARGET_BRANCH}"
|
|
STATUS=10
|
|
else
|
|
echo -e "\tNot present in branch ${TARGET_BRANCH}"
|
|
fi
|
|
fi
|
|
|
|
if [ $fs0 -gt 0 -a $fs1 -gt 0 ]; then
|
|
if ! diff ${INPUT[$i]}."${SOURCE_BRANCH}" ${INPUT[$i]}."${TARGET_BRANCH}" > ${API_OBJECTS[$i]}.diff; then
|
|
echo -e "\tAPI is different, diff (${API_OBJECTS[$i]}.diff):"
|
|
cat ${API_OBJECTS[$i]}.diff
|
|
STATUS=10
|
|
else
|
|
echo -e "\tAPI is compatible (OK)"
|
|
fi
|
|
fi
|
|
|
|
echo "[*] -----------------------------"
|
|
|
|
i=`expr $i + 1`
|
|
done
|
|
|
|
exit $STATUS |