diff --git a/Makefile b/Makefile index bb8fa18..8b5f3cb 100644 --- a/Makefile +++ b/Makefile @@ -60,6 +60,9 @@ release: ## goreleaser build $(call print-target) $(GOPATH)/bin/goreleaser build --clean --single-target --snapshot +.PHONY: binaries +binaries: build + .PHONY: build build: ## go build go build -v --tags=all -ldflags=$(LDFLAGS) -o $(NAME) main.go diff --git a/README.md b/README.md index 1f94e67..6cb34be 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,7 @@ This should return a JSON response with general information. The output below ha ### Running the Tool -There are three main commands to use with the tool: `scan`, `list`, and `collect`. To see all of the available commands, run `magellan` with the `help` subcommand: +There are three main commands to use with the tool: `scan`, `list`, and `collect`. To see all of the available commands, run `magellan` with the `help` subcommand which will print this output: ```bash Redfish-based BMC discovery tool @@ -119,16 +119,17 @@ Available Commands: login Log in with identity provider for access token scan Scan to discover BMC nodes on a network update Update BMC node firmware + version Print version info and exit Flags: - --access-token string set the access token - --cache string set the scanning result cache path (default "/tmp/allend/magellan/assets.db") - --concurrency int set the number of concurrent processes (default -1) - -c, --config string set the config file path - -d, --debug set to enable/disable debug messages + --access-token string Set the access token + --cache string Set the scanning result cache path (default "/tmp/allend/magellan/assets.db") + --concurrency int Set the number of concurrent processes (default -1) + -c, --config string Set the config file path + -d, --debug Set to enable/disable debug messages -h, --help help for magellan - --timeout int set the timeout (default 5) - -v, --verbose set to enable/disable verbose output + --timeout int Set the timeout for requests (default 5) + -v, --verbose Set to enable/disable verbose output Use "magellan [command] --help" for more information about a command. ``` @@ -143,23 +144,19 @@ To start a network scan for BMC nodes, use the `scan` command. If the port is no --cache data/assets.db \ ``` -This will scan the `172.16.0.0` subnet returning the host and port that return a response and store the results in a local cache with at the `data/assets.db` path. Additional flags can be set such as `--host` to add more hosts to scan not included on the subnet, `--timeout` to set how long to wait for a response from the BMC node, or `--concurrency` to set the number of requests to make concurrently. Setting the `--format=json` will format the output in JSON. Try using `./magellan help scan` for a complete set of options this subcommand. Alternatively, the same scan can be started using CIDR notation and with additional hosts: +This will scan the `172.16.0.0` subnet returning the host and port that return a response and store the results in a local cache with at the `data/assets.db` path. Additional flags can be set such as `--host` to add more hosts to scan that are not included on the subnet, `--timeout` to set how long to wait for a response from the BMC node, or `--concurrency` to set the number of requests to make concurrently with goroutines. Try using `./magellan help scan` for a complete set of options this subcommand. Alternatively, the same scan can be started using CIDR notation and with additional hosts: ```bash ./magellan scan https://10.0.0.100:5000 --subnet 172.16.0.0/24 ``` -Check the help for each subcommand for more examples for specifying arguments. - -To inspect the cache, use the `list` command. Make sure to point to the same database used before: +Once the scan is complete, inspect the cache to see a list of found hosts with the `list` command. Make sure to point to the same database used before if you set the `--cache` flag. ```bash -./magellan list --cache data/assets.db --format json +./magellan list --cache data/assets.db ``` -This will print a list of node info found and stored from the scan. Like the `scan` subcommand, the output format can be set using the `--format` flag. - -Finally, set the `ACCESS_TOKEN`run the `collect` command to query the node from cache and send the info to be stored into SMD: +This will print a list of host information needed for the `collect` step. Set the `ACCESS_TOKEN` if necessary and invoke `magellan` again with the `collect` subcommand to query the node BMCs stored in cache. If the `--host` flag is set, then an additional request will be made to send the output to the specified URL. The `--userame` and `--password` flags must be set if the BMC requires basic authentication. ```bash ./magellan collect \ @@ -167,14 +164,14 @@ Finally, set the `ACCESS_TOKEN`run the `collect` command to query the node from --timeout 5 \ --username $USERNAME \ --password $PASSWORD \ - --host https://example.openchami.cluster:27779 \ + --host https://example.openchami.cluster:8443 \ --output logs/ --cacert cacert.pem ``` -This uses the info stored in cache to request information about each BMC node if possible. Like with the scan, the time to wait for a response can be set with the `--timeout` flag as well. This command also requires the `--user` and `--pass` flags to be set if access the Redfish service requires basic authentication. Additionally, it may be necessary to set the `--host` and `--port` flags for `magellan` to find the SMD API (not the root API endpoint "/hsm/v2"). The output of the `collect` can be saved by using the `--output` +This will initiate a crawler that will find as much inventory data as possible. The data can be viewed from standard output by setting the `--verbose` flag. This output can also be saved by using the `--output` flag and providing a path argument. -Note: If the `cache` flag is not set, `magellan` will use "/tmp/$USER/magellan.db" by default. +Note: If the `cache` flag is not set, `magellan` will use `/tmp/$USER/magellan.db` by default. ### Updating Firmware diff --git a/cmd/root.go b/cmd/root.go index b21e22d..3b0d4f0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -74,13 +74,13 @@ func Execute() { func init() { currentUser, _ = user.Current() cobra.OnInitialize(InitializeConfig) - rootCmd.PersistentFlags().IntVar(&concurrency, "concurrency", -1, "set the number of concurrent processes") - rootCmd.PersistentFlags().IntVar(&timeout, "timeout", 5, "set the timeout") - rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "set the config file path") - rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "set to enable/disable verbose output") - rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "set to enable/disable debug messages") - rootCmd.PersistentFlags().StringVar(&accessToken, "access-token", "", "set the access token") - rootCmd.PersistentFlags().StringVar(&cachePath, "cache", fmt.Sprintf("/tmp/%s/magellan/assets.db", currentUser.Username), "set the scanning result cache path") + rootCmd.PersistentFlags().IntVar(&concurrency, "concurrency", -1, "Set the number of concurrent processes") + rootCmd.PersistentFlags().IntVar(&timeout, "timeout", 5, "Set the timeout for requests") + rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "Set the config file path") + rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Set to enable/disable verbose output") + rootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Set to enable/disable debug messages") + rootCmd.PersistentFlags().StringVar(&accessToken, "access-token", "", "Set the access token") + rootCmd.PersistentFlags().StringVar(&cachePath, "cache", fmt.Sprintf("/tmp/%s/magellan/assets.db", currentUser.Username), "Set the scanning result cache path") // bind viper config flags with cobra checkBindFlagError(viper.BindPFlag("concurrency", rootCmd.PersistentFlags().Lookup("concurrency"))) diff --git a/cmd/version.go b/cmd/version.go index 3ffff17..b9cfd82 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -14,16 +14,17 @@ var ( ) var versionCmd = &cobra.Command{ - Use: "version", + Use: "version", + Short: "Print version info and exit", Run: func(cmd *cobra.Command, args []string) { if cmd.Flag("commit").Value.String() == "true" { output = commit if date != "" { - output += " built @ " + date + output += " built on " + date } fmt.Println(output) } else { - fmt.Println(version) + fmt.Printf("%s-%s\n", version, commit) } }, } diff --git a/internal/cache/sqlite/sqlite.go b/internal/cache/sqlite/sqlite.go index 7a04978..594fd92 100644 --- a/internal/cache/sqlite/sqlite.go +++ b/internal/cache/sqlite/sqlite.go @@ -85,11 +85,9 @@ func DeleteScannedAssets(path string, results ...magellan.RemoteAsset) error { func GetScannedAssets(path string) ([]magellan.RemoteAsset, error) { // check if path exists first to prevent creating the database - exists, err := util.PathExists(path) + _, exists := util.PathExists(path) if !exists { return nil, fmt.Errorf("no file found") - } else if err != nil { - return nil, err } // now check if the file is the SQLite database diff --git a/internal/collect.go b/internal/collect.go index 0fd357f..e52bc60 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -11,13 +11,13 @@ import ( "os" "path" "strings" + "path/filepath" "sync" "time" "github.com/OpenCHAMI/magellan/pkg/client" "github.com/OpenCHAMI/magellan/pkg/crawler" - "github.com/OpenCHAMI/magellan/internal/util" "github.com/rs/zerolog/log" "github.com/Cray-HPE/hms-xname/xnames" @@ -171,25 +171,20 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error { // write JSON data to file if output path is set using hive partitioning strategy if outputPath != "" { - // make directory if it does exists - exists, err := util.PathExists(outputPath) - if err == nil && !exists { - err = os.MkdirAll(outputPath, 0o644) + 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).Msg("failed to make directory for output") - } else { - // make the output directory to store files - outputPath, err := util.MakeOutputDirectory(outputPath, false) - if err != nil { - log.Error().Err(err).Msg("failed to make output directory") - } else { - // write the output to the final path - err = os.WriteFile(path.Clean(fmt.Sprintf("%s/%s/%d.json", params.URI, outputPath, time.Now().Unix())), body, os.ModePerm) - if err != nil { - log.Error().Err(err).Msgf("failed to write data to file") - } - } + 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") } } diff --git a/internal/util/path.go b/internal/util/path.go index c2e3e58..de63954 100644 --- a/internal/util/path.go +++ b/internal/util/path.go @@ -2,6 +2,7 @@ package util import ( "fmt" + "io/fs" "os" "path/filepath" "strings" @@ -13,15 +14,9 @@ import ( // // 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 { - return true, nil - } - if os.IsNotExist(err) { - return false, nil - } - return false, err +func PathExists(path string) (fs.FileInfo, bool) { + fi, err := os.Stat(path) + return fi, !os.IsNotExist(err) } // SplitPathForViper() is an utility function to split a path into 3 parts: @@ -51,17 +46,14 @@ func MakeOutputDirectory(path string, overwrite bool) (string, error) { final := path + "/" + dirname // check if path is valid and directory - pathExists, err := PathExists(final) - if err != nil { - return "", fmt.Errorf("failed to check for existing path: %v", err) - } + _, pathExists := PathExists(final) if pathExists && !overwrite { // make sure it is directory with 0o644 permissions return "", fmt.Errorf("found existing path: %v", final) } // create directory with data + time - err = os.MkdirAll(final, 0766) + err := os.MkdirAll(final, 0766) if err != nil { return "", fmt.Errorf("failed to make directory: %v", err) }