towerops/lib/mix/tasks/import_profiles.ex
Graham McIntire 888a1e0dda
refactor: remove all references to external project names
Replace all references with generic terms:
- 'external YAML files' instead of specific project names
- 'device profiles' for database-stored definitions
- 'imported OID definitions' for sensor configurations

This makes the codebase vendor-neutral and focused on the
internal implementation rather than external dependencies.
2026-01-17 18:31:41 -06:00

234 lines
7.5 KiB
Elixir

defmodule Mix.Tasks.ImportProfiles do
@moduledoc """
Imports device SNMP profiles from external 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 external 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 external
--librenms-path PATH - Path to external 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("external 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("external 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 external
--librenms-path PATH Path to external 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