Documentation
¶
Overview ¶
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() )
Index ¶
Constants ¶
This section is empty.
Variables ¶
var CollectCmd = &cobra.Command{ Use: "collect", Short: "Collect system information by interrogating BMC node", Long: "Send request(s) to a collection of hosts running Redfish services found stored from the 'scan' in cache.\n" + "See the 'scan' command on how to perform a scan.\n\n" + "Examples:\n" + " magellan collect --cache ./assets.db --output ./logs --timeout 30 --cacert cecert.pem\n" + " magellan collect --host smd.example.com --port 27779 --username $username --password $password\n\n" + " export MASTER_KEY=$(magellan secrets generatekey)\n" + " magellan secrets store $node_creds_json -f nodes.json" + " magellan collect --host openchami.cluster --username $username --password $password \\\n", Run: func(cmd *cobra.Command, args []string) { scannedResults, err := sqlite.GetScannedAssets(cachePath) if err != nil { log.Error().Err(err).Msgf("failed to get scanned results from cache") } host, err = urlx.Sanitize(host) if err != nil { log.Error().Err(err).Msg("failed to sanitize host") } if accessToken == "" { var err error accessToken, err = auth.LoadAccessToken(tokenPath) if err != nil && verbose { log.Warn().Err(err).Msgf("could not load access token") } } if concurrency <= 0 { concurrency = mathutil.Clamp(len(scannedResults), 1, 10000) } params := &magellan.CollectParams{ URI: host, Timeout: timeout, Concurrency: concurrency, Verbose: verbose, CaCertPath: cacertPath, OutputPath: outputPath, ForceUpdate: forceUpdate, AccessToken: accessToken, SecretsFile: secretsFile, Username: username, Password: password, } if verbose { log.Debug().Any("params", params) } store, err := secrets.OpenStore(params.SecretsFile) if err != nil { log.Warn().Err(err).Msg("failed to open local store") store = secrets.NewStaticStore(username, password) } _, err = magellan.CollectInventory(&scannedResults, params, store) if err != nil { log.Error().Err(err).Msg("failed to collect data") } }, }
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 CrawlCmd = &cobra.Command{ Use: "crawl [uri]", Short: "Crawl a single BMC for inventory information", Long: "Crawl a single BMC for inventory information with URI. This command does NOT scan subnets nor store scan information\n" + "in cache after completion. To do so, use the 'collect' command instead\n\n" + "Examples:\n" + " magellan crawl https://bmc.example.com\n" + " magellan crawl https://bmc.example.com -i -u username -p password", Args: func(cmd *cobra.Command, args []string) error { // Validate that the only argument is a valid URI var err error if err := cobra.ExactArgs(1)(cmd, args); err != nil { return err } args[0], err = urlx.Sanitize(args[0]) if err != nil { return fmt.Errorf("failed to sanitize URI: %w", err) } return nil }, Run: func(cmd *cobra.Command, args []string) { var ( uri = args[0] store secrets.SecretStore err error ) store, err = secrets.OpenStore(secretsFile) if err != nil { log.Warn().Err(err).Msg("failed to open local store...falling back to default provided arguments") store = secrets.NewStaticStore(username, password) } _, err = store.GetSecretByID(uri) if err != nil { store = secrets.NewStaticStore(username, password) } systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{ URI: uri, CredentialStore: store, Insecure: insecure, }) if err != nil { log.Error().Err(err).Msg("failed to crawl BMC") } jsonData, err := json.MarshalIndent(systems, "", " ") if err != nil { log.Error().Err(err).Msg("failed to marshal JSON") return } fmt.Println(string(jsonData)) }, }
The `crawl` command walks a collection of Redfish endpoints to collect specfic inventory detail. This command only expects host names and does not require a scan to be performed beforehand.
var (
Insecure bool
)
var ListCmd = &cobra.Command{ Use: "list", Args: cobra.ExactArgs(0), Short: "List information stored in cache from a scan", Long: "Prints all of the host and associated data found from performing a scan.\n" + "See the 'scan' command on how to perform a scan.\n\n" + "Examples:\n" + " magellan list\n" + " magellan list --cache ./assets.db", Run: func(cmd *cobra.Command, args []string) { if showCache { fmt.Printf("cache: %s\n", cachePath) return } scannedResults, err := sqlite.GetScannedAssets(cachePath) if err != nil { log.Error().Err(err).Msg("failed to get scanned assets") } format = strings.ToLower(format) if format == "json" { b, err := json.Marshal(scannedResults) if err != nil { log.Error().Err(err).Msgf("failed to unmarshal scanned results") } fmt.Printf("%s\n", string(b)) } else { for _, r := range scannedResults { fmt.Printf("%s:%d (%s) @%s\n", r.Host, r.Port, r.Protocol, r.Timestamp.Format(time.UnixDate)) } } }, }
The `list` command provides an easy way to show what was found and stored in a cache database from a scan. The data that's stored is what is consumed by the `collect` command with the --cache flag.
var ScanCmd = &cobra.Command{ Use: "scan urls...", Short: "Scan to discover BMC nodes on a network", Long: "Perform a net scan by attempting to connect to each host and port specified and getting a response.\n" + "Each host is passed *with a full URL* including the protocol and port. Additional subnets can be added\n" + "by using the '--subnet' flag and providing an IP address on the subnet as well as a CIDR. If no CIDR is\n" + "provided, then the subnet mask specified with the '--subnet-mask' flag will be used instead (will use\n" + "default mask if not set).\n\n" + "Similarly, any host provided with no port will use either the ports specified\n" + "with `--port` or the default port used with each specified protocol. The default protocol is 'tcp' unless\n" + "specified. The `--scheme` flag works similarly and the default value is 'https' in the host URL or with the\n" + "'--protocol' flag.\n\n" + "If the '--disable-probe` flag is used, the tool will not send another request to probe for available.\n" + "Redfish services. This is not recommended, since the extra request makes the scan a bit more reliable\n" + "for determining which hosts to collect inventory data.\n\n" + "Examples:\n" + " magellan scan 10.0.0.101\n" + " magellan scan http://10.0.0.101:80 https://user:password@10.0.0.102:443 http://172.16.0.105:8080 --subnet 172.16.0.0/24\n" + " magellan scan 10.0.0.101 10.0.0.102 https://172.16.0.10:443 --port 8080 --protocol tcp\n" + " magellan scan --subnet 10.0.0.0\n" + " magellan scan --subnet 10.0.0.0/16\n" + " magellan scan --subnet 192.168.0.0 --protocol tcp --scheme https --port 5000 --subnet-mask 255.255.0.0\n" + " magellan scan --subnet 10.0.0.0/24 --subnet 172.16.0.0 --subnet-mask 255.255.0.0 --cache ./assets.db\n", Run: func(cmd *cobra.Command, args []string) { if len(ports) == 0 { if debug { log.Debug().Msg("adding default ports") } ports = magellan.GetDefaultPorts() } targetHosts = append(targetHosts, urlx.FormatHosts(args, ports, scheme, verbose)...) targetHosts = append(targetHosts, urlx.FormatHosts(hosts, ports, scheme, verbose)...) if debug { log.Debug().Msg("adding hosts from subnets") } for _, subnet := range subnets { if subnet == "" { continue } subnetHosts := magellan.GenerateHostsWithSubnet(subnet, &subnetMask, ports, scheme) targetHosts = append(targetHosts, subnetHosts...) } if len(targetHosts) <= 0 { log.Warn().Msg("nothing to do (no valid target hosts)") return } else { if len(targetHosts[0]) <= 0 { log.Warn().Msg("nothing to do (no valid target hosts)") return } } if debug { combinedTargetHosts := []string{} for _, targetHost := range targetHosts { combinedTargetHosts = append(combinedTargetHosts, targetHost...) } c := map[string]any{ "hosts": combinedTargetHosts, "cache": cachePath, "concurrency": concurrency, "protocol": protocol, "subnets": subnets, "subnet-mask": subnetMask.String(), "cert": cacertPath, "disable-probing": disableProbing, "disable-caching": disableCache, } b, _ := json.MarshalIndent(c, "", " ") fmt.Printf("%s", string(b)) } if concurrency <= 0 { concurrency = len(targetHosts) } else { concurrency = mathutil.Clamp(len(targetHosts), 1, len(targetHosts)) } foundAssets := magellan.ScanForAssets(&magellan.ScanParams{ TargetHosts: targetHosts, Scheme: scheme, Protocol: protocol, Concurrency: concurrency, Timeout: timeout, DisableProbing: disableProbing, Verbose: verbose, Debug: debug, }) if len(foundAssets) > 0 && debug { log.Info().Any("assets", foundAssets).Msgf("found assets from scan") } if !disableCache && cachePath != "" { err := os.MkdirAll(path.Dir(cachePath), 0755) if err != nil { log.Printf("failed to make cache directory: %v", err) } if len(foundAssets) > 0 { err = sqlite.InsertScannedAssets(cachePath, foundAssets...) if err != nil { log.Error().Err(err).Msg("failed to write scanned assets to cache") } if verbose { log.Info().Msgf("saved assets to cache: %s", cachePath) } } else { log.Warn().Msg("no assets found to save") } } }, }
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.
Functions ¶
func InitializeConfig ¶ added in v0.0.7
func InitializeConfig()
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 SetDefaults ¶ added in v0.0.7
func SetDefaults()
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.
Types ¶
This section is empty.