ci: recreate dev database in cleanup #no_deploy
All checks were successful
CI/CD Pipeline / Manual Action Help (push) Has been skipped
CI/CD Pipeline / Start Dev Containers in Dokploy (push) Has been skipped
CI/CD Pipeline / Drop and Recreate Dev Database (push) Has been skipped
CI/CD Pipeline / Quality Gate (push) Successful in 1m53s
CI/CD Pipeline / Build and Push Images (push) Successful in 3s
CI/CD Pipeline / Deploy Dev in Dokploy (push) Has been skipped
CI/CD Pipeline / Internal Notify (push) Successful in 1s

This commit is contained in:
2026-04-28 13:25:31 +02:00
parent 4aa552341f
commit b373341fcd

View File

@@ -22,7 +22,7 @@ on:
required: true required: true
default: "all" default: "all"
cleanup_confirm: cleanup_confirm:
description: "Type CLEAN_DEV_DB to recreate/reset the dev database as UTF8" description: "Type CLEAN_DEV_DB to drop and recreate the dev database as UTF8"
required: false required: false
default: "" default: ""
@@ -62,6 +62,7 @@ jobs:
echo "For dev DB cleanup run with:" echo "For dev DB cleanup run with:"
echo "- manual_action=cleanup_dev_database" echo "- manual_action=cleanup_dev_database"
echo "- cleanup_confirm=CLEAN_DEV_DB" echo "- cleanup_confirm=CLEAN_DEV_DB"
echo "This drops and recreates the dev database, then triggers Dokploy web/worker/beat."
echo "For Dokploy start run with manual_action=dokploy_start and dokploy_target=all|web|worker|beat." echo "For Dokploy start run with manual_action=dokploy_start and dokploy_target=all|web|worker|beat."
} >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}" } >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}"
@@ -343,11 +344,40 @@ jobs:
import json import json
import os import os
repository = os.environ.get("GITHUB_REPOSITORY", "")
repository_name = repository.rsplit("/", 1)[-1]
branch = os.environ.get("GITHUB_REF_NAME") or "dev"
sha = os.environ.get("GITHUB_SHA") or ""
server_url = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
repository_url = f"{server_url}/{repository}" if server_url and repository else ""
celery_image = f"{os.environ['REGISTRY_HOST']}/{os.environ['REGISTRY_NAMESPACE']}/{os.environ['CELERY_IMAGE']}:dev" celery_image = f"{os.environ['REGISTRY_HOST']}/{os.environ['REGISTRY_NAMESPACE']}/{os.environ['CELERY_IMAGE']}:dev"
payload = { payload = {
"project": os.environ.get("GITHUB_REPOSITORY"), "ref": f"refs/heads/{branch}",
"branch": os.environ.get("GITHUB_REF_NAME"), "after": sha,
"sha": os.environ.get("GITHUB_SHA"), "checkout_sha": sha,
"repository": {
"name": repository_name,
"full_name": repository,
"html_url": repository_url,
"clone_url": f"{repository_url}.git" if repository_url else "",
},
"sender": {"login": os.environ.get("GITHUB_ACTOR")},
"pusher": {"name": os.environ.get("GITHUB_ACTOR")},
"head_commit": {
"id": sha,
"message": f"CI deploy {os.environ.get('CURRENT_DOKPLOY_TARGET')}",
"url": f"{repository_url}/commit/{sha}" if repository_url and sha else "",
},
"commits": [
{
"id": sha,
"message": f"CI deploy {os.environ.get('CURRENT_DOKPLOY_TARGET')}",
"url": f"{repository_url}/commit/{sha}" if repository_url and sha else "",
}
],
"project": repository,
"branch": branch,
"sha": sha,
"actor": os.environ.get("GITHUB_ACTOR"), "actor": os.environ.get("GITHUB_ACTOR"),
"target": os.environ.get("CURRENT_DOKPLOY_TARGET"), "target": os.environ.get("CURRENT_DOKPLOY_TARGET"),
"images": { "images": {
@@ -361,16 +391,24 @@ jobs:
) )
echo "Trigger Dokploy for ${service_name}" echo "Trigger Dokploy for ${service_name}"
curl -fsS \ RESPONSE=$(curl -fsS \
--connect-timeout 5 \ --connect-timeout 5 \
--max-time 30 \ --max-time 30 \
--retry 2 \ --retry 2 \
--retry-delay 2 \ --retry-delay 2 \
-X POST \ -X POST \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "X-Gitea-Event: push" \
-H "X-Gogs-Event: push" \
-H "X-GitHub-Event: push" \
"${AUTH_HEADER[@]}" \ "${AUTH_HEADER[@]}" \
--data "${PAYLOAD}" \ --data "${PAYLOAD}" \
"${webhook_url}" "${webhook_url}")
printf '%s\n' "${RESPONSE}"
if printf '%s' "${RESPONSE}" | grep -qi "Branch Not Match"; then
echo "Dokploy rejected ${service_name}: branch did not match" >&2
exit 1
fi
} }
call_webhook "dev web" "${DOKPLOY_DEV_WEB_WEBHOOK_URL}" "web" call_webhook "dev web" "${DOKPLOY_DEV_WEB_WEBHOOK_URL}" "web"
@@ -429,11 +467,40 @@ jobs:
import json import json
import os import os
repository = os.environ.get("GITHUB_REPOSITORY", "")
repository_name = repository.rsplit("/", 1)[-1]
branch = os.environ.get("GITHUB_REF_NAME") or "dev"
sha = os.environ.get("GITHUB_SHA") or ""
server_url = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
repository_url = f"{server_url}/{repository}" if server_url and repository else ""
celery_image = f"{os.environ['REGISTRY_HOST']}/{os.environ['REGISTRY_NAMESPACE']}/{os.environ['CELERY_IMAGE']}:dev" celery_image = f"{os.environ['REGISTRY_HOST']}/{os.environ['REGISTRY_NAMESPACE']}/{os.environ['CELERY_IMAGE']}:dev"
payload = { payload = {
"project": os.environ.get("GITHUB_REPOSITORY"), "ref": f"refs/heads/{branch}",
"branch": os.environ.get("GITHUB_REF_NAME"), "after": sha,
"sha": os.environ.get("GITHUB_SHA"), "checkout_sha": sha,
"repository": {
"name": repository_name,
"full_name": repository,
"html_url": repository_url,
"clone_url": f"{repository_url}.git" if repository_url else "",
},
"sender": {"login": os.environ.get("GITHUB_ACTOR")},
"pusher": {"name": os.environ.get("GITHUB_ACTOR")},
"head_commit": {
"id": sha,
"message": f"CI deploy {os.environ.get('CURRENT_DOKPLOY_TARGET')}",
"url": f"{repository_url}/commit/{sha}" if repository_url and sha else "",
},
"commits": [
{
"id": sha,
"message": f"CI deploy {os.environ.get('CURRENT_DOKPLOY_TARGET')}",
"url": f"{repository_url}/commit/{sha}" if repository_url and sha else "",
}
],
"project": repository,
"branch": branch,
"sha": sha,
"actor": os.environ.get("GITHUB_ACTOR"), "actor": os.environ.get("GITHUB_ACTOR"),
"target": os.environ.get("CURRENT_DOKPLOY_TARGET"), "target": os.environ.get("CURRENT_DOKPLOY_TARGET"),
"images": { "images": {
@@ -447,16 +514,24 @@ jobs:
) )
echo "Trigger Dokploy for ${service_name}" echo "Trigger Dokploy for ${service_name}"
curl -fsS \ RESPONSE=$(curl -fsS \
--connect-timeout 5 \ --connect-timeout 5 \
--max-time 30 \ --max-time 30 \
--retry 2 \ --retry 2 \
--retry-delay 2 \ --retry-delay 2 \
-X POST \ -X POST \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-H "X-Gitea-Event: push" \
-H "X-Gogs-Event: push" \
-H "X-GitHub-Event: push" \
"${AUTH_HEADER[@]}" \ "${AUTH_HEADER[@]}" \
--data "${PAYLOAD}" \ --data "${PAYLOAD}" \
"${webhook_url}" "${webhook_url}")
printf '%s\n' "${RESPONSE}"
if printf '%s' "${RESPONSE}" | grep -qi "Branch Not Match"; then
echo "Dokploy rejected ${service_name}: branch did not match" >&2
exit 1
fi
} }
triggered=0 triggered=0
@@ -491,9 +566,9 @@ jobs:
} >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}" } >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}"
cleanup_dev_database: cleanup_dev_database:
name: Cleanup Dev Database name: Drop and Recreate Dev Database
runs-on: ubuntu-latest runs-on: ubuntu-latest
timeout-minutes: 10 timeout-minutes: 15
if: | if: |
github.event_name == 'workflow_dispatch' && github.event_name == 'workflow_dispatch' &&
github.ref == 'refs/heads/dev' && github.ref == 'refs/heads/dev' &&
@@ -528,28 +603,12 @@ jobs:
"${APT_RUNNER[@]}" apt-get update "${APT_RUNNER[@]}" apt-get update
"${APT_RUNNER[@]}" apt-get install -y postgresql-client "${APT_RUNNER[@]}" apt-get install -y postgresql-client
- name: Recreate UTF8 database or reset public schema - name: Drop and recreate UTF8 database
run: | run: |
set -euo pipefail set -euo pipefail
export PGPASSWORD="${POSTGRES_PASSWORD}" export PGPASSWORD="${POSTGRES_PASSWORD}"
terminate_connections() { DB_EXISTS=$(psql \
psql \
--set ON_ERROR_STOP=1 \
--host="${POSTGRES_HOST}" \
--port="${POSTGRES_PORT}" \
--username="${POSTGRES_USER}" \
--dbname=postgres \
--set=dbname="${POSTGRES_DB}" \
<<'SQL'
SELECT pg_terminate_backend(pid)
FROM pg_stat_activity
WHERE datname = :'dbname'
AND pid <> pg_backend_pid();
SQL
}
ENCODING=$(psql \
--set ON_ERROR_STOP=1 \ --set ON_ERROR_STOP=1 \
--host="${POSTGRES_HOST}" \ --host="${POSTGRES_HOST}" \
--port="${POSTGRES_PORT}" \ --port="${POSTGRES_PORT}" \
@@ -558,11 +617,13 @@ jobs:
--tuples-only \ --tuples-only \
--no-align \ --no-align \
--set=dbname="${POSTGRES_DB}" \ --set=dbname="${POSTGRES_DB}" \
--command="SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = :'dbname';") <<'SQL'
SELECT 1 FROM pg_database WHERE datname = :'dbname';
SQL
)
if [ "${ENCODING:-}" != "UTF8" ]; then if [ "${DB_EXISTS:-}" = "1" ]; then
echo "Database ${POSTGRES_DB} encoding is ${ENCODING:-missing}; recreating as UTF8" echo "Closing active connections to ${POSTGRES_DB}"
terminate_connections
psql \ psql \
--set ON_ERROR_STOP=1 \ --set ON_ERROR_STOP=1 \
--host="${POSTGRES_HOST}" \ --host="${POSTGRES_HOST}" \
@@ -570,30 +631,30 @@ jobs:
--username="${POSTGRES_USER}" \ --username="${POSTGRES_USER}" \
--dbname=postgres \ --dbname=postgres \
--set=dbname="${POSTGRES_DB}" \ --set=dbname="${POSTGRES_DB}" \
--set=dbuser="${POSTGRES_USER}" \
<<'SQL' <<'SQL'
DROP DATABASE IF EXISTS :"dbname"; ALTER DATABASE :"dbname" WITH ALLOW_CONNECTIONS false;
CREATE DATABASE :"dbname" WITH OWNER :"dbuser" TEMPLATE template0 ENCODING 'UTF8'; SELECT pg_terminate_backend(pid)
SQL FROM pg_stat_activity
else WHERE datname = :'dbname'
echo "Database ${POSTGRES_DB} is already UTF8; resetting public schema" AND pid <> pg_backend_pid();
terminate_connections
psql \
--set ON_ERROR_STOP=1 \
--host="${POSTGRES_HOST}" \
--port="${POSTGRES_PORT}" \
--username="${POSTGRES_USER}" \
--dbname="${POSTGRES_DB}" \
--set=dbuser="${POSTGRES_USER}" \
<<'SQL'
DROP SCHEMA IF EXISTS public CASCADE;
CREATE SCHEMA public AUTHORIZATION :"dbuser";
GRANT ALL ON SCHEMA public TO :"dbuser";
GRANT ALL ON SCHEMA public TO public;
SQL SQL
fi fi
echo "Dropping and recreating ${POSTGRES_DB} with UTF8 encoding"
psql \ psql \
--set ON_ERROR_STOP=1 \
--host="${POSTGRES_HOST}" \
--port="${POSTGRES_PORT}" \
--username="${POSTGRES_USER}" \
--dbname=postgres \
--set=dbname="${POSTGRES_DB}" \
--set=dbuser="${POSTGRES_USER}" \
<<'SQL'
DROP DATABASE IF EXISTS :"dbname";
CREATE DATABASE :"dbname" WITH OWNER :"dbuser" TEMPLATE template0 ENCODING 'UTF8';
SQL
DB_ENCODING=$(psql \
--set ON_ERROR_STOP=1 \ --set ON_ERROR_STOP=1 \
--host="${POSTGRES_HOST}" \ --host="${POSTGRES_HOST}" \
--port="${POSTGRES_PORT}" \ --port="${POSTGRES_PORT}" \
@@ -602,19 +663,118 @@ jobs:
--tuples-only \ --tuples-only \
--no-align \ --no-align \
--set=dbname="${POSTGRES_DB}" \ --set=dbname="${POSTGRES_DB}" \
--command="SELECT pg_encoding_to_char(encoding) FROM pg_database WHERE datname = :'dbname';" \ <<'SQL'
| tee /tmp/mostovik-db-encoding SELECT pg_encoding_to_char(encoding)
FROM pg_database
WHERE datname = :'dbname';
SQL
)
if [ "$(cat /tmp/mostovik-db-encoding)" != "UTF8" ]; then printf '%s\n' "${DB_ENCODING}" | tee /tmp/mostovik-db-encoding
if [ "${DB_ENCODING}" != "UTF8" ]; then
echo "Database ${POSTGRES_DB} is not UTF8 after cleanup" >&2 echo "Database ${POSTGRES_DB} is not UTF8 after cleanup" >&2
exit 1 exit 1
fi fi
- name: Trigger Dokploy after database recreate
env:
DOKPLOY_API_TOKEN: ${{ secrets.DOKPLOY_API_TOKEN }}
run: |
set -euo pipefail
call_webhook() {
service_name="$1"
webhook_url="$2"
target="$3"
AUTH_HEADER=()
if [ -n "${DOKPLOY_API_TOKEN:-}" ]; then
AUTH_HEADER=(-H "Authorization: Bearer ${DOKPLOY_API_TOKEN}")
fi
PAYLOAD=$(CURRENT_DOKPLOY_TARGET="${target}" python3 - <<'PY'
import json
import os
repository = os.environ.get("GITHUB_REPOSITORY", "")
repository_name = repository.rsplit("/", 1)[-1]
branch = os.environ.get("GITHUB_REF_NAME") or "dev"
sha = os.environ.get("GITHUB_SHA") or ""
server_url = os.environ.get("GITHUB_SERVER_URL", "").rstrip("/")
repository_url = f"{server_url}/{repository}" if server_url and repository else ""
celery_image = f"{os.environ['REGISTRY_HOST']}/{os.environ['REGISTRY_NAMESPACE']}/{os.environ['CELERY_IMAGE']}:dev"
payload = {
"ref": f"refs/heads/{branch}",
"after": sha,
"checkout_sha": sha,
"repository": {
"name": repository_name,
"full_name": repository,
"html_url": repository_url,
"clone_url": f"{repository_url}.git" if repository_url else "",
},
"sender": {"login": os.environ.get("GITHUB_ACTOR")},
"pusher": {"name": os.environ.get("GITHUB_ACTOR")},
"head_commit": {
"id": sha,
"message": f"CI deploy {os.environ.get('CURRENT_DOKPLOY_TARGET')}",
"url": f"{repository_url}/commit/{sha}" if repository_url and sha else "",
},
"commits": [
{
"id": sha,
"message": f"CI deploy {os.environ.get('CURRENT_DOKPLOY_TARGET')}",
"url": f"{repository_url}/commit/{sha}" if repository_url and sha else "",
}
],
"project": repository,
"branch": branch,
"sha": sha,
"actor": os.environ.get("GITHUB_ACTOR"),
"target": os.environ.get("CURRENT_DOKPLOY_TARGET"),
"images": {
"web": f"{os.environ['REGISTRY_HOST']}/{os.environ['REGISTRY_NAMESPACE']}/{os.environ['WEB_IMAGE']}:dev",
"worker": celery_image,
"beat": celery_image,
},
}
print(json.dumps(payload, ensure_ascii=True, separators=(",", ":")))
PY
)
echo "Trigger Dokploy for ${service_name}"
RESPONSE=$(curl -fsS \
--connect-timeout 5 \
--max-time 30 \
--retry 2 \
--retry-delay 2 \
-X POST \
-H "Content-Type: application/json" \
-H "X-Gitea-Event: push" \
-H "X-Gogs-Event: push" \
-H "X-GitHub-Event: push" \
"${AUTH_HEADER[@]}" \
--data "${PAYLOAD}" \
"${webhook_url}")
printf '%s\n' "${RESPONSE}"
if printf '%s' "${RESPONSE}" | grep -qi "Branch Not Match"; then
echo "Dokploy rejected ${service_name}: branch did not match" >&2
exit 1
fi
}
call_webhook "dev web" "${DOKPLOY_DEV_WEB_WEBHOOK_URL}" "web"
sleep 45
call_webhook "dev worker" "${DOKPLOY_DEV_WORKER_WEBHOOK_URL}" "worker"
call_webhook "dev beat" "${DOKPLOY_DEV_BEAT_WEBHOOK_URL}" "beat"
- name: Summary - name: Summary
run: | run: |
set -euo pipefail set -euo pipefail
{ {
echo "Dev database cleanup completed." echo "Dev database was dropped and recreated."
echo "Database: ${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}" echo "Database: ${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
echo "Encoding: UTF8" echo "Encoding: UTF8"
echo "Dokploy web/worker/beat deploy was triggered."
} >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}" } >> "${GITHUB_STEP_SUMMARY:-/dev/stdout}"