Giter Club home page Giter Club logo

go.nut's Introduction

GoDoc

go.nut

go.nut is a Golang library for interacting with NUT (Network UPS Tools)

Getting started

import "github.com/robbiet480/go.nut"

Check out the examples in example_test.go. For full documentation, see the Godocs.

Other resources

Contributing

  1. Fork it
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Make sure golint and go vet run successfully.
  4. go fmt your code!
  5. Commit your changes (git commit -am "Add some feature")
  6. Push to the branch (git push origin my-new-feature)
  7. Create a new Pull Request

License

MIT

go.nut's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

go.nut's Issues

Index out of bounds error when rate limited by Synology NUT server

Hey there! An issue was reported in nut_exporter a few days back that traces back to an index out of bounds in the go.nut client. I have asked for additional information to see how to defend against the issue, but a quick look at the source code suggests we could simply just check for length greater than 0 and/or err != nil before attempting to reference the response value in index 0.

I'll open a PR to do this checking, but may follow up with another once I know some more about the actual response that came back.

Errors with spaces in UPS desc

Using this library via nut_exporter, I was getting errors with my UPSs:
ts=2024-03-28T15:25:22.903Z caller=nut_collector.go:131 level=error msg="Failure getting the list of UPS devices" err="error reading response: EOF"
I tracked it down to getting the list of UPSs. When I have spaces in the description, it seems to be failing. When I changed the spaces to underscores, it worked.

This fails with EOF:

Trying ::1...
Connected to localhost.
Escape character is '^]'.
LIST UPS
BEGIN LIST UPS
UPS hotwater "Hotwater UPS"
UPS pi "Raspberry Pi UPSPlus"
END LIST UPS
Connection closed by foreign host.

This works:

Trying ::1...
Connected to localhost.
Escape character is '^]'.
LIST UPS
BEGIN LIST UPS
UPS hotwater "Hotwater_UPS"
UPS pi "RaspberryPi_UPSPlus"
END LIST UPS
Connection closed by foreign host.

Addition of Socket Timeout Support (incl. Patch)

Hi everyone, I'm raising this issue on behalf of @Malinskiy Anton Malinskiy

He has created a patch which adds socket timeouts and is being depended on for a Telegraf plugin that implements monitoring of UPS via nut.go here influxdata/telegraf#9890 and the Telegraf maintainers would prefer the plugin depend on the original repo rather than a fork that creates another point of failure.

timeout.txt <- (txt file as Github won't let you upload patch files ๐Ÿค” )
which originates from Malinskiy@bb4669c

so some changes will probably needed to repoint the code back at this module, rather than his fork.
I'm a beginner at Go, I could give it a try, but I think I'd rather let someone who is more adept than myself do it for fear of introducing a breaking change.

From bb4669c66e1ed80138a6326b3f5185f063f07d46 Mon Sep 17 00:00:00 2001
From: Anton Malinskiy <[email protected]>
Date: Sun, 3 Oct 2021 19:56:06 +1100
Subject: [PATCH] feat(nut): implement socket timeouts

---
 example_test.go |  7 +++----
 go.mod          |  3 +++
 nut.go          | 47 +++++++++++++++++++++++++++--------------------
 3 files changed, 33 insertions(+), 24 deletions(-)
 create mode 100644 go.mod

diff --git a/example_test.go b/example_test.go
index 6a23689..31936a1 100644
--- a/example_test.go
+++ b/example_test.go
@@ -2,17 +2,16 @@ package nut
 
 import (
        "fmt"
-
-       nut "github.com/robbiet480/go.nut"
+       "time"
 )
 
 // This example connects to NUT, authenticates and returns the first UPS listed.
 func ExampleGetUPSList() {
-       client, connectErr := nut.Connect("127.0.0.1")
+       client, connectErr := Connect("127.0.0.1", 10*time.Second, 30*time.Second)
        if connectErr != nil {
                fmt.Print(connectErr)
        }
-       _, authenticationError = client.Authenticate("username", "password")
+       _, authenticationError := client.Authenticate("username", "password")
        if authenticationError != nil {
                fmt.Print(authenticationError)
        }
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..cba4e3e
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,3 @@
+module github.com/Malinskiy/go.nut
+
+go 1.17
diff --git a/nut.go b/nut.go
index 34fd406..9705867 100644
--- a/nut.go
+++ b/nut.go
@@ -8,32 +8,33 @@ import (
        "fmt"
        "net"
        "strings"
+       "time"
 )
 
 // Client contains information about the NUT server as well as the connection.
 type Client struct {
-       Version         string
-       ProtocolVersion string
-       Hostname        net.Addr
-       conn            *net.TCPConn
+       opTimeout time.Duration
+       conn      net.Conn
 }
 
 // Connect accepts a hostname/IP string and creates a connection to NUT, returning a Client.
-func Connect(hostname string) (Client, error) {
-       tcpAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("%s:3493", hostname))
+func Connect(hostname string, connectTimeout time.Duration, opTimeout time.Duration) (*Client, error) {
+       _, _, err := net.SplitHostPort(hostname)
        if err != nil {
-               return Client{}, err
+               hostname = net.JoinHostPort(hostname, "3493")
        }
-       conn, err := net.DialTCP("tcp", nil, tcpAddr)
+       d := net.Dialer{
+               Timeout: connectTimeout,
+       }
+       conn, err := d.Dial("tcp", hostname)
        if err != nil {
-               return Client{}, err
+               return nil, err
        }
-       client := Client{
-               Hostname: conn.RemoteAddr(),
-               conn:     conn,
+
+       client := &Client{
+               opTimeout: opTimeout,
+               conn:      conn,
        }
-       client.GetVersion()
-       client.GetNetworkProtocolVersion()
        return client, nil
 }
 
@@ -55,6 +56,10 @@ func (c *Client) ReadResponse(endLine string, multiLineResponse bool) (resp []st
        response := []string{}
 
        for {
+               err = c.conn.SetReadDeadline(time.Now().Add(c.opTimeout))
+               if err != nil {
+                       return nil, err
+               }
                line, err := connbuff.ReadString('\n')
                if err != nil {
                        return nil, fmt.Errorf("error reading response: %v", err)
@@ -79,18 +84,22 @@ func (c *Client) SendCommand(cmd string) (resp []string, err error) {
        if strings.HasPrefix(cmd, "USERNAME ") || strings.HasPrefix(cmd, "PASSWORD ") || strings.HasPrefix(cmd, "SET ") || strings.HasPrefix(cmd, "HELP ") || strings.HasPrefix(cmd, "VER ") || strings.HasPrefix(cmd, "NETVER ") {
                endLine = "OK\n"
        }
-       _, err = fmt.Fprint(c.conn, cmd)
+       err = c.conn.SetWriteDeadline(time.Now().Add(c.opTimeout))
+       if err != nil {
+               return nil, err
+       }
+       _, err = c.conn.Write([]byte(cmd))
        if err != nil {
-               return []string{}, err
+               return nil, err
        }
 
        resp, err = c.ReadResponse(endLine, strings.HasPrefix(cmd, "LIST "))
        if err != nil {
-               return []string{}, err
+               return nil, err
        }
 
        if strings.HasPrefix(resp[0], "ERR ") {
-               return []string{}, errorForMessage(strings.Split(resp[0], " ")[1])
+               return nil, errorForMessage(strings.Split(resp[0], " ")[1])
        }
 
        return resp, nil
@@ -141,13 +150,11 @@ func (c *Client) Help() (string, error) {
 // GetVersion returns the the version of the server currently in use.
 func (c *Client) GetVersion() (string, error) {
        versionResponse, err := c.SendCommand("VER")
-       c.Version = versionResponse[0]
        return versionResponse[0], err
 }
 
 // GetNetworkProtocolVersion returns the version of the network protocol currently in use.
 func (c *Client) GetNetworkProtocolVersion() (string, error) {
        versionResponse, err := c.SendCommand("NETVER")
-       c.ProtocolVersion = versionResponse[0]
        return versionResponse[0], err
 }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.