Packages
altworx_runbox
21.2.0
25.0.0
24.0.0
23.1.0
23.0.0
22.2.0
22.1.0
22.0.0
21.2.0
21.1.2
21.1.1
21.1.0
21.0.0
20.0.0
19.0.0
18.0.0
17.2.0
17.1.0
17.0.1
17.0.0
16.2.0
16.1.0
16.0.0
15.0.0
14.1.0
14.0.1
14.0.0
13.0.3
13.0.2
13.0.1
13.0.0
12.1.0
12.0.0
11.0.1
11.0.0
10.0.0
9.0.0
8.0.0
7.0.1
7.0.0
6.0.0
5.0.0
4.0.0
3.0.0
2.1.0
2.0.0
1.4.1
1.4.0
1.3.0
1.2.0
1.1.0
1.0.0
0.1.3
0.1.2
0.1.1
0.1.0
Runbox is a library for running Altworx scenarios.
Current section
Files
Jump to
Current section
Files
lib/runbox/scenario_release.ex
defmodule Runbox.ScenarioRelease do
@moduledoc group: :internal
@moduledoc """
Module for inspecting a scenario release.
"""
alias Runbox.Scenario
alias Runbox.ScenarioTemplate
alias Runbox.Slave
defmodule SlaveFunc do
require Logger
@moduledoc group: :internal
@moduledoc """
Functions which are called on the scenario slave via RPC from the master
node.
"""
alias Runbox.Scenario.TemplateInspector
alias Runbox.Scenario.Type
@doc """
Retrieves information about scenarios in the release.
Scenarios with invalid manifests are filtered out. However, scenarios with colliding module
names are not.
"""
@spec release_info([module] | nil) :: [Scenario.t()]
def release_info(modules \\ nil) do
modules_to_scenarios(modules || release_modules())
end
@doc """
Returns list of IDs of scenarios in the release.
Scenarios with invalid manifests or mutually colliding manifest module names are filtered out.
"""
@spec scenarios :: [scenario_id :: String.t()]
def scenarios do
release_modules()
|> Enum.filter(&Scenario.manifest_module?/1)
|> resolve_prefix_conflicts()
|> Enum.flat_map(fn manifest_mod ->
with {:ok, manifest} <- scenario_manifest_info(manifest_mod),
:ok <- validate_scenario_type(manifest.type, manifest_mod) do
[manifest.id]
else
:error ->
[]
end
end)
end
@doc """
Returns all modules of the scenario app.
"""
@spec release_modules() :: [module()]
def release_modules do
scenario_app = Application.fetch_env!(:runbox, :scenario_app)
case :application.get_key(scenario_app, :modules) do
{:ok, modules} -> modules
:undefined -> []
end
end
defp resolve_prefix_conflicts(manifest_mods) do
conflicting =
Enum.reduce(manifest_mods, MapSet.new(), &find_conflicting(&1, &2, manifest_mods))
Enum.filter(manifest_mods, fn mod -> mod not in conflicting end)
end
defp find_conflicting(module, conflicting, others) do
if module in conflicting do
conflicting
else
case Enum.filter(others, &prefix_of?(module, &1)) do
[] -> conflicting
new_conflicts -> MapSet.union(conflicting, MapSet.new([module | new_conflicts]))
end
end
end
defp prefix_of?(module, other) do
prefix = Module.split(other)
module_path = Module.split(module)
if List.starts_with?(module_path, prefix) and module != other do
Logger.warning(
"Module #{other} is prefix of module #{module}, which could " <>
"lead to conflicts when detecting templates - they will be excluded"
)
true
else
false
end
end
defp modules_to_scenarios(modules) do
modules
|> Enum.filter(&Scenario.manifest_module?/1)
|> Enum.flat_map(fn manifest_mod ->
with {:ok, manifest} <- scenario_manifest_info(manifest_mod),
:ok <- validate_scenario_type(manifest.type, manifest_mod) do
opts = scenario_opts(manifest_mod)
templates = scenario_templates(modules, manifest_mod, manifest)
[
%Scenario{
manifest: manifest,
opts: opts,
templates: templates
}
]
else
:error ->
[]
end
end)
end
defp scenario_templates(modules, manifest_mod, manifest) do
template_modules = Enum.filter(modules, &Scenario.template_module?(&1, manifest_mod))
%{template_inspector: inspector_mod} = Scenario.get_impl_for(manifest.type)
Enum.flat_map(template_modules, fn mod ->
if valid_template?(inspector_mod, mod) do
info = template_info(inspector_mod, mod)
[%ScenarioTemplate{module: mod, info: info}]
else
[]
end
end)
end
defp validate_scenario_type(type, manifest_mod) do
if type in Type.all_types() do
:ok
else
Logger.error(
"Ignoring manifest module #{inspect(manifest_mod)} because it has invalid type '#{type}'"
)
:error
end
end
defp scenario_manifest_info(manifest_mod) do
{:ok, manifest_mod.get_info()}
catch
kind, value ->
Logger.error(
"Error reading info of manifest #{inspect(manifest_mod)}: #{inspect({kind, value})}"
)
Logger.error("Ignoring manifest module #{inspect(manifest_mod)}")
:error
end
defp scenario_opts(manifest_mod) do
if Code.ensure_loaded?(manifest_mod) && function_exported?(manifest_mod, :on_start, 0) do
%{on_start: {manifest_mod, :on_start, []}}
else
%{}
end
end
defp valid_template?(inspector_mod, template_mod) do
TemplateInspector.valid_template?(inspector_mod, template_mod)
end
defp template_info(inspector_mod, template_mod) do
TemplateInspector.template_info(inspector_mod, template_mod)
end
end
@doc """
Retrieves information about scenarios in a release.
"""
@spec release_info(String.t()) :: {:ok, [Scenario.t()]} | {:error, term()}
def release_info(release_dir) do
Slave.with_slave(release_dir, fn slave ->
Slave.call(slave, SlaveFunc, :release_info, [])
end)
end
end