@@ -20,13 +20,17 @@ Initially copied from https://github.com/kubevirt/kubevirt/blob/main/pkg/virtctl
20
20
package console
21
21
22
22
import (
23
+ "context"
23
24
"errors"
25
+ "fmt"
24
26
"io"
27
+ "os"
25
28
"strings"
26
29
"time"
27
30
28
31
"github.com/gorilla/websocket"
29
32
"github.com/spf13/cobra"
33
+ "golang.org/x/term"
30
34
31
35
"github.com/deckhouse/virtualization/api/client/kubeclient"
32
36
@@ -79,64 +83,138 @@ func (c *Console) Run(cmd *cobra.Command, args []string) error {
79
83
}
80
84
81
85
for {
82
- err := connect (name , namespace , client , c .timeout )
83
- if err == nil {
84
- continue
85
- }
86
-
87
- if errors .Is (err , util .ErrorInterrupt ) || strings .Contains (err .Error (), "not found" ) {
88
- return err
89
- }
90
-
91
- var e * websocket.CloseError
92
- if errors .As (err , & e ) {
93
- switch e .Code {
94
- case websocket .CloseGoingAway :
95
- cmd .Printf ("\n You were disconnected from the console. This has one of the following reasons:" +
96
- "\n - another user connected to the console of the target vm\n " )
86
+ select {
87
+ case <- cmd .Context ().Done ():
88
+ return nil
89
+ default :
90
+ cmd .Printf ("Connecting to %s via console...\n " , name )
91
+
92
+ err := connect (cmd .Context (), name , namespace , client , c .timeout )
93
+ if err != nil {
94
+ if strings .Contains (err .Error (), "not found" ) {
95
+ return err
96
+ }
97
+
98
+ var e * websocket.CloseError
99
+ if errors .As (err , & e ) {
100
+ switch e .Code {
101
+ case websocket .CloseGoingAway :
102
+ cmd .Printf (util .CloseGoingAwayMessage )
103
+ return nil
104
+ case websocket .CloseAbnormalClosure :
105
+ cmd .Printf (util .CloseAbnormalClosureMessage )
106
+ }
107
+ } else {
108
+ cmd .Printf ("%s\n " , err )
109
+ }
110
+
111
+ time .Sleep (time .Second )
112
+ } else {
97
113
return nil
98
- case websocket .CloseAbnormalClosure :
99
- cmd .Printf ("\n You were disconnected from the console. This has one of the following reasons:" +
100
- "\n - network issues" +
101
- "\n - machine restart\n " )
102
114
}
103
- } else {
104
- cmd .Printf ("%s\n " , err )
105
115
}
106
-
107
- time .Sleep (time .Second )
108
116
}
109
117
}
110
118
111
- func connect (name string , namespace string , virtCli kubeclient.Client , timeout int ) error {
119
+ func connect (ctx context.Context , name string , namespace string , virtCli kubeclient.Client , timeout int ) error {
120
+ // in -> stdinWriter | stdinReader -> console
121
+ // out <- stdoutReader | stdoutWriter <- console
112
122
stdinReader , stdinWriter := io .Pipe ()
113
123
stdoutReader , stdoutWriter := io .Pipe ()
114
124
115
- // in -> stdinWriter | stdinReader -> console
116
- // out <- stdoutReader | stdoutWriter <- console
117
- // Wait until the virtual machine is in running phase, user interrupt or timeout
118
- resChan := make (chan error )
119
- runningChan := make (chan error )
125
+ doneChan := make (chan struct {}, 1 )
120
126
121
- go func () {
122
- con , err := virtCli . VirtualMachines ( namespace ). SerialConsole ( name , & kubeclient. SerialConsoleOptions { ConnectionTimeout : time . Duration ( timeout ) * time . Minute } )
123
- runningChan <- err
127
+ k8sResErr := make ( chan error )
128
+ writeStopErr := make ( chan error )
129
+ readStopErr := make ( chan error )
124
130
125
- if err != nil {
126
- return
127
- }
131
+ console , err := virtCli .VirtualMachines (namespace ).SerialConsole (name , & kubeclient.SerialConsoleOptions {ConnectionTimeout : time .Duration (timeout ) * time .Minute })
132
+ if err != nil {
133
+ return fmt .Errorf ("can't access VM %s: %s" , name , err .Error ())
134
+ }
128
135
129
- resChan <- con .Stream (kubeclient.StreamOptions {
136
+ go func () {
137
+ err := console .Stream (kubeclient.StreamOptions {
130
138
In : stdinReader ,
131
139
Out : stdoutWriter ,
132
140
})
141
+ if err != nil {
142
+ k8sResErr <- err
143
+ }
133
144
}()
134
145
135
- err := <- runningChan
136
- if err != nil {
137
- return err
146
+ if term .IsTerminal (int (os .Stdin .Fd ())) {
147
+ state , err := term .MakeRaw (int (os .Stdin .Fd ()))
148
+ if err != nil {
149
+ return fmt .Errorf ("make raw terminal failed: %w" , err )
150
+ }
151
+ defer term .Restore (int (os .Stdin .Fd ()), state )
152
+ }
153
+
154
+ fmt .Fprintf (os .Stderr , "Successfully connected to %s console. The escape sequence is ^]\n " , name )
155
+
156
+ out := os .Stdout
157
+ go func () {
158
+ _ , err := io .Copy (out , stdoutReader )
159
+ if err != nil {
160
+ readStopErr <- err
161
+ }
162
+ }()
163
+
164
+ stdinCh := make (chan []byte )
165
+ go func () {
166
+ in := os .Stdin
167
+ buf := make ([]byte , 1024 )
168
+ for {
169
+ // reading from stdin
170
+ n , err := in .Read (buf )
171
+ if err != nil {
172
+ if err != io .EOF || n == 0 {
173
+ return
174
+ }
175
+
176
+ readStopErr <- err
177
+ }
178
+
179
+ // the escape sequence
180
+ if buf [0 ] == 29 {
181
+ doneChan <- struct {}{}
182
+ return
183
+ }
184
+
185
+ stdinCh <- buf [0 :n ]
186
+ }
187
+ }()
188
+
189
+ go func () {
190
+ _ , err := stdinWriter .Write ([]byte ("\r " ))
191
+ if err != nil {
192
+ if err == io .EOF {
193
+ return
194
+ }
195
+
196
+ writeStopErr <- err
197
+ }
198
+
199
+ for b := range stdinCh {
200
+ _ , err = stdinWriter .Write (b )
201
+ if err != nil {
202
+ if err == io .EOF {
203
+ return
204
+ }
205
+
206
+ writeStopErr <- err
207
+ }
208
+ }
209
+ }()
210
+
211
+ select {
212
+ case <- ctx .Done ():
213
+ case <- doneChan :
214
+ case err = <- k8sResErr :
215
+ case err = <- writeStopErr :
216
+ case err = <- readStopErr :
138
217
}
139
218
140
- err = util .AttachConsole (stdinReader , stdoutReader , stdinWriter , stdoutWriter , name , resChan )
141
219
return err
142
220
}
0 commit comments