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

Support comparing components between FleetDB and EMAPI #168

Merged
merged 1 commit into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ For the updated list of all commands available, check out the [CLI docs](https:/
- Retrieve information about a firmware - `mctl get firmware --id <>`
- Install a firmware set on a server - `mctl install firmware-set --server <>`
- Import firmware, firmware-set from file - `mctl create firmware-set --from-file samples/fw-set.json`, where the JSON file contents is the output of `mctl list firmware-set`
- Get component gaps between EMAPI and FleetDB for a server - `./mctl get component_gaps -s <>`. You will need to build the `mctl` with a build tag `-tags staff`.
180 changes: 180 additions & 0 deletions cmd/get/component_gaps.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//go:build staff
// +build staff

// We use build tag to avoid mctl infrastructure changes as staff is a private repo
// which lint and gendoc cannot access.
// To download the staff module, please run 2 commands below:
// export GOPRIVATE=go.equinixmetal.net/*
// git config --global url.ssh://[email protected]/equinixmetal.insteadOf https://github.com/equinixmetal
// See https://github.com/equinixmetal/go-staff
package get

import (
"context"
"fmt"
"log"
"os"
"reflect"
"sort"

"github.com/google/uuid"
"github.com/spf13/cobra"
"go.equinixmetal.net/staff"

mctl "github.com/metal-toolbox/mctl/cmd"
"github.com/metal-toolbox/mctl/internal/app"
)

type getComponentGapsFlags struct {
id string
}

var (
flagsDefinedGetComponentGaps *getComponentGapsFlags
)

// CompareComponents compares two objects and prints differences, even if they're not directly structs
func CompareComponents(obj1, obj2 interface{}, path string) {
if reflect.DeepEqual(obj1, obj2) {
return
}

v1 := reflect.ValueOf(obj1)
v2 := reflect.ValueOf(obj2)

if v1.Kind() == reflect.Ptr {
v1 = v1.Elem()
}
if v2.Kind() == reflect.Ptr {
v2 = v2.Elem()
}

if v1.Kind() != reflect.Struct && v1.Kind() != reflect.Slice {
if v1.Interface() != v2.Interface() {
fmt.Printf("Component %s: %v != %v\n", path, v1.Interface(), v2.Interface())
}
return
}

typeOfObj := v1.Type()
for i := 0; i < v1.NumField(); i++ {
fieldName := typeOfObj.Field(i).Name
value1 := v1.Field(i)
value2 := v2.Field(i)

// Build the current path for output (Component + Field)
currentPath := path + "." + fieldName
if value1.Kind() == reflect.Slice {
compareSlices(currentPath, value1, value2, typeOfObj)
} else if value1.Kind() == reflect.Struct {
CompareComponents(value1.Interface(), value2.Interface(), currentPath)
} else {
if value1.Interface() != value2.Interface() {
fmt.Printf("Component %s Field %s: %v != %v\n", path, fieldName, value1.Interface(), value2.Interface())
}
}
}
}

func compareSlices(path string, slice1, slice2 reflect.Value, typeOfObj reflect.Type) {
if slice1.Len() != slice2.Len() {
fmt.Printf("Component %s Field %s: lengths differ (%d != %d)\n", path, typeOfObj, slice1.Len(), slice2.Len())
}

// Slice item order may be different in fleetDB and EMAPI, eg {CPU1, CPU2} and {CPU2, CPU1}.
// Sort the slices by "Serial" field
sort.SliceStable(slice1.Interface(), func(i, j int) bool {
return slice1.Index(i).FieldByName("Serial").String() < slice1.Index(j).FieldByName("Serial").String()
})
sort.SliceStable(slice2.Interface(), func(i, j int) bool {
return slice2.Index(i).FieldByName("Serial").String() < slice2.Index(j).FieldByName("Serial").String()
})

extraElem := "EMAPI EXTRA"
if slice1.Len() > slice2.Len() {
extraElem = "FleetDB EXTRA"
temp := slice2
slice2 = slice1
slice1 = temp
}

for i := 0; i < slice1.Len(); i++ {
elem1 := slice1.Index(i)
elem2 := slice2.Index(i)

if elem1.Kind() == reflect.Struct {
CompareComponents(elem1.Interface(), elem2.Interface(), fmt.Sprintf("%s[%d]", path, i))
} else {
if elem1.Interface() != elem2.Interface() {
fmt.Printf("Component %s Field %s[%d]: %v != %v\n", path, typeOfObj, i, elem1.Interface(), elem2.Interface())
}
}
}

for i := slice1.Len(); i < slice2.Len(); i++ {
elem := slice2.Index(i)
fmt.Printf("Component %s [%v] Field %s[%d]: %v\n", path, extraElem, typeOfObj, i, elem.Interface())
}
}

// Get firmware info
var getComponentGaps = &cobra.Command{
Use: "component_gaps",
Short: "Get gaps between FleetDB inventory and EMAPI hardware",
Run: func(cmd *cobra.Command, _ []string) {
theApp := mctl.MustCreateApp(cmd.Context())

ctx, cancel := context.WithTimeout(cmd.Context(), mctl.CmdTimeout)
defer cancel()

fleetClient, err := app.NewFleetDBAPIClient(cmd.Context(), theApp.Config.FleetDBAPI, theApp.Reauth)
if err != nil {
log.Fatal(err)
}

serverID, err := uuid.Parse(flagsDefinedGetComponentGaps.id)
if err != nil {
log.Fatal(err)
}

fleetInventory, _, err := fleetClient.GetServerInventory(ctx, serverID, false)
if err != nil {
log.Fatal("fleetdb API client returned error: ", err)
}

emapiClient, err := staff.NewClient()
if err != nil {
log.Fatal(err)
}

opts := &staff.ListOptions{
Excludes: []string{"firmware_version"},
}
emapiHardware, _, err := emapiClient.Staff.Components.ListByHardwareID(serverID.String(), opts)
if err != nil {
log.Fatal(err)
}

fleetComponents, err := convertFleetDBServer(fleetInventory)
if err != nil {
log.Fatal(err)
}

emapiComponents, err := convertEMAPIServer(emapiHardware)
if err != nil {
log.Fatal(err)
}

CompareComponents(fleetComponents, emapiComponents, "Component")

os.Exit(0)
},
}

func init() {
cmdGet.AddCommand(getComponentGaps)

flagsDefinedGetComponentGaps = &getComponentGapsFlags{}
mctl.AddServerFlag(getComponentGaps, &flagsDefinedGetComponentGaps.id)
mctl.RequireFlag(getComponentGaps, mctl.ServerFlag)
}
Loading
Loading