Skip to content

Commit

Permalink
Make metrics ip and port, and http endpoint port configurable (#87)
Browse files Browse the repository at this point in the history
  • Loading branch information
mickel8 authored Sep 20, 2023
1 parent bbff33a commit 53599d1
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 160 deletions.
1 change: 0 additions & 1 deletion config/ci.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 0 additions & 1 deletion config/dev.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ config :jellyfish, JellyfishWeb.Endpoint,
check_origin: false,
code_reloader: true,
debug_errors: true,
secret_key_base: "eUQ+pDd8FmAwDrE4taJYgcYtaMqFMLygkVRBPfH8G98U1aveWB3Oa9TkOoehK61t",
watchers: []

# ## SSL Support
Expand Down
4 changes: 3 additions & 1 deletion config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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: {0, 0, 0, 0, 0, 0, 0, 0}, port: 8080],
server: true

# Runtime production configuration, including reading
# of environment variables, is done on config/runtime.exs.
190 changes: 39 additions & 151 deletions config/runtime.exs
Original file line number Diff line number Diff line change
@@ -1,75 +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_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),
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_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: [
Expand All @@ -82,109 +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_integrated_turn_ip(),
integrated_turn_listen_ip:
System.get_env("INTEGRATED_TURN_LISTEN_IP", "127.0.0.1")
|> ConfigParser.parse_integrated_turn_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
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

config :opentelemetry, traces_exporter: :none

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, 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
1 change: 0 additions & 1 deletion config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ config :jellyfish,
# 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
Expand Down
3 changes: 1 addition & 2 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions lib/jellyfish/config_reader.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
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 ->
raise("""
Bad #{env} environment variable value. Expected valid ip address, got: #{value}"
""")
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
value = System.get_env(env)

if value not in ["", nil] do
value
|> String.split(" ", trim: true)
|> 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
12 changes: 9 additions & 3 deletions lib/jellyfish_web/telemetry.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading

0 comments on commit 53599d1

Please sign in to comment.