Spaces:
Sleeping
Sleeping
Scott Hiett
commited on
Commit
·
b2ff4b5
1
Parent(s):
76486d3
Docker building and release
Browse files- Dockerfile +26 -0
- config/config.exs +3 -3
- config/prod.exs +1 -0
- config/runtime.exs +2 -2
- lib/srh/auth/token_resolver.ex +6 -8
- lib/srh/http/base_router.ex +23 -14
- lib/srh/http/command_handler.ex +27 -16
- lib/srh/http/request_validator.ex +8 -3
- lib/srh/redis/client.ex +1 -1
- lib/srh/redis/client_registry.ex +11 -10
- mix.exs +7 -1
- rel/env.bat.eex +5 -0
- rel/env.sh.eex +17 -0
- rel/remote.vm.args.eex +11 -0
- rel/vm.args.eex +11 -0
Dockerfile
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM elixir:1.13.4-alpine AS builder
|
2 |
+
|
3 |
+
WORKDIR /app
|
4 |
+
|
5 |
+
COPY mix.exs .
|
6 |
+
COPY mix.lock .
|
7 |
+
COPY .formatter.exs .
|
8 |
+
|
9 |
+
RUN mix local.hex --force
|
10 |
+
RUN mix local.rebar --force
|
11 |
+
RUN mix deps.get
|
12 |
+
|
13 |
+
COPY lib/ ./lib/
|
14 |
+
COPY config/ ./config/
|
15 |
+
|
16 |
+
ENV MIX_ENV=prod
|
17 |
+
RUN mix release
|
18 |
+
|
19 |
+
FROM elixir:1.13.4-alpine
|
20 |
+
|
21 |
+
WORKDIR /app
|
22 |
+
|
23 |
+
COPY --from=builder /app/_build/prod/rel/prod/ ./_build/prod/rel/prod/
|
24 |
+
|
25 |
+
ENV MIX_ENV=prod
|
26 |
+
CMD ["_build/prod/rel/prod/bin/prod", "start"]
|
config/config.exs
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
import Config
|
2 |
|
3 |
config :srh,
|
4 |
-
|
5 |
-
|
6 |
|
7 |
-
import_config "#{config_env()}.exs"
|
|
|
1 |
import Config
|
2 |
|
3 |
config :srh,
|
4 |
+
mode: "file",
|
5 |
+
file_path: "srh-config/tokens.json"
|
6 |
|
7 |
+
import_config "#{config_env()}.exs"
|
config/prod.exs
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
import Config
|
config/runtime.exs
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
import Config
|
2 |
|
3 |
config :srh,
|
4 |
-
|
5 |
-
|
|
|
1 |
import Config
|
2 |
|
3 |
config :srh,
|
4 |
+
mode: System.get_env("TOKEN_RESOLUTION_MODE") || "file",
|
5 |
+
file_path: System.get_env("TOKEN_RESOLUTION_FILE_PATH") || "srh-config/tokens.json"
|
lib/srh/auth/token_resolver.ex
CHANGED
@@ -55,7 +55,7 @@ defmodule Srh.Auth.TokenResolver do
|
|
55 |
# Load this into ETS
|
56 |
Enum.each(
|
57 |
config_file_data,
|
58 |
-
|
59 |
)
|
60 |
end
|
61 |
|
@@ -78,13 +78,11 @@ defmodule Srh.Auth.TokenResolver do
|
|
78 |
:ok,
|
79 |
# This is done to replicate what will eventually be API endpoints, so they keys are not atoms
|
80 |
Jason.decode!(
|
81 |
-
Jason.encode!(
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
}
|
87 |
-
)
|
88 |
)
|
89 |
}
|
90 |
end
|
|
|
55 |
# Load this into ETS
|
56 |
Enum.each(
|
57 |
config_file_data,
|
58 |
+
&:ets.insert(@ets_table_name, &1)
|
59 |
)
|
60 |
end
|
61 |
|
|
|
78 |
:ok,
|
79 |
# This is done to replicate what will eventually be API endpoints, so they keys are not atoms
|
80 |
Jason.decode!(
|
81 |
+
Jason.encode!(%{
|
82 |
+
srh_id: "1000",
|
83 |
+
connection_string: "redis://localhost:6379",
|
84 |
+
max_connections: 10
|
85 |
+
})
|
|
|
|
|
86 |
)
|
87 |
}
|
88 |
end
|
lib/srh/http/base_router.ex
CHANGED
@@ -3,9 +3,9 @@ defmodule Srh.Http.BaseRouter do
|
|
3 |
alias Srh.Http.RequestValidator
|
4 |
alias Srh.Http.CommandHandler
|
5 |
|
6 |
-
plug
|
7 |
-
plug
|
8 |
-
plug
|
9 |
|
10 |
get "/" do
|
11 |
handle_response({:ok, "Welcome to Serverless Redis HTTP!"}, conn)
|
@@ -13,15 +13,13 @@ defmodule Srh.Http.BaseRouter do
|
|
13 |
|
14 |
post "/" do
|
15 |
conn
|
16 |
-
|> handle_extract_auth(&
|
17 |
|> handle_response(conn)
|
18 |
end
|
19 |
|
20 |
post "/pipeline" do
|
21 |
conn
|
22 |
-
|> handle_extract_auth(
|
23 |
-
&(CommandHandler.handle_command_array(conn, &1))
|
24 |
-
)
|
25 |
|> handle_response(conn)
|
26 |
end
|
27 |
|
@@ -32,10 +30,10 @@ defmodule Srh.Http.BaseRouter do
|
|
32 |
defp handle_extract_auth(conn, success_lambda) do
|
33 |
case conn
|
34 |
|> get_req_header("authorization")
|
35 |
-
|> RequestValidator.validate_bearer_header()
|
36 |
-
do
|
37 |
{:ok, token} ->
|
38 |
success_lambda.(token)
|
|
|
39 |
{:error, _} ->
|
40 |
{:malformed_data, "Missing/Invalid authorization header"}
|
41 |
end
|
@@ -44,11 +42,21 @@ defmodule Srh.Http.BaseRouter do
|
|
44 |
defp handle_response(response, conn) do
|
45 |
%{code: code, message: message, json: json} =
|
46 |
case response do
|
47 |
-
{:ok, data} ->
|
48 |
-
|
49 |
-
|
50 |
-
{:
|
51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
52 |
_ ->
|
53 |
%{code: 500, message: "An error occurred internally", json: false}
|
54 |
end
|
@@ -57,6 +65,7 @@ defmodule Srh.Http.BaseRouter do
|
|
57 |
true ->
|
58 |
conn
|
59 |
|> put_resp_header("content-type", "application/json")
|
|
|
60 |
false ->
|
61 |
conn
|
62 |
end
|
|
|
3 |
alias Srh.Http.RequestValidator
|
4 |
alias Srh.Http.CommandHandler
|
5 |
|
6 |
+
plug(:match)
|
7 |
+
plug(Plug.Parsers, parsers: [:json], pass: ["application/json"], json_decoder: Jason)
|
8 |
+
plug(:dispatch)
|
9 |
|
10 |
get "/" do
|
11 |
handle_response({:ok, "Welcome to Serverless Redis HTTP!"}, conn)
|
|
|
13 |
|
14 |
post "/" do
|
15 |
conn
|
16 |
+
|> handle_extract_auth(&CommandHandler.handle_command(conn, &1))
|
17 |
|> handle_response(conn)
|
18 |
end
|
19 |
|
20 |
post "/pipeline" do
|
21 |
conn
|
22 |
+
|> handle_extract_auth(&CommandHandler.handle_command_array(conn, &1))
|
|
|
|
|
23 |
|> handle_response(conn)
|
24 |
end
|
25 |
|
|
|
30 |
defp handle_extract_auth(conn, success_lambda) do
|
31 |
case conn
|
32 |
|> get_req_header("authorization")
|
33 |
+
|> RequestValidator.validate_bearer_header() do
|
|
|
34 |
{:ok, token} ->
|
35 |
success_lambda.(token)
|
36 |
+
|
37 |
{:error, _} ->
|
38 |
{:malformed_data, "Missing/Invalid authorization header"}
|
39 |
end
|
|
|
42 |
defp handle_response(response, conn) do
|
43 |
%{code: code, message: message, json: json} =
|
44 |
case response do
|
45 |
+
{:ok, data} ->
|
46 |
+
%{code: 200, message: Jason.encode!(data), json: true}
|
47 |
+
|
48 |
+
{:not_found, message} ->
|
49 |
+
%{code: 404, message: message, json: false}
|
50 |
+
|
51 |
+
{:malformed_data, message} ->
|
52 |
+
%{code: 400, message: message, json: false}
|
53 |
+
|
54 |
+
{:not_authorized, message} ->
|
55 |
+
%{code: 401, message: message, json: false}
|
56 |
+
|
57 |
+
{:server_error, _} ->
|
58 |
+
%{code: 500, message: "An error occurred internally", json: false}
|
59 |
+
|
60 |
_ ->
|
61 |
%{code: 500, message: "An error occurred internally", json: false}
|
62 |
end
|
|
|
65 |
true ->
|
66 |
conn
|
67 |
|> put_resp_header("content-type", "application/json")
|
68 |
+
|
69 |
false ->
|
70 |
conn
|
71 |
end
|
lib/srh/http/command_handler.ex
CHANGED
@@ -8,6 +8,7 @@ defmodule Srh.Http.CommandHandler do
|
|
8 |
case RequestValidator.validate_redis_body(conn.body_params) do
|
9 |
{:ok, command_array} ->
|
10 |
do_handle_command(command_array, token)
|
|
|
11 |
{:error, error_message} ->
|
12 |
{:malformed_data, error_message}
|
13 |
end
|
@@ -17,6 +18,7 @@ defmodule Srh.Http.CommandHandler do
|
|
17 |
case RequestValidator.validate_pipeline_redis_body(conn.body_params) do
|
18 |
{:ok, array_of_command_arrays} ->
|
19 |
do_handle_command_array(array_of_command_arrays, token)
|
|
|
20 |
{:error, error_message} ->
|
21 |
{:malformed_data, error_message}
|
22 |
end
|
@@ -26,7 +28,9 @@ defmodule Srh.Http.CommandHandler do
|
|
26 |
case TokenResolver.resolve(token) do
|
27 |
{:ok, connection_info} ->
|
28 |
dispatch_command(command_array, connection_info)
|
29 |
-
|
|
|
|
|
30 |
end
|
31 |
end
|
32 |
|
@@ -34,20 +38,24 @@ defmodule Srh.Http.CommandHandler do
|
|
34 |
case TokenResolver.resolve(token) do
|
35 |
{:ok, connection_info} ->
|
36 |
dispatch_command_array(array_of_command_arrays, connection_info)
|
37 |
-
|
|
|
|
|
38 |
end
|
39 |
end
|
40 |
|
41 |
defp dispatch_command_array(_arr, _connection_info, responses \\ [])
|
42 |
-
|
43 |
defp dispatch_command_array([current | rest], connection_info, responses) do
|
44 |
-
updated_responses =
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
|
|
51 |
|
52 |
dispatch_command_array(rest, connection_info, updated_responses)
|
53 |
end
|
@@ -57,7 +65,10 @@ defmodule Srh.Http.CommandHandler do
|
|
57 |
{:ok, Enum.reverse(responses)}
|
58 |
end
|
59 |
|
60 |
-
defp dispatch_command(
|
|
|
|
|
|
|
61 |
when is_number(max_connections) do
|
62 |
case GenRegistry.lookup_or_start(Client, srh_id, [max_connections, connection_info]) do
|
63 |
{:ok, pid} ->
|
@@ -66,16 +77,16 @@ defmodule Srh.Http.CommandHandler do
|
|
66 |
|> ClientWorker.redis_command(command_array) do
|
67 |
{:ok, res} ->
|
68 |
{:ok, %{result: res}}
|
|
|
69 |
{:error, error} ->
|
70 |
{
|
71 |
:malformed_data,
|
72 |
-
Jason.encode!(
|
73 |
-
|
74 |
-
|
75 |
-
}
|
76 |
-
)
|
77 |
}
|
78 |
end
|
|
|
79 |
{:error, msg} ->
|
80 |
{:server_error, msg}
|
81 |
end
|
|
|
8 |
case RequestValidator.validate_redis_body(conn.body_params) do
|
9 |
{:ok, command_array} ->
|
10 |
do_handle_command(command_array, token)
|
11 |
+
|
12 |
{:error, error_message} ->
|
13 |
{:malformed_data, error_message}
|
14 |
end
|
|
|
18 |
case RequestValidator.validate_pipeline_redis_body(conn.body_params) do
|
19 |
{:ok, array_of_command_arrays} ->
|
20 |
do_handle_command_array(array_of_command_arrays, token)
|
21 |
+
|
22 |
{:error, error_message} ->
|
23 |
{:malformed_data, error_message}
|
24 |
end
|
|
|
28 |
case TokenResolver.resolve(token) do
|
29 |
{:ok, connection_info} ->
|
30 |
dispatch_command(command_array, connection_info)
|
31 |
+
|
32 |
+
{:error, msg} ->
|
33 |
+
{:not_authorized, msg}
|
34 |
end
|
35 |
end
|
36 |
|
|
|
38 |
case TokenResolver.resolve(token) do
|
39 |
{:ok, connection_info} ->
|
40 |
dispatch_command_array(array_of_command_arrays, connection_info)
|
41 |
+
|
42 |
+
{:error, msg} ->
|
43 |
+
{:not_authorized, msg}
|
44 |
end
|
45 |
end
|
46 |
|
47 |
defp dispatch_command_array(_arr, _connection_info, responses \\ [])
|
48 |
+
|
49 |
defp dispatch_command_array([current | rest], connection_info, responses) do
|
50 |
+
updated_responses =
|
51 |
+
case dispatch_command(current, connection_info) do
|
52 |
+
{:ok, result_map} ->
|
53 |
+
[result_map | responses]
|
54 |
+
|
55 |
+
{:malformed_data, result_json} ->
|
56 |
+
# TODO: change up the chain to json this at the last moment, so this isn't here
|
57 |
+
[Jason.decode!(result_json) | responses]
|
58 |
+
end
|
59 |
|
60 |
dispatch_command_array(rest, connection_info, updated_responses)
|
61 |
end
|
|
|
65 |
{:ok, Enum.reverse(responses)}
|
66 |
end
|
67 |
|
68 |
+
defp dispatch_command(
|
69 |
+
command_array,
|
70 |
+
%{"srh_id" => srh_id, "max_connections" => max_connections} = connection_info
|
71 |
+
)
|
72 |
when is_number(max_connections) do
|
73 |
case GenRegistry.lookup_or_start(Client, srh_id, [max_connections, connection_info]) do
|
74 |
{:ok, pid} ->
|
|
|
77 |
|> ClientWorker.redis_command(command_array) do
|
78 |
{:ok, res} ->
|
79 |
{:ok, %{result: res}}
|
80 |
+
|
81 |
{:error, error} ->
|
82 |
{
|
83 |
:malformed_data,
|
84 |
+
Jason.encode!(%{
|
85 |
+
error: error.message
|
86 |
+
})
|
|
|
|
|
87 |
}
|
88 |
end
|
89 |
+
|
90 |
{:error, msg} ->
|
91 |
{:server_error, msg}
|
92 |
end
|
lib/srh/http/request_validator.ex
CHANGED
@@ -1,10 +1,14 @@
|
|
1 |
defmodule Srh.Http.RequestValidator do
|
2 |
-
def validate_redis_body(%{"_json" => command_array}) when is_list(command_array),
|
|
|
3 |
|
4 |
def validate_redis_body(_),
|
5 |
-
|
|
|
|
|
6 |
|
7 |
-
def validate_pipeline_redis_body(%{"_json" => array_of_command_arrays})
|
|
|
8 |
do_validate_pipeline_redis_body(array_of_command_arrays, array_of_command_arrays)
|
9 |
end
|
10 |
|
@@ -32,6 +36,7 @@ defmodule Srh.Http.RequestValidator do
|
|
32 |
|> String.split(" ") do
|
33 |
["Bearer", token] ->
|
34 |
{:ok, token}
|
|
|
35 |
_ ->
|
36 |
do_validate_bearer_header(rest)
|
37 |
end
|
|
|
1 |
defmodule Srh.Http.RequestValidator do
|
2 |
+
def validate_redis_body(%{"_json" => command_array}) when is_list(command_array),
|
3 |
+
do: {:ok, command_array}
|
4 |
|
5 |
def validate_redis_body(_),
|
6 |
+
do:
|
7 |
+
{:error,
|
8 |
+
"Invalid command array. Expected a string array at root of the command and its arguments."}
|
9 |
|
10 |
+
def validate_pipeline_redis_body(%{"_json" => array_of_command_arrays})
|
11 |
+
when is_list(array_of_command_arrays) do
|
12 |
do_validate_pipeline_redis_body(array_of_command_arrays, array_of_command_arrays)
|
13 |
end
|
14 |
|
|
|
36 |
|> String.split(" ") do
|
37 |
["Bearer", token] ->
|
38 |
{:ok, token}
|
39 |
+
|
40 |
_ ->
|
41 |
do_validate_bearer_header(rest)
|
42 |
end
|
lib/srh/redis/client.ex
CHANGED
@@ -23,7 +23,7 @@ defmodule Srh.Redis.Client do
|
|
23 |
}
|
24 |
end
|
25 |
|
26 |
-
def find_worker(client)
|
27 |
GenServer.call(client, {:find_worker})
|
28 |
end
|
29 |
|
|
|
23 |
}
|
24 |
end
|
25 |
|
26 |
+
def find_worker(client) do
|
27 |
GenServer.call(client, {:find_worker})
|
28 |
end
|
29 |
|
lib/srh/redis/client_registry.ex
CHANGED
@@ -35,12 +35,14 @@ defmodule Srh.Redis.ClientRegistry do
|
|
35 |
len ->
|
36 |
target = state.last_worker_index + 1
|
37 |
|
38 |
-
corrected_target =
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
|
|
|
|
44 |
end
|
45 |
end
|
46 |
|
@@ -55,10 +57,9 @@ defmodule Srh.Redis.ClientRegistry do
|
|
55 |
:noreply,
|
56 |
%{
|
57 |
state
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|> Enum.uniq()
|
62 |
}
|
63 |
}
|
64 |
end
|
|
|
35 |
len ->
|
36 |
target = state.last_worker_index + 1
|
37 |
|
38 |
+
corrected_target =
|
39 |
+
case target >= len do
|
40 |
+
true -> 0
|
41 |
+
false -> target
|
42 |
+
end
|
43 |
+
|
44 |
+
{:reply, {:ok, Enum.at(state.worker_pids, corrected_target)},
|
45 |
+
%{state | last_worker_index: corrected_target}}
|
46 |
end
|
47 |
end
|
48 |
|
|
|
57 |
:noreply,
|
58 |
%{
|
59 |
state
|
60 |
+
| worker_pids:
|
61 |
+
[pid | state.worker_pids]
|
62 |
+
|> Enum.uniq()
|
|
|
63 |
}
|
64 |
}
|
65 |
end
|
mix.exs
CHANGED
@@ -8,7 +8,13 @@ defmodule Srh.MixProject do
|
|
8 |
elixir: "~> 1.13",
|
9 |
start_permanent: Mix.env() == :prod,
|
10 |
config_path: "config/config.exs",
|
11 |
-
deps: deps()
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
]
|
13 |
end
|
14 |
|
|
|
8 |
elixir: "~> 1.13",
|
9 |
start_permanent: Mix.env() == :prod,
|
10 |
config_path: "config/config.exs",
|
11 |
+
deps: deps(),
|
12 |
+
releases: [
|
13 |
+
prod: [
|
14 |
+
include_executables_for: [:unix],
|
15 |
+
steps: [:assemble, :tar]
|
16 |
+
]
|
17 |
+
]
|
18 |
]
|
19 |
end
|
20 |
|
rel/env.bat.eex
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
@echo off
|
2 |
+
rem Set the release to work across nodes.
|
3 |
+
rem RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none".
|
4 |
+
rem set RELEASE_DISTRIBUTION=name
|
5 |
+
rem set RELEASE_NODE=<%= @release.name %>
|
rel/env.sh.eex
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/bin/sh
|
2 |
+
|
3 |
+
# Sets and enables heart (recommended only in daemon mode)
|
4 |
+
# case $RELEASE_COMMAND in
|
5 |
+
# daemon*)
|
6 |
+
# HEART_COMMAND="$RELEASE_ROOT/bin/$RELEASE_NAME $RELEASE_COMMAND"
|
7 |
+
# export HEART_COMMAND
|
8 |
+
# export ELIXIR_ERL_OPTIONS="-heart"
|
9 |
+
# ;;
|
10 |
+
# *)
|
11 |
+
# ;;
|
12 |
+
# esac
|
13 |
+
|
14 |
+
# Set the release to work across nodes.
|
15 |
+
# RELEASE_DISTRIBUTION must be "sname" (local), "name" (distributed) or "none".
|
16 |
+
# export RELEASE_DISTRIBUTION=name
|
17 |
+
# export RELEASE_NODE=<%= @release.name %>
|
rel/remote.vm.args.eex
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
## Customize flags given to the VM: https://erlang.org/doc/man/erl.html
|
2 |
+
## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
|
3 |
+
|
4 |
+
## Number of dirty schedulers doing IO work (file, sockets, and others)
|
5 |
+
##+SDio 5
|
6 |
+
|
7 |
+
## Increase number of concurrent ports/sockets
|
8 |
+
##+Q 65536
|
9 |
+
|
10 |
+
## Tweak GC to run more often
|
11 |
+
##-env ERL_FULLSWEEP_AFTER 10
|
rel/vm.args.eex
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
## Customize flags given to the VM: https://erlang.org/doc/man/erl.html
|
2 |
+
## -mode/-name/-sname/-setcookie are configured via env vars, do not set them here
|
3 |
+
|
4 |
+
## Number of dirty schedulers doing IO work (file, sockets, and others)
|
5 |
+
##+SDio 5
|
6 |
+
|
7 |
+
## Increase number of concurrent ports/sockets
|
8 |
+
##+Q 65536
|
9 |
+
|
10 |
+
## Tweak GC to run more often
|
11 |
+
##-env ERL_FULLSWEEP_AFTER 10
|