diff --git a/.devcontainer/.env b/.devcontainer/.env new file mode 100644 index 0000000..15181a6 --- /dev/null +++ b/.devcontainer/.env @@ -0,0 +1,4 @@ +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +POSTGRES_DB=postgres +POSTGRES_HOSTNAME=localhost diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 0000000..d557f51 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,13 @@ +FROM mcr.microsoft.com/devcontainers/go:1-1.20-bullseye + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment the next lines to use go get to install anything else you need +# USER vscode +# RUN go get -x +# USER root + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..f767ed3 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,28 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/go-postgres +{ + "name": "Go & PostgreSQL", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "features": { + "ghcr.io/marcozac/devcontainer-features/goreleaser:1": {}, + "ghcr.io/devcontainers/features/docker-in-docker:2": {} + + } + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Configure tool-specific properties. + // "customizations": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [5432], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "go version", + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 0000000..071a939 --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,38 @@ +version: '3.8' + +volumes: + postgres-data: + +services: + app: + build: + context: . + dockerfile: Dockerfile + env_file: + # Ensure that the variables in .env match the same variables in devcontainer.json + - .env + + volumes: + - ../..:/workspaces:cached + + # Overrides default command so things don't shut down after the process ends. + command: sleep infinity + + # Runs app on the same network as the database container, allows "forwardPorts" in devcontainer.json function. + network_mode: service:db + + # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) + + db: + image: postgres:latest + restart: unless-stopped + volumes: + - postgres-data:/var/lib/postgresql/data + env_file: + # Ensure that the variables in .env match the same variables in devcontainer.json + - .env + + + # Add "forwardPorts": ["5432"] to **devcontainer.json** to forward PostgreSQL locally. + # (Adding the "ports" property to this file will not forward from a Codespace.) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index a1c7c39..946f525 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -3,25 +3,29 @@ before: - go mod download builds: - env: - - CGO_ENABLED=0 + - CGO_ENABLED=1 goos: - - darwin - linux - - windows goarch: - amd64 + - arm64 dockers: - image_templates: - - bikeshack/{{.ProjectName}}:latest - - bikeshack/{{.ProjectName}}:{{ .Tag }} - - bikeshack/{{.ProjectName}}:{{ .Major }} - - bikeshack/{{.ProjectName}}:{{ .Major }}.{{ .Minor }} + - ghcr.io/openchami/{{.ProjectName}}:latest + - ghcr.io/openchami/{{.ProjectName}}:{{ .Tag }} + - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }} + - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}.{{ .Minor }} build_flag_templates: - "--pull" - "--label=org.opencontainers.image.created={{.Date}}" - "--label=org.opencontainers.image.title={{.ProjectName}}" - "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.version={{.Version}}" + extra_files: + - LICENSE + - CHANGELOG.md + - README.md + - bin/magellan.sh archives: - format: tar.gz rlcp: true @@ -34,10 +38,10 @@ archives: {{- else }}{{ .Arch }}{{ end }} {{- if .Arm }}v{{ .Arm }}{{ end }} files: - - migrations/* - LICENSE - CHANGELOG.md - README.md + - bin/magellan.sh checksum: name_template: 'checksums.txt' snapshot: diff --git a/CHANGELOG.md b/CHANGELOG.md index 701718f..f999cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] + + +## [0.0.5] - 2023-11-02 + +### Added + + * Ability to update firmware + * Refactored connection handling for faster scanning + * Updated to refelct home at github.com/OpenCHAMI + * Updated to reflect ghcr.io as container home + + ## [Unreleased] ## [0.0.1] - 2023-09-14 diff --git a/Dockerfile b/Dockerfile index f5d7b65..1e87c61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,15 @@ FROM cgr.dev/chainguard/wolfi-base -RUN apk add --no-cache tini +RUN apk add --no-cache tini bash # nobody 65534:65534 USER 65534:65534 -COPY magellan / +COPY magellan /magellan +COPY /bin/magellan.sh /magellan.sh -CMD [ "/magellan" ] + +CMD [ "/magellan.sh" ] ENTRYPOINT [ "/sbin/tini", "--" ] diff --git a/README.md b/README.md index 124f80a..d8f8d06 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,9 @@ go mod tidy && go build 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. +To build the Docker container, run `docker build -t magellan:latest .` in the +project's directory. + ## Usage There are three main commands to use with the tool: `scan`, `list`, and `collect`. @@ -81,6 +84,13 @@ to find the `hms-smd` API. Note: If the `db.path` flag is not set, `magellan` will use /tmp/magellan.db by default. +Both the `scan` and `collect` commands can be ran via Docker after pulling the image: + +```bash +docker pull bikeshack/magellan:latest +docker run bikeshack/magellan:latest /magellan.sh --scan "--subnet 172.16.0.0 --port 443 --timeout 3" --collect "--user admin --pass password --host http://vm01 --port 27779" +``` + ## TODO List of things left to fix, do, or ideas... @@ -89,9 +99,9 @@ List of things left to fix, do, or ideas... * [ ] Set default port automatically depending on the driver used to scan * [X] Test using different `bmclib` supported drivers (mainly 'redfish') * [X] Confirm loading different components into `hms-smd` -* [ ] Add ability to set subnet mask for scanning +* [X] Add ability to set subnet mask for scanning * [ ] Add unit tests for `scan`, `list`, and `collect` commands -* [ ] Clean up, remove unused, and tidy code +* [X] Clean up, remove unused, and tidy code ## Copyright diff --git a/bin/magellan.sh b/bin/magellan.sh old mode 100644 new mode 100755 index 676c62d..98b3a37 --- a/bin/magellan.sh +++ b/bin/magellan.sh @@ -1,21 +1,126 @@ +#!/bin/bash + +EXE=./magellan +SUBNETS="" +PORTS="" +USER="" +PASS="" +SMD_HOST="" +SMD_PORT="" +THREADS="1" +TIMEOUT="30" +ARGS="" +FORCE_UPDATE=false +SCAN_PARAMS="" +COLLECT_PARAMS="" + function build(){ 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 + ${EXE} scan ${SCAN_PARAMS} + # --subnet ${SUBNETS} \ + # --port ${PORTS} \ + # --timeout ${TIMEOUT} \ + # --threads ${THREADS} } -function list() { - ./magellan list -} - -function update() { - ./magellan update --user admin --pass password --host 172.16.0.109 --component BMC --protocol HTTP --firmware-path "" +function list(){ + # ./magellan list + ${EXE} list } function collect() { - ./magellan collect --user admin --pass password + # ./magellan collect --user admin --pass password + ${EXE} collect ${COLLECT_PARAMS} + # --user ${USER} \ + # --pass ${PASS} \ + # --timeout ${TIMEOUT} \ + # --threads ${THREADS} \ + # --host ${SMD_HOST} \ + # --port ${SMD_PORT} \ + # --force-update ${FORCE_UPDATE} } + +# parse incoming arguments to set variables +while [[ $# -gt 0 ]]; do + case $1 in + --scan) + SCAN_PARAMS="$2" + shift + shift + ;; + --collect) + COLLECT_PARAMS="$2" + shift + shift + ;; + --subnet) + SUBNETS="$2" + shift # past argument + shift # past value + ;; + -p|--port) + PORTS="$2" + shift # past argument + shift # past value + ;; + --user) + USER="$2" + shift # past argument + shift # past value + ;; + --pass|--password) + PASS="$2" + shift + shift + ;; + --smd-host) + SMD_HOST="$2" + shift + shift + ;; + --smd-port) + SMD_PORT="$2" + shift + shift + ;; + --timeout) + TIMEOUT="$2" + shift + shift + ;; + --threads) + THREADS="$2" + shift + shift + ;; + -*|--*) + echo "Unknown option $1" + exit 1 + ;; + *) + ARGS+=("$1") # save positional arg + shift # past argument + ;; + esac +done + +set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters + +if [[ -n $1 ]]; then + echo "Last line of file specified as non-opt/last argument:" + tail -1 "$1" +fi + +scan +collect + +# run with docker +# docker run magellan:latest magellan.sh \ +# --scan "--subnet 127.16.0.0 --port 443" \ +# --collect "--user admin --pass password --timeout 300 --threads 1 --smd-host host --smd-port port" \ No newline at end of file diff --git a/cmd/collect.go b/cmd/collect.go index 7481171..16c9e0a 100644 --- a/cmd/collect.go +++ b/cmd/collect.go @@ -1,10 +1,10 @@ package cmd import ( - magellan "github.com/bikeshack/magellan/internal" - "github.com/bikeshack/magellan/internal/api/smd" - "github.com/bikeshack/magellan/internal/db/sqlite" - "github.com/bikeshack/magellan/internal/log" + magellan "github.com/OpenCHAMI/magellan/internal" + "github.com/OpenCHAMI/magellan/internal/api/smd" + "github.com/OpenCHAMI/magellan/internal/db/sqlite" + "github.com/OpenCHAMI/magellan/internal/log" "github.com/cznic/mathutil" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -28,21 +28,31 @@ var collectCmd = &cobra.Command{ l.Log.Errorf("could not get states: %v", err) } + // try to load access token either from env var, file, or config if var not set + if accessToken == "" { + var err error + accessToken, err = LoadAccessToken() + if err != nil { + l.Log.Errorf("failed to load access token: %v", err) + } + } + + // if threads <= 0 { threads = mathutil.Clamp(len(probeStates), 1, 255) } q := &magellan.QueryParams{ - User: user, - Pass: pass, - Protocol: protocol, - Drivers: drivers, - Preferred: preferredDriver, - Timeout: timeout, - Threads: threads, - Verbose: verbose, - WithSecureTLS: withSecureTLS, - OutputPath: outputPath, - ForceUpdate: forceUpdate, + User: user, + Pass: pass, + Protocol: protocol, + Drivers: drivers, + Preferred: preferredDriver, + Timeout: timeout, + Threads: threads, + Verbose: verbose, + CaCertPath: cacertPath, + OutputPath: outputPath, + ForceUpdate: forceUpdate, } magellan.CollectAll(&probeStates, l, q) @@ -81,6 +91,5 @@ func init() { viper.BindPFlag("collect.secure-tls", collectCmd.Flags().Lookup("secure-tls")) viper.BindPFlag("collect.cert-pool", collectCmd.Flags().Lookup("cert-pool")) - rootCmd.AddCommand(collectCmd) } diff --git a/cmd/list.go b/cmd/list.go index 3e344a0..9ad54bb 100644 --- a/cmd/list.go +++ b/cmd/list.go @@ -3,7 +3,7 @@ package cmd import ( "fmt" - "github.com/bikeshack/magellan/internal/db/sqlite" + "github.com/OpenCHAMI/magellan/internal/db/sqlite" "github.com/sirupsen/logrus" "github.com/spf13/cobra" diff --git a/cmd/login.go b/cmd/login.go new file mode 100644 index 0000000..bd23e29 --- /dev/null +++ b/cmd/login.go @@ -0,0 +1,84 @@ +package cmd + +import ( + "errors" + "fmt" + "net/http" + "os" + + magellan "github.com/OpenCHAMI/magellan/internal" + "github.com/OpenCHAMI/magellan/internal/log" + "github.com/lestrrat-go/jwx/jwt" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +var ( + loginUrl string + targetHost string + targetPort int + tokenPath string + forceLogin bool + noBrowser bool +) + +var loginCmd = &cobra.Command{ + Use: "login", + Short: "Log in with identity provider for access token", + Long: "", + Run: func(cmd *cobra.Command, args []string) { + // make application logger + l := log.NewLogger(logrus.New(), logrus.DebugLevel) + + // check if we have a valid JWT before starting login + if !forceLogin { + // try getting the access token from env var + testToken, err := LoadAccessToken() + if err != nil { + l.Log.Errorf("failed to load access token: %v", err) + } + + // parse into jwt.Token to validate + token, err := jwt.Parse([]byte(testToken)) + if err != nil { + fmt.Printf("failed to parse access token contents: %v\n", err) + return + } + // check if the token is invalid and we need a new one + err = jwt.Validate(token) + if err != nil { + fmt.Printf("failed to validate access token...fetching a new one") + } else { + fmt.Printf("found a valid token...skipping login (use the '-f/--force' flag to login anyway)") + return + } + } + + // start the login flow + var err error + accessToken, err = magellan.Login(loginUrl, targetHost, targetPort) + if errors.Is(err, http.ErrServerClosed) { + fmt.Printf("\n=========================================\nServer closed.\n=========================================\n\n") + } else if err != nil { + fmt.Printf("failed to start server: %v\n", err) + } + + // if we got a new token successfully, save it to the token path + if accessToken != "" && tokenPath != "" { + err := os.WriteFile(tokenPath, []byte(accessToken), os.ModePerm) + if err != nil { + fmt.Printf("failed to write access token to file: %v\n", err) + } + } + }, +} + +func init() { + loginCmd.Flags().StringVar(&loginUrl, "url", "http://127.0.0.1:3333/login", "set the login URL") + loginCmd.Flags().StringVar(&targetHost, "target-host", "127.0.0.1", "set the target host to return the access code") + loginCmd.Flags().IntVar(&targetPort, "target-port", 5000, "set the target host to return the access code") + loginCmd.Flags().BoolVarP(&forceLogin, "force", "f", false, "start the login process even with a valid token") + loginCmd.Flags().StringVar(&tokenPath, "token-path", ".ochami-token", "set the path the load/save the access token") + loginCmd.Flags().BoolVar(&noBrowser, "no-browser", false, "prevent the default browser from being opened automatically") + rootCmd.AddCommand(loginCmd) +} diff --git a/cmd/root.go b/cmd/root.go index e738020..f53d493 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,21 +12,21 @@ import ( ) var ( + accessToken string timeout int threads int ports []int hosts []string protocol string - withSecureTLS bool - certPoolFile string + cacertPath string user string pass string dbpath string drivers []string preferredDriver string ipmitoolPath string - outputPath string - configPath string + outputPath string + configPath string verbose bool ) @@ -54,14 +54,32 @@ func Execute() { } } +func LoadAccessToken() (string, error) { + // try to load token from env var + testToken := os.Getenv("OCHAMI_ACCESS_TOKEN") + if testToken != "" { + return testToken, nil + } + + // try reading access token from a file + b, err := os.ReadFile(tokenPath) + if err == nil { + return string(b), nil + } + + // TODO: try to load token from config + return "", fmt.Errorf("could not load from environment variable or file") +} + func init() { cobra.OnInitialize(InitializeConfig) rootCmd.PersistentFlags().IntVar(&threads, "threads", -1, "set the number of threads") rootCmd.PersistentFlags().IntVar(&timeout, "timeout", 30, "set the timeout") rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "set the config file path") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", true, "set verbose flag") + rootCmd.PersistentFlags().StringVar(&accessToken, "access-token", "", "set the access token") rootCmd.PersistentFlags().StringVar(&dbpath, "db.path", "/tmp/magellan/magellan.db", "set the probe storage path") - + // bind viper config flags with cobra viper.BindPFlag("threads", rootCmd.Flags().Lookup("threads")) viper.BindPFlag("timeout", rootCmd.Flags().Lookup("timeout")) @@ -112,4 +130,4 @@ func SetDefaults() { viper.SetDefault("secure-tls", false) viper.SetDefault("status", false) -} \ No newline at end of file +} diff --git a/cmd/scan.go b/cmd/scan.go index 62a2312..f190d21 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -6,8 +6,8 @@ import ( "os" "path" - magellan "github.com/bikeshack/magellan/internal" - "github.com/bikeshack/magellan/internal/db/sqlite" + magellan "github.com/OpenCHAMI/magellan/internal" + "github.com/OpenCHAMI/magellan/internal/db/sqlite" "github.com/cznic/mathutil" "github.com/spf13/cobra" @@ -15,10 +15,10 @@ import ( ) var ( - begin uint8 - end uint8 - subnets []string - subnetMasks []net.IP + begin uint8 + end uint8 + subnets []string + subnetMasks []net.IP disableProbing bool ) @@ -37,10 +37,10 @@ var scanCmd = &cobra.Command{ return } - if len(subnetMasks) < i + 1 { + if len(subnetMasks) < i+1 { subnetMasks = append(subnetMasks, net.IP{255, 255, 255, 0}) } - + hostsToScan = append(hostsToScan, magellan.GenerateHosts(subnet, &subnetMasks[i])...) } } diff --git a/cmd/update.go b/cmd/update.go index 7c59e7b..bd64986 100644 --- a/cmd/update.go +++ b/cmd/update.go @@ -1,46 +1,45 @@ package cmd import ( - magellan "github.com/bikeshack/magellan/internal" - "github.com/bikeshack/magellan/internal/log" + magellan "github.com/OpenCHAMI/magellan/internal" + "github.com/OpenCHAMI/magellan/internal/log" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( - host string - port int - firmwareUrl string - firmwareVersion string - component string + host string + port int + firmwareUrl string + firmwareVersion string + component string transferProtocol string - status bool + status bool ) var updateCmd = &cobra.Command{ - Use: "update", + 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: firmwareUrl, - FirmwareVersion: firmwareVersion, - Component: component, + q := &magellan.UpdateParams{ + FirmwarePath: firmwareUrl, + FirmwareVersion: firmwareVersion, + Component: component, TransferProtocol: transferProtocol, QueryParams: magellan.QueryParams{ - Drivers: []string{"redfish"}, + Drivers: []string{"redfish"}, Preferred: "redfish", - Protocol: protocol, - Host: host, - User: user, - Pass: pass, - Timeout: timeout, - Port: port, - WithSecureTLS: withSecureTLS, + Protocol: protocol, + Host: host, + User: user, + Pass: pass, + Timeout: timeout, + Port: port, }, } - + // check if required params are set if host == "" || user == "" || pass == "" { l.Log.Fatal("requires host, user, and pass to be set") @@ -54,7 +53,7 @@ var updateCmd = &cobra.Command{ } return } - + // client, err := magellan.NewClient(l, &q.QueryParams) // if err != nil { // l.Log.Errorf("could not make client: %v", err) @@ -77,7 +76,6 @@ func init() { updateCmd.Flags().StringVar(&firmwareUrl, "firmware-url", "", "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") viper.BindPFlag("bmc-host", updateCmd.Flags().Lookup("bmc-host")) @@ -93,4 +91,4 @@ func init() { viper.BindPFlag("status", updateCmd.Flags().Lookup("status")) rootCmd.AddCommand(updateCmd) -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index ad1c52d..0891164 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/bikeshack/magellan +module github.com/OpenCHAMI/magellan go 1.20 @@ -6,9 +6,12 @@ require ( github.com/Cray-HPE/hms-xname v1.3.0 github.com/bmc-toolbox/bmclib/v2 v2.0.1-0.20230714152943-a1b87e2ff47f github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 + github.com/go-chi/chi/v5 v5.0.12 github.com/jacobweinstock/registrar v0.4.7 github.com/jmoiron/sqlx v1.3.5 + github.com/lestrrat-go/jwx v1.2.29 github.com/mattn/go-sqlite3 v1.14.6 + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 github.com/spf13/viper v1.17.0 @@ -22,6 +25,7 @@ require ( github.com/bmc-toolbox/common v0.0.0-20230717121556-5eb9915a8a5a // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/logr v1.2.4 // indirect + github.com/goccy/go-json v0.10.2 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -30,6 +34,12 @@ require ( github.com/magiconair/properties v1.8.7 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/sagikazarmark/locafero v0.3.0 // indirect diff --git a/internal/api/dora/dora.go b/internal/api/dora/dora.go index e1a27f9..880ac7b 100644 --- a/internal/api/dora/dora.go +++ b/internal/api/dora/dora.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/bikeshack/magellan/internal/util" + "github.com/OpenCHAMI/magellan/internal/util" "github.com/jmoiron/sqlx" ) diff --git a/internal/api/smd/smd.go b/internal/api/smd/smd.go index f257ad2..ca0a606 100644 --- a/internal/api/smd/smd.go +++ b/internal/api/smd/smd.go @@ -5,9 +5,8 @@ package smd // https://github.com/alexlovelltroy/hms-smd import ( "fmt" - "net/http" - "github.com/bikeshack/magellan/internal/util" + "github.com/OpenCHAMI/magellan/internal/util" // hms "github.com/alexlovelltroy/hms-smd" ) @@ -52,8 +51,9 @@ func AddRedfishEndpoint(data []byte, headers map[string]string) error { url := makeEndpointUrl("/Inventory/RedfishEndpoints") res, body, err := util.MakeRequest(url, "POST", data, headers) if res != nil { - if res.StatusCode != http.StatusOK { - return fmt.Errorf("could not add redfish endpoint") + statusOk := res.StatusCode >= 200 && res.StatusCode < 300 + if !statusOk { + return fmt.Errorf("returned status code %d when adding endpoint", res.StatusCode) } fmt.Printf("%v (%v)\n%s\n", url, res.Status, string(body)) } @@ -69,7 +69,8 @@ func UpdateRedfishEndpoint(xname string, data []byte, headers map[string]string) res, body, err := util.MakeRequest(url, "PUT", data, headers) fmt.Printf("%v (%v)\n%s\n", url, res.Status, string(body)) if res != nil { - if res.StatusCode != http.StatusOK { + statusOk := res.StatusCode >= 200 && res.StatusCode < 300 + if !statusOk { return fmt.Errorf("could not update redfish endpoint") } } diff --git a/internal/collect.go b/internal/collect.go index 9e63253..4a2189a 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -12,10 +12,10 @@ import ( "sync" "time" - "github.com/bikeshack/magellan/internal/log" + "github.com/OpenCHAMI/magellan/internal/log" - "github.com/bikeshack/magellan/internal/api/smd" - "github.com/bikeshack/magellan/internal/util" + "github.com/OpenCHAMI/magellan/internal/api/smd" + "github.com/OpenCHAMI/magellan/internal/util" "github.com/Cray-HPE/hms-xname/xnames" bmclib "github.com/bmc-toolbox/bmclib/v2" @@ -33,27 +33,27 @@ const ( HTTPS_PORT = 443 ) - // NOTE: ...params were getting too long... type QueryParams struct { - Host string - Port int - Protocol string - User string - Pass string - Drivers []string - Threads int - Preferred string - Timeout int - WithSecureTLS bool - CertPoolFile string - Verbose bool - IpmitoolPath string - OutputPath string - ForceUpdate bool + Host string + Port int + Protocol string + User string + Pass string + Drivers []string + Threads int + Preferred string + Timeout int + CaCertPath string + Verbose bool + IpmitoolPath string + OutputPath string + ForceUpdate bool + AccessToken string } func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) { + tr := &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } @@ -75,9 +75,9 @@ func NewClient(l *log.Logger, q *QueryParams) (*bmclib.Client, error) { } // only work if valid cert is provided - if q.WithSecureTLS && q.CertPoolFile != "" { + if q.CaCertPath != "" { pool := x509.NewCertPool() - data, err := os.ReadFile(q.CertPoolFile) + data, err := os.ReadFile(q.CaCertPath) if err != nil { return nil, fmt.Errorf("could not read cert pool file: %v", err) } @@ -122,15 +122,8 @@ func CollectAll(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) err done := make(chan struct{}, q.Threads+1) chanProbeState := make(chan ScannedResult, q.Threads+1) - // generate custom xnames for bmcs - node := xnames.Node{ - Cabinet: 1000, - Chassis: 1, - ComputeModule: 7, - NodeBMC: -1, - } - // collect bmc information asynchronously + var offset = 0 var wg sync.WaitGroup wg.Add(q.Threads) for i := 0; i < q.Threads; i++ { @@ -144,23 +137,34 @@ func CollectAll(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) err q.Host = ps.Host q.Port = ps.Port - client, err := NewClient(l, q) - if err != nil { - l.Log.Errorf("could not make client: %v", err) - continue + // generate custom xnames for bmcs + node := xnames.Node{ + Cabinet: 1000, + Chassis: 1, + ComputeModule: 7, + NodeBMC: offset, } + offset += 1 - node.NodeBMC += 1 + // bmclibClient, err := NewClient(l, q) + // if err != nil { + // l.Log.Errorf("could not make client: %v", err) + // } + + gofishClient, err := connectGofish(q) + if err != nil { + l.Log.Errorf("could not connect to bmc (%v:%v): %v", q.Host, q.Port, err) + } // data to be sent to smd data := map[string]any{ - "ID": fmt.Sprintf("%v", node.String()[:len(node.String())-2]), - "Type": "", - "Name": "", - "FQDN": ps.Host, - "User": q.User, - "Password": q.Pass, - "MACRequired": true, + "ID": fmt.Sprintf("%v", node.String()[:len(node.String())-2]), + "Type": "", + "Name": "", + "FQDN": ps.Host, + "User": q.User, + "Password": q.Pass, + "MACRequired": true, "RediscoverOnUpdate": false, } @@ -168,96 +172,72 @@ func CollectAll(probeStates *[]ScannedResult, l *log.Logger, q *QueryParams) err var rm map[string]json.RawMessage // inventories - inventory, err := CollectInventory(client, q) - if err != nil { - l.Log.Errorf("could not query inventory (%v:%v): %v", q.Host, q.Port, err) - } - json.Unmarshal(inventory, &rm) - data["Inventory"] = rm["Inventory"] + // if bmclibClient != nil { + // inventory, err := CollectInventory(bmclibClient, q) + // if err != nil { + // l.Log.Errorf("could not query inventory (%v:%v): %v", q.Host, q.Port, err) + // } + // json.Unmarshal(inventory, &rm) + // data["Inventory"] = rm["Inventory"] + // } // chassis - chassis, err := CollectChassis(q) - if err != nil { - l.Log.Errorf("could not query chassis: %v", err) - continue + if gofishClient != nil { + chassis, err := CollectChassis(gofishClient, q) + if err != nil { + l.Log.Errorf("could not query chassis: %v", err) + continue + } + json.Unmarshal(chassis, &rm) + data["Chassis"] = rm["Chassis"] + + // systems + systems, err := CollectSystems(gofishClient, q) + if err != nil { + l.Log.Errorf("could not query systems: %v", err) + } + json.Unmarshal(systems, &rm) + data["Systems"] = rm["Systems"] + + // add other fields from systems + if len(rm["Systems"]) > 0 { + var s map[string][]interface{} + json.Unmarshal(rm["Systems"], &s) + data["Name"] = s["Name"] + } } - json.Unmarshal(chassis, &rm) - data["Chassis"] = rm["Chassis"] - - // ethernet interfaces - // interfaces, err := QueryEthernetInterfaces(client, q) - // if err != nil { - // l.Log.Errorf("could not query ethernet interfaces: %v", err) - // continue - // } - // json.Unmarshal(interfaces, &rm) - // data["Interfaces"] = rm["Interfaces"] - - // storage - // storage, err := QueryStorage(q) - // if err != nil { - // l.Log.Errorf("could not query storage: %v", err) - // continue - // } - // json.Unmarshal(storage, &rm) - // data["Storage"] = rm["Storage"] - - // get specific processor info - // procs, err := QueryProcessors(q) - // if err != nil { - // l.Log.Errorf("could not query processors: %v", err) - // } - // var p map[string]interface{} - // json.Unmarshal(procs, &p) - // data["Processors"] = rm["Processors"] - - // systems - systems, err := CollectSystems(client, q) - if err != nil { - l.Log.Errorf("could not query systems: %v", err) - } - json.Unmarshal(systems, &rm) - data["Systems"] = rm["Systems"] - - // add other fields from systems - if len(rm["Systems"]) > 0 { - var s map[string][]interface{} - json.Unmarshal(rm["Systems"], &s) - data["Name"] = s["Name"] - } - - // data["Type"] = rm[""] - - // registries - // registries, err := QueryRegisteries(q) - // if err != nil { - // l.Log.Errorf("could not query registries: %v", err) - // } - // json.Unmarshal(registries, &rm) - // data["Registries"] = rm["Registries"] headers := make(map[string]string) headers["Content-Type"] = "application/json" - b, err := json.MarshalIndent(data, "", " ") + // use access token in authorization header if we have it + if q.AccessToken != "" { + headers["Authorization"] = "Bearer " + q.AccessToken + } + + body, err := json.MarshalIndent(data, "", " ") if err != nil { l.Log.Errorf("could not marshal JSON: %v", err) } + if q.Verbose { + fmt.Printf("%v\n", string(body)) + } + // write JSON data to file - err = os.WriteFile(path.Clean(outputPath + "/" + q.Host + ".json"), b, os.ModePerm) + err = os.WriteFile(path.Clean(outputPath+"/"+q.Host+".json"), body, os.ModePerm) if err != nil { l.Log.Errorf("could not write data to file: %v", err) } // add all endpoints to smd - err = smd.AddRedfishEndpoint(b, headers) + err = smd.AddRedfishEndpoint(body, headers) if err != nil { l.Log.Error(err) // try updating instead if q.ForceUpdate { - err = smd.UpdateRedfishEndpoint(data["ID"].(string), b, headers) + err = smd.UpdateRedfishEndpoint(data["ID"].(string), body, headers) if err != nil { l.Log.Error(err) } @@ -324,9 +304,6 @@ func CollectMetadata(client *bmclib.Client, q *QueryParams) ([]byte, error) { return nil, fmt.Errorf("could not marshal JSON: %v", err) } - if q.Verbose { - fmt.Printf("Metadata: %v\n", string(b)) - } ctxCancel() return b, nil } @@ -340,7 +317,6 @@ func CollectInventory(client *bmclib.Client, q *QueryParams) ([]byte, error) { ctxCancel() return nil, fmt.Errorf("could not open client: %v", err) } - defer client.Close(ctx) inventory, err := client.Inventory(ctx) if err != nil { @@ -348,7 +324,6 @@ func CollectInventory(client *bmclib.Client, q *QueryParams) ([]byte, error) { return nil, fmt.Errorf("could not get inventory: %v", err) } - // retrieve inventory data data := map[string]any{"Inventory": inventory} b, err := json.MarshalIndent(data, "", " ") @@ -357,9 +332,6 @@ func CollectInventory(client *bmclib.Client, q *QueryParams) ([]byte, error) { return nil, fmt.Errorf("could not marshal JSON: %v", err) } - if q.Verbose { - fmt.Printf("%v\n", string(b)) - } ctxCancel() return b, nil } @@ -372,7 +344,6 @@ func CollectPowerState(client *bmclib.Client, q *QueryParams) ([]byte, error) { ctxCancel() return nil, fmt.Errorf("could not open client: %v", err) } - defer client.Close(ctx) powerState, err := client.GetPowerState(ctx) if err != nil { @@ -388,9 +359,6 @@ func CollectPowerState(client *bmclib.Client, q *QueryParams) ([]byte, error) { return nil, fmt.Errorf("could not marshal JSON: %v", err) } - if q.Verbose { - fmt.Printf("%v\n", string(b)) - } ctxCancel() return b, nil @@ -415,18 +383,14 @@ func CollectUsers(client *bmclib.Client, q *QueryParams) ([]byte, error) { } // retrieve inventory data - data := map[string]any {"Users": users} + data := map[string]any{"Users": users} b, err := json.MarshalIndent(data, "", " ") if err != nil { ctxCancel() return nil, fmt.Errorf("could not marshal JSON: %v", err) } - // return b, nil ctxCancel() - if q.Verbose { - fmt.Printf("%v\n", string(b)) - } return b, nil } @@ -436,18 +400,10 @@ func CollectBios(client *bmclib.Client, q *QueryParams) ([]byte, error) { // return nil, fmt.Errorf("could not make query: %v", err) // } b, err := makeRequest(client, client.GetBiosConfiguration, q.Timeout) - if q.Verbose { - fmt.Printf("%v\n", string(b)) - } return b, err } -func CollectEthernetInterfaces(client *bmclib.Client, q *QueryParams, systemID string) ([]byte, error) { - c, err := connectGofish(q) - if err != nil { - return nil, fmt.Errorf("could not connect to bmc: %v", err) - } - +func CollectEthernetInterfaces(c *gofish.APIClient, q *QueryParams, systemID string) ([]byte, error) { 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) @@ -455,13 +411,13 @@ func CollectEthernetInterfaces(client *bmclib.Client, q *QueryParams, systemID s var interfaces []*redfish.EthernetInterface for _, system := range systems { - i, err := redfish.ListReferencedEthernetInterfaces(c, "/redfish/v1/Systems/" + system.ID + "/EthernetInterfaces/") + 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) } @@ -472,17 +428,10 @@ func CollectEthernetInterfaces(client *bmclib.Client, q *QueryParams, systemID s return nil, fmt.Errorf("could not marshal JSON: %v", err) } - // if q.Verbose { - // fmt.Printf("%v\n", string(b)) - // } return b, nil } -func CollectChassis(q *QueryParams) ([]byte, error) { - c, err := connectGofish(q) - if err != nil { - return nil, fmt.Errorf("could not connect to bmc (%v:%v): %v", q.Host, q.Port, err) - } +func CollectChassis(c *gofish.APIClient, q *QueryParams) ([]byte, error) { chassis, err := c.Service.Chassis() if err != nil { return nil, fmt.Errorf("could not query chassis (%v:%v): %v", q.Host, q.Port, err) @@ -494,18 +443,10 @@ func CollectChassis(q *QueryParams) ([]byte, error) { return nil, fmt.Errorf("could not marshal JSON: %v", err) } - if q.Verbose { - fmt.Printf("%v\n", string(b)) - } return b, nil } -func CollectStorage(q *QueryParams) ([]byte, error) { - c, err := connectGofish(q) - if err != nil { - return nil, fmt.Errorf("could not connect to bmc (%v:%v): %v", q.Host, q.Port, err) - } - +func CollectStorage(c *gofish.APIClient, q *QueryParams) ([]byte, error) { systems, err := c.Service.StorageSystems() if err != nil { return nil, fmt.Errorf("could not query storage systems (%v:%v): %v", q.Host, q.Port, err) @@ -518,7 +459,7 @@ func CollectStorage(q *QueryParams) ([]byte, error) { data := map[string]any{ "Storage": map[string]any{ - "Systems": systems, + "Systems": systems, "Services": services, }, } @@ -527,18 +468,10 @@ func CollectStorage(q *QueryParams) ([]byte, error) { return nil, fmt.Errorf("could not marshal JSON: %v", err) } - if q.Verbose { - fmt.Printf("%v\n", string(b)) - } return b, nil } -func CollectSystems(client *bmclib.Client, q *QueryParams) ([]byte, error) { - c, err := connectGofish(q) - if err != nil { - return nil, fmt.Errorf("could not connect to bmc (%v:%v): %v", q.Host, q.Port, err) - } - +func CollectSystems(c *gofish.APIClient, q *QueryParams) ([]byte, error) { systems, err := c.Service.Systems() if err != nil { return nil, fmt.Errorf("could not query systems (%v:%v): %v", q.Host, q.Port, err) @@ -547,50 +480,39 @@ func CollectSystems(client *bmclib.Client, q *QueryParams) ([]byte, error) { // query the system's ethernet interfaces var temp []map[string]any for _, system := range systems { - interfaces, err := CollectEthernetInterfaces(client, q, system.ID) + interfaces, err := CollectEthernetInterfaces(c, q, system.ID) if err != nil { continue } var i map[string]any json.Unmarshal(interfaces, &i) temp = append(temp, map[string]any{ - "Data": system, + "Data": system, "EthernetInterfaces": i["EthernetInterfaces"], }) } - data := map[string]any{"Systems": temp } + data := map[string]any{"Systems": temp} b, err := json.MarshalIndent(data, "", " ") if err != nil { return nil, fmt.Errorf("could not marshal JSON: %v", err) } - if q.Verbose { - fmt.Printf("%v\n", string(b)) - } return b, nil } -func CollectRegisteries(q *QueryParams) ([]byte, error) { - c, err := connectGofish(q) - if err != nil { - return nil, fmt.Errorf("could not connect to bmc (%v:%v): %v", q.Host, q.Port, err) - } - +func CollectRegisteries(c *gofish.APIClient, q *QueryParams) ([]byte, error) { registries, err := c.Service.Registries() if err != nil { return nil, fmt.Errorf("could not query storage systems (%v:%v): %v", q.Host, q.Port, err) } - data := map[string]any{"Registries": registries } + data := map[string]any{"Registries": registries} b, err := json.MarshalIndent(data, "", " ") if err != nil { return nil, fmt.Errorf("could not marshal JSON: %v", err) } - if q.Verbose { - fmt.Printf("%v\n", string(b)) - } return b, nil } @@ -625,45 +547,71 @@ func CollectProcessors(q *QueryParams) ([]byte, error) { } } - data := map[string]any{"Processors": procs } + data := map[string]any{"Processors": procs} b, err := json.MarshalIndent(data, "", " ") if err != nil { return nil, fmt.Errorf("could not marshal JSON: %v", err) } - if q.Verbose { - fmt.Printf("%v\n", string(b)) - } return b, nil } func connectGofish(q *QueryParams) (*gofish.APIClient, error) { - config := makeGofishConfig(q) + config, err := makeGofishConfig(q) + if err != nil { + return nil, fmt.Errorf("failed to make gofish config: %v", err) + } c, err := gofish.Connect(config) if err != nil { - return nil, fmt.Errorf("could not connect to redfish endpoint: %v", err) } if c != nil { c.Service.ProtocolFeaturesSupported = gofish.ProtocolFeaturesSupported{ ExpandQuery: gofish.Expand{ ExpandAll: true, - Links: true, + Links: true, }, } } return c, err } -func makeGofishConfig(q *QueryParams) gofish.ClientConfig { - url := baseRedfishUrl(q) +func makeGofishConfig(q *QueryParams) (gofish.ClientConfig, error) { + var ( + client = &http.Client{} + url = baseRedfishUrl(q) + config = gofish.ClientConfig{ + Endpoint: url, + Username: q.User, + Password: q.Pass, + Insecure: q.CaCertPath == "", + TLSHandshakeTimeout: q.Timeout, + HTTPClient: client, + // MaxConcurrentRequests: int64(q.Threads), // NOTE: this was added in latest gofish + } + ) + if q.CaCertPath != "" { + cacert, err := os.ReadFile(q.CaCertPath) + if err != nil { + return config, fmt.Errorf("failed to read CA cert file: %v", err) + } + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(cacert) + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: certPool, + }, + } + } return gofish.ClientConfig{ Endpoint: url, Username: q.User, Password: q.Pass, - Insecure: !q.WithSecureTLS, + Insecure: q.CaCertPath == "", TLSHandshakeTimeout: q.Timeout, - } + HTTPClient: client, + // MaxConcurrentRequests: int64(q.Threads), // NOTE: this was added in latest gofish + }, nil } func makeRequest[T any](client *bmclib.Client, fn func(context.Context) (T, error), timeout int) ([]byte, error) { diff --git a/internal/db/sqlite/sqlite.go b/internal/db/sqlite/sqlite.go index 7665f71..73f0376 100644 --- a/internal/db/sqlite/sqlite.go +++ b/internal/db/sqlite/sqlite.go @@ -3,7 +3,7 @@ package sqlite import ( "fmt" - magellan "github.com/bikeshack/magellan/internal" + magellan "github.com/OpenCHAMI/magellan/internal" "github.com/jmoiron/sqlx" ) diff --git a/internal/login.go b/internal/login.go new file mode 100644 index 0000000..508a987 --- /dev/null +++ b/internal/login.go @@ -0,0 +1,41 @@ +package magellan + +import ( + "fmt" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/pkg/browser" +) + +func Login(loginUrl string, targetHost string, targetPort int) (string, error) { + var accessToken string + + // check and make sure the login URL isn't empty + if loginUrl == "" { + return "", fmt.Errorf("no login URL provided") + } + + // if a target host and port are provided, then add to URL + if targetHost != "" && targetPort > 0 && targetPort < 65536 { + loginUrl += fmt.Sprintf("?target=http://%s:%d", targetHost, targetPort) + } + + // open browser with the specified URL + err := browser.OpenURL(loginUrl) + if err != nil { + return "", fmt.Errorf("failed to open browser: %v", err) + } + + // start a temporary server to listen for token + s := http.Server{ + Addr: fmt.Sprintf("%s:%d", targetHost, targetPort), + } + r := chi.NewRouter() + r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + // try and extract access token from headers + accessToken = r.Header.Get("access_token") + s.Close() + }) + return accessToken, s.ListenAndServe() +} diff --git a/internal/scan.go b/internal/scan.go index d30a51a..68e8f4f 100644 --- a/internal/scan.go +++ b/internal/scan.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/bikeshack/magellan/internal/util" + "github.com/OpenCHAMI/magellan/internal/util" ) type ScannedResult struct { @@ -51,7 +51,6 @@ func rawConnect(host string, ports []int, timeout int, keepOpenOnly bool) []Scan return results } - func GenerateHosts(subnet string, subnetMask *net.IP) []string { if subnet == "" || subnetMask == nil { return nil @@ -68,7 +67,7 @@ func GenerateHosts(subnet string, subnetMask *net.IP) []string { subnetIp = ip if network != nil { t := net.IP(network.Mask) - subnetMask = &t + subnetMask = &t } } @@ -82,7 +81,7 @@ func generateHosts(ip *net.IP, mask *net.IPMask) []string { // get all IP addresses in network ones, _ := mask.Size() hosts := []string{} - end := int(math.Pow(2, float64((32-ones))))-1 + end := int(math.Pow(2, float64((32-ones)))) - 1 for i := 0; i < end; i++ { // ip[3] = byte(i) ip = util.GetNextIP(ip, 1) diff --git a/internal/update.go b/internal/update.go index 312eeb8..ce5c544 100644 --- a/internal/update.go +++ b/internal/update.go @@ -10,30 +10,29 @@ import ( "strings" "time" - "github.com/bikeshack/magellan/internal/log" - "github.com/bikeshack/magellan/internal/util" + "github.com/OpenCHAMI/magellan/internal/log" + "github.com/OpenCHAMI/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 + FirmwarePath string + FirmwareVersion string + Component string TransferProtocol string } -// NOTE: Does not work since OpenBMC, whic bmclib uses underneath, does not +// 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) @@ -121,20 +120,20 @@ func UpdateFirmware(client *bmclib.Client, l *log.Logger, q *UpdateParams) error 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", + headers := map[string]string{ + "Content-Type": "application/json", "cache-control": "no-cache", } b := map[string]any{ - "UpdateComponent": q.Component, // BMC, BIOS + "UpdateComponent": q.Component, // BMC, BIOS "TransferProtocol": q.TransferProtocol, - "ImageURI": q.FirmwarePath, + "ImageURI": q.FirmwarePath, } data, err := json.Marshal(b) if err != nil { @@ -184,12 +183,10 @@ func GetUpdateStatus(q *UpdateParams) error { // 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/main.go b/main.go index 2cfda3a..ebe2d95 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/bikeshack/magellan/cmd" + "github.com/OpenCHAMI/magellan/cmd" ) func main() {