github.com/netdata/go.d.plugin@v0.58.1/modules/upsd/client.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package upsd
     4  
     5  import (
     6  	"encoding/csv"
     7  	"errors"
     8  	"fmt"
     9  	"strings"
    10  
    11  	"github.com/netdata/go.d.plugin/pkg/socket"
    12  )
    13  
    14  const (
    15  	commandUsername = "USERNAME %s"
    16  	commandPassword = "PASSWORD %s"
    17  	commandListUPS  = "LIST UPS"
    18  	commandListVar  = "LIST VAR %s"
    19  	commandLogout   = "LOGOUT"
    20  )
    21  
    22  // https://github.com/networkupstools/nut/blob/81fca30b2998fa73085ce4654f075605ff0b9e01/docs/net-protocol.txt#L647
    23  var errUpsdCommand = errors.New("upsd command error")
    24  
    25  type upsUnit struct {
    26  	name string
    27  	vars map[string]string
    28  }
    29  
    30  func newUpsdConn(conf Config) upsdConn {
    31  	return &upsdClient{conn: socket.New(socket.Config{
    32  		ConnectTimeout: conf.Timeout.Duration,
    33  		ReadTimeout:    conf.Timeout.Duration,
    34  		WriteTimeout:   conf.Timeout.Duration,
    35  		Address:        conf.Address,
    36  	})}
    37  }
    38  
    39  type upsdClient struct {
    40  	conn socket.Client
    41  }
    42  
    43  func (c *upsdClient) connect() error {
    44  	return c.conn.Connect()
    45  }
    46  
    47  func (c *upsdClient) disconnect() error {
    48  	_, _ = c.sendCommand(commandLogout)
    49  	return c.conn.Disconnect()
    50  }
    51  
    52  func (c *upsdClient) authenticate(username, password string) error {
    53  	cmd := fmt.Sprintf(commandUsername, username)
    54  	resp, err := c.sendCommand(cmd)
    55  	if err != nil {
    56  		return err
    57  	}
    58  	if resp[0] != "OK" {
    59  		return errors.New("authentication failed: invalid username")
    60  	}
    61  
    62  	cmd = fmt.Sprintf(commandPassword, password)
    63  	resp, err = c.sendCommand(cmd)
    64  	if err != nil {
    65  		return err
    66  	}
    67  	if resp[0] != "OK" {
    68  		return errors.New("authentication failed: invalid password")
    69  	}
    70  
    71  	return nil
    72  }
    73  
    74  func (c *upsdClient) upsUnits() ([]upsUnit, error) {
    75  	resp, err := c.sendCommand(commandListUPS)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	var upsNames []string
    81  
    82  	for _, v := range resp {
    83  		if !strings.HasPrefix(v, "UPS ") {
    84  			continue
    85  		}
    86  		parts := splitLine(v)
    87  		if len(parts) < 2 {
    88  			continue
    89  		}
    90  		name := parts[1]
    91  		upsNames = append(upsNames, name)
    92  	}
    93  
    94  	var upsUnits []upsUnit
    95  
    96  	for _, name := range upsNames {
    97  		cmd := fmt.Sprintf(commandListVar, name)
    98  		resp, err := c.sendCommand(cmd)
    99  		if err != nil {
   100  			return nil, err
   101  		}
   102  
   103  		ups := upsUnit{
   104  			name: name,
   105  			vars: make(map[string]string),
   106  		}
   107  
   108  		upsUnits = append(upsUnits, ups)
   109  
   110  		for _, v := range resp {
   111  			if !strings.HasPrefix(v, "VAR ") {
   112  				continue
   113  			}
   114  			parts := splitLine(v)
   115  			if len(parts) < 4 {
   116  				continue
   117  			}
   118  			n, v := parts[2], parts[3]
   119  			ups.vars[n] = v
   120  		}
   121  	}
   122  
   123  	return upsUnits, nil
   124  }
   125  
   126  func (c *upsdClient) sendCommand(cmd string) ([]string, error) {
   127  	var resp []string
   128  	var errMsg string
   129  	endLine := getEndLine(cmd)
   130  
   131  	err := c.conn.Command(cmd+"\n", func(bytes []byte) bool {
   132  		line := string(bytes)
   133  		resp = append(resp, line)
   134  
   135  		if strings.HasPrefix(line, "ERR ") {
   136  			errMsg = strings.TrimPrefix(line, "ERR ")
   137  		}
   138  
   139  		return line != endLine && errMsg == ""
   140  	})
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	if errMsg != "" {
   145  		return nil, fmt.Errorf("%w: %s (cmd: '%s')", errUpsdCommand, errMsg, cmd)
   146  	}
   147  
   148  	return resp, nil
   149  }
   150  
   151  func getEndLine(cmd string) string {
   152  	px, _, _ := strings.Cut(cmd, " ")
   153  
   154  	switch px {
   155  	case "USERNAME", "PASSWORD", "VER":
   156  		return "OK"
   157  	}
   158  	return fmt.Sprintf("END %s", cmd)
   159  }
   160  
   161  func splitLine(s string) []string {
   162  	r := csv.NewReader(strings.NewReader(s))
   163  	r.Comma = ' '
   164  
   165  	parts, _ := r.Read()
   166  
   167  	return parts
   168  }