diff --git a/README.md b/README.md index 1f12869..124f80a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # Magellan -Magellan is a small tool designed to scan a network and collect BMC information -to load the data into an [`hms-smd`](https://github.com/alexlovelltroy/hms-smd/tree/master) instance. +Magellan is a board management controller discovery tool designed to scan a network +and collect information about a BMC node and load that data into an +[`hms-smd`](https://github.com/bikeshack/smd/tree/master) instance. ## How It Works @@ -13,9 +14,13 @@ Magellan is designed to do three things: Magellan first tries to probe for specified hosts using the [`dora`](https://github.com/bmc-toolbox/dora) API. If that fails, it then tries to use its own built-in, simpler scanner as a fallback. -Next, it tries to query information about the BMC node using `bmclib` functions, but requires -access to a redfish interface on the node to work. Once the BMC information is received, -it is then stored into `hms-smd` using its API. +This is done by sending a raw TCP request to a number of potential hosts over a +network, and noting which requests are successful. At this point, `magellan` sees +no difference between a services. + +Next, it tries to query information about the BMC node using `bmclib` functions, +but requires access to a redfish interface on the node to work. Once the BMC +information is received, it is then stored into `hms-smd` using its API. In summary, `magellan` needs at minimum the following configured to work on each node: @@ -33,7 +38,7 @@ cd magellan go mod tidy && go build ``` -This should find and download all of the required dependencies. Although other +This should find and download all of the required dependencies. Although other versions of Go may work, the project has only been tested with v1.20. ## Usage diff --git a/bin/magellan.sh b/bin/magellan.sh index 0850f87..676c62d 100644 --- a/bin/magellan.sh +++ b/bin/magellan.sh @@ -1,17 +1,21 @@ function build(){ - go mod tidy && go build -C bin/magellan + go mod tidy && go build -C bin/magellan } function scan() { - ./magellan scan --subnet 172.16.0.0 --port 443 + ./magellan scan --subnet 172.16.0.0 --port 443 } -function list(){ - ./magellan list +function list() { + ./magellan list +} + +function update() { + ./magellan update --user admin --pass password --host 172.16.0.109 --component BMC --protocol HTTP --firmware-path "" } function collect() { - ./magellan collect --user admin --pass password + ./magellan collect --user admin --pass password } diff --git a/cmd/collect.go b/cmd/collect.go index e54be33..8d9a89b 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -33,6 +33,7 @@ var collectCmd = &cobra.Command{ q := &magellan.QueryParams{ User: user, Pass: pass, + Protocol: protocol, Drivers: drivers, Preferred: preferredDriver, Timeout: timeout, @@ -58,6 +59,7 @@ func init() { collectCmd.PersistentFlags().IntVarP(&smd.Port, "port", "p", smd.Port, "set the port to the smd API") collectCmd.PersistentFlags().StringVar(&user, "user", "", "set the BMC user") collectCmd.PersistentFlags().StringVar(&pass, "pass", "", "set the BMC password") + collectCmd.PersistentFlags().StringVar(&protocol, "protocol", "https", "set the Redfish protocol") collectCmd.PersistentFlags().StringVarP(&outputPath, "output", "o", "/tmp/magellan/data/", "set the path to store collection data") collectCmd.PersistentFlags().BoolVar(&forceUpdate, "force-update", true, "set flag to force update data sent to SMD ") collectCmd.PersistentFlags().StringVar(&preferredDriver, "preferred-driver", "ipmi", "set the preferred driver to use") diff --git a/cmd/root.go b/cmd/root.go index 3fa5217..82d6b4e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,6 +12,7 @@ var ( threads int ports []int hosts []string + protocol string withSecureTLS bool certPoolFile string user string diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 0000000..0df2916 --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,82 @@ +package cmd + +import ( + magellan "github.com/bikeshack/magellan/internal" + "github.com/bikeshack/magellan/internal/log" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + host string + port int + firmwarePath string + firmwareVersion string + component string + transferProtocol string + status bool +) + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update BMC node firmware", + Run: func(cmd *cobra.Command, args []string) { + l := log.NewLogger(logrus.New(), logrus.DebugLevel) + q := &magellan.UpdateParams { + FirmwarePath: firmwarePath, + FirmwareVersion: firmwareVersion, + Component: component, + TransferProtocol: transferProtocol, + QueryParams: magellan.QueryParams{ + Drivers: []string{"redfish"}, + Preferred: "redfish", + Protocol: protocol, + Host: host, + User: user, + Pass: pass, + Timeout: timeout, + Port: port, + WithSecureTLS: withSecureTLS, + }, + } + + // check if required params are set + if host == "" || user == "" || pass == "" { + l.Log.Fatal("requires host, user, and pass to be set") + } + + // get status if flag is set and exit + if status { + err := magellan.GetUpdateStatus(q) + if err != nil { + l.Log.Errorf("could not get update status: %v", err) + } + return + } + + // client, err := magellan.NewClient(l, &q.QueryParams) + // if err != nil { + // l.Log.Errorf("could not make client: %v", err) + // } + // err = magellan.UpdateFirmware(client, l, q) + err := magellan.UpdateFirmwareRemote(q) + if err != nil { + l.Log.Errorf("could not update firmware: %v", err) + } + }, +} + +func init() { + updateCmd.Flags().StringVar(&host, "host", "", "set the BMC host") + updateCmd.Flags().IntVar(&port, "port", 443, "set the BMC port") + updateCmd.Flags().StringVar(&user, "user", "", "set the BMC user") + updateCmd.Flags().StringVar(&pass, "pass", "", "set the BMC password") + updateCmd.Flags().StringVar(&transferProtocol, "transfer-protocol", "HTTP", "set the transfer protocol") + updateCmd.Flags().StringVar(&protocol, "protocol", "https", "set the Redfish protocol") + updateCmd.Flags().StringVar(&firmwarePath, "firmware-path", "", "set the path to the firmware") + updateCmd.Flags().StringVar(&firmwareVersion, "firmware-version", "", "set the version of firmware to be installed") + updateCmd.Flags().StringVar(&component, "component", "", "set the component to upgrade") + updateCmd.Flags().BoolVar(&withSecureTLS, "secure-tls", false, "enable secure TLS") + updateCmd.Flags().BoolVar(&status, "status", false, "get the status of the update") + rootCmd.AddCommand(updateCmd) +} \ No newline at end of file diff --git a/internal/api/smd/smd.go b/internal/api/smd/smd.go index a8369ea..67a56d1 100644 --- a/internal/api/smd/smd.go +++ b/internal/api/smd/smd.go @@ -51,9 +51,7 @@ func AddRedfishEndpoint(data []byte, headers map[string]string) error { // Add redfish endpoint via POST `/hsm/v2/Inventory/RedfishEndpoints` endpoint url := makeEndpointUrl("/Inventory/RedfishEndpoints") res, body, err := util.MakeRequest(url, "POST", data, headers) - fmt.Printf("smd url: %v\n", url) - fmt.Printf("res: %v\n", res.Status) - fmt.Printf("body: %v\n", string(body)) + fmt.Printf("%v (%v)\n%s\n", url, res.Status, string(body)) if res != nil { if res.StatusCode != http.StatusOK { return fmt.Errorf("could not add redfish endpoint") @@ -69,9 +67,7 @@ func UpdateRedfishEndpoint(xname string, data []byte, headers map[string]string) // Update redfish endpoint via PUT `/hsm/v2/Inventory/RedfishEndpoints` endpoint url := makeEndpointUrl("/Inventory/RedfishEndpoints/" + xname) res, body, err := util.MakeRequest(url, "PUT", data, headers) - fmt.Printf("smd url: %v\n", url) - fmt.Printf("res: %v\n", res.Status) - fmt.Printf("body: %v\n", string(body)) + fmt.Printf("%v (%v)\n%s\n", url, res.Status, string(body)) if res != nil { if res.StatusCode != http.StatusOK { return fmt.Errorf("could not update redfish endpoint") diff --git a/internal/collect.go b/internal/collect.go index 7cead57..28af60f 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -38,6 +38,7 @@ const ( type QueryParams struct { Host string Port int + Protocol string User string Pass string Drivers []string @@ -53,9 +54,6 @@ type QueryParams struct { } func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) { - // NOTE: bmclib.NewClient(host, port, user, pass) - // ...seems like the `port` params doesn't work like expected depending on interface - tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -77,22 +75,20 @@ func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) { } // only work if valid cert is provided - if q.WithSecureTLS { - var pool *x509.CertPool - if q.CertPoolFile != "" { - pool = x509.NewCertPool() - data, err := os.ReadFile(q.CertPoolFile) - if err != nil { - return nil, fmt.Errorf("could not read cert pool file: %v", err) - } - pool.AppendCertsFromPEM(data) + if q.WithSecureTLS && q.CertPoolFile != "" { + pool := x509.NewCertPool() + data, err := os.ReadFile(q.CertPoolFile) + if err != nil { + return nil, fmt.Errorf("could not read cert pool file: %v", err) } + pool.AppendCertsFromPEM(data) // a nil pool uses the system certs clientOpts = append(clientOpts, bmclib.WithSecureTLS(pool)) } url := "" + fmt.Println(url) if q.User != "" && q.Pass != "" { - url += fmt.Sprintf("https://%s:%s@%s", q.User, q.Pass, q.Host) + url += fmt.Sprintf("%s://%s:%s@%s", q.Protocol, q.User, q.Pass, q.Host) } else { url += q.Host } @@ -172,7 +168,7 @@ func CollectInfo(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) er var rm map[string]json.RawMessage // inventories - inventory, err := QueryInventory(client, l, q) + inventory, err := QueryInventory(client, q) if err != nil { l.Log.Errorf("could not query inventory (%v:%v): %v", q.Host, q.Port, err) } @@ -189,7 +185,7 @@ func CollectInfo(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) er data["Chassis"] = rm["Chassis"] // ethernet interfaces - interfaces, err := QueryEthernetInterfaces(client, l, q) + interfaces, err := QueryEthernetInterfaces(client, q) if err != nil { l.Log.Errorf("could not query ethernet interfaces: %v", err) continue @@ -312,7 +308,7 @@ func CollectInfo(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) er return nil } -func QueryMetadata(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryMetadata(client *bmclib.Client, q *QueryParams) ([]byte, error) { // client, err := NewClient(l, q) // open BMC session and update driver registry @@ -346,7 +342,7 @@ func QueryMetadata(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte return b, nil } -func QueryInventory(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryInventory(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)) client.Registry.FilterForCompatible(ctx) @@ -379,7 +375,7 @@ func QueryInventory(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byt return b, nil } -func QueryPowerState(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryPowerState(client *bmclib.Client, q *QueryParams) ([]byte, error) { ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout)) client.Registry.FilterForCompatible(ctx) err := client.PreferProvider(q.Preferred).Open(ctx) @@ -411,7 +407,7 @@ func QueryPowerState(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]by } -func QueryUsers(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryUsers(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)) client.Registry.FilterForCompatible(ctx) @@ -445,7 +441,7 @@ func QueryUsers(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, e return b, nil } -func QueryBios(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryBios(client *bmclib.Client, q *QueryParams) ([]byte, error) { // client, err := NewClient(l, q) // if err != nil { // return nil, fmt.Errorf("could not make query: %v", err) @@ -457,14 +453,27 @@ func QueryBios(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, er return b, err } -func QueryEthernetInterfaces(client *bmclib.Client, l *log.Logger, q *QueryParams) ([]byte, error) { +func QueryEthernetInterfaces(client *bmclib.Client, q *QueryParams) ([]byte, error) { c, err := connectGofish(q) if err != nil { return nil, fmt.Errorf("could not connect to bmc: %v", err) } - interfaces, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/") + systems, err := c.Service.Systems() if err != nil { + return nil, fmt.Errorf("could not query storage systems (%v:%v): %v", q.Host, q.Port, err) + } + + var interfaces []*redfish.EthernetInterface + for _, system := range systems { + i, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/" + system.ID + "/EthernetInterfaces/") + if err != nil { + continue + } + interfaces = append(interfaces, i...) + } + + if len(interfaces) <= 0 { return nil, fmt.Errorf("could not get ethernet interfaces: %v", err) } @@ -582,12 +591,7 @@ func QueryRegisteries(q *QueryParams) ([]byte, error) { } func QueryProcessors(q *QueryParams) ([]byte, error) { - baseUrl := "https://" - if q.User != "" && q.Pass != "" { - baseUrl += fmt.Sprintf("%s:%s@", q.User, q.Pass) - } - baseUrl += fmt.Sprintf("%s:%d", q.Host, q.Port) - url := baseUrl + "/redfish/v1/Systems" + url := baseRedfishUrl(q) + "/Systems" res, body, err := util.MakeRequest(url, "GET", nil, nil) if err != nil { return nil, fmt.Errorf("something went wrong: %v", err) @@ -606,7 +610,7 @@ func QueryProcessors(q *QueryParams) ([]byte, error) { // request data about each processor member on node for _, member := range members { var oid = member["@odata.id"].(string) - var infoUrl = baseUrl + oid + var infoUrl = url + oid res, _, err := util.MakeRequest(infoUrl, "GET", nil, nil) if err != nil { return nil, fmt.Errorf("something went wrong: %v", err) @@ -633,6 +637,7 @@ func connectGofish(q *QueryParams) (*gofish.APIClient, error) { config := makeGofishConfig(q) c, err := gofish.Connect(config) if err != nil { + return nil, fmt.Errorf("could not connect to redfish endpoint: %v", err) } if c != nil { @@ -647,12 +652,7 @@ func connectGofish(q *QueryParams) (*gofish.APIClient, error) { } func makeGofishConfig(q *QueryParams) gofish.ClientConfig { - url := "https://" - if q.User != "" && q.Pass != "" { - url += fmt.Sprintf("%s:%s@", q.User, q.Pass) - } - url += fmt.Sprintf("%s:%d", q.Host, q.Port) - + url := baseRedfishUrl(q) return gofish.ClientConfig{ Endpoint: url, Username: q.User, @@ -690,3 +690,11 @@ func makeJson(object any) ([]byte, error) { } return []byte(b), nil } + +func baseRedfishUrl(q *QueryParams) string { + url := fmt.Sprintf("%s://", q.Protocol) + if q.User != "" && q.Pass != "" { + url += fmt.Sprintf("%s:%s@", q.User, q.Pass) + } + return fmt.Sprintf("%s%s:%d", url, q.Host, q.Port) +} diff --git a/internal/db/sqlite/sqlite.go b/internal/db/sqlite/sqlite.go index 2dbc67c..586a73d 100644 --- a/internal/db/sqlite/sqlite.go +++ b/internal/db/sqlite/sqlite.go @@ -8,12 +8,7 @@ import ( "github.com/jmoiron/sqlx" ) -func InsertProbeResults(path string, states *[]magellan.ScannedResult) error { - if states == nil { - return fmt.Errorf("states == nil") - } - - // create database if it doesn't already exist +func CreateProbeResultsIfNotExists(path string) (*sqlx.DB, error) { schema := ` CREATE TABLE IF NOT EXISTS magellan_scanned_ports ( host TEXT NOT NULL, @@ -25,9 +20,22 @@ func InsertProbeResults(path string, states *[]magellan.ScannedResult) error { ` db, err := sqlx.Open("sqlite3", path) if err != nil { - return fmt.Errorf("could not open database: %v", err) + return nil, fmt.Errorf("could not open database: %v", err) } db.MustExec(schema) + return db, nil +} + +func InsertProbeResults(path string, states *[]magellan.BMCProbeResult) error { + if states == nil { + return fmt.Errorf("states == nil") + } + + // create database if it doesn't already exist + db, err := CreateProbeResultsIfNotExists(path) + if err != nil { + return err + } // insert all probe states into db tx := db.MustBegin() @@ -46,6 +54,30 @@ func InsertProbeResults(path string, states *[]magellan.ScannedResult) error { return nil } +func DeleteProbeResults(path string, results *[]magellan.BMCProbeResult) error { + if results == nil { + return fmt.Errorf("no probe results found") + } + db, err := sqlx.Open("sqlite3", path) + if err != nil { + return fmt.Errorf("could not open database: %v", err) + } + tx := db.MustBegin() + for _, state := range *results { + sql := `DELETE FROM magellan_scanned_ports WHERE host = :host, port = :port;` + _, err := tx.NamedExec(sql, &state) + if err != nil { + fmt.Printf("could not execute transaction: %v\n", err) + } + } + + err = tx.Commit() + if err != nil { + return fmt.Errorf("could not commit transaction: %v", err) + } + return nil +} + func GetProbeResults(path string) ([]magellan.ScannedResult, error) { db, err := sqlx.Open("sqlite3", path) if err != nil { diff --git a/internal/update.go b/internal/update.go new file mode 100644 index 0000000..fbfc52e --- /dev/null +++ b/internal/update.go @@ -0,0 +1,195 @@ +package magellan + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "strings" + "time" + + "github.com/bikeshack/magellan/internal/log" + "github.com/bikeshack/magellan/internal/util" + bmclib "github.com/bmc-toolbox/bmclib/v2" + "github.com/bmc-toolbox/bmclib/v2/constants" + bmclibErrs "github.com/bmc-toolbox/bmclib/v2/errors" + "github.com/sirupsen/logrus" +) + + +type UpdateParams struct { + QueryParams + FirmwarePath string + FirmwareVersion string + Component string + TransferProtocol string +} + +// NOTE: Does not work since OpenBMC, whic bmclib uses underneath, does not +// support multipart updates. See issue: https://github.com/bmc-toolbox/bmclib/issues/341 +func UpdateFirmware(client *bmclib.Client, l *log.Logger, q *UpdateParams) error { + if q.Component == "" { + return fmt.Errorf("component is required") + } + + // open BMC session and update driver registry + ctx, ctxCancel := context.WithTimeout(context.Background(), time.Second*time.Duration(q.Timeout)) + client.Registry.FilterForCompatible(ctx) + err := client.Open(ctx) + if err != nil { + ctxCancel() + return fmt.Errorf("could not connect to bmc: %v", err) + } + + defer client.Close(ctx) + + file, err := os.Open(q.FirmwarePath) + if err != nil { + ctxCancel() + return fmt.Errorf("could not open firmware path: %v", err) + } + + defer file.Close() + + taskId, err := client.FirmwareInstall(ctx, q.Component, constants.FirmwareApplyOnReset, true, file) + if err != nil { + ctxCancel() + return fmt.Errorf("could not install firmware: %v", err) + } + + for { + if ctx.Err() != nil { + ctxCancel() + return fmt.Errorf("context error: %v", ctx.Err()) + } + + state, err := client.FirmwareInstallStatus(ctx, q.FirmwareVersion, q.Component, taskId) + if err != nil { + // when its under update a connection refused is returned + if strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "operation timed out") { + l.Log.Info("BMC refused connection, BMC most likely resetting...") + time.Sleep(2 * time.Second) + + continue + } + + if errors.Is(err, bmclibErrs.ErrSessionExpired) || strings.Contains(err.Error(), "session expired") { + err := client.Open(ctx) + if err != nil { + l.Log.Fatal(err, "bmc re-login failed") + } + + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("BMC session expired, logging in...") + + continue + } + + l.Log.Fatal(err) + } + + switch state { + case constants.FirmwareInstallRunning, constants.FirmwareInstallInitializing: + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("firmware install running") + + case constants.FirmwareInstallFailed: + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("firmware install failed") + return fmt.Errorf("failed to install firmware") + + case constants.FirmwareInstallComplete: + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("firmware install completed") + return nil + + case constants.FirmwareInstallPowerCyleHost: + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("host powercycle required") + + if _, err := client.SetPowerState(ctx, "cycle"); err != nil { + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("error power cycling host for install") + return fmt.Errorf("failed to install firmware") + } + + ctxCancel() + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("host power cycled, all done!") + return nil + default: + l.Log.WithFields(logrus.Fields{"state": state, "component": q.Component}).Info("unknown state returned") + } + + time.Sleep(2 * time.Second) + } + + return nil +} + +func UpdateFirmwareRemote(q *UpdateParams) error { + url := baseRedfishUrl(&q.QueryParams) + "/redfish/v1/UpdateService/Actions/SimpleUpdate" + headers := map[string]string { + "Content-Type": "application/json", + "cache-control": "no-cache", + } + b := map[string]any{ + "UpdateComponent": q.Component, // BMC, BIOS + "TransferProtocol": q.TransferProtocol, + "ImageURI": q.FirmwarePath, + } + data, err := json.Marshal(b) + if err != nil { + return fmt.Errorf("could not marshal data: %v", err) + } + res, body, err := util.MakeRequest(url, "POST", data, headers) + if err != nil { + return fmt.Errorf("something went wrong: %v", err) + } else if res == nil { + return fmt.Errorf("no response returned (url: %s)", url) + } + if len(body) > 0 { + fmt.Printf("%d: %v\n", res.StatusCode, string(body)) + } + return nil +} + +func GetUpdateStatus(q *UpdateParams) error { + url := baseRedfishUrl(&q.QueryParams) + "/redfish/v1/UpdateService" + res, body, err := util.MakeRequest(url, "GET", nil, nil) + if err != nil { + return fmt.Errorf("something went wrong: %v", err) + } else if res == nil { + return fmt.Errorf("no response returned (url: %s)", url) + } else if res.StatusCode != http.StatusOK { + return fmt.Errorf("returned status code %d", res.StatusCode) + } + if len(body) > 0 { + fmt.Printf("%d: %v\n", res.StatusCode, string(body)) + } + return nil +} + +// func UpdateFirmwareLocal(q *UpdateParams) error { +// fwUrl := baseUrl(&q.QueryParams) + "" +// url := baseUrl(&q.QueryParams) + "UpdateService/Actions/" +// headers := map[string]string { + +// } + +// // get etag from FW inventory +// response, err := util.MakeRequest() + +// // load file from disk +// file, err := os.ReadFile(q.FirmwarePath) +// if err != nil { +// return fmt.Errorf("could not read file: %v", err) +// } + + + +// switch q.TransferProtocol { +// case "HTTP": +// default: +// return fmt.Errorf("transfer protocol not supported") +// } +// return nil +// } \ No newline at end of file diff --git a/internal/util/util.go b/internal/util/util.go index 661d2ba..990bda9 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -11,15 +11,18 @@ import ( ) func PathExists(path string) (bool, error) { - _, err := os.Stat(path) - if err == nil { return true, nil } - if os.IsNotExist(err) { return false, nil } - return false, err + _, err := os.Stat(path) + if err == nil { return true, nil } + if os.IsNotExist(err) { return false, nil } + return false, err } func MakeRequest(url string, httpMethod string, body []byte, headers map[string]string) (*http.Response, []byte, error) { http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - req, _ := http.NewRequest(httpMethod, url, bytes.NewBuffer(body)) + req, err := http.NewRequest(httpMethod, url, bytes.NewBuffer(body)) + if err != nil { + return nil, nil, fmt.Errorf("could not create new HTTP request: %v", err) + } req.Header.Add("User-Agent", "magellan") for k, v := range headers { req.Header.Add(k, v) @@ -37,20 +40,20 @@ func MakeRequest(url string, httpMethod string, body []byte, headers map[string] } func MakeOutputDirectory(path string) (string, error) { - // get the current data + time using Go's stupid formatting - t := time.Now() - dirname := t.Format("2006-01-01 15:04:05") - final := path + "/" + dirname + // get the current data + time using Go's stupid formatting + t := time.Now() + dirname := t.Format("2006-01-01 15:04:05") + final := path + "/" + dirname // check if path is valid and directory - pathExists, err := PathExists(final); - if err != nil { - return final, fmt.Errorf("could not check for existing path: %v", err) - } + pathExists, err := PathExists(final); + if err != nil { + return final, fmt.Errorf("could not check for existing path: %v", err) + } if pathExists { - // make sure it is directory with 0o644 permissions - return final, fmt.Errorf("found existing path: %v", final) - } + // make sure it is directory with 0o644 permissions + return final, fmt.Errorf("found existing path: %v", final) + } // create directory with data + time err = os.MkdirAll(final, 0766)