diff --git a/README.md b/README.md index e09bda8..dd84554 100644 --- a/README.md +++ b/README.md @@ -25,15 +25,16 @@ mysql:connect Connect via mysql to a mysql service mysql:create Create a mysql service mysql:destroy Delete the service and stop its container if there are no links left mysql:export Export a dump of the mysql service database -mysql:expose NOT IMPLEMENTED +mysql:expose [port] Expose a mysql service on custom port if provided (random port otherwise) mysql:import NOT IMPLEMENTED mysql:info Print the connection information mysql:link Link the mysql service to the app mysql:list List all mysql services mysql:logs [-t] Print the most recent log(s) for this service -mysql:restart Graceful shutdown and restart of the service container -mysql:unexpose NOT IMPLEMENTED -mysql:unlink Unlink the mysql service from the app +mysql:restart Graceful shutdown and restart of the mysql service container +mysql:start Start a previously stopped mysql service +mysql:stop Stop a running mysql service +mysql:unexpose Unexpose a previously exposed mysql service ``` ## usage @@ -65,7 +66,7 @@ dokku mysql:link lolipop playground # the above will expose the following environment variables # # DATABASE_URL=mysql://mysql:SOME_PASSWORD@172.17.0.1:3306/lolipop -# DATABASE_NAME=/playground/DATABASE +# DATABASE_NAME=/lolipop/DATABASE # DATABASE_PORT=tcp://172.17.0.1:3306 # DATABASE_PORT_3306_TCP=tcp://172.17.0.1:3306 # DATABASE_PORT_3306_TCP_PROTO=tcp @@ -85,11 +86,10 @@ dokku mysql:logs lolipop dokku mysql:logs lolipop -t # to tail # finally, you can destroy the container -dokku mysql:destroy playground +dokku mysql:destroy lolipop ``` ## todo - implement mysql:clone -- implement mysql:expose - implement mysql:import diff --git a/commands b/commands index 90a1338..e3e6e50 100755 --- a/commands +++ b/commands @@ -42,7 +42,8 @@ case "$1" in touch "$LINKS_FILE" dokku_log_info1 "Starting container" - ID=$(docker run --name "dokku.mysql.$SERVICE" -v "$SERVICE_ROOT/data:/var/lib/mysql" -e "MYSQL_ROOT_PASSWORD=$rootpassword" -e MYSQL_USER=mysql -e "MYSQL_PASSWORD=$password" -e "MYSQL_DATABASE=$SERVICE" -d "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION") + SERVICE_NAME=$(get_service_name "$SERVICE") + ID=$(docker run --name "$SERVICE_NAME" -v "$SERVICE_ROOT/data:/var/lib/mysql" -e "MYSQL_ROOT_PASSWORD=$rootpassword" -e MYSQL_USER=mysql -e "MYSQL_PASSWORD=$password" -e "MYSQL_DATABASE=$SERVICE" -d --restart always --label dokku=service --label dokku.service=mysql "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION") echo "$ID" > "$SERVICE_ROOT/ID" dokku_log_verbose_quiet "Waiting for container to be ready" @@ -78,11 +79,13 @@ case "$1" in ID=$(cat "$SERVICE_ROOT/ID") dokku_log_verbose_quiet "Deleting container data" + service_start "$SERVICE" + sleep 10 docker exec -it "$ID" chmod -R 777 /var/lib/mysql - dokku_log_verbose_quiet "Stopping container" - docker stop "$ID" > /dev/null - docker kill "$ID" > /dev/null + service_stop "$SERVICE" + dokku_log_verbose_quiet "Killing container" + docker kill "$ID" > /dev/null || true sleep 1 dokku_log_verbose_quiet "Removing container" @@ -139,14 +142,23 @@ case "$1" in service_logs "$2" "$3" ;; + $PLUGIN_COMMAND_PREFIX:start) + [[ -z $2 ]] && dokku_log_fail "Please specify a name for the service" + verify_service_name "$2" + service_start "$2" + ;; + + $PLUGIN_COMMAND_PREFIX:stop) + [[ -z $2 ]] && dokku_log_fail "Please specify a name for the service" + verify_service_name "$2" + service_stop "$2" + ;; + $PLUGIN_COMMAND_PREFIX:restart) [[ -z $2 ]] && dokku_log_fail "Please specify a name for the service" verify_service_name "$2" - SERVICE="$2"; SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" - - ID=$(cat "$SERVICE_ROOT/ID") - - docker restart --time=10 "$ID" + service_stop "$2" + service_start "$2" dokku_log_info1 "Please call dokku ps:restart on all linked apps" ;; @@ -182,34 +194,13 @@ case "$1" in $PLUGIN_COMMAND_PREFIX:expose) [[ -z $2 ]] && dokku_log_fail "Please specify a name for the service" verify_service_name "$2" - SERVICE="$2"; SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"; PORT_FILE="$SERVICE_ROOT/PORT"; DESTINATION_FILE="$SERVICE_ROOT/IPTABLES_DESTINATION" - - [[ -f "$PORT_FILE" ]] && PORT=$(cat "$PORT_FILE") && dokku_log_fail "Service $SERVICE already exposed on port $PORT" - - ID=$(cat "$SERVICE_ROOT/ID") - IP=$(get_container_ip "$ID") - PORT=$(get_random_port) - echo "$PORT" > "$PORT_FILE" - echo "$IP:$PLUGIN_DATASTORE_PORT" > "$DESTINATION_FILE" - - iptables -t nat -A DOCKER -p tcp --dport "$PORT" -j DNAT --to-destination "$IP:$PLUGIN_DATASTORE_PORT" - dokku_log_info1 "Service $SERVICE exposed on port $PORT" + service_port_expose "$2" "${@:3}" ;; $PLUGIN_COMMAND_PREFIX:unexpose) [[ -z $2 ]] && dokku_log_fail "Please specify a name for the service" verify_service_name "$2" - SERVICE="$2"; SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"; PORT_FILE="$SERVICE_ROOT/PORT"; DESTINATION_FILE="$SERVICE_ROOT/IPTABLES_DESTINATION" - - [[ ! -f "$PORT_FILE" ]] && dokku_log_fail "Service not exposed" - - ID=$(cat "$SERVICE_ROOT/ID") - IP=$(get_container_ip "$ID") - PORT=$(cat "$PORT_FILE") - DESTINATION=$(cat "$DESTINATION_FILE") - - iptables -t nat -D DOCKER -p tcp --dport "$PORT" -j DNAT --to-destination "$DESTINATION" - rm -rf "$PORT_FILE" + service_port_unexpose "$2" ;; help) @@ -223,12 +214,14 @@ case "$1" in $PLUGIN_COMMAND_PREFIX:import , NOT IMPLEMENTED $PLUGIN_COMMAND_PREFIX:connect , Connect via mysql to a $PLUGIN_SERVICE service $PLUGIN_COMMAND_PREFIX:logs [-t], Print the most recent log(s) for this service - $PLUGIN_COMMAND_PREFIX:restart , Graceful shutdown and restart of the service container + $PLUGIN_COMMAND_PREFIX:restart , Graceful shutdown and restart of the $PLUGIN_SERVICE service container $PLUGIN_COMMAND_PREFIX:info , Print the connection information $PLUGIN_COMMAND_PREFIX:list, List all $PLUGIN_SERVICE services $PLUGIN_COMMAND_PREFIX:clone , NOT IMPLEMENTED - $PLUGIN_COMMAND_PREFIX:expose , NOT IMPLEMENTED - $PLUGIN_COMMAND_PREFIX:unexpose , NOT IMPLEMENTED + $PLUGIN_COMMAND_PREFIX:expose [port], Expose a $PLUGIN_SERVICE service on custom port if provided (random port otherwise) + $PLUGIN_COMMAND_PREFIX:unexpose , Unexpose a previously exposed $PLUGIN_SERVICE service + $PLUGIN_COMMAND_PREFIX:start , Start a previously stopped $PLUGIN_SERVICE service + $PLUGIN_COMMAND_PREFIX:stop , Stop a running $PLUGIN_SERVICE service EOF ;; diff --git a/config b/config index 68b4470..35b97db 100644 --- a/config +++ b/config @@ -5,7 +5,7 @@ export MYSQL_ROOT=/var/lib/dokku/services/mysql export PLUGIN_COMMAND_PREFIX="mysql" export PLUGIN_DATA_ROOT=$MYSQL_ROOT -export PLUGIN_DATASTORE_PORT=3306 +export PLUGIN_DATASTORE_PORTS=(3306) export PLUGIN_DEFAULT_ALIAS="DATABASE" export PLUGIN_IMAGE=$MYSQL_IMAGE export PLUGIN_IMAGE_VERSION=$MYSQL_IMAGE_VERSION diff --git a/docker-args b/docker-args index f96362e..35e1a6b 100755 --- a/docker-args +++ b/docker-args @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x -source "$(dirname "$0")/../common/functions" +source "$PLUGIN_PATH/common/functions" source "$(dirname "$0")/functions" source "$(dirname "$0")/config" diff --git a/functions b/functions index 3f648e6..d827d8e 100755 --- a/functions +++ b/functions @@ -2,19 +2,21 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$(dirname "$0")/config" -get_random_port() { - local port=$RANDOM - local quit=0 - - while [ "$quit" -ne 1 ]; do - netstat -a | grep $port >> /dev/null - if [ $? -gt 0 ]; then - quit=1 - else - port=$((port + 1)) - fi +get_random_ports() { + local iterations="${1:-1}" + for (( i=0; i < iterations; i++ )); do + local port=$RANDOM + local quit=0 + while [ "$quit" -ne 1 ]; do + netstat -an | grep $port > /dev/null + if [ $? -gt 0 ]; then + quit=1 + else + port=$((port + 1)) + fi + done + echo $port done - echo $port } get_container_ip() { @@ -54,11 +56,19 @@ service_list() { else dokku_log_info1_quiet "$PLUGIN_SERVICE services:" for SERVICE in $SERVICES; do - dokku_log_verbose "$SERVICE $(service_status $SERVICE)" + dokku_log_verbose "$SERVICE $(service_status "$SERVICE")$(service_exposed_ports "$SERVICE")" done fi } +service_exposed_ports() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + [[ ! -f $PORT_FILE ]] && return 0 + printf ", exposed port(s): %s" "$(cat "$PORT_FILE")" +} + service_link() { local APP="$2" local SERVICE="$1" @@ -111,6 +121,109 @@ service_status() { echo "(stopped)" && return 0 } +service_port_expose() { + service_start "$1" + service_port_unpause "$1" "true" "${@:2}" +} + +service_port_pause() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local EXPOSED_NAME="$(get_service_name "$SERVICE").ambassador" + local PORT_FILE="$SERVICE_ROOT/PORT" + local LOG_FAIL="$2" + + if [[ "$LOG_FAIL" == "true" ]]; then + [[ ! -f "$PORT_FILE" ]] && dokku_log_fail "Service not exposed" + else + [[ ! -f "$PORT_FILE" ]] && return 0 + fi + + docker stop "$EXPOSED_NAME" > /dev/null + docker rm "$EXPOSED_NAME" > /dev/null + if [[ "$LOG_FAIL" == "true" ]]; then + dokku_log_info1 "Service $SERVICE unexposed" + fi +} + +service_port_unexpose() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local PORT_FILE="$SERVICE_ROOT/PORT" + service_port_pause "$SERVICE" "true" + rm -rf "$PORT_FILE" +} + +service_port_unpause() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME=$(get_service_name "$SERVICE") + local EXPOSED_NAME="${SERVICE_NAME}.ambassador" + local PORT_FILE="$SERVICE_ROOT/PORT" + local LOG_FAIL="$2" + local PORTS=(${@:3}) + PORTS=(${PORTS[@]:-$(get_random_ports ${#PLUGIN_DATASTORE_PORTS[@]})}) + local ID=$(cat "$SERVICE_ROOT/ID") + + [[ "${#PORTS[@]}" != "${#PLUGIN_DATASTORE_PORTS[@]}" ]] && dokku_log_fail "${#PLUGIN_DATASTORE_PORTS[@]} ports to be exposed need to be provided" + + if [[ "$LOG_FAIL" == "true" ]]; then + [[ -f "$PORT_FILE" ]] && PORTS=($(cat "$PORT_FILE")) && dokku_log_fail "Service $SERVICE already exposed on port(s) ${PORTS[*]}" + else + [[ ! -f "$PORT_FILE" ]] && return 0 + PORTS=($(cat "$PORT_FILE")) + fi + + echo "${PORTS[@]}" > "$PORT_FILE" + + docker run -d --link "$SERVICE_NAME:redis" --name "$EXPOSED_NAME" $(docker_ports_options "${PORTS[@]}") --restart always --label dokku=ambassador --label dokku.ambassador=redis svendowideit/ambassador > /dev/null + if [[ "$LOG_FAIL" == "true" ]]; then + dokku_log_info1 "Service $SERVICE exposed on port(s) ${PORTS[*]}" + fi +} + +docker_ports_options() { + local PORTS=("$@") + for (( i=0; i < ${#PLUGIN_DATASTORE_PORTS[@]}; i++ )); do + echo -n "-p ${PORTS[i]}:${PLUGIN_DATASTORE_PORTS[i]} " + done +} + +service_start() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE" + local SERVICE_NAME=$(get_service_name "$SERVICE") + local ID=$(docker ps -f status=running | grep "$SERVICE_NAME" | awk '{print $1}') || true + [[ -n $ID ]] && dokku_log_warn "Service is already started" && return 0 + + dokku_log_info1_quiet "Starting container" + local PREVIOUS_ID=$(docker ps -f status=exited | grep "$SERVICE_NAME" | awk '{print $1}') || true + if [[ -n $PREVIOUS_ID ]]; then + docker start "$PREVIOUS_ID" > /dev/null + service_port_unpause "$SERVICE" + dokku_log_info2 "Container started" + else + dokku_log_verbose_quiet "No container exists for $SERVICE" + fi +} + +service_stop() { + local SERVICE="$1" + local SERVICE_ROOT="$PLUGIN_DATA_ROOT/$SERVICE"; + local SERVICE_NAME=$(get_service_name "$SERVICE") + local ID=$(docker ps -f status=running | grep "$SERVICE_NAME" | awk '{print $1}') || true + [[ -z $ID ]] && dokku_log_warn "Service is already stopped" && return 0 + + if [[ -n $ID ]]; then + dokku_log_info1_quiet "Stopping container" + docker stop "$SERVICE_NAME" > /dev/null + service_port_pause "$SERVICE" + dokku_log_info2 "Container stopped" + else + dokku_log_verbose_quiet "No container exists for $SERVICE" + fi +} + service_unlink() { local APP="$2" local SERVICE="$1" @@ -133,7 +246,7 @@ service_url() { local ID="$(cat "$SERVICE_ROOT/ID")" local IP="$(get_container_ip "$ID")" local PASSWORD="$(cat "$SERVICE_ROOT/PASSWORD")" - echo "$PLUGIN_SCHEME://mysql:$PASSWORD@$IP:$PLUGIN_DATASTORE_PORT/$SERVICE" + echo "$PLUGIN_SCHEME://mysql:$PASSWORD@$IP:${PLUGIN_DATASTORE_PORTS[0]}/$SERVICE" } is_container_status () { @@ -147,3 +260,8 @@ is_container_status () { return 1 fi } + +get_service_name() { + local SERVICE="$1" + echo "dokku.${PLUGIN_COMMAND_PREFIX}.$SERVICE" +} diff --git a/install b/install index 91a71d1..f3b2bed 100755 --- a/install +++ b/install @@ -6,5 +6,7 @@ if ! docker images | grep -e "^$PLUGIN_IMAGE " | grep -q $PLUGIN_IMAGE_VERSION ; docker pull $PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION fi +docker pull svendowideit/ambassador:latest + mkdir -p $PLUGIN_DATA_ROOT || echo "Failed to create $PLUGIN_SERVICE directory" chown dokku:dokku $PLUGIN_DATA_ROOT