Skip to content

Commit

Permalink
refactor to separate shared/dedicated workflows, and add test
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljguarino committed Sep 19, 2024
1 parent 0027bf6 commit 0d73868
Show file tree
Hide file tree
Showing 7 changed files with 310 additions and 193 deletions.
5 changes: 5 additions & 0 deletions apps/core/lib/core/clients/console.ex
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ defmodule Core.Clients.Console do
}
"""

def queries(:me_q), do: @me_q
def queries(:stack_q), do: @stack_q
def queries(:stack_create), do: @create_stack_q
def queries(:stack_delete), do: @delete_stack_q

def new(url, token) do
Req.new(base_url: with_gql(url), auth: "Token #{token}")
|> AbsintheClient.attach()
Expand Down
2 changes: 1 addition & 1 deletion apps/core/lib/core/services/cloud/configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule Core.Services.Cloud.Configuration do
cluster_id: Core.conf(:mgmt_cluster),
type: "TERRAFORM",
manageState: true,
approval: true,
approval: false,
configuration: %{version: "1.8"},
git: %{ref: "main", folder: "terraform/modules/dedicated/#{inst.cloud}"},
environment: Enum.map([
Expand Down
14 changes: 14 additions & 0 deletions apps/core/lib/core/services/cloud/utils.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule Core.Services.Cloud.Utils do
alias Core.Repo
alias Core.Clients.Console
alias Core.Schema.{ConsoleInstance}

def mark_provisioned(inst) do
ConsoleInstance.changeset(inst, %{status: :provisioned})
|> Repo.update()
end

def console(), do: Console.new(Core.conf(:console_url), Core.conf(:console_token))

def dedicated_console(), do: Console.new(Core.conf(:console_url), Core.conf(:dedicated_console_token))
end
209 changes: 17 additions & 192 deletions apps/core/lib/core/services/cloud/workflow.ex
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
defmodule Core.Services.Cloud.Workflow do
use Core.Services.Base
alias Core.Clients.Console
alias Core.Services.{Cloud, Users}
alias Core.Services.Cloud.{Poller, Configuration}
alias Core.Schema.{ConsoleInstance, PostgresCluster, User}
alias Core.Repo
alias Core.Clients.Console
alias Core.Schema.{ConsoleInstance}
alias Core.Services.Cloud.Workflow.{Dedicated, Shared}

require Logger

def sync(%ConsoleInstance{type: :dedicated, external_id: id} = inst) when is_binary(id) do
with {:ok, project_id} <- Poller.project(),
{:ok, repo_id} <- Poller.repository(),
{:ok, actor} <- Console.me(dedicated_console()),
attrs = %{actor_id: actor, project_id: project_id, repository_id: repo_id},
do: Console.update_stack(dedicated_console(), id, Configuration.stack_attributes(inst, attrs))
end
@type error :: {:error, term}
@type resp :: {:ok, ConsoleInstance.t} | error

def sync(%ConsoleInstance{external_id: id} = instance) when is_binary(id) do
instance = Repo.preload(instance, [:cluster, :postgres])
Console.update_service(console(), id, %{
configuration: Configuration.build(instance)
})
end
def sync(_), do: :ok
@callback sync(inst :: ConsoleInstance.t) :: :ok | {:ok, term} | error
@callback up(inst :: ConsoleInstance.t) :: resp
@callback down(inst :: ConsoleInstance.t) :: resp
@callback finalize(inst :: ConsoleInstance.t, :up | :down) :: resp

def provision(%ConsoleInstance{} = instance) do
instance = Repo.preload(instance, [:postgres, :cluster])
Expand Down Expand Up @@ -58,181 +49,15 @@ defmodule Core.Services.Cloud.Workflow do
|> finalize(:down)
end

defp up(%ConsoleInstance{status: :pending, type: :dedicated} = inst) do
with {:ok, id} <- Poller.project(),
{:ok, repo_id} <- Poller.repository(),
{:ok, actor} <- Console.me(dedicated_console()),
attrs = %{actor_id: actor, project_id: id, repository_id: repo_id},
{:ok, stack_id} <- Console.create_stack(dedicated_console(), Configuration.stack_attributes(inst, attrs)) do
ConsoleInstance.changeset(inst, %{
instance_status: %{stack: true},
status: :stack_created,
external_id: stack_id
})
|> Repo.update()
end
end

defp up(%ConsoleInstance{type: :dedicated, status: :stack_created, external_id: id} = inst) do
Enum.reduce_while(0..120, inst, fn _, inst ->
dedicated_console()
|> Console.stack(id)
|> case do
{:ok, %{"status" => "SUCCESSFUL"}} ->
ConsoleInstance.changeset(inst, %{status: :provisioned})
|> Repo.update()
status ->
Logger.info "stack not ready yet, sleeping: #{inspect(status)}"
:timer.sleep(:timer.minutes(1))
{:ok, inst}
end
end)
end

defp up(%ConsoleInstance{status: :deployment_created, url: url} = inst) do
case {DNS.resolve(url), DNS.resolve(url, :cname)} do
{{:ok, [_ | _]}, _} -> mark_provisioned(inst)
{_, {:ok, [_ | _]}} -> mark_provisioned(inst)
{{:error, err}, _} -> {:error, "cannot resolve #{url}: #{inspect(err)}"}
end
end

defp up(%ConsoleInstance{status: :pending, postgres: pg, configuration: conf} = inst) do
with {:ok, pid} <- connect(pg),
{:ok, _} <- Postgrex.query(pid, "CREATE DATABASE #{conf.database}", []),
{:ok, _} <- Postgrex.transaction(pid, fn conn ->
Postgrex.query!(conn, "CREATE USER #{conf.dbuser} WITH PASSWORD '#{conf.dbpassword}'", [])
Postgrex.query!(conn, "GRANT ALL ON DATABASE #{conf.database} TO #{conf.dbuser}", [])
end) do
ConsoleInstance.changeset(inst, %{
instance_status: %{db: true},
status: :database_created,
})
|> Repo.update()
end
end

defp up(%ConsoleInstance{instance_status: %{db: true}, name: name, cluster: cluster} = inst) do
with {:ok, id} <- Poller.repository(),
{:ok, svc_id} <- Console.create_service(console(), cluster.external_id, %{
name: "console-cloud-#{name}",
namespace: "plrl-cloud-#{name}",
helm: %{
url: "https://pluralsh.github.io/console",
chart: "console-rapid",
release: "console",
version: "x.x.x",
valuesFiles: ["console.yaml.liquid"]
},
repository_id: id,
git: %{ref: "main", folder: "helm"},
configuration: Configuration.build(inst),
}) do
ConsoleInstance.changeset(inst, %{
external_id: svc_id,
instance_status: %{svc: true},
status: :deployment_created
})
|> Repo.update()
end
end

defp up(inst), do: {:ok, inst}

defp down(%ConsoleInstance{type: :dedicated, instance_status: %{stack: true}, external_id: id} = inst) do
with {:ok, _} <- Console.delete_stack(dedicated_console(), id) do
ConsoleInstance.changeset(inst, %{status: :stack_deleted})
|> Repo.update()
end
end

defp down(%ConsoleInstance{instance_status: %{svc: false, db: true}, configuration: conf, postgres: pg} = inst) do
with {:ok, pid} <- connect(pg),
{:ok, _} <- Postgrex.query(pid, "DROP DATABASE IF EXISTS #{conf.database}", []),
{:ok, _} <- Postgrex.transaction(pid, fn conn ->
Postgrex.query!(conn, "DROP USER IF EXISTS #{conf.dbuser}", [])
end) do
ConsoleInstance.changeset(inst, %{
instance_status: %{db: false},
status: :database_deleted,
})
|> Repo.update()
end
end
def sync(%ConsoleInstance{type: :dedicated} = inst), do: Dedicated.sync(inst)
def sync(inst), do: Shared.sync(inst)

defp down(%ConsoleInstance{instance_status: %{svc: true}} = inst) do
with {:ok, _} <- Console.delete_service(console(), inst.external_id) do
ConsoleInstance.changeset(inst, %{
instance_status: %{svc: false, db: true},
status: :deployment_deleted,
})
|> Repo.update()
end
end

defp down(inst), do: {:ok, inst}

defp finalize(%ConsoleInstance{status: :provisioned} = inst, :up), do: {:ok, inst}

defp finalize(%ConsoleInstance{status: :database_deleted, cluster: cluster, postgres: pg} = inst, :down) do
start_transaction()
|> add_operation(:inst, fn _ -> Repo.delete(inst) end)
|> add_operation(:cluster, fn _ -> Cloud.dec(cluster) end)
|> add_operation(:pg, fn _ -> Cloud.dec(pg) end)
|> add_operation(:sa, fn %{inst: %{name: name}} ->
case Users.get_user_by_email("#{name}[email protected]") do
%User{} = u -> Repo.delete(u)
_ -> {:ok, nil}
end
end)
|> execute(extract: :inst)
end

defp finalize(%ConsoleInstance{type: :dedicated} = inst, :down) do
start_transaction()
|> add_operation(:inst, fn _ -> Repo.delete(inst) end)
|> add_operation(:sa, fn %{inst: %{name: name}} ->
case Users.get_user_by_email("#{name}[email protected]") do
%User{} = u -> Repo.delete(u)
_ -> {:ok, nil}
end
end)
|> execute(extract: :inst)
end

defp finalize(inst, _) do
Logger.warn "failed to finalize console instance: #{inst.id}"
{:ok, inst}
end

defp connect(%PostgresCluster{} = roach) do
uri = URI.parse(roach.url)
user = userinfo(uri)
Postgrex.start_link(
database: uri.path && String.trim_leading(uri.path, "/"),
username: user[:username],
password: user[:password],
hostname: uri.host,
port: uri.port,
ssl: Core.conf(:bootstrap_ssl)
)
end

defp userinfo(%URI{userinfo: info}) when is_binary(info) do
case String.split(info, ":") do
[user, pwd] -> %{username: user, password: pwd}
[user] -> %{username: user}
_ -> %{}
end
end
defp userinfo(_), do: %{}

defp mark_provisioned(inst) do
ConsoleInstance.changeset(inst, %{status: :provisioned})
|> Repo.update()
end
defp up(%ConsoleInstance{type: :dedicated} = inst), do: Dedicated.up(inst)
defp up(inst), do: Shared.up(inst)

defp console(), do: Console.new(Core.conf(:console_url), Core.conf(:console_token))
defp down(%ConsoleInstance{type: :dedicated} = inst), do: Dedicated.down(inst)
defp down(inst), do: Shared.down(inst)

defp dedicated_console(), do: Console.new(Core.conf(:console_url), Core.conf(:dedicated_console_token))
defp finalize(%ConsoleInstance{type: :dedicated} = inst, direction), do: Dedicated.finalize(inst, direction)
defp finalize(%ConsoleInstance{} = inst, direction), do: Shared.finalize(inst, direction)
end
79 changes: 79 additions & 0 deletions apps/core/lib/core/services/cloud/workflow/dedicated.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
defmodule Core.Services.Cloud.Workflow.Dedicated do
use Core.Services.Base
import Core.Services.Cloud.Utils

alias Core.Clients.Console
alias Core.Services.{Users}
alias Core.Services.Cloud.{Poller, Configuration}
alias Core.Schema.{ConsoleInstance, User}
alias Core.Repo

require Logger

@behaviour Core.Services.Cloud.Workflow

def sync(%ConsoleInstance{external_id: id} = inst) when is_binary(id) do
with {:ok, project_id} <- Poller.project(),
{:ok, repo_id} <- Poller.repository(),
{:ok, actor} <- Console.me(dedicated_console()),
attrs = %{actor_id: actor, project_id: project_id, repository_id: repo_id},
do: Console.update_stack(dedicated_console(), id, Configuration.stack_attributes(inst, attrs))
end

def sync(_), do: :ok

def up(%ConsoleInstance{status: :pending} = inst) do
with {:ok, id} <- Poller.project(),
{:ok, repo_id} <- Poller.repository(),
{:ok, actor} <- Console.me(dedicated_console()),
attrs = %{actor_id: actor, project_id: id, repository_id: repo_id},
{:ok, stack_id} <- Console.create_stack(dedicated_console(), Configuration.stack_attributes(inst, attrs)) do
ConsoleInstance.changeset(inst, %{
instance_status: %{stack: true},
status: :stack_created,
external_id: stack_id
})
|> Repo.update()
end
end

def up(%ConsoleInstance{status: :stack_created, external_id: id} = inst) do
Enum.reduce_while(0..120, inst, fn _, inst ->
dedicated_console()
|> Console.stack(id)
|> case do
{:ok, %{"status" => "SUCCESSFUL"}} ->
{:halt, mark_provisioned(inst)}
status ->
Logger.info "stack not ready yet, sleeping: #{inspect(status)}"
:timer.sleep(:timer.minutes(1))
{:cont, inst}
end
end)
end

def up(inst), do: {:ok, inst}

def down(%ConsoleInstance{instance_status: %{stack: true}, external_id: id} = inst) do
with {:ok, _} <- Console.delete_stack(dedicated_console(), id) do
ConsoleInstance.changeset(inst, %{status: :stack_deleted})
|> Repo.update()
end
end

def down(inst), do: {:ok, inst}

def finalize(%ConsoleInstance{} = inst, :down) do
start_transaction()
|> add_operation(:inst, fn _ -> Repo.delete(inst) end)
|> add_operation(:sa, fn %{inst: %{name: name}} ->
case Users.get_user_by_email("#{name}[email protected]") do
%User{} = u -> Repo.delete(u)
_ -> {:ok, nil}
end
end)
|> execute(extract: :inst)
end

def finalize(inst, _), do: {:ok, inst}
end
Loading

0 comments on commit 0d73868

Please sign in to comment.