diff --git a/docs/process-management.md b/docs/process-management.md index 619ec8f60..17e541b69 100644 --- a/docs/process-management.md +++ b/docs/process-management.md @@ -4,10 +4,38 @@ Dokku supports rudimentary process (really container) management via the `ps` pl ``` ps List processes running in app container(s) +ps:rebuildall Rebuild all apps +ps:rebuild Rebuild an app +ps:restartall Restart all deployed app containers +ps:restart Restart app container(s) +ps:scale = [=] Set how many processes of a given process to run ps:start Start app container(s) ps:stop Stop app container(s) -ps:restart Restart app container(s) -ps:restartall Restart all deployed app containers ``` *NOTE*: As of v0.3.14, `dokku deploy:all` in now deprecated by `ps:restartall` and will be removed in a future version. + + +## Scaling + +Dokku allows you to run multiple process types at different container counts. For example, if you had an app that contained 1 web app listener and 1 background job processor, dokku can, spin up 1 container for each process type defined in the Procfile. By default we will only start the web process. However, if you wanted 2 job processors running simultaneously, you can modify this behavior in a few ways. + +## Include a DOKKU_SCALE file in your repo + +Dokku expects this file to contain one line for every process defined in your Procfile. Example: +``` +web=1 +worker=2 +``` + + +## Use the ps:scale command. Example: +``` +dokku ps:scale app_name web=1 worker=2 +``` +*NOTE*: Dokku will always use the DOKKU_SCALE file that ships with the repo to override any local settings. + + +## The web proctype + +Like Heroku, we handle the `web` proctype differently from others. The `web` proctype is the only proctype that will invoke custom checks as defined by a CHECKS file. It is also the only proctype that will be launched in a container that is either proxied via nginx or bound to an external port. diff --git a/dokku b/dokku index 283e11d20..6316a5a5e 100755 --- a/dokku +++ b/dokku @@ -66,68 +66,102 @@ case "$1" in APP="$2"; IMAGE="dokku/$APP" pluginhook pre-deploy $APP - if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then - oldid=$(< "$DOKKU_ROOT/$APP/CONTAINER") - fi - - # start the app - DOCKER_ARGS=$(: | pluginhook docker-args $APP deploy) - DOCKER_ARGS+=$(: | pluginhook docker-args-deploy $APP) - BIND_EXTERNAL=$(pluginhook bind-external-ip $APP) - is_image_buildstep_based "$IMAGE" && DOKKU_BUILDSTEP=true - [[ -n "$DOKKU_BUILDSTEP" ]] && START_CMD="/start web" - [[ -z "$DOKKU_BUILDSTEP" ]] && DOKKU_DOCKERFILE_PORT=$(dokku config:get $APP DOKKU_DOCKERFILE_PORT || true) + DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + oldids=$(get_container_ids $APP) - if [[ "$BIND_EXTERNAL" = "false" ]];then - port=${DOKKU_DOCKERFILE_PORT:=5000} - id=$(docker run -d -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) - ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $id) - else - id=$(docker run -d -p 5000 -e PORT=5000 $DOCKER_ARGS $IMAGE $START_CMD) - port=$(docker port $id 5000 | sed 's/[0-9.]*://') - ipaddr=127.0.0.1 - fi + while read line || [ -n "$line" ] + do + PROC_TYPE=${line%%=*} + PROC_COUNT=${line#*=} + CONTAINER_INDEX=1 - # if we can't post-deploy successfully, kill new container - kill_new() { - docker inspect $id &> /dev/null && docker stop $id > /dev/null && docker kill $id > /dev/null - trap - INT TERM EXIT - kill -9 $$ - } + while [[ $CONTAINER_INDEX -le $PROC_COUNT ]];do + id=""; port=""; ipaddr="" + DOKKU_CONTAINER_ID_FILE="$DOKKU_ROOT/$APP/CONTAINER.$PROC_TYPE.$CONTAINER_INDEX" + DOKKU_IP_FILE="$DOKKU_ROOT/$APP/IP.$PROC_TYPE.$CONTAINER_INDEX" + DOKKU_PORT_FILE="$DOKKU_ROOT/$APP/PORT.$PROC_TYPE.$CONTAINER_INDEX" - # run checks first, then post-deploy hooks, which switches Nginx traffic - trap kill_new INT TERM EXIT - dokku_log_info1 "Running pre-flight checks" - pluginhook check-deploy $APP $port $ipaddr $id - trap - INT TERM EXIT + # start the app + DOCKER_ARGS=$(: | pluginhook docker-args $APP deploy) + DOCKER_ARGS+=" -e DYNO=$PROC_TYPE " + DOCKER_ARGS+=$(: | pluginhook docker-args-deploy $APP) + BIND_EXTERNAL=$(pluginhook bind-external-ip $APP) - # now using the new container - echo $id > "$DOKKU_ROOT/$APP/CONTAINER" - echo $ipaddr > "$DOKKU_ROOT/$APP/IP" - echo $port > "$DOKKU_ROOT/$APP/PORT" - echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$port" > "$DOKKU_ROOT/$APP/URL" + [[ -n "$DOKKU_BUILDSTEP" ]] && START_CMD="/start $PROC_TYPE" + [[ -z "$DOKKU_BUILDSTEP" ]] && DOKKU_DOCKERFILE_PORT=$(dokku config:get $APP DOKKU_DOCKERFILE_PORT || true) + + if [[ "$BIND_EXTERNAL" = "false" ]] && [[ "$PROC_TYPE" == "web" ]];then + port=${DOKKU_DOCKERFILE_PORT:=5000} + id=$(docker run -d -e PORT=$port $DOCKER_ARGS $IMAGE $START_CMD) + ipaddr=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' $id) + elif [[ "$BIND_EXTERNAL" = "true" ]] && [[ "$PROC_TYPE" == "web" ]];then + id=$(docker run -d -p 5000 -e PORT=5000 $DOCKER_ARGS $IMAGE $START_CMD) + port=$(docker port $id 5000 | sed 's/[0-9.]*://') + ipaddr=127.0.0.1 + elif [[ "$PROC_TYPE" != "web" ]];then + id=$(docker run -d $DOCKER_ARGS $IMAGE $START_CMD) + fi + + # if we can't post-deploy successfully, kill new container + kill_new() { + docker inspect $id &> /dev/null && docker stop $id > /dev/null && docker kill $id > /dev/null + trap - INT TERM EXIT + kill -9 $$ + } + + # run checks first, then post-deploy hooks, which switches Nginx traffic + trap kill_new INT TERM EXIT + dokku_log_info1 "Running pre-flight checks" + pluginhook check-deploy $APP $id $PROC_TYPE $port $ipaddr + trap - INT TERM EXIT + + # now using the new container + [[ -n "$id" ]] && echo $id > "$DOKKU_CONTAINER_ID_FILE" + [[ -n "$ipaddr" ]] && echo $ipaddr > "$DOKKU_IP_FILE" + [[ -n "$port" ]] && echo $port > "$DOKKU_PORT_FILE" + [[ -n "$port" ]] && echo "http://$(< "$DOKKU_ROOT/HOSTNAME"):$port" > "$DOKKU_ROOT/$APP/URL" + + # cleanup pre-migration files + rm -f $DOKKU_ROOT/$APP/CONTAINER $DOKKU_ROOT/$APP/IP $DOKKU_ROOT/$APP/PORT + + CONTAINER_INDEX=$(( CONTAINER_INDEX + 1 )) + done + # cleanup when we scale down + if [[ "$PROC_COUNT" == 0 ]]; then + CONTAINER_IDX_OFFSET=0 + else + CONTAINER_IDX_OFFSET=$((PROC_COUNT + 1)) + fi + find $DOKKU_ROOT/$APP -maxdepth 1 -name "CONTAINER.$PROC_TYPE.*" | sort -t . -k 4 -n | tail -n +$CONTAINER_IDX_OFFSET | xargs rm -f + find $DOKKU_ROOT/$APP -maxdepth 1 -name "IP.$PROC_TYPE.*" | sort -t . -k 4 -n | tail -n +$CONTAINER_IDX_OFFSET | xargs rm -f + find $DOKKU_ROOT/$APP -maxdepth 1 -name "PORT.$PROC_TYPE.*" | sort -t . -k 4 -n | tail -n +$CONTAINER_IDX_OFFSET | xargs rm -f + done < "$DOKKU_SCALE_FILE" dokku_log_info1 "Running post-deploy" - pluginhook post-deploy $APP $port $ipaddr + pluginhook post-deploy $APP $port $ipaddr # kill the old container - if [[ -n "$oldid" ]]; then + if [[ -n "$oldids" ]]; then # Let the old container finish processing requests, before terminating it WAIT="${DOKKU_WAIT_TO_RETIRE:-60}" - dokku_log_info1 "Shutting down old container in $WAIT seconds" + dokku_log_info1 "Shutting down old containers in $WAIT seconds" + for oldid in $oldids; do + dokku_log_info2 "$oldid" + done ( exec >/dev/null 2>/dev/null /dev/null + docker kill $oldid &> /dev/null # force a kill as docker seems to not send SIGKILL as the docs would indicate + done ) & disown -a # Use trap since disown/nohup don't seem to keep child alive # Give child process just enough time to set the traps sleep 0.1 fi - ;; cleanup) diff --git a/plugins/00_dokku-standard/commands b/plugins/00_dokku-standard/commands index a4d50b49d..2c67807f2 100755 --- a/plugins/00_dokku-standard/commands +++ b/plugins/00_dokku-standard/commands @@ -109,15 +109,23 @@ case "$1" in ;; ls) - apps=$(ls -d $DOKKU_ROOT/*/ 2>/dev/null) || (echo "You haven't depoyed any applications yet" && exit 1) + dokku_apps=$(ls -d $DOKKU_ROOT/*/ 2>/dev/null) || (echo "You haven't deployed any applications yet" && exit 1) - dokku_col_log_info2_quiet "App Name" "Container id" + dokku_col_log_info1_quiet "App Name" "Container Type" "Container Id" "Status" - for app in $apps; do - if [[ -f $app/CONTAINER ]]; then - dokku_col_log_msg "$(basename $app)" "$(< $app/CONTAINER)" + for dokku_app in $dokku_apps; do + APP=$(basename $dokku_app) + DOKKU_APP_CIDS=$(get_container_ids $APP) + DOCKER_RUNNING_CONTAINERS=$(docker ps -q --no-trunc) + if [[ -n $DOKKU_APP_CIDS ]]; then + for DOKKU_APP_CID in $DOKKU_APP_CIDS; do + DOKKU_APP_CONTAINER_STATUS="stopped" + [[ $DOCKER_RUNNING_CONTAINERS =~ $DOKKU_APP_CID ]] && DOKKU_APP_CONTAINER_STATUS="running" + DOKKU_APP_CONTAINER_TYPE=$(grep -l $DOKKU_APP_CID $DOKKU_ROOT/$APP/CONTAINER.* | awk -F. '{ print $2 }') + dokku_col_log_msg "$APP" "$DOKKU_APP_CONTAINER_TYPE" "${DOKKU_APP_CID:0:12}" "$DOKKU_APP_CONTAINER_STATUS" + done else - dokku_col_log_msg "$(basename $app)" + dokku_col_log_msg "$APP" "NOT_DEPLOYED" "NOT_DEPLOYED" "NOT_DEPLOYED" fi done ;; @@ -127,13 +135,23 @@ case "$1" in verify_app_name "$2" APP="$2"; - if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then - CONTAINER=$(<$DOKKU_ROOT/$APP/CONTAINER) + if (is_deployed $APP); then + CONTAINER_IDS=( $(get_container_ids $APP) ) + LAST_CONTAINER_ID=${CONTAINER_IDS[${#CONTAINER_IDS[@]} - 1]} + if [[ $3 == "-t" ]]; then - docker logs --follow $CONTAINER + DOKKU_LOGS_ARGS="--follow" else - docker logs $CONTAINER | tail -n 100 + DOKKU_LOGS_ARGS="--tail 100" fi + for CID in "${CONTAINER_IDS[@]}";do + if [[ "$CID" != "$LAST_CONTAINER_ID" ]];then + DOKKU_LOGS_CMD+="docker logs $DOKKU_LOGS_ARGS $CID& " + else + DOKKU_LOGS_CMD+="docker logs $DOKKU_LOGS_ARGS $CID; " + fi + done + bash -c "($DOKKU_LOGS_CMD)" else echo "Application's container not found" fi @@ -181,7 +199,9 @@ case "$1" in if [[ -f "$DOKKU_ROOT/VHOST" ]]; then echo "$SCHEME://$(< "$DOKKU_ROOT/VHOST")" else - echo "$SCHEME://$(< "$DOKKU_ROOT/HOSTNAME"):$(< "$DOKKU_ROOT/$APP/PORT")" + for PORT_FILE in $DOKKU_ROOT/$APP/PORT.*; do + echo "$SCHEME://$(< "$DOKKU_ROOT/HOSTNAME"):$(< "$PORT_FILE")" + done fi ;; diff --git a/plugins/apps/commands b/plugins/apps/commands index a82aa1e4f..935d2b1f7 100755 --- a/plugins/apps/commands +++ b/plugins/apps/commands @@ -40,11 +40,12 @@ case "$1" in echo "Destroying $APP (including all add-ons)" pluginhook pre-delete $APP - if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then - ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - - docker stop $ID > /dev/null || true - docker rm $ID > /dev/null || true + DOKKU_APP_CIDS=$(get_container_ids $APP) + if [[ -n $DOKKU_APP_CIDS ]]; then + for ID in $DOKKU_APP_CIDS;do + docker stop $ID > /dev/null || true + docker rm $ID > /dev/null || true + done fi docker images | grep $IMAGE | awk '{print $3}' | xargs docker rmi &> /dev/null & diff --git a/plugins/checks/check-deploy b/plugins/checks/check-deploy index 1dea6172e..dbeac5b0a 100755 --- a/plugins/checks/check-deploy +++ b/plugins/checks/check-deploy @@ -37,15 +37,16 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$(dirname $0)/../common/functions" -APP="$1"; DOKKU_APP_LISTEN_PORT="$2"; DOKKU_APP_LISTEN_IP="$3"; DOKKU_APP_CONTAINER_ID="$4" +APP="$1"; DOKKU_APP_CONTAINER_ID="$2"; DOKKU_APP_CONTAINER_TYPE="$3"; DOKKU_APP_LISTEN_PORT="$4"; DOKKU_APP_LISTEN_IP="$5" if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT" ]]; then DOKKU_APP_LISTEN_PORT=$(< "$DOKKU_ROOT/$APP/PORT") fi if [[ -z "$DOKKU_APP_LISTEN_IP" ]] && [[ -f "$DOKKU_ROOT/$APP/IP" ]]; then DOKKU_APP_LISTEN_IP=$(< "$DOKKU_ROOT/$APP/IP") fi -if [[ -z "$DOKKU_APP_CONTAINER_ID" ]] && [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then - DOKKU_APP_CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") +if [[ -z "$DOKKU_APP_CONTAINER_ID" ]]; then + DOKKU_APP_CIDS=( $(get_container_ids $APP) ) + DOKKU_APP_CONTAINER_ID=${DOKKU_APP_CIDS[0]} fi @@ -77,7 +78,7 @@ cleanup() { } trap cleanup EXIT -if [[ ! -s "${TMPDIR}/CHECKS" ]] ; then +if [[ ! -s "${TMPDIR}/CHECKS" ]] || [[ "$DOKKU_APP_CONTAINER_TYPE" != "web" ]]; then dokku_log_verbose "For more efficient zero downtime deployments, create a file CHECKS." dokku_log_verbose "See http://progrium.viewdocs.io/dokku/checks-examples.md for examples" dokku_log_verbose "CHECKS file not found in container: Running simple container check..." @@ -86,7 +87,7 @@ if [[ ! -s "${TMPDIR}/CHECKS" ]] ; then # simple default check to see if the container stuck around # for more thorough checks, create a CHECKS file - DOKKU_DEFAULT_WAIT=$((WAIT+TIMEOUT)) + DOKKU_DEFAULT_WAIT=10 dokku_log_info1 "Waiting for $DOKKU_DEFAULT_WAIT seconds ..." sleep $DOKKU_DEFAULT_WAIT diff --git a/plugins/common/functions b/plugins/common/functions index 8db3af6dd..7bc26b0ce 100755 --- a/plugins/common/functions +++ b/plugins/common/functions @@ -34,36 +34,36 @@ dokku_log_info2_quiet() { } dokku_col_log_info1() { - printf "%-40s %-40s\n" "-----> $@" + printf "%-25s %-25s %-25s %-25s\n" "-----> $@" } dokku_col_log_info1_quiet() { if [[ -z "$DOKKU_QUIET_OUTPUT" ]];then - printf "%-40s %-40s\n" "-----> $@" + printf "%-25s %-25s %-25s %-25s\n" "-----> $@" else return 0 fi } dokku_col_log_info2() { - printf "%-40s %-40s\n" "=====> $@" + printf "%-25s %-25s %-25s %-25s\n" "=====> $@" } dokku_col_log_info2_quiet() { if [[ -z "$DOKKU_QUIET_OUTPUT" ]];then - printf "%-40s %-40s\n" "=====> $@" + printf "%-25s %-25s %-25s %-25s\n" "=====> $@" else return 0 fi } dokku_col_log_msg() { - printf "%-40s %-40s\n" "$@" + printf "%-25s %-25s %-25s %-25s\n" "$@" } dokku_col_log_msg_quiet() { if [[ -z "$DOKKU_QUIET_OUTPUT" ]];then - printf "%-40s %-40s\n" "$@" + printf "%-25s %-25s %-25s %-25s\n" "$@" else return 0 fi @@ -110,12 +110,31 @@ verify_app_name() { return 0 } +verify_image() { + local IMAGE="$1" + DOKKU_APP_IMAGES=$(docker images -q $IMAGE) + if [[ -n "$DOKKU_APP_IMAGES" ]]; then + return 0 + else + return 1 + fi +} + is_image_buildstep_based() { # circleci can't support --rm as they run lxc in lxc [[ ! -f "/home/ubuntu/.circlerc" ]] && local DOCKER_ARGS="--rm" docker run --entrypoint="/bin/bash" $DOCKER_ARGS "$@" -c "[[ -f /exec ]]" } +is_number() { + local NUMBER=$1; local NUM_RE='^[0-9]+$' + if [[ $NUMBER =~ $NUM_RE ]];then + return 0 + else + return 1 + fi +} + parse_args() { for arg in "$@"; do case "$arg" in @@ -135,3 +154,39 @@ parse_args() { done return 0 } + +copy_from_image() { + local IMAGE="$1"; local SRC_FILE="$2"; local DST_DIR="$3"; verify_app_name $APP + + if verify_image "$IMAGE"; then + CID=$(docker run -d $IMAGE bash) + docker cp "$CID:$SRC_FILE" "$DST_DIR" + docker rm -f "$CID" &> /dev/null + else + return 1 + fi +} + +is_deployed() { + APP="$1" + if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] || [[ $(ls $DOKKU_ROOT/$APP/CONTAINER.* &> /dev/null; echo $?) -eq 0 ]];then + return 0 + else + return 1 + fi +} + +get_container_ids() { + APP="$1" + verify_app_name $APP + [[ -f $DOKKU_ROOT/$APP/CONTAINER ]] && DOKKU_CIDS+=$(< $DOKKU_ROOT/$APP/CONTAINER) + + shopt -s nullglob + for DOKKU_CID_FILE in $DOKKU_ROOT/$APP/CONTAINER.*; do + DOKKU_CIDS+=" " + DOKKU_CIDS+=$(< $DOKKU_CID_FILE) + DOKKU_CIDS+=" " + done + shopt -u nullglob + echo $DOKKU_CIDS +} diff --git a/plugins/nginx-vhosts/commands b/plugins/nginx-vhosts/commands index 224247c97..197086f03 100755 --- a/plugins/nginx-vhosts/commands +++ b/plugins/nginx-vhosts/commands @@ -35,11 +35,18 @@ case "$1" in SSL="$DOKKU_ROOT/$APP/tls" APP_NGINX_TEMPLATE="$DOKKU_ROOT/$APP/nginx.conf.template" - if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -f "$DOKKU_ROOT/$APP/PORT" ]]; then - DOKKU_APP_LISTEN_PORT=$(< "$DOKKU_ROOT/$APP/PORT") - fi - if [[ -z "$DOKKU_APP_LISTEN_IP" ]] && [[ -f "$DOKKU_ROOT/$APP/IP" ]]; then - DOKKU_APP_LISTEN_IP=$(< "$DOKKU_ROOT/$APP/IP") + if [[ -z "$DOKKU_APP_LISTEN_PORT" ]] && [[ -z "$DOKKU_APP_LISTEN_IP" ]]; then + shopt -s nullglob + for DOKKU_APP_IP_FILE in $DOKKU_ROOT/$APP/IP.web.*;do + DOKKU_APP_PORT_FILE=$(echo $DOKKU_APP_IP_FILE | sed -e "s:IP:PORT:g") + DOKKU_APP_LISTENER_IP=$(< $DOKKU_APP_IP_FILE) + DOKKU_APP_LISTENER_PORT=$(< $DOKKU_APP_PORT_FILE) + + DOKKU_APP_LISTENERS+=" " + DOKKU_APP_LISTENERS+="$DOKKU_APP_LISTENER_IP:$DOKKU_APP_LISTENER_PORT" + DOKKU_APP_LISTENERS+=" " + done + shopt -u nullglob fi [[ -f "$DOKKU_ROOT/ENV" ]] && source $DOKKU_ROOT/ENV @@ -98,18 +105,23 @@ EOF if [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then echo "upstream $APP { server $DOKKU_APP_LISTEN_IP:$DOKKU_APP_LISTEN_PORT; }" >> $NGINX_CONF + elif [[ -n "$DOKKU_APP_LISTENERS" ]]; then + echo "upstream $APP { " >> $NGINX_CONF + for listener in $DOKKU_APP_LISTENERS;do + echo " server $listener;" >> $NGINX_CONF + done + echo "}" >> $NGINX_CONF fi dokku_log_info1 "Creating $SCHEME nginx.conf" mv $NGINX_CONF "$DOKKU_ROOT/$APP/nginx.conf" - if [[ -n "$DOKKU_APP_LISTEN_PORT" ]] && [[ -n "$DOKKU_APP_LISTEN_IP" ]]; then + if is_deployed "$APP"; then dokku_log_info1 "Running nginx-pre-reload" pluginhook nginx-pre-reload $APP $DOKKU_APP_LISTEN_PORT $DOKKU_APP_LISTEN_IP dokku_log_verbose "Reloading nginx" - validate_nginx - restart_nginx + validate_nginx && restart_nginx fi echo "# THIS FILE IS GENERATED BY DOKKU - DO NOT EDIT, YOUR CHANGES WILL BE OVERWRITTEN" > $URLS_PATH @@ -124,8 +136,10 @@ EOF dokku_log_info1 "VHOST support disabled, deleting nginx.conf" rm "$DOKKU_ROOT/$APP/nginx.conf" - dokku_log_info1 "VHOST support disabled, reloading nginx after nginx.conf deletion" - restart_nginx + if is_deployed "$APP"; then + dokku_log_info1 "VHOST support disabled, reloading nginx after nginx.conf deletion" + validate_nginx && restart_nginx + fi fi fi ;; diff --git a/plugins/nginx-vhosts/post-deploy b/plugins/nginx-vhosts/post-deploy index c803c980b..3b1940dc3 100755 --- a/plugins/nginx-vhosts/post-deploy +++ b/plugins/nginx-vhosts/post-deploy @@ -2,7 +2,7 @@ set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$(dirname $0)/../common/functions" -APP="$1"; PORT="$2"; IP="$3" +APP="$1" NO_VHOST=$(dokku config:get $APP NO_VHOST || true) if [[ -n "$NO_VHOST" ]]; then @@ -11,4 +11,4 @@ elif [[ ! -f "$DOKKU_ROOT/$APP/VHOST" ]]; then dokku domains:setup $APP fi -dokku nginx:build-config $APP $PORT $IP +dokku nginx:build-config $APP diff --git a/plugins/ps/commands b/plugins/ps/commands index 4fd227b76..92e28d1cc 100755 --- a/plugins/ps/commands +++ b/plugins/ps/commands @@ -1,47 +1,29 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$(dirname $0)/../common/functions" - -release_and_deploy() { - source "$(dirname $0)/../common/functions" - local APP="$1"; local IMAGE="dokku/$APP" - - if [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]]; then - if is_image_buildstep_based "$IMAGE"; then - IMAGE_SOURCE_TYPE="buildstep" - else - IMAGE_SOURCE_TYPE="dockerfile" - fi - - dokku_log_info1 "Releasing $APP ..." - dokku release "$APP" "$IMAGE_SOURCE_TYPE" - dokku_log_info1 "Deploying $APP ..." - dokku deploy "$APP" - dokku_log_info2 "Application deployed:" - dokku urls $APP | sed "s/^/ /" - echo - fi -} +source "$(dirname $0)/functions" case "$1" in ps) [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 verify_app_name "$2" - APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - [[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 + APP="$2"; CONTAINER_IDS=$(get_container_ids $APP) + ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 - docker exec -ti "$CONTAINER_ID" /bin/bash -c "ps auxwww" + for CID in $CONTAINER_IDS; do + docker exec -ti "$CID" /bin/bash -c "ps auxwww" + done ;; ps:start) [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 verify_app_name "$2" - APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - [[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 + APP="$2"; CONTAINER_IDS=( $(get_container_ids $APP) ) + ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 - if [[ "$(docker ps -q --no-trunc| grep -q $CONTAINER_ID; echo $?)" != "0" ]]; then + if [[ "$(docker ps -q --no-trunc| grep -q ${CONTAINER_IDS[0]}; echo $?)" != "0" ]]; then release_and_deploy $APP else echo "App $APP already running" @@ -52,12 +34,15 @@ case "$1" in [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 verify_app_name "$2" - APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - [[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 + APP="$2"; CONTAINER_IDS=$(get_container_ids $APP) + ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 - if [[ "$(docker ps -q --no-trunc| grep -q $CONTAINER_ID; echo $?)" = "0" ]]; then + CONTAINER_IDS_EGREP_PATTERN=$(echo $CONTAINER_IDS | xargs | sed -e "s: :|:g") + if [[ "$(docker ps -q --no-trunc| egrep -q $CONTAINER_IDS_EGREP_PATTERN; echo $?)" = "0" ]]; then echo "Stopping $APP ..." - docker stop $CONTAINER_ID > /dev/null + for CID in $CONTAINER_IDS;do + docker stop $CID > /dev/null + done else echo "App $APP already stopped" fi @@ -73,33 +58,55 @@ case "$1" in ps:rebuildall) shopt -s nullglob - for app in $DOKKU_ROOT/*/CONTAINER; do - APP=$(basename "$(dirname $app)"); - dokku ps:rebuild $APP + for app in $DOKKU_ROOT/*; do + [[ ! -d $app ]] && continue + APP=$(basename $app) + is_deployed $APP && dokku ps:rebuild $APP done + shopt -u nullglob ;; ps:restart) [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 verify_app_name "$2" - APP="$2"; [[ -f "$DOKKU_ROOT/$APP/CONTAINER" ]] && CONTAINER_ID=$(< "$DOKKU_ROOT/$APP/CONTAINER") - [[ -z "$CONTAINER_ID" ]] && echo "App $APP has not been deployed" && exit 0 + APP="$2" + ! (is_deployed $APP) && echo "App $APP has not been deployed" && exit 0 release_and_deploy $APP ;; ps:restartall) shopt -s nullglob - for app in $DOKKU_ROOT/*/CONTAINER; do - APP=$(basename "$(dirname $app)"); + for app in $DOKKU_ROOT/*; do + [[ ! -d $app ]] && continue + APP=$(basename $app) dokku ps:restart $APP done + shopt -u nullglob + ;; + + ps:scale) + [[ -z $2 ]] && echo "Please specify an app to run the command on" && exit 1 + verify_app_name "$2" + + APP="$2"; DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + shift 2 + + generate_scale_file "$APP" + if [[ -z "$@" ]];then + dokku_log_info1 "Scaling for $APP" + dokku_log_info2 "$(< $DOKKU_SCALE_FILE)" + else + set_scale "$APP" "$@" + release_and_deploy "$APP" + fi ;; help | ps:help) cat && cat< List processes running in app container(s) + ps:scale = [=] Set how many processes of a given process to run ps:start Start app container(s) ps:stop Stop app container(s) ps:rebuild Rebuild an app diff --git a/plugins/ps/functions b/plugins/ps/functions new file mode 100755 index 000000000..e92632564 --- /dev/null +++ b/plugins/ps/functions @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$(dirname $0)/../common/functions" + +release_and_deploy() { + source "$(dirname $0)/../common/functions" + local APP="$1"; local IMAGE="dokku/$APP" + + if verify_image "$IMAGE"; then + if is_image_buildstep_based "$IMAGE"; then + IMAGE_SOURCE_TYPE="buildstep" + else + IMAGE_SOURCE_TYPE="dockerfile" + fi + + dokku_log_info1 "Releasing $APP ..." + dokku release "$APP" "$IMAGE_SOURCE_TYPE" + dokku_log_info1 "Deploying $APP ..." + dokku deploy "$APP" + dokku_log_info2 "Application deployed:" + dokku urls $APP | sed "s/^/ /" + echo + fi +} + +generate_scale_file() { + local APP="$1"; local IMAGE="dokku/$APP"; local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + copy_from_image "$IMAGE" "/app/DOKKU_SCALE" "$DOKKU_ROOT/$APP" 2>/dev/null || true + if [[ ! -f $DOKKU_SCALE_FILE ]]; then + dokku_log_info1_quiet "DOKKU_SCALE not found in app image. Defaulting to a single web process" + echo "web=1" >> $DOKKU_SCALE_FILE + + dokku_log_info1_quiet "New DOKKU_SCALE file generated" + while read line || [ -n "$line" ] + do + dokku_log_info2_quiet "$line" + done < "$DOKKU_SCALE_FILE" + fi +} + +set_scale() { + local APP="$1"; local DOKKU_SCALE_FILE="$DOKKU_ROOT/$APP/DOKKU_SCALE" + shift 1 + local SCALE_SETTINGS=("$@") + for procscale in "${SCALE_SETTINGS[@]}"; do + PROC_NAME=${procscale%%=*} + PROC_COUNT=${procscale#*=} + is_number $PROC_COUNT || dokku_log_fail "ps:scale $PROC_COUNT is not a number" + dokku_log_info1_quiet "Scaling $APP:$PROC_NAME to $PROC_COUNT" + if (egrep -q ^${PROC_NAME}= $DOKKU_SCALE_FILE > /dev/null 2>&1);then + sed --in-place "s:^${PROC_NAME}=.*:$PROC_NAME=$PROC_COUNT:g" $DOKKU_SCALE_FILE + else + echo "$PROC_NAME=$PROC_COUNT" >> $DOKKU_SCALE_FILE + fi + done +} + diff --git a/plugins/ps/pre-deploy b/plugins/ps/pre-deploy new file mode 100755 index 000000000..bc4f77c94 --- /dev/null +++ b/plugins/ps/pre-deploy @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$(dirname $0)/../common/functions" +source "$(dirname $0)/functions" + +APP="$1" + +generate_scale_file $APP diff --git a/tests/apps/nodejs-express/Procfile b/tests/apps/nodejs-express/Procfile index ae7366d41..91da256cc 100644 --- a/tests/apps/nodejs-express/Procfile +++ b/tests/apps/nodejs-express/Procfile @@ -1 +1,3 @@ +cron: node worker.js web: node web.js +worker: node worker.js diff --git a/tests/apps/nodejs-express/web.js b/tests/apps/nodejs-express/web.js index 66aa53614..6b8f4d819 100644 --- a/tests/apps/nodejs-express/web.js +++ b/tests/apps/nodejs-express/web.js @@ -9,4 +9,4 @@ app.get('/', function(request, response) { var port = process.env.PORT || 5000; app.listen(port, function() { console.log("Listening on " + port); -}); \ No newline at end of file +}); diff --git a/tests/apps/nodejs-express/worker.js b/tests/apps/nodejs-express/worker.js new file mode 100644 index 000000000..12133530d --- /dev/null +++ b/tests/apps/nodejs-express/worker.js @@ -0,0 +1,6 @@ +function worker() { + console.log('sleeping for 60 seconds'); + setTimeout(worker, 60 * 1000); +} + +worker(); diff --git a/tests/unit/client.bats b/tests/unit/client.bats index 2d5e5e677..786b23524 100644 --- a/tests/unit/client.bats +++ b/tests/unit/client.bats @@ -131,10 +131,12 @@ teardown() { echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" - echo "output: "$output - echo "status: "$status - assert_success + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_success + done } @test "(client) ps:stop" { @@ -143,10 +145,12 @@ teardown() { echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" - echo "output: "$output - echo "status: "$status - assert_failure + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_failure + done } @test "(client) ps:restart" { @@ -155,8 +159,10 @@ teardown() { echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" - echo "output: "$output - echo "status: "$status - assert_success + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_success + done } diff --git a/tests/unit/core_ports.bats b/tests/unit/core_ports.bats index 0bee19df1..437732b79 100644 --- a/tests/unit/core_ports.bats +++ b/tests/unit/core_ports.bats @@ -37,7 +37,7 @@ teardown() { @test "(core) port exposure (with NO_VHOST set)" { deploy_app dokku config:set $TEST_APP NO_VHOST=1 - CONTAINER_ID=$(docker ps --no-trunc| grep dokku/$TEST_APP | grep "start web" | awk '{ print $1 }') + CONTAINER_ID=$(docker ps --no-trunc| grep dokku/$TEST_APP | grep "start web" | awk '{ print $1 }' | head -1) run bash -c "docker port $CONTAINER_ID | sed 's/[0-9.]*://' | egrep '[0-9]*'" echo "output: "$output echo "status: "$status @@ -116,7 +116,7 @@ teardown() { @test "(core) dockerfile port exposure" { deploy_app dockerfile - run bash -c "grep upstream $DOKKU_ROOT/$TEST_APP/nginx.conf | grep 3000" + run bash -c "grep -A1 upstream $DOKKU_ROOT/$TEST_APP/nginx.conf | grep 3000" echo "output: "$output echo "status: "$status assert_success diff --git a/tests/unit/ps-buildstep.bats b/tests/unit/ps-buildstep.bats index 934a57b65..e10534239 100644 --- a/tests/unit/ps-buildstep.bats +++ b/tests/unit/ps-buildstep.bats @@ -3,7 +3,7 @@ load test_helper setup() { - deploy_app + create_app } teardown() { @@ -22,39 +22,104 @@ teardown() { # } @test "(ps) buildstep" { + deploy_app run bash -c "dokku ps:stop $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" - echo "output: "$output - echo "status: "$status - assert_failure + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_failure + done run bash -c "dokku ps:start $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" - echo "output: "$output - echo "status: "$status - assert_success + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_success + done run bash -c "dokku ps:restart $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" - echo "output: "$output - echo "status: "$status - assert_success + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_success + done run bash -c "dokku ps:rebuild $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_success + done +} + +@test "(ps:scale) buildstep" { + run bash -c "dokku ps:scale $TEST_APP web=2 worker=2" echo "output: "$output echo "status: "$status assert_success + + deploy_app + for PROC_TYPE in web worker; do + CIDS="" + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do + CIDS+=$(< $CID_FILE) + CIDS+=" " + done + CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") + run bash -c "docker ps -q --no-trunc | egrep \"$CIDS_PATTERN\" | wc -l | grep 2" + echo "output: "$output + echo "status: "$status + assert_success + done + + run bash -c "dokku ps:scale $TEST_APP web=1 worker=1" + echo "output: "$output + echo "status: "$status + assert_success + for PROC_TYPE in web worker; do + CIDS="" + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do + CIDS+=$(< $CID_FILE) + CIDS+=" " + done + CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") + run bash -c "docker ps -q --no-trunc | egrep \"$CIDS_PATTERN\" | wc -l | grep 1" + echo "output: "$output + echo "status: "$status + assert_success + done + + run bash -c "dokku ps:scale $TEST_APP web=0 worker=0" + echo "output: "$output + echo "status: "$status + assert_success + for PROC_TYPE in web worker; do + CIDS="" + shopt -s nullglob + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.$PROC_TYPE.*; do + CIDS+=$(< $CID_FILE) + CIDS+=" "1 + done + shopt -u nullglob + run bash -c "[[ -z \"$CIDS\" ]]" + echo "output: "$output + echo "status: "$status + assert_success + done } diff --git a/tests/unit/ps-dockerfile.bats b/tests/unit/ps-dockerfile.bats index de4fea29e..75b9ba1d7 100644 --- a/tests/unit/ps-dockerfile.bats +++ b/tests/unit/ps-dockerfile.bats @@ -3,7 +3,7 @@ load test_helper setup() { - deploy_app dockerfile + create_app } teardown() { @@ -22,38 +22,96 @@ teardown() { # } @test "(ps) dockerfile" { + deploy_app dockerfile run bash -c "dokku ps:stop $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" - echo "output: "$output - echo "status: "$status - assert_failure + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_failure + done run bash -c "dokku ps:start $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" - echo "output: "$output - echo "status: "$status - assert_success + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_success + done run bash -c "dokku ps:restart $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" - echo "output: "$output - echo "status: "$status - assert_success + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_success + done run bash -c "dokku ps:rebuild $TEST_APP" echo "output: "$output echo "status: "$status assert_success - run bash -c "docker ps -q --no-trunc | grep -q $(< $DOKKU_ROOT/$TEST_APP/CONTAINER)" + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.*; do + run bash -c "docker ps -q --no-trunc | grep -q $(< $CID_FILE)" + echo "output: "$output + echo "status: "$status + assert_success + done +} + +@test "(ps:scale) dockerfile" { + run bash -c "dokku ps:scale $TEST_APP web=2" + echo "output: "$output + echo "status: "$status + assert_success + + deploy_app dockerfile + CIDS="" + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + CIDS+=$(< $CID_FILE) + CIDS+=" " + done + CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") + run bash -c "docker ps -q --no-trunc | egrep \"$CIDS_PATTERN\" | wc -l | grep 2" + echo "output: "$output + echo "status: "$status + assert_success + + run bash -c "dokku ps:scale $TEST_APP web=1" + echo "output: "$output + echo "status: "$status + assert_success + CIDS="" + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + CIDS+=$(< $CID_FILE) + CIDS+=" " + done + CIDS_PATTERN=$(echo $CIDS | sed -e "s: :|:g") + run bash -c "docker ps -q --no-trunc | egrep \"$CIDS_PATTERN\" | wc -l | grep 1" + echo "output: "$output + echo "status: "$status + assert_success + + run bash -c "dokku ps:scale $TEST_APP web=0" + echo "output: "$output + echo "status: "$status + assert_success + CIDS="" + shopt -s nullglob + for CID_FILE in $DOKKU_ROOT/$TEST_APP/CONTAINER.web.*; do + CIDS+=$(< $CID_FILE) + CIDS+=" " + done + run bash -c "[[ -z \"$CIDS\" ]]" echo "output: "$output echo "status: "$status assert_success diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 66df4fdfc..1a8faa162 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -101,7 +101,7 @@ add_domain() { deploy_app() { APP_TYPE="$1"; APP_TYPE=${APP_TYPE:="nodejs-express"} GIT_REMOTE="$2"; GIT_REMOTE=${GIT_REMOTE:="dokku@dokku.me:$TEST_APP"} - TMP=$(mktemp -d -t "$TARGET.XXXXX") + TMP=$(mktemp -d -t "dokku.me.XXXXX") rmdir $TMP && cp -r ./tests/apps/$APP_TYPE $TMP cd $TMP git init @@ -117,7 +117,7 @@ deploy_app() { } setup_client_repo() { - TMP=$(mktemp -d -t "$TARGET.XXXXX") + TMP=$(mktemp -d -t "dokku.me.XXXXX") rmdir $TMP && cp -r ./tests/apps/nodejs-express $TMP cd $TMP git init