defmodule Mix.Tasks.Coolify.Deploy do use Mix.Task @shortdoc "Pushes git, triggers a Coolify deployment, and optionally verifies it" alias CoolifyEx.Config alias CoolifyEx.Deployer alias CoolifyEx.MixTaskSupport alias CoolifyEx.Verifier @moduledoc """ Deploys one configured Coolify app. mix coolify.deploy mix coolify.deploy --project web mix coolify.deploy --project api --no-push --force mix coolify.deploy --config .coolify_ex.exs --skip-verify """ @impl Mix.Task def run(args) do MixTaskSupport.ensure_started!() {opts, _argv, _invalid} = OptionParser.parse(args, strict: [ project: :string, app: :string, config: :string, force: :boolean, instant: :boolean, no_push: :boolean, poll_interval: :integer, skip_verify: :boolean, timeout: :integer ] ) case Config.load(Keyword.get(opts, :config)) do {:ok, config} -> run_deploy(config, opts) {:error, reason} -> Mix.raise("Coolify deploy failed: #{inspect(reason)}") end end defp run_deploy(config, opts) do case Deployer.deploy(config, target_name(opts), deploy_opts(opts)) do {:ok, deployment} -> Mix.shell().info("Deployment finished: #{deployment.uuid}") maybe_verify(config, opts) {:error, {:deployment_failed, deployment}} -> Mix.raise("Deployment failed with status #{deployment.status}: #{deployment.uuid}") {:error, {:deployment_timeout, deployment}} -> Mix.raise("Deployment timed out while waiting for #{deployment.uuid}") {:error, reason} -> Mix.raise("Coolify deploy failed: #{inspect(reason)}") end end defp deploy_opts(opts) do [ force: Keyword.get(opts, :force, false), instant: Keyword.get(opts, :instant, false), push?: not Keyword.get(opts, :no_push, false), poll_interval: Keyword.get(opts, :poll_interval, 3_000), timeout: Keyword.get(opts, :timeout, 900_000) ] end defp maybe_verify(config, opts) do if Keyword.get(opts, :skip_verify, false) do :ok else case Verifier.verify(config, target_name(opts)) do {:ok, result} -> Mix.shell().info("Readiness passed after #{result.readiness.attempts} attempt(s)") Mix.shell().info( "Verification passed: #{result.verification.passed}/#{result.verification.total} checks" ) {:error, result} when is_struct(result, CoolifyEx.Verifier.Result) -> print_failed_checks(result) Mix.raise(failure_message(result)) {:error, reason} -> Mix.raise("Verification failed: #{inspect(reason)}") end end end defp target_name(opts), do: Keyword.get(opts, :project) || Keyword.get(opts, :app) defp print_failed_checks(result) do (result.readiness.checks ++ result.verification.checks) |> Enum.each(fn check -> if not check.ok? do Mix.shell().error("#{check.phase} #{check.name}: #{check.reason}") end end) end defp failure_message(result) do cond do result.readiness.failed > 0 -> "Verification failed during readiness with #{result.readiness.failed} failing checks" result.verification.failed > 0 -> "Verification failed with #{result.verification.failed} failing checks" true -> "Verification failed" end end end