-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient.go
More file actions
129 lines (111 loc) · 3.7 KB
/
client.go
File metadata and controls
129 lines (111 loc) · 3.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package eos
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
func New(_ context.Context, cfg Config) (*Client, error) {
timeout := cfg.Timeout
if timeout <= 0 {
timeout = 15 * time.Second
}
c := &Client{
sshTarget: cfg.SSHTarget,
timeout: timeout,
acceptNewHostKeys: cfg.AcceptNewHostKeys,
runner: execCommandRunner{},
}
c.sessionLogPath = initSessionLog()
return c, nil
}
// initSessionLog creates ~/.eos-tui/sessions/ if needed, generates a
// timestamped log file path for this session, and updates the
// ~/.eos-tui/latest.log symlink to point at it.
// Returns the session log path, or "" if setup fails (logging silently disabled).
func initSessionLog() string {
home, err := os.UserHomeDir()
if err != nil {
return ""
}
logDir := filepath.Join(home, ".eos-tui", "sessions")
if err := os.MkdirAll(logDir, 0755); err != nil {
return ""
}
// Use a timestamp that is both human-readable and filesystem-safe.
ts := time.Now().Format("2006-01-02T15-04-05")
sessionFile := filepath.Join(logDir, ts+".log")
// Create the file immediately so the symlink target exists.
f, err := os.OpenFile(sessionFile, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return ""
}
f.Close()
// Update ~/.eos-tui/latest.log → sessions/<timestamp>.log (relative symlink).
latestLink := filepath.Join(home, ".eos-tui", "latest.log")
// Relative target from ~/.eos-tui/ to sessions/<ts>.log
relTarget := filepath.Join("sessions", ts+".log")
// Remove stale symlink (or file) then re-create.
_ = os.Remove(latestLink)
_ = os.Symlink(relTarget, latestLink)
return sessionFile
}
func (c *Client) Close() error {
return nil
}
// effectiveSSHTarget returns the host that runCommand will actually SSH to.
func (c *Client) effectiveSSHTarget() string {
if c.resolvedSSHTarget != "" {
return c.resolvedSSHTarget
}
return c.sshTarget
}
// ResolvedSSHTarget returns the effective SSH target after master discovery,
// or the original target if discovery has not run.
func (c *Client) ResolvedSSHTarget() string {
return c.effectiveSSHTarget()
}
// OriginalSSHTarget returns the user-supplied SSH target before master discovery.
func (c *Client) OriginalSSHTarget() string {
return c.sshTarget
}
// ensureRootPrefix returns target with a "root@" prefix, adding one only if
// it is not already present.
func ensureRootPrefix(target string) string {
if strings.HasPrefix(target, "root@") {
return target
}
return "root@" + target
}
// DiscoverMGMMaster runs `redis-cli raft-info` on the current SSH target,
// identifies the QDB/MGM leader, and updates the client so that all subsequent
// commands are routed directly to the leader host.
// Returns the resolved hostname (e.g. "eospilot-ns-02.cern.ch").
func (c *Client) DiscoverMGMMaster(ctx context.Context) (string, error) {
output, err := c.runCommandContext(ctx, "redis-cli", "-p", "7777", "raft-info")
if err != nil {
return "", fmt.Errorf("raft-info for master discovery: %w", err)
}
// QDB may require authentication — fall back to the current SSH target.
if strings.Contains(string(output), "NOAUTH") {
target := c.effectiveSSHTarget()
if target == "" {
return "", fmt.Errorf("raft-info requires authentication and no SSH target is configured")
}
resolved := ensureRootPrefix(target)
c.resolvedSSHTarget = resolved
return resolved, nil
}
info := parseRaftInfo(output)
if info.Leader == "" {
return "", fmt.Errorf("no leader found in raft-info output")
}
leader := hostOnly(info.Leader)
// EOS nodes run as root; use explicit root@ so the resolved hostname
// works without relying on SSH config aliases.
resolved := ensureRootPrefix(leader)
c.resolvedSSHTarget = resolved
return resolved, nil
}