Skip to content

Commit

Permalink
Added initial round of comments for API documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
davidallendj committed Jul 19, 2024
1 parent 796a67d commit 1ab5c8a
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 13 deletions.
3 changes: 3 additions & 0 deletions cmd/collect.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ var (
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{
Use: "collect",
Short: "Query information about BMC",
Expand Down
39 changes: 34 additions & 5 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -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

import (
Expand Down Expand Up @@ -30,11 +44,8 @@ var (
verbose bool
)

// TODO: discover bmc's on network (dora)
// TODO: query bmc component information and store in db (?)
// TODO: send bmc component information to smd
// TODO: set ports to scan automatically with set driver

// The `root` command doesn't do anything on it's own except display
// a help message and then exits.
var rootCmd = &cobra.Command{
Use: "magellan",
Short: "Tool for BMC discovery",
Expand All @@ -47,13 +58,22 @@ var rootCmd = &cobra.Command{
},
}

// This Execute() function is called from main to run the CLI.
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

// 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) {
// try to load token from env var
testToken := os.Getenv("MAGELLAN_ACCESS_TOKEN")
Expand Down Expand Up @@ -93,12 +113,21 @@ func init() {
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() {
if 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() {
viper.SetDefault("threads", 1)
viper.SetDefault("timeout", 30)
Expand Down
6 changes: 6 additions & 0 deletions cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ var (
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{
Use: "scan",
Short: "Scan for BMC nodes on a network",
Expand Down
39 changes: 32 additions & 7 deletions internal/collect.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// Package magellan implements the core routines for the tools.
package magellan

import (
Expand Down Expand Up @@ -50,6 +51,11 @@ type QueryParams struct {
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 {
// check for available probe states
if probeStates == nil {
Expand Down Expand Up @@ -102,6 +108,7 @@ func CollectAll(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) err
if err != nil {
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 := map[string]any{
Expand Down Expand Up @@ -218,6 +225,7 @@ func CollectAll(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) err
return nil
}

// TODO: DELETE ME!!!
func CollectMetadata(client *bmclib.Client, q *QueryParams) ([]byte, error) {
// open BMC session and update driver registry
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
Expand Down Expand Up @@ -275,6 +283,7 @@ func CollectInventory(client *bmclib.Client, q *QueryParams) ([]byte, error) {
return b, nil
}

// TODO: DELETE ME!!!
func CollectPowerState(client *bmclib.Client, q *QueryParams) ([]byte, error) {
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
client.Registry.FilterForCompatible(ctx)
Expand Down Expand Up @@ -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) {
// open BMC session and update driver registry
ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout))
Expand Down Expand Up @@ -333,11 +343,18 @@ func CollectUsers(client *bmclib.Client, q *QueryParams) ([]byte, error) {
return b, nil
}

// TODO: DELETE ME!!!q

func CollectBios(client *bmclib.Client, q *QueryParams) ([]byte, error) {
b, err := makeRequest(client, client.GetBiosConfiguration, q.Timeout)
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) {
// TODO: add more endpoints to test for ethernet interfaces
// /redfish/v1/Chassis/{ChassisID}/NetworkAdapters/{NetworkAdapterId}/NetworkDeviceFunctions/{NetworkDeviceFunctionId}/EthernetInterfaces/{EthernetInterfaceId}
Expand Down Expand Up @@ -380,6 +397,7 @@ func CollectEthernetInterfaces(c *gofish.APIClient, q *QueryParams, systemID str
return b, nil
}

// TODO: DELETE ME!!!
func CollectChassis(c *gofish.APIClient, q *QueryParams) ([]map[string]any, error) {
rfChassis, err := c.Service.Chassis()
if err != nil {
Expand All @@ -402,6 +420,7 @@ func CollectChassis(c *gofish.APIClient, q *QueryParams) ([]map[string]any, erro
return chassis, nil
}

// TODO: DELETE ME!!!
func CollectStorage(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
systems, err := c.Service.StorageSystems()
if err != nil {
Expand All @@ -427,19 +446,23 @@ func CollectStorage(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
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) {
rfSystems, err := c.Service.Systems()
if err != nil {
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

for _, system := range rfSystems {
Expand Down Expand Up @@ -605,6 +628,7 @@ func CollectSystems(c *gofish.APIClient, q *QueryParams) ([]map[string]any, erro
return systems, nil
}

// TODO: DELETE ME!!!
func CollectRegisteries(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
registries, err := c.Service.Registries()
if err != nil {
Expand All @@ -620,6 +644,7 @@ func CollectRegisteries(c *gofish.APIClient, q *QueryParams) ([]byte, error) {
return b, nil
}

// TODO: MAYBE DELETE???
func CollectProcessors(q *QueryParams) ([]byte, error) {
url := baseRedfishUrl(q) + "/Systems"
res, body, err := util.MakeRequest(nil, url, "GET", nil, nil)
Expand Down
40 changes: 39 additions & 1 deletion internal/util/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import (
"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) {
_, err := os.Stat(path)
if err == nil {
Expand All @@ -24,6 +29,8 @@ func PathExists(path string) (bool, error) {
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 {
if ip == nil {
return &net.IP{}
Expand All @@ -40,7 +47,14 @@ func GetNextIP(ip *net.IP, inc uint) *net.IP {
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) {
// use defaults if no client provided
if client == nil {
Expand Down Expand Up @@ -69,6 +83,12 @@ func MakeRequest(client *http.Client, url string, httpMethod string, body []byte
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) {
// get the current data + time using Go's stupid formatting
t := time.Now()
Expand All @@ -93,12 +113,27 @@ func MakeOutputDirectory(path string) (string, error) {
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) {
filename := filepath.Base(path)
ext := filepath.Ext(filename)
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 {
var err error
for i, e := range errList {
Expand All @@ -108,6 +143,9 @@ func FormatErrorList(errList []error) error {
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 {
return len(errList) > 0
}

0 comments on commit 1ab5c8a

Please sign in to comment.