Skip to content

Commit af86afb

Browse files
Implement vpn crud apis (#294)
1 parent 989da8e commit af86afb

28 files changed

+753
-10
lines changed

lib/console/application.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ defmodule Console.Application do
1212
Console.Clustering.Connect,
1313
Console.Commands.Configuration,
1414
Console.Plural.Config,
15+
Console.Features,
1516
Console.Cron,
1617
Console.Cache,
1718
Console.ReplicatedCache,
@@ -20,6 +21,7 @@ defmodule Console.Application do
2021
{Absinthe.Subscription, [ConsoleWeb.Endpoint]},
2122
Console.Cached.Namespace,
2223
Console.Cached.Pod,
24+
Console.Cached.VPN,
2325
{OpenIDConnect.Worker, Application.get_env(:console, :oidc_providers)},
2426
] ++ consumers() ++ [
2527
Piazza.GracefulShutdown

lib/console/cached/kubernetes.ex

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ defmodule Console.Cached.Kubernetes do
22
use GenServer
33
alias Kazan.Watcher
44
alias ETS.KeyValueSet
5-
alias Kazan.Models.Apimachinery.Meta.V1, as: MetaV1
65
require Logger
76

87
defmodule State, do: defstruct [:table, :model, :pid]
@@ -29,9 +28,16 @@ defmodule Console.Cached.Kubernetes do
2928
|> Enum.map(fn {_, v} -> v end)
3029
end
3130

31+
def get(name, key) do
32+
case KeyValueSet.wrap_existing(name) do
33+
{:ok, set} -> set[key]
34+
_ -> nil
35+
end
36+
end
37+
3238
def handle_info({:start, request}, %State{table: table, model: model} = state) do
3339
Logger.info "starting namespace watcher"
34-
{:ok, %{items: instances, metadata: %MetaV1.ListMeta{resource_version: vsn}}} = Kazan.run(request)
40+
{:ok, %{items: instances, metadata: %{resource_version: vsn}}} = Kazan.run(request)
3541
{:ok, pid} = Watcher.start_link(%{request | response_model: model}, send_to: self(), resource_vsn: vsn)
3642

3743
:timer.send_interval(5000, :watcher_ping)

lib/console/cached/vpn.ex

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
defmodule Console.Cached.VPN do
2+
@moduledoc """
3+
This genserver will query and poll all pods then cache them in-memory. This is to accelerate pod lookups since
4+
it's pretty damn slow currently
5+
"""
6+
use Console.Cached.Base
7+
import Kube.Client.Base, only: [path_builder: 4]
8+
alias Kube.{WireguardServer, WireguardServerList}
9+
10+
def start_link(), do: Console.Cached.Kubernetes.start_link(__MODULE__, vpn_request(), WireguardServer)
11+
12+
def start(), do: Console.Cached.Kubernetes.start(__MODULE__, vpn_request(), WireguardServer)
13+
14+
def fetch(), do: Console.Cached.Kubernetes.fetch(__MODULE__)
15+
16+
def get(key), do: Console.Cached.Kubernetes.get(__MODULE__, key)
17+
18+
defp vpn_request() do
19+
%Kazan.Request{
20+
method: "get",
21+
path: path_builder("vpn.plural.sh", "v1alpha1", "wireguardservers", Console.namespace("wireguard")),
22+
body: "",
23+
query_params: %{},
24+
content_type: "application/json",
25+
response_model: WireguardServerList
26+
}
27+
end
28+
end

lib/console/configuration.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
defmodule Console.Configuration do
2-
defstruct [:git_commit, :is_demo_project, :is_sandbox, :plural_login]
2+
defstruct [:git_commit, :is_demo_project, :is_sandbox, :plural_login, :vpn_enabled, :features]
33

44
def new() do
55
%__MODULE__{
66
git_commit: Console.conf(:git_commit),
77
is_demo_project: Console.conf(:is_demo_project),
88
is_sandbox: Console.sandbox?(),
9-
plural_login: Console.conf(:plural_login)
9+
plural_login: Console.conf(:plural_login),
10+
vpn_enabled: Console.Services.VPN.enabled?(),
11+
features: Console.Features.fetch()
1012
}
1113
end
1214
end

lib/console/features.ex

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
defmodule Console.Features do
2+
use GenServer
3+
alias Console.Plural.{Accounts, Account, Features}
4+
5+
def start_link(_opts \\ :ok) do
6+
GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
7+
end
8+
9+
def start() do
10+
GenServer.start_link(__MODULE__, :ok)
11+
end
12+
13+
def init(_) do
14+
if Console.conf(:initialize) do
15+
:timer.send_interval(:timer.seconds(60), :poll)
16+
send self(), :poll
17+
end
18+
19+
{:ok, %Features{}}
20+
end
21+
22+
def available?(feature) do
23+
case fetch() do
24+
%{^feature => true} -> true
25+
_ -> false
26+
end
27+
end
28+
29+
def fetch(), do: GenServer.call(__MODULE__, :fetch)
30+
31+
def handle_call(:fetch, _, state), do: {:reply, state, state}
32+
33+
def handle_info(:poll, state) do
34+
case Accounts.account() do
35+
{:ok, %Account{availableFeatures: %Features{} = feats}} -> {:noreply, feats}
36+
_ -> {:noreply, state}
37+
end
38+
end
39+
end

lib/console/graphql.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ defmodule Console.GraphQl do
22
use Absinthe.Schema
33
use Absinthe.Relay.Schema, :modern
44
import Console.GraphQl.Helpers
5-
alias Console.GraphQl.Resolvers.{Build, User, Kubecost, License}
5+
alias Console.GraphQl.Resolvers.{Build, User, Kubecost, License, UserLoader}
66

77
import_types Absinthe.Type.Custom
88
import_types Absinthe.Plug.Types
@@ -23,7 +23,8 @@ defmodule Console.GraphQl do
2323
Build,
2424
User,
2525
Kubecost,
26-
License
26+
License,
27+
UserLoader
2728
]
2829

2930
def context(ctx) do

lib/console/graphql/configuration.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,17 @@ defmodule Console.GraphQl.Configuration do
2929
field :output, :string
3030
end
3131

32+
object :available_features do
33+
field :vpn, :boolean
34+
end
35+
3236
object :console_configuration do
3337
field :git_commit, :string
3438
field :is_demo_project, :boolean
3539
field :is_sandbox, :boolean
3640
field :plural_login, :boolean
41+
field :vpn_enabled, :boolean
42+
field :features, :available_features
3743

3844
field :manifest, :plural_manifest, resolve: fn
3945
_, _, _ ->

lib/console/graphql/kubernetes.ex

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
defmodule Console.GraphQl.Kubernetes do
22
use Console.GraphQl.Schema.Base
33
alias Console.GraphQl.Resolvers.Kubernetes
4-
alias Console.Middleware.{Authenticated, AdminRequired, Rbac}
4+
alias Console.GraphQl.Resolvers.VPN
5+
alias Console.Middleware.{Authenticated, AdminRequired, Rbac, Feature}
56

67
object :metadata do
78
field :labels, list_of(:label_pair), resolve: fn %{labels: labels}, _, _ -> {:ok, make_labels(labels)} end
@@ -63,6 +64,7 @@ defmodule Console.GraphQl.Kubernetes do
6364
import_types Console.GraphQl.Kubernetes.ConfigurationOverlay
6465
import_types Console.GraphQl.Kubernetes.VerticalPodAutoscaler
6566
import_types Console.GraphQl.Kubernetes.Namespace
67+
import_types Console.GraphQl.Kubernetes.VPN
6668

6769
delta :application
6870

@@ -164,6 +166,26 @@ defmodule Console.GraphQl.Kubernetes do
164166
safe_resolve &Kubernetes.list_all_pods/2
165167
end
166168

169+
field :wireguard_peers, list_of(:wireguard_peer) do
170+
middleware Authenticated
171+
middleware AdminRequired
172+
173+
safe_resolve &VPN.list_peers/2
174+
end
175+
176+
field :my_wireguard_peers, list_of(:wireguard_peer) do
177+
middleware Authenticated
178+
179+
safe_resolve &VPN.list_my_peers/2
180+
end
181+
182+
field :wireguard_peer, :wireguard_peer do
183+
middleware Authenticated
184+
arg :name, non_null(:string)
185+
186+
safe_resolve &VPN.get_peer/2
187+
end
188+
167189
field :cached_pods, list_of(:pod) do
168190
middleware Authenticated
169191
arg :namespaces, list_of(:string)
@@ -241,6 +263,26 @@ defmodule Console.GraphQl.Kubernetes do
241263

242264
safe_resolve &Kubernetes.execute_overlay/2
243265
end
266+
267+
field :create_peer, :wireguard_peer do
268+
middleware Authenticated
269+
middleware AdminRequired
270+
middleware Feature, :vpn
271+
arg :user_id, :id
272+
arg :email, :string
273+
arg :name, non_null(:string)
274+
275+
safe_resolve &VPN.create_peer/2
276+
end
277+
278+
field :delete_peer, :wireguard_peer do
279+
middleware Authenticated
280+
middleware AdminRequired
281+
middleware Feature, :vpn
282+
arg :name, non_null(:string)
283+
284+
safe_resolve &VPN.delete_peer/2
285+
end
244286
end
245287

246288
object :kubernetes_subscriptions do
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
defmodule Console.GraphQl.Kubernetes.VPN do
2+
use Console.GraphQl.Schema.Base
3+
import Console.GraphQl.Kubernetes.Base
4+
alias Console.GraphQl.Resolvers.UserLoader
5+
6+
object :wireguard_peer do
7+
field :metadata, non_null(:metadata)
8+
field :status, non_null(:wireguard_peer_status)
9+
field :spec, non_null(:wireguard_peer_spec)
10+
11+
field :config, :string, resolve: fn
12+
peer, _, _ -> Console.Services.VPN.config(peer)
13+
end
14+
15+
field :user, :user, resolve: fn
16+
%{metadata: %{labels: %{"vpn.plural.sh/email" => email}}}, _, %{context: %{loader: loader}} when is_binary(email) ->
17+
loader
18+
|> Dataloader.load(UserLoader, :email, email)
19+
|> on_load(fn loader ->
20+
{:ok, Dataloader.get(loader, UserLoader, :email, email)}
21+
end)
22+
_, _, _ -> {:ok, nil}
23+
end
24+
25+
field :raw, non_null(:string), resolve: fn model, _, _ -> encode(model) end
26+
end
27+
28+
object :wireguard_server do
29+
field :metadata, non_null(:metadata)
30+
field :status, non_null(:wireguard_server_status)
31+
32+
field :raw, non_null(:string), resolve: fn model, _, _ -> encode(model) end
33+
end
34+
35+
object :wireguard_peer_status do
36+
field :ready, :boolean
37+
field :conditions, list_of(:status_condition)
38+
end
39+
40+
object :wireguard_peer_spec do
41+
field :wireguard_ref, :string
42+
end
43+
44+
object :wireguard_server_status do
45+
field :ready, :boolean
46+
field :conditions, list_of(:status_condition)
47+
end
48+
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
defmodule Console.Middleware.Feature do
2+
@behaviour Absinthe.Middleware
3+
alias Console.Features
4+
5+
def call(resolution, feature) do
6+
case Features.available?(feature) do
7+
true -> resolution
8+
_ -> Absinthe.Resolution.put_result(resolution, {:error, "you don't have the #{feature} enabled"})
9+
end
10+
end
11+
end

0 commit comments

Comments
 (0)