From 4e290b1938a37b420cf709f8aab5a85a79b2ceef Mon Sep 17 00:00:00 2001 From: Graham McIntire Date: Wed, 11 Feb 2026 13:27:46 -0600 Subject: [PATCH] fix String.replace crash when YAML index is numeric YAML parses bare numbers like `index: 0` as integers. coerce index_template to string before calling String.replace in both build_table_sensor and build_state_sensor. --- lib/towerops/snmp/profiles/dynamic.ex | 6 ++-- test/towerops/snmp/profiles/dynamic_test.exs | 37 ++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/lib/towerops/snmp/profiles/dynamic.ex b/lib/towerops/snmp/profiles/dynamic.ex index 1e8b4ff5..1b346ac4 100644 --- a/lib/towerops/snmp/profiles/dynamic.ex +++ b/lib/towerops/snmp/profiles/dynamic.ex @@ -324,7 +324,8 @@ defmodule Towerops.Snmp.Profiles.Dynamic do template -> # Replace {{ $index }} with the actual OID index - String.replace(template, "{{ $index }}", oid_index) + # Coerce to string since YAML parses bare numbers (e.g. `index: 0`) as integers + template |> to_string() |> String.replace("{{ $index }}", oid_index) end %{ @@ -387,7 +388,8 @@ defmodule Towerops.Snmp.Profiles.Dynamic do template -> # Replace {{ $index }} with the actual OID index - String.replace(template, "{{ $index }}", oid_index) + # Coerce to string since YAML parses bare numbers (e.g. `index: 0`) as integers + template |> to_string() |> String.replace("{{ $index }}", oid_index) end %{ diff --git a/test/towerops/snmp/profiles/dynamic_test.exs b/test/towerops/snmp/profiles/dynamic_test.exs index ebdd0b6a..6ee846ad 100644 --- a/test/towerops/snmp/profiles/dynamic_test.exs +++ b/test/towerops/snmp/profiles/dynamic_test.exs @@ -1110,5 +1110,42 @@ defmodule Towerops.Snmp.Profiles.DynamicTest do indices = count_sensors |> Enum.map(& &1.sensor_index) |> Enum.sort() assert indices == ["count_1", "count_2"] end + + test "handles numeric index_template from YAML without crashing" do + # YAML parses bare numbers like `index: 0` as integers, not strings. + # This must not crash String.replace/4. + profile = + base_profile(%{ + count_sensor_oids: [ + %{ + base_oid: "1.3.6.1.4.1.99999.1.1", + sensor_type: "count", + sensor_descr: "Memory Usage", + sensor_unit: "", + sensor_divisor: 1, + descr_oid: nil, + index_template: 0 + } + ] + }) + + stub(SnmpMock, :get, fn _, _, _ -> {:error, :no_such_object} end) + + stub(SnmpMock, :walk, fn _, oid, _ -> + case oid do + "1.3.6.1.4.1.99999.1.1" -> + {:ok, [%{oid: "1.3.6.1.4.1.99999.1.1.1", value: {:integer, 42}}]} + + _ -> + {:ok, []} + end + end) + + assert {:ok, sensors} = Dynamic.discover_sensors(profile, @client_opts) + + count_sensors = Enum.filter(sensors, &(&1.sensor_type == "count")) + assert length(count_sensors) == 1 + assert hd(count_sensors).sensor_index == "0" + end end end