MishkaInstaller.Installer.LibraryHandler (Mishka installer v0.1.7)

Copy Markdown View Source

This module provides programmers with a public APIs and helpers that allow them to load a pre-built Elixir/Erlang library (its compiled ebin) into a running system.

It also includes aids and tools that allow them to work with the library while it is running.

MishkaInstaller.Installer.Installer is also referred to as action and aggregator functions, which is something that should be taken into consideration.

If you are unsure about the responsibilities of each function, it is recommended that you utilise the MishkaInstaller.Installer.Installer module and its functions, which consist of a collection of predefined strategies.

Why only the ebin?

Compiling a library at runtime (mix deps.get/deps.compile/compile) cannot work in a production release, because a release ships no Mix, no Hex, no project source and no _build tree. So instead of compiling, this module loads the already compiled artifacts (ebin/*.beam + ebin/<app>.app):

  1. add the ebin directory to the code path (Code.prepend_path/1),
  2. Application.load/1 + Application.ensure_all_started/1.

Restart consideration

The code path is held in memory by the Erlang code server and is not persisted across a restart. Only the files on disk and the MishkaInstaller.Installer.Installer Mnesia record survive. The path + load must be replayed on every boot (see MishkaInstaller.Installer.CompileHandler).

Security considerations

It is important to remember that all of the functionalities contained within this section must be implemented at the high access level, and they should not directly take any input from the user. Ensure that you include the required safety measures.

Loading a .beam is running arbitrary code with full node privileges; the BEAM has no sandbox. Only load artifacts from a trusted source, built for the same Erlang/OTP and Elixir as the host. Native code (NIF/port driver .so/.dll) is not portable across OS/architecture/ERTS.

Summary

Functions

Helper function to load an application and all its dependencies.

Helper function to get the path where the pre-built runtime libraries (extensions) are stored.

Extracts a downloaded pre-built artifact (a tar.gz whose content is a compiled ebin) into the extensions directory under the canonical name (e.g. "<app>-<version>").

By means of this helper function, identify the paths of dependencies to the Erlang VM.

Helper function to read Erlang .app file in Elixir.

Helper function to unload an application.

Types

app()

error_return()

@type error_return() ::
  {:error, [%{action: atom(), field: atom(), message: String.t()}]}

okey_return()

@type okey_return() :: {:ok, struct() | map() | binary() | [any()]}

Functions

application_ensure(app_name)

@spec application_ensure(atom()) :: :ok | error_return()

Helper function to load an application and all its dependencies.

It is idempotent: an already loaded application is treated as a success, so this is safe to call again on every boot when re-activating a previously installed library.

Security considerations

It is important to remember that all of the functionalities contained within this section must be implemented at the high access level, and they should not directly take any input from the user. Ensure that you include the required safety measures.

Example:

application_ensure(:mishka_developer_tools)

extensions_path()

@spec extensions_path() :: Path.t()

Helper function to get the path where the pre-built runtime libraries (extensions) are stored.

It defaults to deployment/<env>/extensions under the project path and can be overridden with the :extensions_path application env so a production release can point it at a writable volume outside the (read-only) release directory.

Example:

extensions_path()

extract(atom, archived, name)

@spec extract(:tar, binary(), String.t()) :: :ok | error_return()

Extracts a downloaded pre-built artifact (a tar.gz whose content is a compiled ebin) into the extensions directory under the canonical name (e.g. "<app>-<version>").

The archive may contain ebin/... at its top level or nested one directory deep (for example <app>-<version>/ebin/...). Nothing is compiled here.

Security considerations

It is important to remember that all of the functionalities contained within this section must be implemented at the high access level, and they should not directly take any input from the user. Ensure that you include the required safety measures.

Example:

extract(:tar, tar_gz_binary, "elixir_uuid-1.2.1")

prepend_compiled_apps(files_list)

@spec prepend_compiled_apps([tuple()]) :: :ok | error_return()

By means of this helper function, identify the paths of dependencies to the Erlang VM.

For more information see Code.prepend_path/1.

Security considerations

It is important to remember that all of the functionalities contained within this section must be implemented at the high access level, and they should not directly take any input from the user. Ensure that you include the required safety measures.

Example:

files_list = [
  decimal: "/_build/dev/lib/decimal/ebin",
  ecto: "/_build/dev/lib/ecto/ebin",
  uniq: "/_build/dev/lib/uniq/ebin"
]

prepend_compiled_apps(files_list)

read_app(app, app_path)

@spec read_app(atom(), Path.t()) :: {:ok, any()} | error_return()

Helper function to read Erlang .app file in Elixir.

Reads the given app from path in an optimized format and returns its contents.

Based on: https://github.com/elixir-lang/elixir/blob/f0fcd64f937af8ccdd98e086c107c3902485d404/lib/mix/lib/mix/app_loader.ex#L59-L75

Security considerations

It is important to remember that all of the functionalities contained within this section must be implemented at the high access level, and they should not directly take any input from the user. Ensure that you include the required safety measures.

Example:

read_app(:mishka_developer_tools, app_bin_path)

unload(app)

@spec unload(atom()) :: :ok | error_return()

Helper function to unload an application.

Security considerations

It is important to remember that all of the functionalities contained within this section must be implemented at the high access level, and they should not directly take any input from the user. Ensure that you include the required safety measures.

Example:

unload(:mishka_developer_tools)