diff --git a/go.mod b/go.mod index 035fb1e9..a1052ea7 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( github.com/bmc-toolbox/bmclib/v2 v2.0.1-0.20230825151635-6cf01686c513 - github.com/bmc-toolbox/common v0.0.0-20230717121556-5eb9915a8a5a + github.com/bmc-toolbox/common v0.0.0-20231204194243-7bcbccab7116 github.com/bombsimon/logrusr/v2 v2.0.1 github.com/coreos/go-oidc v2.2.1+incompatible github.com/equinix-labs/otel-init-go v0.0.7 diff --git a/go.sum b/go.sum index f84684cd..fc762106 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bmc-toolbox/bmclib/v2 v2.0.1-0.20230825151635-6cf01686c513 h1:FArNt6XfHzD55b67tw7UwMPL9Uz/6/bQ+weI1kPNbbU= github.com/bmc-toolbox/bmclib/v2 v2.0.1-0.20230825151635-6cf01686c513/go.mod h1:a3Ra0ce/LV3wAj7AHuphlHNTx5Sg67iQqtLGr1zoqio= -github.com/bmc-toolbox/common v0.0.0-20230717121556-5eb9915a8a5a h1:SjtoU9dE3bYfYnPXODCunMztjoDgnE3DVJCPLBqwz6Q= -github.com/bmc-toolbox/common v0.0.0-20230717121556-5eb9915a8a5a/go.mod h1:SY//n1PJjZfbFbmAsB6GvEKbc7UXz3d30s3kWxfJQ/c= +github.com/bmc-toolbox/common v0.0.0-20231204194243-7bcbccab7116 h1:gqWn/cMjryKoUfITx2vRHrRHTvd9fQ+zKPwWsmMIrK4= +github.com/bmc-toolbox/common v0.0.0-20231204194243-7bcbccab7116/go.mod h1:SY//n1PJjZfbFbmAsB6GvEKbc7UXz3d30s3kWxfJQ/c= github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM= github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= @@ -517,12 +517,6 @@ github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= -github.com/metal-toolbox/ironlib v0.2.15-0.20231222121932-c3b1c18fa034 h1:zpmLfbLxouW8vDqugG33kJO4CaMaJVVvkj5dSLd5cZA= -github.com/metal-toolbox/ironlib v0.2.15-0.20231222121932-c3b1c18fa034/go.mod h1:QRGRb//lV7LR2yDIe/zmXTFYGKhltQsRol05QlHzfBw= -github.com/metal-toolbox/ironlib v0.2.15-0.20231222142904-33805cc706da h1:f5H0nVwkhvRgBytQb1WTGpmLZ7CRSniekKmRO8sTnmM= -github.com/metal-toolbox/ironlib v0.2.15-0.20231222142904-33805cc706da/go.mod h1:QRGRb//lV7LR2yDIe/zmXTFYGKhltQsRol05QlHzfBw= -github.com/metal-toolbox/ironlib v0.2.15-0.20231222143939-19caf253762a h1:5GuMn4Gj39lJrZ3oDhdWUo2DfsbtTD2v/pm7uMVVwtw= -github.com/metal-toolbox/ironlib v0.2.15-0.20231222143939-19caf253762a/go.mod h1:QRGRb//lV7LR2yDIe/zmXTFYGKhltQsRol05QlHzfBw= github.com/metal-toolbox/ironlib v0.2.15 h1:0uLmUnS7GgRZVwTOGJ+uyUlDEkSzoD+BYvZme9/C5tk= github.com/metal-toolbox/ironlib v0.2.15/go.mod h1:QRGRb//lV7LR2yDIe/zmXTFYGKhltQsRol05QlHzfBw= github.com/metal-toolbox/rivets v0.2.3-0.20231222134907-c139dbc19928 h1:j9suDVBwndsBQreWaqkS/3NDgPlRhHY3fk5uMTehSgg= diff --git a/internal/store/serverservice/attributes.go b/internal/store/serverservice/attributes.go index 497b5bf6..bb2eb714 100644 --- a/internal/store/serverservice/attributes.go +++ b/internal/store/serverservice/attributes.go @@ -6,6 +6,7 @@ import ( "os" "sort" + "github.com/bmc-toolbox/common" "github.com/google/go-cmp/cmp" "github.com/google/uuid" "github.com/metal-toolbox/alloy/internal/helpers" @@ -106,8 +107,9 @@ func (r *Store) deviceVendorData(asset *model.Asset) map[string]string { m[serverSerialAttributeKey] = asset.Inventory.Serial } - if asset.Inventory.Model != "" { - m[serverModelAttributeKey] = asset.Inventory.Model + invModelFormatted := common.FormatProductName(asset.Inventory.Model) + if invModelFormatted != "" { + m[serverModelAttributeKey] = invModelFormatted } if asset.Inventory.Vendor != "" { @@ -119,21 +121,48 @@ func (r *Store) deviceVendorData(asset *model.Asset) map[string]string { } // returns a map with device vendor attributes when an update is required +// +// nolint:gocyclo // data verify is cyclomatic func vendorDataUpdate(newData, currentData map[string]string) map[string]string { + const unknown = "unknown" + var changes bool + if currentData == nil { return newData } - var changes bool + newModelNumber := common.FormatProductName(newData[serverModelAttributeKey]) + currentModelNumber := currentData[serverModelAttributeKey] - setValue := func(key string, newData, currentData map[string]string) { - const unknown = "unknown" + changeRequired := func(key string) bool { + newModelDefined := newModelNumber != unknown && newModelNumber != "" + currentModelUndefined := currentModelNumber == unknown || currentModelNumber == "" + + // model number to be updated + if key == serverModelAttributeKey { + if currentModelUndefined && newModelDefined { + return true + } - if currentData[key] == "" || currentData[key] == unknown { - if newData[key] != unknown { - changes = true - currentData[key] = newData[key] + if newModelDefined && currentModelNumber != newModelNumber { + newData[serverModelAttributeKey] = newModelNumber + return true } + + return false + } + + // rest of the keys are updated when the new data is defined and current is not. + newKeyDefined := newData[key] != unknown && newData[key] != "" + currentKeyUndefined := currentData[key] == "" || currentData[key] == unknown + return newKeyDefined && currentKeyUndefined + } + + setValue := func(key string, newData, currentData map[string]string) { + // value is empty or explicit check for model number + if changeRequired(key) { + changes = true + currentData[key] = newData[key] } } diff --git a/internal/store/serverservice/attributes_test.go b/internal/store/serverservice/attributes_test.go index e989f722..6467afed 100644 --- a/internal/store/serverservice/attributes_test.go +++ b/internal/store/serverservice/attributes_test.go @@ -966,3 +966,157 @@ func TestMetadataFilter(t *testing.T) { got = mustFilterAssetMetadata(dirty) require.Equal(t, json.RawMessage(exp), got, "dirty doesn't serialize properly") } + +func TestDeviceVendorData(t *testing.T) { + testcases := []struct { + name string + asset *model.Asset + expected map[string]string + }{ + { + "default values", + &model.Asset{ + ID: uuid.NewString(), + Inventory: &common.Device{ + Common: common.Common{}, + }, + }, + map[string]string{ + serverSerialAttributeKey: "unknown", + serverVendorAttributeKey: "unknown", + serverModelAttributeKey: "unknown", + }, + }, + { + "expected values set", + &model.Asset{ + ID: uuid.NewString(), + Inventory: &common.Device{ + Common: common.Common{ + Vendor: "foobar", + Model: "baz", + Serial: "00123", + }, + }, + }, + map[string]string{ + serverVendorAttributeKey: "foobar", + serverModelAttributeKey: "baz", + serverSerialAttributeKey: "00123", + }, + }, + { + "model attribute formatted", + &model.Asset{ + ID: uuid.NewString(), + Inventory: &common.Device{ + Common: common.Common{ + Vendor: "foobar", + Model: "PIO-519C-MR-PH004", + Serial: "00123", + }, + }, + }, + map[string]string{ + serverVendorAttributeKey: "foobar", + serverModelAttributeKey: "x11sch-f", + serverSerialAttributeKey: "00123", + }, + }, + } + + s := &Store{} + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + got := s.deviceVendorData(tc.asset) + assert.Equal(t, tc.expected, got, tc.name) + }) + } +} + +func TestVendorDataUpdate(t *testing.T) { + tests := []struct { + name string + newData map[string]string + currentData map[string]string + expected map[string]string + }{ + { + name: "updates when currentData is nil", + newData: map[string]string{"vendor": "foo", "model": "bar"}, + currentData: nil, + expected: map[string]string{"vendor": "foo", "model": "bar"}, + }, + { + name: "updates model when current model is unknown", + newData: map[string]string{"model": "foo"}, + currentData: map[string]string{"model": "unknown"}, + expected: map[string]string{"model": "foo"}, + }, + { + name: "updates serial when current serial is unknown", + newData: map[string]string{"model": "foo", "serial": "123"}, + currentData: map[string]string{"model": "foo", "serial": "unknown"}, + expected: map[string]string{"model": "foo", "serial": "123"}, + }, + { + name: "updates serial when current serial is empty", + newData: map[string]string{"model": "foo", "serial": "123"}, + currentData: map[string]string{"model": "foo", "serial": ""}, + expected: map[string]string{"model": "foo", "serial": "123"}, + }, + { + name: "no update when new model matches current", + newData: map[string]string{"model": "r6515"}, + currentData: map[string]string{"model": "r6515"}, + expected: nil, + }, + { + name: "no update when new vendor matches current", + newData: map[string]string{"vendor": "r6515"}, + currentData: map[string]string{"vendor": "baz"}, + expected: nil, + }, + { + name: "no update when serial matches current", + newData: map[string]string{"model": "foo", "serial": "123"}, + currentData: map[string]string{"model": "foo", "serial": "123"}, + expected: nil, + }, + { + name: "update model when current does not match new", + newData: map[string]string{"vendor": "bar", "model": "newfoo"}, + currentData: map[string]string{"vendor": "baz", "model": "oldfoo"}, + expected: map[string]string{"vendor": "baz", "model": "newfoo"}, + }, + { + name: "no update when new model is formatted and matches current", + newData: map[string]string{"vendor": "bar", "model": "PIO-519C-MR-PH004"}, + currentData: map[string]string{"vendor": "baz", "model": "x11sch-f"}, + expected: nil, + }, + { + name: "update when new model is formatted and does not match current", + newData: map[string]string{"vendor": "bar", "model": "PIO-519C-MR-PH004"}, + currentData: map[string]string{"vendor": "baz", "model": "PIO-519C-MR-PH004"}, + expected: map[string]string{"vendor": "baz", "model": "x11sch-f"}, + }, + { + name: "ignore 'unknown' in new values", + newData: map[string]string{"model": "unknown", "vendor": "unknown"}, + currentData: map[string]string{"model": "foo", "vendor": "bar"}, + expected: nil, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + got := vendorDataUpdate(tc.newData, tc.currentData) + if tc.expected != nil { + assert.Equal(t, tc.expected, got, tc.name) + } else { + assert.Nil(t, got, tc.name) + } + }) + } +}