defmodule Mix.Tasks.Edeliver do use Mix.Task @shortdoc "Build and deploy releases" @moduledoc """ Build and deploy Elixir applications and perform hot-code upgrades # Usage: * mix edeliver command-info [Options] * mix edeliver --help|--version * mix edeliver help ## Build Commands: * mix edeliver build release [--revision=|--tag=] [--branch=] [Options] * mix edeliver build appups|upgrade --from=|--with= [--to=] [--branch=] [Options] ## Deploy Commands: * mix edeliver deploy release|upgrade [[to] staging|production] [--version=] [Options] * mix edeliver upgrade [staging|production] [--to=] [--branch=] [Options] * mix edeliver update [staging|production] [--to=] [--branch=] [Options] ## Node Commands: * mix edeliver start|stop|restart|ping|version [staging|production] [Options] * mix edeliver migrate [staging|production] [up|down] [--version=] * mix edeliver [show] migrations [on] [staging|production] ## Local Commands: * mix edeliver check release|config [--version=] * mix edeliver show releases|appups * mix edeliver show relup * mix edeliver edit relup|appups [--version=] * mix edeliver upload|download [release|upgrade ]| [] * mix edeliver increase [major|minor] versions [--from=] [--to=] * mix edeliver unpack|pack release|upgrade [--version=] ## Command line Options * `--quiet` - do not output verbose messages * `--only` - only fetch dependencies for given environment * `-C`, `--compact` Displays every task as it's run, silences all output. (default mode) * `-V`, `--verbose` Same as above, does not silence output. * `-P`, `--plain` Displays every task as it's run, silences all output. No colouring. (CI) * `-D`, `--debug` Runs in shell debug mode, displays everything. * `-S`, `--skip-existing` Skip copying release archives if they exist already on the deploy hosts. * `-F`, `--force` Do not ask, just do, overwrite, delete or destroy everything * `--clean-deploy` Delete the release, lib and erts-* directories before deploying the release * `--start-deploy` Starts the deployed release. If release is running, it is restarted! * `--host=[u@]vwx.yz` Run command only on that host, even if different hosts are configured * `--skip-git-clean` Don't build from a clean state for faster builds. By default all built files are removed before the next build using `git clean`. This can be adjusted by the $GIT_CLEAN_PATHS env. * `--skip-mix-clean` Skip the 'mix clean step' for faster builds. Makes only sense in addition to the --skip-git-clean * `--skip-relup-mod` Skip modification of relup file. Custom relup instructions are not added * `--relup-mod=` The name of the module to modify the relup * `--auto-version=revision|commit-count|branch|date` Automatically append metadata to release version. * `--increment-version=major|minor|patch` Increment the version for the current build. * `--set-version=` Set the release version for the current build. * `--mix-env=` Build with custom mix env $MIX_ENV. Default is 'prod' """ @spec run(OptionParser.argv) :: :ok def run(args) do edeliver = Path.join [Mix.Project.config[:deps_path], "edeliver", "bin", "edeliver"] if (res = run_edeliver(Enum.join([edeliver | args] ++ ["--runs-as-mix-task"], " "))) > 0, do: System.halt(res) end defp run_edeliver(command) do port = Port.open({:spawn, shell_command(command)}, [:stream, :binary, :exit_status, :use_stdio, :stderr_to_stdout]) stdin_pid = Process.spawn(__MODULE__, :forward_stdin, [port], [:link]) print_stdout(port, stdin_pid) end @doc """ Forwards stdin to the edeliver script which was spawned as port. """ @spec forward_stdin(port::port) :: :ok def forward_stdin(port) do case IO.gets(:stdio, "") do :eof -> :ok {:error, reason} -> throw reason data -> Port.command(port, data) end end # Prints the output received from the port running the edeliver command to stdout. # If the edeliver command terminates, it returns the exit code of the edeliver script. @spec print_stdout(port::port, stdin_pid::pid) :: exit_status::non_neg_integer defp print_stdout(port, stdin_pid) do receive do {^port, {:data, data}} -> IO.write(data) print_stdout(port, stdin_pid) {^port, {:exit_status, status}} -> Process.unlink(stdin_pid) Process.exit(stdin_pid, :kill) status end end # Finding shell command logic from :os.cmd in OTP # https://github.com/erlang/otp/blob/8deb96fb1d017307e22d2ab88968b9ef9f1b71d0/lib/kernel/src/os.erl#L184 defp shell_command(command) do case :os.type do {:unix, _} -> command = command |> String.replace("\"", "\\\"") |> :binary.bin_to_list 'sh -c "' ++ command ++ '"' {:win32, osname} -> command = :binary.bin_to_list(command) case {System.get_env("COMSPEC"), osname} do {nil, :windows} -> 'command.com /c ' ++ command {nil, _} -> 'cmd /c ' ++ command {cmd, _} -> '#{cmd} /c ' ++ command end end end end