refactor some code
This commit is contained in:
parent
37f411ec55
commit
58d1af4f84
12 changed files with 47 additions and 28 deletions
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
|
@ -39,6 +39,11 @@ jobs:
|
||||||
- name: Vet
|
- name: Vet
|
||||||
run: go vet ./...
|
run: go vet ./...
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
uses: golangci/golangci-lint-action@v7
|
||||||
|
with:
|
||||||
|
version: latest
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: go test -v ./...
|
run: go test -v ./...
|
||||||
|
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,6 +1,7 @@
|
||||||
# Go
|
# Go
|
||||||
towerops-agent
|
towerops-agent
|
||||||
*.test
|
*.test
|
||||||
|
cover.out
|
||||||
|
|
||||||
# Database files
|
# Database files
|
||||||
*.db
|
*.db
|
||||||
|
|
|
||||||
2
agent.go
2
agent.go
|
|
@ -65,7 +65,7 @@ func runSession(ctx context.Context, baseURL, token string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("connect: %w", err)
|
return fmt.Errorf("connect: %w", err)
|
||||||
}
|
}
|
||||||
defer ws.Close()
|
defer func() { _ = ws.Close() }()
|
||||||
|
|
||||||
agentID := fmt.Sprintf("agent-%d", time.Now().Unix())
|
agentID := fmt.Sprintf("agent-%d", time.Now().Unix())
|
||||||
topic := "agent:" + agentID
|
topic := "agent:" + agentID
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
pkgs.go
|
pkgs.go
|
||||||
|
pkgs.golangci-lint
|
||||||
pkgs.protobuf
|
pkgs.protobuf
|
||||||
pkgs.protoc-gen-go
|
pkgs.protoc-gen-go
|
||||||
pkgs.git
|
pkgs.git
|
||||||
|
|
|
||||||
13
mikrotik.go
13
mikrotik.go
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -42,7 +43,7 @@ func mikrotikConnect(ip string, port uint32, username, password string, useSSL b
|
||||||
NetDialer: &net.Dialer{Timeout: mikrotikConnTimeout},
|
NetDialer: &net.Dialer{Timeout: mikrotikConnTimeout},
|
||||||
Config: &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS12},
|
Config: &tls.Config{InsecureSkipVerify: true, MinVersion: tls.VersionTLS12},
|
||||||
}
|
}
|
||||||
conn, err = dialer.DialContext(nil, "tcp", addr)
|
conn, err = dialer.DialContext(context.Background(), "tcp", addr)
|
||||||
} else {
|
} else {
|
||||||
conn, err = net.DialTimeout("tcp", addr, mikrotikConnTimeout)
|
conn, err = net.DialTimeout("tcp", addr, mikrotikConnTimeout)
|
||||||
}
|
}
|
||||||
|
|
@ -55,11 +56,11 @@ func mikrotikConnect(ip string, port uint32, username, password string, useSSL b
|
||||||
// Authenticate
|
// Authenticate
|
||||||
resp, err := c.execute("/login", map[string]string{"name": username, "password": password})
|
resp, err := c.execute("/login", map[string]string{"name": username, "password": password})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return nil, fmt.Errorf("auth: %w", err)
|
return nil, fmt.Errorf("auth: %w", err)
|
||||||
}
|
}
|
||||||
if resp.err != "" {
|
if resp.err != "" {
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return nil, fmt.Errorf("auth failed: %s", resp.err)
|
return nil, fmt.Errorf("auth failed: %s", resp.err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,7 +86,7 @@ func (c *mikrotikClient) execute(command string, args map[string]string) (*mikro
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *mikrotikClient) close() error {
|
func (c *mikrotikClient) close() error {
|
||||||
c.execute("/quit", nil) // best-effort
|
_, _ = c.execute("/quit", nil) // best-effort
|
||||||
return c.conn.Close()
|
return c.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -145,7 +146,7 @@ func (c *mikrotikClient) readSentence() ([]string, error) {
|
||||||
var words []string
|
var words []string
|
||||||
for {
|
for {
|
||||||
if tc, ok := c.conn.(net.Conn); ok {
|
if tc, ok := c.conn.(net.Conn); ok {
|
||||||
tc.SetReadDeadline(time.Now().Add(mikrotikReadTimeout))
|
_ = tc.SetReadDeadline(time.Now().Add(mikrotikReadTimeout))
|
||||||
}
|
}
|
||||||
word, err := c.readWord()
|
word, err := c.readWord()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -268,7 +269,7 @@ func executeMikrotikJob(job *pb.AgentJob, resultCh chan<- *pb.MikrotikResult) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer client.close()
|
defer func() { _ = client.close() }()
|
||||||
|
|
||||||
var allSentences []*pb.MikrotikSentence
|
var allSentences []*pb.MikrotikSentence
|
||||||
var errorMessage string
|
var errorMessage string
|
||||||
|
|
|
||||||
11
snmp.go
11
snmp.go
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gosnmp/gosnmp"
|
"github.com/gosnmp/gosnmp"
|
||||||
"github.com/towerops-app/towerops-agent/pb"
|
"github.com/towerops-app/towerops-agent/pb"
|
||||||
|
|
@ -22,7 +23,7 @@ func executeSnmpJob(job *pb.AgentJob, resultCh chan<- *pb.SnmpResult) {
|
||||||
slog.Error("snmp connect", "job_id", job.JobId, "device", dev.Ip, "error", err)
|
slog.Error("snmp connect", "job_id", job.JobId, "device", dev.Ip, "error", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Conn.Close()
|
defer func() { _ = conn.Conn.Close() }()
|
||||||
|
|
||||||
oidValues := make(map[string]string)
|
oidValues := make(map[string]string)
|
||||||
|
|
||||||
|
|
@ -96,7 +97,7 @@ func executeCredentialTest(job *pb.AgentJob, resultCh chan<- *pb.CredentialTestR
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer conn.Conn.Close()
|
defer func() { _ = conn.Conn.Close() }()
|
||||||
|
|
||||||
result, err := conn.Get([]string{"1.3.6.1.2.1.1.1.0"})
|
result, err := conn.Get([]string{"1.3.6.1.2.1.1.1.0"})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -221,10 +222,12 @@ func snmpValueToString(pdu gosnmp.SnmpPDU) string {
|
||||||
return fmt.Sprintf("%d", gosnmp.ToBigInt(pdu.Value).Int64())
|
return fmt.Sprintf("%d", gosnmp.ToBigInt(pdu.Value).Int64())
|
||||||
case gosnmp.OctetString:
|
case gosnmp.OctetString:
|
||||||
b := pdu.Value.([]byte)
|
b := pdu.Value.([]byte)
|
||||||
// Try UTF-8 first
|
if !utf8.Valid(b) {
|
||||||
|
return formatHex(b)
|
||||||
|
}
|
||||||
|
// Check for non-printable control chars
|
||||||
for _, c := range b {
|
for _, c := range b {
|
||||||
if c < 0x20 && c != '\n' && c != '\r' && c != '\t' {
|
if c < 0x20 && c != '\n' && c != '\r' && c != '\t' {
|
||||||
// Non-printable - return hex
|
|
||||||
return formatHex(b)
|
return formatHex(b)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,11 @@ func TestSnmpValueToString(t *testing.T) {
|
||||||
pdu: gosnmp.SnmpPDU{Type: gosnmp.NoSuchObject, Value: nil},
|
pdu: gosnmp.SnmpPDU{Type: gosnmp.NoSuchObject, Value: nil},
|
||||||
want: "null",
|
want: "null",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "invalid utf8",
|
||||||
|
pdu: gosnmp.SnmpPDU{Type: gosnmp.OctetString, Value: []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x80, 0xFE}},
|
||||||
|
want: "48:65:6c:6c:6f:80:fe",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
||||||
4
ssh.go
4
ssh.go
|
|
@ -23,13 +23,13 @@ func executeMikrotikBackup(ip string, port uint16, username, password string) (s
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("ssh dial %s: %w", addr, err)
|
return "", fmt.Errorf("ssh dial %s: %w", addr, err)
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer func() { _ = conn.Close() }()
|
||||||
|
|
||||||
session, err := conn.NewSession()
|
session, err := conn.NewSession()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("ssh session: %w", err)
|
return "", fmt.Errorf("ssh session: %w", err)
|
||||||
}
|
}
|
||||||
defer session.Close()
|
defer func() { _ = session.Close() }()
|
||||||
|
|
||||||
output, err := session.CombinedOutput("/export compact")
|
output, err := session.CombinedOutput("/export compact")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ func selfUpdate(downloadURL, expectedChecksum string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("download: %w", err)
|
return fmt.Errorf("download: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer func() { _ = resp.Body.Close() }()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("download failed: status %d", resp.StatusCode)
|
return fmt.Errorf("download failed: status %d", resp.StatusCode)
|
||||||
|
|
@ -52,7 +52,7 @@ func selfUpdate(downloadURL, expectedChecksum string) error {
|
||||||
|
|
||||||
// Replace current binary
|
// Replace current binary
|
||||||
if err := os.Rename(tempPath, currentExe); err != nil {
|
if err := os.Rename(tempPath, currentExe); err != nil {
|
||||||
os.Remove(tempPath)
|
_ = os.Remove(tempPath)
|
||||||
return fmt.Errorf("rename: %w", err)
|
return fmt.Errorf("rename: %w", err)
|
||||||
}
|
}
|
||||||
slog.Info("binary replaced", "path", currentExe)
|
slog.Info("binary replaced", "path", currentExe)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ func TestSelfUpdateBadURL(t *testing.T) {
|
||||||
|
|
||||||
func TestSelfUpdateChecksumMismatch(t *testing.T) {
|
func TestSelfUpdateChecksumMismatch(t *testing.T) {
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write([]byte("fake binary"))
|
_, _ = w.Write([]byte("fake binary"))
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
@ -32,7 +32,7 @@ func TestSelfUpdateChecksumMatch(t *testing.T) {
|
||||||
checksum := fmt.Sprintf("%x", sha256.Sum256(body))
|
checksum := fmt.Sprintf("%x", sha256.Sum256(body))
|
||||||
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Write(body)
|
_, _ = w.Write(body)
|
||||||
}))
|
}))
|
||||||
defer srv.Close()
|
defer srv.Close()
|
||||||
|
|
||||||
|
|
|
||||||
16
websocket.go
16
websocket.go
|
|
@ -57,7 +57,7 @@ func WSDial(rawURL string) (*WSConn, error) {
|
||||||
// Generate random key for Sec-WebSocket-Key
|
// Generate random key for Sec-WebSocket-Key
|
||||||
keyBytes := make([]byte, 16)
|
keyBytes := make([]byte, 16)
|
||||||
if _, err := rand.Read(keyBytes); err != nil {
|
if _, err := rand.Read(keyBytes); err != nil {
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return nil, fmt.Errorf("generate key: %w", err)
|
return nil, fmt.Errorf("generate key: %w", err)
|
||||||
}
|
}
|
||||||
key := base64.StdEncoding.EncodeToString(keyBytes)
|
key := base64.StdEncoding.EncodeToString(keyBytes)
|
||||||
|
|
@ -67,7 +67,7 @@ func WSDial(rawURL string) (*WSConn, error) {
|
||||||
path, u.Host, key)
|
path, u.Host, key)
|
||||||
|
|
||||||
if _, err := conn.Write([]byte(req)); err != nil {
|
if _, err := conn.Write([]byte(req)); err != nil {
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return nil, fmt.Errorf("write handshake: %w", err)
|
return nil, fmt.Errorf("write handshake: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,12 +75,12 @@ func WSDial(rawURL string) (*WSConn, error) {
|
||||||
buf := make([]byte, 4096)
|
buf := make([]byte, 4096)
|
||||||
n, err := conn.Read(buf)
|
n, err := conn.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return nil, fmt.Errorf("read handshake: %w", err)
|
return nil, fmt.Errorf("read handshake: %w", err)
|
||||||
}
|
}
|
||||||
resp := string(buf[:n])
|
resp := string(buf[:n])
|
||||||
if !strings.Contains(resp, "101") {
|
if !strings.Contains(resp, "101") {
|
||||||
conn.Close()
|
_ = conn.Close()
|
||||||
return nil, fmt.Errorf("handshake failed: %s", strings.SplitN(resp, "\r\n", 2)[0])
|
return nil, fmt.Errorf("handshake failed: %s", strings.SplitN(resp, "\r\n", 2)[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ func (ws *WSConn) ReadMessage() ([]byte, int, error) {
|
||||||
return nil, 0, fmt.Errorf("pong: %w", err)
|
return nil, 0, fmt.Errorf("pong: %w", err)
|
||||||
}
|
}
|
||||||
case opClose:
|
case opClose:
|
||||||
ws.writeFrame(opClose, nil) // best-effort close reply
|
_ = ws.writeFrame(opClose, nil) // best-effort close reply
|
||||||
return nil, opClose, io.EOF
|
return nil, opClose, io.EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -115,7 +115,7 @@ func (ws *WSConn) WriteText(data []byte) error {
|
||||||
|
|
||||||
// Close sends a close frame and closes the underlying connection.
|
// Close sends a close frame and closes the underlying connection.
|
||||||
func (ws *WSConn) Close() error {
|
func (ws *WSConn) Close() error {
|
||||||
ws.writeFrame(opClose, nil) // best-effort
|
_ = ws.writeFrame(opClose, nil) // best-effort
|
||||||
return ws.conn.Close()
|
return ws.conn.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +175,7 @@ func (ws *WSConn) writeFrame(opcode int, payload []byte) error {
|
||||||
// Max header: 2 + 8 + 4 (mask) = 14 bytes
|
// Max header: 2 + 8 + 4 (mask) = 14 bytes
|
||||||
header := make([]byte, 2, 14)
|
header := make([]byte, 2, 14)
|
||||||
header[0] = 0x80 | byte(opcode) // FIN + opcode
|
header[0] = 0x80 | byte(opcode) // FIN + opcode
|
||||||
header[1] = 0x80 // masked (client must mask)
|
header[1] = 0x80 // masked (client must mask)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case length <= 125:
|
case length <= 125:
|
||||||
|
|
@ -194,7 +194,7 @@ func (ws *WSConn) writeFrame(opcode int, payload []byte) error {
|
||||||
|
|
||||||
// Generate mask key
|
// Generate mask key
|
||||||
maskKey := make([]byte, 4)
|
maskKey := make([]byte, 4)
|
||||||
rand.Read(maskKey)
|
_, _ = rand.Read(maskKey)
|
||||||
header = append(header, maskKey...)
|
header = append(header, maskKey...)
|
||||||
|
|
||||||
// Mask payload
|
// Mask payload
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,9 @@ type captureWriter struct {
|
||||||
written []byte
|
written []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *captureWriter) Read(p []byte) (int, error) { return c.Reader.Read(p) }
|
func (c *captureWriter) Read(p []byte) (int, error) { return c.Reader.Read(p) }
|
||||||
func (c *captureWriter) Write(p []byte) (int, error) { c.written = append(c.written, p...); return len(p), nil }
|
func (c *captureWriter) Write(p []byte) (int, error) {
|
||||||
func (c *captureWriter) Close() error { return nil }
|
c.written = append(c.written, p...)
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
func (c *captureWriter) Close() error { return nil }
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue