defmodule Prymn.Apps.Wordpress do use Ecto.Schema import Ecto.Changeset alias Prymn.Apps.App alias Prymn.Agents alias PrymnProto.Prymn.{ExecRequest, ExecResponse} @primary_key false embedded_schema do field :path, :string field :db_host, :string field :db_name, :string field :db_user, :string field :db_pass, :string field :admin_username, :string field :admin_email, :string end def changeset(app, attrs) do app |> cast(attrs, [ :path, :db_host, :db_name, :db_user, :db_pass, :admin_username, :admin_email ]) |> validate_required([ :path, :db_user, :db_host, :db_user, :db_pass, :admin_email, :admin_username ]) end @max_steps 6 # TODO: We need a mechanism to drive some messages to the pubsub, maybe using Health: # Health.change_task(%Health.Task{ # key: "create_db_user", # message: "Creating db user...", # curr_step: 0, # max_steps: 5 # }) # |> Health.update_and_broadcast() def deploy(%App{type: "wordpress"} = app, agent, notify_fun) do # TODO: Run sanity checks # e.g Database does not exist, domain does not exist, etc. deploy(app, agent, notify_fun, 1) end defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 1 = step) do # TODO: We need a mechanism to wait for the agent to connect before proceeding, # this is executed faster than the connection (which happens on deploy_app in Worker) Agents.exec(agent, %ExecRequest{ user: "root", program: "mysql", args: [ "-e", # TODO: Sanitize the string to protect from injection "create user '#{wp.db_user}'@'#{wp.db_host}' identified by '#{wp.db_pass}';" ] }) |> then(&get_results/1) |> case do {_, _, exit_code} = data when exit_code != 0 -> notify_fun.(:error, data, 1 / 5 * 100) data -> notify_fun.(:progress, data, step / @max_steps * 100) deploy(app, agent, notify_fun, step + 1) end end defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 2 = step) do Agents.exec(agent, %ExecRequest{ user: "root", program: "mysql", args: [ "-e", # TODO: Sanitize the string to protect from injection "grant all privileges on #{wp.db_name}.* to '#{wp.db_user}'@'#{wp.db_host}';" ] }) |> then(&get_results/1) |> case do {_, _, exit_code} = data when exit_code != 0 -> notify_fun.(:error, data, step / @max_steps * 100) data -> notify_fun.(:progress, data, step / @max_steps * 100) deploy(app, agent, notify_fun, step + 1) end end defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 3 = step) do Agents.exec(agent, %ExecRequest{ user: "vagrant", program: "wp", args: ["core", "download", "--path=#{wp.path}"] }) |> then(&get_results/1) |> case do {_, _, exit_code} = data when exit_code != 0 -> notify_fun.(:error, data, step / @max_steps * 100) data -> notify_fun.(:progress, data, step / @max_steps * 100) deploy(app, agent, notify_fun, step + 1) end end defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 4 = step) do Agents.exec(agent, %ExecRequest{ user: "vagrant", program: "wp", args: [ "config", "create", "--path=#{wp.path}", "--dbhost=#{wp.db_host}", "--dbname=#{wp.db_name}", "--dbuser=#{wp.db_user}", "--dbpass=#{wp.db_pass}" ] }) |> then(&get_results/1) |> case do {_, _, exit_code} = data when exit_code != 0 -> notify_fun.(:error, data, step / @max_steps * 100) data -> notify_fun.(:progress, data, step / @max_steps * 100) deploy(app, agent, notify_fun, step + 1) end end defp deploy(%App{wordpress: %__MODULE__{} = wp} = app, agent, notify_fun, 5 = step) do Agents.exec(agent, %ExecRequest{ user: "vagrant", program: "wp", args: ["db", "create", "--path=#{wp.path}"] }) |> then(&get_results/1) |> case do {_, _, exit_code} = data when exit_code != 0 -> notify_fun.(:error, data, step / @max_steps * 100) data -> notify_fun.(:progress, data, step / @max_steps * 100) deploy(app, agent, notify_fun, step + 1) end end defp deploy(%App{name: name, wordpress: %__MODULE__{} = wp}, agent, notify_fun, 6 = step) do Agents.exec(agent, %ExecRequest{ user: "vagrant", program: "wp", args: [ "core", "install", "--path=#{wp.path}", "--url=http://site.test", "--title=#{name}", "--admin_user=#{wp.admin_username}", "--admin_email=#{wp.admin_email}" ] }) |> then(&get_results/1) |> case do {_, _, exit_code} = data when exit_code != 0 -> notify_fun.(:error, data, step / @max_steps * 100) data -> notify_fun.(:complete, data, step / @max_steps * 100) end end defp get_results(stream) do Enum.reduce_while(stream, {"", "", nil}, fn {:ok, %ExecResponse{out: {:exit_code, exit_code}}}, {stdout, stderr, _} -> {:halt, {stdout, stderr, exit_code}} {:ok, %ExecResponse{out: {:stdout, stdout}}}, {acc_stdout, stderr, exit_code} -> {:cont, {acc_stdout <> stdout, stderr, exit_code}} {:ok, %ExecResponse{out: {:stderr, stderr}}}, {stdout, acc_stderr, exit_code} -> {:cont, {stdout, acc_stderr <> stderr, exit_code}} end) end end