Skip to content

Contributing

This guide covers the development workflow, tooling, and conventions for contributing to the OPNsense Exporter.

This page is the canonical contribution guide. The repository root CONTRIBUTING.md is a short pointer to this page — edit only this file; do not duplicate content into the root file.

Prerequisites

  • Go - Check go.mod for the required version (currently Go 1.26)
  • Make - For build automation
  • golangci-lint - Optional; runs in CI but can be used locally
  • Docker - Optional; for container builds

Getting started

Clone the repository:

git clone https://github.com/rknightion/opnsense-exporter.git
cd opnsense-exporter

Build commands

Command Description
make Build the binary (static, version-embedded)
make test Run all tests: go test ./...
make lint Run gofmt and golangci-lint --fix
make sync-vendor go mod tidy && go mod vendor
make clean Format source and remove the binary

Building on macOS

The Makefile uses -extldflags "-static" which does not work on macOS. Use this instead:

CGO_ENABLED=0 go build -o opnsense-exporter .

Running a single test

go test ./internal/collector/ -run TestCollector
go test ./opnsense/ -run TestFetchGateways

Project conventions

Vendor directory

The vendor directory is committed to the repository. Always run make sync-vendor after modifying go.mod:

make sync-vendor

Static binary

The build produces a fully static binary with:

  • CGO_ENABLED=0
  • -ldflags "-s -w" for stripped, optimized output
  • -trimpath and -mod=vendor for reproducibility

Version

The version is embedded at build time via -ldflags -X main.version=.... The version.txt file at the repository root is managed by release-please and tracks the current released version; GoReleaser embeds the git tag as the version in release builds, while local make builds embed local-test.

Linters

The project uses golangci-lint with:

  • misspell and revive enabled
  • unused disabled

Linting runs in CI. Locally, make lint will run gofmt formatting (the golangci-lint step may fail if the tool is not installed, which is expected).

Commit messages

This project uses conventional commits for automated changelog generation via release-please:

feat(collector): add new subsystem collector
fix(kea): handle disabled DHCP service response
docs: update README with new collector descriptions
refactor: modernize Go syntax patterns

Changelog

Release history lives entirely in CHANGELOG.md, generated by release-please from conventional commit messages. There is no separate "changes from upstream" section to maintain — do not add one to README.md; it was deliberately removed and conventional commit messages are what drive the changelog instead.

Pull request checklist

Before submitting a PR, the local equivalent of everything ci-success requires:

  • Code compiles: make or CGO_ENABLED=0 go build
  • Tests pass: go test ./... (make test)
  • Tests pass under the race detector: go test -race ./...
  • Linters pass: golangci-lint run ./... (make lint runs gofmt plus golangci-lint --fix)
  • Generated docs are current: make docs-check (run make docs first if it fails, then commit the regenerated files — never hand-edit content between <!-- docgen:begin/end --> markers)
  • Grafana dashboard/alerts are current: make grafana-check (run make dashboard and, if alert rules changed, make rules, then commit the regenerated files — see grafana/README.md)
  • Vendor is synced (if dependencies changed): make sync-vendor
  • Conventional commit messages used
  • New collectors follow the adding a collector guide

CI additionally verifies the container image builds and embeds the correct version (docker-build-verify); this can optionally be reproduced locally with docker build . but is not required before opening a PR.

Project structure

.
+-- main.go                      # Entry point
+-- internal/
|   +-- collector/               # Prometheus collectors
|   |   +-- collector.go         # Top-level collector, interface
|   |   +-- arp_table.go         # Per-subsystem collector files
|   |   +-- gateways.go
|   |   +-- ...
|   +-- options/                 # CLI flags and configuration
|   |   +-- ops.go               # OPNsense connection config
|   |   +-- exporter.go          # Server config
|   |   +-- collectors.go        # Collector enable/disable switches
|   +-- logship/                 # Syslog + Zenarmor receivers, log enrichment, OTLP log sink
|   |   +-- syslog/              # Per-program syslog parsers
|   |   +-- zenarmor/            # Zenarmor HTTP receiver
|   |   +-- enrich/              # Device/service enrichment
|   |   +-- flowlog/             # Sink for internal/flow's correlated flow logs
|   |   +-- ...
|   +-- flow/                    # NetFlow receiver, rollup, correlator
|   |   +-- netflow/             # NetFlow wire decode + listener
|   |   +-- correlate.go         # NetFlow + Zenarmor conn merge
|   |   +-- ...
|   +-- webui/                   # Operator console served at /
|   |   +-- server.go            # Route registration, self-registering tabs
|   |   +-- status.go
|   |   +-- ...
|   +-- metricsnap/              # Passive metric capture, teed at both scrape sites
|       +-- recorder.go
+-- opnsense/                    # API client
|   +-- client.go                # HTTP client, TLS, retries
|   +-- gateways.go              # Per-subsystem Fetch methods
|   +-- ...
+-- deploy/                      # Deployment manifests
|   +-- k8s/                     # Kubernetes manifests
+-- grafana/                     # Grafana dashboard builder, panels, alert rules
+-- docs/                        # Documentation (this site)
+-- vendor/                      # Vendored dependencies