Skip to content

Commit

Permalink
Using custom values in checks executions (#584)
Browse files Browse the repository at this point in the history
* Add a SelectedCheck struct represeting a cheeck that has been selected and it's possibly being used during an execution

* Expose a function allowing to get selected checks matching a given execution context

* Adjust fake server to use selected checks

* Use customized values during a checks execution

* Adjust checks execution server to use selected checks

* Adjust openapi schemas

* Address review feedback

* Refactor get_current_selection to to_selected_checks
  • Loading branch information
nelsonkopliku authored Mar 4, 2025
1 parent d47c09e commit c0f5a5b
Show file tree
Hide file tree
Showing 18 changed files with 653 additions and 95 deletions.
11 changes: 8 additions & 3 deletions demo/fake_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ defmodule Wanda.Executions.FakeServer do
"""
@behaviour Wanda.Executions.ServerBehaviour

alias Wanda.Catalog.SelectedCheck
alias Wanda.EvaluationEngine
alias Wanda.Executions.{Evaluation, FakeGatheredFacts}

Expand All @@ -29,12 +30,16 @@ defmodule Wanda.Executions.FakeServer do
) do
env = Map.put(env, "target_type", target_type)

checks =
selected_checks =
targets
|> Executions.Target.get_checks_from_targets()
|> Catalog.get_checks(env)
|> Catalog.to_selected_checks(group_id)

gathered_facts = FakeGatheredFacts.get_demo_gathered_facts(checks, targets)
gathered_facts =
selected_checks
|> SelectedCheck.extract_specs()
|> FakeGatheredFacts.get_demo_gathered_facts(targets)

Executions.create_execution!(execution_id, group_id, targets)
execution_started = Messaging.Mapper.to_execution_started(execution_id, group_id, targets)
Expand All @@ -46,7 +51,7 @@ defmodule Wanda.Executions.FakeServer do
Evaluation.execute(
execution_id,
group_id,
checks,
selected_checks,
gathered_facts,
env,
EvaluationEngine.new()
Expand Down
24 changes: 24 additions & 0 deletions lib/wanda/catalog.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ defmodule Wanda.Catalog do
Function to interact with the checks catalog.
"""

alias Wanda.Catalog.CustomizedValue
alias Wanda.Catalog.SelectedCheck

alias Wanda.Catalog.{
Check,
CheckCustomization,
Expand Down Expand Up @@ -79,6 +82,13 @@ defmodule Wanda.Catalog do
|> Enum.map(&map_to_selectable_check(&1, available_customizations))
end

@spec to_selected_checks([Check.t()], String.t()) :: [SelectedCheck.t()]
def to_selected_checks(checks, group_id) do
available_customizations = ChecksCustomizations.get_customizations(group_id)

Enum.map(checks, &map_to_selected_check(&1, available_customizations))
end

defp map_to_selectable_check(
%Check{
id: id,
Expand Down Expand Up @@ -111,6 +121,20 @@ defmodule Wanda.Catalog do
}
end

defp map_to_selected_check(%Check{id: id} = check, available_customizations) do
customizations =
id
|> find_custom_values(available_customizations)
|> Enum.map(&CustomizedValue.from_custom_value/1)

%SelectedCheck{
id: id,
spec: check,
customized: Enum.count(customizations) > 0,
customizations: customizations
}
end

defp find_custom_values(check_id, available_customizations) do
Enum.find_value(available_customizations, [], fn
%CheckCustomization{check_id: ^check_id, custom_values: custom_values} ->
Expand Down
19 changes: 19 additions & 0 deletions lib/wanda/catalog/customized_value.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule Wanda.Catalog.CustomizedValue do
@moduledoc """
Represents a check's customized value.
"""

defstruct [:name, :value]

@type t :: %__MODULE__{
name: String.t(),
value: boolean() | number() | String.t()
}

def from_custom_value(%{name: name, value: value}) do
%__MODULE__{
name: name,
value: value
}
end
end
26 changes: 26 additions & 0 deletions lib/wanda/catalog/selected_check.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Wanda.Catalog.SelectedCheck do
@moduledoc """
Represents a selected check used during a check execution.
It carries information about the check specification and its available customizations.
"""

alias Wanda.Catalog.{Check, CustomizedValue}

defstruct [
:id,
:spec,
:customized,
:customizations
]

@type t :: %__MODULE__{
id: String.t(),
spec: Check.t(),
customized: boolean(),
customizations: [CustomizedValue.t()]
}

@spec extract_specs([t]) :: [Check.t()]
def extract_specs(selected_checks), do: Enum.map(selected_checks, & &1.spec)
end
1 change: 1 addition & 0 deletions lib/wanda/executions/check_result.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ defmodule Wanda.Executions.CheckResult do
defstruct [
:check_id,
:result,
:customized,
agents_check_results: [],
expectation_results: []
]
Expand Down
70 changes: 53 additions & 17 deletions lib/wanda/executions/evaluation.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Wanda.Executions.Evaluation do
Evaluation functional core.
"""

alias Wanda.Catalog.{Check, Condition, Expectation}
alias Wanda.Catalog.{Check, Condition, CustomizedValue, Expectation, SelectedCheck}
alias Wanda.Catalog.Value, as: CatalogValue

alias Wanda.Executions.{
Expand All @@ -28,7 +28,7 @@ defmodule Wanda.Executions.Evaluation do
@spec execute(
String.t(),
String.t(),
[Check.t()],
[SelectedCheck.t()],
map(),
%{String.t() => boolean() | number() | String.t()},
[String.t()],
Expand All @@ -52,24 +52,35 @@ defmodule Wanda.Executions.Evaluation do
defp add_checks_result(%Result{} = result, checks, gathered_facts, env, engine) do
check_results =
Enum.map(gathered_facts, fn {check_id, agents_facts} ->
%Check{severity: severity, values: values, expectations: expectations} =
Enum.find(checks, &(&1.id == check_id))
%SelectedCheck{} = selected_check = Enum.find(checks, &(&1.id == check_id))

build_check_result(check_id, severity, expectations, agents_facts, env, values, engine)
build_check_result(selected_check, agents_facts, env, engine)
end)

%Result{result | check_results: check_results}
end

defp build_check_result(check_id, severity, expectations, agents_facts, env, values, engine) do
defp build_check_result(
%SelectedCheck{
id: check_id,
spec: %Check{
severity: severity,
expectations: expectations
},
customized: customized
} = selected_check,
agents_facts,
env,
engine
) do
%CheckResult{
check_id: check_id
check_id: check_id,
customized: customized
}
|> add_agents_results(
expectations,
selected_check,
agents_facts,
env,
values,
engine
)
|> add_expectation_results(expectations, engine)
Expand All @@ -78,10 +89,9 @@ defmodule Wanda.Executions.Evaluation do

defp add_agents_results(
%CheckResult{} = check_result,
expectations,
%SelectedCheck{} = selected_check,
agents_facts,
env,
values,
engine
) do
agents_results =
Expand All @@ -94,13 +104,13 @@ defmodule Wanda.Executions.Evaluation do
}

{agent_id, facts} ->
add_agent_check_result_or_error(agent_id, facts, expectations, env, values, engine)
add_agent_check_result_or_error(agent_id, selected_check, facts, env, engine)
end)

%CheckResult{check_result | agents_check_results: agents_results}
end

defp add_agent_check_result_or_error(agent_id, facts, expectations, env, values, engine) do
defp add_agent_check_result_or_error(agent_id, selected_check, facts, env, engine) do
if has_some_fact_gathering_error?(facts) do
%AgentCheckError{
agent_id: agent_id,
Expand All @@ -109,19 +119,32 @@ defmodule Wanda.Executions.Evaluation do
message: "Fact gathering error occurred during the execution"
}
else
%SelectedCheck{
spec: %Check{
values: spec_values,
expectations: expectations
},
customizations: customizations
} = selected_check

value_evaluation_scope = add_scope(%{"env" => env}, "facts", facts)

evaluated_values = Enum.map(values, &eval_value(&1, value_evaluation_scope, engine))
resolved_values =
Enum.map(spec_values, fn %CatalogValue{name: name} = specified_value ->
customizations
|> Enum.find(specified_value, &(&1.name == name))
|> eval_value(value_evaluation_scope, engine)
end)

expectation_evaluation_scope =
%{}
|> add_scope("facts", facts)
|> add_scope("values", evaluated_values)
|> add_scope("values", resolved_values)

%AgentCheckResult{
agent_id: agent_id,
facts: facts,
values: evaluated_values,
values: resolved_values,
expectation_evaluations:
Enum.map(expectations, &eval_expectation(&1, expectation_evaluation_scope, engine))
}
Expand Down Expand Up @@ -154,7 +177,20 @@ defmodule Wanda.Executions.Evaluation do
) do
%Value{
name: name,
value: find_value(conditions, default, evaluation_scope, engine)
value: find_value(conditions, default, evaluation_scope, engine),
customized: false
}
end

defp eval_value(
%CustomizedValue{name: name, value: custom_value},
_,
_
) do
%Value{
name: name,
value: custom_value,
customized: true
}
end

Expand Down
7 changes: 6 additions & 1 deletion lib/wanda/executions/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ defmodule Wanda.Executions.Server do

use GenServer, restart: :transient

alias Wanda.Catalog.SelectedCheck

alias Wanda.Executions.{
Evaluation,
Gathering,
Expand Down Expand Up @@ -46,6 +48,7 @@ defmodule Wanda.Executions.Server do
targets
|> Target.get_checks_from_targets()
|> Catalog.get_checks(env)
|> Catalog.to_selected_checks(group_id)

checks_ids = Enum.map(checks, & &1.id)

Expand Down Expand Up @@ -99,8 +102,10 @@ defmodule Wanda.Executions.Server do
) do
engine = EvaluationEngine.new()

specs = SelectedCheck.extract_specs(checks)

facts_gathering_requested =
Messaging.Mapper.to_facts_gathering_requested(execution_id, group_id, targets, checks)
Messaging.Mapper.to_facts_gathering_requested(execution_id, group_id, targets, specs)

execution_started = Messaging.Mapper.to_execution_started(execution_id, group_id, targets)

Expand Down
4 changes: 2 additions & 2 deletions lib/wanda/executions/state.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule Wanda.Executions.State do
State of an execution.
"""

alias Wanda.Catalog
alias Wanda.Catalog.SelectedCheck
alias Wanda.Executions.Target

defstruct [
Expand All @@ -24,7 +24,7 @@ defmodule Wanda.Executions.State do
group_id: String.t(),
timeout: integer(),
targets: [Target.t()],
checks: [Catalog.Check.t()],
checks: [SelectedCheck.t()],
env: %{String.t() => boolean() | number() | String.t()},
gathered_facts: map(),
agents_gathered: [String.t()]
Expand Down
6 changes: 4 additions & 2 deletions lib/wanda/executions/value.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ defmodule Wanda.Executions.Value do
@derive Jason.Encoder
defstruct [
:name,
:value
:value,
:customized
]

@type t :: %__MODULE__{
name: String.t(),
value: boolean() | number() | String.t()
value: boolean() | number() | String.t(),
customized: boolean()
}
end
8 changes: 7 additions & 1 deletion lib/wanda_web/schemas/v1/execution/agent_check_result.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ defmodule WandaWeb.Schemas.V1.Execution.AgentCheckResult do
alias WandaWeb.Schemas.V1.Execution.{
ExpectationEvaluation,
ExpectationEvaluationError,
Fact
Fact,
Value
}

require OpenApiSpex
Expand All @@ -19,6 +20,11 @@ defmodule WandaWeb.Schemas.V1.Execution.AgentCheckResult do
properties: %{
agent_id: %Schema{type: :string, format: :uuid, description: "Agent ID"},
facts: %Schema{type: :array, items: Fact, description: "Facts gathered from the targets"},
values: %Schema{
type: :array,
items: Value,
description: "Values resolved for the current execution"
},
expectation_evaluations: %Schema{
type: :array,
items: %Schema{
Expand Down
1 change: 1 addition & 0 deletions lib/wanda_web/schemas/v1/execution/check_result.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ defmodule WandaWeb.Schemas.V1.Execution.CheckResult do
additionalProperties: false,
properties: %{
check_id: %Schema{type: :string, description: "Check ID"},
customized: %Schema{type: :boolean, description: "Whether the check has been customized"},
expectation_results: %Schema{type: :array, items: ExpectationResult},
agents_check_results: %Schema{
type: :array,
Expand Down
3 changes: 2 additions & 1 deletion lib/wanda_web/schemas/v1/execution/value.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ defmodule WandaWeb.Schemas.V1.Execution.Value do
value: %Schema{
oneOf: [%Schema{type: :string}, %Schema{type: :number}, %Schema{type: :boolean}],
description: "Value"
}
},
customized: %Schema{type: :boolean, description: "Whether the value has been customized"}
},
required: [:check_id, :name, :value]
},
Expand Down
1 change: 1 addition & 0 deletions lib/wanda_web/schemas/v2/execution/check_result.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ defmodule WandaWeb.Schemas.V2.Execution.CheckResult do
additionalProperties: false,
properties: %{
check_id: %Schema{type: :string, description: "Check ID"},
customized: %Schema{type: :boolean, description: "Whether the check has been customized"},
expectation_results: %Schema{type: :array, items: ExpectationResult},
agents_check_results: %Schema{
type: :array,
Expand Down
Loading

0 comments on commit c0f5a5b

Please sign in to comment.