Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(internal/collector): Collector skeleton #7

Merged
merged 10 commits into from
Jan 16, 2025
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ require (
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.10.0
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -14,5 +15,4 @@ require (
github.com/kr/pretty v0.3.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
8 changes: 8 additions & 0 deletions internal/collector/sysinfo/export_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package sysinfo

// WithRoot overrides default root directory of the system.
func WithRoot(root string) Options {
return func(o *options) {
o.root = root
}
}
62 changes: 62 additions & 0 deletions internal/collector/sysinfo/sysinfo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Package sysinfo allows collecting "common" system information for all insight reports.
package sysinfo

type options struct {
root string
}

// Options is the variadic options available to the manager.
type Options func(*options)

// Manager allows collecting Software and Hardware information of the system.
type Manager struct {
root string
}

// SysInfo contains Software and Hardware information of the system.
type SysInfo struct {
Hardware HwInfo
Software SwInfo
}

// HwInfo is the hardware specific part.
type HwInfo struct {
Product map[string]string
}

// SwInfo is the software specific part.
type SwInfo struct {
}

// New returns a new SysInfo.
func New(args ...Options) Manager {
// options defaults
opts := &options{
root: "/",
}

for _, opt := range args {
opt(opts)
}

return Manager{
root: opts.root,
}
}

// Collect gather system information and return it.
func (s Manager) Collect() (SysInfo, error) {
hwInfo, err := s.collectHardware()
if err != nil {
return SysInfo{}, err
}
swInfo, err := s.collectSoftware()
if err != nil {
return SysInfo{}, err
}

return SysInfo{
Hardware: hwInfo,
Software: swInfo,
}, nil
}
9 changes: 9 additions & 0 deletions internal/collector/sysinfo/sysinfo_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package sysinfo

func (s Manager) collectHardware() (HwInfo, error) {
return HwInfo{}, nil
}

func (s Manager) collectSoftware() (SwInfo, error) {
return SwInfo{}, nil
}
23 changes: 23 additions & 0 deletions internal/collector/sysinfo/sysinfo_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package sysinfo

import (
"os"
"path/filepath"
)

func (s Manager) collectHardware() (hwInfo HwInfo, err error) {
// System vendor
d, err := os.ReadFile(filepath.Join(s.root, "sys/class/dmi/id/sys_vendor"))
if err != nil {
return HwInfo{}, err
}
hwInfo.Product = map[string]string{
"Vendor": string(d),
}

return hwInfo, nil
}

func (s Manager) collectSoftware() (swInfo SwInfo, err error) {
return swInfo, nil
}
59 changes: 59 additions & 0 deletions internal/collector/sysinfo/sysinfo_linux_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package sysinfo_test

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/require"
"github.com/ubuntu/ubuntu-insights/internal/collector/sysinfo"
"github.com/ubuntu/ubuntu-insights/internal/testutils"
)

func TestNew(t *testing.T) {
t.Parallel()

tests := map[string]struct {
}{
"Instantiate a sys info manager": {},
}
for name := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

s := sysinfo.New(sysinfo.WithRoot("/myspecialroot"))

require.NotEmpty(t, s, "sysinfo manager has custom fields")
})
}
}

func TestCollect(t *testing.T) {
t.Parallel()

tests := map[string]struct {
root string

wantErr bool
}{
"Regular hardware information": {root: "regular"},

"Error on missing vendor information": {root: "withoutvendor", wantErr: true},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
t.Parallel()

s := sysinfo.New(sysinfo.WithRoot(filepath.Join("testdata", "linuxfs", tc.root)))

got, err := s.Collect()
if tc.wantErr {
require.Error(t, err, "Collect should return an error and didn’t")
return
}
require.NoError(t, err, "Collect should not return an error")

want := testutils.LoadWithUpdateFromGoldenYAML(t, got)
require.Equal(t, want, got, "Collect should return expected sys information")
})
}
}
9 changes: 9 additions & 0 deletions internal/collector/sysinfo/sysinfo_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package sysinfo

func (s Manager) collectHardware() (HwInfo, error) {
return HwInfo{}, nil
}

func (s Manager) collectSoftware() (SwInfo, error) {
return SwInfo{}, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
hardware:
product:
Vendor: |
Framework
software: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Framework
126 changes: 126 additions & 0 deletions internal/testutils/golden.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// TiCS: disabled // Test helpers.

package testutils

import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/stretchr/testify/require"
"gopkg.in/yaml.v3"
)

var update bool

const (
// UpdateGoldenFilesEnv is the environment variable used to indicate go test that
// the golden files should be overwritten with the current test results.
UpdateGoldenFilesEnv = `TESTS_UPDATE_GOLDEN`
)

func init() {
if os.Getenv(UpdateGoldenFilesEnv) != "" {
update = true
}
}

type goldenOptions struct {
goldenPath string
}

// GoldenOption is a supported option reference to change the golden files comparison.
type GoldenOption func(*goldenOptions)

// WithGoldenPath overrides the default path for golden files used.
func WithGoldenPath(path string) GoldenOption {
return func(o *goldenOptions) {
if path != "" {
o.goldenPath = path
}
}
}

// LoadWithUpdateFromGolden loads the element from a plaintext golden file.
// It will update the file if the update flag is used prior to loading it.
func LoadWithUpdateFromGolden(t *testing.T, data string, opts ...GoldenOption) string {
t.Helper()

o := goldenOptions{
goldenPath: GoldenPath(t),
}

for _, opt := range opts {
opt(&o)
}

if update {
t.Logf("updating golden file %s", o.goldenPath)
err := os.MkdirAll(filepath.Dir(o.goldenPath), 0750)
require.NoError(t, err, "Cannot create directory for updating golden files")
err = os.WriteFile(o.goldenPath, []byte(data), 0600)
require.NoError(t, err, "Cannot write golden file")
}

want, err := os.ReadFile(o.goldenPath)
require.NoError(t, err, "Cannot load golden file")

return string(want)
}

// LoadWithUpdateFromGoldenYAML load the generic element from a YAML serialized golden file.
// It will update the file if the update flag is used prior to deserializing it.
func LoadWithUpdateFromGoldenYAML[E any](t *testing.T, got E, opts ...GoldenOption) E {
t.Helper()

t.Logf("Serializing object for golden file")
data, err := yaml.Marshal(got)
require.NoError(t, err, "Cannot serialize provided object")
want := LoadWithUpdateFromGolden(t, string(data), opts...)

var wantDeserialized E
err = yaml.Unmarshal([]byte(want), &wantDeserialized)
require.NoError(t, err, "Cannot create expanded policy objects from golden file")

return wantDeserialized
}

// NormalizeGoldenName returns the name of the golden file with illegal Windows
// characters replaced or removed.
func NormalizeGoldenName(t *testing.T, name string) string {
t.Helper()

name = strings.ReplaceAll(name, `\`, "_")
name = strings.ReplaceAll(name, ":", "")
name = strings.ToLower(name)
return name
}

// TestFamilyPath returns the path of the dir for storing fixtures and other files related to the test.
func TestFamilyPath(t *testing.T) string {
t.Helper()

// Ensures that only the name of the parent test is used.
super, _, _ := strings.Cut(t.Name(), "/")

return filepath.Join("testdata", super)
}

// GoldenPath returns the golden path for the provided test.
func GoldenPath(t *testing.T) string {
t.Helper()

path := filepath.Join(TestFamilyPath(t), "golden")
_, sub, found := strings.Cut(t.Name(), "/")
if found {
path = filepath.Join(path, NormalizeGoldenName(t, sub))
}

return path
}

// UpdateEnabled returns true if updating the golden files is requested.
func UpdateEnabled() bool {
return update
}
Loading