Skip to content

Commit

Permalink
Code refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
Charlie-Root committed Jun 15, 2024
1 parent 46b33c1 commit bc37d47
Show file tree
Hide file tree
Showing 10 changed files with 336 additions and 267 deletions.
30 changes: 30 additions & 0 deletions cmd/boot.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cmd

import (
"fmt"

"github.com/Charlie-Root/smcli/host"
"github.com/spf13/cobra"
)

var BootCmd = &cobra.Command{
Use: "boot [cd|pxe]",
Short: "Set boot order",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
action := args[0]
hostName := args[1]

host.SelectHost(hostName)
host.GetRedfishPath()

switch action {
case "cd":
host.ServerSetBootOnceCD()
case "pxe":
host.ServerSetBootOncePXE()
default:
fmt.Println("Unknown boot command:", action)
}
},
}
32 changes: 32 additions & 0 deletions cmd/media.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cmd

import (
"fmt"

"github.com/Charlie-Root/smcli/host"
"github.com/spf13/cobra"
)

var MediaCmd = &cobra.Command{
Use: "media [insert|eject|status]",
Short: "Manage virtual media",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
action := args[0]
hostName := args[1]

host.SelectHost(hostName)
host.GetRedfishPath()

switch action {
case "insert":
host.ServerVirtualMediaInsert()
case "eject":
host.ServerVirtualMediaEject()
case "status":
host.ServerVirtualMediaStatus()
default:
fmt.Println("Unknown media command:", action)
}
},
}
32 changes: 32 additions & 0 deletions cmd/power.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cmd

import (
"fmt"

"github.com/Charlie-Root/smcli/host"
"github.com/spf13/cobra"
)

var PowerCmd = &cobra.Command{
Use: "power [on|off|restart]",
Short: "Manage power",
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
action := args[0]
hostName := args[1]

host.SelectHost(hostName)
host.GetRedfishPath()

switch action {
case "on":
host.ServerPowerOn()
case "off":
host.ServerPowerOff()
case "restart":
host.ServerRestart()
default:
fmt.Println("Unknown power command:", action)
}
},
}
11 changes: 11 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package cmd

import (
"github.com/spf13/cobra"
)

var Verbose bool

var RootCmd = &cobra.Command{
Use: "sm-cli",
}
35 changes: 35 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package config

import (
"log"
"os"

"gopkg.in/yaml.v2"
)

type Host struct {
Name string `yaml:"name"`
BMCAddress string `yaml:"bmc_address"`
UsernamePassword string `yaml:"username_password"`
ISOImage string `yaml:"iso_image"`
}

type Config struct {
Hosts []Host `yaml:"hosts"`
}

var ConfigData Config

func LoadConfig(filename string) {
file, err := os.Open(filename)
if err != nil {
log.Fatalf("Error opening config file: %v", err)
}
defer file.Close()

decoder := yaml.NewDecoder(file)
err = decoder.Decode(&ConfigData)
if err != nil {
log.Fatalf("Error decoding config file: %v", err)
}
}
27 changes: 27 additions & 0 deletions host/host.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package host

import (
"log"

"github.com/Charlie-Root/smcli/config"
)

var (
CurrentHost config.Host
RedfishPath string
verbose bool
)

func SetVerbose(v bool) {
verbose = v
}

func SelectHost(name string) {
for _, host := range config.ConfigData.Hosts {
if host.Name == name {
CurrentHost = host
return
}
}
log.Fatalf("Host with name %s not found", name)
}
23 changes: 23 additions & 0 deletions host/redfish.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package host

import (
"encoding/json"
"fmt"
"log"
)

func GetRedfishPath() {
url := fmt.Sprintf("https://%s/redfish/v1/Systems", CurrentHost.BMCAddress)
respBody := MakeRequest("GET", url, nil)

var data map[string]interface{}
if err := json.Unmarshal(respBody, &data); err != nil {
log.Fatal(err)
}

if members, ok := data["Members"].([]interface{}); ok && len(members) > 0 {
if member, ok := members[0].(map[string]interface{}); ok {
RedfishPath = member["@odata.id"].(string)
}
}
}
83 changes: 83 additions & 0 deletions host/request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package host

import (
"bytes"
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
)

func MakeRequest(method, url string, body []byte) []byte {
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}

req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
if err != nil {
log.Fatal(err)
}
authParts := strings.SplitN(CurrentHost.UsernamePassword, ":", 2)
req.SetBasicAuth(authParts[0], authParts[1])
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "application/json")

resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()

respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}

if resp.StatusCode >= 200 && resp.StatusCode < 300 {
var success struct {
Message string `json:"message"`
}
if err := json.Unmarshal(respBody, &success); err == nil {
if verbose {
fmt.Println("Success:", success.Message)
}
} else {
if verbose {
fmt.Println("Success:", string(respBody))
}
}
} else {
var errorResp struct {
Error struct {
Code string `json:"code"`
Message string `json:"message"`
MessageExtended []struct {
MessageId string `json:"MessageId"`
Severity string `json:"Severity"`
Resolution string `json:"Resolution"`
Message string `json:"Message"`
MessageArgs []string `json:"MessageArgs"`
RelatedProperties []string `json:"RelatedProperties"`
} `json:"@Message.ExtendedInfo"`
} `json:"error"`
}
if err := json.Unmarshal(respBody, &errorResp); err == nil {
fmt.Printf("Error: %s - %s\n", errorResp.Error.Code, errorResp.Error.Message)
for _, info := range errorResp.Error.MessageExtended {
fmt.Printf(" %s: %s\n", info.Severity, info.Message)
if info.Resolution != "" {
fmt.Printf(" Resolution: %s\n", info.Resolution)
}
}
} else {
fmt.Printf("Error: %s\n", resp.Status)
fmt.Println(string(respBody))
}
}

return respBody
}
52 changes: 52 additions & 0 deletions host/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package host

import (
"fmt"
)

func ServerPowerOff() {
url := fmt.Sprintf("https://%s%s/Actions/ComputerSystem.Reset", CurrentHost.BMCAddress, RedfishPath)
body := []byte(`{"ResetType": "ForceOff"}`)
MakeRequest("POST", url, body)
}

func ServerPowerOn() {
url := fmt.Sprintf("https://%s%s/Actions/ComputerSystem.Reset", CurrentHost.BMCAddress, RedfishPath)
body := []byte(`{"ResetType": "On"}`)
MakeRequest("POST", url, body)
}

func ServerRestart() {
url := fmt.Sprintf("https://%s%s/Actions/ComputerSystem.Reset", CurrentHost.BMCAddress, RedfishPath)
body := []byte(`{"ResetType": "ForceRestart"}`)
MakeRequest("POST", url, body)
}

func ServerVirtualMediaInsert() {
url := fmt.Sprintf("https://%s/redfish/v1/Managers/1/VirtualMedia/CD1/Actions/VirtualMedia.InsertMedia", CurrentHost.BMCAddress)
body := []byte(fmt.Sprintf(`{"Image": "%s"}`, CurrentHost.ISOImage))
MakeRequest("POST", url, body)
}

func ServerVirtualMediaEject() {
url := fmt.Sprintf("https://%s/redfish/v1/Managers/1/VirtualMedia/CD1/Actions/VirtualMedia.EjectMedia", CurrentHost.BMCAddress)
body := []byte(`{}`)
MakeRequest("POST", url, body)
}

func ServerVirtualMediaStatus() {
url := fmt.Sprintf("https://%s/redfish/v1/Managers/1/VirtualMedia/1", CurrentHost.BMCAddress)
MakeRequest("GET", url, nil)
}

func ServerSetBootOnceCD() {
url := fmt.Sprintf("https://%s%s", CurrentHost.BMCAddress, RedfishPath)
body := []byte(`{"Boot":{ "BootSourceOverrideEnabled": "Once", "BootSourceOverrideTarget": "Cd", "BootSourceOverrideMode": "UEFI"}}`)
MakeRequest("PATCH", url, body)
}

func ServerSetBootOncePXE() {
url := fmt.Sprintf("https://%s%s", CurrentHost.BMCAddress, RedfishPath)
body := []byte(`{"Boot":{ "BootSourceOverrideEnabled": "Once", "BootSourceOverrideTarget": "Pxe", "BootSourceOverrideMode": "UEFI"}}`)
MakeRequest("PATCH", url, body)
}
Loading

0 comments on commit bc37d47

Please sign in to comment.