Skip to content

Commit

Permalink
Feat/support firmware info (#43)
Browse files Browse the repository at this point in the history
* support firmware info

---------
Signed-off-by: Felix Kronlage-Dammers <[email protected]>
  • Loading branch information
fkr authored Jan 13, 2025
1 parent 1a56a2f commit a23a4e6
Show file tree
Hide file tree
Showing 9 changed files with 224 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Finaly we have a Grafana dashboard to visualize the data from this exporter. The
| GUI | Status: DNS Overview |
| GUI | Status: OpenVPN |
| GUI | Status: Services |
| GUI | System: Firmware |
| GUI | System: Gateways |
| GUI | System: Settings: Cron |
| GUI | System: Status |
Expand Down Expand Up @@ -179,6 +180,7 @@ Gathering metrics for specific subsystems can be disabled with the following fla
- `--exporter.disable-unbound` - Disable the scraping of Unbound service. Defaults to `false`.
- `--exporter.disable-openvpn` - Disable the scraping of OpenVPN service. Defaults to `false`.
- `--exporter.disable-firewall` - Disable the scraping of Firewall (pf) metrics. Defaults to `false`.
- `--exporter.disable-firmware` - Disable the scraping of Firmware infos. Defaults to `false`.

To disable the exporter metrics itself use the following flag:

Expand Down Expand Up @@ -208,6 +210,9 @@ Flags:
--[no-]exporter.disable-firewall
Disable the scraping of Firewall (pf) metrics
($OPNSENSE_EXPORTER_DISABLE_FIREWALL)
--[no-]exporter.disable-firmware
Disable the scraping of Firmware infos
($OPNSENSE_EXPORTER_DISABLE_FIRMWARE)
--web.telemetry-path="/metrics"
Path under which to expose metrics.
--[no-]web.disable-exporter-metrics
Expand Down
Binary file added docs/assets/firmware.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 14 additions & 0 deletions docs/metrics.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,21 @@ opnsense_firewall_in_ipv6_pass_packets | Gauge | interface | Firewall | The numb
opnsense_firewall_out_ipv6_block_packets | Gauge | interface | Firewall | The number of IPv6 outgoing packets that were blocked by the firewall by interface | --exporter.disable-firewall |
opnsense_firewall_out_ipv6_pass_packets | Gauge | interface | Firewall | The number of IPv6 outgoing packets that were passed by the firewall by interface | --exporter.disable-firewall |

### Firmware

![firmware](assets/firmware.png)

| Metric Name | Type | Labels | Subsystem | Description | Disable Flag |
| --- | --- | --- | --- | --- | --- |
opnsense_firmware_last_check | Gauge | last_check | firmware | Last upgrade check for OPNsense | --exporter.disable-firmware |
opnsense_firmware_needs_reboot | Gauge | needs_reboot | Firmware | Wether the OPNsense has a pending reboot | --exporter.disable-firmware |
opnsense_firmware_new_packages | Gauge | new_packages | Firmware | Amount of packages that will be newly installed during upgrade | --exporter.disable-firmware |
opnsense_firmware_os_version | Gauge | os_version | Firmware | OS Version of OPNsense | --exporter.disable-firmware |
opnsense_firmware_product_abi | Gauge | product_api | Firmware | Product ABI of OPNsense | --exporter.disable-firmware |
opnsense_firmware_product_id | Gauge | product_id | Firmware | Product ID (community or business) of OPNsense | --exporter.disable-firmware |
opnsense_firmware_product_version | Gauge | product_version | Firmware | Product version of OPNsense | --exporter.disable-firmware |
opnsense_firmware_upgrade_needs_reboot | Gauge | upgrade_needs_reboot | Firmware | Wether the upgrade will involve a reboot | --exporter.disable-firmware |
opnsense_firmware_upgrade_packages | Gauge | upgrade_packages | Firmware | Amount of packages that will be upgraded during upgrade | --exporter.disable-firmware |

### ARP

Expand Down
5 changes: 5 additions & 0 deletions internal/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
OpenVPNSubsystem = "openvpn"
ServicesSubsystem = "services"
FirewallSubsystem = "firewall"
FirmwareSubsystem = "firmware"
)

// CollectorInstance is the interface a service specific collectors must implement.
Expand Down Expand Up @@ -104,6 +105,10 @@ func WithoutFirewallCollector() Option {
return withoutCollectorInstance(FirewallSubsystem)
}

// WithoutFirmwareCollector Option
// removes the firmware collector from the list of collectors
func WithoutFirmwareCollector() Option { return withoutCollectorInstance(FirmwareSubsystem) }

// WithoutOpenVPNCollector Option
// removes the openvpn collector from the list of collectors
func WithoutOpenVPNCollector() Option {
Expand Down
108 changes: 108 additions & 0 deletions internal/collector/firmware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package collector

import (
"github.com/AthennaMind/opnsense-exporter/opnsense"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/prometheus/client_golang/prometheus"
"strconv"
)

type firmwareCollector struct {
log log.Logger

lastCheck *prometheus.Desc
needsReboot *prometheus.Desc
newPackages *prometheus.Desc
osVersion *prometheus.Desc
productAbi *prometheus.Desc
productId *prometheus.Desc
productVersion *prometheus.Desc
upgradePackages *prometheus.Desc
upgradeNeedsReboot *prometheus.Desc

subsystem string
instance string
}

func init() {
collectorInstances = append(collectorInstances, &firmwareCollector{
subsystem: FirmwareSubsystem,
})
}

func (c *firmwareCollector) Name() string {
return c.subsystem
}

func (c *firmwareCollector) Register(namespace, instanceLabel string, log log.Logger) {
c.log = log
c.instance = instanceLabel

level.Debug(c.log).
Log("msg", "Registering collector", "collector", c.Name())

c.lastCheck = buildPrometheusDesc(c.subsystem, "last_check",
"last check for upgrade", []string{"last_check"})

c.needsReboot = buildPrometheusDesc(c.subsystem, "needs_reboot",
"opnsense would like to be rebooted", []string{"needs_reboot"})

c.newPackages = buildPrometheusDesc(c.subsystem, "new_packages",
"new packages", []string{"new_packages"})

c.osVersion = buildPrometheusDesc(c.subsystem, "os_version",
"Version of this opnSense", []string{"os_version"})

c.productAbi = buildPrometheusDesc(c.subsystem, "product_abi",
"Product ABI of this opnSense", []string{"product_abi"})

c.productId = buildPrometheusDesc(c.subsystem, "product_id",
"Product ID of this opnSense", []string{"product_id"})

c.productVersion = buildPrometheusDesc(c.subsystem, "product_version",
"Product Version of this opnSense", []string{"product_version"})

c.upgradePackages = buildPrometheusDesc(c.subsystem, "upgrade_packages",
"upgrade packages", []string{"upgrade_packages"})

c.upgradeNeedsReboot = buildPrometheusDesc(c.subsystem, "upgrade_needs_reboot",
"upgrade involves reboot", []string{"upgrade_needs_reboot"})
}

func (c *firmwareCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.needsReboot
ch <- c.newPackages
ch <- c.lastCheck
ch <- c.osVersion
ch <- c.productAbi
ch <- c.productId
ch <- c.productVersion
ch <- c.upgradePackages
ch <- c.upgradeNeedsReboot
}

func (c *firmwareCollector) update(ch chan<- prometheus.Metric, desc *prometheus.Desc, valueType prometheus.ValueType, value float64, labelValues ...string) {
ch <- prometheus.MustNewConstMetric(
desc, valueType, value, labelValues...,
)
}

func (c *firmwareCollector) Update(client *opnsense.Client, ch chan<- prometheus.Metric) *opnsense.APICallError {
data, err := client.FetchFirmwareStatus()
if err != nil {
return err
}

ch <- prometheus.MustNewConstMetric(c.needsReboot, prometheus.GaugeValue, float64(data.NeedsReboot), strconv.Itoa(data.NeedsReboot), c.instance)
ch <- prometheus.MustNewConstMetric(c.newPackages, prometheus.GaugeValue, float64(data.NewPackages), strconv.Itoa(data.NewPackages), c.instance)
ch <- prometheus.MustNewConstMetric(c.lastCheck, prometheus.GaugeValue, float64(1), data.LastCheck, c.instance)
ch <- prometheus.MustNewConstMetric(c.osVersion, prometheus.GaugeValue, float64(1), data.OsVersion, c.instance)
ch <- prometheus.MustNewConstMetric(c.productAbi, prometheus.GaugeValue, float64(1), data.ProductABI, c.instance)
ch <- prometheus.MustNewConstMetric(c.productId, prometheus.GaugeValue, float64(1), data.ProductId, c.instance)
ch <- prometheus.MustNewConstMetric(c.productVersion, prometheus.GaugeValue, float64(1), data.ProductVersion, c.instance)
ch <- prometheus.MustNewConstMetric(c.upgradePackages, prometheus.GaugeValue, float64(data.UpgradePackages), strconv.Itoa(data.UpgradePackages), c.instance)
ch <- prometheus.MustNewConstMetric(c.upgradeNeedsReboot, prometheus.GaugeValue, float64(data.UpgradeNeedsReboot), strconv.Itoa(data.UpgradeNeedsReboot), c.instance)

return nil
}
6 changes: 6 additions & 0 deletions internal/options/collectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ var (
"exporter.disable-firewall",
"Disable the scraping of the firewall (pf) metrics",
).Envar("OPNSENSE_EXPORTER_DISABLE_FIREWALL").Default("false").Bool()
firmwareCollectorDisabled = kingpin.Flag(
"exporter.disable-firmware",
"Disable the scraping of the firmware metrics",
).Envar("OPNSENSE_EXPORTER_DISABLE_FIRMWARE").Default("false").Bool()
)

// CollectorsDisableSwitch hold the enabled/disabled state of the collectors
Expand All @@ -37,6 +41,7 @@ type CollectorsDisableSwitch struct {
Unbound bool
OpenVPN bool
Firewall bool
Firmware bool
}

// CollectorsSwitches returns configured instances of CollectorsDisableSwitch
Expand All @@ -48,5 +53,6 @@ func CollectorsSwitches() CollectorsDisableSwitch {
Unbound: !*unboundCollectorDisabled,
OpenVPN: !*openVPNCollectorDisabled,
Firewall: !*firewallCollectorDisabled,
Firmware: !*firmwareCollectorDisabled,
}
}
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ func main() {
collectorOptionFuncs = append(collectorOptionFuncs, collector.WithoutFirewallCollector())
level.Info(logger).Log("msg", "firewall collector disabled")
}
if !collectorsSwitches.Firmware {
collectorOptionFuncs = append(collectorOptionFuncs, collector.WithoutFirmwareCollector())
level.Info(logger).Log("msg", "firmware collector disabled")
}
if !collectorsSwitches.OpenVPN {
collectorOptionFuncs = append(collectorOptionFuncs, collector.WithoutOpenVPNCollector())
level.Info(logger).Log("msg", "openvpn collector disabled")
Expand Down
1 change: 1 addition & 0 deletions opnsense/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ func NewClient(cfg options.OPNSenseConfig, userAgentVersion string, log log.Logg
"cronJobs": "api/cron/settings/searchJobs",
"wireguardClients": "api/wireguard/service/show",
"healthCheck": "api/core/system/status",
"firmware": "api/core/firmware/status",
},
headers: map[string]string{
"Accept": "application/json",
Expand Down
81 changes: 81 additions & 0 deletions opnsense/firmware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package opnsense

type firmwareStatusResponse struct {
LastCheck string `json:"last_check"`
NeedsReboot string `json:"needs_reboot"`
OsVersion string `json:"os_version"`
ProductID string `json:"product_id"`
ProductVersion string `json:"product_version"`
ProductAbi string `json:"product_abi"`
NewPackages []struct {
Name string `json:"name"`
Repository string `json:"repository"`
Version string `json:"version"`
} `json:"new_packages"`
UpgradePackages []struct {
Name string `json:"name"`
Repository string `json:"repository"`
CurrentVersion string `json:"current_version"`
NewVersion string `json:"new_version"`
Size string `json:"size,omitempty"`
} `json:"upgrade_packages"`
Product struct {
ProductCheck struct {
UpgradeNeedsReboot string `json:"upgrade_needs_reboot"`
} `json:"product_check"`
} `json:"product"`
}

type FirmwareStatus struct {
LastCheck string
NeedsReboot int
NewPackages int
OsVersion string
ProductABI string
ProductId string
ProductVersion string
UpgradePackages int
UpgradeNeedsReboot int
}

func (c *Client) FetchFirmwareStatus() (FirmwareStatus, *APICallError) {
var resp firmwareStatusResponse
var data FirmwareStatus

url, ok := c.endpoints["firmware"]

if !ok {
return data, &APICallError{
Endpoint: "firmware",
Message: "Missing endpoint 'firmwareStatus'",
StatusCode: 0,
}
}

if err := c.do("GET", url, nil, &resp); err != nil {
return data, err
}

data.LastCheck = resp.LastCheck
data.OsVersion = resp.OsVersion
data.ProductABI = resp.ProductAbi
data.ProductId = resp.ProductID
data.ProductVersion = resp.ProductVersion

tNeedsReboot, err := parseStringToInt(resp.NeedsReboot, url)
if err != nil {
return data, err
}
data.NeedsReboot = tNeedsReboot

data.NewPackages = len(resp.NewPackages)
data.UpgradePackages = len(resp.UpgradePackages)

tUpgradeNeedsReboot, err := parseStringToInt(resp.Product.ProductCheck.UpgradeNeedsReboot, url)
if err != nil {
return data, err
}
data.UpgradeNeedsReboot = tUpgradeNeedsReboot

return data, nil
}

0 comments on commit a23a4e6

Please sign in to comment.