...
1 package emulator
2
3 import (
4 "context"
5 "errors"
6 "fmt"
7 "os"
8 "os/exec"
9 "path"
10 "path/filepath"
11 "time"
12
13 "github.com/go-logr/logr"
14
15 "edge-infra.dev/pkg/lib/fog"
16 "edge-infra.dev/pkg/sds/emergencyaccess/msgdata"
17 "edge-infra.dev/pkg/sds/lib/colors"
18 )
19
20
21
22
23
24
25
26
27
28
29 const (
30 historyFileLimit = 5000
31 sessionTimeoutDefault = "300s"
32
33 connectHistoryFileName = "rcli_connect_history"
34 sessionHistoryFileName = "rcli_session_history"
35 shellHistoryFileName = "rcli_shell_history"
36 workspaceFileName = "rcli_workspace.json"
37
38 UNSET = "UNSET"
39 )
40
41 var (
42 ErrorRCLINoSubcmd = errors.New("no subcommands")
43 ErrorRCLIUnknownSubcmd = errors.New("unrecognised subcommand")
44
45
46 exitCommands = []string{"q", "end", "exit"}
47 )
48
49 type CLIService interface {
50 Connect(ctx context.Context, projectID string, bannerID string, storeID string, terminalID string) error
51 Send(command string) (commandID string, err error)
52 SetTopicTemplate(topicTemplate string)
53 SetSubscriptionTemplate(subscriptionTemplate string)
54 RetrieveIdentity(context.Context) error
55 GetDisplayChannel() <-chan msgdata.CommandResponse
56 GetSessionContext() context.Context
57 UserID() string
58 IdleTime() time.Duration
59 EnablePerSessionSubscription()
60 DisablePerSessionSubscription()
61 End() error
62
63
64
65 Env() []string
66 }
67
68 type DarkModeService interface {
69 SetDarkmode(bool)
70 Darkmode() bool
71 }
72
73
74 func handleExit() {
75 rawModeOff := exec.Command("/bin/stty", "-raw", "echo")
76 rawModeOff.Stdin = os.Stdin
77 _ = rawModeOff.Run()
78 }
79
80 func isPhase1() bool {
81
82 return path.Base(os.Args[0]) == "remotecliv1"
83 }
84
85
86
87 type Emulator struct {
88 cls CLIService
89 runCtx context.Context
90 log logr.Logger
91
92
93
94 config Config
95 sessionHistory commandHistory
96 connectHistory commandHistory
97 shellHistory commandHistory
98 workspace *workspace
99 sessionPromptPaused bool
100
101 commandOptions map[string]CommandOpts
102
103
104 connectData connectionData
105 }
106
107
108 func NewEmulator(ctx context.Context, cls CLIService, config Config) Emulator {
109 return Emulator{
110 cls: cls,
111 log: fog.FromContext(ctx).WithName("emulator"),
112 config: config,
113 commandOptions: map[string]CommandOpts{},
114 }
115 }
116
117 func (em *Emulator) sessionPath() string { return filepath.Join(em.config.dir, sessionHistoryFileName) }
118 func (em *Emulator) connectPath() string { return filepath.Join(em.config.dir, connectHistoryFileName) }
119 func (em *Emulator) shellHistPath() string { return filepath.Join(em.config.dir, shellHistoryFileName) }
120 func (em *Emulator) workspacePath() string { return filepath.Join(em.config.dir, workspaceFileName) }
121
122
123
124 func (em *Emulator) Run(ctx context.Context) {
125 defer handleExit()
126 em.runCtx = ctx
127
128
129 err := em.cls.RetrieveIdentity(ctx)
130 if err != nil {
131 em.log.Error(err, "Failed to retrieve valid BSL token")
132 fmt.Println(colors.Text("Error authenticating user. See logs for more detail.", colors.BgRed))
133 return
134 }
135
136
137 em.loadWorkspace(em.connectPath(), em.sessionPath(), em.workspacePath())
138
139 em.setupCompleters()
140
141 if em.workspace.DisablePerSessionSubscription {
142 em.cls.DisablePerSessionSubscription()
143 }
144
145 em.displayConnectBanner()
146 em.connectPrompt(em.connectHistory.history).Run()
147
148 err = em.workspace.save(em.workspacePath())
149 if err != nil {
150 em.log.Error(err, "Error saving workspace")
151 }
152 }
153
154 func (em *Emulator) loadWorkspace(connectFile, sessionFile, workspaceFile string) {
155 var err error
156 em.connectHistory, err = newCommandHistory(connectFile)
157 if err != nil {
158 em.log.Error(err, "Error establishing remote cli history")
159 }
160
161 em.sessionHistory, err = newCommandHistory(sessionFile)
162 if err != nil {
163 em.log.Error(err, "Error establishing session prompt history")
164 }
165
166 em.shellHistory, err = newCommandHistory(em.shellHistPath())
167 if err != nil {
168 em.log.Error(err, "Error establishing shell prompt history")
169 }
170
171 em.workspace, err = newWorkspace(workspaceFile)
172 if err != nil {
173 em.log.Error(err, "Error establishing workspace")
174 }
175 }
176
177 func getDarkmode(cls CLIService) bool {
178 if dm, ok := cls.(DarkModeService); ok {
179 return dm.Darkmode()
180 }
181 return false
182 }
183
184
185
186 func (em *Emulator) setupCompleters() {
187 if isPhase1() {
188 rcliconfigCompleter = append(rcliconfigCompleter, rcliConfigCompleterPhase1...)
189 } else {
190 topLevelConnectCommands = append(topLevelConnectCommands, connectCommandsCompleterPhase2...)
191 }
192 }
193
View as plain text