live reload yaml in dev

This commit is contained in:
Graham McIntire 2026-01-25 13:38:17 -06:00
parent 6e86f54aa0
commit 9cd3d57433
No known key found for this signature in database
2 changed files with 87 additions and 0 deletions

View file

@ -34,6 +34,7 @@ defmodule Towerops.Application do
SnmpKit.SnmpMgr.Config,
SnmpKit.SnmpMgr.MIB
] ++
dev_only_workers() ++
background_workers() ++
[
# Start to serve requests, typically the last entry
@ -68,6 +69,18 @@ defmodule Towerops.Application do
end
end
# Configure dev-only workers - profile watcher for auto-reload
defp dev_only_workers do
if Application.get_env(:towerops, :env) == :dev do
[
# Watch YAML profiles for changes and auto-reload
Towerops.Profiles.ProfileWatcher
]
else
[]
end
end
# Returns PubSub spec - uses Redis in production/clustered environments,
# falls back to default PG2 adapter for Mix tasks and development
defp pubsub_spec do

View file

@ -0,0 +1,74 @@
defmodule Towerops.Profiles.ProfileWatcher do
@moduledoc """
Watches YAML profile files for changes and automatically reloads them.
Uses FileSystem to monitor the priv/profiles directory for file changes.
When a .yaml or .yml file is created, modified, or deleted, triggers
a reload of all profiles via YamlProfiles.reload/0.
"""
use GenServer
alias Towerops.Profiles.YamlProfiles
require Logger
@profiles_dir "priv/profiles"
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
@impl true
def init(_opts) do
# Get the absolute path to the profiles directory
profiles_path = Path.join(Application.app_dir(:towerops), @profiles_dir)
# Start watching the profiles directory for changes
{:ok, watcher_pid} = FileSystem.start_link(dirs: [profiles_path])
FileSystem.subscribe(watcher_pid)
Logger.info("ProfileWatcher: Started watching #{profiles_path}")
{:ok, %{watcher_pid: watcher_pid, profiles_path: profiles_path}}
end
@impl true
def handle_info({:file_event, _watcher_pid, {path, events}}, state) do
# Only reload if it's a YAML file change
if yaml_file?(path) and should_reload?(events) do
Logger.info("ProfileWatcher: Detected change in #{Path.relative_to_cwd(path)}, reloading profiles...")
case YamlProfiles.reload() do
{:ok, count} ->
Logger.info("ProfileWatcher: Successfully reloaded #{count} profiles")
{:error, reason} ->
Logger.error("ProfileWatcher: Failed to reload profiles: #{inspect(reason)}")
end
end
{:noreply, state}
end
@impl true
def handle_info({:file_event, _watcher_pid, :stop}, state) do
Logger.warning("ProfileWatcher: File watcher stopped")
{:noreply, state}
end
# Check if the file is a YAML profile file
defp yaml_file?(path) do
String.ends_with?(path, [".yaml", ".yml"])
end
# Check if the events indicate we should reload
# Reload on created, modified, or removed events
# Don't reload on :undefined or other metadata-only changes
defp should_reload?(events) when is_list(events) do
Enum.any?(events, fn event ->
event in [:created, :modified, :removed, :renamed]
end)
end
defp should_reload?(_), do: false
end