diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs new file mode 100644 index 0000000000..c0660e9a11 --- /dev/null +++ b/.dialyzer_ignore.exs @@ -0,0 +1,3 @@ +[ + ~r/.*domo_generated_code.*/ +] diff --git a/.formatter.exs b/.formatter.exs index 8c0cc76a3d..2df85018c5 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,5 +1,5 @@ [ - import_deps: [:ecto, :phoenix], + import_deps: [:ecto, :phoenix, :typed_struct], locals_without_parens: [ # mock assert_called: :* diff --git a/lib/tronto/application.ex b/lib/tronto/application.ex index fd258d5ad0..289364b0ef 100644 --- a/lib/tronto/application.ex +++ b/lib/tronto/application.ex @@ -16,6 +16,7 @@ defmodule Tronto.Application do {Phoenix.PubSub, name: Tronto.PubSub}, # Start the Endpoint (http/https) TrontoWeb.Endpoint, + Tronto.Commanded, Tronto.Scheduler # Start a worker by calling: Tronto.Worker.start_link(arg) # {Tronto.Worker, arg} diff --git a/lib/tronto/commanded.ex b/lib/tronto/commanded.ex index ee8d15bbd5..e9de3e2664 100644 --- a/lib/tronto/commanded.ex +++ b/lib/tronto/commanded.ex @@ -4,4 +4,6 @@ defmodule Tronto.Commanded do """ use Commanded.Application, otp_app: :tronto + + router(Tronto.Router) end diff --git a/lib/tronto/monitoring/domain/hosts/commands/register_host.ex b/lib/tronto/monitoring/domain/hosts/commands/register_host.ex new file mode 100644 index 0000000000..4a365f5360 --- /dev/null +++ b/lib/tronto/monitoring/domain/hosts/commands/register_host.ex @@ -0,0 +1,17 @@ +defmodule Tronto.Monitoring.Domain.Commands.RegisterHost do + @moduledoc """ + Register a host to the monitoring system. + """ + + use TypedStruct + use Domo + + typedstruct do + @typedoc "RegisterHost command" + + field :id_host, String.t(), enforce: true + field :hostname, String.t(), enforce: true + field :ip_addresses, [String.t()], enforce: true + field :agent_version, String.t(), enforce: true + end +end diff --git a/lib/tronto/monitoring/domain/hosts/events/host_registered.ex b/lib/tronto/monitoring/domain/hosts/events/host_registered.ex new file mode 100644 index 0000000000..c6bab1db56 --- /dev/null +++ b/lib/tronto/monitoring/domain/hosts/events/host_registered.ex @@ -0,0 +1,17 @@ +defmodule Tronto.Monitoring.Domain.Events.HostRegistered do + @moduledoc """ + This event is emitted when a host is registered. + """ + + use TypedStruct + + @derive Jason.Encoder + typedstruct do + @typedoc "HostRegistered event" + + field :id_host, String.t(), enforce: true + field :hostname, String.t(), enforce: true + field :ip_addresses, [String.t()], enforce: true + field :agent_version, String.t(), enforce: true + end +end diff --git a/lib/tronto/monitoring/domain/hosts/host.ex b/lib/tronto/monitoring/domain/hosts/host.ex new file mode 100644 index 0000000000..854b26b47f --- /dev/null +++ b/lib/tronto/monitoring/domain/hosts/host.ex @@ -0,0 +1,70 @@ +defmodule Tronto.Monitoring.Domain.Host do + @moduledoc false + + alias Tronto.Monitoring.Domain.Host + + alias Tronto.Monitoring.Domain.Commands.{ + RegisterHost + } + + alias Tronto.Monitoring.Domain.Events.{ + HostRegistered + } + + defstruct [ + :id_host, + :hostname, + :ip_addresses, + :agent_version + ] + + @type t :: %__MODULE__{ + id_host: String.t(), + hostname: String.t(), + ip_addresses: [String.t()], + agent_version: String.t() + } + + # New host registered + def execute( + %Host{id_host: nil}, + %RegisterHost{ + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + ) do + %HostRegistered{ + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + end + + def execute( + %Host{id_host: _}, + %RegisterHost{} + ) do + [] + end + + def apply( + %Host{} = host, + %HostRegistered{ + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + ) do + %Host{ + host + | id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + end +end diff --git a/lib/tronto/router.ex b/lib/tronto/router.ex new file mode 100644 index 0000000000..773e8c0b24 --- /dev/null +++ b/lib/tronto/router.ex @@ -0,0 +1,12 @@ +defmodule Tronto.Router do + use Commanded.Commands.Router + + alias Tronto.Monitoring.Domain.Host + + alias Tronto.Monitoring.Domain.Commands.{ + RegisterHost + } + + identify(Host, by: :id_host) + dispatch(RegisterHost, to: Host) +end diff --git a/mix.exs b/mix.exs index 78d42c78f5..b17185d217 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ defmodule Tronto.MixProject do version: "0.1.0", elixir: "~> 1.12", elixirc_paths: elixirc_paths(Mix.env()), - compilers: [:gettext] ++ Mix.compilers(), + compilers: [:gettext, :domo_compiler] ++ Mix.compilers(), start_permanent: Mix.env() == :prod, aliases: aliases(), deps: deps(), @@ -30,7 +30,9 @@ defmodule Tronto.MixProject do end # Specifies which paths to compile per environment. - defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(:test), + do: ["lib", "test/support", "deps/commanded/test/support/aggregate_case.ex"] + defp elixirc_paths(_), do: ["lib"] # Specifies your project dependencies. @@ -42,6 +44,7 @@ defmodule Tronto.MixProject do {:commanded_eventstore_adapter, "~> 1.2"}, {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}, + {:domo, "~> 1.5"}, {:ecto_sql, "~> 3.6"}, {:esbuild, "~> 0.2", runtime: Mix.env() == :dev}, {:faker, "~> 0.17", only: :test}, @@ -61,7 +64,8 @@ defmodule Tronto.MixProject do {:quantum, ">= 1.8.0"}, {:swoosh, "~> 1.3"}, {:telemetry_metrics, "~> 0.6"}, - {:telemetry_poller, "~> 1.0"} + {:telemetry_poller, "~> 1.0"}, + {:typed_struct, "~> 0.2.1"} ] end diff --git a/mix.lock b/mix.lock index 1678c406ef..fec69f7f67 100644 --- a/mix.lock +++ b/mix.lock @@ -13,6 +13,7 @@ "db_connection": {:hex, :db_connection, "2.4.1", "6411f6e23f1a8b68a82fa3a36366d4881f21f47fc79a9efb8c615e62050219da", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ea36d226ec5999781a9a8ad64e5d8c4454ecedc7a4d643e4832bf08efca01f00"}, "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, + "domo": {:hex, :domo, "1.5.1", "188f1ab6fa4d57c531d4fc191406505e9be5d03b38e890fb51ad55b946e6adb9", [:mix], [{:decimal, ">= 0.0.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "bc5b9cd87bd3557bc7cd997df3dcd375a808565fe9c5858ddd6ad476389b483e"}, "ecto": {:hex, :ecto, "3.7.1", "a20598862351b29f80f285b21ec5297da1181c0442687f9b8329f0445d228892", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d36e5b39fc479e654cffd4dbe1865d9716e4a9b6311faff799b6f90ab81b8638"}, "ecto_sql": {:hex, :ecto_sql, "3.7.1", "8de624ef50b2a8540252d8c60506379fbbc2707be1606853df371cf53df5d053", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2b42a32e2ce92f64aba5c88617891ab3b0ba34f3f3a503fa20009eae1a401c81"}, "elixir_uuid": {:hex, :elixir_uuid, "1.2.1", "dce506597acb7e6b0daeaff52ff6a9043f5919a4c3315abb4143f0b00378c097", [:mix], [], "hexpm", "f7eba2ea6c3555cea09706492716b0d87397b88946e6380898c2889d68585752"}, @@ -50,4 +51,5 @@ "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "telemetry_registry": {:hex, :telemetry_registry, "0.3.0", "6768f151ea53fc0fbca70dbff5b20a8d663ee4e0c0b2ae589590e08658e76f1e", [:mix, :rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e2adbc609f3e79ece7f29fec363a97a2c484ac78a83098535d6564781e917"}, + "typed_struct": {:hex, :typed_struct, "0.2.1", "e1993414c371f09ff25231393b6430bd89d780e2a499ae3b2d2b00852f593d97", [:mix], [], "hexpm", "8f5218c35ec38262f627b2c522542f1eae41f625f92649c0af701a6fab2e11b3"}, } diff --git a/test/tronto/domain/host/host_test.exs b/test/tronto/domain/host/host_test.exs new file mode 100644 index 0000000000..526063334c --- /dev/null +++ b/test/tronto/domain/host/host_test.exs @@ -0,0 +1,69 @@ +defmodule Tronto.Monitoring.HostTest do + use Commanded.AggregateCase, aggregate: Tronto.Monitoring.Domain.Host, async: true + + alias Tronto.Monitoring.Domain.Host + alias Tronto.Monitoring.Domain.Commands.RegisterHost + alias Tronto.Monitoring.Domain.Events.HostRegistered + + describe "host registration" do + test "should register a host" do + id_host = Faker.UUID.v4() + hostname = Faker.StarWars.character() + ip_addresses = [Faker.Internet.ip_v4_address()] + agent_version = Faker.Internet.slug() + + commands = [ + RegisterHost.new!( + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + ) + ] + + assert_events( + commands, + %HostRegistered{ + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + ) + + assert_state( + commands, + %Host{ + id_host: id_host, + hostname: hostname, + ip_addresses: ip_addresses, + agent_version: agent_version + } + ) + end + + test "should not register a host if it is already registered" do + id_host = Faker.UUID.v4() + + assert_events( + [ + %HostRegistered{ + id_host: id_host, + hostname: Faker.StarWars.character(), + ip_addresses: [Faker.Internet.ip_v4_address()], + agent_version: Faker.Internet.slug() + } + ], + [ + RegisterHost.new!( + id_host: id_host, + hostname: Faker.StarWars.character(), + ip_addresses: [Faker.Internet.ip_v4_address()], + agent_version: Faker.Internet.slug() + ) + ], + [] + ) + end + end +end