Scott Hiett commited on
Commit
b2ff4b5
1 Parent(s): 76486d3

Docker building and release

Browse files
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
- mode: "file",
5
- file_path: "srh-config/tokens.json"
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
- mode: System.get_env("TOKEN_RESOLUTION_MODE") || "file",
5
- file_path: System.get_env("TOKEN_RESOLUTION_FILE_PATH") || "srh-config/tokens.json"
 
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
- &(:ets.insert(@ets_table_name, &1))
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
- srh_id: "1000",
84
- connection_string: "redis://localhost:6379",
85
- max_connections: 10
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 :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,15 +13,13 @@ defmodule Srh.Http.BaseRouter do
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(
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} -> %{code: 200, message: Jason.encode!(data), json: true}
48
- {:not_found, message} -> %{code: 404, message: message, json: false}
49
- {:malformed_data, message} -> %{code: 400, message: message, json: false}
50
- {:not_authorized, message} -> %{code: 401, message: message, json: false}
51
- {:server_error, _} -> %{code: 500, message: "An error occurred internally", json: false}
 
 
 
 
 
 
 
 
 
 
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
- {:error, msg} -> {:not_authorized, msg}
 
 
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
- {:error, msg} -> {:not_authorized, msg}
 
 
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 = case dispatch_command(current, connection_info) do
45
- {:ok, result_map} ->
46
- [result_map | responses]
47
- {:malformed_data, result_json} ->
48
- # TODO: change up the chain to json this at the last moment, so this isn't here
49
- [Jason.decode!(result_json) | responses]
50
- end
 
 
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(command_array, %{"srh_id" => srh_id, "max_connections" => max_connections} = connection_info)
 
 
 
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
- error: error.message
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), do: {:ok, command_array}
 
3
 
4
  def validate_redis_body(_),
5
- do: {:error, "Invalid command array. Expected a string array at root of the command and its arguments."}
 
 
6
 
7
- def validate_pipeline_redis_body(%{"_json" => array_of_command_arrays}) when is_list(array_of_command_arrays) do
 
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) do
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 = case target >= len do
39
- true -> 0
40
- false -> target
41
- end
42
-
43
- {:reply, {:ok, Enum.at(state.worker_pids, corrected_target)}, %{state | last_worker_index: corrected_target}}
 
 
44
  end
45
  end
46
 
@@ -55,10 +57,9 @@ defmodule Srh.Redis.ClientRegistry do
55
  :noreply,
56
  %{
57
  state
58
- |
59
- worker_pids:
60
- [pid | state.worker_pids]
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