Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single container with multiple databases #99

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,23 @@ postgres:backup-unschedule <name> Unschedules the backup of the postgres service
postgres:clone <name> <new-name> Create container <new-name> then copy data from <name> into <new-name>
postgres:connect <name> Connect via psql to a postgres service
postgres:create <name> Create a postgres service with environment variables
postgres:create-database <name> <db> Create a postgres database in the specified service
postgres:destroy <name> Delete the service and stop its container if there are no links left
postgres:destroy-database <name> <db> Delete a postgres database in the specified service
postgres:enter <name> [command] Enter or run a command in a running postgres service container
postgres:export <name> > <file> Export a dump of the postgres service database
postgres:expose <name> [port] Expose a postgres service on custom port if provided (random port otherwise)
postgres:import <name> < <file> Import a dump into the postgres service database
postgres:info <name> Print the connection information
postgres:link <name> <app> Link the postgres service to the app
postgres:link <name> <app> [--user user] [--database database] Link the postgres service to the app
postgres:list List all postgres services
postgres:logs <name> [-t] Print the most recent log(s) for this service
postgres:promote <name> <app> Promote service <name> as DATABASE_URL in <app>
postgres:restart <name> Graceful shutdown and restart of the postgres service container
postgres:start <name> Start a previously stopped postgres service
postgres:stop <name> Stop a running postgres service
postgres:unexpose <name> Unexpose a previously exposed postgres service
postgres:unlink <name> <app> Unlink the postgres service from the app
postgres:unlink <name> <app> [--user user] [--database database] Unlink the postgres service from the app
```

## usage
Expand Down
14 changes: 12 additions & 2 deletions commands
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,18 @@ case "$1" in
"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/subcommands/create" "$@"
;;

$PLUGIN_COMMAND_PREFIX:create-database)
"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/subcommands/create-database" "$@"
;;

$PLUGIN_COMMAND_PREFIX:destroy)
"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/subcommands/destroy" "$@"
;;

$PLUGIN_COMMAND_PREFIX:destroy-database)
"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/subcommands/destroy-database" "$@"
;;

$PLUGIN_COMMAND_PREFIX:export)
"$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/subcommands/export" "$@"
;;
Expand Down Expand Up @@ -112,20 +120,22 @@ case "$1" in
$PLUGIN_COMMAND_PREFIX:clone <name> <new-name>, Create container <new-name> then copy data from <name> into <new-name>
$PLUGIN_COMMAND_PREFIX:connect <name>, Connect via psql to a $PLUGIN_SERVICE service
$PLUGIN_COMMAND_PREFIX:create <name>, Create a $PLUGIN_SERVICE service
$PLUGIN_COMMAND_PREFIX:create-database <name> <db>, Create a $PLUGIN_SERVICE database in the specified service
$PLUGIN_COMMAND_PREFIX:destroy <name>, Delete the $PLUGIN_SERVICE service and stop its container if there are no links left
$PLUGIN_COMMAND_PREFIX:destroy-database <name> <db>, Delete a $PLUGIN_SERVICE database in the specified service
$PLUGIN_COMMAND_PREFIX:export <name>, Export a dump of the $PLUGIN_SERVICE service database
$PLUGIN_COMMAND_PREFIX:expose <name> [port], Expose a $PLUGIN_SERVICE service on custom port if provided (random port otherwise)
$PLUGIN_COMMAND_PREFIX:import <name> < <file>, Import a dump into the $PLUGIN_SERVICE service database
$PLUGIN_COMMAND_PREFIX:info <name>, Print the connection information
$PLUGIN_COMMAND_PREFIX:link <name> <app>, Link the $PLUGIN_SERVICE service to the app
$PLUGIN_COMMAND_PREFIX:link <name> <app> [--user user] [--database database], Link the $PLUGIN_SERVICE service to the app
$PLUGIN_COMMAND_PREFIX:list, List all $PLUGIN_SERVICE services
$PLUGIN_COMMAND_PREFIX:logs <name> [-t], Print the most recent log(s) for this service
$PLUGIN_COMMAND_PREFIX:promote <name> <app>, Promote service <name> as ${PLUGIN_DEFAULT_ALIAS}_URL in <app>
$PLUGIN_COMMAND_PREFIX:restart <name>, Graceful shutdown and restart of the $PLUGIN_SERVICE service container
$PLUGIN_COMMAND_PREFIX:start <name>, Start a previously stopped $PLUGIN_SERVICE service
$PLUGIN_COMMAND_PREFIX:stop <name>, Stop a running $PLUGIN_SERVICE service
$PLUGIN_COMMAND_PREFIX:unexpose <name>, Unexpose a previously exposed $PLUGIN_SERVICE service
$PLUGIN_COMMAND_PREFIX:unlink <name> <app>, Unlink the $PLUGIN_SERVICE service from the app
$PLUGIN_COMMAND_PREFIX:unlink <name> <app> [--user user] [--database database], Unlink the $PLUGIN_SERVICE service from the app
help_content
}

Expand Down
77 changes: 71 additions & 6 deletions common-functions
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,34 @@ service_exposed_ports() {
done
}

service_databases() {
declare desc="Lists databases for a service"
declare SERVICE="$1"
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
local DATABASE_DIR="$SERVICE_ROOT/databases"
[[ ! -d $DATABASE_DIR ]] && echo '-' && return 0
for DATABASE in $DATABASE_DIR/*; do
echo -n "$(basename "$DATABASE") "
done
}

service_users() {
declare desc="Lists users for a service"
declare SERVICE="$1"
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
local USER_DIR="$SERVICE_ROOT/auth"
[[ ! -d $USER_DIR ]] && echo '-' && return 0
for USER in $USER_DIR/*; do
echo -n "$(basename "$USER") "
done
}

service_info() {
declare desc="Retrieves information about a given service"
declare SERVICE="$1" INFO_FLAG="$2"
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
local SERVICE_URL=$(service_url "$SERVICE")
local PASSWORD=$(cat "$SERVICE_ROOT/auth/postgres")
local SERVICE_URL=$(service_url "$SERVICE" postgres "$PASSWORD" "$SERVICE")
local PORT_FILE="$SERVICE_ROOT/PORT"
local SERVICE_CONTAINER_ID="$(cat "$SERVICE_ROOT/ID")"
local flag key valid_flags
Expand Down Expand Up @@ -235,9 +258,13 @@ service_link() {
declare desc="Links a service to an application"
declare SERVICE="$1" APP="$2"
update_plugin_scheme_for_app "$APP"
local SERVICE_URL=$(service_url "$SERVICE")
local SERVICE_NAME="$(get_service_name "$SERVICE")"
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
check_auth_migration "$SERVICE"
local USER=${3:-postgres}
local DATABASE=${4:-$(get_database_name "$SERVICE")}
local PASSWORD=$(cat "$SERVICE_ROOT/auth/$USER")
local SERVICE_URL=$(service_url "$SERVICE" "$USER" "$PASSWORD" "$DATABASE")
local SERVICE_NAME="$(get_service_name "$SERVICE")"
local EXISTING_CONFIG=$(config_all "$APP")
local LINK=$(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1) || true
local DEFAULT_ALIAS=$(echo "$EXISTING_CONFIG" | grep "${PLUGIN_DEFAULT_ALIAS}_URL") || true
Expand Down Expand Up @@ -282,9 +309,9 @@ service_list() {
if [[ -z $SERVICES ]]; then
dokku_log_warn "There are no $PLUGIN_SERVICE services"
else
LIST="NAME,VERSION,STATUS,EXPOSED PORTS,LINKS\n"
LIST="NAME,VERSION,STATUS,EXPOSED PORTS,DATABASES,USERS,LINKS\n"
for SERVICE in $SERVICES; do
LIST+="$SERVICE,$(service_version "$SERVICE"),$(service_status "$SERVICE"),$(service_exposed_ports "$SERVICE"),$(service_linked_apps "$SERVICE")\n"
LIST+="$SERVICE,$(service_version "$SERVICE"),$(service_status "$SERVICE"),$(service_exposed_ports "$SERVICE"),$(service_databases "$SERVICE"),$(service_users "$SERVICE"),$(service_linked_apps "$SERVICE")\n"
done
printf "%b" "$LIST" | column -t -s,
fi
Expand Down Expand Up @@ -447,8 +474,12 @@ service_stop() {
service_unlink() {
declare desc="Unlinks an application from a service"
declare SERVICE="$1" APP="$2"
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
local USER=${3:-postgres}
local PASSWORD=$(cat "$SERVICE_ROOT/auth/$USER")
local DATABASE=${4:-$(get_database_name "$SERVICE")}
update_plugin_scheme_for_app "$APP"
local SERVICE_URL=$(service_url "$SERVICE")
local SERVICE_URL=$(service_url "$SERVICE" "$USER" "$PASSWORD" "$DATABASE")
local SERVICE_NAME="$(get_service_name "$SERVICE")"
local EXISTING_CONFIG=$(config_all "$APP")
local SERVICE_ALIAS=$(service_alias "$SERVICE")
Expand Down Expand Up @@ -490,3 +521,37 @@ verify_service_name() {
[[ ! -d "$PLUGIN_DATA_ROOT/$SERVICE" ]] && dokku_log_fail "$PLUGIN_SERVICE service $SERVICE does not exist"
return 0
}

verify_user_name() {
declare desc="Verifies that a user exists"
declare SERVICE="$1" USER="$2"
[[ ! -n "$SERVICE" ]] && dokku_log_fail "(verify_user_name) SERVICE must not be null"
[[ ! -n "$USER" ]] && dokku_log_fail "(verify_user_name) SERVICE must not be null"
[[ ! -f "$PLUGIN_DATA_ROOT/$SERVICE/auth/$USER" ]] && dokku_log_fail "$PLUGIN_SERVICE user $USER for service $SERVICE does not exist"
return 0
}

verify_database_name() {
declare desc="Verifies that a database exists"
declare SERVICE="$1"
declare DATABASE="$2"
[[ ! -n "$SERVICE" ]] && dokku_log_fail "(verify_service_name) SERVICE must not be null"
[[ ! -n "$DATABASE" ]] && dokku_log_fail "(verify_service_name) DATABASE must not be null"
[[ ! -f "$PLUGIN_DATA_ROOT/$SERVICE/databases/$DATABASE" ]] && dokku_log_fail "$PLUGIN_SERVICE database $DATABASE for service $SERVICE does not exist"
return 0
}

check_auth_migration() {
declare desc="Check whether root credentials need to be migrated into multi-user format and do so if needed"
declare SERVICE="$1"
[[ ! -n "$SERVICE" ]] && dokku_log_fail "(verify_service_name) SERVICE must not be null"
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
if [ -f "$SERVICE_ROOT/PASSWORD" ]; then
dokku_log_verbose_quiet "Migrating root user to multi-user format"
local AUTH_DIR="$SERVICE_ROOT/auth"
mkdir -p "$AUTH_DIR"
chmod 750 "$AUTH_DIR"
mv "$SERVICE_ROOT/PASSWORD" "$AUTH_DIR/postgres"
touch "$SERVICE_ROOT/databases/$SERVICE"
fi
}
51 changes: 42 additions & 9 deletions functions
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ service_create() {
mkdir -p "$SERVICE_ROOT" || dokku_log_fail "Unable to create service directory"
mkdir -p "$SERVICE_ROOT/data" || dokku_log_fail "Unable to create service data directory"
mkdir -p "$SERVICE_ROOT/config" || dokku_log_fail "Unable to create service config directory"
mkdir -p "$SERVICE_ROOT/databases" || dokku_log_fail "Unable to create service database directory"
mkdir -p "$SERVICE_ROOT/auth" || dokku_log_fail "Unable to create service auth directory"
chmod 750 "$SERVICE_ROOT/auth"
touch "$LINKS_FILE"
password=$(openssl rand -hex 16)
echo "$password" > "$SERVICE_ROOT/PASSWORD"
chmod 640 "$SERVICE_ROOT/PASSWORD"
echo "$password" > "$SERVICE_ROOT/auth/postgres"
chmod 640 "$SERVICE_ROOT/auth/postgres"

if [[ -n $POSTGRES_CUSTOM_ENV ]]; then
echo "$POSTGRES_CUSTOM_ENV" | tr ';' "\n" > "$SERVICE_ROOT/ENV"
Expand All @@ -35,11 +38,40 @@ service_create() {
service_create_container "$SERVICE"
}

database_create() {
local SERVICE="$1"
local NAME="$2"
[[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a name for the service"
[[ -z "$NAME" ]] && dokku_log_fail "Please specify a name for the database"

check_auth_migration "$SERVICE"

local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
local SERVICE_NAME="$(get_service_name "$SERVICE")"

dokku_log_verbose_quiet "Creating user"
password=$(openssl rand -hex 16)
if docker exec "$SERVICE_NAME" su - postgres -c "psql -c \"CREATE USER \\\"$NAME\\\" WITH PASSWORD '$password';\"" > /dev/null 2>&1; then
echo "$password" > "$SERVICE_ROOT/auth/$NAME"
chmod 640 "$SERVICE_ROOT/auth/$NAME"
else
echo 'Already exists'
fi

dokku_log_verbose_quiet "Creating database"
if docker exec "$SERVICE_NAME" su - postgres -c "createdb -E utf8 $NAME" 2> /dev/null; then
touch "$SERVICE_ROOT/databases/$NAME"
else
echo 'Already exists'
fi
docker exec "$SERVICE_NAME" su - postgres -c "psql -c \"GRANT ALL PRIVILEGES ON DATABASE \\\"$NAME\\\" TO \\\"$NAME\\\";\"" > /dev/null
}

service_create_container() {
local SERVICE="$1"
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
local SERVICE_NAME="$(get_service_name "$SERVICE")"
local PASSWORD="$(cat "$SERVICE_ROOT/PASSWORD")"
local PASSWORD="$(cat "$SERVICE_ROOT/auth/postgres")"
local PREVIOUS_ID

ID=$(docker run --name "$SERVICE_NAME" -v "$SERVICE_ROOT/data:/var/lib/postgresql/data" -e "POSTGRES_PASSWORD=$PASSWORD" --env-file="$SERVICE_ROOT/ENV" -d --restart always --label dokku=service --label dokku.service=postgres "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION")
Expand Down Expand Up @@ -69,7 +101,7 @@ service_export() {
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
local SERVICE_NAME="$(get_service_name "$SERVICE")"
local DATABASE_NAME="$(get_database_name "$SERVICE")"
local PASSWORD="$(cat "$SERVICE_ROOT/PASSWORD")"
local PASSWORD="$(cat "$SERVICE_ROOT/auth/postgres")"

[[ -n $SSH_TTY ]] && stty -opost
docker exec "$SERVICE_NAME" env PGPASSWORD="$PASSWORD" pg_dump -Fc --no-acl --no-owner -h localhost -U postgres -w "$DATABASE_NAME"
Expand All @@ -83,7 +115,7 @@ service_import() {
SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
SERVICE_NAME="$(get_service_name "$SERVICE")"
DATABASE_NAME="$(get_database_name "$SERVICE")"
PASSWORD="$(cat "$SERVICE_ROOT/PASSWORD")"
PASSWORD="$(cat "$SERVICE_ROOT/auth/postgres")"

if [[ -t 0 ]]; then
dokku_log_fail "No data provided on stdin."
Expand All @@ -105,7 +137,7 @@ service_start() {
dokku_log_info1_quiet "Starting container"
local PREVIOUS_ID=$(docker ps -f status=exited | grep -e "$SERVICE_NAME$" | awk '{print $1}') || true
local IMAGE_EXISTS=$(docker images | grep -e "^$PLUGIN_IMAGE " | grep -q " $PLUGIN_IMAGE_VERSION " && true)
local PASSWORD="$(cat "$SERVICE_ROOT/PASSWORD")"
local PASSWORD="$(cat "$SERVICE_ROOT/auth/postgres")"

if [[ -n $PREVIOUS_ID ]]; then
docker start "$PREVIOUS_ID" > /dev/null
Expand All @@ -122,8 +154,9 @@ service_url() {
local SERVICE="$1"
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"

local PASSWORD="$(cat "$SERVICE_ROOT/PASSWORD")"
local DATABASE_NAME="$(get_database_name "$SERVICE")"
local USER="$2" # postgres
local PASSWORD="$3"
local DATABASE_NAME="$4"
local SERVICE_ALIAS="$(service_alias "$SERVICE")"
echo "$PLUGIN_SCHEME://postgres:$PASSWORD@$SERVICE_ALIAS:${PLUGIN_DATASTORE_PORTS[0]}/$DATABASE_NAME"
echo "$PLUGIN_SCHEME://$USER:$PASSWORD@$SERVICE_ALIAS:${PLUGIN_DATASTORE_PORTS[0]}/$DATABASE_NAME"
}
15 changes: 15 additions & 0 deletions subcommands/create-database
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config"
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_BASE_PATH/common/functions"
source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions"

postgres-create-database-cmd() {
declare desc="create a $PLUGIN_SERVICE database"
local cmd="$PLUGIN_COMMAND_PREFIX:create-database" argv=("$@"); [[ ${argv[0]} == "$cmd" ]] && shift 1
declare SERVICE="$1" NAME="$2"

database_create "$SERVICE" "$NAME"
}

postgres-create-database-cmd "$@"
29 changes: 29 additions & 0 deletions subcommands/destroy-database
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env bash
source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config"
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$PLUGIN_BASE_PATH/common/functions"
source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions"

postgres-destroy-database-cmd() {
declare desc="delete a $PLUGIN_SERVICE database from the specified service"
local cmd="$PLUGIN_COMMAND_PREFIX:destroy-database" argv=("$@"); [[ ${argv[0]} == "$cmd" ]] && shift 1
declare SERVICE="$1" DATABASE="$2"

[[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a name for the service"
[[ -z "$DATABASE" ]] && dokku_log_fail "Please specify a name for the database"
verify_service_name "$SERVICE"
verify_database_name "$SERVICE" "$DATABASE"
SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
SERVICE_NAME="$(get_service_name "$SERVICE")"

dokku_log_info1 "Deleting $DATABASE from $SERVICE"
if docker exec "$SERVICE_NAME" su - postgres -c "psql -c \"DROP DATABASE $DATABASE;\"" 2> /dev/null && docker exec "$SERVICE_NAME" su - postgres -c "psql -c \"DROP USER $DATABASE;\"" 2> /dev/null; then
rm -f "$SERVICE_ROOT/databases/$DATABASE"
rm -f "$SERVICE_ROOT/auth/$DATABASE"
dokku_log_info2 "$PLUGIN_SERVICE $SERVICE database deleted: $DATABASE"
else
dokku_log_fail "Could not delete the database"
fi
}

postgres-destroy-database-cmd "$@"
25 changes: 23 additions & 2 deletions subcommands/link
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,35 @@ source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/functions"
postgres-link-cmd() {
declare desc="link the $PLUGIN_SERVICE service to the app"
local cmd="$PLUGIN_COMMAND_PREFIX:link" argv=("$@"); [[ ${argv[0]} == "$cmd" ]] && shift 1
declare SERVICE="$1" APP="$2"

local USER=''

local next_index=1; local skip=false; local args=("$@"); local positional=()
for arg in "$@"; do
$skip && skip=false && local next_index=$(( next_index + 1 )) && continue
case "$arg" in
--user)
USER=${args[$next_index]}; skip=true
;;
--database)
DATABASE=${args[$next_index]}; skip=true
;;
*)
positional+=("$arg")
esac
local next_index=$(( next_index + 1 ))
done

declare SERVICE="${positional[0]}" APP="${positional[1]}"
APP=${APP:="$DOKKU_APP_NAME"}

[[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a name for the service"
[[ -z "$APP" ]] && dokku_log_fail "Please specify an app to run the command on"
verify_app_name "$APP"
verify_service_name "$SERVICE"
service_link "$SERVICE" "$APP"
[[ -z "$USER" ]] || verify_user_name "$SERVICE" "$USER"
[[ -z "$DATABASE" ]] || verify_database_name "$SERVICE" "$DATABASE"
service_link "$SERVICE" "$APP" "$USER" "$DATABASE"
}

postgres-link-cmd "$@"
Loading