Skip to content

Commit

Permalink
Implement dedicated cloud console types (#1363)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeljguarino authored Sep 20, 2024
1 parent 4566e48 commit 07113b1
Show file tree
Hide file tree
Showing 22 changed files with 567 additions and 141 deletions.
68 changes: 68 additions & 0 deletions apps/core/lib/core/clients/console.ex
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
defmodule Core.Clients.Console do
require Logger

@me_q """
query {
me { id }
}
"""

@clusters_q """
query {
clusters(first: 100) {
Expand Down Expand Up @@ -33,6 +39,30 @@ defmodule Core.Clients.Console do
}
"""

@create_stack_q """
mutation Create($attributes: StackAttributes!) {
createStack(attributes: $attributes) {
id
}
}
"""

@update_stack_q """
mutation update($id: ID!, $attributes: StackAttributes!) {
updateStack(id: $id, attributes: $attributes) {
id
}
}
"""

@delete_stack_q """
mutation delete($id: ID!) {
deleteStack(id: $id) {
id
}
}
"""

@repo_q """
query Repo($url: String!) {
gitRepository(url: $url) {
Expand All @@ -41,10 +71,17 @@ defmodule Core.Clients.Console do
}
"""

@project_q """
query Project($name: String!) {
project(name: $name) { id }
}
"""

@stack_q """
query Stack($id: ID!) {
infrastructureStack(id: $id) {
id
status
output {
name
value
Expand All @@ -53,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 All @@ -74,6 +116,16 @@ defmodule Core.Clients.Console do
|> service_resp("gitRepository")
end

def me(client) do
Req.post(client, graphql: {@me_q, %{}})
|> service_resp("me")
end

def project(client, name) do
Req.post(client, graphql: {@project_q, %{name: name}})
|> service_resp("project")
end

def create_service(client, cluster_id, attrs) do
Req.post(client, graphql: {@create_svc_q, %{clusterId: cluster_id, attributes: attrs}})
|> service_resp("createServiceDeployment")
Expand All @@ -90,6 +142,22 @@ defmodule Core.Clients.Console do
|> ignore_not_found()
end

def create_stack(client, attrs) do
Req.post(client, graphql: {@create_stack_q, %{attributes: attrs}})
|> service_resp("createStack")
end

def update_stack(client, id, attrs) do
Req.post(client, graphql: {@update_stack_q, %{id: id, attributes: attrs}})
|> service_resp("updateStack")
end

def delete_stack(client, id) do
Req.post(client, graphql: {@delete_stack_q, %{id: id}})
|> service_resp("deleteStack")
|> ignore_not_found()
end

def stack(client, id) do
Req.post(client, graphql: {@stack_q, %{id: id}})
|> case do
Expand Down
8 changes: 8 additions & 0 deletions apps/core/lib/core/policies/cloud.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ defmodule Core.Policies.Cloud do
alias Core.Schema.{User, ConsoleInstance}
alias Core.Services.Payments

def can?(%User{} = user, %ConsoleInstance{type: :dedicated}, :create) do
case Payments.enterprise?(user) do
true -> :pass
_ ->
{:error, "you must be on an enterprise plan to create a dedicated Plural cluster"}
end
end

def can?(%User{} = user, %ConsoleInstance{}, :create) do
case Payments.has_feature?(user, :cd) do
true -> :pass
Expand Down
20 changes: 16 additions & 4 deletions apps/core/lib/core/schema/console_instance.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,24 @@ defmodule Core.Schema.ConsoleInstance do
alias Piazza.Ecto.EncryptedString
alias Core.Schema.{PostgresCluster, CloudCluster, User}

defenum Type, shared: 0, dedicated: 1
defenum Size, small: 0, medium: 1, large: 2
defenum Status,
pending: 0,
database_created: 1,
deployment_created: 2,
provisioned: 3,
deployment_deleted: 4,
database_deleted: 5
database_deleted: 5,
stack_created: 6,
stack_deleted: 7

@region_map %{
aws: ~w(us-east-1)
}

schema "console_instances" do
field :type, Type, default: :shared
field :name, :string
field :status, Status
field :subdomain, :string
Expand All @@ -33,6 +37,7 @@ defmodule Core.Schema.ConsoleInstance do
embeds_one :instance_status, InstanceStatus, on_replace: :update do
field :db, :boolean, default: false
field :svc, :boolean, default: false
field :stack, :boolean, default: false
end

embeds_one :configuration, Configuration, on_replace: :update do
Expand Down Expand Up @@ -96,14 +101,14 @@ defmodule Core.Schema.ConsoleInstance do

def regions(), do: @region_map

@valid ~w(name cloud size region status subdomain url external_id postgres_id cluster_id owner_id)a
@valid ~w(name type cloud size region status subdomain url external_id postgres_id cluster_id owner_id)a

def changeset(model, attrs \\ %{}) do
model
|> cast(attrs, @valid)
|> cast_embed(:configuration, with: &configuration_changeset/2)
|> cast_embed(:instance_status, with: &status_changeset/2)
|> validate_required(@valid -- [:external_id])
|> validate_required(@valid -- ~w(external_id postgres_id cluster_id)a)
|> foreign_key_constraint(:cluster_id)
|> foreign_key_constraint(:postgres_id)
|> foreign_key_constraint(:owner_id)
Expand All @@ -118,6 +123,13 @@ defmodule Core.Schema.ConsoleInstance do
|> cast(attrs, @valid)
|> validate_required(~w(name region status cloud size)a)
|> validate_region()
|> foreign_key_constraint(:cluster_id)
|> foreign_key_constraint(:postgres_id)
|> foreign_key_constraint(:owner_id)
|> unique_constraint(:subdomain)
|> unique_constraint(:name)
|> validate_format(:name, ~r/[a-z][a-z0-9]{5,10}/, message: "must be an alphanumeric string between 5 and 11 characters")
|> validate_region()
end

defp validate_region(cs) do
Expand Down Expand Up @@ -146,6 +158,6 @@ defmodule Core.Schema.ConsoleInstance do

defp status_changeset(model, attrs) do
model
|> cast(attrs, ~w(db svc)a)
|> cast(attrs, ~w(db svc stack)a)
end
end
10 changes: 8 additions & 2 deletions apps/core/lib/core/services/cloud.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,14 @@ defmodule Core.Services.Cloud do
|> allow(user, :create)
|> when_ok(:insert)
end)
|> add_operation(:cluster, fn _ -> select_cluster(attrs[:cloud], attrs[:region]) end)
|> add_operation(:postgres, fn _ -> select_roach(attrs[:cloud]) end)
|> add_operation(:cluster, fn
%{inst: %ConsoleInstance{type: :dedicated}} -> {:ok, %{id: nil}}
_ -> select_cluster(attrs[:cloud], attrs[:region])
end)
|> add_operation(:postgres, fn
%{inst: %ConsoleInstance{type: :dedicated}} -> {:ok, %{id: nil}}
_ -> select_roach(attrs[:cloud])
end)
|> add_operation(:sa, fn _ ->
Accounts.create_service_account(%{
name: "#{name}-cloud-sa",
Expand Down
21 changes: 21 additions & 0 deletions apps/core/lib/core/services/cloud/configuration.ex
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,32 @@ defmodule Core.Services.Cloud.Configuration do
size: "#{size}",
})
|> Map.put(:size, "#{size}")
|> Enum.filter(fn {_, v} -> is_binary(v) end)
|> Enum.map(fn {k, v} -> %{name: k, value: v} end)
end

def stack_attributes(%ConsoleInstance{name: name} = inst, attrs) do
Map.merge(attrs, %{
name: "dedicated-#{name}",
cluster_id: Core.conf(:mgmt_cluster),
type: "TERRAFORM",
manageState: true,
approval: false,
configuration: %{version: "1.8"},
git: %{ref: "main", folder: "terraform/modules/dedicated/#{inst.cloud}"},
environment: Enum.map([
region: inst.region,
development: String.contains?(Core.conf(:dedicated_project), "dev"),
cluster_name: "dedicated-#{inst.name}",
size: inst.size,
service_secrets: build(inst) |> Map.new(fn %{name: n, value: v} -> {n, v} end) |> Jason.encode!()
], fn {k, v} -> %{name: "TF_VAR_#{k}", value: "#{v}"} end)
})
end

# defp certificate(%ConsoleInstance{postgres: %PostgresCluster{certificate: cert}}), do: cert

defp build_pg_url(%ConsoleInstance{type: :dedicated}), do: nil
defp build_pg_url(%ConsoleInstance{
configuration: %{dbuser: u, dbpassword: p, database: database},
postgres: %PostgresCluster{host: host}
Expand Down
22 changes: 20 additions & 2 deletions apps/core/lib/core/services/cloud/poller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Core.Services.Cloud.Poller do

@poll :timer.minutes(2)

defmodule State, do: defstruct [:client, :repo]
defmodule State, do: defstruct [:client, :dedicated_client, :repo, :project]

def start_link(_) do
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
Expand All @@ -16,15 +16,24 @@ defmodule Core.Services.Cloud.Poller do
:timer.send_interval(@poll, :clusters)
:timer.send_interval(@poll, :pgs)
send self(), :repo
{:ok, %State{client: Console.new(Core.conf(:console_url), Core.conf(:console_token))}}
send self(), :project
client = Console.new(Core.conf(:console_url), Core.conf(:console_token))
dedicated_client = Console.new(Core.conf(:console_url), Core.conf(:dedicated_console_token))
{:ok, %State{client: client, dedicated_client: dedicated_client}}
end

def repository(), do: GenServer.call(__MODULE__, :repo)

def project(), do: GenServer.call(__MODULE__, :project)

def handle_call(:repo, _, %{repo: id} = state) when is_binary(id),
do: {:reply, {:ok, id}, state}
def handle_call(:repo, _, state), do: {:reply, {:error, "repo not pulled"}, state}

def handle_call(:project, _, %{project: id} = state) when is_binary(id),
do: {:reply, {:ok, id}, state}
def handle_call(:project, _, state), do: {:reply, {:error, "project not pulled"}, state}

def handle_info(:repo, %{client: client} = state) do
case Console.repo(client, Core.conf(:mgmt_repo)) do
{:ok, id} -> {:noreply, %{state | repo: id}}
Expand All @@ -34,6 +43,15 @@ defmodule Core.Services.Cloud.Poller do
end
end

def handle_info(:project, %{dedicated_client: client} = state) do
case Console.project(client, Core.conf(:dedicated_project)) do
{:ok, id} -> {:noreply, %{state | project: id}}
err ->
Logger.warn "failed to find dedicated project: #{inspect(err)}"
{:noreply, state}
end
end

def handle_info(:clusters, %{client: client} = state) do
with {:ok, clusters} <- Console.clusters(client) do
Enum.each(clusters, &upsert_cluster/1)
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
Loading

0 comments on commit 07113b1

Please sign in to comment.