mirror of
https://github.com/davidallendj/magellan.git
synced 2025-12-20 03:27:03 -07:00
Added initial round of comments for API documentation
This commit is contained in:
parent
796a67d5ab
commit
1ab5c8a7df
5 changed files with 114 additions and 13 deletions
|
|
@ -18,6 +18,9 @@ var (
|
||||||
forceUpdate bool
|
forceUpdate bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The `collect` command fetches data from a collection of BMC nodes.
|
||||||
|
// This command should be ran after the `scan` to find available hosts
|
||||||
|
// on a subnet.
|
||||||
var collectCmd = &cobra.Command{
|
var collectCmd = &cobra.Command{
|
||||||
Use: "collect",
|
Use: "collect",
|
||||||
Short: "Query information about BMC",
|
Short: "Query information about BMC",
|
||||||
|
|
|
||||||
39
cmd/root.go
39
cmd/root.go
|
|
@ -1,3 +1,17 @@
|
||||||
|
// The cmd package implements the interface for the magellan CLI. The files
|
||||||
|
// contained in this package only contains implementations for handling CLI
|
||||||
|
// arguments and passing them to functions within magellan's internal API.
|
||||||
|
//
|
||||||
|
// Each CLI subcommand will have at least one corresponding internal file
|
||||||
|
// with an API routine that implements the command's functionality. The main
|
||||||
|
// API routine will usually be the first function defined in the fill.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// cmd/scan.go --> internal/scan.go ( magellan.ScanForAssets() )
|
||||||
|
// cmd/collect.go --> internal/collect.go ( magellan.CollectAll() )
|
||||||
|
// cmd/list.go --> none (doesn't have API call since it's simple)
|
||||||
|
// cmd/update.go --> internal/update.go ( magellan.UpdateFirmware() )
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -30,11 +44,8 @@ var (
|
||||||
verbose bool
|
verbose bool
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: discover bmc's on network (dora)
|
// The `root` command doesn't do anything on it's own except display
|
||||||
// TODO: query bmc component information and store in db (?)
|
// a help message and then exits.
|
||||||
// TODO: send bmc component information to smd
|
|
||||||
// TODO: set ports to scan automatically with set driver
|
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "magellan",
|
Use: "magellan",
|
||||||
Short: "Tool for BMC discovery",
|
Short: "Tool for BMC discovery",
|
||||||
|
|
@ -47,6 +58,7 @@ var rootCmd = &cobra.Command{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This Execute() function is called from main to run the CLI.
|
||||||
func Execute() {
|
func Execute() {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
|
@ -54,6 +66,14 @@ func Execute() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadAccessToken() tries to load a JWT string from an environment
|
||||||
|
// variable, file, or config in that order. If loading the token
|
||||||
|
// fails with one options, it will fallback to the next option until
|
||||||
|
// all options are exhausted.
|
||||||
|
//
|
||||||
|
// Returns a token as a string with no error if successful.
|
||||||
|
// Alternatively, returns an empty string with an error if a token is
|
||||||
|
// not able to be loaded.
|
||||||
func LoadAccessToken() (string, error) {
|
func LoadAccessToken() (string, error) {
|
||||||
// try to load token from env var
|
// try to load token from env var
|
||||||
testToken := os.Getenv("MAGELLAN_ACCESS_TOKEN")
|
testToken := os.Getenv("MAGELLAN_ACCESS_TOKEN")
|
||||||
|
|
@ -93,12 +113,21 @@ func init() {
|
||||||
viper.BindPFlags(rootCmd.Flags())
|
viper.BindPFlags(rootCmd.Flags())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitializeConfig() initializes a new config object by loading it
|
||||||
|
// from a file given a non-empty string.
|
||||||
|
//
|
||||||
|
// See the 'LoadConfig' function in 'internal/config' for details.
|
||||||
func InitializeConfig() {
|
func InitializeConfig() {
|
||||||
if configPath != "" {
|
if configPath != "" {
|
||||||
magellan.LoadConfig(configPath)
|
magellan.LoadConfig(configPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetDefaults() resets all of the viper properties back to their
|
||||||
|
// default values.
|
||||||
|
//
|
||||||
|
// TODO: This function should probably be moved to 'internal/config.go'
|
||||||
|
// instead of in this file.
|
||||||
func SetDefaults() {
|
func SetDefaults() {
|
||||||
viper.SetDefault("threads", 1)
|
viper.SetDefault("threads", 1)
|
||||||
viper.SetDefault("timeout", 30)
|
viper.SetDefault("timeout", 30)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@ var (
|
||||||
disableProbing bool
|
disableProbing bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// The `scan` command is usually the first step to using the CLI tool.
|
||||||
|
// This command will perform a network scan over a subnet by supplying
|
||||||
|
// a list of subnets, subnet masks, and additional IP address to probe.
|
||||||
|
//
|
||||||
|
// See the `ScanForAssets()` function in 'internal/scan.go' for details
|
||||||
|
// related to the implementation.
|
||||||
var scanCmd = &cobra.Command{
|
var scanCmd = &cobra.Command{
|
||||||
Use: "scan",
|
Use: "scan",
|
||||||
Short: "Scan for BMC nodes on a network",
|
Short: "Scan for BMC nodes on a network",
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
// Package magellan implements the core routines for the tools.
|
||||||
package magellan
|
package magellan
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -50,6 +51,11 @@ type QueryParams struct {
|
||||||
AccessToken string
|
AccessToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is the main function used to collect information from the BMC nodes via Redfish.
|
||||||
|
// The function expects a list of hosts found using the `ScanForAssets()` function.
|
||||||
|
//
|
||||||
|
// Requests can be made to several of the nodes using a goroutine by setting the q.Concurrency
|
||||||
|
// property value between 1 and 255.
|
||||||
func CollectAll(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) error {
|
func CollectAll(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) error {
|
||||||
// check for available probe states
|
// check for available probe states
|
||||||
if probeStates == nil {
|
if probeStates == nil {
|
||||||
|
|
@ -102,6 +108,7 @@ func CollectAll(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) err
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.Log.Errorf("failed to connect to BMC (%v:%v): %v", q.Host, q.Port, err)
|
l.Log.Errorf("failed to connect to BMC (%v:%v): %v", q.Host, q.Port, err)
|
||||||
}
|
}
|
||||||
|
defer gofishClient.Logout()
|
||||||
|
|
||||||
// data to be sent to smd
|
// data to be sent to smd
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
|
|
@ -218,6 +225,7 @@ func CollectAll(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: DELETE ME!!!
|
||||||
func CollectMetadata(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
func CollectMetadata(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
// open BMC session and update driver registry
|
// open BMC session and update driver registry
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
||||||
|
|
@ -275,6 +283,7 @@ func CollectInventory(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: DELETE ME!!!
|
||||||
func CollectPowerState(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
func CollectPowerState(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
||||||
client.Registry.FilterForCompatible(ctx)
|
client.Registry.FilterForCompatible(ctx)
|
||||||
|
|
@ -303,6 +312,7 @@ func CollectPowerState(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: DELETE ME!!!
|
||||||
func CollectUsers(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
func CollectUsers(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
// open BMC session and update driver registry
|
// open BMC session and update driver registry
|
||||||
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
|
||||||
|
|
@ -333,11 +343,18 @@ func CollectUsers(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: DELETE ME!!!q
|
||||||
|
|
||||||
func CollectBios(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
func CollectBios(client *bmclib.Client, q *QueryParams) ([]byte, error) {
|
||||||
b, err := makeRequest(client, client.GetBiosConfiguration, q.Timeout)
|
b, err := makeRequest(client, client.GetBiosConfiguration, q.Timeout)
|
||||||
return b, err
|
return b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectEthernetInterfaces() collects all of the ethernet interfaces found
|
||||||
|
// from all systems from under the "/redfish/v1/Systems" endpoint.
|
||||||
|
//
|
||||||
|
// TODO: This function needs to be refactored entirely...if not deleted
|
||||||
|
// in favor of using crawler.CrawlBM() instead.
|
||||||
func CollectEthernetInterfaces(c *gofish.APIClient, q *QueryParams, systemID string) ([]byte, error) {
|
func CollectEthernetInterfaces(c *gofish.APIClient, q *QueryParams, systemID string) ([]byte, error) {
|
||||||
// TODO: add more endpoints to test for ethernet interfaces
|
// TODO: add more endpoints to test for ethernet interfaces
|
||||||
// /redfish/v1/Chassis/{ChassisID}/NetworkAdapters/{NetworkAdapterId}/NetworkDeviceFunctions/{NetworkDeviceFunctionId}/EthernetInterfaces/{EthernetInterfaceId}
|
// /redfish/v1/Chassis/{ChassisID}/NetworkAdapters/{NetworkAdapterId}/NetworkDeviceFunctions/{NetworkDeviceFunctionId}/EthernetInterfaces/{EthernetInterfaceId}
|
||||||
|
|
@ -380,6 +397,7 @@ func CollectEthernetInterfaces(c *gofish.APIClient, q *QueryParams, systemID str
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: DELETE ME!!!
|
||||||
func CollectChassis(c *gofish.APIClient, q *QueryParams) ([]map[string]any, error) {
|
func CollectChassis(c *gofish.APIClient, q *QueryParams) ([]map[string]any, error) {
|
||||||
rfChassis, err := c.Service.Chassis()
|
rfChassis, err := c.Service.Chassis()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -402,6 +420,7 @@ func CollectChassis(c *gofish.APIClient, q *QueryParams) ([]map[string]any, erro
|
||||||
return chassis, nil
|
return chassis, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: DELETE ME!!!
|
||||||
func CollectStorage(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
|
func CollectStorage(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
|
||||||
systems, err := c.Service.StorageSystems()
|
systems, err := c.Service.StorageSystems()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -427,19 +446,23 @@ func CollectStorage(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CollectSystems pulls system information from each BMC node via Redfish using the
|
||||||
|
// `gofish` library.
|
||||||
|
//
|
||||||
|
// The process of collecting this info is as follows:
|
||||||
|
// 1. check if system has ethernet interfaces
|
||||||
|
// 1.a. if yes, create system data and ethernet interfaces JSON
|
||||||
|
// 1.b. if no, try to get data using manager instead
|
||||||
|
// 2. check if manager has "ManagerForServices" and "EthernetInterfaces" properties
|
||||||
|
// 2.a. if yes, query both properties to use in next step
|
||||||
|
// 2.b. for each service, query its data and add the ethernet interfaces
|
||||||
|
// 2.c. add the system to list of systems to marshal and return
|
||||||
func CollectSystems(c *gofish.APIClient, q *QueryParams) ([]map[string]any, error) {
|
func CollectSystems(c *gofish.APIClient, q *QueryParams) ([]map[string]any, error) {
|
||||||
rfSystems, err := c.Service.Systems()
|
rfSystems, err := c.Service.Systems()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get systems (%v:%v): %v", q.Host, q.Port, err)
|
return nil, fmt.Errorf("failed to get systems (%v:%v): %v", q.Host, q.Port, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. check if system has ethernet interfaces
|
|
||||||
// 1.a. if yes, create system data and ethernet interfaces JSON
|
|
||||||
// 1.b. if no, try to get data using manager instead
|
|
||||||
// 2. check if manager has "ManagerForServices" and "EthernetInterfaces" properties
|
|
||||||
// 2.a. if yes, query both properties to use in next step
|
|
||||||
// 2.b. for each service, query its data and add the ethernet interfaces
|
|
||||||
// 2.c. add the system to list of systems to marshal and return
|
|
||||||
var systems []map[string]any
|
var systems []map[string]any
|
||||||
|
|
||||||
for _, system := range rfSystems {
|
for _, system := range rfSystems {
|
||||||
|
|
@ -605,6 +628,7 @@ func CollectSystems(c *gofish.APIClient, q *QueryParams) ([]map[string]any, erro
|
||||||
return systems, nil
|
return systems, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: DELETE ME!!!
|
||||||
func CollectRegisteries(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
|
func CollectRegisteries(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
|
||||||
registries, err := c.Service.Registries()
|
registries, err := c.Service.Registries()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -620,6 +644,7 @@ func CollectRegisteries(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
|
||||||
return b, nil
|
return b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: MAYBE DELETE???
|
||||||
func CollectProcessors(q *QueryParams) ([]byte, error) {
|
func CollectProcessors(q *QueryParams) ([]byte, error) {
|
||||||
url := baseRedfishUrl(q) + "/Systems"
|
url := baseRedfishUrl(q) + "/Systems"
|
||||||
res, body, err := util.MakeRequest(nil, url, "GET", nil, nil)
|
res, body, err := util.MakeRequest(nil, url, "GET", nil, nil)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,11 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PathExists() is a wrapper function that simplifies checking
|
||||||
|
// if a file or directory already exists at the provided path.
|
||||||
|
//
|
||||||
|
// Returns whether the path exists and no error if successful,
|
||||||
|
// otherwise, it returns false with an error.
|
||||||
func PathExists(path string) (bool, error) {
|
func PathExists(path string) (bool, error) {
|
||||||
_, err := os.Stat(path)
|
_, err := os.Stat(path)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|
@ -24,6 +29,8 @@ func PathExists(path string) (bool, error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetNextIP() returns the next IP address, but does not account
|
||||||
|
// for net masks.
|
||||||
func GetNextIP(ip *net.IP, inc uint) *net.IP {
|
func GetNextIP(ip *net.IP, inc uint) *net.IP {
|
||||||
if ip == nil {
|
if ip == nil {
|
||||||
return &net.IP{}
|
return &net.IP{}
|
||||||
|
|
@ -40,7 +47,14 @@ func GetNextIP(ip *net.IP, inc uint) *net.IP {
|
||||||
return &r
|
return &r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic convenience function used to make HTTP requests.
|
// MakeRequest() is a wrapper function that condenses simple HTTP
|
||||||
|
// requests done to a single call. It expects an optional HTTP client,
|
||||||
|
// URL, HTTP method, request body, and request headers. This function
|
||||||
|
// is useful when making many requests where only these few arguments
|
||||||
|
// are changing.
|
||||||
|
//
|
||||||
|
// Returns a HTTP response object, response body as byte array, and any
|
||||||
|
// error that may have occurred with making the request.
|
||||||
func MakeRequest(client *http.Client, url string, httpMethod string, body []byte, headers map[string]string) (*http.Response, []byte, error) {
|
func MakeRequest(client *http.Client, url string, httpMethod string, body []byte, headers map[string]string) (*http.Response, []byte, error) {
|
||||||
// use defaults if no client provided
|
// use defaults if no client provided
|
||||||
if client == nil {
|
if client == nil {
|
||||||
|
|
@ -69,6 +83,12 @@ func MakeRequest(client *http.Client, url string, httpMethod string, body []byte
|
||||||
return res, b, err
|
return res, b, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeOutputDirectory() creates a new directory at the path argument if
|
||||||
|
// the path does not exist
|
||||||
|
//
|
||||||
|
// TODO: Refactor this function for hive partitioning or possibly move into
|
||||||
|
// the logging package.
|
||||||
|
// TODO: Add an option to force overwriting the path.
|
||||||
func MakeOutputDirectory(path string) (string, error) {
|
func MakeOutputDirectory(path string) (string, error) {
|
||||||
// get the current data + time using Go's stupid formatting
|
// get the current data + time using Go's stupid formatting
|
||||||
t := time.Now()
|
t := time.Now()
|
||||||
|
|
@ -93,12 +113,27 @@ func MakeOutputDirectory(path string) (string, error) {
|
||||||
return final, nil
|
return final, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SplitPathForViper() is an utility function to split a path into 3 parts:
|
||||||
|
// - directory
|
||||||
|
// - filename
|
||||||
|
// - extension
|
||||||
|
// The intent was to break a path into a format that's more easily consumable
|
||||||
|
// by spf13/viper's API. See the "LoadConfig()" function in internal/config.go
|
||||||
|
// for more details.
|
||||||
|
//
|
||||||
|
// TODO: Rename function to something more generalized.
|
||||||
func SplitPathForViper(path string) (string, string, string) {
|
func SplitPathForViper(path string) (string, string, string) {
|
||||||
filename := filepath.Base(path)
|
filename := filepath.Base(path)
|
||||||
ext := filepath.Ext(filename)
|
ext := filepath.Ext(filename)
|
||||||
return filepath.Dir(path), strings.TrimSuffix(filename, ext), strings.TrimPrefix(ext, ".")
|
return filepath.Dir(path), strings.TrimSuffix(filename, ext), strings.TrimPrefix(ext, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FormatErrorList() is a wrapper function that unifies error list formatting
|
||||||
|
// and makes printing error lists consistent.
|
||||||
|
//
|
||||||
|
// NOTE: The error returned IS NOT an error in itself and may be a bit misleading.
|
||||||
|
// Instead, it is a single condensed error composed of all of the errors included
|
||||||
|
// in the errList argument.
|
||||||
func FormatErrorList(errList []error) error {
|
func FormatErrorList(errList []error) error {
|
||||||
var err error
|
var err error
|
||||||
for i, e := range errList {
|
for i, e := range errList {
|
||||||
|
|
@ -108,6 +143,9 @@ func FormatErrorList(errList []error) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HasErrors() is a simple wrapper function to check if an error list contains
|
||||||
|
// errors. Having a function that clearly states its purpose helps to improve
|
||||||
|
// readibility although it may seem pointless.
|
||||||
func HasErrors(errList []error) bool {
|
func HasErrors(errList []error) bool {
|
||||||
return len(errList) > 0
|
return len(errList) > 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue