From 7b18615e5fbcefc65f0e0842ed36ff9dd05dcf51 Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 3 Sep 2024 16:16:35 -0600 Subject: [PATCH 01/34] Minor changes --- tests/compatibility_test.go | 47 ++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/tests/compatibility_test.go b/tests/compatibility_test.go index 86e3a4f..4941437 100644 --- a/tests/compatibility_test.go +++ b/tests/compatibility_test.go @@ -15,7 +15,6 @@ import ( "github.com/OpenCHAMI/magellan/pkg/client" "github.com/OpenCHAMI/magellan/pkg/crawler" - "github.com/rs/zerolog/log" ) var ( @@ -24,37 +23,42 @@ var ( password = flag.String("password", "", "set the BMC password used for the tests") ) -// Simple test to fetch the base Redfish URL and assert a 200 OK response. -func TestRedfishV1Availability(t *testing.T) { - var ( - url = fmt.Sprintf("%s/redfish/v1", *host) - body = []byte{} - headers = map[string]string{} - ) - res, b, err := client.MakeRequest(nil, url, http.MethodGet, body, headers) - if err != nil { - t.Fatalf("failed to make request to BMC: %v", err) - } - +func checkResponse(res *http.Response, b []byte) error { // test for a 200 response code here if res.StatusCode != http.StatusOK { - t.Fatalf("expected response code to return status code 200") + return fmt.Errorf("expected response code to return status code 200") } // make sure the response body is not empty if len(b) <= 0 { - t.Fatalf("expected response body to not be empty") + return fmt.Errorf("expected response body to not be empty") } - // make sure the response body is in a JSON format + // make sure the response body is in a valid JSON format if json.Valid(b) { - t.Fatalf("expected response body to be valid JSON") + return fmt.Errorf("expected response body to be valid JSON") } + return nil +} + +// Simple test to fetch the base Redfish URL and assert a 200 OK response. +func TestRedfishV1ServiceRootAvailability(t *testing.T) { + var ( + url = fmt.Sprintf("%s/redfish/v1", *host) + body = []byte{} + headers = map[string]string{} + ) + res, b, err := client.MakeRequest(nil, url, http.MethodGet, body, headers) + if err != nil { + t.Fatalf("failed to make request to BMC node: %w", err) + } + + err = checkResponse(res, b) } // Simple test to ensure an expected Redfish version minimum requirement. -func TestRedfishVersion(t *testing.T) { +func TestRedfishV1Version(t *testing.T) { var ( url string = fmt.Sprintf("%s/redfish/v1", *host) body client.HTTPBody = []byte{} @@ -62,17 +66,18 @@ func TestRedfishVersion(t *testing.T) { err error ) - _, _, err = client.MakeRequest(nil, url, http.MethodGet, body, headers) + res, b, err := client.MakeRequest(nil, url, http.MethodGet, body, headers) if err != nil { - log.Error().Err(err).Msg("failed to make request") + t.Fatalf("failed to make request to BMC node: %w", err) } + err = checkResponse(res, b) } // Crawls a BMC node and checks that we're able to query certain properties // that we need for Magellan to run correctly. This test differs from the // `TestCrawlCommand` testing function as it is not checking specifically // for functionality. -func TestExpectedProperties(t *testing.T) { +func TestExpectedOutput(t *testing.T) { // make sure what have a valid host if host == nil { t.Fatal("invalid host (host is nil)") From dd7bb5ec7743807f5142bed0d9f87db92fff3a09 Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 12 Sep 2024 10:57:50 -0600 Subject: [PATCH 02/34] Updated tests --- tests/api_test.go | 135 ++++++++++++++++++++++++++++++++---- tests/compatibility_test.go | 10 ++- 2 files changed, 129 insertions(+), 16 deletions(-) diff --git a/tests/api_test.go b/tests/api_test.go index c213451..20b397a 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -8,10 +8,14 @@ package tests import ( + "fmt" + "net" + "os/exec" "testing" + "flag" + magellan "github.com/OpenCHAMI/magellan/internal" - "github.com/rs/zerolog/log" ) var ( @@ -29,33 +33,114 @@ var ( DisableProbing: false, Verbose: false, } + exePath = flag.String("exe", "./magellan", "path to 'magellan' binary executable") + emuPath = flag.String("emu", "./emulator/setup.sh", "path to emulator 'setup.sh' script") ) +func runEmulator() {} + func TestScanAndCollect(t *testing.T) { - // do a scan on the emulator cluster with probing disabled and check results - results := magellan.ScanForAssets(scanParams) - if len(results) <= 0 { - t.Fatal("expected to find at least one BMC node, but found none") + var ( + err error + emuErr error + output []byte + tempDir = t.TempDir() + command string + ) + + // try and start the emulator in the background if arg passed + if *emuPath != "" { + t.Parallel() + t.Run("emulator", func(t *testing.T) { + _, emuErr = exec.Command("bash", "-c", *emuPath).CombinedOutput() + if emuErr != nil { + t.Fatalf("failed to start emulator: %v", emuErr) + } + }) } - // do a scan on the emulator cluster with probing enabled - results = magellan.ScanForAssets(scanParams) - if len(results) <= 0 { - t.Fatal("expected to find at least one BMC node, but found none") + + // try and run a "scan" with the emulator + command = fmt.Sprintf("%s scan --subnet 127.0.0.1 --subnet-mask 255.255.255.0 --cache %s", exePath, tempDir) + output, err = exec.Command("bash", "-c", command).CombinedOutput() + if err != nil { + t.Fatalf("failed to run 'scan' command: %v", err) + } + + // make sure that the expected output is not empty + if len(output) <= 0 { + t.Fatalf("expected the 'scan' output to not be empty") } - // do a collect on the emulator cluster to collect Redfish info - err := magellan.CollectInventory(&results, &magellan.CollectParams{}) + // try and run a "collect" with the emulator + command = fmt.Sprintf("%s collect --username root --password root_password --cache %s", exePath, tempDir) + output, err = exec.Command("bash", "-c", command).CombinedOutput() if err != nil { - log.Error().Err(err).Msg("failed to collect inventory") + t.Fatalf("failed to run 'collect' command: %v", err) } + + // make sure that the output is not empty + if len(output) <= 0 { + t.Fatalf("expected the 'collect' output to not be empty") + } + + // TODO: check for at least one System/EthernetInterface that we know should exist } func TestCrawlCommand(t *testing.T) { - // TODO: add test to check the crawl command's behavior + var ( + err error + emuErr error + output []byte + command string + ) + + // try and start the emulator in the background if arg passed + if *emuPath != "" { + t.Parallel() + t.Run("emulator", func(t *testing.T) { + _, emuErr = exec.Command("bash", "-c", *emuPath).CombinedOutput() + if emuErr != nil { + t.Fatalf("failed to start emulator: %v", emuErr) + } + }) + } + + // try and run a "collect" with the emulator + command = fmt.Sprintf("%s crawl --username root --password root_password -i", exePath) + output, err = exec.Command("bash", "-c", command).CombinedOutput() + if err != nil { + t.Fatalf("failed to run 'crawl' command: %v", err) + } + + // make sure that the output is not empty + if len(output) <= 0 { + t.Fatalf("expected the 'crawl' output to not be empty") + } + } func TestListCommand(t *testing.T) { - // TODO: add test to check the list command's output + // TODO: need magellan binary to test command + var ( + cmd *exec.Cmd + err error + output []byte + ) + + // set up the test + + // set up temporary directory + cmd = exec.Command("bash", "-c", fmt.Sprintf("%s list", *exePath)) + output, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("failed to run 'list' command: %v", err) + } + + // make sure that the output is not empty + if len(output) <= 0 { + t.Fatalf("expected the 'list' output to not be empty") + } + } func TestUpdateCommand(t *testing.T) { @@ -70,4 +155,26 @@ func TestGofishFunctions(t *testing.T) { func TestGenerateHosts(t *testing.T) { // TODO: add test to generate hosts using a collection of subnets/masks + t.Run("generate-hosts.1", func(t *testing.T) { + var ( + subnet = "172.16.0.0" + subnetMask = &net.IPMask{255, 255, 255, 0} + ports = []int{443} + scheme = "https" + hosts = [][]string{} + ) + hosts = magellan.GenerateHostsWithSubnet(subnet, subnetMask, ports, scheme) + }) + + t.Run("generate-hosts.2", func(t *testing.T) { + var ( + subnet = "127.0.0.1" + subnetMask = &net.IPMask{255, 255, 255, 0} + ports = []int{443, 5000} + scheme = "https" + hosts = [][]string{} + ) + hosts = magellan.GenerateHostsWithSubnet(subnet, subnetMask, ports, scheme) + }) + } diff --git a/tests/compatibility_test.go b/tests/compatibility_test.go index 4941437..7825a66 100644 --- a/tests/compatibility_test.go +++ b/tests/compatibility_test.go @@ -50,10 +50,13 @@ func TestRedfishV1ServiceRootAvailability(t *testing.T) { ) res, b, err := client.MakeRequest(nil, url, http.MethodGet, body, headers) if err != nil { - t.Fatalf("failed to make request to BMC node: %w", err) + t.Fatalf("failed to make request to BMC node: %v", err) } err = checkResponse(res, b) + if err != nil { + t.Fatalf("failed to check response for redfish service root: %v", err) + } } @@ -68,9 +71,12 @@ func TestRedfishV1Version(t *testing.T) { res, b, err := client.MakeRequest(nil, url, http.MethodGet, body, headers) if err != nil { - t.Fatalf("failed to make request to BMC node: %w", err) + t.Fatalf("failed to make request to BMC node: %v", err) } err = checkResponse(res, b) + if err != nil { + t.Fatalf("failed to check response for redfish version: %v", err) + } } // Crawls a BMC node and checks that we're able to query certain properties From 997b7710b8a8ab2b507ec242742cf57fbf6fae9b Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 12 Sep 2024 12:13:59 -0600 Subject: [PATCH 03/34] Changed test rule in Makefile to use specific tests --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3285e00..da3913a 100644 --- a/Makefile +++ b/Makefile @@ -83,7 +83,9 @@ lint: ## golangci-lint .PHONY: test test: ## go test $(call print-target) - go test -race -covermode=atomic -coverprofile=coverage.out -coverpkg=./... ./... + ./emulator/setup.sh & + sleep 10 + go test -race -covermode=atomic -coverprofile=coverage.out -coverpkg=./... tests/api_test.go tests/compatibility_test.go go tool cover -html=coverage.out -o coverage.html .PHONY: diff From 543efe913437c83d2cbd28458c313a96a6707565 Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 12 Sep 2024 12:14:37 -0600 Subject: [PATCH 04/34] Updated .gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 96c3c6e..6019d1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,11 @@ magellan emulator/rf-emulator **/*.db +<<<<<<< HEAD **.tar.gz **.tar.zst **.part +======= +dist/* +**coverage.out** +>>>>>>> 4b062dd (Updated .gitignore) From 7a04afdfa850d472ae7e2184f80a9e40c2eda20f Mon Sep 17 00:00:00 2001 From: David Allen Date: Mon, 16 Sep 2024 08:54:29 -0600 Subject: [PATCH 05/34] Added API tests --- tests/api_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/api_test.go b/tests/api_test.go index 20b397a..8d10489 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -146,6 +146,17 @@ func TestListCommand(t *testing.T) { func TestUpdateCommand(t *testing.T) { // TODO: add test that does a Redfish simple update checking it success and // failure points + var ( + cmd *exec.Cmd + err error + output []byte + ) + // set up temporary directory + cmd = exec.Command("bash", "-c", fmt.Sprintf("%s list", *exePath)) + output, err = cmd.CombinedOutput() + if err != nil { + t.Fatalf("failed to run 'list' command: %v", err) + } } func TestGofishFunctions(t *testing.T) { From 879cfac61ed88b48a6d8ce83050740c9c2637433 Mon Sep 17 00:00:00 2001 From: David Allen Date: Tue, 17 Sep 2024 14:11:10 -0600 Subject: [PATCH 06/34] Added function to wait for emulator to start --- tests/api_test.go | 103 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 86 insertions(+), 17 deletions(-) diff --git a/tests/api_test.go b/tests/api_test.go index 8d10489..b3a8ebf 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -10,12 +10,16 @@ package tests import ( "fmt" "net" + "net/http" "os/exec" "testing" + "time" "flag" magellan "github.com/OpenCHAMI/magellan/internal" + "github.com/OpenCHAMI/magellan/internal/util" + "github.com/OpenCHAMI/magellan/pkg/client" ) var ( @@ -94,6 +98,12 @@ func TestCrawlCommand(t *testing.T) { command string ) + // set up the emulator to run before test + err = waitUntilEmulatorIsReady() + if err != nil { + t.Fatalf("failed to start emulator: %v", err) + } + // try and start the emulator in the background if arg passed if *emuPath != "" { t.Parallel() @@ -127,7 +137,11 @@ func TestListCommand(t *testing.T) { output []byte ) - // set up the test + // set up the emulator to run before test + err = waitUntilEmulatorIsReady() + if err != nil { + t.Fatalf("failed to start emulator: %v", err) + } // set up temporary directory cmd = exec.Command("bash", "-c", fmt.Sprintf("%s list", *exePath)) @@ -151,12 +165,24 @@ func TestUpdateCommand(t *testing.T) { err error output []byte ) + + // set up the emulator to run before test + err = waitUntilEmulatorIsReady() + if err != nil { + t.Fatalf("failed to start emulator: %v", err) + } + // set up temporary directory cmd = exec.Command("bash", "-c", fmt.Sprintf("%s list", *exePath)) output, err = cmd.CombinedOutput() if err != nil { t.Fatalf("failed to run 'list' command: %v", err) } + + // make sure that the output is not empty + if len(output) <= 0 { + t.Fatalf("expected the 'list' output to not be empty") + } } func TestGofishFunctions(t *testing.T) { @@ -164,28 +190,71 @@ func TestGofishFunctions(t *testing.T) { // gofish's output isn't changing spontaneously and remains predictable } +// TestGenerateHosts() tests creating a collection of hosts by changing arguments +// and calling GenerateHostsWithSubnet(). func TestGenerateHosts(t *testing.T) { // TODO: add test to generate hosts using a collection of subnets/masks - t.Run("generate-hosts.1", func(t *testing.T) { - var ( - subnet = "172.16.0.0" - subnetMask = &net.IPMask{255, 255, 255, 0} - ports = []int{443} - scheme = "https" - hosts = [][]string{} - ) + var ( + subnet = "127.0.0.1" + subnetMask = &net.IPMask{255, 255, 255, 0} + ports = []int{443} + scheme = "https" + hosts = [][]string{} + ) + t.Run("generate-hosts", func(t *testing.T) { hosts = magellan.GenerateHostsWithSubnet(subnet, subnetMask, ports, scheme) + + // check for at least one host to be generated + if len(hosts) <= 0 { + t.Fatalf("expected at least one host to be generated for subnet %s", subnet) + } }) - t.Run("generate-hosts.2", func(t *testing.T) { - var ( - subnet = "127.0.0.1" - subnetMask = &net.IPMask{255, 255, 255, 0} - ports = []int{443, 5000} - scheme = "https" - hosts = [][]string{} - ) + t.Run("generate-hosts-with-multiple-ports", func(t *testing.T) { + ports = []int{443, 5000} hosts = magellan.GenerateHostsWithSubnet(subnet, subnetMask, ports, scheme) + + // check for at least one host to be generated + if len(hosts) <= 0 { + t.Fatalf("expected at least one host to be generated for subnet %s", subnet) + } }) + t.Run("generate-hosts-with-subnet-mask", func(t *testing.T) { + subnetMask = &net.IPMask{255, 255, 125, 0} + hosts = magellan.GenerateHostsWithSubnet(subnet, subnetMask, ports, scheme) + + // check for at least one host to be generated + if len(hosts) <= 0 { + t.Fatalf("expected at least one host to be generated for subnet %s", subnet) + } + }) + +} + +// waitUntilEmulatorIsReady() polls with +func waitUntilEmulatorIsReady() error { + var ( + interval = time.Second * 5 + timeout = time.Second * 60 + testClient = &http.Client{} + body client.HTTPBody + header client.HTTPHeader + err error + ) + err = util.CheckUntil(interval, timeout, func() (bool, error) { + // send request to host until we get expected response + res, _, err := client.MakeRequest(testClient, "http://127.0.0.1", http.MethodPost, body, header) + if err != nil { + return false, fmt.Errorf("failed to start emulator: %w", err) + } + if res == nil { + return false, fmt.Errorf("response returned nil") + } + if res.StatusCode == http.StatusOK { + return true, nil + } + return false, nil + }) + return err } From affba7dfa30068f5f948f551255c2a48b850c71a Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 19 Sep 2024 16:38:46 -0600 Subject: [PATCH 07/34] Added CheckUntil() for tests --- internal/util/util.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 internal/util/util.go diff --git a/internal/util/util.go b/internal/util/util.go new file mode 100644 index 0000000..3edeeff --- /dev/null +++ b/internal/util/util.go @@ -0,0 +1,27 @@ +package util + +import ( + "fmt" + "time" +) + +// CheckUntil regularly check a predicate until it's true or time out is reached. +func CheckUntil(interval time.Duration, timeout time.Duration, predicate func() (bool, error)) error { + timeoutCh := time.After(timeout) + + for { + select { + case <-time.After(interval): + predTrue, err := predicate() + if predTrue { + return nil + } + + if err != nil { + return err + } + case <-timeoutCh: + return fmt.Errorf("timeout of %ds reached", int64(timeout/time.Second)) + } + } +} From 18d5ef10b034a8ab8d3a11fac130ac6f00a3c56a Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Fri, 20 Sep 2024 16:40:31 -0600 Subject: [PATCH 08/34] Updated tests fixing some issues --- tests/api_test.go | 204 +++++++++++++++++++++--------------- tests/compatibility_test.go | 66 +++++++++--- 2 files changed, 169 insertions(+), 101 deletions(-) diff --git a/tests/api_test.go b/tests/api_test.go index b3a8ebf..bc4bf1c 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -8,9 +8,12 @@ package tests import ( + "bytes" + "crypto/tls" "fmt" "net" "net/http" + "os" "os/exec" "testing" "time" @@ -20,70 +23,72 @@ import ( magellan "github.com/OpenCHAMI/magellan/internal" "github.com/OpenCHAMI/magellan/internal/util" "github.com/OpenCHAMI/magellan/pkg/client" + "github.com/rs/zerolog/log" ) var ( - scanParams = &magellan.ScanParams{ - TargetHosts: [][]string{ - []string{ - "http://127.0.0.1:443", - "http://127.0.0.1:5000", - }, - }, - Scheme: "https", - Protocol: "tcp", - Concurrency: 1, - Timeout: 30, - DisableProbing: false, - Verbose: false, - } - exePath = flag.String("exe", "./magellan", "path to 'magellan' binary executable") + exePath = flag.String("exe", "../magellan", "path to 'magellan' binary executable") emuPath = flag.String("emu", "./emulator/setup.sh", "path to emulator 'setup.sh' script") ) -func runEmulator() {} - func TestScanAndCollect(t *testing.T) { var ( err error - emuErr error - output []byte tempDir = t.TempDir() command string + cwd string + cmd *exec.Cmd + buf bytes.Buffer ) - // try and start the emulator in the background if arg passed - if *emuPath != "" { - t.Parallel() - t.Run("emulator", func(t *testing.T) { - _, emuErr = exec.Command("bash", "-c", *emuPath).CombinedOutput() - if emuErr != nil { - t.Fatalf("failed to start emulator: %v", emuErr) - } - }) + // set up the emulator to run before test + err = waitUntilEmulatorIsReady() + if err != nil { + t.Fatalf("failed while waiting for emulator: %v", err) } + // get the current working directory and print + cwd, err = os.Getwd() + if err != nil { + t.Fatalf("failed to get working directory: %v", err) + } + fmt.Printf("cwd: %s\n", cwd) + + // path, err := exec.LookPath("dexdump") + // if err != nil { + // log.Fatal(err) + // } + // try and run a "scan" with the emulator - command = fmt.Sprintf("%s scan --subnet 127.0.0.1 --subnet-mask 255.255.255.0 --cache %s", exePath, tempDir) - output, err = exec.Command("bash", "-c", command).CombinedOutput() + command = fmt.Sprintf("%s scan https://172.23.0.2 --port 5000 --cache %s", *exePath, tempDir) + cmd = exec.Command("bash", "-c", command) + cmd.Stdout = &buf + err = cmd.Run() if err != nil { t.Fatalf("failed to run 'scan' command: %v", err) } // make sure that the expected output is not empty - if len(output) <= 0 { + if len(buf.Bytes()) <= 0 { t.Fatalf("expected the 'scan' output to not be empty") } // try and run a "collect" with the emulator - command = fmt.Sprintf("%s collect --username root --password root_password --cache %s", exePath, tempDir) - output, err = exec.Command("bash", "-c", command).CombinedOutput() + command = fmt.Sprintf("%s collect --username root --password root_password --cache %s", *exePath, tempDir) + cmd = exec.Command("bash", "-c", command) + cmd.Stdout = &buf + err = cmd.Start() if err != nil { t.Fatalf("failed to run 'collect' command: %v", err) } + err = cmd.Wait() + if err != nil { + t.Fatalf("failed to call 'wait' for scan: %v", err) + } + // make sure that the output is not empty - if len(output) <= 0 { + if len(buf.Bytes()) <= 0 { t.Fatalf("expected the 'collect' output to not be empty") } @@ -93,67 +98,57 @@ func TestScanAndCollect(t *testing.T) { func TestCrawlCommand(t *testing.T) { var ( err error - emuErr error - output []byte command string + cmd *exec.Cmd + buf bytes.Buffer ) // set up the emulator to run before test err = waitUntilEmulatorIsReady() if err != nil { - t.Fatalf("failed to start emulator: %v", err) - } - - // try and start the emulator in the background if arg passed - if *emuPath != "" { - t.Parallel() - t.Run("emulator", func(t *testing.T) { - _, emuErr = exec.Command("bash", "-c", *emuPath).CombinedOutput() - if emuErr != nil { - t.Fatalf("failed to start emulator: %v", emuErr) - } - }) + t.Fatalf("failed while waiting for emulator: %v", err) } // try and run a "collect" with the emulator - command = fmt.Sprintf("%s crawl --username root --password root_password -i", exePath) - output, err = exec.Command("bash", "-c", command).CombinedOutput() + command = fmt.Sprintf("%s crawl --username root --password root_password -i", *exePath) + cmd = exec.Command("bash", "-c", command) + cmd.Stdout = &buf + err = cmd.Start() if err != nil { t.Fatalf("failed to run 'crawl' command: %v", err) } + err = cmd.Wait() + if err != nil { + t.Fatalf("failed to call 'wait' for crawl: %v", err) + } + // make sure that the output is not empty - if len(output) <= 0 { + if len(buf.Bytes()) <= 0 { t.Fatalf("expected the 'crawl' output to not be empty") } } func TestListCommand(t *testing.T) { - // TODO: need magellan binary to test command var ( - cmd *exec.Cmd - err error - output []byte + err error + cmd *exec.Cmd ) // set up the emulator to run before test err = waitUntilEmulatorIsReady() if err != nil { - t.Fatalf("failed to start emulator: %v", err) + t.Fatalf("failed while waiting for emulator: %v", err) } // set up temporary directory cmd = exec.Command("bash", "-c", fmt.Sprintf("%s list", *exePath)) - output, err = cmd.CombinedOutput() + err = cmd.Start() if err != nil { t.Fatalf("failed to run 'list' command: %v", err) } - - // make sure that the output is not empty - if len(output) <= 0 { - t.Fatalf("expected the 'list' output to not be empty") - } + // NOTE: the output of `list` can be empty if no scan has been performed } @@ -161,28 +156,23 @@ func TestUpdateCommand(t *testing.T) { // TODO: add test that does a Redfish simple update checking it success and // failure points var ( - cmd *exec.Cmd - err error - output []byte + cmd *exec.Cmd + err error ) // set up the emulator to run before test err = waitUntilEmulatorIsReady() if err != nil { - t.Fatalf("failed to start emulator: %v", err) + t.Fatalf("failed while waiting for emulator: %v", err) } // set up temporary directory - cmd = exec.Command("bash", "-c", fmt.Sprintf("%s list", *exePath)) - output, err = cmd.CombinedOutput() + cmd = exec.Command("bash", "-c", fmt.Sprintf("%s update", *exePath)) + err = cmd.Start() if err != nil { - t.Fatalf("failed to run 'list' command: %v", err) + t.Fatalf("failed to run 'update' command: %v", err) } - // make sure that the output is not empty - if len(output) <= 0 { - t.Fatalf("expected the 'list' output to not be empty") - } } func TestGofishFunctions(t *testing.T) { @@ -193,9 +183,8 @@ func TestGofishFunctions(t *testing.T) { // TestGenerateHosts() tests creating a collection of hosts by changing arguments // and calling GenerateHostsWithSubnet(). func TestGenerateHosts(t *testing.T) { - // TODO: add test to generate hosts using a collection of subnets/masks var ( - subnet = "127.0.0.1" + subnet = "172.23.0.0" subnetMask = &net.IPMask{255, 255, 255, 0} ports = []int{443} scheme = "https" @@ -221,7 +210,7 @@ func TestGenerateHosts(t *testing.T) { }) t.Run("generate-hosts-with-subnet-mask", func(t *testing.T) { - subnetMask = &net.IPMask{255, 255, 125, 0} + subnetMask = &net.IPMask{255, 255, 0, 0} hosts = magellan.GenerateHostsWithSubnet(subnet, subnetMask, ports, scheme) // check for at least one host to be generated @@ -232,29 +221,74 @@ func TestGenerateHosts(t *testing.T) { } +func startEmulatorInBackground(path string) (int, error) { + // try and start the emulator in the background if arg passed + var ( + cmd *exec.Cmd + err error + ) + if path != "" { + cmd = exec.Command("bash", "-c", path) + err = cmd.Start() + if err != nil { + return -1, fmt.Errorf("failed while executing emulator startup script: %v", err) + } + } else { + return -1, fmt.Errorf("path to emulator start up script is required") + } + return cmd.Process.Pid, nil +} + // waitUntilEmulatorIsReady() polls with func waitUntilEmulatorIsReady() error { var ( interval = time.Second * 5 - timeout = time.Second * 60 - testClient = &http.Client{} - body client.HTTPBody - header client.HTTPHeader - err error + timeout = time.Second * 15 + testClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + body client.HTTPBody + header client.HTTPHeader + err error ) err = util.CheckUntil(interval, timeout, func() (bool, error) { // send request to host until we get expected response - res, _, err := client.MakeRequest(testClient, "http://127.0.0.1", http.MethodPost, body, header) + res, _, err := client.MakeRequest(testClient, "https://172.23.0.2:5000/redfish/v1/", http.MethodGet, body, header) if err != nil { - return false, fmt.Errorf("failed to start emulator: %w", err) + return false, fmt.Errorf("failed to make request to emulator: %w", err) } if res == nil { - return false, fmt.Errorf("response returned nil") + return false, fmt.Errorf("invalid response from emulator (response is nil)") } if res.StatusCode == http.StatusOK { return true, nil + } else { + return false, fmt.Errorf("unexpected status code %d", res.StatusCode) } - return false, nil + }) return err } + +func init() { + var ( + cwd string + err error + ) + // get the current working directory + cwd, err = os.Getwd() + if err != nil { + log.Error().Err(err).Msg("failed to get working directory") + } + fmt.Printf("cwd: %s\n", cwd) + + // start emulator in the background before running tests + pid, err := startEmulatorInBackground(*emuPath) + if err != nil { + log.Error().Err(err).Msg("failed to start emulator in background") + os.Exit(1) + } + _ = pid +} diff --git a/tests/compatibility_test.go b/tests/compatibility_test.go index 7825a66..0e220f2 100644 --- a/tests/compatibility_test.go +++ b/tests/compatibility_test.go @@ -7,6 +7,7 @@ package tests import ( + "crypto/tls" "encoding/json" "flag" "fmt" @@ -18,9 +19,9 @@ import ( ) var ( - host = flag.String("host", "localhost", "set the BMC host") - username = flag.String("username", "", "set the BMC username used for the tests") - password = flag.String("password", "", "set the BMC password used for the tests") + host = flag.String("host", "https://172.23.0.2:5000", "set the BMC host") + username = flag.String("username", "root", "set the BMC username used for the tests") + password = flag.String("password", "root_password", "set the BMC password used for the tests") ) func checkResponse(res *http.Response, b []byte) error { @@ -35,7 +36,7 @@ func checkResponse(res *http.Response, b []byte) error { } // make sure the response body is in a valid JSON format - if json.Valid(b) { + if !json.Valid(b) { return fmt.Errorf("expected response body to be valid JSON") } return nil @@ -44,11 +45,24 @@ func checkResponse(res *http.Response, b []byte) error { // Simple test to fetch the base Redfish URL and assert a 200 OK response. func TestRedfishV1ServiceRootAvailability(t *testing.T) { var ( - url = fmt.Sprintf("%s/redfish/v1", *host) - body = []byte{} - headers = map[string]string{} + url = fmt.Sprintf("%s/redfish/v1/", *host) + body = []byte{} + headers = map[string]string{} + testClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + err error ) - res, b, err := client.MakeRequest(nil, url, http.MethodGet, body, headers) + + // set up the emulator to run before test + err = waitUntilEmulatorIsReady() + if err != nil { + t.Fatalf("failed while waiting for emulator: %v", err) + } + + res, b, err := client.MakeRequest(testClient, url, http.MethodGet, body, headers) if err != nil { t.Fatalf("failed to make request to BMC node: %v", err) } @@ -63,13 +77,19 @@ func TestRedfishV1ServiceRootAvailability(t *testing.T) { // Simple test to ensure an expected Redfish version minimum requirement. func TestRedfishV1Version(t *testing.T) { var ( - url string = fmt.Sprintf("%s/redfish/v1", *host) - body client.HTTPBody = []byte{} - headers client.HTTPHeader = map[string]string{} - err error + url string = fmt.Sprintf("%s/redfish/v1/", *host) + body client.HTTPBody = []byte{} + headers client.HTTPHeader = map[string]string{} + testClient = &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + root map[string]any + err error ) - res, b, err := client.MakeRequest(nil, url, http.MethodGet, body, headers) + res, b, err := client.MakeRequest(testClient, url, http.MethodGet, body, headers) if err != nil { t.Fatalf("failed to make request to BMC node: %v", err) } @@ -77,6 +97,17 @@ func TestRedfishV1Version(t *testing.T) { if err != nil { t.Fatalf("failed to check response for redfish version: %v", err) } + + // check the "RedfishVersion" from service root + err = json.Unmarshal(b, &root) + if err != nil { + t.Fatalf("failed to unmarshal redfish response: %v", err) + } + + _, ok := root["RedfishVersion"] + if !ok { + t.Fatalf("failed to get 'RedfishVersion' from service root") + } } // Crawls a BMC node and checks that we're able to query certain properties @@ -89,6 +120,12 @@ func TestExpectedOutput(t *testing.T) { t.Fatal("invalid host (host is nil)") } + // set up the emulator to run before test + err := waitUntilEmulatorIsReady() + if err != nil { + t.Fatalf("failed while waiting for emulator: %v", err) + } + systems, err := crawler.CrawlBMC( crawler.CrawlerConfig{ URI: *host, @@ -117,8 +154,5 @@ func TestExpectedOutput(t *testing.T) { if len(system.EthernetInterfaces) <= 0 { t.Errorf("no ethernet interfaces found for system '%s'", system.Name) } - if len(system.NetworkInterfaces) <= 0 { - t.Errorf("no network interfaces found for system '%s'", system.Name) - } } } From 61c65e5419125ace02b7063a55189bd1aa96e522 Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 26 Sep 2024 08:30:15 -0600 Subject: [PATCH 09/34] Changed IP address to use local --- tests/api_test.go | 6 +++--- tests/compatibility_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/api_test.go b/tests/api_test.go index bc4bf1c..abeb78b 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -60,7 +60,7 @@ func TestScanAndCollect(t *testing.T) { // } // try and run a "scan" with the emulator - command = fmt.Sprintf("%s scan https://172.23.0.2 --port 5000 --cache %s", *exePath, tempDir) + command = fmt.Sprintf("%s scan https://127.0.0.1 --port 5000 --cache %s", *exePath, tempDir) cmd = exec.Command("bash", "-c", command) cmd.Stdout = &buf err = cmd.Run() @@ -184,7 +184,7 @@ func TestGofishFunctions(t *testing.T) { // and calling GenerateHostsWithSubnet(). func TestGenerateHosts(t *testing.T) { var ( - subnet = "172.23.0.0" + subnet = "127.0.0.1" subnetMask = &net.IPMask{255, 255, 255, 0} ports = []int{443} scheme = "https" @@ -255,7 +255,7 @@ func waitUntilEmulatorIsReady() error { ) err = util.CheckUntil(interval, timeout, func() (bool, error) { // send request to host until we get expected response - res, _, err := client.MakeRequest(testClient, "https://172.23.0.2:5000/redfish/v1/", http.MethodGet, body, header) + res, _, err := client.MakeRequest(testClient, "https://127.0.0.1:5000/redfish/v1/", http.MethodGet, body, header) if err != nil { return false, fmt.Errorf("failed to make request to emulator: %w", err) } diff --git a/tests/compatibility_test.go b/tests/compatibility_test.go index 0e220f2..03917ec 100644 --- a/tests/compatibility_test.go +++ b/tests/compatibility_test.go @@ -19,7 +19,7 @@ import ( ) var ( - host = flag.String("host", "https://172.23.0.2:5000", "set the BMC host") + host = flag.String("host", "https://127.0.0.1:5000", "set the BMC host") username = flag.String("username", "root", "set the BMC username used for the tests") password = flag.String("password", "root_password", "set the BMC password used for the tests") ) From c07ea3c8eb04cf7522063d74fef4fd7bbf16f0ca Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 26 Sep 2024 10:07:00 -0600 Subject: [PATCH 10/34] Updated go deps --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index e00981d..e153625 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,6 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - ) require ( From 8334b0e4cadd1cdaae64ba1ed0559423fc8a3c0d Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 26 Sep 2024 10:07:12 -0600 Subject: [PATCH 11/34] Fixed issue with test not working --- tests/api_test.go | 80 +++++++++++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 31 deletions(-) diff --git a/tests/api_test.go b/tests/api_test.go index abeb78b..e823bfa 100644 --- a/tests/api_test.go +++ b/tests/api_test.go @@ -15,6 +15,8 @@ import ( "net/http" "os" "os/exec" + "path/filepath" + "strings" "testing" "time" @@ -33,12 +35,14 @@ var ( func TestScanAndCollect(t *testing.T) { var ( - err error - tempDir = t.TempDir() - command string + err error + // tempDir = t.TempDir() + path string + command []string cwd string cmd *exec.Cmd - buf bytes.Buffer + bufout bytes.Buffer + buferr bytes.Buffer ) // set up the emulator to run before test @@ -60,35 +64,40 @@ func TestScanAndCollect(t *testing.T) { // } // try and run a "scan" with the emulator - command = fmt.Sprintf("%s scan https://127.0.0.1 --port 5000 --cache %s", *exePath, tempDir) - cmd = exec.Command("bash", "-c", command) - cmd.Stdout = &buf + // set up the emulator to run before test + path, err = filepath.Abs(*exePath) + if err != nil { + t.Fatalf("failed to get absolute path: %v", err) + } + command = strings.Split("scan https://127.0.0.1 --port 5000 --verbose", " ") + cmd = exec.Command(path, command...) + cmd.Stdout = &bufout + cmd.Stderr = &buferr err = cmd.Run() + fmt.Printf("out:\n%s\nerr:\n%s\n", bufout.String(), buferr.String()) if err != nil { t.Fatalf("failed to run 'scan' command: %v", err) } // make sure that the expected output is not empty - if len(buf.Bytes()) <= 0 { + if len(buferr.Bytes()) <= 0 { t.Fatalf("expected the 'scan' output to not be empty") } // try and run a "collect" with the emulator - command = fmt.Sprintf("%s collect --username root --password root_password --cache %s", *exePath, tempDir) - cmd = exec.Command("bash", "-c", command) - cmd.Stdout = &buf - err = cmd.Start() - if err != nil { - t.Fatalf("failed to run 'collect' command: %v", err) - } - err = cmd.Wait() + command = strings.Split("collect --username root --password root_password --verbose", " ") + cmd = exec.Command(path, command...) + cmd.Stdout = &bufout + cmd.Stderr = &buferr + err = cmd.Run() + fmt.Printf("out:\n%s\nerr:\n%s\n", bufout.String(), buferr.String()) if err != nil { - t.Fatalf("failed to call 'wait' for scan: %v", err) + t.Fatalf("failed to run 'collect' command: %v", err) } // make sure that the output is not empty - if len(buf.Bytes()) <= 0 { + if len(bufout.Bytes()) <= 0 { t.Fatalf("expected the 'collect' output to not be empty") } @@ -98,33 +107,42 @@ func TestScanAndCollect(t *testing.T) { func TestCrawlCommand(t *testing.T) { var ( err error - command string + command []string cmd *exec.Cmd - buf bytes.Buffer + bufout bytes.Buffer + buferr bytes.Buffer + path string ) // set up the emulator to run before test + path, err = filepath.Abs(*exePath) + if err != nil { + t.Fatalf("failed to get absolute path: %v", err) + } + fmt.Printf("path: %s\n", path) err = waitUntilEmulatorIsReady() if err != nil { t.Fatalf("failed while waiting for emulator: %v", err) } // try and run a "collect" with the emulator - command = fmt.Sprintf("%s crawl --username root --password root_password -i", *exePath) - cmd = exec.Command("bash", "-c", command) - cmd.Stdout = &buf - err = cmd.Start() + command = strings.Split("crawl --username root --password root_password -i https://127.0.0.1:5000", " ") + cmd = exec.Command(path, command...) + cmd.Stdout = &bufout + cmd.Stderr = &buferr + err = cmd.Run() + fmt.Printf("out:\n%s\nerr:\n%s\n", bufout.String(), buferr.String()) if err != nil { t.Fatalf("failed to run 'crawl' command: %v", err) } - err = cmd.Wait() - if err != nil { - t.Fatalf("failed to call 'wait' for crawl: %v", err) - } + // err = cmd.Wait() + // if err != nil { + // t.Fatalf("failed to call 'wait' for crawl: %v", err) + // } // make sure that the output is not empty - if len(buf.Bytes()) <= 0 { + if len(bufout.Bytes()) <= 0 { t.Fatalf("expected the 'crawl' output to not be empty") } @@ -242,8 +260,8 @@ func startEmulatorInBackground(path string) (int, error) { // waitUntilEmulatorIsReady() polls with func waitUntilEmulatorIsReady() error { var ( - interval = time.Second * 5 - timeout = time.Second * 15 + interval = time.Second * 2 + timeout = time.Second * 6 testClient = &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, From 26989d1a1880d02e1815354e0d02a1641eacac7e Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 26 Sep 2024 10:49:48 -0600 Subject: [PATCH 12/34] Fixed releaser .PHONY in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index da3913a..75f7be9 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ inst: ## go install tools go install github.com/goreleaser/goreleaser@v1.18.2 go install github.com/cpuguy83/go-md2man/v2@latest -.PHONY: goreleaser +.PHONY: releaser release: ## goreleaser build $(call print-target) $(GOPATH)/bin/goreleaser build --clean --single-target --snapshot From 38da4a20ba2c5990e6e5de81d686d10c05ed2b5d Mon Sep 17 00:00:00 2001 From: David Allen Date: Thu, 26 Sep 2024 10:51:05 -0600 Subject: [PATCH 13/34] Fixed typo in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 75f7be9..dd7f9b8 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,7 @@ inst: ## go install tools go install github.com/goreleaser/goreleaser@v1.18.2 go install github.com/cpuguy83/go-md2man/v2@latest -.PHONY: releaser +.PHONY: release release: ## goreleaser build $(call print-target) $(GOPATH)/bin/goreleaser build --clean --single-target --snapshot From 6212114948ffc1673e80fb1b5382cbca0e3f03d6 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Thu, 26 Sep 2024 10:53:58 -0600 Subject: [PATCH 14/34] .gitignore: Delete rebase delineators --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 6019d1b..45e95ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,8 @@ magellan emulator/rf-emulator **/*.db -<<<<<<< HEAD **.tar.gz **.tar.zst **.part -======= dist/* **coverage.out** ->>>>>>> 4b062dd (Updated .gitignore) From 71fe229452331b016c80fc0e72bde4f88d2ffa83 Mon Sep 17 00:00:00 2001 From: Devon Bautista Date: Thu, 26 Sep 2024 10:55:52 -0600 Subject: [PATCH 15/34] Makefile: Update goreleaser to v2 (v2.3.2) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dd7f9b8..bb8fa18 100644 --- a/Makefile +++ b/Makefile @@ -52,7 +52,7 @@ inst: ## go install tools $(call print-target) go install github.com/client9/misspell/cmd/misspell@v0.3.4 go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.52.2 - go install github.com/goreleaser/goreleaser@v1.18.2 + go install github.com/goreleaser/goreleaser/v2@v2.3.2 go install github.com/cpuguy83/go-md2man/v2@latest .PHONY: release From e1d49e17a1b3df4b5998d44dca33571823e25757 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 2 Oct 2024 15:12:47 -0600 Subject: [PATCH 16/34] goreleaser: fixed hook to output correct filename --- .goreleaser.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 56af355..b202123 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,7 +4,7 @@ before: hooks: - go mod download - go install github.com/cpuguy83/go-md2man/v2@latest - - go-md2man -in README.md -out manpage.1 + - go-md2man -in README.md -out magellan.1 builds: - env: From e1cc0bfd38cb4fcf1ff4117e13b41142da58a416 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 15 Oct 2024 13:51:38 -0600 Subject: [PATCH 17/34] crawler: add function to fetch manager data --- pkg/crawler/main.go | 94 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 87 insertions(+), 7 deletions(-) diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index e28cb1b..e90593a 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -41,6 +41,17 @@ type NetworkInterface struct { Adapter NetworkAdapter `json:"adapter,omitempty"` // Adapter of the interface } +type Manager struct { + URI string `json:"uri,omitempty"` + UUID string `json:"uuid,omitempty"` + Name string `json:"name,omitempty"` + Description string `json:"description,omitempty"` + Model string `json:"model,omitempty"` + Type string `json:"type,omitempty"` + FirmwareVersion string `json:"firmware_version,omitempty"` + EthernetInterfaces []EthernetInterface `json:"ethernet_interfaces,omitempty"` +} + type InventoryDetail struct { URI string `json:"uri,omitempty"` // URI of the BMC UUID string `json:"uuid,omitempty"` // UUID of Node @@ -65,9 +76,12 @@ type InventoryDetail struct { Chassis_Model string `json:"chassis_model,omitempty"` // Model of the Chassis } -// CrawlBMC pulls all pertinent information from a BMC. It accepts a CrawlerConfig and returns a list of InventoryDetail structs. -func CrawlBMC(config CrawlerConfig) ([]InventoryDetail, error) { - var systems []InventoryDetail +// CrawlBMCForSystems pulls all pertinent information from a BMC. It accepts a CrawlerConfig and returns a list of InventoryDetail structs. +func CrawlBMCForSystems(config CrawlerConfig) ([]InventoryDetail, error) { + var ( + systems []InventoryDetail + rf_systems []*redfish.ComputerSystem + ) // initialize gofish client client, err := gofish.Connect(gofish.ClientConfig{ Endpoint: config.URI, @@ -94,8 +108,6 @@ func CrawlBMC(config CrawlerConfig) ([]InventoryDetail, error) { rf_service := client.GetService() log.Info().Msgf("found ServiceRoot %s. Redfish Version %s", rf_service.ID, rf_service.RedfishVersion) - var rf_systems []*redfish.ComputerSystem - // Nodes are sometimes only found under Chassis, but they should be found under Systems. rf_chassis, err := rf_service.Chassis() if err == nil { @@ -114,8 +126,43 @@ func CrawlBMC(config CrawlerConfig) ([]InventoryDetail, error) { } log.Info().Msgf("found %d systems in ServiceRoot", len(rf_root_systems)) rf_systems = append(rf_systems, rf_root_systems...) - systems, err = walkSystems(rf_systems, nil, config.URI) - return systems, err + return walkSystems(rf_systems, nil, config.URI) +} + +// CrawlBMCForSystems pulls BMC manager information. +func CrawlBMCForManagers(config CrawlerConfig) ([]Manager, error) { + // initialize gofish client + var managers []Manager + client, err := gofish.Connect(gofish.ClientConfig{ + Endpoint: config.URI, + Username: config.Username, + Password: config.Password, + Insecure: config.Insecure, + BasicAuth: true, + }) + if err != nil { + if strings.HasPrefix(err.Error(), "404:") { + err = fmt.Errorf("no ServiceRoot found. This is probably not a BMC: %s", config.URI) + } + if strings.HasPrefix(err.Error(), "401:") { + err = fmt.Errorf("authentication failed. Check your username and password: %s", config.URI) + } + event := log.Error() + event.Err(err) + event.Msg("failed to connect to BMC") + return managers, err + } + defer client.Logout() + + // Obtain the ServiceRoot + rf_service := client.GetService() + log.Info().Msgf("found ServiceRoot %s. Redfish Version %s", rf_service.ID, rf_service.RedfishVersion) + + rf_managers, err := rf_service.Managers() + if err != nil { + log.Error().Err(err).Msg("failed to get managers from ServiceRoot") + } + return walkManagers(rf_managers, config.URI) } func walkSystems(rf_systems []*redfish.ComputerSystem, rf_chassis *redfish.Chassis, baseURI string) ([]InventoryDetail, error) { @@ -200,7 +247,40 @@ func walkSystems(rf_systems []*redfish.ComputerSystem, rf_chassis *redfish.Chass for _, rf_trustedmodule := range rf_computersystem.TrustedModules { system.TrustedModules = append(system.TrustedModules, fmt.Sprintf("%s %s", rf_trustedmodule.InterfaceType, rf_trustedmodule.FirmwareVersion)) } + systems = append(systems, system) } return systems, nil } + +func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, error) { + var managers []Manager + for _, rf_manager := range rf_managers { + rf_ethernetinterfaces, err := rf_manager.EthernetInterfaces() + if err != nil { + log.Error().Err(err).Msg("failed to get ethernet interfaces from manager") + return managers, err + } + var ethernet_interfaces []EthernetInterface + for _, rf_ethernetinterface := range rf_ethernetinterfaces { + ethernet_interfaces = append(ethernet_interfaces, EthernetInterface{ + URI: baseURI + rf_ethernetinterface.ODataID, + MAC: rf_ethernetinterface.MACAddress, + Name: rf_ethernetinterface.Name, + Description: rf_ethernetinterface.Description, + Enabled: rf_ethernetinterface.InterfaceEnabled, + }) + } + managers = append(managers, Manager{ + URI: baseURI + "/redfish/v1/Managers/" + rf_manager.ID, + UUID: rf_manager.UUID, + Name: rf_manager.Name, + Description: rf_manager.Description, + Model: rf_manager.Model, + Type: string(rf_manager.ManagerType), + FirmwareVersion: rf_manager.FirmwareVersion, + EthernetInterfaces: ethernet_interfaces, + }) + } + return managers, nil +} From e31e91ec155fdd7dc4f9a78444e42642bcb1d45f Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 15 Oct 2024 13:52:23 -0600 Subject: [PATCH 18/34] refactor: changed crawler function name --- cmd/crawl.go | 2 +- tests/compatibility_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/crawl.go b/cmd/crawl.go index 8069c9b..2611ed3 100644 --- a/cmd/crawl.go +++ b/cmd/crawl.go @@ -35,7 +35,7 @@ var crawlCmd = &cobra.Command{ return nil }, Run: func(cmd *cobra.Command, args []string) { - systems, err := crawler.CrawlBMC(crawler.CrawlerConfig{ + systems, err := crawler.CrawlBMCForSystems(crawler.CrawlerConfig{ URI: args[0], Username: cmd.Flag("username").Value.String(), Password: cmd.Flag("password").Value.String(), diff --git a/tests/compatibility_test.go b/tests/compatibility_test.go index 03917ec..ce2e876 100644 --- a/tests/compatibility_test.go +++ b/tests/compatibility_test.go @@ -126,7 +126,7 @@ func TestExpectedOutput(t *testing.T) { t.Fatalf("failed while waiting for emulator: %v", err) } - systems, err := crawler.CrawlBMC( + systems, err := crawler.CrawlBMCForSystems( crawler.CrawlerConfig{ URI: *host, Username: *username, From deffdc48db0ce12023d54dde2f32b687be90230f Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 15 Oct 2024 13:54:00 -0600 Subject: [PATCH 19/34] collect: added MACAddr and manager data to output --- internal/collect.go | 114 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 105 insertions(+), 9 deletions(-) diff --git a/internal/collect.go b/internal/collect.go index 8c03050..737582d 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -10,6 +10,7 @@ import ( "net/http" "os" "path" + "strings" "sync" "time" @@ -21,7 +22,9 @@ import ( "github.com/Cray-HPE/hms-xname/xnames" _ "github.com/mattn/go-sqlite3" + "github.com/stmcginnis/gofish" _ "github.com/stmcginnis/gofish" + "github.com/stmcginnis/gofish/redfish" "golang.org/x/exp/slices" ) @@ -41,12 +44,13 @@ type CollectParams struct { } // This is the main function used to collect information from the BMC nodes via Redfish. +// The results of the collect are stored in a cache specified with the `--cache` flag. // The function expects a list of hosts found using the `ScanForAssets()` function. // // Requests can be made to several of the nodes using a goroutine by setting the q.Concurrency -// property value between 1 and 255. +// property value between 1 and 10000. func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error { - // check for available probe states + // check for available remote assets found from scan if assets == nil { return fmt.Errorf("no assets found") } @@ -109,14 +113,23 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error { offset += 1 // crawl BMC node to fetch inventory data via Redfish - systems, err := crawler.CrawlBMC(crawler.CrawlerConfig{ - URI: fmt.Sprintf("%s:%d", sr.Host, sr.Port), - Username: params.Username, - Password: params.Password, - Insecure: true, - }) + var ( + systems []crawler.InventoryDetail + managers []crawler.Manager + config = crawler.CrawlerConfig{ + URI: fmt.Sprintf("%s:%d", sr.Host, sr.Port), + Username: params.Username, + Password: params.Password, + Insecure: true, + } + ) + systems, err := crawler.CrawlBMCForSystems(config) + if err != nil { + log.Error().Err(err).Msg("failed to crawl BMC for systems") + } + managers, err = crawler.CrawlBMCForManagers(config) if err != nil { - log.Error().Err(err).Msgf("failed to crawl BMC") + log.Error().Err(err).Msg("failed to crawl BMC for managers") } // data to be sent to smd @@ -129,9 +142,20 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error { "MACRequired": true, "RediscoverOnUpdate": false, "Systems": systems, + "Managers": managers, "SchemaVersion": 1, } + // optionally, add the MACAddr property if we find a matching IP + // from the correct ethernet interface + mac, err := FindMACAddressWithIP(config, net.ParseIP(sr.Host)) + if err != nil { + log.Warn().Err(err).Msgf("failed to find MAC address with IP '%s'", sr.Host) + } + if mac != "" { + data["MACAddr"] = mac + } + // create and set headers for request headers := client.HTTPHeader{} headers.Authorization(params.AccessToken) @@ -225,3 +249,75 @@ func CollectInventory(assets *[]RemoteAsset, params *CollectParams) error { return nil } + +// FindMACAddressWithIP() returns the MAC address of an ethernet interface with +// a matching IPv4Address. Returns an empty string and error if there are no matches +// found. +func FindMACAddressWithIP(config crawler.CrawlerConfig, targetIP net.IP) (string, error) { + // get the managers to find the BMC MAC address compared with IP + // + // NOTE: Since we don't have a RedfishEndpoint type abstraction in + // magellan and the crawler crawls for systems information, it + // may just make more sense to get the managers directly via + // gofish (at least for now). If there's a need for grabbing more + // manager information in the future, we can move the logic into + // the crawler. + client, err := gofish.Connect(gofish.ClientConfig{ + Endpoint: config.URI, + Username: config.Username, + Password: config.Password, + Insecure: config.Insecure, + BasicAuth: true, + }) + if err != nil { + if strings.HasPrefix(err.Error(), "404:") { + err = fmt.Errorf("no ServiceRoot found. This is probably not a BMC: %s", config.URI) + } + if strings.HasPrefix(err.Error(), "401:") { + err = fmt.Errorf("authentication failed. Check your username and password: %s", config.URI) + } + event := log.Error() + event.Err(err) + event.Msg("failed to connect to BMC") + return "", err + } + defer client.Logout() + + var ( + rf_service = client.GetService() + rf_managers []*redfish.Manager + ) + rf_managers, err = rf_service.Managers() + if err != nil { + return "", fmt.Errorf("failed to get managers: %v", err) + } + + // find the manager with the same IP address of the BMC to get + // it's MAC address from its EthernetInterface + for _, manager := range rf_managers { + eths, err := manager.EthernetInterfaces() + if err != nil { + log.Error().Err(err).Msgf("failed to get ethernet interfaces from manager '%s'", manager.Name) + continue + } + for _, eth := range eths { + // compare the ethernet interface IP with argument + for _, ip := range eth.IPv4Addresses { + if ip.Address == targetIP.String() { + // we found matching IP address so return the ethernet interface MAC + return eth.MACAddress, nil + } + } + // do the same thing as above, but with static IP addresses + for _, ip := range eth.IPv4StaticAddresses { + if ip.Address == targetIP.String() { + return eth.MACAddress, nil + } + } + // no matches found, so go to next ethernet interface + continue + } + } + // no matches found, so return an empty string + return "", fmt.Errorf("no ethernet interfaces found with IP address") +} From 036a5d3ab7fa1d41f8536913a70a50efe9c2934d Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 15 Oct 2024 15:25:03 -0600 Subject: [PATCH 20/34] util: refactor checking if path exists --- internal/util/path.go | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) 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) } From e87b2184a29296eeb3dbef4aaf2166ebcb43c606 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 15 Oct 2024 15:25:42 -0600 Subject: [PATCH 21/34] cache: refactor after updating util --- internal/cache/sqlite/sqlite.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) 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 From 27fec74ceacb13cbe894afa8a05a7ec9e4c3fed3 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 15 Oct 2024 15:26:20 -0600 Subject: [PATCH 22/34] collect: fixed issue with writing output to file --- internal/collect.go | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/internal/collect.go b/internal/collect.go index 8c03050..e853802 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -10,13 +10,13 @@ import ( "net/http" "os" "path" + "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" @@ -148,25 +148,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") } } From 3aaa7c4f1d0cb9c0aa9a9f692cd2b4434bc7687a Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 16 Oct 2024 13:31:26 -0600 Subject: [PATCH 23/34] makefile: small tweaks --- Makefile | 3 +++ 1 file changed, 3 insertions(+) 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 From 26522ebd12de82c993c1313bf1a6f990eb1437c1 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 16 Oct 2024 13:32:16 -0600 Subject: [PATCH 24/34] version: more small tweaks --- cmd/root.go | 14 +++++++------- cmd/version.go | 7 ++++--- 2 files changed, 11 insertions(+), 10 deletions(-) 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) } }, } From 88ae24b1314cd4f735753148a30174662caf3509 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 16 Oct 2024 13:32:57 -0600 Subject: [PATCH 25/34] readme: updated content and sections --- README.md | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) 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 From c9ae96ab76fe2d370c257d7218728bea55c6dd34 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 17 Oct 2024 15:54:45 -0600 Subject: [PATCH 26/34] crawler: add check to get IP address from manager's ethernet interface --- pkg/crawler/main.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index e90593a..ac5b0ea 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -263,13 +263,17 @@ func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, er } var ethernet_interfaces []EthernetInterface for _, rf_ethernetinterface := range rf_ethernetinterfaces { - ethernet_interfaces = append(ethernet_interfaces, EthernetInterface{ + ethernetinterface := EthernetInterface{ URI: baseURI + rf_ethernetinterface.ODataID, MAC: rf_ethernetinterface.MACAddress, Name: rf_ethernetinterface.Name, Description: rf_ethernetinterface.Description, Enabled: rf_ethernetinterface.InterfaceEnabled, - }) + } + if len(rf_ethernetinterface.IPv4Addresses) > 0 { + ethernetinterface.IP = rf_ethernetinterface.IPv4Addresses[0].Address + } + ethernet_interfaces = append(ethernet_interfaces, ethernetinterface) } managers = append(managers, Manager{ URI: baseURI + "/redfish/v1/Managers/" + rf_manager.ID, From ac78e21ee8688fe5d25903a57cc316c475db59e2 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Thu, 17 Oct 2024 18:38:29 -0600 Subject: [PATCH 27/34] crawler: change check to exclude ethernet interfaces without IPs --- pkg/crawler/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index ac5b0ea..3b29bad 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -263,6 +263,9 @@ func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, er } var ethernet_interfaces []EthernetInterface for _, rf_ethernetinterface := range rf_ethernetinterfaces { + if len(rf_ethernetinterface.IPv4Addresses) <= 0 { + continue + } ethernetinterface := EthernetInterface{ URI: baseURI + rf_ethernetinterface.ODataID, MAC: rf_ethernetinterface.MACAddress, @@ -270,9 +273,6 @@ func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, er Description: rf_ethernetinterface.Description, Enabled: rf_ethernetinterface.InterfaceEnabled, } - if len(rf_ethernetinterface.IPv4Addresses) > 0 { - ethernetinterface.IP = rf_ethernetinterface.IPv4Addresses[0].Address - } ethernet_interfaces = append(ethernet_interfaces, ethernetinterface) } managers = append(managers, Manager{ From 4f6011381b1f0fa4239d1e97e8cf801513f23404 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 22 Oct 2024 14:23:24 -0600 Subject: [PATCH 28/34] crawler: include IP in manager's ethernet interfaces --- pkg/crawler/main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/crawler/main.go b/pkg/crawler/main.go index 3b29bad..771efb9 100644 --- a/pkg/crawler/main.go +++ b/pkg/crawler/main.go @@ -266,14 +266,14 @@ func walkManagers(rf_managers []*redfish.Manager, baseURI string) ([]Manager, er if len(rf_ethernetinterface.IPv4Addresses) <= 0 { continue } - ethernetinterface := EthernetInterface{ + ethernet_interfaces = append(ethernet_interfaces, EthernetInterface{ URI: baseURI + rf_ethernetinterface.ODataID, MAC: rf_ethernetinterface.MACAddress, Name: rf_ethernetinterface.Name, Description: rf_ethernetinterface.Description, Enabled: rf_ethernetinterface.InterfaceEnabled, - } - ethernet_interfaces = append(ethernet_interfaces, ethernetinterface) + IP: rf_ethernetinterface.IPv4Addresses[0].Address, + }) } managers = append(managers, Manager{ URI: baseURI + "/redfish/v1/Managers/" + rf_manager.ID, From 265021056f9fa1b5d0a6985821b22b9abb30bf54 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Wed, 23 Oct 2024 11:30:07 -0600 Subject: [PATCH 29/34] collect: removed extra gofish import --- internal/collect.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/collect.go b/internal/collect.go index 737582d..0fd357f 100644 --- a/internal/collect.go +++ b/internal/collect.go @@ -23,7 +23,6 @@ import ( "github.com/Cray-HPE/hms-xname/xnames" _ "github.com/mattn/go-sqlite3" "github.com/stmcginnis/gofish" - _ "github.com/stmcginnis/gofish" "github.com/stmcginnis/gofish/redfish" "golang.org/x/exp/slices" ) From 95cdd1b9d5f26b4283cecc9ab4ecbb3b76759bb4 Mon Sep 17 00:00:00 2001 From: "David J. Allen" Date: Tue, 29 Oct 2024 16:30:31 -0600 Subject: [PATCH 30/34] changelog: updated and added missing tag entries --- CHANGELOG.md | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc8586..a6a8e92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,265 @@ 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). +## [0.1.5] +### Added + + * Added Init() to Client interface + * Added temporary solution for creating new clients + +### Changed + + * Changed interface func from GetClient() to GetInternalClient() + +### Fixed + + * Fixed field tag in crawler + * Fixed panic when setting --cacert from invalid client + +### Updated + + * Updated warning message and changed SMD client to use pointer receivers + +### Miscellaneous + + * Merge pull request #55 from OpenCHAMI/cacert-hotfix + +## [0.1.4] + +### Added + + * Added response body into error messages + * Added schema version to output + +### Changed + + * Changed collect messages to using JSON format + +### Miscellaneous + + * Merge branch 'main' into minor-changes + * Merge pull request #50 from OpenCHAMI/container-build + * Merge pull request #51 from OpenCHAMI/minor-changes + * Merge pull request #52 from OpenCHAMI/minor-changes + * Merge pull request #53 from OpenCHAMI/minor-changes + * Merge pull request #54 from OpenCHAMI/update-readme + * Rearranged collect error to only show when not force updating + * Updated README.md and fixed outdated info + * magellan.sh: remove unused build helper function + * release: prefix all version tags with "v" + +## [0.1.3] + +### Fixed + + * Fixed automatic builds with docker container + * Fixed deprecation warning in goreleaser + * Fixed permissions in workflow + * Fixed typo in workflow + +## [0.1.2] + +### Fixed + + * Fixed automatic builds with docker container + * Fixed typo in workflow + +## [0.1.1] + +### Added + + * Added container building working + * Added more information to crawler output + +### Removed + + * Removed copying script in container + +### Miscellaneous + + * Merge pull request #49 from OpenCHAMI/add-types + +## [0.1.0] + +### Added + + * Added TODO comments to tests and other minor change + * Added URL sanitization for SMD host and moved auth from util + * Added check for output directory for collect + * Added disclaimer about incompatibility with SMD + * Added flag to show cache info with list command and other minor changes + +### Changed + + * Changed 'docker' rule to 'container' + * Changed build rule and added release rule to Makefile + * Changed firmware.* back to firmware-* + * Changed host to hostname being stored in cache + * Changed how arguments are passed to update command + * Changed how based URL is derived in update functions + * Changed order of adding default ports to add host correctly + * Changed saving host to include scheme for collect + * Changed short help message for root command + * Changed showing target host to use debug instead of verbose flag + * Changed transfer-protocol flag to scheme to match other commands + * Changed the username/password flag names + +### Fixed + + * Fixed '--subnet' flag not adding hosts to scan + * Fixed crawl command help string + * Fixed error message format for list command + * Fixed getting ethernet interfaces in CollectEthernetInterfaces() + * Fixed imports and removed unused query params + * Fixed issue with collect requests and other minor changes + * Fixed issue with host string and added internal url package + * Fixed lint errors + * Fixed passing the correct argument in Sanitize() + * Fixed port not being added to probing request + * Fixed root persistent flags not binding correctly + * Fixed scan not probing the host correctly + * Fixed small issue with command string + * Fixed typo errors in changelog and readme + * Fixed viper flag binding in collect cmd + +### Removed + + * Removed 'dora' API + * Removed commented out code + * Removed extra print statement + * Removed files from util + * Removed magellan's internal logger for zerolog + * Removed storage file + * Removed unused code, rename vars, and changed output to use hive partitioning strategy + * Removed unused functions in collect.go + * Removed unused port and clarified default in README.md + * Removed unused query params + * Removed unused updating code and bmclib dependency and other minor changes + * Removed unused variables in client package + +### Updated + + * Updated 'cmd' package + * Updated .gitignore + * Updated Makefile to include GOPATH in some targets + * Updated README.md with features section + * Updated example config + * Updated go dependencies + * Updated tests to reflect new API changes + +### Renamed + + * Renamed smd package to client + * Renamed struct + * Renamed vars and switched to use zerolog + +### Miscellaneous + + * Minor changes and improvements + * Minor changes to fix lint errors + * Minor changes to tests + * More minor changes + * Moved SMD-related API to pkg + * Refactored how clients work to reduce hard-coded dependencies + * Refactored/reorganized utils + * Reformatted scan help message + * Separated auth from util and fixed help strings + +## [0.0.20] + + * Updated workflows to publish container + +## [0.0.19] + +### Added + + * Added 'docs' rule to Makefile + * Added initial round of comments for API documentation + * Added initial tests for API and compatibiilty coverage + * Added more API documentation + * Added more documentation and changed param names + +### Changed + + * Changed Dockerfile to use binary instead of script + +### Fixed + + * Fixed issue with required param + * Fixed small typo + * Fixed syntax error with command description + +### Removed + + * Removed unused code that used bmclib + +### Updated + + * Updated README to include information about building on Debian + * Updated go dependencies removing bmclib + * Updated dependencies + +### Miscellaneous + +Minor changes to README.md +Tidied up CLI flag names + +## [0.0.18] + +### Fixed + + * Fixed formatting error in workflow + +## [0.0.17] + + * Addressed x/net dependabot issue + +## [0.0.16] + + * Updated attestation path + +## [0.0.15] + +### Removed + + * Removed unnecessary attestation support script + +## [0.0.14] + + * Updated to goreleaser v2 + +## [0.0.13] + + * Updated to goreleaser v2 + +## [0.0.12] + + * Removed attestation of non-existent container + +## [0.0.11] + +### Removed + + * Removed docker container from goreleaser to address build errors + +## [0.0.10] + + * Updated .goreleaser.yaml + +## [0.0.9] + + * Included Checkout in workflow + +## [0.0.8] +## [0.0.7] + +## [0.0.6] + +### Added + + * Adding dev container to standardize Linux build + * Merge pull request #1 from OpenCHAMI/rehome ## [0.0.5] - 2023-11-02 From 041d134079cfbedb2c8f44f8fa3dad34311fe3a5 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Wed, 30 Oct 2024 09:56:44 -0600 Subject: [PATCH 31/34] refactor: Update Go version This commit updates the Go version to the latest stable release and adds QEMU setup to the GitHub workflow. It also sets up the necessary environment variables required by GoReleaser. These changes ensure that the project is using the latest Go version and is ready for release. Adds support for more architectures, package types, and better docker image. --- .github/workflows/main.yml | 29 ++++++-- .github/workflows/prbuild.yml | 44 ++++++++++++ .gitignore | 1 + .goreleaser.yaml | 109 +++++++++++++++++++++++++----- Dockerfile | 10 ++- bin/magellan.sh | 122 ---------------------------------- dist/archlinux/PKGBUILD | 29 -------- 7 files changed, 168 insertions(+), 176 deletions(-) create mode 100644 .github/workflows/prbuild.yml delete mode 100755 bin/magellan.sh delete mode 100644 dist/archlinux/PKGBUILD diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2629cfd..edf7304 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -14,21 +14,36 @@ jobs: runs-on: ubuntu-latest steps: - - name: Set up Go 1.21 + - name: Set up latest stable Go uses: actions/setup-go@v5 with: - go-version: 1.21 + go-version: stable + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-tags: 1 + fetch-depth: 1 + + # Set environment variables required by GoReleaser + - name: Set build environment variables + run: | + echo "GIT_STATE=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi)" >> $GITHUB_ENV + echo "BUILD_HOST=$(hostname)" >> $GITHUB_ENV + echo "GO_VERSION=$(go version | awk '{print $3}')" >> $GITHUB_ENV + echo "BUILD_USER=$(whoami)" >> $GITHUB_ENV + echo "CGO_ENABLED=1" >> $GITHUB_ENV + - name: Docker Login uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-tags: 1 - fetch-depth: 0 + - name: Release with goreleaser uses: goreleaser/goreleaser-action@v6 env: diff --git a/.github/workflows/prbuild.yml b/.github/workflows/prbuild.yml new file mode 100644 index 0000000..476ba60 --- /dev/null +++ b/.github/workflows/prbuild.yml @@ -0,0 +1,44 @@ +name: Build PR with goreleaser + +on: + pull_request: + branches: + - main + types: [opened, synchronize, reopened, edited] + workflow_dispatch: + +jobs: + prbuild: + runs-on: ubuntu-latest + steps: + + - name: Set up latest stable Go + uses: actions/setup-go@v5 + with: + go-version: stable + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-tags: 1 + fetch-depth: 1 + + # Set environment variables required by GoReleaser + - name: Set build environment variables + run: | + echo "GIT_STATE=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi)" >> $GITHUB_ENV + echo "BUILD_HOST=$(hostname)" >> $GITHUB_ENV + echo "GO_VERSION=$(go version | awk '{print $3}')" >> $GITHUB_ENV + echo "BUILD_USER=$(whoami)" >> $GITHUB_ENV + echo "CGO_ENABLED=1" >> $GITHUB_ENV + + - name: Build with goreleaser + uses: goreleaser/goreleaser-action@v6 + + with: + version: '~> v2' + args: release --snapshot + id: goreleaser \ No newline at end of file diff --git a/.gitignore b/.gitignore index 45e95ea..aeafd79 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ emulator/rf-emulator **.part dist/* **coverage.out** +magellan.1 \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b202123..4eca1fe 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -7,12 +7,33 @@ before: - go-md2man -in README.md -out magellan.1 builds: - - env: - - CGO_ENABLED=1 + - binary: magellan + # export GIT_STATE=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi) + # export BUILD_HOST=$(hostname) + # export GO_VERSION=$(go version | awk '{print $3}') + # export BUILD_USER=$(whoami) + ldflags: + - "-X main.GitCommit={{.Commit}} \ + -X main.BuildTime={{.Timestamp}} \ + -X main.Version={{.Version}} \ + -X main.GitBranch={{.Branch}} \ + -X main.GitTag={{.Tag}} \ + -X main.GitState={{ .Env.GIT_STATE }} \ + -X main.BuildHost={{ .Env.BUILD_HOST }} \ + -X main.GoVersion={{ .Env.GO_VERSION }} \ + -X main.BuildUser={{ .Env.BUILD_USER }} " goos: - linux + - darwin + - windows goarch: - amd64 + - arm64 + goamd64: + - v3 + env: + - CGO_ENABLED=0 + archives: - format: tar.gz # this name template makes the OS and Arch compatible with the results of uname. @@ -27,26 +48,89 @@ archives: - LICENSE - CHANGELOG.md - README.md - - bin/magellan.sh - magellan.1 + +nfpms: + - id: magellan + formats: + - deb + - rpm + - apk + - archlinux + maintainer: "David J. Allen " + description: "Magellan is a discovery tool for BMCs." + homepage: "https://www.openchami.org" + license: MIT + section: utils + priority: optional + contents: + - src: dist/magellan_{{ .Os }}_{{ if eq .Arch "amd64" }}{{ .Arch }}_{{ .Amd64 }}{{ else }}{{ .Arch }}{{ end }}/magellan + dst: /usr/local/bin/magellan + - src: magellan.1 + dst: /usr/share/man/man1/ + + dockers: - - - image_templates: - - ghcr.io/openchami/{{.ProjectName}}:latest - - ghcr.io/openchami/{{.ProjectName}}:{{ .Tag }} - - ghcr.io/openchami/{{.ProjectName}}:v{{ .Major }} - - ghcr.io/openchami/{{.ProjectName}}:v{{ .Major }}.{{ .Minor }} + - image_templates: + - &amd64_linux_image ghcr.io/openchami/{{.ProjectName}}:{{ .Tag }}-amd64 + - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}-amd64 + - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}-amd64 + use: buildx build_flag_templates: - "--pull" + - "--platform=linux/amd64" - "--label=org.opencontainers.image.created={{.Date}}" - "--label=org.opencontainers.image.title={{.ProjectName}}" - "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.version={{.Version}}" + goarch: amd64 + goamd64: v3 extra_files: - LICENSE - CHANGELOG.md - README.md - - bin/magellan.sh + - image_templates: + - &arm64v8_linux_image ghcr.io/openchami/{{.ProjectName}}:{{ .Tag }}-arm64 + - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}-arm64 + - ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}-arm64 + use: buildx + build_flag_templates: + - "--pull" + - "--platform=linux/arm64" + - "--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 + goarch: arm64 + +docker_manifests: + - name_template: "ghcr.io/openchami/{{.ProjectName}}:latest" + image_templates: + - *amd64_linux_image + - *arm64v8_linux_image + + - name_template: "ghcr.io/openchami/{{.ProjectName}}:{{ .Tag }}" + image_templates: + - *amd64_linux_image + - *arm64v8_linux_image + + - name_template: "ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}" + image_templates: + - *amd64_linux_image + - *arm64v8_linux_image + + - name_template: "ghcr.io/openchami/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}" + image_templates: + - *amd64_linux_image + - *arm64v8_linux_image + + + + checksum: name_template: 'checksums.txt' snapshot: @@ -57,8 +141,3 @@ changelog: exclude: - '^docs:' - '^test:' -release: - github: - name_template: "{{.Version}}" - prerelease: auto - mode: append diff --git a/Dockerfile b/Dockerfile index 510a291..d9632fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,17 @@ -FROM cgr.dev/chainguard/wolfi-base +FROM chainguard/wolfi-base:latest -RUN apk add --no-cache tini bash +# Include curl in the final image for manual checks of the Redfish urls +RUN set -ex \ + && apk update \ + && apk add --no-cache curl tini \ + && rm -rf /var/cache/apk/* \ + && rm -rf /tmp/* # nobody 65534:65534 USER 65534:65534 COPY magellan /magellan -COPY /bin/magellan.sh /magellan.sh CMD [ "/magellan" ] diff --git a/bin/magellan.sh b/bin/magellan.sh deleted file mode 100755 index c91d922..0000000 --- a/bin/magellan.sh +++ /dev/null @@ -1,122 +0,0 @@ -#!/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 scan() { - # ./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 - ${EXE} list -} - -function collect() { - # ./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/dist/archlinux/PKGBUILD b/dist/archlinux/PKGBUILD deleted file mode 100644 index 23e69b7..0000000 --- a/dist/archlinux/PKGBUILD +++ /dev/null @@ -1,29 +0,0 @@ -# Maintainer: David J. Allen -pkgname=magellan -pkgver=v0.1.5 -pkgrel=1 -pkgdesc="Redfish-based BMC discovery tool written in Go" -arch=("x86_64") -url="https://github.com/OpenCHAMI/magellan" -license=('MIT') -groups=("openchami") -provides=('magellan') -conflicts=('magellan') -source_x86_64=("${url}/releases/download/${pkgver}/${pkgname}_Linux_x86_64.tar.gz") -sha256sums_x86_64=('1bb028d592d5389b519362e6aa7021f27443f0b36471e09ee7f47ab5cb6d4d7f') - -# Please refer to the 'USING VCS SOURCES' section of the PKGBUILD man page for -# a description of each element in the source array. - -pkgver() { - cd "$srcdir" || exit - printf "%s" "$(git describe --tags --abbrev=0)" -} - -package() { - cd "$srcdir/" || exit - - # install the binary to /usr/bin - mkdir -p "${pkgdir}/usr/bin" - install -m755 magellan "${pkgdir}/usr/bin/magellan" -} From 237eb6219bbbe0d0b13f3ca2ae26b86cb639d501 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Wed, 30 Oct 2024 10:09:32 -0600 Subject: [PATCH 32/34] Add better version reporting. --- cmd/version.go | 75 ++++++++++++++++++++++++++++++++++++-------------- main.go | 7 ----- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/cmd/version.go b/cmd/version.go index b9cfd82..d61aff7 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -6,36 +6,69 @@ import ( "github.com/spf13/cobra" ) -var ( - version string - commit string - date string - output string -) - var versionCmd = &cobra.Command{ 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 on " + date - } - fmt.Println(output) - } else { - fmt.Printf("%s-%s\n", version, commit) - } + PrintVersionInfo() }, } func init() { - versionCmd.Flags().Bool("commit", false, "show the version commit") rootCmd.AddCommand(versionCmd) } -func SetVersionInfo(buildVersion string, buildCommit string, buildDate string) { - version = buildVersion - commit = buildCommit - date = buildDate +// GitCommit stores the latest Git commit hash. +// Set via -ldflags "-X main.GitCommit=$(git rev-parse HEAD)" +var GitCommit string + +// BuildTime stores the build timestamp in UTC. +// Set via -ldflags "-X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" +var BuildTime string + +// Version indicates the version of the binary, such as a release number or semantic version. +// Set via -ldflags "-X main.Version=v1.0.0" +var Version string + +// GitBranch holds the name of the Git branch from which the build was created. +// Set via -ldflags "-X main.GitBranch=$(git rev-parse --abbrev-ref HEAD)" +var GitBranch string + +// GitTag represents the most recent Git tag at build time, if any. +// Set via -ldflags "-X main.GitTag=$(git describe --tags --abbrev=0)" +var GitTag string + +// GitState indicates whether the working directory was "clean" or "dirty" (i.e., with uncommitted changes). +// Set via -ldflags "-X main.GitState=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi)" +var GitState string + +// BuildHost stores the hostname of the machine where the binary was built. +// Set via -ldflags "-X main.BuildHost=$(hostname)" +var BuildHost string + +// GoVersion captures the Go version used to build the binary. +// Typically, this can be obtained automatically with runtime.Version(), but you can set it manually. +// Set via -ldflags "-X main.GoVersion=$(go version | awk '{print $3}')" +var GoVersion string + +// BuildUser is the username of the person or system that initiated the build process. +// Set via -ldflags "-X main.BuildUser=$(whoami)" +var BuildUser string + +// PrintVersionInfo outputs all versioning information for troubleshooting or version checks. +func PrintVersionInfo() { + fmt.Printf("Version: %s\n", Version) + fmt.Printf("Git Commit: %s\n", GitCommit) + fmt.Printf("Build Time: %s\n", BuildTime) + fmt.Printf("Git Branch: %s\n", GitBranch) + fmt.Printf("Git Tag: %s\n", GitTag) + fmt.Printf("Git State: %s\n", GitState) + fmt.Printf("Build Host: %s\n", BuildHost) + fmt.Printf("Go Version: %s\n", GoVersion) + fmt.Printf("Build User: %s\n", BuildUser) +} + +func VersionInfo() string { + return fmt.Sprintf("Version: %s, Git Commit: %s, Build Time: %s, Git Branch: %s, Git Tag: %s, Git State: %s, Build Host: %s, Go Version: %s, Build User: %s", + Version, GitCommit, BuildTime, GitBranch, GitTag, GitState, BuildHost, GoVersion, BuildUser) } diff --git a/main.go b/main.go index 0d248c4..ebe2d95 100644 --- a/main.go +++ b/main.go @@ -4,13 +4,6 @@ import ( "github.com/OpenCHAMI/magellan/cmd" ) -var ( - version string - commit string - date string -) - func main() { - cmd.SetVersionInfo(version, commit, date) cmd.Execute() } From 6f1809468068647718bf35fa70cd0c5b3e47be22 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Wed, 30 Oct 2024 12:36:07 -0600 Subject: [PATCH 33/34] Refactor how versioning information is indicated in the build and in the source. --- .goreleaser.yaml | 21 +++++++------ cmd/version.go | 60 ++----------------------------------- internal/version/version.go | 60 +++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 67 deletions(-) create mode 100644 internal/version/version.go diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 4eca1fe..d8552b6 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -8,20 +8,23 @@ before: builds: - binary: magellan + main: ./main.go # export GIT_STATE=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi) # export BUILD_HOST=$(hostname) # export GO_VERSION=$(go version | awk '{print $3}') # export BUILD_USER=$(whoami) ldflags: - - "-X main.GitCommit={{.Commit}} \ - -X main.BuildTime={{.Timestamp}} \ - -X main.Version={{.Version}} \ - -X main.GitBranch={{.Branch}} \ - -X main.GitTag={{.Tag}} \ - -X main.GitState={{ .Env.GIT_STATE }} \ - -X main.BuildHost={{ .Env.BUILD_HOST }} \ - -X main.GoVersion={{ .Env.GO_VERSION }} \ - -X main.BuildUser={{ .Env.BUILD_USER }} " + - "-X github.com/OpenCHAMI/magellan/internal/version.GitCommit={{ .Commit }} \ + -X github.com/OpenCHAMI/magellan/internal/version.BuildTime={{ .Timestamp }} \ + -X github.com/OpenCHAMI/magellan/internal/version.Version={{ .Version }} \ + -X github.com/OpenCHAMI/magellan/internal/version.GitBranch={{ .Branch }} \ + -X github.com/OpenCHAMI/magellan/internal/version.GitTag={{ .Tag }} \ + -X github.com/OpenCHAMI/magellan/internal/version.GitState={{ .Env.GIT_STATE }} \ + -X github.com/OpenCHAMI/magellan/internal/version.BuildHost={{ .Env.BUILD_HOST }} \ + -X github.com/OpenCHAMI/magellan/internal/version.GoVersion={{ .Env.GO_VERSION }} \ + -X github.com/OpenCHAMI/magellan/internal/version.BuildUser={{ .Env.BUILD_USER }} " + tags: + - version goos: - linux - darwin diff --git a/cmd/version.go b/cmd/version.go index d61aff7..87b7596 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -1,8 +1,7 @@ package cmd import ( - "fmt" - + "github.com/OpenCHAMI/magellan/internal/version" "github.com/spf13/cobra" ) @@ -10,65 +9,10 @@ var versionCmd = &cobra.Command{ Use: "version", Short: "Print version info and exit", Run: func(cmd *cobra.Command, args []string) { - PrintVersionInfo() + version.PrintVersionInfo() }, } func init() { rootCmd.AddCommand(versionCmd) } - -// GitCommit stores the latest Git commit hash. -// Set via -ldflags "-X main.GitCommit=$(git rev-parse HEAD)" -var GitCommit string - -// BuildTime stores the build timestamp in UTC. -// Set via -ldflags "-X main.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -var BuildTime string - -// Version indicates the version of the binary, such as a release number or semantic version. -// Set via -ldflags "-X main.Version=v1.0.0" -var Version string - -// GitBranch holds the name of the Git branch from which the build was created. -// Set via -ldflags "-X main.GitBranch=$(git rev-parse --abbrev-ref HEAD)" -var GitBranch string - -// GitTag represents the most recent Git tag at build time, if any. -// Set via -ldflags "-X main.GitTag=$(git describe --tags --abbrev=0)" -var GitTag string - -// GitState indicates whether the working directory was "clean" or "dirty" (i.e., with uncommitted changes). -// Set via -ldflags "-X main.GitState=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi)" -var GitState string - -// BuildHost stores the hostname of the machine where the binary was built. -// Set via -ldflags "-X main.BuildHost=$(hostname)" -var BuildHost string - -// GoVersion captures the Go version used to build the binary. -// Typically, this can be obtained automatically with runtime.Version(), but you can set it manually. -// Set via -ldflags "-X main.GoVersion=$(go version | awk '{print $3}')" -var GoVersion string - -// BuildUser is the username of the person or system that initiated the build process. -// Set via -ldflags "-X main.BuildUser=$(whoami)" -var BuildUser string - -// PrintVersionInfo outputs all versioning information for troubleshooting or version checks. -func PrintVersionInfo() { - fmt.Printf("Version: %s\n", Version) - fmt.Printf("Git Commit: %s\n", GitCommit) - fmt.Printf("Build Time: %s\n", BuildTime) - fmt.Printf("Git Branch: %s\n", GitBranch) - fmt.Printf("Git Tag: %s\n", GitTag) - fmt.Printf("Git State: %s\n", GitState) - fmt.Printf("Build Host: %s\n", BuildHost) - fmt.Printf("Go Version: %s\n", GoVersion) - fmt.Printf("Build User: %s\n", BuildUser) -} - -func VersionInfo() string { - return fmt.Sprintf("Version: %s, Git Commit: %s, Build Time: %s, Git Branch: %s, Git Tag: %s, Git State: %s, Build Host: %s, Go Version: %s, Build User: %s", - Version, GitCommit, BuildTime, GitBranch, GitTag, GitState, BuildHost, GoVersion, BuildUser) -} diff --git a/internal/version/version.go b/internal/version/version.go new file mode 100644 index 0000000..7d9d231 --- /dev/null +++ b/internal/version/version.go @@ -0,0 +1,60 @@ +package version + +import ( + "fmt" +) + +// GitCommit stores the latest Git commit hash. +// Set via -ldflags "-X github.com/OpenCHAMI/magellan/internal/version.GitCommit=$(git rev-parse HEAD)" +var GitCommit string + +// BuildTime stores the build timestamp in UTC. +// Set via -ldflags "-X github.com/OpenCHAMI/magellan/internal/version.BuildTime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" +var BuildTime string + +// Version indicates the version of the binary, such as a release number or semantic version. +// Set via -ldflags "-X github.com/OpenCHAMI/magellan/internal/version.Version=v1.0.0" +var Version string + +// GitBranch holds the name of the Git branch from which the build was created. +// Set via -ldflags "-X github.com/OpenCHAMI/magellan/internal/version.GitBranch=$(git rev-parse --abbrev-ref HEAD)" +var GitBranch string + +// GitTag represents the most recent Git tag at build time, if any. +// Set via -ldflags "-X github.com/OpenCHAMI/magellan/internal/version.GitTag=$(git describe --tags --abbrev=0)" +var GitTag string + +// GitState indicates whether the working directory was "clean" or "dirty" (i.e., with uncommitted changes). +// Set via -ldflags "-X github.com/OpenCHAMI/magellan/internal/version.GitState=$(if git diff-index --quiet HEAD --; then echo 'clean'; else echo 'dirty'; fi)" +var GitState string + +// BuildHost stores the hostname of the machine where the binary was built. +// Set via -ldflags "-X github.com/OpenCHAMI/magellan/internal/version.BuildHost=$(hostname)" +var BuildHost string + +// GoVersion captures the Go version used to build the binary. +// Typically, this can be obtained automatically with runtime.Version(), but you can set it manually. +// Set via -ldflags "-X github.com/OpenCHAMI/magellan/internal/version.GoVersion=$(go version | awk '{print $3}')" +var GoVersion string + +// BuildUser is the username of the person or system that initiated the build process. +// Set via -ldflags "-X github.com/OpenCHAMI/magellan/internal/version.BuildUser=$(whoami)" +var BuildUser string + +// PrintVersionInfo outputs all versioning information for troubleshooting or version checks. +func PrintVersionInfo() { + fmt.Printf("Version: %s\n", Version) + fmt.Printf("Git Commit: %s\n", GitCommit) + fmt.Printf("Build Time: %s\n", BuildTime) + fmt.Printf("Git Branch: %s\n", GitBranch) + fmt.Printf("Git Tag: %s\n", GitTag) + fmt.Printf("Git State: %s\n", GitState) + fmt.Printf("Build Host: %s\n", BuildHost) + fmt.Printf("Go Version: %s\n", GoVersion) + fmt.Printf("Build User: %s\n", BuildUser) +} + +func VersionInfo() string { + return fmt.Sprintf("Version: %s, Git Commit: %s, Build Time: %s, Git Branch: %s, Git Tag: %s, Git State: %s, Build Host: %s, Go Version: %s, Build User: %s", + Version, GitCommit, BuildTime, GitBranch, GitTag, GitState, BuildHost, GoVersion, BuildUser) +} From 45ee89ffc323587ffe65c51792a9dc97c8241847 Mon Sep 17 00:00:00 2001 From: Alex Lovell-Troy Date: Wed, 30 Oct 2024 12:51:47 -0600 Subject: [PATCH 34/34] fix AMD64 microcode version in attestation Signed-off-by: Alex Lovell-Troy --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index edf7304..1136387 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -55,4 +55,4 @@ jobs: - name: Attest Binaries uses: actions/attest-build-provenance@v1 with: - subject-path: '${{ github.workspace }}/dist/magellan_linux_amd64_v1/magellan' + subject-path: '${{ github.workspace }}/dist/magellan_linux_amd64_v3/magellan'