1
1
package main
2
2
3
3
import (
4
+ "context"
5
+ "encoding/json"
6
+ "fmt"
7
+ "fyne.io/fyne/v2"
8
+ "fyne.io/fyne/v2/dialog"
4
9
"fyne.io/fyne/v2/widget"
10
+ "github.com/fyne-io/terminal"
11
+ "github.com/tk103331/stream"
12
+ corev1 "k8s.io/api/core/v1"
13
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
14
+ "k8s.io/client-go/kubernetes"
15
+ "k8s.io/client-go/kubernetes/scheme"
16
+ "k8s.io/client-go/rest"
17
+ "k8s.io/client-go/tools/remotecommand"
5
18
)
6
19
7
20
type K8SConfigData struct {
8
- Name string
9
- Type string
10
- Server string
11
- Token string
12
- User string
13
- Pswd string
21
+ Name string `json:"name,omitempty"`
22
+ Type string `json:"type,omitempty"`
23
+ Server string `json:"server,omitempty"`
24
+ Token string `json:"token,omitempty"`
14
25
}
15
26
type K8SConfig struct {
16
27
data * K8SConfigData
28
+ onOk func ()
17
29
}
18
30
19
31
func (c * K8SConfig ) Name () string {
@@ -23,19 +35,202 @@ func (c *K8SConfig) Name() string {
23
35
func (c * K8SConfig ) Type () string {
24
36
return "k8s"
25
37
}
38
+ func (c * K8SConfig ) Load (s string ) error {
39
+ data := & K8SConfigData {}
26
40
41
+ err := json .Unmarshal ([]byte (s ), data )
42
+ if err != nil {
43
+ return err
44
+ }
45
+ c .data = data
46
+ return nil
47
+ }
27
48
func (c * K8SConfig ) Data () interface {} {
28
49
return c .data
29
50
}
30
51
31
52
func (c * K8SConfig ) Form () * widget.Form {
32
- return widget .NewForm ()
53
+ nameEntry := widget .NewEntry ()
54
+ serverEntry := widget .NewEntry ()
55
+ tokenEntry := widget .NewEntry ()
56
+ tokenEntry .MultiLine = true
57
+ tokenEntry .Wrapping = fyne .TextWrapBreak
58
+ data := c .data
59
+ if data != nil {
60
+ nameEntry .Text = data .Name
61
+ nameEntry .Disable ()
62
+ serverEntry .Text = data .Server
63
+ tokenEntry .Text = data .Token
64
+ }
65
+ c .onOk = func () {
66
+ if c .data == nil {
67
+ c .data = & K8SConfigData {Type : c .Type ()}
68
+ }
69
+ c .data .Name = nameEntry .Text
70
+ c .data .Server = serverEntry .Text
71
+ c .data .Token = tokenEntry .Text
72
+ }
73
+ return widget .NewForm ([]* widget.FormItem {
74
+ widget .NewFormItem ("Name" , nameEntry ),
75
+ widget .NewFormItem ("Server" , serverEntry ),
76
+ widget .NewFormItem ("Token" , tokenEntry ),
77
+ }... )
33
78
}
34
79
35
80
func (c * K8SConfig ) OnOk () {
81
+ c .onOk ()
82
+ }
36
83
84
+ type ExecOpt struct {
85
+ Namespace string
86
+ PodName string
87
+ Container string
37
88
}
38
89
39
90
func (c * K8SConfig ) Term (win * Window ) {
40
- panic ("implement me" )
91
+ cfg := c .data
92
+ restCfg := & rest.Config {
93
+ Host : cfg .Server ,
94
+ BearerToken : cfg .Token ,
95
+ BearerTokenFile : "" ,
96
+ TLSClientConfig : rest.TLSClientConfig {
97
+ Insecure : true ,
98
+ },
99
+ }
100
+ clientset , err := kubernetes .NewForConfig (restCfg )
101
+ if err != nil {
102
+ win .showError (err )
103
+ return
104
+ }
105
+ namespaceList , err := clientset .CoreV1 ().Namespaces ().List (context .Background (), metav1.ListOptions {})
106
+ if err != nil {
107
+ win .showError (err )
108
+ return
109
+ }
110
+ s , _ := stream .New (namespaceList .Items )
111
+ namespaces := make ([]string , 0 )
112
+ s .Map (func (n corev1.Namespace ) string {
113
+ return n .ObjectMeta .Name
114
+ }).ToSlice (& namespaces )
115
+
116
+ execOpt := & ExecOpt {}
117
+
118
+ containerSelect := widget .NewSelect (namespaces , func (container string ) {
119
+ execOpt .Container = container
120
+ })
121
+
122
+ podSelect := widget .NewSelect (namespaces , func (pod string ) {
123
+ execOpt .PodName = pod
124
+ containerSelect .ClearSelected ()
125
+ containerSelect .Options = []string {}
126
+
127
+ go func () {
128
+ pod , err2 := clientset .CoreV1 ().Pods (execOpt .Namespace ).Get (context .Background (), pod , metav1.GetOptions {})
129
+ if err2 != nil {
130
+ win .showError (err2 )
131
+ return
132
+ }
133
+ s , _ := stream .New (pod .Spec .Containers )
134
+ containers := make ([]string , 0 )
135
+ s .Map (func (n corev1.Container ) string {
136
+ return n .Name
137
+ }).Limit (20 ).ToSlice (& containers )
138
+ containerSelect .Options = containers
139
+ }()
140
+
141
+ })
142
+
143
+ namespaceSelect := widget .NewSelect (namespaces , func (namespace string ) {
144
+ execOpt .Namespace = namespace
145
+ podSelect .ClearSelected ()
146
+ podSelect .Options = []string {}
147
+
148
+ go func () {
149
+ podList , err2 := clientset .CoreV1 ().Pods (namespace ).List (context .Background (), metav1.ListOptions {})
150
+ if err2 != nil {
151
+ win .showError (err2 )
152
+ return
153
+ }
154
+ s , _ := stream .New (podList .Items )
155
+ pods := make ([]string , 0 )
156
+ s .Map (func (n corev1.Pod ) string {
157
+ return n .ObjectMeta .Name
158
+ }).Limit (20 ).ToSlice (& pods )
159
+ podSelect .Options = pods
160
+ }()
161
+ })
162
+ namespaceSelect .Resize (fyne.Size {Height : 200 })
163
+
164
+ form := widget .NewForm (widget .NewFormItem ("Namespace" , namespaceSelect ), widget .NewFormItem ("Pod" , podSelect ), widget .NewFormItem ("Container" , containerSelect ))
165
+ dlg := dialog .NewCustomConfirm ("Select a container" , "Connect" , "Cancel" , form , func (b bool ) {
166
+ if b {
167
+ req := clientset .CoreV1 ().RESTClient ().Post ().
168
+ Resource ("pods" ).
169
+ Name (execOpt .PodName ).
170
+ Namespace (execOpt .Namespace ).
171
+ SubResource ("exec" ).
172
+ VersionedParams (& corev1.PodExecOptions {
173
+ Container : execOpt .Container ,
174
+ Command : []string {"bash" },
175
+ Stdin : true ,
176
+ Stdout : true ,
177
+ Stderr : true ,
178
+ TTY : true ,
179
+ }, scheme .ParameterCodec )
180
+
181
+ executor , err := remotecommand .NewSPDYExecutor (restCfg , "POST" , req .URL ())
182
+ if err != nil {
183
+ win .showError (err )
184
+ return
185
+ }
186
+
187
+ termCfgChan := make (chan terminal.Config )
188
+
189
+ term := NewTerm (execOpt .PodName , c )
190
+
191
+ writer , reader := term .StartWithPipe (func (err error ) {
192
+ if err != nil {
193
+ fmt .Println (err .Error ())
194
+ win .showError (err )
195
+ }
196
+ })
197
+
198
+ go func () {
199
+
200
+ err = executor .Stream (remotecommand.StreamOptions {
201
+ Stdin : reader ,
202
+ Stdout : writer ,
203
+ Stderr : writer ,
204
+ Tty : true ,
205
+ TerminalSizeQueue : TermConfigSizeQueue (termCfgChan ),
206
+ })
207
+ if err != nil {
208
+ win .showError (err )
209
+ return
210
+ }
211
+ }()
212
+
213
+ term .AddConfigListener (func (config * terminal.Config ) {
214
+ if config != nil {
215
+ go func () {
216
+ termCfgChan <- * config
217
+ }()
218
+ }
219
+ })
220
+ win .AddTermTab (term )
221
+ }
222
+ }, win .win )
223
+ dlg .Resize (fyne.Size {Width : 400 })
224
+ dlg .Show ()
225
+ }
226
+
227
+ type TermConfigSizeQueue chan terminal.Config
228
+
229
+ func (t TermConfigSizeQueue ) Next () * remotecommand.TerminalSize {
230
+ cfg := <- t
231
+ return & remotecommand.TerminalSize {Width : uint16 (cfg .Columns ), Height : uint16 (cfg .Rows )}
232
+ }
233
+
234
+ func (t TermConfigSizeQueue ) Send (cfg terminal.Config ) {
235
+ t <- cfg
41
236
}
0 commit comments