From 72294869c41be48b03c23378e8435a65f4db0970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Mon, 18 Sep 2023 11:30:15 +0200 Subject: [PATCH 1/9] Make metrics and endpoint ip and port configurable --- config/dev.exs | 5 ++- config/runtime.exs | 56 ++++++++++++++++++++-------------- lib/jellyfish_web/telemetry.ex | 12 ++++++-- 3 files changed, 44 insertions(+), 29 deletions(-) diff --git a/config/dev.exs b/config/dev.exs index 34109d39..92e0057e 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -4,14 +4,13 @@ config :jellyfish, server_api_token: "development", dev_routes: true # For development, we disable any cache and enable # debugging and code reloading. +# We set ip and port in the runtime.exs to allow +# for spawning more thane one Jellyfish instance. # # The watchers configuration can be used to run external # watchers to your application. For example, we use it # with esbuild to bundle .js and .css sources. config :jellyfish, JellyfishWeb.Endpoint, - # Binding to loopback ipv4 address prevents access from other machines. - # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. - http: [ip: {127, 0, 0, 1}, port: 5002], check_origin: false, code_reloader: true, debug_errors: true, diff --git a/config/runtime.exs b/config/runtime.exs index f04c3175..3013f776 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -7,26 +7,6 @@ import Config # any compile-time configuration in here, as it won't be applied. # The block below contains prod specific runtime configuration. defmodule ConfigParser do - def parse_integrated_turn_ip(addr) do - addr = addr |> to_charlist() - - case :inet.parse_address(addr) do - {:ok, parsed_ip} -> - parsed_ip - - _error -> - with {:ok, parsed_ip} <- :inet.getaddr(addr, :inet) do - parsed_ip - else - _error -> - raise(""" - Bad integrated TURN address. Expected IPv4 or a valid hostname, got: \ - #{inspect(addr)} - """) - end - end - end - def parse_integrated_turn_port_range(range) do with [str1, str2] <- String.split(range, "-"), from when from in 0..65_535 <- String.to_integer(str1), @@ -42,6 +22,25 @@ defmodule ConfigParser do end end + def parse_ip(var_value, var_name) do + var_value = var_value |> to_charlist() + + case :inet.parse_address(var_value) do + {:ok, parsed_ip} -> + parsed_ip + + _error -> + with {:ok, parsed_ip} <- :inet.getaddr(var_value, :inet) do + parsed_ip + else + _error -> + raise(""" + Bad #{var_name} environment variable value. Expected valid ip address, got: #{inspect(var_value)} + """) + end + end + end + def parse_port_number(nil, _var_name), do: nil def parse_port_number(var_value, var_name) do @@ -102,10 +101,11 @@ config :ex_dtls, impl: :nif config :jellyfish, webrtc_used: String.downcase(System.get_env("WEBRTC_USED", "true")) not in ["false", "f", "0"], integrated_turn_ip: - System.get_env("INTEGRATED_TURN_IP", "127.0.0.1") |> ConfigParser.parse_integrated_turn_ip(), + System.get_env("INTEGRATED_TURN_IP", "127.0.0.1") + |> ConfigParser.parse_ip("INTEGRATED_TURN_IP"), integrated_turn_listen_ip: System.get_env("INTEGRATED_TURN_LISTEN_IP", "127.0.0.1") - |> ConfigParser.parse_integrated_turn_ip(), + |> ConfigParser.parse_ip("INTEGRATED_TURN_LISTEN_IP"), integrated_turn_port_range: System.get_env("INTEGRATED_TURN_PORT_RANGE", "50000-59999") |> ConfigParser.parse_integrated_turn_port_range(), @@ -114,10 +114,20 @@ config :jellyfish, |> ConfigParser.parse_port_number("INTEGRATED_TURN_TCP_PORT"), jwt_max_age: 24 * 3600, output_base_path: System.get_env("OUTPUT_BASE_PATH", "jellyfish_output") |> Path.expand(), - address: jellyfish_address + address: jellyfish_address, + metrics_ip: System.get_env("METRICS_IP", "127.0.0.1") |> ConfigParser.parse_ip("METRICS_IP"), + metrics_port: + System.get_env("METRICS_PORT", "9568") |> ConfigParser.parse_port_number("METRICS_PORT") config :opentelemetry, traces_exporter: :none +# we set ip and port here to allow for +# running multiple Jellyfishes in development +config :jellyfish, JellyfishWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: port] + if prod? do token = System.fetch_env!("SERVER_API_TOKEN") || diff --git a/lib/jellyfish_web/telemetry.ex b/lib/jellyfish_web/telemetry.ex index 4587fbbb..e7b63446 100644 --- a/lib/jellyfish_web/telemetry.ex +++ b/lib/jellyfish_web/telemetry.ex @@ -11,11 +11,17 @@ defmodule JellyfishWeb.Telemetry do @impl true def init(_arg) do - children = [ - MetricsAggregator, - {TelemetryMetricsPrometheus, metrics: metrics(&last_value/2)} + metrics_ip = Application.fetch_env!(:jellyfish, :metrics_ip) + metrics_port = Application.fetch_env!(:jellyfish, :metrics_port) + + metrics_opts = [ + metrics: metrics(&last_value/2), + port: metrics_port, + plug_cowboy_opts: [ip: metrics_ip] ] + children = [MetricsAggregator, {TelemetryMetricsPrometheus, metrics_opts}] + Supervisor.init(children, strategy: :one_for_one) end From 5b43f860dafa1852ae3e92f7fcbc0c0a6ac9002c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Mon, 18 Sep 2023 12:40:35 +0200 Subject: [PATCH 2/9] Move endpoint config into the case --- config/runtime.exs | 154 +++++++++++++++++++++++---------------------- config/test.exs | 1 - 2 files changed, 78 insertions(+), 77 deletions(-) diff --git a/config/runtime.exs b/config/runtime.exs index 3013f776..6229d77b 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -121,80 +121,82 @@ config :jellyfish, config :opentelemetry, traces_exporter: :none -# we set ip and port here to allow for -# running multiple Jellyfishes in development -config :jellyfish, JellyfishWeb.Endpoint, - # Binding to loopback ipv4 address prevents access from other machines. - # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. - http: [ip: {127, 0, 0, 1}, port: port] - -if prod? do - token = - System.fetch_env!("SERVER_API_TOKEN") || - raise """ - environment variable SERVER_API_TOKEN is missing. - SERVER_API_TOKEN is used for HTTP requests and - server WebSocket authorization. - """ - - config :jellyfish, server_api_token: token - - # The secret key base is used to sign/encrypt cookies and other secrets. - # A default value is used in config/dev.exs and config/test.exs but you - # want to use a different value for prod and you most likely don't want - # to check this value into version control, so we use an environment - # variable instead. - secret_key_base = - System.get_env("SECRET_KEY_BASE") || - raise """ - environment variable SECRET_KEY_BASE is missing. - You can generate one by calling: mix phx.gen.secret - """ - - check_origin? = System.get_env("CHECK_ORIGIN", "true") == "true" - - config :jellyfish, JellyfishWeb.Endpoint, - url: [host: host, port: 443, scheme: "https"], - check_origin: check_origin?, - http: [ - # Enable IPv6 and bind on all interfaces. - # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. - # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html - # for details about using IPv6 vs IPv4 and loopback vs public addresses. - ip: {0, 0, 0, 0, 0, 0, 0, 0}, - port: port - ], - secret_key_base: secret_key_base - - # ## SSL Support - # - # To get SSL working, you will need to add the `https` key - # to your endpoint configuration: - # - # config :jellyfish, JellyfishWeb.Endpoint, - # https: [ - # ..., - # port: 443, - # cipher_suite: :strong, - # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), - # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") - # ] - # - # The `cipher_suite` is set to `:strong` to support only the - # latest and more secure SSL ciphers. This means old browsers - # and clients may not be supported. You can set it to - # `:compatible` for wider support. - # - # `:keyfile` and `:certfile` expect an absolute path to the key - # and cert in disk or a relative path inside priv, for example - # "priv/ssl/server.key". For all supported SSL configuration - # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 - # - # We also recommend setting `force_ssl` in your endpoint, ensuring - # no data is ever sent via http, always redirecting to https: - # - # config :jellyfish, JellyfishWeb.Endpoint, - # force_ssl: [hsts: true] - # - # Check `Plug.SSL` for all available options in `force_ssl`. +case config_env() do + env when env != :prod -> + # we set ip and port here to allow for + # running multiple Jellyfishes in development + config :jellyfish, JellyfishWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: port] + + :prod -> + token = + System.fetch_env!("SERVER_API_TOKEN") || + raise """ + environment variable SERVER_API_TOKEN is missing. + SERVER_API_TOKEN is used for HTTP requests and + server WebSocket authorization. + """ + + config :jellyfish, server_api_token: token + + # The secret key base is used to sign/encrypt cookies and other secrets. + # A default value is used in config/dev.exs and config/test.exs but you + # want to use a different value for prod and you most likely don't want + # to check this value into version control, so we use an environment + # variable instead. + secret_key_base = + System.get_env("SECRET_KEY_BASE") || + raise """ + environment variable SECRET_KEY_BASE is missing. + You can generate one by calling: mix phx.gen.secret + """ + + check_origin? = System.get_env("CHECK_ORIGIN", "true") == "true" + + config :jellyfish, JellyfishWeb.Endpoint, + url: [host: host, port: 443, scheme: "https"], + check_origin: check_origin?, + http: [ + # Enable IPv6 and bind on all interfaces. + # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. + # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html + # for details about using IPv6 vs IPv4 and loopback vs public addresses. + ip: {0, 0, 0, 0, 0, 0, 0, 0}, + port: port + ], + secret_key_base: secret_key_base + + # ## SSL Support + # + # To get SSL working, you will need to add the `https` key + # to your endpoint configuration: + # + # config :jellyfish, JellyfishWeb.Endpoint, + # https: [ + # ..., + # port: 443, + # cipher_suite: :strong, + # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), + # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") + # ] + # + # The `cipher_suite` is set to `:strong` to support only the + # latest and more secure SSL ciphers. This means old browsers + # and clients may not be supported. You can set it to + # `:compatible` for wider support. + # + # `:keyfile` and `:certfile` expect an absolute path to the key + # and cert in disk or a relative path inside priv, for example + # "priv/ssl/server.key". For all supported SSL configuration + # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 + # + # We also recommend setting `force_ssl` in your endpoint, ensuring + # no data is ever sent via http, always redirecting to https: + # + # config :jellyfish, JellyfishWeb.Endpoint, + # force_ssl: [hsts: true] + # + # Check `Plug.SSL` for all available options in `force_ssl`. end diff --git a/config/test.exs b/config/test.exs index eb9df258..f38bc283 100644 --- a/config/test.exs +++ b/config/test.exs @@ -7,7 +7,6 @@ config :jellyfish, # We don't run a server during test. If one is required, # you can enable the server option below. config :jellyfish, JellyfishWeb.Endpoint, - http: [ip: {127, 0, 0, 1}, port: 4002], secret_key_base: "DtVd7qfpae0tk5zRgAM75hOaCc+phk38gDFVvLPyqVN/vvVg0EPmksTSm5JcyjoJ", server: false From 6fc314aa9492892515850969772d745f5d8ed54f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Mon, 18 Sep 2023 16:13:10 +0200 Subject: [PATCH 3/9] Refactor jellyfish configuration --- config/dev.exs | 6 +- config/prod.exs | 4 +- config/runtime.exs | 202 +++++++-------------------------- config/test.exs | 2 +- lib/jellyfish/config_reader.ex | 71 ++++++++++++ 5 files changed, 117 insertions(+), 168 deletions(-) create mode 100644 lib/jellyfish/config_reader.ex diff --git a/config/dev.exs b/config/dev.exs index 92e0057e..ab0e4a21 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -4,17 +4,17 @@ config :jellyfish, server_api_token: "development", dev_routes: true # For development, we disable any cache and enable # debugging and code reloading. -# We set ip and port in the runtime.exs to allow -# for spawning more thane one Jellyfish instance. # # The watchers configuration can be used to run external # watchers to your application. For example, we use it # with esbuild to bundle .js and .css sources. config :jellyfish, JellyfishWeb.Endpoint, + # Binding to loopback ipv4 address prevents access from other machines. + # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. + http: [ip: {127, 0, 0, 1}, port: 5002], check_origin: false, code_reloader: true, debug_errors: true, - secret_key_base: "eUQ+pDd8FmAwDrE4taJYgcYtaMqFMLygkVRBPfH8G98U1aveWB3Oa9TkOoehK61t", watchers: [] # ## SSL Support diff --git a/config/prod.exs b/config/prod.exs index 12e68b19..a934e9f2 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -8,7 +8,9 @@ import Config config :logger, level: :info # run the server automatically when using prod release -config :jellyfish, JellyfishWeb.Endpoint, server: true +config :jellyfish, JellyfishWeb.Endpoint, + http: [ip: {127, 0, 0, 1}, port: 8080], + server: true # Runtime production configuration, including reading # of environment variables, is done on config/runtime.exs. diff --git a/config/runtime.exs b/config/runtime.exs index 6229d77b..20f3bfa9 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -1,74 +1,19 @@ import Config +alias Jellyfish.ConfigReader + # config/runtime.exs is executed for all environments, including # during releases. It is executed after compilation and before the # system starts, so it is typically used to load production configuration # and secrets from environment variables or elsewhere. Do not define # any compile-time configuration in here, as it won't be applied. # The block below contains prod specific runtime configuration. -defmodule ConfigParser do - def parse_integrated_turn_port_range(range) do - with [str1, str2] <- String.split(range, "-"), - from when from in 0..65_535 <- String.to_integer(str1), - to when to in from..65_535 and from <= to <- String.to_integer(str2) do - {from, to} - else - _else -> - raise(""" - Bad INTEGRATED_TURN_PORT_RANGE environment variable value. Expected "from-to", where `from` and `to` \ - are numbers between 0 and 65535 and `from` is not bigger than `to`, got: \ - #{inspect(range)} - """) - end - end - - def parse_ip(var_value, var_name) do - var_value = var_value |> to_charlist() - - case :inet.parse_address(var_value) do - {:ok, parsed_ip} -> - parsed_ip - - _error -> - with {:ok, parsed_ip} <- :inet.getaddr(var_value, :inet) do - parsed_ip - else - _error -> - raise(""" - Bad #{var_name} environment variable value. Expected valid ip address, got: #{inspect(var_value)} - """) - end - end - end - - def parse_port_number(nil, _var_name), do: nil - - def parse_port_number(var_value, var_name) do - with {port, _sufix} when port in 1..65535 <- Integer.parse(var_value) do - port - else - _var -> - raise( - "Bad #{var_name} environment variable value. Expected valid port number, got: #{inspect(var_value)}" - ) - end - end - - def get_env!(env_key) do - case System.get_env(env_key) do - nil -> raise("Environmental variable #{env_key} was not set properly") - env_val -> env_val - end - end -end +config :ex_dtls, impl: :nif +config :opentelemetry, traces_exporter: :none -hosts = - System.get_env("NODES", "") - |> String.split(" ") - |> Enum.reject(&(&1 == "")) - |> Enum.map(&String.to_atom(&1)) +hosts = ConfigReader.read_nodes("NODES") -unless Enum.empty?(hosts) do +if hosts do config :libcluster, topologies: [ epmd_cluster: [ @@ -81,122 +26,53 @@ end prod? = config_env() == :prod host = - case System.get_env("VIRTUAL_HOST") do - nil when prod? -> raise "Unset VIRTUAL_HOST environment variable" + case System.get_env("HOST") do + nil when prod? -> raise "Unset HOST environment variable" nil -> "localhost" other -> other end port = - case System.get_env("PORT") do - nil when prod? -> raise "Unset PORT environment variable" - nil -> 5002 - other -> String.to_integer(other) - end - -jellyfish_address = System.get_env("JELLYFISH_ADDRESS") || "#{host}:#{port}" - -config :ex_dtls, impl: :nif + ConfigReader.read_port("PORT") || + Application.get_env(:jellyfish, JellyfishWeb.Endpoint)[:http][:port] config :jellyfish, - webrtc_used: String.downcase(System.get_env("WEBRTC_USED", "true")) not in ["false", "f", "0"], - integrated_turn_ip: - System.get_env("INTEGRATED_TURN_IP", "127.0.0.1") - |> ConfigParser.parse_ip("INTEGRATED_TURN_IP"), - integrated_turn_listen_ip: - System.get_env("INTEGRATED_TURN_LISTEN_IP", "127.0.0.1") - |> ConfigParser.parse_ip("INTEGRATED_TURN_LISTEN_IP"), + webrtc_used: ConfigReader.read_boolean("WEBRTC_USED") || true, + integrated_turn_ip: ConfigReader.read_ip("INTEGRATED_TURN_IP") || {127, 0, 0, 1}, + integrated_turn_listen_ip: ConfigReader.read_ip("INTEGRATED_TURN_LISTEN_IP") || {127, 0, 0, 1}, integrated_turn_port_range: - System.get_env("INTEGRATED_TURN_PORT_RANGE", "50000-59999") - |> ConfigParser.parse_integrated_turn_port_range(), - integrated_turn_tcp_port: - System.get_env("INTEGRATED_TURN_TCP_PORT") - |> ConfigParser.parse_port_number("INTEGRATED_TURN_TCP_PORT"), + ConfigReader.read_port_range("INTEGRATED_TURN_PORT_RANGE") || {50_000, 59_999}, + integrated_turn_tcp_port: ConfigReader.read_port("INTEGRATED_TURN_TCP_PORT"), jwt_max_age: 24 * 3600, output_base_path: System.get_env("OUTPUT_BASE_PATH", "jellyfish_output") |> Path.expand(), - address: jellyfish_address, - metrics_ip: System.get_env("METRICS_IP", "127.0.0.1") |> ConfigParser.parse_ip("METRICS_IP"), - metrics_port: - System.get_env("METRICS_PORT", "9568") |> ConfigParser.parse_port_number("METRICS_PORT") - -config :opentelemetry, traces_exporter: :none - -case config_env() do - env when env != :prod -> - # we set ip and port here to allow for - # running multiple Jellyfishes in development - config :jellyfish, JellyfishWeb.Endpoint, - # Binding to loopback ipv4 address prevents access from other machines. - # Change to `ip: {0, 0, 0, 0}` to allow access from other machines. - http: [ip: {127, 0, 0, 1}, port: port] + address: System.get_env("JELLYFISH_ADDRESS") || "#{host}:#{port}", + metrics_ip: ConfigReader.read_ip("METRICS_IP") || {127, 0, 0, 1}, + metrics_port: ConfigReader.read_port("METRICS_PORT") || 9568 - :prod -> - token = - System.fetch_env!("SERVER_API_TOKEN") || - raise """ - environment variable SERVER_API_TOKEN is missing. - SERVER_API_TOKEN is used for HTTP requests and - server WebSocket authorization. - """ +config :jellyfish, JellyfishWeb.Endpoint, + secret_key_base: + System.get_env("SECRET_KEY_BASE") || Base.encode64(:crypto.strong_rand_bytes(48)), + http: [port: port] - config :jellyfish, server_api_token: token +if check_origin = ConfigReader.read_boolean("CHECK_ORIGIN") do + config :jellyfish, JellyfishWeb.Endpoint, check_origin: check_origin +end - # The secret key base is used to sign/encrypt cookies and other secrets. - # A default value is used in config/dev.exs and config/test.exs but you - # want to use a different value for prod and you most likely don't want - # to check this value into version control, so we use an environment - # variable instead. - secret_key_base = - System.get_env("SECRET_KEY_BASE") || - raise """ - environment variable SECRET_KEY_BASE is missing. - You can generate one by calling: mix phx.gen.secret - """ +case System.get_env("SERVER_API_TOKEN") do + nil when prod? == true -> + raise """ + environment variable SERVER_API_TOKEN is missing. + SERVER_API_TOKEN is used for HTTP requests and + server WebSocket authorization. + """ - check_origin? = System.get_env("CHECK_ORIGIN", "true") == "true" + nil -> + :ok - config :jellyfish, JellyfishWeb.Endpoint, - url: [host: host, port: 443, scheme: "https"], - check_origin: check_origin?, - http: [ - # Enable IPv6 and bind on all interfaces. - # Set it to {0, 0, 0, 0, 0, 0, 0, 1} for local network only access. - # See the documentation on https://hexdocs.pm/plug_cowboy/Plug.Cowboy.html - # for details about using IPv6 vs IPv4 and loopback vs public addresses. - ip: {0, 0, 0, 0, 0, 0, 0, 0}, - port: port - ], - secret_key_base: secret_key_base + token -> + config :jellyfish, server_api_token: token +end - # ## SSL Support - # - # To get SSL working, you will need to add the `https` key - # to your endpoint configuration: - # - # config :jellyfish, JellyfishWeb.Endpoint, - # https: [ - # ..., - # port: 443, - # cipher_suite: :strong, - # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), - # certfile: System.get_env("SOME_APP_SSL_CERT_PATH") - # ] - # - # The `cipher_suite` is set to `:strong` to support only the - # latest and more secure SSL ciphers. This means old browsers - # and clients may not be supported. You can set it to - # `:compatible` for wider support. - # - # `:keyfile` and `:certfile` expect an absolute path to the key - # and cert in disk or a relative path inside priv, for example - # "priv/ssl/server.key". For all supported SSL configuration - # options, see https://hexdocs.pm/plug/Plug.SSL.html#configure/1 - # - # We also recommend setting `force_ssl` in your endpoint, ensuring - # no data is ever sent via http, always redirecting to https: - # - # config :jellyfish, JellyfishWeb.Endpoint, - # force_ssl: [hsts: true] - # - # Check `Plug.SSL` for all available options in `force_ssl`. +if prod? do + config :jellyfish, JellyfishWeb.Endpoint, url: [host: host, port: 443, scheme: "https"] end diff --git a/config/test.exs b/config/test.exs index f38bc283..46bb0ab7 100644 --- a/config/test.exs +++ b/config/test.exs @@ -7,7 +7,7 @@ config :jellyfish, # We don't run a server during test. If one is required, # you can enable the server option below. config :jellyfish, JellyfishWeb.Endpoint, - secret_key_base: "DtVd7qfpae0tk5zRgAM75hOaCc+phk38gDFVvLPyqVN/vvVg0EPmksTSm5JcyjoJ", + http: [ip: {127, 0, 0, 1}, port: 4002], server: false # Print only warnings and errors during test diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex new file mode 100644 index 00000000..4273c103 --- /dev/null +++ b/lib/jellyfish/config_reader.ex @@ -0,0 +1,71 @@ +defmodule Jellyfish.ConfigReader do + @moduledoc false + + def read_port_range(env) do + if value = System.get_env(env) do + with [str1, str2] <- String.split(value, "-"), + from when from in 0..65_535 <- String.to_integer(str1), + to when to in from..65_535 and from <= to <- String.to_integer(str2) do + {from, to} + else + _else -> + raise(""" + Bad #{env} environment variable value. Expected "from-to", where `from` and `to` \ + are numbers between 0 and 65535 and `from` is not bigger than `to`, got: \ + #{value} + """) + end + end + end + + def read_ip(env) do + if value = System.get_env(env) do + value = value |> to_charlist() + + case :inet.parse_address(value) do + {:ok, parsed_ip} -> + parsed_ip + + _error -> + case :inet.getaddr(value, :inet) do + {:ok, parsed_ip} -> + parsed_ip + + _error -> + raise(""" + Bad #{env} environment variable value. Expected valid ip address, got: #{value}" + """) + end + end + end + end + + def read_port(env) do + if value = System.get_env(env) do + case Integer.parse(value) do + {port, _sufix} when port in 1..65_535 -> + port + + _other -> + raise(""" + Bad #{env} environment variable value. Expected valid port number, got: #{value} + """) + end + end + end + + def read_nodes(env) do + if value = System.get_env(env) do + value + |> String.split(" ") + |> Enum.reject(&(&1 == "")) + |> Enum.map(&String.to_atom(&1)) + end + end + + def read_boolean(env) do + if value = System.get_env(env) do + String.downcase(value) not in ["false", "f", "0"] + end + end +end From 571dee05e276f12658da616301a025e9f47c743c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Mon, 18 Sep 2023 20:57:42 +0200 Subject: [PATCH 4/9] Add ConfigReader tests --- .credo.exs | 2 +- test/jellyfish/config_reader_test.exs | 83 +++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 test/jellyfish/config_reader_test.exs diff --git a/.credo.exs b/.credo.exs index f9cc8d32..1bb11e31 100644 --- a/.credo.exs +++ b/.credo.exs @@ -129,7 +129,7 @@ {Credo.Check.Refactor.MatchInCondition, []}, {Credo.Check.Refactor.NegatedConditionsInUnless, []}, {Credo.Check.Refactor.NegatedConditionsWithElse, []}, - {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.Nesting, [max_nesting: 3]}, {Credo.Check.Refactor.UnlessWithElse, []}, {Credo.Check.Refactor.WithClauses, []}, diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs new file mode 100644 index 00000000..0cd51a12 --- /dev/null +++ b/test/jellyfish/config_reader_test.exs @@ -0,0 +1,83 @@ +defmodule Jellyfish.ConfigReaderTest do + use ExUnit.Case, async: true + + alias Jellyfish.ConfigReader + + defmacrop with_env(env, do: body) do + quote do + old = System.get_env(unquote(env)) + unquote(body) + + if old do + System.put_env(unquote(env), old) + else + System.delete_env(unquote(env)) + end + end + end + + test "read_ip/1" do + env_name = "JF_CONF_READER_TEST_IP" + + with_env env_name do + System.put_env(env_name, "127.0.0.1") + assert ConfigReader.read_ip(env_name) == {127, 0, 0, 1} + :os.unsetenv(to_charlist(env_name)) + assert ConfigReader.read_ip(env_name) == nil + end + end + + test "read_port/1" do + env_name = "JF_CONF_READER_TEST_PORT" + + with_env env_name do + System.put_env(env_name, "20000") + assert ConfigReader.read_port(env_name) == 20_000 + System.put_env(env_name, "65536") + assert_raise RuntimeError, fn -> ConfigReader.read_port(env_name) end + System.put_env(env_name, "-1") + assert_raise RuntimeError, fn -> ConfigReader.read_port(env_name) end + :os.unsetenv(to_charlist(env_name)) + assert ConfigReader.read_port(env_name) == nil + end + end + + test "read_boolean/1" do + env_name = "JF_CONF_READER_TEST_BOOL" + + with_env env_name do + for {env_value, expected_value} <- [ + {"f", false}, + {"0", false}, + {"false", false}, + {"1", true}, + {"true", true} + ] do + System.put_env(env_name, env_value) + assert ConfigReader.read_boolean(env_name) == expected_value + end + end + end + + test "read_port_range/1" do + env_name = "JF_CONF_READER_TEST_PORT_RANGE" + + with_env env_name do + System.put_env(env_name, "50000-60000") + assert ConfigReader.read_port_range(env_name) == {50_000, 60_000} + System.put_env(env_name, "50000-65536") + assert_raise RuntimeError, fn -> ConfigReader.read_port_range(env_name) end + System.put_env(env_name, "-1-65536") + assert_raise RuntimeError, fn -> ConfigReader.read_port_range(env_name) end + end + end + + test "read_nodes/1" do + env_name = "JF_CONF_READER_TEST_NODES" + + with_env env_name do + System.put_env(env_name, "app1@127.0.0.1 app2@127.0.0.2") + assert ConfigReader.read_nodes(env_name) == [:"app1@127.0.0.1", :"app2@127.0.0.2"] + end + end +end From 5bb9081cfa50d944444b0d1531be2b12b3a4dc87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Tue, 19 Sep 2023 16:27:08 +0200 Subject: [PATCH 5/9] Update docker-compose.yml --- docker-compose.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker-compose.yaml b/docker-compose.yaml index 7e42a5e2..8f90236b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,8 +5,7 @@ x-jellyfish-template: &jellyfish-template environment: &jellyfish-environment ERLANG_COOKIE: "panuozzo-pollo-e-pancetta" SERVER_API_TOKEN: "development" - SECRET_KEY_BASE: "super-secret-key" - VIRTUAL_HOST: "localhost" + HOST: "localhost" NODES: "app@app1 app@app2" networks: - net1 From 688ca451f0ba96c3987e38a92145455112175376 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Tue, 19 Sep 2023 17:30:56 +0200 Subject: [PATCH 6/9] Remove redundant secret_key_base from ci.exs --- config/ci.exs | 1 - 1 file changed, 1 deletion(-) diff --git a/config/ci.exs b/config/ci.exs index 46160fb0..5dd541e3 100644 --- a/config/ci.exs +++ b/config/ci.exs @@ -6,7 +6,6 @@ config :jellyfish, server_api_token: "development" # you can enable the server option below. config :jellyfish, JellyfishWeb.Endpoint, http: [ip: {127, 0, 0, 1}, port: 4002], - secret_key_base: "DtVd7qfpae0tk5zRgAM75hOaCc+phk38gDFVvLPyqVN/vvVg0EPmksTSm5JcyjoJ", server: false # Print only warnings and errors during test From 84a534f24daf41afc47fdbbe2a4a2dd6989741ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Wed, 20 Sep 2023 10:00:09 +0200 Subject: [PATCH 7/9] Bind prod to 0.0.0.0.0.0.0.0 --- config/prod.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/prod.exs b/config/prod.exs index a934e9f2..d80d20ec 100644 --- a/config/prod.exs +++ b/config/prod.exs @@ -9,7 +9,7 @@ config :logger, level: :info # run the server automatically when using prod release config :jellyfish, JellyfishWeb.Endpoint, - http: [ip: {127, 0, 0, 1}, port: 8080], + http: [ip: {0, 0, 0, 0, 0, 0, 0, 0}, port: 8080], server: true # Runtime production configuration, including reading From 790a629033eccd66017c03d1ae4b2dfccf5f7278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Wed, 20 Sep 2023 11:21:04 +0200 Subject: [PATCH 8/9] Add `with_env` macro comment --- test/jellyfish/config_reader_test.exs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index 0cd51a12..d2aad0de 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -4,6 +4,13 @@ defmodule Jellyfish.ConfigReaderTest do alias Jellyfish.ConfigReader defmacrop with_env(env, do: body) do + # get current env value, + # execute test code, + # put back original env value + # + # if env was not set, we have + # to call System.delete_env as + # System.put_env does not accept `nil` quote do old = System.get_env(unquote(env)) unquote(body) From cd00d5b2348c97a2b178a6b9e6f60c685edb791e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=9Aled=C5=BA?= Date: Wed, 20 Sep 2023 11:50:49 +0200 Subject: [PATCH 9/9] Revert max nesting settings. Don't allow fqdn as ip. Improve nodes parsing --- .credo.exs | 2 +- lib/jellyfish/config_reader.ex | 19 +++++++------------ test/jellyfish/config_reader_test.exs | 6 +++++- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/.credo.exs b/.credo.exs index 1bb11e31..f9cc8d32 100644 --- a/.credo.exs +++ b/.credo.exs @@ -129,7 +129,7 @@ {Credo.Check.Refactor.MatchInCondition, []}, {Credo.Check.Refactor.NegatedConditionsInUnless, []}, {Credo.Check.Refactor.NegatedConditionsWithElse, []}, - {Credo.Check.Refactor.Nesting, [max_nesting: 3]}, + {Credo.Check.Refactor.Nesting, []}, {Credo.Check.Refactor.UnlessWithElse, []}, {Credo.Check.Refactor.WithClauses, []}, diff --git a/lib/jellyfish/config_reader.ex b/lib/jellyfish/config_reader.ex index 4273c103..f90edeaf 100644 --- a/lib/jellyfish/config_reader.ex +++ b/lib/jellyfish/config_reader.ex @@ -27,15 +27,9 @@ defmodule Jellyfish.ConfigReader do parsed_ip _error -> - case :inet.getaddr(value, :inet) do - {:ok, parsed_ip} -> - parsed_ip - - _error -> - raise(""" - Bad #{env} environment variable value. Expected valid ip address, got: #{value}" - """) - end + raise(""" + Bad #{env} environment variable value. Expected valid ip address, got: #{value}" + """) end end end @@ -55,10 +49,11 @@ defmodule Jellyfish.ConfigReader do end def read_nodes(env) do - if value = System.get_env(env) do + value = System.get_env(env) + + if value not in ["", nil] do value - |> String.split(" ") - |> Enum.reject(&(&1 == "")) + |> String.split(" ", trim: true) |> Enum.map(&String.to_atom(&1)) end end diff --git a/test/jellyfish/config_reader_test.exs b/test/jellyfish/config_reader_test.exs index d2aad0de..f80db9ec 100644 --- a/test/jellyfish/config_reader_test.exs +++ b/test/jellyfish/config_reader_test.exs @@ -29,8 +29,10 @@ defmodule Jellyfish.ConfigReaderTest do with_env env_name do System.put_env(env_name, "127.0.0.1") assert ConfigReader.read_ip(env_name) == {127, 0, 0, 1} - :os.unsetenv(to_charlist(env_name)) + System.delete_env(env_name) assert ConfigReader.read_ip(env_name) == nil + System.put_env(env_name, "example.com") + assert_raise RuntimeError, fn -> ConfigReader.read_ip(env_name) end end end @@ -85,6 +87,8 @@ defmodule Jellyfish.ConfigReaderTest do with_env env_name do System.put_env(env_name, "app1@127.0.0.1 app2@127.0.0.2") assert ConfigReader.read_nodes(env_name) == [:"app1@127.0.0.1", :"app2@127.0.0.2"] + System.put_env(env_name, "") + assert ConfigReader.read_nodes(env_name) == nil end end end