Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement dedicated cloud console types #1363

Merged
merged 2 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading