diff --git a/lib/bootleg/tasks/build/remote.exs b/lib/bootleg/tasks/build/remote.exs index cebea8a..d3975d4 100644 --- a/lib/bootleg/tasks/build/remote.exs +++ b/lib/bootleg/tasks/build/remote.exs @@ -81,8 +81,10 @@ task :remote_generate_release do UI.info("⚡ Creating Tarball...") + time_stamp = System.os_time(:millisecond) remote :build, cd: source_path do - "tar -czf #{app_name}.tar.gz #{app_name}/" + "mv #{app_name}/ #{time_stamp}/" + "tar -czf #{time_stamp}.tar.gz #{time_stamp}/" end end @@ -128,24 +130,21 @@ end task :download_release do mix_env = config({:mix_env, "prod"}) source_path = config({:ex_path, ""}) - app_name = Config.app() - app_version = Config.version() remote_path = Path.join( source_path, - "_build/#{mix_env}/rel/#{app_name}.tar.gz" + "_build/#{mix_env}/rel/*.tar.gz" ) local_archive_folder = "#{File.cwd!()}/releases" - local_path = Path.join(local_archive_folder, "#{app_version}.tar.gz") UI.info("⚡ Downloading release archive") File.mkdir_p!(local_archive_folder) - download(:build, remote_path, local_path) + download(:build, remote_path, local_archive_folder) - UI.info("⚡ Saved: releases/#{app_version}.tar.gz") + UI.info("⚡ Saved in releases/") end task :reset_remote do diff --git a/lib/bootleg/tasks/deploy.exs b/lib/bootleg/tasks/deploy.exs index cbef836..87cd8c3 100644 --- a/lib/bootleg/tasks/deploy.exs +++ b/lib/bootleg/tasks/deploy.exs @@ -30,24 +30,32 @@ end task :upload_release do remote_path = "#{Config.app()}.tar.gz" local_archive_folder = "#{File.cwd!()}/releases" - local_path = Path.join(local_archive_folder, "#{Config.version()}.tar.gz") - UI.info("Uploading release archive") + tar_ball = "ls -t #{local_archive_folder} | head -1" + |> System.shell() + |> elem(0) + |> String.trim_trailing() + local_path = Path.join(local_archive_folder, tar_ball) + UI.info("⚡ Uploading release archive #{tar_ball}") upload(:app, local_path, remote_path) end -# credo:disable-for-next-line Credo.Check.Design.TagTODO -# TODO: Prepare for Rollback -# * create releases/ directory -# * fetch timestamp -# * unpack into releases/[timestamp] -# * create revisions.log file -# * create shared/ directory -# task :unpack_release do - remote_path = "#{Config.app()}.tar.gz" - UI.info("Unpacking release archive: #{remote_path}") + app = Config.app() + remote_path = "#{app}.tar.gz" + keep_releases = Config.get_role(:app).options[:keep_releases] + UI.info("⚡ Unpacking release archive: #{remote_path}") remote :app do - "tar -zxf #{remote_path}" + "mkdir -p releases/" + "tar -zxf #{remote_path} -C releases/" + "ls -td releases/*/ | head -1 | xargs -I{} ln -sfn {} current" + "rm #{remote_path}" + "touch --reference current/bin/#{app} current/bin/#{app}" + end + + if keep_releases do + remote :app do + "ls -1dt releases/*/ | tail -n +#{keep_releases + 1} | xargs -I{} rm -rf {}" + end end end diff --git a/lib/bootleg/tasks/ping.exs b/lib/bootleg/tasks/ping.exs index 2fa1624..cbedecf 100644 --- a/lib/bootleg/tasks/ping.exs +++ b/lib/bootleg/tasks/ping.exs @@ -3,7 +3,7 @@ use Bootleg.DSL task :ping do remote :app do - "bin/#{Config.app()} ping" + "current/bin/#{Config.app()} ping" end :ok diff --git a/lib/bootleg/tasks/restart.exs b/lib/bootleg/tasks/restart.exs index 50907b6..5e7f9d9 100644 --- a/lib/bootleg/tasks/restart.exs +++ b/lib/bootleg/tasks/restart.exs @@ -3,7 +3,7 @@ use Bootleg.DSL task :restart do remote :app do - "bin/#{Config.app()} restart" + "current/bin/#{Config.app()} restart" end UI.info("#{Config.app()} restarted") diff --git a/lib/bootleg/tasks/rollback.exs b/lib/bootleg/tasks/rollback.exs new file mode 100644 index 0000000..0f81f2b --- /dev/null +++ b/lib/bootleg/tasks/rollback.exs @@ -0,0 +1,14 @@ +alias Bootleg.{Config, UI} +use Bootleg.DSL + +task :rollback do + app = Config.app() + + remote :app do + "test $(ls -1d releases/*/ 2>/dev/null | wc -l) -ge 2 || (echo 'No previous release to roll back to' && exit 1)" + "previous=$(ls -1dt releases/*/ | sed -n '2p') && ln -sfn $previous current" + "current/bin/#{app} restart" + end + + UI.info("#{app} rolled back") +end diff --git a/lib/bootleg/tasks/start.exs b/lib/bootleg/tasks/start.exs index 8f66389..07078e2 100644 --- a/lib/bootleg/tasks/start.exs +++ b/lib/bootleg/tasks/start.exs @@ -3,7 +3,7 @@ use Bootleg.DSL task :start do remote :app do - "bin/#{Config.app()} start" + "current/bin/#{Config.app()} start" end UI.info("#{Config.app()} started") diff --git a/lib/bootleg/tasks/stop.exs b/lib/bootleg/tasks/stop.exs index e2a0785..22d7f42 100644 --- a/lib/bootleg/tasks/stop.exs +++ b/lib/bootleg/tasks/stop.exs @@ -5,7 +5,7 @@ task :stop do app_name = Config.app() remote :app do - "bin/#{app_name} stop" + "current/bin/#{app_name} stop" end UI.info("#{app_name} stopped") diff --git a/lib/bootleg/tasks/update.exs b/lib/bootleg/tasks/update.exs index eef1f4a..fb065db 100644 --- a/lib/bootleg/tasks/update.exs +++ b/lib/bootleg/tasks/update.exs @@ -9,7 +9,7 @@ task :update do end task :stop_silent do - nodetool = "bin/#{Config.app()}" + nodetool = "current/bin/#{Config.app()}" remote :app do "#{nodetool} describe && (#{nodetool} stop || true)" diff --git a/lib/mix/tasks/rollback.ex b/lib/mix/tasks/rollback.ex new file mode 100644 index 0000000..ba87406 --- /dev/null +++ b/lib/mix/tasks/rollback.ex @@ -0,0 +1,14 @@ +defmodule Mix.Tasks.Bootleg.Rollback do + use Bootleg.MixTask, :rollback + + @shortdoc "Roll back to the previous release" + + @moduledoc """ + Roll back to the previous release + + # Usage: + + * mix bootleg.rollback + + """ +end diff --git a/test/bootleg/tasks/deploy_task_functional_test.exs b/test/bootleg/tasks/deploy_task_functional_test.exs index 1265d5d..dab55b5 100644 --- a/test/bootleg/tasks/deploy_task_functional_test.exs +++ b/test/bootleg/tasks/deploy_task_functional_test.exs @@ -25,7 +25,7 @@ defmodule Bootleg.Tasks.DeployTaskFunctionalTest do File.cd!("test/fixtures", fn -> capture_io(fn -> - assert_raise File.Error, fn -> invoke(:deploy) end + assert_raise SSHError, fn -> invoke(:deploy) end end) end) end @@ -49,18 +49,17 @@ defmodule Bootleg.Tasks.DeployTaskFunctionalTest do end) end - @tag role_opts: %{release_workspace: "/fixtures"} + @tag role_opts: %{release_workspace: "/project/test/fixtures/releases"} test "deploy/1 deploys the release to the target hosts from a remote release_workspace path" do alias Bootleg.Config File.cd!("test/fixtures", fn -> capture_io(fn -> release_name = "#{Config.version()}.tar.gz" - app_name = "#{Config.app()}.tar.gz" - assert [{:ok, _, 0, _}] = remote(:app, "[ -f /fixtures/#{release_name} ]") + assert [{:ok, _, 0, _}] = remote(:app, "[ -f /project/test/fixtures/releases/#{release_name} ]") invoke(:deploy) - assert [{:ok, _, 0, _}] = remote(:app, "[ -f #{app_name} ]") - assert [{:ok, _, 0, _}] = remote(:app, "[ -f release.txt ]") + assert [{:ok, _, 0, _}] = remote(:app, "[ -L current ]") + assert [{:ok, _, 0, _}] = remote(:app, "[ -f current/release.txt ]") end) end) end diff --git a/test/bootleg/tasks/manage_tasks_test.exs b/test/bootleg/tasks/manage_tasks_test.exs index 1f2c7bd..86aeeab 100644 --- a/test/bootleg/tasks/manage_tasks_test.exs +++ b/test/bootleg/tasks/manage_tasks_test.exs @@ -21,6 +21,7 @@ defmodule Bootleg.Tasks.ManageTasksTest do capture_io(fn -> conn = SSH.init(:app) SSH.run!(conn, "install-app build_me") + SSH.run!(conn, "ln -sfn . current") send(self(), {:connection, conn}) end) diff --git a/test/fixtures/releases/valid_archive.tar.gz b/test/fixtures/releases/valid_archive.tar.gz index 1cce2f2..6ae688f 100644 Binary files a/test/fixtures/releases/valid_archive.tar.gz and b/test/fixtures/releases/valid_archive.tar.gz differ diff --git a/test/support/docker/Dockerfile b/test/support/docker/Dockerfile index d435d53..f22c0cc 100644 --- a/test/support/docker/Dockerfile +++ b/test/support/docker/Dockerfile @@ -3,7 +3,7 @@ FROM bitwalker/alpine-elixir:latest # Set up an Alpine Linux machine running an SSH server. # Autogenerate missing host keys. -RUN apk add --update --no-cache openssh sudo git perl-utils bash +RUN apk add --update --no-cache openssh sudo git perl-utils bash tar RUN ssh-keygen -A RUN printf "PermitUserEnvironment yes\n" >> /etc/ssh/sshd_config diff --git a/test/support/docker/fixtures/valid_archive.tar.gz b/test/support/docker/fixtures/valid_archive.tar.gz index 1cce2f2..5f4502b 100644 Binary files a/test/support/docker/fixtures/valid_archive.tar.gz and b/test/support/docker/fixtures/valid_archive.tar.gz differ