deploy 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. #!/usr/bin/env bash
  2. #
  3. # deploy(1) - Minimalistic shell script to deploy Git repositories.
  4. # Released under the MIT License.
  5. #
  6. # https://github.com/visionmedia/deploy
  7. #
  8. VERSION="0.6.0"
  9. CONFIG=./deploy.conf
  10. LOG=/tmp/pm2-deploy-${USER:-default}.log
  11. FORCE=0
  12. REF=
  13. ENV=
  14. #
  15. # Read PIPED JSON
  16. #
  17. read conf
  18. #
  19. # Output usage information.
  20. #
  21. usage() {
  22. cat <<-EOF
  23. Usage: deploy [options] <env> [command]
  24. Options:
  25. -C, --chdir <path> change the working directory to <path>
  26. -c, --config <path> set config path. defaults to ./deploy.conf
  27. -V, --version output program version
  28. -h, --help output help information
  29. -f, --force skip local change checking
  30. Commands:
  31. setup run remote setup commands
  32. revert [n] revert to [n]th last deployment or 1
  33. config [key] output config file or [key]
  34. curr[ent] output current release commit
  35. prev[ious] output previous release commit
  36. exec|run <cmd> execute the given <cmd>
  37. list list previous deploy commits
  38. ref [ref] deploy [ref]
  39. EOF
  40. }
  41. #
  42. # Abort with <msg>
  43. #
  44. abort() {
  45. echo
  46. echo " $@" 1>&2
  47. echo
  48. exit 1
  49. }
  50. #
  51. # Log <msg>.
  52. #
  53. log() {
  54. echo " ○ $@"
  55. }
  56. #
  57. # Get config value by <key>.
  58. #
  59. config_get() {
  60. local key=$1
  61. echo $(expr "$conf" : '.*"'$key'":"\([^"]*\)"')
  62. }
  63. #
  64. # Output version.
  65. #
  66. version() {
  67. echo $VERSION
  68. }
  69. #
  70. # Return the ssh command to run.
  71. #
  72. ssh_command() {
  73. local user="`config_get user`"
  74. if test -n "$user"; then
  75. local url="$user@`config_get host`"
  76. else
  77. local url="`config_get host`"
  78. fi
  79. local unexpanded_key="`config_get key`"
  80. local key="${unexpanded_key/#\~/$HOME}"
  81. local forward_agent="`config_get forward-agent`"
  82. local port="`config_get port`"
  83. local needs_tty="`config_get needs_tty`"
  84. local ssh_options="`config_get ssh_options`"
  85. test -n "$forward_agent" && local agent="-A"
  86. test -n "$key" && local identity="-i $key"
  87. test -n "$port" && local port="-p $port"
  88. test -n "$needs_tty" && local tty="-t"
  89. test -n "ssh_options" && local ssh_opts="$ssh_options"
  90. echo "ssh $ssh_opts $tty $agent $port $identity $url"
  91. }
  92. #
  93. # Run the given remote <cmd>.
  94. #
  95. runRemote() {
  96. local shell="`ssh_command`"
  97. echo $shell "\"$@\"" >> $LOG
  98. $shell $@
  99. }
  100. #
  101. # Run the given local <cmd>.
  102. #
  103. runLocal() {
  104. echo "\"$@\"" >> $LOG
  105. /usr/bin/env bash -c "$*"
  106. }
  107. #
  108. # Run the given <cmd> either locally or remotely
  109. #
  110. run() {
  111. local host="`config_get host`"
  112. if [[ $host == localhost ]]
  113. then
  114. runLocal $@
  115. else
  116. runRemote $@
  117. fi
  118. }
  119. #
  120. # Output config or [key].
  121. #
  122. config() {
  123. echo $(expr "$conf" : '.*"$key":"\([^"]*\)"')
  124. }
  125. #
  126. # Execute hook <name> relative to the path configured.
  127. #
  128. hook() {
  129. test -n "$1" || abort hook name required
  130. local hook=$1
  131. local path=`config_get path`
  132. local cmd=`config_get $hook`
  133. if test -n "$cmd"; then
  134. log "executing $hook \`$cmd\`"
  135. run "cd $path/current; \
  136. SHARED=\"$path/shared\" \
  137. $cmd 2>&1 | tee -a $LOG; \
  138. exit \${PIPESTATUS[0]}"
  139. test $? -eq 0
  140. else
  141. log hook $hook
  142. fi
  143. }
  144. #
  145. # Pre Setup hook runs on the host before the actual setup runs
  146. # multiple commands or a script
  147. #
  148. hook_pre_setup() {
  149. local cmd=`config_get pre-setup`
  150. if test -n "$cmd"; then
  151. local is_script=($cmd)
  152. if [ -f "${is_script[0]}" ]; then
  153. log "executing pre-setup script \`$cmd\`"
  154. local shell="`ssh_command`"
  155. runLocal "$shell 'bash -s' -- < $cmd"
  156. else
  157. log "executing pre-setup \`$cmd\`"
  158. run "$cmd"
  159. fi
  160. test $? -eq 0
  161. else
  162. log hook pre-setup
  163. fi
  164. }
  165. #
  166. # Run setup.
  167. #
  168. setup() {
  169. local path=`config_get path`
  170. local repo=`config_get repo`
  171. local ref=`config_get ref`
  172. local fetch=`config_get fetch`
  173. local branch=${ref#*/}
  174. hook_pre_setup || abort pre-setup hook failed
  175. run "mkdir -p $path/{shared/{logs,pids},source}"
  176. test $? -eq 0 || abort setup paths failed
  177. log running setup
  178. log cloning $repo
  179. if test "$fetch" != "fast"; then
  180. log "full fetch"
  181. run "git clone --branch $branch $repo $path/source"
  182. else
  183. log "fast fetch"
  184. run "git clone --depth=5 --branch $branch $repo $path/source"
  185. fi
  186. test $? -eq 0 || abort failed to clone
  187. run "ln -sfn $path/source $path/current"
  188. test $? -eq 0 || abort symlink failed
  189. hook post-setup || abort post-setup hook failed
  190. log setup complete
  191. }
  192. #
  193. # Deploy [ref].
  194. #
  195. deploy() {
  196. local ref=$1
  197. local branch=$2
  198. if test -z "$branch"; then
  199. branch=${ref#*/}
  200. fi
  201. local path=`config_get path`
  202. local fetch=`config_get fetch`
  203. log "deploying ${ref}"
  204. # 1- Execute local commands
  205. log executing pre-deploy-local
  206. local pdl=`config_get pre-deploy-local`
  207. runLocal $pdl || abort pre-deploy-local hook failed
  208. # 2- Execute pre deploy commands on remote server
  209. hook pre-deploy || abort pre-deploy hook failed
  210. # 3- Fetch updates
  211. log fetching updates
  212. if test "$fetch" != "fast"; then
  213. log "full fetch"
  214. run "cd $path/source && git fetch --all --tags"
  215. else
  216. log "fast fetch"
  217. run "cd $path/source && git fetch --depth=5 --all --tags"
  218. fi
  219. test $? -eq 0 || abort fetch failed
  220. # 4- If no reference retrieve shorthand name for the remote repo
  221. if test -z "$ref"; then
  222. log fetching latest tag
  223. ref=`run "cd $path/source && git for-each-ref \
  224. --sort=-*authordate \
  225. --format='%(refname)' \
  226. --count=1 | cut -d '/' -f 3"`
  227. test $? -eq 0 || abort failed to determine latest tag
  228. fi
  229. # 5- Reset to ref
  230. log resetting HEAD to $ref
  231. run "cd $path/source && git reset --hard $ref"
  232. test $? -eq 0 || abort git reset failed
  233. # 6- Link current
  234. run "ln -sfn $path/source $path/current"
  235. test $? -eq 0 || abort symlink failed
  236. # deploy log
  237. run "cd $path/source && \
  238. echo \`git rev-parse HEAD\` \
  239. >> $path/.deploys"
  240. test $? -eq 0 || abort deploy log append failed
  241. hook post-deploy || abort post-deploy hook failed
  242. # done
  243. log successfully deployed $ref
  244. }
  245. #
  246. # Get current commit.
  247. #
  248. current_commit() {
  249. local path=`config_get path`
  250. run "cd $path/source && \
  251. git rev-parse --short HEAD"
  252. }
  253. #
  254. # Get <n>th deploy commit.
  255. #
  256. nth_deploy_commit() {
  257. local n=$1
  258. local path=`config_get path`
  259. run "cat $path/.deploys | tail -n $n | head -n 1 | cut -d ' ' -f 1"
  260. }
  261. #
  262. # List deploys.
  263. #
  264. list_deploys() {
  265. local path=`config_get path`
  266. run "tac $path/.deploys | awk '{printf \"%d\t%s\n\", NR-1, \$0}'"
  267. }
  268. #
  269. # Revert to the <n>th last deployment.
  270. #
  271. revert_to() {
  272. local n=$1
  273. log "reverting $n deploy(s)"
  274. local commit=`nth_deploy_commit $((n + 1))`
  275. deploy "$commit"
  276. }
  277. #
  278. # Ensure all changes are committed and pushed before deploying.
  279. #
  280. check_for_local_changes() {
  281. local path=`config_get path`
  282. if [ $FORCE -eq 1 ]
  283. then
  284. return
  285. fi
  286. git --no-pager diff --exit-code --quiet || abort "commit or stash your changes before deploying"
  287. git --no-pager diff --exit-code --quiet --cached || abort "commit your staged changes before deploying"
  288. [ -z "`git rev-list @{upstream}.. -n 1`" ] || abort "push your changes before deploying"
  289. }
  290. # parse argv
  291. while test $# -ne 0; do
  292. arg=$1; shift
  293. case $arg in
  294. -h|--help) usage; exit ;;
  295. -V|--version) version; exit ;;
  296. -C|--chdir) log cd $1; cd $1; shift ;;
  297. -F|--force) FORCE=1 ;;
  298. run|exec) run "cd `config_get path`/current && $@"; exit ;;
  299. console) console; exit ;;
  300. curr|current) current_commit; exit ;;
  301. prev|previous) nth_deploy_commit 2; exit ;;
  302. revert) revert_to ${1-1}; exit ;;
  303. setup) setup $@; exit ;;
  304. list) list_deploys; exit ;;
  305. config) config $@; exit ;;
  306. ref) REF=$1; BRANCH=$2 ;;
  307. esac
  308. done
  309. check_for_local_changes
  310. echo
  311. # deploy
  312. deploy "${REF:-`config_get ref`}" "${BRANCH}"