diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bad8e4..e37a1d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 1.3.1 (2016-04-21) + +- A lot of refactoring to bring interactive shell experience to Windows (via winpty, which is a requirement now on Windows) + - `dsh update boot2docker` will now ask for a confirmation + - `dsh exec` (`dsh run`) can now run interactive commands (try `dsh exec top`) + - `dsh bash` now longer relies on `vagrant ssh -c` and thus launches much faster + - `dsh mysql-import` now disaplays a progress via `pv` (same as on Mac/Linux) +- Fixed `dsh bash`regression from v1.3.0 on Mac/Linux +- Documentation fixes and updates + +A full update is recommended. Please follow the updates instructions: +https://github.com/blinkreaction/drude#updates + + ## 1.3.0 (2016-04-15) - Implement dsh drupal shortcut for Drupal Console commands diff --git a/VERSION b/VERSION index 589268e..6261a05 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.0 \ No newline at end of file +1.3.1 \ No newline at end of file diff --git a/bin/dsh b/bin/dsh index e78ca8e..f67e2e6 100755 --- a/bin/dsh +++ b/bin/dsh @@ -1,6 +1,6 @@ #!/bin/bash -DSH_VERSION=1.12.11 +DSH_VERSION=1.13.0 # Console colors red='\033[0;31m' @@ -30,36 +30,6 @@ echo-red () { echo -e "${red}$1${NC}"; } echo-green () { echo -e "${green}$1${NC}"; } echo-yellow () { echo -e "${yellow}$1${NC}"; } -# Check whether shell is interactive -# Otherwise we are running in a non-interactive script environment -is_tty () -{ - # Override option via $DRUDE_IS_TTY (true|false) env variable. - if [[ $DRUDE_IS_TTY == 'true' ]]; then - return 0; - fi - if [[ $DRUDE_IS_TTY == 'false' ]]; then - return 1; - fi - - # disable interactive shell on windows for now - # known Docker issue: https://github.com/docker/docker/issues/12469 - res=$(uname | grep 'CYGWIN_NT') - if [[ "$res" != '' ]]; then - return 1; - fi - - [[ "$(/usr/bin/tty || true)" != "not a tty" ]] -} - -testing_warn () -{ - if ! is_tty ; then return; fi - if [[ ! $B2D_BRANCH == 'master' ]] || [[ ! $DRUDE_BRANCH == 'master' ]]; then - echo-yellow "dsh: boot2docker - ${B2D_BRANCH}, drude - ${DRUDE_BRANCH}" - fi -} - # Exits dsh if previous command exited with non-zero code if_failed () { @@ -146,20 +116,12 @@ clean_string () get_mysql_connect () { # Run drush forcing tty to false to avoid colored output string from drush. - echo "$(DRUDE_IS_TTY=false _run drush sql-connect)" + echo "$(DRUDE_IS_TTY=0 _run drush sql-connect)" } # Use this function for every docker-compose invocation. docker_compose () { - # TODO: remove this - # if is_windows; then - # # Trim CR(\r) from the output, otherwise there will be issues passing it to the docker binary on Windows. - # docker-compose $* | tr -d '\r' - # else - # docker-compose $* - # fi - docker-compose $* } @@ -253,17 +215,15 @@ is_vagrant () } # check that docker-compose binary exists -is_docker_compose () +check_docker_compose () { binary_found 'docker-compose'; - return $? } # check that docker binary exists -is_docker () +check_docker () { binary_found 'docker'; - return $? } is_docker_running () @@ -297,10 +257,38 @@ is_docker_running () return $? } +# Check whether shell is interactive +# Otherwise we are running in a non-interactive script environment +is_tty () +{ + # Override option via $DRUDE_IS_TTY (true|false) env variable. + if [[ $DRUDE_IS_TTY == 1 ]]; then + return 0; + fi + if [[ $DRUDE_IS_TTY == 0 ]]; then + return 1; + fi + + [[ "$(/usr/bin/tty || true)" != "not a tty" ]] +} + +#---------------------------- Other helper functions ------------------------------- + +testing_warn () +{ + if ! is_tty; then return; fi + if [[ ! $B2D_BRANCH == 'master' ]] || [[ ! $DRUDE_BRANCH == 'master' ]]; then + echo-yellow "dsh: boot2docker - ${B2D_BRANCH}, drude - ${DRUDE_BRANCH}" + fi +} + #---------------------------- Control functions ------------------------------- check_docker_running () { + check_docker + check_docker_compose + check_yml if ! is_docker_running; then echo-red "dsh: Unable to connect to the docker daemon"; exit 1 @@ -341,12 +329,19 @@ is_yml_absent () return 1 } +check_yml() +{ + if is_yml_absent; then + exit 2 + fi +} + # Yes/no confirmation dialog with an optional message # @param $1 confirmation message _confirm () { - # Skip checks if not a tty - if ! is_tty ; then return 0; fi + # Skip checks if not running interactively (not a tty or not on Windows) + if ! is_tty; then return 0; fi while true; do read -p "$1 [y/n]: " answer @@ -533,7 +528,7 @@ bash_comp_words () # Start containers up () { - if is_yml_absent ; then return 2; fi + check_yml if is_vagrant ; then if ! is_docker_running; then echo-green "dsh: Starting vagrant vm..." @@ -542,10 +537,10 @@ up () else started=0 fi - if [ $started -eq 0 ] && is_docker_compose ; then + if [ $started -eq 0 ] ; then _start_containers fi - elif ( is_boot2docker || is_linux ) && is_docker_compose ; then + elif ( is_boot2docker || is_linux ) ; then _start_containers fi } @@ -554,17 +549,13 @@ up () down () { check_docker_running - if is_yml_absent ; then return 2; fi - if is_docker_compose ; then - _stop_containers $@ - fi + _stop_containers $@ } # Stop Vagrant box halt () { check_docker_running - if is_yml_absent ; then return 2; fi if is_vagrant ; then vagrant halt elif ( is_boot2docker || is_linux ) ; then @@ -576,22 +567,22 @@ halt () # @param $1 container_name restart () { - if is_yml_absent ; then return 2; fi + check_docker_running _restart_containers $* } # Restart Vagrant and start containers again reload () { - if is_yml_absent ; then return 2; fi + check_yml if is_vagrant ; then echo-green "dsh: Restarting vagrant vm..." vagrant reload started=$? - if [ $started -eq 0 ] && is_docker_compose ; then + if [ $started -eq 0 ] ; then _start_containers fi - elif ( is_boot2docker || is_linux ) && is_docker_compose ; then + elif ( is_boot2docker || is_linux ) ; then _start_containers fi } @@ -605,7 +596,6 @@ status () if [[ "$1" == "-a" ]] || [[ "$1" == "--all" ]]; then docker ps else - if is_yml_absent ; then return 2; fi cd $(yml_get_path) docker_compose ps fi @@ -840,7 +830,7 @@ update_images () update_boot2docker () { testing_warn - if ! is_tty ; then + if ! is_tty; then echo "'update' is not allowed in a non-interactive environment." return 1 fi @@ -940,91 +930,97 @@ update_dsh () exit } -# Start interactive bash in container -# @param $1 container name (defaults to cli) +# Start an interactive bash session in a container +# @param $1 container name _bash () { - if is_yml_absent ; then return 2; fi - cd $(yml_get_path) - # Interactive shell requires a tty. # On Windows we assume we run interactively via winpty (console.exe). - if ! (is_tty || is_windows) ; then + if ! is_tty; then echo "Interactive bash console in a non-interactive enveronment!? Nope, won't happen." return 1 fi - if is_docker && is_docker_compose ; then - check_docker_running - local container_name - if [[ "$1" == "" ]]; then - container_name='cli' - else - container_name="$1" - fi - local container_id - container_id=$(get_container_id $container_name) - if is_windows && is_binary_found console; then - # Workaround - run docker via winpty (console.exe) to get a tty console in cygwin. - console docker exec -it $container_id bash -i - else - # Drop /cygdrive prefix if the working directory is opened as /cygdrive//.. instead of /.. - cwd=$(pwd); cwd=${cwd#/cygdrive} - command="cd $cwd && docker exec -it $container_id bash -i"; - vagrant ssh -c "$command" - fi - fi + # Pass container name to _run + CONTAINER_NAME=$1 _run bash -i } -# Run a command in the cli container in the project root +# Run a command in the cli container changing dir to the same folder # @param $* command with it's params to run -run () +_run () { - if is_docker && is_docker_compose ; then - check_docker_running - cd $(yml_get_path) - container_id=$(get_container_id cli) + check_docker_running - if is_tty ; then - # need to use $* here to finally resolve slash-escaped string if any. - # passing $@ will not work properly if $@ contains slash espaped string from drush command - docker exec -it $container_id bash -ic "$*" + if [[ "$CONTAINER_NAME" == "" ]]; then CONTAINER_NAME='cli'; fi + container_id=$(get_container_id $CONTAINER_NAME) + + # 1) $winpty_cmd + local winpty_cmd + # Running docker exec interactively on Windows requires workarounds + if is_windows; then + # Workaround - run docker exec via winpty (console.exe) to get a tty console in cygwin. + if is_binary_found 'console'; then + winpty_cmd='console' else - docker exec $container_id bash -c "$*" + echo-red 'Winpty (console.exe) binary missing.' + echo 'Run "dsh install prerequisites" to install it.' + exit 1 fi fi -} -# Run a command in the cli container changing dir to the same folder -# @param $* command with it's params to run -_run () -{ - #get client window width for interactive sessions - local COLS='' - if is_tty ; then - COLS=$(tput cols) - fi + # 2) cmd + local cmd - if is_yml_absent ; then return 2; fi - if is_docker && is_docker_compose ; then - local path=$(get_current_relative_path) - local cd='' + local cdir + # Only chdir to the same dir in cli container + # RUN_NO_CDIR can be used to override this (used in mysql_import) + if [[ $CONTAINER_NAME == "cli" ]] && [[ $RUN_NO_CDIR != 1 ]]; then + local path + path=$(get_current_relative_path) if [[ "$path" != "$(pwd)" ]] && [[ "$path" != "" ]] ; then - # if we're not outside project folder - cd="cd $path &&" + # we're inside docroot + cdir="cd $path &&" fi if [[ "$path" == "" ]] ; then - # if we're on project folder level (outside docroot) - cd="cd docroot &&" + # we're on project folder level (outside docroot) + cdir="cd docroot &&" + fi + fi + + local columns + if is_tty ; then + columns="export COLUMNS=$(tput cols) &&" + fi + + cmd="$cdir $columns" + + # 3) convert array of parameters into escaped string + # Escape spaces that are "spaces" and not parameter delimeters (i.e. param1 param2\ with\ spaces param3) + if [[ $2 != "" ]]; then + cmd="$cmd "$(printf " %q" "$@") + # Do not escape spaces if there is only one parameter (e.g. dsh run "ls -la | grep txt") + else + cmd="$cmd $@" + fi + + # 4) execute + if is_tty ; then + # interactive + if [[ $1 == "" ]]; then + $winpty_cmd docker exec -it $container_id bash -i + else + $winpty_cmd docker exec -it $container_id bash -ic "$cmd" fi - run "$cd export COLUMNS=$COLS && $@" + else + # non-interactive + docker exec $container_id bash -c "$cmd" fi } # start interactive mysql shell mysql () { - if is_yml_absent ; then return 2; fi + check_docker_running if ! get_drush_path; then return; fi _run $(get_mysql_connect)" -A" # -A option to speed up mysql load } @@ -1033,7 +1029,7 @@ mysql () # @param $1 filename of backup file. Should be inside project root mysql_import () { - if is_yml_absent ; then return 2; fi + check_docker_running if ! get_drush_path; then return; fi local confirm=1 @@ -1060,9 +1056,7 @@ mysql_import () _confirm "[!] This will drop the existing database. Continue?" fi - check_docker_running - - _run drush sql-drop -y + #_run drush sql-drop -y local sql_connect sql_connect=$(get_mysql_connect) echo "Importing $filename into the database..." @@ -1072,26 +1066,26 @@ mysql_import () # Check if we have a gzipped dump and treat it differently. if [[ $filename == *.gz ]]; then file_size=$(gzip -l $1 | sed -n 2p | awk '{print $2}') - run "zcat ./$pathdiff/$filename | pv --size $file_size | "$sql_connect + RUN_NO_CDIR=1 _run "zcat ./$pathdiff/$filename | pv --size $file_size | $sql_connect" else - run "pv ./$pathdiff/$filename | "$sql_connect + RUN_NO_CDIR=1 _run "pv ./$pathdiff/$filename | $sql_connect" fi else # Check if we have a gzipped dump and treat it differently. if [[ $filename == *.gz ]]; then - run "zcat ./$pathdiff/$filename | "$sql_connect + RUN_NO_CDIR=1 _run "zcat ./$pathdiff/$filename | $sql_connect" else - run "cat ./$pathdiff/$filename | "$sql_connect + RUN_NO_CDIR=1 _run "cat ./$pathdiff/$filename | $sql_connect" fi fi # Check if import succeded or not and print results. if [ $? -eq 0 ]; then echo-green "dsh: mysql-import finished"; - _notify "Dsh" "Mysql import finished" + if is_tty; then _notify "Dsh" "Mysql import finished"; fi else echo-red "dsh: mysql-import failed"; - _notify "Dsh" "Mysql import failed!" + if is_tty; then _notify "Dsh" "Mysql import failed!"; fi fi } @@ -1099,7 +1093,7 @@ mysql_import () # @param $1 type of cache to clean (all, css-js, ...) clear_cache () { - if is_yml_absent ; then return 2; fi + check_docker_running #if ! get_drush_path; then return; fi local type='all' if [[ ! "$1" == "" ]]; then @@ -1113,7 +1107,7 @@ clear_cache () # @param $* arguments and params passed to Behat behat () { - if is_yml_absent ; then return 2; fi + check_docker_running local params='' local path='tests/behat' for i in "$@"; do @@ -1170,11 +1164,11 @@ exec_url () # @param $1 $2... container names remove () { + check_docker_running if [[ $1 == "" ]]; then _confirm "[!] This will delete all project containers including the DB one. You will have to re-import DB."; fi - if is_yml_absent ; then return 2; fi if is_vagrant ; then if ! is_docker_running; then echo-green "dsh: Starting vagrant vm..." @@ -1184,10 +1178,10 @@ remove () started=0 fi - if [ $started -eq 0 ] && is_docker_compose ; then + if [ $started -eq 0 ] ; then _remove_containers $* fi - elif ( is_boot2docker || is_linux ) && is_docker_compose ; then + elif ( is_boot2docker || is_linux ) ; then _remove_containers $* fi } @@ -1286,7 +1280,7 @@ case $1 in if [[ "$1" == "prerequisites" ]]; then install_prerequisites elif [[ "$1" == "boot2docker" ]]; then - update_boot2docker + update_boot2docker $* elif [[ "$1" == "images" ]]; then update_images elif [[ "$1" == "dsh" ]]; then @@ -1304,11 +1298,11 @@ case $1 in ;; exec) shift - _run "$*" + _run "$@" ;; run) shift - _run "$*" + _run "$@" ;; mysql) mysql @@ -1330,9 +1324,7 @@ case $1 in if [[ $1 == "" ]]; then _run drush else - # slash escape space-delimited parameters for safe passing between functions - cmd=$(printf " %q" "$@") - _run drush $cmd + _run drush "$@" fi ;; drupal) @@ -1340,9 +1332,7 @@ case $1 in if [[ "$1" == "" ]]; then _run drupal else - # slash escape space-delimited parameters for safe passing between functions - cmd=$(printf " %q" "$@") - _run drupal $cmd + _run drupal "$@" fi ;; behat) diff --git a/docs/directory-structure.md b/docs/directory-structure.md index c6795e3..77ce8a9 100644 --- a/docs/directory-structure.md +++ b/docs/directory-structure.md @@ -1,13 +1,13 @@ # Directory structure -Drude enforces a directory structure where you have an arbitrary located and named `projects` folder. -All your projects go into subfolders under the `projects` folder. +Drude enforces a directory structure where all your projects go into subfolders under the main `` folder. +The `` folder can be arbitrarily located and named. It is best to put it on the fastest drive you have. -`dsh` will install the necessary boot2docker-vagrant VM files (`Vagrantfile` and `vagrant.yml`) into the `projects` folder. +`dsh` will install the necessary boot2docker-vagrant VM files (`Vagrantfile` and `vagrant.yml`) into the `` folder. ``` + ... - - projects + - |--- drupal-site | docker-compose.yml | docroot diff --git a/docs/drude-env-setup.md b/docs/drude-env-setup.md index 489e7bc..bbc1505 100644 --- a/docs/drude-env-setup.md +++ b/docs/drude-env-setup.md @@ -54,7 +54,7 @@ Babun should be installed and run **as a regular user (do not use admin command dsh install boot2docker ``` - On Mac and Widnows you should see two files created in the `projects` folder: + On Mac and Windows you should see two files created in the `projects` folder: ``` Vagrantfile