Implement dynamic device profile system using LibreNMS YAML definitions: - Create 7 database tables for device profiles, detection rules, sensors, processors, memory pools - Add DeviceProfiles context module with profile matching logic - Add YAML importer to parse LibreNMS os_detection and os_discovery files - Add mix task for importing profiles from LibreNMS repository - Create Dynamic profile module to interpret database definitions at runtime - Update discovery.ex to check database profiles before hard-coded modules - Fix Redis PubSub configuration to support unnamed nodes for Mix tasks Imported 5 Ubiquiti/Cambium profiles: epmp, airos-af, airos-af-ltu, airos-af60, unifi The system now supports 671+ device profiles from LibreNMS without requiring code changes for each device type.
234 lines
7.5 KiB
Elixir
234 lines
7.5 KiB
Elixir
defmodule Mix.Tasks.ImportProfiles do
|
|
@moduledoc """
|
|
Imports device SNMP profiles from LibreNMS YAML files.
|
|
|
|
## Usage
|
|
|
|
# Import a single profile
|
|
mix import_profiles --profile epmp --detection ~/dev/librenms/resources/definitions/os_detection/epmp.yaml --discovery ~/dev/librenms/resources/definitions/os_discovery/epmp.yaml
|
|
|
|
# Import specific profiles
|
|
mix import_profiles --profiles epmp,airos-af,unifi
|
|
|
|
# Import all profiles from LibreNMS directory
|
|
mix import_profiles --all --librenms-path ~/dev/librenms
|
|
|
|
## Options
|
|
|
|
--profile NAME - Import a single profile by name
|
|
--profiles LIST - Import multiple profiles (comma-separated)
|
|
--all - Import all profiles from LibreNMS
|
|
--librenms-path PATH - Path to LibreNMS installation
|
|
--detection PATH - Path to detection YAML file
|
|
--discovery PATH - Path to discovery YAML file (optional)
|
|
--force - Delete existing profile before importing
|
|
|
|
"""
|
|
use Mix.Task
|
|
|
|
alias Towerops.DeviceProfiles
|
|
alias Towerops.DeviceProfiles.Importer
|
|
alias Towerops.Repo
|
|
|
|
@requirements ["app.start"]
|
|
|
|
@impl Mix.Task
|
|
def run(args) do
|
|
{opts, _, _} =
|
|
OptionParser.parse(args,
|
|
strict: [
|
|
profile: :string,
|
|
profiles: :string,
|
|
all: :boolean,
|
|
librenms_path: :string,
|
|
detection: :string,
|
|
discovery: :string,
|
|
force: :boolean
|
|
]
|
|
)
|
|
|
|
cond do
|
|
opts[:all] ->
|
|
import_all_profiles(opts)
|
|
|
|
opts[:profiles] ->
|
|
import_multiple_profiles(opts)
|
|
|
|
opts[:profile] || opts[:detection] ->
|
|
import_single_profile(opts)
|
|
|
|
true ->
|
|
print_usage()
|
|
end
|
|
end
|
|
|
|
defp import_single_profile(opts) do
|
|
detection_file = opts[:detection]
|
|
discovery_file = opts[:discovery]
|
|
force = opts[:force] || false
|
|
|
|
if detection_file && File.exists?(detection_file) do
|
|
os_name = Path.basename(detection_file, ".yaml")
|
|
|
|
if force do
|
|
delete_existing_profile(os_name)
|
|
end
|
|
|
|
Mix.shell().info("Importing profile: #{os_name}")
|
|
|
|
case Importer.import_profile(detection_file, discovery_file) do
|
|
{:ok, profile} ->
|
|
Mix.shell().info("Successfully imported profile: #{profile.os}")
|
|
print_profile_stats(profile)
|
|
|
|
{:error, reason} ->
|
|
Mix.shell().error("Failed to import profile: #{inspect(reason)}")
|
|
end
|
|
else
|
|
Mix.shell().error("Detection file not found: #{detection_file}")
|
|
end
|
|
end
|
|
|
|
defp import_multiple_profiles(opts) do
|
|
profiles = String.split(opts[:profiles], ",", trim: true)
|
|
librenms_path = opts[:librenms_path] || guess_librenms_path()
|
|
force = opts[:force] || false
|
|
|
|
if librenms_path do
|
|
detection_dir = Path.join(librenms_path, "resources/definitions/os_detection")
|
|
discovery_dir = Path.join(librenms_path, "resources/definitions/os_discovery")
|
|
|
|
Enum.each(profiles, fn profile_name ->
|
|
import_profile_by_name(profile_name, detection_dir, discovery_dir, force)
|
|
end)
|
|
else
|
|
Mix.shell().error("LibreNMS path not found. Use --librenms-path option.")
|
|
end
|
|
end
|
|
|
|
defp import_all_profiles(opts) do
|
|
librenms_path = opts[:librenms_path] || guess_librenms_path()
|
|
force = opts[:force] || false
|
|
|
|
if librenms_path do
|
|
detection_dir = Path.join(librenms_path, "resources/definitions/os_detection")
|
|
discovery_dir = Path.join(librenms_path, "resources/definitions/os_discovery")
|
|
|
|
if force do
|
|
Mix.shell().info("Deleting all existing profiles...")
|
|
delete_all_profiles()
|
|
end
|
|
|
|
Mix.shell().info("Importing all profiles from: #{librenms_path}")
|
|
Mix.shell().info("This may take several minutes...")
|
|
|
|
case Importer.import_all_from_directory(detection_dir, discovery_dir) do
|
|
{:ok, stats} ->
|
|
Mix.shell().info("\nImport complete!")
|
|
Mix.shell().info("Successfully imported: #{stats.success} profiles")
|
|
Mix.shell().info("Failed to import: #{stats.failed} profiles")
|
|
|
|
{:error, reason} ->
|
|
Mix.shell().error("Failed to import profiles: #{inspect(reason)}")
|
|
end
|
|
else
|
|
Mix.shell().error("LibreNMS path not found. Use --librenms-path option.")
|
|
end
|
|
end
|
|
|
|
defp import_profile_by_name(profile_name, detection_dir, discovery_dir, force) do
|
|
detection_file = Path.join(detection_dir, "#{profile_name}.yaml")
|
|
discovery_file = Path.join(discovery_dir, "#{profile_name}.yaml")
|
|
|
|
discovery_file = if File.exists?(discovery_file), do: discovery_file
|
|
|
|
if File.exists?(detection_file) do
|
|
if force do
|
|
delete_existing_profile(profile_name)
|
|
end
|
|
|
|
Mix.shell().info("Importing profile: #{profile_name}")
|
|
|
|
case Importer.import_profile(detection_file, discovery_file) do
|
|
{:ok, profile} ->
|
|
Mix.shell().info("✓ Successfully imported: #{profile.os}")
|
|
|
|
{:error, reason} ->
|
|
Mix.shell().error("✗ Failed to import #{profile_name}: #{inspect(reason)}")
|
|
end
|
|
else
|
|
Mix.shell().error("Detection file not found for: #{profile_name}")
|
|
end
|
|
end
|
|
|
|
defp delete_existing_profile(os_name) do
|
|
if profile = DeviceProfiles.get_profile(os_name) do
|
|
Mix.shell().info("Deleting existing profile: #{os_name}")
|
|
DeviceProfiles.delete_profile(profile)
|
|
end
|
|
end
|
|
|
|
defp delete_all_profiles do
|
|
profiles = DeviceProfiles.list_profiles()
|
|
|
|
Enum.each(profiles, fn profile ->
|
|
DeviceProfiles.delete_profile(profile)
|
|
end)
|
|
|
|
Mix.shell().info("Deleted #{length(profiles)} existing profiles")
|
|
end
|
|
|
|
defp guess_librenms_path do
|
|
home = System.get_env("HOME")
|
|
possible_paths = ["~/dev/librenms", "#{home}/dev/librenms", "/opt/librenms"]
|
|
|
|
possible_paths
|
|
|> Enum.find(fn path ->
|
|
expanded = Path.expand(path)
|
|
File.dir?(expanded)
|
|
end)
|
|
|> case do
|
|
nil -> nil
|
|
path -> Path.expand(path)
|
|
end
|
|
end
|
|
|
|
defp print_profile_stats(profile) do
|
|
profile = Repo.preload(profile, [:detection_rules, :sensor_definitions, :processor_definitions])
|
|
|
|
Mix.shell().info("")
|
|
Mix.shell().info(" OS: #{profile.os}")
|
|
Mix.shell().info(" Text: #{profile.text}")
|
|
Mix.shell().info(" Type: #{profile.type}")
|
|
Mix.shell().info(" Detection rules: #{length(profile.detection_rules)}")
|
|
Mix.shell().info(" Sensor definitions: #{length(profile.sensor_definitions)}")
|
|
Mix.shell().info(" Processor definitions: #{length(profile.processor_definitions)}")
|
|
Mix.shell().info("")
|
|
end
|
|
|
|
defp print_usage do
|
|
Mix.shell().info("""
|
|
Usage:
|
|
mix import_profiles [options]
|
|
|
|
Options:
|
|
--profile NAME Import a single profile by name
|
|
--profiles LIST Import multiple profiles (comma-separated)
|
|
--all Import all profiles from LibreNMS
|
|
--librenms-path PATH Path to LibreNMS installation
|
|
--detection PATH Path to detection YAML file
|
|
--discovery PATH Path to discovery YAML file (optional)
|
|
--force Delete existing profile before importing
|
|
|
|
Examples:
|
|
# Import specific profiles
|
|
mix import_profiles --profiles epmp,airos-af,unifi --librenms-path ~/dev/librenms
|
|
|
|
# Import a single profile with explicit paths
|
|
mix import_profiles --detection ~/dev/librenms/resources/definitions/os_detection/epmp.yaml --discovery ~/dev/librenms/resources/definitions/os_discovery/epmp.yaml
|
|
|
|
# Import all profiles (takes several minutes)
|
|
mix import_profiles --all --librenms-path ~/dev/librenms
|
|
""")
|
|
end
|
|
end
|