Skip to content

Commit

Permalink
fix(linux): 🐛 actually report correct distribution, distribution and …
Browse files Browse the repository at this point in the history
…kernel version as sensors
  • Loading branch information
joshuar committed May 24, 2024
1 parent d07a9b1 commit 6db5082
Show file tree
Hide file tree
Showing 9 changed files with 447 additions and 80 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
!codecov.yml

!/assets/**/*
!**/testdata/*

!*.md
!*.txt
Expand Down
2 changes: 1 addition & 1 deletion internal/agent/device_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func sensorWorkers() []func(context.Context) chan sensor.Details {
power.ProfileUpdater,
power.IdleUpdater,
user.Updater,
system.Versions,
system.InfoUpdater,
system.HWSensorUpdater,
system.UptimeUpdater,
desktop.Updater,
Expand Down
78 changes: 66 additions & 12 deletions internal/linux/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package linux
import (
"os"
"strings"
"syscall"

"github.com/gofrs/uuid/v5"
"github.com/iancoleman/strcase"
Expand Down Expand Up @@ -95,19 +96,8 @@ func NewDevice(name, version string) *Device {
deviceID: getDeviceID(),
hostname: getHostname(),
}

osReleaseInfo, err := whichdistro.GetOSRelease()
if err != nil {
log.Warn().Err(err).Msg("Could not read /etc/os-release. Contact your distro vendor to implement this file.")
dev.distro = unknownDistro
dev.distroVersion = unknownDistroVersion
} else {
dev.distro = osReleaseInfo["ID"]
dev.distroVersion = osReleaseInfo["VERSION_ID"]
}

dev.distro, dev.distroVersion = GetDistroID()
dev.hwModel, dev.hwVendor = getHWProductInfo()

return dev
}

Expand Down Expand Up @@ -185,3 +175,67 @@ func FindPortal() string {
return ""
}
}

// GetDistroID will retrieve the distribution ID and version ID. These are
// suitable for usage as part of identifiers and variables. See also
// GetDistroDetails.
func GetDistroID() (id, versionid string) {
var distroName, distroVersion string
osReleaseInfo, err := whichdistro.GetOSRelease()
if err != nil {
log.Warn().Err(err).Msg("Could not read /etc/os-release. Contact your distro vendor to implement this file.")
return unknownDistro, unknownDistroVersion
}
if v, ok := osReleaseInfo.GetValue("ID"); !ok {
distroName = unknownDistro
} else {
distroName = v
}
if v, ok := osReleaseInfo.GetValue("VERSION_ID"); !ok {
distroVersion = unknownDistroVersion
} else {
distroVersion = v
}
return distroName, distroVersion
}

// GetDistroDetails will retrieve the distribution name and version. The values
// are pretty-printed and may not be suitable for usage as identifiers and
// variables. See also GetDistroID.
func GetDistroDetails() (name, version string) {
var distroName, distroVersion string
osReleaseInfo, err := whichdistro.GetOSRelease()
if err != nil {
log.Warn().Err(err).Msg("Could not read /etc/os-release. Contact your distro vendor to implement this file.")
return unknownDistro, unknownDistroVersion
}
if v, ok := osReleaseInfo.GetValue("NAME"); !ok {
distroName = unknownDistro
} else {
distroName = v
}
if v, ok := osReleaseInfo.GetValue("VERSION"); !ok {
distroVersion = unknownDistroVersion
} else {
distroVersion = v
}
return distroName, distroVersion
}

// GetKernelVersion will retrieve the kernel version.
func GetKernelVersion() string {
var utsname syscall.Utsname
var versionBytes []byte
err := syscall.Uname(&utsname)
if err != nil {
log.Warn().Err(err).Msg("Could not retrieve kernel version.")
return "Unknown"
}
for _, v := range utsname.Release {
if v == 0 {
continue
}
versionBytes = append(versionBytes, uint8(v))
}
return string(versionBytes)
}
177 changes: 131 additions & 46 deletions internal/linux/device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
package linux

import (
"fmt"
"os"
"path/filepath"
"reflect"
"testing"

Expand All @@ -17,6 +19,54 @@ import (
"github.com/joshuar/go-hass-agent/pkg/linux/whichdistro"
)

func compareDevice(t *testing.T, a, b *Device) bool {
switch {
case !reflect.DeepEqual(a.appName, b.appName):
t.Error("appName does not match")
return false
case !reflect.DeepEqual(a.appVersion, b.appVersion):
t.Error("appVersion does not match")
return false
case !reflect.DeepEqual(a.distro, b.distro):
t.Error("distro does not match")
return false
case !reflect.DeepEqual(a.distroVersion, b.distroVersion):
t.Error("distroVersion does not match")
return false
case !reflect.DeepEqual(a.hostname, b.hostname):
t.Error("hostname does not match")
return false
case !reflect.DeepEqual(a.hwModel, b.hwModel):
t.Error("hwModel does not match")
return false
case !reflect.DeepEqual(a.hwVendor, b.hwVendor):
t.Error("hwVendor does not match")
return false
}
return true
}

func compareMQTTDevice(t *testing.T, a, b *mqtthass.Device) bool {
switch {
case !reflect.DeepEqual(a.Name, b.Name):
t.Error("name does not match")
return false
case !reflect.DeepEqual(a.URL, b.URL):
t.Error("URL does not match")
return false
case !reflect.DeepEqual(a.SWVersion, b.SWVersion):
t.Error("SWVersion does not match")
return false
case !reflect.DeepEqual(a.Manufacturer, b.Manufacturer):
t.Error("Manufacturer does not match")
return false
case !reflect.DeepEqual(a.Model, b.Model):
t.Error("Model does not match")
return false
}
return true
}

func TestNewDevice(t *testing.T) {
origOSRelease := whichdistro.OSReleaseFile
origAltOSRelease := whichdistro.OSReleaseAltFile
Expand All @@ -33,11 +83,8 @@ func TestNewDevice(t *testing.T) {
withoutOSRelease.distro = unknownDistro
withoutOSRelease.distroVersion = unknownDistroVersion

osReleaseInfo, err := whichdistro.GetOSRelease()
assert.Nil(t, err)
withOSRelease := baseDev
withOSRelease.distro = osReleaseInfo["ID"]
withOSRelease.distroVersion = osReleaseInfo["VERSION_ID"]
withOSRelease.distro, withOSRelease.distroVersion = GetDistroID()

type args struct {
name string
Expand Down Expand Up @@ -150,50 +197,88 @@ func TestFindPortal(t *testing.T) {
}
}

func compareDevice(t *testing.T, a, b *Device) bool {
switch {
case !reflect.DeepEqual(a.appName, b.appName):
t.Error("appName does not match")
return false
case !reflect.DeepEqual(a.appVersion, b.appVersion):
t.Error("appVersion does not match")
return false
case !reflect.DeepEqual(a.distro, b.distro):
t.Error("distro does not match")
return false
case !reflect.DeepEqual(a.distroVersion, b.distroVersion):
t.Error("distroVersion does not match")
return false
case !reflect.DeepEqual(a.hostname, b.hostname):
t.Error("hostname does not match")
return false
case !reflect.DeepEqual(a.hwModel, b.hwModel):
t.Error("hwModel does not match")
return false
case !reflect.DeepEqual(a.hwVendor, b.hwVendor):
t.Error("hwVendor does not match")
return false
func TestGetDistroID(t *testing.T) {
versionID := "9.9"
id := "testdistro"
goodFile := filepath.Join(t.TempDir(), "goodfile")
fh, err := os.Create(goodFile)
assert.Nil(t, err)
fmt.Fprintln(fh, `VERSION_ID="`+versionID+`"`)
fmt.Fprintln(fh, `ID="`+id+`"`)
fh.Close()

tests := []struct {
name string
wantId string
wantVersionid string
osReleaseFile string
}{
{
name: "File exists",
wantId: id,
wantVersionid: versionID,
osReleaseFile: goodFile,
},
{
name: "File does not exist.",
wantId: unknownDistro,
wantVersionid: unknownDistroVersion,
osReleaseFile: "/dev/null",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
whichdistro.OSReleaseFile = tt.osReleaseFile
gotId, gotVersionid := GetDistroID()
if gotId != tt.wantId {
t.Errorf("GetDistroID() gotId = %v, want %v", gotId, tt.wantId)
}
if gotVersionid != tt.wantVersionid {
t.Errorf("GetDistroID() gotVersionid = %v, want %v", gotVersionid, tt.wantVersionid)
}
})
}
return true
}

func compareMQTTDevice(t *testing.T, a, b *mqtthass.Device) bool {
switch {
case !reflect.DeepEqual(a.Name, b.Name):
t.Error("name does not match")
return false
case !reflect.DeepEqual(a.URL, b.URL):
t.Error("URL does not match")
return false
case !reflect.DeepEqual(a.SWVersion, b.SWVersion):
t.Error("SWVersion does not match")
return false
case !reflect.DeepEqual(a.Manufacturer, b.Manufacturer):
t.Error("Manufacturer does not match")
return false
case !reflect.DeepEqual(a.Model, b.Model):
t.Error("Model does not match")
return false
func TestGetDistroDetails(t *testing.T) {
version := "9.9 (note)"
name := "Test Distro"
goodFile := filepath.Join(t.TempDir(), "goodfile")
fh, err := os.Create(goodFile)
assert.Nil(t, err)
fmt.Fprintln(fh, `VERSION="`+version+`"`)
fmt.Fprintln(fh, `NAME="`+name+`"`)
fh.Close()

tests := []struct {
name string
wantName string
wantVersion string
osReleaseFile string
}{
{
name: "File exists",
wantName: name,
wantVersion: version,
osReleaseFile: goodFile,
},
{
name: "File does not exist.",
wantName: unknownDistro,
wantVersion: unknownDistroVersion,
osReleaseFile: "/dev/null",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
whichdistro.OSReleaseFile = tt.osReleaseFile
gotName, gotVersion := GetDistroDetails()
if gotName != tt.wantName {
t.Errorf("GetDistroDetails() gotName = %v, want %v", gotName, tt.wantName)
}
if gotVersion != tt.wantVersion {
t.Errorf("GetDistroDetails() gotVersion = %v, want %v", gotVersion, tt.wantVersion)
}
})
}
return true
}
27 changes: 11 additions & 16 deletions internal/linux/system/version.go → internal/linux/system/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,26 @@ import (
"context"
"sync"

"github.com/rs/zerolog/log"
"github.com/shirou/gopsutil/v3/host"
"golang.org/x/text/cases"
"golang.org/x/text/language"

"github.com/joshuar/go-hass-agent/internal/hass/sensor"
"github.com/joshuar/go-hass-agent/internal/linux"
)

func Versions(ctx context.Context) chan sensor.Details {
func InfoUpdater(_ context.Context) chan sensor.Details {
sensorCh := make(chan sensor.Details)
info, err := host.InfoWithContext(ctx)
if err != nil {
log.Debug().Err(err).Caller().
Msg("Failed to retrieve host info.")
close(sensorCh)
return sensorCh
}

// Get distribution name and version.
distro, distroVersion := linux.GetDistroDetails()

// Get kernel version.
kernelVersion := linux.GetKernelVersion()

var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
sensorCh <- &linux.Sensor{
SensorTypeValue: linux.SensorKernel,
Value: info.KernelVersion,
Value: kernelVersion,
IsDiagnostic: true,
IconString: "mdi:chip",
SensorSrc: linux.DataSrcProcfs,
Expand All @@ -44,7 +39,7 @@ func Versions(ctx context.Context) chan sensor.Details {
defer wg.Done()
sensorCh <- &linux.Sensor{
SensorTypeValue: linux.SensorDistribution,
Value: cases.Title(language.English).String(info.Platform),
Value: distro,
IsDiagnostic: true,
IconString: "mdi:linux",
SensorSrc: linux.DataSrcProcfs,
Expand All @@ -55,7 +50,7 @@ func Versions(ctx context.Context) chan sensor.Details {
defer wg.Done()
sensorCh <- &linux.Sensor{
SensorTypeValue: linux.SensorVersion,
Value: info.PlatformVersion,
Value: distroVersion,
IsDiagnostic: true,
IconString: "mdi:numeric",
SensorSrc: linux.DataSrcProcfs,
Expand Down
Loading

0 comments on commit 6db5082

Please sign in to comment.