# 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