Skip to content

Single container with multiple databases #138

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

Closed
wants to merge 18 commits into from
Closed
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 @@ -27,14 +27,16 @@ postgres:backup-unset-encryption <name>, Removes backup encryption for future ba
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, delete the data 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:exists <service> Check if the postgres service exists
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> [--database database] Link the postgres service to the app
postgres:linked <name> <app> Check if the postgres service is linked to an app
postgres:list List all postgres services
postgres:logs <name> [-t] Print the most recent log(s) for this service
Expand All @@ -43,7 +45,7 @@ postgres:restart <name> Graceful shutdown and restart of the postgres
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> [--database database] Unlink the postgres service from the app
```

## usage
Expand Down
167 changes: 144 additions & 23 deletions common-functions
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ get_database_name() {
echo "$DATABASE" | tr .- _
}

get_database_user_name() {
declare SERVICE="$1"
declare DATABASE="$2"

if [ "$DATABASE" = "$SERVICE" ]; then
echo "postgres"
else
echo "$DATABASE"
fi
}

get_random_ports() {
declare desc="Retrieves N random ports"
declare iterations="${1:-1}"
Expand Down Expand Up @@ -250,11 +261,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 @@ -307,9 +341,12 @@ 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"
local DATABASE=${3:-$(get_database_name "$SERVICE")}
local USER=$(get_database_user_name "$SERVICE" "$DATABASE")
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 @@ -355,9 +392,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 @@ -515,26 +552,19 @@ service_promote() {
local PLUGIN_DEFAULT_CONFIG_VAR="${PLUGIN_DEFAULT_ALIAS}_URL"
local EXISTING_CONFIG=$(config_all "$APP")
update_plugin_scheme_for_app "$APP"
local SERVICE_URL=$(service_url "$SERVICE")
local CONFIG_VARS=($(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1)) || true
local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"
local PREVIOUS_DEFAULT_URL=$(get_url_from_config "$EXISTING_CONFIG" "$PLUGIN_DEFAULT_CONFIG_VAR")
local USER="postgres"
local DATABASE="$SERVICE"
local PASSWORD=$(cat "$SERVICE_ROOT/auth/$USER")
local SERVICE_URL=$(service_url "$SERVICE" "$USER" "$PASSWORD" "$DATABASE")
local CONFIG_VAR=$(echo "$EXISTING_CONFIG" | grep "$SERVICE_URL" | cut -d: -f1) || true

[[ -z ${CONFIG_VARS[*]} ]] && dokku_log_fail "Not linked to app $APP"
[[ ${CONFIG_VARS[*]} =~ $PLUGIN_DEFAULT_CONFIG_VAR ]] && dokku_log_fail "Service $1 already promoted as $PLUGIN_DEFAULT_CONFIG_VAR"
[[ -z $CONFIG_VAR ]] && dokku_log_fail "Not linked to app $APP"
[[ $CONFIG_VAR =~ $PLUGIN_DEFAULT_CONFIG_VAR ]] && dokku_log_fail "Service $1 already promoted as $PLUGIN_DEFAULT_CONFIG_VAR"

local NEW_CONFIG_VARS=""
if [[ -n $PREVIOUS_DEFAULT_URL ]]; then
local PREVIOUS_ALIAS=$(echo "$EXISTING_CONFIG" | grep "$PREVIOUS_DEFAULT_URL" | grep -v "$PLUGIN_DEFAULT_CONFIG_VAR") || true
if [[ -z $PREVIOUS_ALIAS ]]; then
local ALIAS=$(service_alternative_alias "$EXISTING_CONFIG")
NEW_CONFIG_VARS+="${ALIAS}_URL=$PREVIOUS_DEFAULT_URL "
fi
fi
local PROMOTE_URL=$(get_url_from_config "$EXISTING_CONFIG" "${CONFIG_VARS[0]}")
NEW_CONFIG_VARS+="$PLUGIN_DEFAULT_CONFIG_VAR=$PROMOTE_URL"

# shellcheck disable=SC2086
config_set "$APP" $NEW_CONFIG_VARS
config_set --no-restart "$APP" "${PLUGIN_DEFAULT_CONFIG_VAR}=$SERVICE_URL"
config_set "$APP" "${CONFIG_VAR}=$PREVIOUS_DEFAULT_URL"
}

service_set_alias() {
Expand Down Expand Up @@ -583,8 +613,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 DATABASE=${3:-$(get_database_name "$SERVICE")}
local USER=$(get_database_user_name "$SERVICE" "$DATABASE")
local PASSWORD=$(cat "$SERVICE_ROOT/auth/$USER")
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 All @@ -611,6 +645,59 @@ service_version() {
docker inspect -f '{{.Config.Image}}' "$SERVICE_NAME"
}


service_create_database() {
local SERVICE="$1"
local DATABASE="$2"
local USER="$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"

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 \\\"$USER\\\" WITH PASSWORD '$password';\"" > /dev/null 2>&1; then
echo "$password" > "$SERVICE_ROOT/auth/$USER"
chmod 640 "$SERVICE_ROOT/auth/$USER"
else
dokku_log_verbose_quiet "Already exists"
fi

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

dokku_log_info2 "$PLUGIN_SERVICE database created: $DATABASE"
}

service_destroy_database() {
local SERVICE="$1"
local DATABASE="$2"

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
}


update_plugin_scheme_for_app() {
declare desc="Retrieves the updated plugin scheme"
declare APP="$1"
Expand All @@ -627,3 +714,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 for $SERVICE"
local AUTH_DIR="$SERVICE_ROOT/auth"
mkdir -p "$AUTH_DIR"
mv "$SERVICE_ROOT/PASSWORD" "$AUTH_DIR/postgres"
mkdir -p "$SERVICE_ROOT/databases/"
touch "$SERVICE_ROOT/databases/postgres"
fi
}
25 changes: 15 additions & 10 deletions functions
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ service_create() {
local SERVICE="$1"
[[ -z "$SERVICE" ]] && dokku_log_fail "Please specify a name for the service"
[[ ! -d "$PLUGIN_DATA_ROOT/$SERVICE" ]] || dokku_log_fail "$PLUGIN_SERVICE service $SERVICE already exists"
SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"; LINKS_FILE="$SERVICE_ROOT/LINKS"
SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"; LINKS_FILE="$SERVICE_ROOT/LINKS"; AUTH_DIR="$SERVICE_ROOT/auth"; DB_DIR="$SERVICE_ROOT/databases"

service_parse_args "${@:2}"

Expand All @@ -35,14 +35,16 @@ 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 "$AUTH_DIR" || dokku_log_fail "Unable to create service auth directory"
mkdir -p "$DB_DIR" || dokku_log_fail "Unable to create service databases directory"
touch "$LINKS_FILE"
PASSWORD=$(openssl rand -hex 16)
if [[ -n "$SERVICE_PASSWORD" ]]; then
PASSWORD="$SERVICE_PASSWORD"
dokku_log_warn "Specified password may not be as secure as the auto-generated password"
fi
echo "$PASSWORD" > "$SERVICE_ROOT/PASSWORD"
chmod 640 "$SERVICE_ROOT/PASSWORD"
echo "$PASSWORD" > "$AUTH_DIR/postgres"
chmod 640 "$AUTH_DIR/postgres"

[[ -n "$SERVICE_CUSTOM_ENV" ]] && POSTGRES_CUSTOM_ENV="$SERVICE_CUSTOM_ENV"
if [[ -n $POSTGRES_CUSTOM_ENV ]]; then
Expand All @@ -57,8 +59,9 @@ 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
local DB_DIR="$SERVICE_ROOT/databases"

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")
echo "$ID" > "$SERVICE_ROOT/ID"
Expand All @@ -68,6 +71,7 @@ service_create_container() {

dokku_log_verbose_quiet "Creating container database"
DATABASE_NAME="$(get_database_name "$SERVICE")"
touch "$DB_DIR/$DATABASE_NAME"
docker exec "$SERVICE_NAME" su - postgres -c "createdb -E utf8 $DATABASE_NAME" 2> /dev/null || echo 'Already exists'

dokku_log_verbose_quiet "Securing connection to database"
Expand All @@ -87,7 +91,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 @@ -101,7 +105,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 @@ -123,7 +127,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 @@ -140,8 +144,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"
}
5 changes: 5 additions & 0 deletions install
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env bash
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config"
set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions"

plugin-install() {
pull-docker-image() {
Expand Down Expand Up @@ -36,6 +37,10 @@ plugin-install() {
EOL

chmod 0440 "$_SUDOERS_FILE"

for d in "$PLUGIN_DATA_ROOT"/* ; do
check_auth_migration "$d"
done
}

plugin-install "$@"
18 changes: 18 additions & 0 deletions subcommands/create-database
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/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"

service-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" 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"

service_create_database "$SERVICE" "$DATABASE"
}

service-create-database-cmd "$@"
Loading