feat: initial implementation of command split

This commit is contained in:
David Allen 2025-04-21 08:47:52 -06:00
parent 522ddb985d
commit 835b678e75
Signed by: towk
GPG key ID: 793B2924A49B3A3F
8 changed files with 181 additions and 63 deletions

View file

@ -9,8 +9,6 @@ import (
"net/http"
"os"
"time"
"github.com/rs/zerolog/log"
)
type Option[T Client] func(client *T)
@ -38,47 +36,47 @@ func NewClient[T Client](opts ...func(T)) T {
return *client
}
func WithCertPool[T Client](certPool *x509.CertPool) func(T) {
func WithCertPool(client Client, certPool *x509.CertPool) error {
// make sure we have a valid cert pool
if certPool == nil {
return func(client T) {}
return fmt.Errorf("invalid cert pool")
}
return func(client T) {
// make sure that we can access the internal client
if client.GetInternalClient() == nil {
log.Warn().Any("client", client.GetInternalClient()).Msg("invalid internal HTTP client ()")
return
}
client.GetInternalClient().Transport = &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
Dial: (&net.Dialer{
Timeout: 120 * time.Second,
KeepAlive: 120 * time.Second,
}).Dial,
TLSHandshakeTimeout: 120 * time.Second,
ResponseHeaderTimeout: 120 * time.Second,
}
// make sure that we can access the internal client
internalClient := client.GetInternalClient()
if internalClient == nil {
return fmt.Errorf("invalid HTTP client")
}
internalClient.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
Dial: (&net.Dialer{
Timeout: 120 * time.Second,
KeepAlive: 120 * time.Second,
}).Dial,
TLSHandshakeTimeout: 120 * time.Second,
ResponseHeaderTimeout: 120 * time.Second,
}
return nil
}
func WithSecureTLS[T Client](certPath string) func(T) {
func WithSecureTLS(client Client, certPath string) error {
cacert, err := os.ReadFile(certPath)
if err != nil {
return func(client T) {}
return fmt.Errorf("failed to read certificate from path '%s': %v", certPath, err)
}
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(cacert)
return WithCertPool[T](certPool)
return WithCertPool(client, certPool)
}
// Post() is a simplified wrapper function that packages all of the
// that marshals a mapper into a JSON-formatted byte array, and then performs
// a request to the specified URL.
func (c *MagellanClient) Post(url string, data map[string]any, header HTTPHeader) (*http.Response, HTTPBody, error) {
func (c *DefaultClient) Post(url string, data map[string]any, header HTTPHeader) (*http.Response, HTTPBody, error) {
// serialize data into byte array
body, err := json.Marshal(data)
if err != nil {

View file

@ -5,11 +5,11 @@ import (
"net/http"
)
type MagellanClient struct {
type DefaultClient struct {
*http.Client
}
func (c *MagellanClient) Name() string {
func (c *DefaultClient) Name() string {
return "default"
}
@ -18,7 +18,7 @@ func (c *MagellanClient) Name() string {
// the first argument with no data processing or manipulation. The function sends
// the data to a set callback URL (which may be changed to use a configurable value
// instead).
func (c *MagellanClient) Add(data HTTPBody, headers HTTPHeader) error {
func (c *DefaultClient) Add(data HTTPBody, headers HTTPHeader) error {
if data == nil {
return fmt.Errorf("no data found")
}
@ -35,7 +35,7 @@ func (c *MagellanClient) Add(data HTTPBody, headers HTTPHeader) error {
return err
}
func (c *MagellanClient) Update(data HTTPBody, headers HTTPHeader) error {
func (c *DefaultClient) Update(data HTTPBody, headers HTTPHeader) error {
if data == nil {
return fmt.Errorf("no data found")
}

View file

@ -18,6 +18,7 @@ import (
"github.com/OpenCHAMI/magellan/pkg/client"
"github.com/OpenCHAMI/magellan/pkg/crawler"
"github.com/OpenCHAMI/magellan/pkg/secrets"
"gopkg.in/yaml.v3"
"github.com/rs/zerolog/log"
@ -39,6 +40,7 @@ type CollectParams struct {
CaCertPath string // set the cert path with the 'cacert' flag
Verbose bool // set whether to include verbose output with 'verbose' flag
OutputPath string // set the path to save output with 'output' flag
Format string // set the output format
ForceUpdate bool // set whether to force updating SMD with 'force-update' flag
AccessToken string // set the access token to include in request with 'access-token' flag
SecretsFile string // set the path to secrets file
@ -191,9 +193,18 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, localStore s
headers.Authorization(params.AccessToken)
headers.ContentType("application/json")
body, err := json.MarshalIndent(data, "", " ")
if err != nil {
log.Error().Err(err).Msgf("failed to marshal output to JSON")
var body []byte
switch params.Format {
case "json":
body, err = json.MarshalIndent(data, "", " ")
if err != nil {
log.Error().Err(err).Msgf("failed to marshal output to JSON")
}
case "yaml":
body, err = yaml.Marshal(data)
if err != nil {
log.Error().Err(err).Msgf("failed to marshal output to YAML")
}
}
if params.Verbose {
@ -203,22 +214,28 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams, localStore s
// add data output to collections
collection = append(collection, data)
// write JSON data to file if output path is set using hive partitioning strategy
// write data to file if output path is set using set format
if outputPath != "" {
var (
finalPath = fmt.Sprintf("./%s/%s/%d.json", outputPath, data["ID"], time.Now().Unix())
finalDir = filepath.Dir(finalPath)
)
// if it doesn't, make the directory and write file
err = os.MkdirAll(finalDir, 0o777)
if err == nil { // no error
err = os.WriteFile(path.Clean(finalPath), body, os.ModePerm)
if err != nil {
log.Error().Err(err).Msgf("failed to write collect output to file")
switch params.Format {
case "hive":
var (
finalPath = fmt.Sprintf("./%s/%s/%d.json", outputPath, data["ID"], time.Now().Unix())
finalDir = filepath.Dir(finalPath)
)
// if it doesn't, make the directory and write file
err = os.MkdirAll(finalDir, 0o777)
if err == nil { // no error
err = os.WriteFile(path.Clean(finalPath), body, os.ModePerm)
if err != nil {
log.Error().Err(err).Msgf("failed to write collect output to file")
}
} else { // error is set
log.Error().Err(err).Msg("failed to make directory for collect output")
}
case "json":
case "yaml":
} else { // error is set
log.Error().Err(err).Msg("failed to make directory for collect output")
default:
}
}

View file

@ -126,17 +126,17 @@ func CrawlBMCForSystems(config CrawlerConfig) ([]InventoryDetail, error) {
// Obtain the ServiceRoot
rf_service := client.GetService()
log.Info().Msgf("found ServiceRoot %s. Redfish Version %s", rf_service.ID, rf_service.RedfishVersion)
log.Debug().Msgf("found ServiceRoot %s. Redfish Version %s", rf_service.ID, rf_service.RedfishVersion)
// Nodes are sometimes only found under Chassis, but they should be found under Systems.
rf_chassis, err := rf_service.Chassis()
if err == nil {
log.Info().Msgf("found %d chassis in ServiceRoot", len(rf_chassis))
log.Debug().Msgf("found %d chassis in ServiceRoot", len(rf_chassis))
for _, chassis := range rf_chassis {
rf_chassis_systems, err := chassis.ComputerSystems()
if err == nil {
rf_systems = append(rf_systems, rf_chassis_systems...)
log.Info().Msgf("found %d systems in chassis %s", len(rf_chassis_systems), chassis.ID)
log.Debug().Msgf("found %d systems in chassis %s", len(rf_chassis_systems), chassis.ID)
}
}
}
@ -144,7 +144,7 @@ func CrawlBMCForSystems(config CrawlerConfig) ([]InventoryDetail, error) {
if err != nil {
log.Error().Err(err).Msg("failed to get systems from ServiceRoot")
}
log.Info().Msgf("found %d systems in ServiceRoot", len(rf_root_systems))
log.Debug().Msgf("found %d systems in ServiceRoot", len(rf_root_systems))
rf_systems = append(rf_systems, rf_root_systems...)
return walkSystems(rf_systems, nil, config.URI)
}
@ -202,7 +202,7 @@ func CrawlBMCForManagers(config CrawlerConfig) ([]Manager, error) {
// Obtain the ServiceRoot
rf_service := client.GetService()
log.Info().Msgf("found ServiceRoot %s. Redfish Version %s", rf_service.ID, rf_service.RedfishVersion)
log.Debug().Msgf("found ServiceRoot %s. Redfish Version %s", rf_service.ID, rf_service.RedfishVersion)
rf_managers, err := rf_service.Managers()
if err != nil {