mirror of https://github.com/adamveld12/dracli
Merge 6948203aa2 into fe4555465a
commit
37dfed2471
|
|
@ -1,3 +1,6 @@
|
|||
.vscode
|
||||
credentials.json
|
||||
dracli
|
||||
dracli
|
||||
viewer.jnlp
|
||||
.DS_Store
|
||||
drac
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
version: v1.0
|
||||
name: Drac Pipeline
|
||||
agent:
|
||||
machine:
|
||||
type: e1-standard-2
|
||||
os_image: ubuntu1804
|
||||
|
||||
blocks:
|
||||
- name: "Vet"
|
||||
task:
|
||||
jobs:
|
||||
- name: Test
|
||||
commands:
|
||||
- checkout
|
||||
- make setup
|
||||
- echo "make test"
|
||||
|
||||
- name: Lint code
|
||||
commands:
|
||||
- checkout
|
||||
- make setup
|
||||
- echo "make lint"
|
||||
|
||||
- name: "Build"
|
||||
task:
|
||||
env_vars:
|
||||
- name: APP_ENV
|
||||
value: prod
|
||||
jobs:
|
||||
- name: build
|
||||
commands:
|
||||
- checkout
|
||||
- make setup
|
||||
- make dev
|
||||
|
|
@ -1,5 +1,11 @@
|
|||
# DRACLI
|
||||
|
||||
[](http://godoc.org/github.com/adamveld12/dracli)
|
||||
[](https://gowalker.org/github.com/adamveld12/dracli)
|
||||
[](http://gocover.io/github.com/adamveld12/dracli)
|
||||
[](https://goreportcard.com/report/github.com/adamveld12/dracli)
|
||||
[](https://semaphoreci.com/adamveld12/dracli)
|
||||
|
||||
A quick and dirty CLI/client library for the Integrated Dell Remote Access Controller v6.
|
||||
|
||||
## CLI Usage
|
||||
|
|
|
|||
307
client.go
307
client.go
|
|
@ -14,6 +14,165 @@ import (
|
|||
xj "github.com/basgys/goxml2json"
|
||||
)
|
||||
|
||||
//Client is an http client that talks to the iDRAC
|
||||
type Client struct {
|
||||
client *http.Client
|
||||
Username string
|
||||
AuthToken string
|
||||
Host string
|
||||
}
|
||||
|
||||
func (c *Client) doHTTP(path, qs string, body io.Reader) (string, []*http.Cookie, error) {
|
||||
method := "GET"
|
||||
if strings.ContainsAny(qs, "set") {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("https://%s/data/%s?%s", c.Host, path, qs)
|
||||
req, _ := http.NewRequest(method, uri, body)
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "_appwebSessionId_",
|
||||
Value: c.AuthToken,
|
||||
})
|
||||
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return "", nil, errors.New("could not make request")
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
jsonData, err := xj.Convert(res.Body)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode >= 300 || res.StatusCode < 200 {
|
||||
return jsonData.String(), res.Cookies(), fmt.Errorf("got a non 200 status: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return jsonData.String(), res.Cookies(), nil
|
||||
}
|
||||
|
||||
//OpenConsole downloads the jnlp that opens the console
|
||||
func (c *Client) OpenConsole() error {
|
||||
//https://192.168.0.228/viewer.jnlp(192.168.0.228@0@root@1548390931405)
|
||||
uri := fmt.Sprintf("https://%s/viewer.jnlp(%s@0@%s@%d)", c.Host, c.Host, c.Username, time.Now().Unix())
|
||||
req, _ := http.NewRequest("GET", uri, nil)
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "_appwebSessionId_",
|
||||
Value: c.AuthToken,
|
||||
})
|
||||
res, err := c.client.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return errors.New("bad status")
|
||||
}
|
||||
|
||||
f, err := os.Create("./viewer.jnlp")
|
||||
if err != nil {
|
||||
return errors.New("could not download")
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if _, err := io.Copy(f, res.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//SetPowerState changes the power state of the server (ie turn off or on)
|
||||
func (c *Client) SetPowerState(ps PowerState) (string, error) {
|
||||
res, _, err := c.doHTTP("", fmt.Sprintf("set=pwState:%d", ps), nil)
|
||||
return res, err
|
||||
}
|
||||
|
||||
//SetBootOverride changes the boot override settings
|
||||
func (c *Client) SetBootOverride(bo BootDevice, bootOnce bool) (string, error) {
|
||||
res, _, err := c.doHTTP("", fmt.Sprintf("set=vmBootOnce:%v,firstBootDevice:%d", bootOnce, bo), nil)
|
||||
return res, err
|
||||
}
|
||||
|
||||
//Query queries attributes and returns them to json
|
||||
func (c *Client) Query(ds ...Attribute) (string, error) {
|
||||
bufs := bytes.NewBufferString("")
|
||||
for idx, attr := range ds {
|
||||
bufs.WriteString(string(attr))
|
||||
if idx < len(ds)-1 {
|
||||
bufs.WriteString(",")
|
||||
}
|
||||
}
|
||||
|
||||
res, _, err := c.doHTTP("", fmt.Sprintf("get=%s", bufs.String()), nil)
|
||||
return res, err
|
||||
}
|
||||
|
||||
//NewFromCredentials creates a new client from a credentials file at the specified path
|
||||
func NewFromCredentials(path string) (*Client, error) {
|
||||
credential, err := LoadCredentials(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.New("You should log in first")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := NewClient(credential.Host, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.AuthToken = credential.AuthToken
|
||||
c.Username = credential.Username
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
//NewClient creates a new Client
|
||||
func NewClient(host string, skipVerify bool) (*Client, error) {
|
||||
c := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
Transport: &http.Transport{
|
||||
IdleConnTimeout: time.Second,
|
||||
|
||||
TLSHandshakeTimeout: time.Second * 5,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: skipVerify,
|
||||
},
|
||||
},
|
||||
}
|
||||
client := &Client{client: c, Host: host}
|
||||
// err := client.Login(host, username, password)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
//Login sends a login http request to the iDRAC
|
||||
func (c *Client) Login(username, password string) (string, error) {
|
||||
body := bytes.NewBufferString(fmt.Sprintf("user=%s&password=%s", username, password))
|
||||
_, cookies, err := c.doHTTP("login", "", body)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == "_appwebSessionId_" {
|
||||
c.AuthToken = cookie.Value
|
||||
return cookie.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("could not find auth token in cookie")
|
||||
}
|
||||
|
||||
var (
|
||||
PowerStatus = Attribute("pwState")
|
||||
SystemDescription = Attribute("sysDesc")
|
||||
|
|
@ -54,139 +213,25 @@ var (
|
|||
|
||||
PowerOff = PowerState(0)
|
||||
PowerOn = PowerState(1)
|
||||
NonMaskingInterrupt = PowerState(2)
|
||||
GracefulShutdown = PowerState(3)
|
||||
ColdReboot = PowerState(4)
|
||||
WarmReboot = PowerState(5)
|
||||
NoOverride = BootDevice(0)
|
||||
PXE = BootDevice(1)
|
||||
HardDrive = BootDevice(2)
|
||||
BIOS = BootDevice(6)
|
||||
VirtualCD = BootDevice(8)
|
||||
LocalSD = BootDevice(16)
|
||||
LocalCD = BootDevice(5)
|
||||
ColdReboot = PowerState(2)
|
||||
WarmReboot = PowerState(3)
|
||||
NonMaskingInterrupt = PowerState(4)
|
||||
GracefulShutdown = PowerState(5)
|
||||
|
||||
NoOverride = BootDevice(0)
|
||||
PXE = BootDevice(1)
|
||||
HardDrive = BootDevice(2)
|
||||
BIOS = BootDevice(6)
|
||||
VirtualCD = BootDevice(8)
|
||||
LocalSD = BootDevice(16)
|
||||
LocalCD = BootDevice(5)
|
||||
)
|
||||
|
||||
//Attribute is an attribute that can be queried
|
||||
type Attribute string
|
||||
|
||||
//PowerState represents a powerstate option
|
||||
type PowerState uint8
|
||||
|
||||
//BootDevice represents a bootdevice option
|
||||
type BootDevice uint8
|
||||
|
||||
type SensorType struct {
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
client *http.Client
|
||||
AuthToken string
|
||||
Host string
|
||||
}
|
||||
|
||||
func (c *Client) doHTTP(path, qs string, body io.Reader) (string, []*http.Cookie, error) {
|
||||
method := "GET"
|
||||
if strings.ContainsAny(qs, "set") {
|
||||
method = "POST"
|
||||
}
|
||||
|
||||
uri := fmt.Sprintf("https://%s/data/%s?%s", c.Host, path, qs)
|
||||
req, _ := http.NewRequest(method, uri, body)
|
||||
req.AddCookie(&http.Cookie{
|
||||
Name: "_appwebSessionId_",
|
||||
Value: c.AuthToken,
|
||||
})
|
||||
|
||||
res, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
return "", nil, errors.New("could not make request")
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
jsonData, err := xj.Convert(res.Body)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode >= 300 || res.StatusCode < 200 {
|
||||
return jsonData.String(), res.Cookies(), fmt.Errorf("got a non 200 status: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return jsonData.String(), res.Cookies(), nil
|
||||
}
|
||||
|
||||
func (c *Client) SetPowerState(ps PowerState) (string, error) {
|
||||
res, _, err := c.doHTTP("", fmt.Sprintf("set=pwState:%d", ps), nil)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (c *Client) SetBootOverride(bo BootDevice, bootOnce bool) (string, error) {
|
||||
res, _, err := c.doHTTP("", fmt.Sprintf("set=vmBootOnce:%v,firstBootDevice:%d", bootOnce, bo), nil)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (c *Client) Query(ds ...Attribute) (string, error) {
|
||||
bufs := bytes.NewBufferString("")
|
||||
for idx, attr := range ds {
|
||||
bufs.WriteString(string(attr))
|
||||
if idx < len(ds)-1 {
|
||||
bufs.WriteString(",")
|
||||
}
|
||||
}
|
||||
|
||||
res, _, err := c.doHTTP("", fmt.Sprintf("get=%s", bufs.String()), nil)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func NewFromCredentials(path string) (*Client, error) {
|
||||
credential, err := LoadCredentials(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.New("You should log in first")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c, err := NewClient(credential.Host, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.AuthToken = credential.AuthToken
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func NewClient(host string, skipVerify bool) (*Client, error) {
|
||||
c := &http.Client{
|
||||
Timeout: time.Second * 5,
|
||||
Transport: &http.Transport{
|
||||
IdleConnTimeout: time.Second,
|
||||
|
||||
TLSHandshakeTimeout: time.Second * 5,
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: skipVerify,
|
||||
},
|
||||
},
|
||||
}
|
||||
client := &Client{client: c, Host: host}
|
||||
// err := client.Login(host, username, password)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (c *Client) Login(username, password string) (string, error) {
|
||||
body := bytes.NewBufferString(fmt.Sprintf("user=%s&password=%s", username, password))
|
||||
_, cookies, err := c.doHTTP("login", "", body)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == "_appwebSessionId_" {
|
||||
c.AuthToken = cookie.Value
|
||||
return cookie.Value, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("could not find auth token in cookie")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,14 @@ import (
|
|||
"os"
|
||||
)
|
||||
|
||||
//Credential represents credentials for the credentials json
|
||||
type Credential struct {
|
||||
Host string
|
||||
Username string
|
||||
AuthToken string
|
||||
}
|
||||
|
||||
//LoadCredentials from a credentials json file
|
||||
func LoadCredentials(path string) (*Credential, error) {
|
||||
fp := fmt.Sprintf("%s/credentials.json", path)
|
||||
f, err := os.Open(fp)
|
||||
|
|
@ -28,6 +31,7 @@ func LoadCredentials(path string) (*Credential, error) {
|
|||
return credential, nil
|
||||
}
|
||||
|
||||
//SaveCredentials saves credentials to a json file
|
||||
func SaveCredentials(path string, c Credential) error {
|
||||
f, err := os.Create(fmt.Sprintf("%s/credentials.json", path))
|
||||
if err != nil {
|
||||
|
|
|
|||
30
main.go
30
main.go
|
|
@ -12,11 +12,12 @@ import (
|
|||
|
||||
var (
|
||||
commands = map[string]func(map[string][]string) error{
|
||||
"login": loginAction,
|
||||
"logout": logoutAction,
|
||||
"power": powerStateAction,
|
||||
"query": queryAction,
|
||||
"help": helpAction,
|
||||
"login": loginAction,
|
||||
"logout": logoutAction,
|
||||
"console": consoleAction,
|
||||
"power": powerStateAction,
|
||||
"query": queryAction,
|
||||
"help": helpAction,
|
||||
}
|
||||
|
||||
queryHelp = []Attribute{
|
||||
|
|
@ -60,7 +61,7 @@ var (
|
|||
)
|
||||
|
||||
func main() {
|
||||
c, err := ToCommand(os.Args[1:]...)
|
||||
c, err := toCommand(os.Args[1:]...)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
os.Exit(-1)
|
||||
|
|
@ -172,6 +173,15 @@ func queryAction(args map[string][]string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func consoleAction(args map[string][]string) error {
|
||||
c, err := NewFromCredentials(".")
|
||||
if err != nil || os.IsNotExist(err) {
|
||||
return errors.New("you are already logged in")
|
||||
}
|
||||
|
||||
return c.OpenConsole()
|
||||
}
|
||||
|
||||
func loginAction(args map[string][]string) error {
|
||||
u, ok1 := args["u"]
|
||||
p, ok2 := args["p"]
|
||||
|
|
@ -236,17 +246,17 @@ func helpAction(args map[string][]string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type Command struct {
|
||||
type command struct {
|
||||
Name string
|
||||
Arguments map[string][]string
|
||||
}
|
||||
|
||||
func ToCommand(args ...string) (Command, error) {
|
||||
func toCommand(args ...string) (command, error) {
|
||||
if len(args) < 1 {
|
||||
return Command{}, nil
|
||||
return command{}, nil
|
||||
|
||||
}
|
||||
c := Command{
|
||||
c := command{
|
||||
Name: args[0],
|
||||
Arguments: map[string][]string{},
|
||||
}
|
||||
|
|
|
|||
16
main_test.go
16
main_test.go
|
|
@ -6,17 +6,17 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func Test_ToCommand(t *testing.T) {
|
||||
func Test_toCommand(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args string
|
||||
want Command
|
||||
want command
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "login works",
|
||||
args: "login -u root -p calvin 10.0.0.5",
|
||||
want: Command{
|
||||
want: command{
|
||||
Name: "login",
|
||||
Arguments: map[string][]string{
|
||||
"u": []string{"root"},
|
||||
|
|
@ -29,7 +29,7 @@ func Test_ToCommand(t *testing.T) {
|
|||
{
|
||||
name: "boot settings",
|
||||
args: "boot_settings -once local_cd",
|
||||
want: Command{
|
||||
want: command{
|
||||
Name: "boot_settings",
|
||||
Arguments: map[string][]string{
|
||||
"once": []string{"true"},
|
||||
|
|
@ -41,7 +41,7 @@ func Test_ToCommand(t *testing.T) {
|
|||
{
|
||||
name: "query multi value",
|
||||
args: "query pwState kvmEnabled voltages temperatures",
|
||||
want: Command{
|
||||
want: command{
|
||||
Name: "query",
|
||||
Arguments: map[string][]string{
|
||||
"": []string{"pwState", "kvmEnabled", "voltages", "temperatures"},
|
||||
|
|
@ -53,13 +53,13 @@ func Test_ToCommand(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
args := strings.Split(tt.args, " ")
|
||||
got, err := ToCommand(args...)
|
||||
got, err := toCommand(args...)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ToCommand() error = %v, wantErr %v", err, tt.wantErr)
|
||||
t.Errorf("toCommand() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("ToCommand() = %v, want %v", got, tt.want)
|
||||
t.Errorf("toCommand() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
15
makefile
15
makefile
|
|
@ -1,5 +1,6 @@
|
|||
.PHONY: clean debug dev test
|
||||
.PHONY: check clean debug dev test
|
||||
|
||||
ci: clean setup lint test
|
||||
|
||||
clean:
|
||||
@rm -rf ./dracli
|
||||
|
|
@ -10,7 +11,15 @@ debug:
|
|||
dlv debug --headless --api-version=2 -l 127.0.0.1:2456 -- query pwState
|
||||
|
||||
dracli:
|
||||
@go build -o dracli
|
||||
@go build -o dracli
|
||||
|
||||
test:
|
||||
lint:
|
||||
go get golang.org/x/lint/golint
|
||||
golint -min_confidence 1.5 -set_exit_status
|
||||
go vet -all -v
|
||||
|
||||
setup:
|
||||
@go get -t -d -v ./...
|
||||
|
||||
test:
|
||||
@go test -v -cover
|
||||
Loading…
Reference in New Issue