# Copyright(c) 2015-2023 ACCESS CO., LTD. All rights reserved.
use Croma
defmodule Antikythera.Env do
@moduledoc """
Module to provide helpers to access environment variables defined by antikythera.
## Environments and deployments
Antikythera instances and gears may run in the following two modes:
- As a mix project, when invoked by running `iex` or `mix` command-line tool.
- As an OTP release, which is generated by e.g. `antikythera_core.generate_release` task.
In general there are multiple deployments per antikythera instance.
To distingish the target deployment from code, antikythera defines the following environment variables:
- At compile time (for metaprogramming):
`ANTIKYTHERA_COMPILE_ENV` must be appropriately set; the value can be retrieved by `compile_env/0`.
- At runtime:
`ANTIKYTHERA_RUNTIME_ENV` must be appropriately set; the value can be retrieved by `runtime_env/0`.
It is the responsibility of antikythera instance administrators to correctly set these environment variables
when compiling/running antikythera and gears.
Possible values returned by `compile_env/0` and `runtime_env/0` are:
- names of deployments given by `:deployments` application config
- `:local` (running an OTP release at local machine for testing purpose)
- `:undefined`
As an example, if you set `:dev` and `:prod` in `:deployments`, then you get:
| | Mix.env() at compile-time | Mix.env() at runtime | compile_env(), runtime_env() |
|---------------------------+----------------------------------------+-------------------------+------------------------------|
| $ iex -S mix | :dev (toplevel), :prod (dependencies) | :dev | :undefined |
| $ mix test | :test (toplevel), :prod (dependencies) | :test | :undefined |
| $ mix antikythera_local.* | :prod | (:mix is not available) | :local |
| :dev deployment | :prod | (:mix is not available) | :dev |
| :prod deployment | :prod | (:mix is not available) | :prod |
You can use these values to distinguish the current context from your code.
"""
deployment_envs = Application.compile_env!(:antikythera, :deployments) |> Keyword.keys()
use Croma.SubtypeOfAtom, values: deployment_envs ++ [:local, :undefined]
alias Antikythera.{GearName, GearNameStr, Url}
alias AntikytheraCore.Handler.CowboyRouting, as: Routing
defmodule Mapping do
Enum.each(deployment_envs, fn env ->
env_str = Atom.to_string(env)
def env_var_to_atom(unquote(env_str)), do: unquote(env)
end)
def env_var_to_atom("local"), do: :local
def env_var_to_atom(_), do: :undefined
Enum.each(deployment_envs, fn env ->
def cloud?(unquote(env)), do: true
end)
def cloud?(_), do: false
end
defun no_listen?() :: boolean do
case System.get_env() do
%{"NO_LISTEN" => "true"} -> true
%{"TEST_MODE" => "blackbox_" <> _} -> true
_otherwise -> false
end
end
defun runtime_env() :: t,
do: System.get_env("ANTIKYTHERA_RUNTIME_ENV") |> Mapping.env_var_to_atom()
defun running_on_mix_task?() :: boolean,
do: System.get_env("ANTIKYTHERA_MIX_TASK_MODE") == "true"
defun running_with_release?() :: boolean,
do: !running_on_mix_task?() && runtime_env() != :undefined
defun running_in_cloud?() :: boolean,
do: !running_on_mix_task?() && Mapping.cloud?(runtime_env())
@compile_env System.get_env("ANTIKYTHERA_COMPILE_ENV") |> Mapping.env_var_to_atom()
@compiling_for_mix_task? System.get_env("ANTIKYTHERA_MIX_TASK_MODE") == "true"
@compiling_for_release? @compile_env != :undefined
@compiling_for_cloud? Mapping.cloud?(@compile_env)
defun compile_env() :: t, do: @compile_env
defun compiling_for_mix_task?() :: boolean, do: @compiling_for_mix_task?
defun compiling_for_release?() :: boolean,
do: !compiling_for_mix_task?() && @compiling_for_release?
defun compiling_for_cloud?() :: boolean, do: !compiling_for_mix_task?() && @compiling_for_cloud?
@antikythera_instance_name Application.compile_env!(:antikythera, :antikythera_instance_name)
defun antikythera_instance_name() :: atom, do: @antikythera_instance_name
@gear_action_timeout_default 10_000
@gear_action_timeout String.to_integer(
System.get_env("GEAR_ACTION_TIMEOUT") ||
"#{@gear_action_timeout_default}"
)
@doc """
Default timeout (in milli-seconds) for gear actions.
This can be configurable by specifying `"GEAR_ACTION_TIMEOUT"` environment variable when compiling antikythera.
Defaults to `#{@gear_action_timeout_default}`.
"""
defun gear_action_timeout() :: pos_integer, do: @gear_action_timeout
@default_port 8080
@default_port_during_test 8081
# To see which port number to use, we have to get the current mix environment at runtime,
# since `Mix.env()` always returns `:prod` during compilation of antikythera within a gear project.
# However, when running with an OTP release, `Mix.env/0` is not available at runtime
# (as `:mix` is not included in antikythera's runtime dependencies).
# Therefore we need both compile-time- and runtime-branching.
if @compiling_for_release? do
defp default_port_at_runtime(), do: @default_port
else
defp default_port_at_runtime(),
do: if(Mix.env() == :test, do: @default_port_during_test, else: @default_port)
end
@doc """
TCP port to listen to for incoming web requests.
The port can be specified by `"PORT"` runtime environment variable.
Defaults to `#{@default_port_during_test}` during `mix test`, and `#{@default_port}` otherwise
(thus one can run both `iex -S mix` and `mix test` at the same time).
"""
defun port_to_listen() :: non_neg_integer do
case System.get_env("PORT") do
nil -> default_port_at_runtime()
port_str -> String.to_integer(port_str)
end
end
if Antikythera.Env.Mapping.cloud?(@compile_env) do
@scheme "https"
else
@scheme "http"
end
@doc """
Return the base URL which based on the `Host` HTTP header of the request.
This function is useful if you use a custom domain.
Whether the scheme of the URL is `https` or `http` depends on whether `Antikythera.Env.Mapping.cloud?/1` returns true or false.
"""
defun base_url(%Antikythera.Conn{request: %Antikythera.Request{headers: headers}}) :: v[Url.t()] do
case Map.get(headers, "host") do
nil -> raise "`Host` header is not in the request"
host -> @scheme <> "://" <> host
end
end
@doc """
Return the base URL of the gear.
If the gear name is `my_gear` and the Antikythera is deployed at `antikythera.example.com`,
this function returns `https://my-gear.antikythera.example.com`.
"""
defun default_base_url(
gear_name :: v[GearName.t() | GearNameStr.t()],
env :: v[t] \\ @compile_env
) :: Url.t() do
if Mapping.cloud?(env) do
"https://" <> Routing.default_domain(gear_name, env)
else
"http://" <> Routing.default_domain(gear_name, env) <> ":#{port_to_listen()}"
end
end
defun asset_base_url(gear_name :: v[GearName.t() | GearNameStr.t()]) :: Url.t() do
case Application.fetch_env!(:antikythera, :asset_cdn_endpoint) do
nil -> default_base_url(gear_name)
base_url -> base_url
end
end
end