Skip to content

Commit 1bb1484

Browse files
Implement account users tool (#1412)
1 parent 8ed0bed commit 1bb1484

File tree

14 files changed

+135
-337
lines changed

14 files changed

+135
-337
lines changed

Diff for: apps/core/lib/core/mcp/serialization/schema.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ defimpl Core.Mcp.Serialization.Proto, for: Core.Schema.ConsoleInstance do
5656
def serialize(console) do
5757
console
5858
|> Map.from_struct()
59-
|> Map.drop(~w(__meta__ configuration)a)
59+
|> Map.take(~w(id first_notif_at second_notif_at type name status subdomain url cloud size region cluster owner)a)
6060
|> Core.Mcp.Serialization.Proto.serialize()
6161
end
6262
end

Diff for: apps/core/lib/core/mcp/server.ex

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ defmodule Core.MCP.Server do
22
use MCPServer
33
alias Core.MCP.Tools.{
44
Account,
5+
AccountUsers,
56
Enterprise,
67
CloudConsole,
8+
CloudReap,
79
RemoveEnterprise
810
}
911

10-
@tools [Account, Enterprise, CloudConsole, RemoveEnterprise]
12+
@tools [Account, AccountUsers, Enterprise, CloudConsole, CloudReap, RemoveEnterprise]
1113
@by_name Map.new(@tools, & {&1.name(), &1})
1214

1315
@protocol_version "2024-11-05"

Diff for: apps/core/lib/core/mcp/tools/account_users.ex

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
defmodule Core.MCP.Tools.AccountUsers do
2+
@behaviour Core.MCP.Tool
3+
alias Core.Services.Accounts
4+
alias Core.Schema.{User, Account}
5+
6+
def name(), do: "account_users"
7+
8+
def description(), do: "Fetches the users associated with a Plural account"
9+
10+
def schema(), do: %{
11+
type: "object",
12+
required: ["account_id"],
13+
properties: %{
14+
account_id: %{
15+
type: "string",
16+
description: "The ID of the account you want to fetch users for"
17+
}
18+
}
19+
}
20+
21+
def invoke(%{"account_id" => id}) do
22+
with %Account{} = account <- Accounts.get_account(id) do
23+
User.for_account(account.id)
24+
|> Core.Repo.all()
25+
|> Enum.map(&Map.take(&1, ~w(id email name)a))
26+
|> Jason.encode()
27+
else
28+
_ -> {:ok, "no account with id #{id}"}
29+
end
30+
end
31+
def invoke(_), do: {:error, "account_id is required"}
32+
end

Diff for: apps/core/lib/core/mcp/tools/cloud_console.ex

+2-7
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,13 @@ defmodule Core.MCP.Tools.CloudConsole do
2323
end
2424

2525
def invoke(%{"name" => name}) do
26-
with %ConsoleInstance{} = console <- Cloud.get_instance_by_name(name),
27-
console = Repo.preload(console, [:cluster, owner: [account: [subscription: :plan]]]) do
28-
format(console)
26+
with %ConsoleInstance{} = console <- Cloud.get_instance_by_name(name) do
27+
Repo.preload(console, [:cluster, owner: [account: [subscription: :plan]]])
2928
|> Proto.serialize()
3029
|> Jason.encode()
3130
else
3231
_ -> {:ok, "no instance with name #{name}"}
3332
end
3433
end
3534
def invoke(_), do: {:error, "email is required"}
36-
37-
defp format(console) do
38-
Map.take(console, ~w(id type name status subdomain url cloud size region cluster owner)a)
39-
end
4035
end

Diff for: apps/core/lib/core/mcp/tools/cloud_reap.ex

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
defmodule Core.MCP.Tools.CloudReap do
2+
@behaviour Core.MCP.Tool
3+
alias Core.Repo
4+
alias Core.Services.Cloud
5+
alias Core.Mcp.Serialization.Proto
6+
alias Core.Services.Cloud
7+
alias Core.Schema.{ConsoleInstance}
8+
9+
def name(), do: "reap_cloud_cluster"
10+
11+
def description(), do: "Initiates the reaping process for a cloud cluster, which involves sending two warning emails until ultimately deleting it"
12+
13+
def schema() do
14+
%{
15+
type: "object",
16+
required: ["name"],
17+
properties: %{
18+
name: %{
19+
type: "string",
20+
description: "The name of the console instance to reap"
21+
}
22+
}
23+
}
24+
end
25+
26+
def invoke(%{"name" => name}) do
27+
with %ConsoleInstance{} = console <- Cloud.get_instance_by_name(name),
28+
{:ok, inst} <- Cloud.reap(console) do
29+
res = Repo.preload(console, [:cluster, owner: [account: [subscription: :plan]]])
30+
|> Proto.serialize()
31+
|> Jason.encode!()
32+
{:ok, """
33+
Initiated reaping of cloud console. Attributes are:
34+
35+
```json
36+
#{res}
37+
```
38+
"""}
39+
else
40+
_ -> {:ok, "no instance with name #{name}"}
41+
end
42+
end
43+
def invoke(_), do: {:error, "email is required"}
44+
end

Diff for: apps/core/test/mcp/tools/account_users_test.exs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule Core.MCP.Tools.AccountUsersTest do
2+
use Core.SchemaCase, async: true
3+
alias Core.MCP.Tools.AccountUsers
4+
5+
describe "invoke/1" do
6+
test "it will fetch a users account info" do
7+
account = insert(:account)
8+
user = insert(:user, account: account)
9+
10+
{:ok, res} = AccountUsers.invoke(%{"account_id" => account.id})
11+
{:ok, [parsed]} = Jason.decode(res)
12+
13+
assert parsed["id"] == user.id
14+
assert parsed["email"] == user.email
15+
assert parsed["name"] == user.name
16+
end
17+
end
18+
end

Diff for: apps/core/test/mcp/tools/cloud_reap.exs

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
defmodule Core.MCP.Tools.CloudReapTest do
2+
use Core.SchemaCase, async: true
3+
alias Core.MCP.Tools.CloudReap
4+
5+
describe "invoke/1" do
6+
test "it will initiate the reaping process for a cloud cluster" do
7+
inst = insert(:console_instance)
8+
9+
{:ok, res} = CloudReap.invoke(%{"name" => inst.name})
10+
11+
assert is_binary(res)
12+
13+
assert refetch(inst).first_notif_at
14+
refute refetch(inst).second_notif_at
15+
end
16+
end
17+
end

Diff for: apps/cron/lib/cron/prune/cloud.ex

+10-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ defmodule Cron.Prune.Cloud do
1212
|> ConsoleInstance.ordered(asc: :id)
1313
|> Core.Repo.stream(method: :keyset)
1414
|> Core.throttle()
15-
|> Enum.each(&Cloud.reap/1)
15+
|> Enum.each(fn inst ->
16+
Logger.info("Reaping cloud console #{inst.id}")
17+
Cloud.reap(inst)
18+
|> log_error()
19+
end)
20+
end
21+
22+
defp log_error({:ok, _}), do: :ok
23+
defp log_error(err) do
24+
Logger.error("Error reaping cloud console: #{inspect(err)}")
1625
end
1726
end

Diff for: apps/cron/lib/cron/prune/trial.ex

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ defmodule Cron.Prune.Trials do
55
use Cron
66
alias Core.Services.Payments
77
alias Core.Schema.PlatformSubscription
8-
require Logger
98

109
def run() do
1110
PlatformSubscription

Diff for: apps/cron/lib/cron/runner.ex

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ defmodule Cron.Runner do
22
use Task, restart: :transient
33
require Logger
44

5+
@sleep :timer.seconds(10)
6+
57
def start_link(_) do
68
Task.start_link(&run_cron/0)
79
end
810

911
def run_cron() do
1012
module = resolve_module()
11-
Logger.info "Starting cron #{module}"
13+
Logger.info "Sleeping to initialize dependencies for #{@sleep} seconds first..."
14+
:timer.sleep(@sleep)
15+
Logger.info "Starting cron #{module}..."
1216
module.execute()
1317
after
1418
:init.stop()

Diff for: plural/helm/plural/Chart.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ apiVersion: v2
22
name: plural
33
description: A helm chart for installing plural
44
appVersion: 0.11.8
5-
version: 0.10.104
5+
version: 0.10.105
66
dependencies:
77
- name: hydra
88
version: 0.26.5

Diff for: plural/helm/plural/templates/configurationoverlays.yaml

-22
This file was deleted.

Diff for: plural/helm/plural/templates/cron.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ spec:
2525
{{- end }}
2626
containers:
2727
- name: cron
28-
image: "{{ .Values.global.registry }}/{{ .Values.cron.repository }}:{{ .Values.cron.tag | default .Chart.AppVersion }}"
28+
image: "{{ .Values.global.registry }}/{{ .Values.cron.repository }}:{{ include "plural.imageTag" . }}"
2929
imagePullPolicy: {{ .Values.image.pullPolicy }}
3030
envFrom:
3131
- secretRef:

0 commit comments

Comments
 (0)