diff --git a/Dockerfile b/Dockerfile index db5c779..ab21ead 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.21.3-alpine3.18 AS build +FROM golang:1.22.2-alpine3.18 AS build ARG APP_NAME WORKDIR "/go/src/github.com/metal-toolbox/${APP_NAME}" diff --git a/go.mod b/go.mod index ccd2d21..65fd102 100644 --- a/go.mod +++ b/go.mod @@ -1,14 +1,16 @@ module github.com/metal-toolbox/component-inventory -go 1.21 +go 1.22 + +toolchain go1.22.2 require ( github.com/bmc-toolbox/common v0.0.0-20240416132216-a56a09c16f4e github.com/equinix-labs/otel-init-go v0.0.9 github.com/gin-gonic/gin v1.9.1 github.com/google/uuid v1.6.0 - github.com/metal-toolbox/alloy v0.3.3-0.20240320183632-05dfbd5e9110 - github.com/metal-toolbox/fleetdb v0.17.1 + github.com/metal-toolbox/alloy v0.3.3-0.20240415055734-d09250fed38a + github.com/metal-toolbox/fleetdb v0.17.2-0.20240419204835-60c421433f0a github.com/metal-toolbox/rivets v1.0.3 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.0 diff --git a/go.sum b/go.sum index 1ec88f9..916d070 100644 --- a/go.sum +++ b/go.sum @@ -500,14 +500,13 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= -github.com/mattn/go-sqlite3 v2.0.1+incompatible h1:xQ15muvnzGBHpIpdrNi1DA5x0+TcBZzsIDwmw9uTHzw= -github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/metal-toolbox/alloy v0.3.3-0.20240320183632-05dfbd5e9110 h1:v3PTHUK9KIF/mEmctQ5mgsBtG8dtdfboHI/7QQg/Ft0= -github.com/metal-toolbox/alloy v0.3.3-0.20240320183632-05dfbd5e9110/go.mod h1:NcVZtbVhBCcpyTHew0TJ8yzon69vMf+rGMLoGb9jTa0= -github.com/metal-toolbox/fleetdb v0.17.1 h1:eyaCg4yGQnbXIjiBMZPsLqmEpxbltlEUNDG+Mn27Fsc= -github.com/metal-toolbox/fleetdb v0.17.1/go.mod h1:TbRbU+ppHIknqiAONR7JDQfzKij30uiPkehgxlA1Hv0= +github.com/metal-toolbox/alloy v0.3.3-0.20240415055734-d09250fed38a h1:w6uita6zAXZoGn75vcnEDW2Y6K1rYeLWnv7EXAemByE= +github.com/metal-toolbox/alloy v0.3.3-0.20240415055734-d09250fed38a/go.mod h1:Sv+oD+xnACNV3E7r9kmtgUi1LGKQumTv7mpWOe8a2VU= +github.com/metal-toolbox/fleetdb v0.17.2-0.20240419204835-60c421433f0a h1:Jrjbk0M8EiljlgxeLso232AizQzuaQ4Bnqf8L9ITkug= +github.com/metal-toolbox/fleetdb v0.17.2-0.20240419204835-60c421433f0a/go.mod h1:U9YArAdibILSBJzNbmtJAz6m+Cljce6a7pL0JkOckqQ= github.com/metal-toolbox/rivets v1.0.3 h1:ZW9q8V3vz6VxAczC4eR1YJdl+kapHF3ebVc+4r3NmR8= github.com/metal-toolbox/rivets v1.0.3/go.mod h1:EMQJRT1mjIyFRXxvKNaBlz7Z4Sp88rTaGO8W18olN2I= github.com/microsoft/go-mssqldb v0.17.0/go.mod h1:OkoNGhGEs8EZqchVTtochlXruEhEOaO4S0d2sB5aeGQ= diff --git a/internal/fleetdb/attributes.go b/internal/fleetdb/attributes.go deleted file mode 100644 index f3d1a38..0000000 --- a/internal/fleetdb/attributes.go +++ /dev/null @@ -1,52 +0,0 @@ -package internalfleetdb - -import ( - "encoding/json" - - "github.com/metal-toolbox/alloy/types" - "github.com/metal-toolbox/component-inventory/pkg/api/constants" - fleetdb "github.com/metal-toolbox/fleetdb/pkg/api/v1" -) - -func deviceVendorAttributes(cid *types.InventoryDevice) (map[string]string, *fleetdb.Attributes, error) { - deviceVendorData := map[string]string{ - constants.ServerSerialAttributeKey: "unknown", - constants.ServerVendorAttributeKey: "unknown", - constants.ServerModelAttributeKey: "unknown", - } - - if cid.Inv != nil { - if cid.Inv.Serial != "" { - deviceVendorData[constants.ServerSerialAttributeKey] = cid.Inv.Serial - } - - if cid.Inv.Model != "" { - deviceVendorData[constants.ServerModelAttributeKey] = cid.Inv.Model - } - - if cid.Inv.Vendor != "" { - deviceVendorData[constants.ServerVendorAttributeKey] = cid.Inv.Vendor - } - } - - deviceVendorDataBytes, err := json.Marshal(deviceVendorData) - if err != nil { - return nil, nil, err - } - - return deviceVendorData, &fleetdb.Attributes{ - Namespace: constants.ServerVendorAttributeNS, - Data: deviceVendorDataBytes, - }, nil -} - -// attributeByNamespace returns the attribute in the slice that matches the namespace -func attributeByNamespace(ns string, attributes []fleetdb.Attributes) *fleetdb.Attributes { - for _, attribute := range attributes { - if attribute.Namespace == ns { - return &attribute - } - } - - return nil -} diff --git a/internal/fleetdb/fleetdb.go b/internal/fleetdb/fleetdb.go index 327eab2..b266a8c 100644 --- a/internal/fleetdb/fleetdb.go +++ b/internal/fleetdb/fleetdb.go @@ -2,13 +2,12 @@ package internalfleetdb import ( "context" - "encoding/json" "fmt" "github.com/google/uuid" "github.com/metal-toolbox/alloy/types" "github.com/metal-toolbox/component-inventory/internal/app" - "github.com/metal-toolbox/component-inventory/pkg/api/constants" + "github.com/metal-toolbox/component-inventory/internal/inventoryconverter" fleetdb "github.com/metal-toolbox/fleetdb/pkg/api/v1" "go.uber.org/zap" ) @@ -16,12 +15,11 @@ import ( type Client interface { GetServer(context.Context, uuid.UUID) (*fleetdb.Server, *fleetdb.ServerResponse, error) GetComponents(context.Context, uuid.UUID, *fleetdb.PaginationParams) (fleetdb.ServerComponentSlice, *fleetdb.ServerResponse, error) - UpdateAttributes(context.Context, *fleetdb.Server, *types.InventoryDevice, *zap.Logger) error - UpdateServerBIOSConfig() error + UpdateServerInventory(context.Context, *fleetdb.Server, *types.InventoryDevice, *zap.Logger, bool) error } // Creates a new Client, with reasonable defaults -func NewFleetDBClient(cfg *app.Configuration) (Client, error) { +func NewFleetDBClient(ctx context.Context, cfg *app.Configuration) (Client, error) { client, err := fleetdb.NewClient(cfg.FleetDBAddress, nil) if err != nil { return nil, err @@ -31,13 +29,26 @@ func NewFleetDBClient(cfg *app.Configuration) (Client, error) { client.SetToken(cfg.FleetDBToken) } + slugs := make(map[string]*fleetdb.ServerComponentType) + serverComponentTypes, _, err := client.ListServerComponentTypes(ctx, nil) + if err != nil { + return nil, fmt.Errorf("failed to get server component types: %w", err) + } + for _, ct := range serverComponentTypes { + slugs[ct.Slug] = ct + } + return &fleetDBClient{ - client: client, + client: client, + slugs: slugs, + inventoryConverterInstance: inventoryconverter.NewInventoryConverter(slugs), }, nil } type fleetDBClient struct { - client *fleetdb.Client + client *fleetdb.Client + slugs map[string]*fleetdb.ServerComponentType + inventoryConverterInstance *inventoryconverter.InventoryConverter } func (fc fleetDBClient) GetServer(ctx context.Context, id uuid.UUID) (*fleetdb.Server, *fleetdb.ServerResponse, error) { @@ -48,69 +59,17 @@ func (fc fleetDBClient) GetComponents(ctx context.Context, id uuid.UUID, params return fc.client.GetComponents(ctx, id, params) } -func (fc fleetDBClient) UpdateAttributes(ctx context.Context, server *fleetdb.Server, dev *types.InventoryDevice, log *zap.Logger) error { - return createUpdateServerAttributes(ctx, fc.client, server, dev, log) -} - -// Functions below may be refactored in the near future. -func createUpdateServerAttributes(ctx context.Context, c *fleetdb.Client, server *fleetdb.Server, dev *types.InventoryDevice, log *zap.Logger) error { - newVendorData, newVendorAttrs, err := deviceVendorAttributes(dev) +func (fc fleetDBClient) UpdateServerInventory(ctx context.Context, server *fleetdb.Server, dev *types.InventoryDevice, log *zap.Logger, inband bool) error { + log.Info("update server inventory", zap.String("server", server.Name)) + rivetsServer, err := fc.inventoryConverterInstance.ToRivetsServer(server.UUID.String(), server.FacilityCode, dev.Inv, dev.BiosCfg) if err != nil { + log.Error("convert inventory fail", zap.String("server", server.Name), zap.String("err", err.Error())) return err } - - // identify current vendor data in the inventory - existingVendorAttrs := attributeByNamespace(constants.ServerVendorAttributeNS, server.Attributes) - if existingVendorAttrs == nil { - // create if none exists - _, err = c.CreateAttributes(ctx, server.UUID, *newVendorAttrs) - return err - } - - // unpack vendor data from inventory - existingVendorData := map[string]string{} - if err := json.Unmarshal(existingVendorAttrs.Data, &existingVendorData); err != nil { - // update vendor data since it seems to be invalid - log.Warn("server vendor attributes data invalid, updating..") - - _, err = c.UpdateAttributes(ctx, server.UUID, constants.ServerVendorAttributeNS, newVendorAttrs.Data) - - return err - } - - updatedVendorData := existingVendorData - var changes bool - for key := range newVendorData { - if updatedVendorData[key] == "" || updatedVendorData[key] == "unknown" { - if newVendorData[key] != "unknown" { - changes = true - updatedVendorData[key] = newVendorData[key] - } - } - } - - if !changes { - return nil - } - - if len(updatedVendorData) > 0 { - updateBytes, err := json.Marshal(updatedVendorData) - if err != nil { - return err - } - - _, err = c.UpdateAttributes(ctx, server.UUID, constants.ServerVendorAttributeNS, updateBytes) - + _, err = fc.client.SetServerInventory(ctx, server.UUID, rivetsServer, inband) + if err != nil { + log.Error("set inventory fail", zap.String("server", server.Name), zap.String("err", err.Error())) return err } - return nil } - -func (fc fleetDBClient) UpdateServerBIOSConfig() error { - return createUpdateServerBIOSConfig() -} - -func createUpdateServerBIOSConfig() error { - return fmt.Errorf("unimplemented") -} diff --git a/pkg/api/routes/components_test.go b/pkg/api/routes/components_test.go index c250943..07f0a26 100644 --- a/pkg/api/routes/components_test.go +++ b/pkg/api/routes/components_test.go @@ -1,20 +1,19 @@ package routes import ( - "github.com/metal-toolbox/component-inventory/internal/app" - internalfleetdb "github.com/metal-toolbox/component-inventory/internal/fleetdb" - + "context" "encoding/json" "fmt" - - "github.com/google/uuid" - fleetdb "github.com/metal-toolbox/fleetdb/pkg/api/v1" - "net/http" "net/http/httptest" "testing" + "github.com/google/uuid" + "github.com/metal-toolbox/component-inventory/internal/app" "github.com/stretchr/testify/require" + + internalfleetdb "github.com/metal-toolbox/component-inventory/internal/fleetdb" + fleetdb "github.com/metal-toolbox/fleetdb/pkg/api/v1" ) var serverUUID = uuid.New() @@ -43,6 +42,19 @@ var validComponents = fleetdb.ServerComponentSlice{ }, } +var validComponentTypes = fleetdb.ServerComponentTypeSlice{ + &fleetdb.ServerComponentType{ + ID: "02dc2503-b64c-439b-9f25-8e130705f14a", + Name: "Backplane-Expander", + Slug: "backplane-expander", + }, + &fleetdb.ServerComponentType{ + ID: "1e0c3417-d63c-4fd5-88f7-4c525c70da12", + Name: "Mainboard", + Slug: "mainboard", + }, +} + func getComponentsHandler(t *testing.T, comps *fleetdb.ServerComponentSlice, code int) http.HandlerFunc { return func(w http.ResponseWriter, _ *http.Request) { var byt []byte @@ -67,6 +79,30 @@ func getComponentsHandler(t *testing.T, comps *fleetdb.ServerComponentSlice, cod } } +func getComponentTypesHandler(t *testing.T, comps *fleetdb.ServerComponentSlice, code int) http.HandlerFunc { + return func(w http.ResponseWriter, _ *http.Request) { + var byt []byte + if comps != nil { + var err error + srvResponse := fleetdb.ServerResponse{ + Records: validComponentTypes, + } + byt, err = json.Marshal(srvResponse) + if err != nil { + t.Fatalf("serializing server response: %s", err.Error()) + } + } + + w.WriteHeader(code) + if byt != nil { + _, err := w.Write(byt) + if err != nil { + t.Fatalf("writing http response: %s", err.Error()) + } + } + } +} + func TestFetchServerComponents(t *testing.T) { t.Parallel() t.Run("valid component return", func(t *testing.T) { @@ -82,6 +118,9 @@ func TestFetchServerComponents(t *testing.T) { w.WriteHeader(http.StatusBadRequest) }, ) + mux.HandleFunc("/api/v1/server-component-types", + getComponentTypesHandler(t, &validComponents, 200), + ) mux.HandleFunc( fmt.Sprintf("/api/v1/servers/%s/components", serverUUID), getComponentsHandler(t, &validComponents, 200), @@ -91,7 +130,7 @@ func TestFetchServerComponents(t *testing.T) { logger := app.GetLogger(true) - client, err := internalfleetdb.NewFleetDBClient(&app.Configuration{ + client, err := internalfleetdb.NewFleetDBClient(context.Background(), &app.Configuration{ FleetDBAddress: ts.URL, }) require.NoError(t, err) @@ -111,6 +150,9 @@ func TestFetchServerComponents(t *testing.T) { w.WriteHeader(http.StatusBadRequest) }, ) + mux.HandleFunc("/api/v1/server-component-types", + getComponentTypesHandler(t, &validComponents, 200), + ) mux.HandleFunc( fmt.Sprintf("/api/v1/servers/%s/components", serverUUID), getComponentsHandler(t, nil, 500), @@ -120,7 +162,7 @@ func TestFetchServerComponents(t *testing.T) { logger := app.GetLogger(true) - client, err := internalfleetdb.NewFleetDBClient(&app.Configuration{ + client, err := internalfleetdb.NewFleetDBClient(context.Background(), &app.Configuration{ FleetDBAddress: ts.URL, }) require.NoError(t, err) diff --git a/pkg/api/routes/inventory.go b/pkg/api/routes/inventory.go index 1390db0..9fd4596 100644 --- a/pkg/api/routes/inventory.go +++ b/pkg/api/routes/inventory.go @@ -2,7 +2,8 @@ package routes import ( "context" - "errors" + "fmt" + "strings" "github.com/metal-toolbox/alloy/types" internalfleetdb "github.com/metal-toolbox/component-inventory/internal/fleetdb" @@ -12,13 +13,33 @@ import ( func processInband(ctx context.Context, c internalfleetdb.Client, server *fleetdb.Server, dev *types.InventoryDevice, log *zap.Logger) error { //nolint log.Info("processing", zap.String("server", server.Name), zap.String("device", dev.Inv.Serial)) - if err := c.UpdateAttributes(ctx, server, dev, log); err != nil { + if err := verifyComponent(c, server, dev, log); err != nil { return err } - return errors.New("not implemented") + return c.UpdateServerInventory(ctx, server, dev, log, true) } func processOutofband(ctx context.Context, c internalfleetdb.Client, server *fleetdb.Server, dev *types.InventoryDevice, log *zap.Logger) error { //nolint log.Info("processing", zap.String("server", server.Name), zap.String("device", dev.Inv.Serial)) - return errors.New("not implemented") + if err := verifyComponent(c, server, dev, log); err != nil { + log.Error("verify component", zap.String("server", server.Name), zap.String("err", err.Error())) + return err + } + return c.UpdateServerInventory(ctx, server, dev, log, false) +} + +func verifyComponent(c internalfleetdb.Client, server *fleetdb.Server, dev *types.InventoryDevice, log *zap.Logger) error { + components, err := fetchServerComponents(c, server.UUID, log) + if err != nil { + if strings.Contains(err.Error(), "404") { + // The server doesn't have components, we can create it. + return nil + } + return err + } + return isBadComponents(components, dev) +} + +func isBadComponents(_ serverComponents, _ *types.InventoryDevice) error { + return fmt.Errorf("unimplement") } diff --git a/pkg/api/routes/routes.go b/pkg/api/routes/routes.go index ac08f4a..5f1e615 100644 --- a/pkg/api/routes/routes.go +++ b/pkg/api/routes/routes.go @@ -132,7 +132,7 @@ func ComposeHTTPServer(theApp *app.App) *http.Server { return } - client, err := internalfleetdb.NewFleetDBClient(theApp.Cfg) + client, err := internalfleetdb.NewFleetDBClient(ctx, theApp.Cfg) if err != nil { ctx.JSON(http.StatusInternalServerError, map[string]any{ "message": "failed to connect to fleetdb", @@ -224,7 +224,12 @@ func composeInventoryHandler(theApp *app.App, fn inventoryHandler) gin.HandlerFu return } - fleetDBClient, err := internalfleetdb.NewFleetDBClient(theApp.Cfg) + if dev.Inv == nil { + reject(ctx, http.StatusBadRequest, "empty inventory", "") + return + } + + fleetDBClient, err := internalfleetdb.NewFleetDBClient(ctx, theApp.Cfg) if err != nil { ctx.JSON(http.StatusInternalServerError, map[string]any{ "message": "failed to connect to fleetdb", @@ -236,6 +241,7 @@ func composeInventoryHandler(theApp *app.App, fn inventoryHandler) gin.HandlerFu server, _, err := fleetDBClient.GetServer(ctx, serverID) if err != nil { reject(ctx, http.StatusBadRequest, "server not exisit", err.Error()) + return } if err := fn(