1 package emulator
2
3 import (
4 "errors"
5 "fmt"
6 "strings"
7
8 "github.com/c-bata/go-prompt"
9 "github.com/google/shlex"
10
11 "edge-infra.dev/pkg/sds/lib/colors"
12 )
13
14
15
16
17
18
19
20
21
22
23 type connectionData struct {
24 projectID string
25 bannerID string
26 storeID string
27 terminalID string
28 }
29
30 var topLevelConnectCommands = []prompt.Suggest{
31 {Text: "connect", Description: "Connect to a new session. See help for further info."},
32 {Text: "rcliconfig", Description: "Sets rcliconfig settings."},
33 {Text: "help", Description: "Display help"},
34 }
35
36 var connectCommandsCompleterPhase2 = []prompt.Suggest{
37 {Text: "shell", Description: "Enter local shell mode to interact with remotecli exec output"},
38 }
39
40 var rcliconfigCompleter = []prompt.Suggest{
41 {Text: "bannerID", Description: "Set default banner ID for workspace."},
42 {Text: "storeID", Description: "Set default store ID for workspace."},
43 }
44
45 var rcliConfigCompleterPhase1 = []prompt.Suggest{
46 {Text: "projectID", Description: "Set default project ID for workspace."},
47 {Text: "enablePerSessionSubscription", Description: "Enables Creating a new Subscription per Session"},
48 {Text: "disablePerSessionSubscription", Description: "Disables Creating a new Subscription per Session"},
49 }
50
51 func connectPromptCompleter(in prompt.Document) []prompt.Suggest {
52 commands, err := shlex.Split(in.TextBeforeCursor())
53 if err != nil {
54
55 return []prompt.Suggest{}
56 }
57
58
59 lenCommands := len(commands)
60
61
62
63 wordidx := lenCommands - 1
64 if in.GetWordBeforeCursor() == "" {
65 wordidx = lenCommands
66 }
67
68
69 command := ""
70 if lenCommands > 0 {
71 command = commands[0]
72 }
73
74 if wordidx < 1 {
75 return prompt.FilterHasPrefix(topLevelConnectCommands, command, true)
76 }
77
78 if command == "rcliconfig" && wordidx < 2 {
79 command2 := ""
80 if len(commands) > 1 {
81 command2 = commands[1]
82 }
83 return prompt.FilterHasPrefix(rcliconfigCompleter, command2, true)
84 }
85
86 if command != "connect" {
87 return []prompt.Suggest{}
88 }
89
90 if wordidx > 4 {
91 return []prompt.Suggest{}
92 }
93
94 return []prompt.Suggest{}
95 }
96
97
98 func (em Emulator) connectPrompt(optionHistory []string) *prompt.Prompt {
99 return prompt.New(em.connectPromptExecutor, connectPromptCompleter,
100 prompt.OptionHistory(optionHistory),
101 prompt.OptionSetExitCheckerOnInput(exitChecker),
102 prompt.OptionPrefix("\r> "), prompt.OptionPrefixTextColor(prompt.Yellow),
103 prompt.OptionPreviewSuggestionTextColor(prompt.Blue),
104 prompt.OptionSelectedSuggestionBGColor(prompt.LightGray),
105 prompt.OptionSuggestionBGColor(prompt.DarkGray),
106 prompt.OptionAddKeyBind(commonKeyBindings...),
107 )
108 }
109
110
111 func (em *Emulator) connectPromptExecutor(in string) {
112 if in == "" {
113 return
114 }
115 if in == "help" {
116 fmt.Println(connectHelp)
117 return
118 }
119 for _, item := range exitCommands {
120 if in == item {
121 em.connectHistory.close()
122 em.sessionHistory.close()
123 em.shellHistory.close()
124 return
125 }
126 }
127
128 err := em.connectHistory.updateHistory(in, historyFileLimit)
129 if err != nil {
130 em.log.Error(err, "Error updating connect history file")
131 }
132 if strings.HasPrefix(in, "dark ") {
133
134 in = in[5:]
135
136 dm, ok := em.cls.(DarkModeService)
137 if !ok {
138 fmt.Println(colors.Text("Error Dark mode not supported.", colors.BgRed))
139 return
140 }
141 fmt.Println(colors.BufferedText("darkmode enabled", colors.BgBrightBlack))
142 dm.SetDarkmode(true)
143
144 defer dm.SetDarkmode(false)
145 }
146 if strings.HasPrefix(in, "connect") {
147 connectData, err := em.parseConnectInput(in)
148 if err != nil {
149 fmt.Println(colors.Text("error whilst parsing connection query: %q. Type help to see more info.", colors.FgRed, err))
150 return
151 }
152 err = em.cls.Connect(em.runCtx, connectData.projectID, connectData.bannerID, connectData.storeID, connectData.terminalID)
153 if err != nil {
154 fmt.Println(colors.Text("Error whilst setting up connection:", colors.BgRed))
155 em.displayError(em.log, err)
156 return
157 }
158 em.startSession()
159 fmt.Println(colors.Text("Exited session", colors.FgGreenBold))
160
161 em.displayConnectBanner()
162 return
163 }
164
165 if strings.HasPrefix(in, "shell") {
166 connectData, err := em.parseConnectInput(in)
167 if err != nil {
168 fmt.Println(colors.Text("error whilst parsing connection query: %q. Type help to see more info.", colors.FgRed, err))
169 return
170 }
171
172 err = em.startShell(connectData)
173 if err != nil {
174 fmt.Println(colors.Text("Error during local shell mode initialisation:", colors.BgRed))
175 em.displayError(em.log, err)
176 return
177 }
178
179 fmt.Println(colors.Text("Exited shell mode", colors.FgGreenBold))
180 em.displayConnectBanner()
181 return
182 }
183
184 if strings.HasPrefix(in, "rcliconfig") {
185 err := em.rcliconfig(in)
186 if err != nil {
187 em.log.Error(err, "Error processing rcliconfig command")
188 }
189 return
190 }
191
192 fmt.Println(colors.Text("Unknown command: %s", colors.FgRed, in))
193 }
194
195
196
197 func (em *Emulator) parseConnectInput(str string) (connectionData, error) {
198 vals, err := shlex.Split(str)
199 if err != nil {
200 return connectionData{}, err
201 }
202 flags := []string{"bannerID", "storeID", "terminalID"}
203 switch len(vals) {
204 case 2:
205 projectID := getProjectID(em.workspace.ProjectID)
206 fmt.Println(colors.Text("Using bannerID, and storeID from workspace", colors.FgBlue))
207 for idx, item := range []string{em.workspace.BannerID, em.workspace.StoreID} {
208 if item == "" || item == UNSET {
209 return connectionData{}, fmt.Errorf("%s in workspace was not set", flags[idx])
210 }
211 }
212 return connectionData{
213 projectID: projectID,
214 bannerID: em.workspace.BannerID,
215 storeID: em.workspace.StoreID,
216 terminalID: vals[1]}, nil
217 case 3:
218 projectID := getProjectID(em.workspace.ProjectID)
219 fmt.Println(colors.Text("Using bannerID from workspace", colors.FgBlue))
220 for idx, item := range []string{em.workspace.BannerID} {
221 if item == "" || item == UNSET {
222 return connectionData{}, fmt.Errorf("%s in workspace was not set", flags[idx])
223 }
224 }
225 return connectionData{
226 projectID: projectID,
227 bannerID: em.workspace.BannerID,
228 storeID: vals[1],
229 terminalID: vals[2],
230 }, nil
231 case 4:
232 projectID := getProjectID(em.workspace.ProjectID)
233
234 return connectionData{
235 projectID: projectID,
236 bannerID: vals[1],
237 storeID: vals[2],
238 terminalID: vals[3],
239 }, nil
240 case 5:
241 return connectionData{
242 projectID: vals[1],
243 bannerID: vals[2],
244 storeID: vals[3],
245 terminalID: vals[4],
246 }, nil
247 }
248 return connectionData{}, fmt.Errorf("wrong number of values given")
249 }
250
251 func getProjectID(projectID string) string {
252 if projectID != "" && projectID != UNSET {
253 fmt.Println(colors.Text("Using projectID from workspace", colors.FgBlue))
254 }
255 if projectID == UNSET {
256
257
258 projectID = ""
259 }
260 return projectID
261 }
262
263 func exitChecker(in string, breakline bool) bool {
264 if breakline {
265 for _, item := range exitCommands {
266 if in == item {
267 return true
268 }
269 }
270 }
271
272 return false
273 }
274
275
276
277 func (em *Emulator) rcliconfig(in string) error {
278
279 values, err := shlex.Split(in)
280 if err != nil {
281 fmt.Println(colors.Text("Error processing command, see log for further details", colors.FgRed))
282 return err
283 }
284
285 if len(values) < 2 {
286 fmt.Println(colors.Text("Please provide a subcommand", colors.FgRed))
287 return ErrorRCLINoSubcmd
288 }
289 return em.rcliConfigSwitch(values)
290 }
291
292 func (em *Emulator) rcliConfigSwitch(values []string) error {
293 switch values[1] {
294 case "topicTemplate":
295 err := checkrcliConfValues(values)
296 if err != nil {
297 return err
298 }
299 em.cls.SetTopicTemplate(values[2])
300 case "subscriptionTemplate":
301 err := checkrcliConfValues(values)
302 if err != nil {
303 return err
304 }
305 em.cls.SetSubscriptionTemplate(values[2])
306 case "projectID":
307 err := checkrcliConfValues(values)
308 if err != nil {
309 return err
310 }
311 em.workspace.ProjectID = values[2]
312 em.displayWorkspace()
313 case "bannerID":
314 err := checkrcliConfValues(values)
315 if err != nil {
316 return err
317 }
318 em.workspace.BannerID = values[2]
319 em.displayWorkspace()
320 case "storeID":
321 err := checkrcliConfValues(values)
322 if err != nil {
323 return err
324 }
325 em.workspace.StoreID = values[2]
326 em.displayWorkspace()
327 case "enablePerSessionSubscription":
328 em.workspace.DisablePerSessionSubscription = false
329 em.cls.EnablePerSessionSubscription()
330 case "disablePerSessionSubscription":
331 em.workspace.DisablePerSessionSubscription = true
332 em.cls.DisablePerSessionSubscription()
333 default:
334 fmt.Println(colors.Text("Unknown subcommand: %s", colors.FgRed, values[1]))
335 return ErrorRCLIUnknownSubcmd
336 }
337 return nil
338 }
339
340 func checkrcliConfValues(values []string) error {
341 if len(values) != 3 {
342 fmt.Println(colors.Text("Unexpected number of arguments for %s subcommand", colors.FgRed, values[1]))
343 return errors.New("Unexpected number of arguments for subcommand " + values[1])
344 }
345 fmt.Println(colors.Text("Setting "+values[1]+" to "+values[2], colors.FgBlue))
346 return nil
347 }
348
349 func (em Emulator) displayConnectBanner() {
350 fmt.Println(colors.Text("Remote CLI: 'Ctrl-D', 'end', 'exit' or 'q' to exit. ", colors.BgBlue))
351 em.displayUser()
352 em.displayWorkspace()
353 }
354
355
356 func (em Emulator) displayWorkspace() {
357 fmt.Print(colors.Text("Workspace: ", colors.FgBlueBold))
358 fmt.Print(colors.Text("%s", colors.FgBlue, em.workspace.string()))
359 }
360
361 func (em Emulator) displayUser() {
362 fmt.Print(colors.Text("User: ", colors.FgBlueBold))
363 fmt.Print(colors.Text("%s\n", colors.FgBlue, em.cls.UserID()))
364 }
365
View as plain text