199 lines
5.6 KiB
Elixir
199 lines
5.6 KiB
Elixir
defmodule Mix.Tasks.UploadLibrenms do
|
|
@shortdoc "Upload MIBs and import profiles from LibreNMS installation"
|
|
|
|
@moduledoc """
|
|
Uploads MIB files and imports device profiles from a LibreNMS installation.
|
|
|
|
## Usage
|
|
|
|
# Upload from local LibreNMS installation
|
|
mix upload_librenms --librenms-path ~/dev/librenms
|
|
|
|
# Upload to specific server
|
|
mix upload_librenms --librenms-path ~/dev/librenms --url https://towerops.net
|
|
|
|
# Upload specific profiles only
|
|
mix upload_librenms --librenms-path ~/dev/librenms --profiles mikrotik,cisco
|
|
|
|
## Options
|
|
|
|
* `--librenms-path` - Path to LibreNMS installation directory (required)
|
|
* `--token` - Superuser API token for authentication (required for MIB upload)
|
|
* `--url` - Server URL (default: http://localhost:4000)
|
|
* `--profiles` - Comma-separated list of profiles to import (default: all)
|
|
|
|
## What it does
|
|
|
|
1. Uploads all MIB files from `{librenms-path}/mibs` to the server
|
|
2. Imports device profiles from `{librenms-path}/resources/definitions`
|
|
|
|
## Requirements
|
|
|
|
* LibreNMS installation with mibs/ and resources/definitions/ directories
|
|
* Server must be running and accessible
|
|
"""
|
|
|
|
use Mix.Task
|
|
|
|
require Logger
|
|
|
|
@requirements ["app.start"]
|
|
|
|
def run(args) do
|
|
{opts, _} =
|
|
OptionParser.parse!(args,
|
|
strict: [
|
|
librenms_path: :string,
|
|
url: :string,
|
|
token: :string,
|
|
profiles: :string
|
|
]
|
|
)
|
|
|
|
librenms_path = opts[:librenms_path] || raise "Missing --librenms-path argument"
|
|
base_url = opts[:url] || "http://localhost:4000"
|
|
token = opts[:token]
|
|
profiles = opts[:profiles]
|
|
|
|
if !File.exists?(librenms_path) do
|
|
raise "LibreNMS directory not found: #{librenms_path}"
|
|
end
|
|
|
|
# Step 1: Upload MIBs
|
|
mibs_path = Path.join(librenms_path, "mibs")
|
|
|
|
if File.exists?(mibs_path) do
|
|
if token do
|
|
Logger.info("Step 1/2: Uploading MIB files from #{mibs_path}...")
|
|
upload_mibs(mibs_path, base_url, token)
|
|
else
|
|
Logger.warning("Skipping MIB upload: --token is required for MIB uploads (superuser API token needed)")
|
|
end
|
|
else
|
|
Logger.warning("MIBs directory not found: #{mibs_path}, skipping MIB upload")
|
|
end
|
|
|
|
# Step 2: Import profiles
|
|
definitions_path = Path.join(librenms_path, "resources/definitions")
|
|
|
|
if File.exists?(definitions_path) do
|
|
Logger.info("Step 2/2: Importing device profiles from #{definitions_path}...")
|
|
import_profiles(definitions_path, profiles)
|
|
else
|
|
Logger.warning("Definitions directory not found: #{definitions_path}, skipping profile import")
|
|
end
|
|
|
|
Logger.info("Upload complete!")
|
|
end
|
|
|
|
defp upload_mibs(mibs_path, base_url, token) do
|
|
# Find all vendor directories in mibs/
|
|
vendor_dirs =
|
|
mibs_path
|
|
|> Path.join("*")
|
|
|> Path.wildcard()
|
|
|> Enum.filter(&File.dir?/1)
|
|
|> Enum.sort()
|
|
|
|
if Enum.empty?(vendor_dirs) do
|
|
Logger.warning("No vendor directories found in #{mibs_path}")
|
|
else
|
|
Logger.info("Found #{length(vendor_dirs)} vendor directories")
|
|
|
|
# Upload each vendor's MIBs
|
|
results = Enum.flat_map(vendor_dirs, &upload_vendor_mibs(&1, base_url, token))
|
|
|
|
report_upload_results(results)
|
|
end
|
|
end
|
|
|
|
defp upload_vendor_mibs(vendor_dir, base_url, token) do
|
|
vendor = Path.basename(vendor_dir)
|
|
Logger.info("Uploading MIBs for vendor: #{vendor}")
|
|
|
|
vendor_dir
|
|
|> find_mib_files()
|
|
|> Enum.map(&upload_single_mib(&1, vendor, base_url, token))
|
|
|> Enum.reject(&is_nil/1)
|
|
end
|
|
|
|
defp find_mib_files(vendor_dir) do
|
|
vendor_dir
|
|
|> Path.join("*")
|
|
|> Path.wildcard()
|
|
|> Enum.filter(&File.regular?/1)
|
|
|> Enum.sort()
|
|
end
|
|
|
|
defp upload_single_mib(file_path, vendor, base_url, token) do
|
|
if File.regular?(file_path) do
|
|
filename = Path.basename(file_path)
|
|
Logger.debug(" Uploading: #{filename}")
|
|
|
|
case upload_mib_file(file_path, vendor, base_url, token) do
|
|
:ok ->
|
|
{:ok, filename}
|
|
|
|
{:error, reason} ->
|
|
Logger.error(" ✗ Failed to upload #{filename}: #{inspect(reason)}")
|
|
{:error, filename, reason}
|
|
end
|
|
else
|
|
Logger.debug(" Skipping non-file: #{Path.basename(file_path)}")
|
|
nil
|
|
end
|
|
end
|
|
|
|
defp report_upload_results(results) do
|
|
successes = Enum.count(results, &match?({:ok, _}, &1))
|
|
failures = Enum.count(results, &match?({:error, _, _}, &1))
|
|
Logger.info("MIB upload complete: #{successes} succeeded, #{failures} failed")
|
|
end
|
|
|
|
defp upload_mib_file(file_path, vendor, base_url, token) do
|
|
url = "#{base_url}/api/v1/mibs"
|
|
filename = Path.basename(file_path)
|
|
|
|
# Build base request
|
|
req = Req.new(url: url)
|
|
|
|
# Add authorization header if token is provided
|
|
req =
|
|
if token do
|
|
Req.Request.put_header(req, "authorization", "Bearer #{token}")
|
|
else
|
|
req
|
|
end
|
|
|
|
# Add multipart form data
|
|
form_data = [
|
|
file: {file_path, filename: filename},
|
|
vendor: vendor
|
|
]
|
|
|
|
case Req.post(req, form_multipart: form_data) do
|
|
{:ok, %{status: status}} when status in 200..299 ->
|
|
:ok
|
|
|
|
{:ok, %{status: status, body: body}} ->
|
|
{:error, "HTTP #{status}: #{inspect(body)}"}
|
|
|
|
{:error, reason} ->
|
|
{:error, reason}
|
|
end
|
|
end
|
|
|
|
defp import_profiles(definitions_path, profiles_filter) do
|
|
# Call the existing import_profiles task
|
|
args =
|
|
case profiles_filter do
|
|
nil ->
|
|
["--source-path", definitions_path]
|
|
|
|
profiles ->
|
|
["--source-path", definitions_path, "--profiles", profiles]
|
|
end
|
|
|
|
Mix.Task.run("import_profiles", args)
|
|
end
|
|
end
|