Skip to content

Commit

Permalink
support k8s pod
Browse files Browse the repository at this point in the history
  • Loading branch information
tk103331 committed Sep 9, 2022
1 parent 9c15da4 commit f73a579
Show file tree
Hide file tree
Showing 12 changed files with 405 additions and 68 deletions.
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ GoShell is a simple terminal GUI client, written in Go,via [Fyne](https://fyne.i
![GoShell SSH](screenshot/ssh-conf.png)
### Docker Config
![GoShell Docker](screenshot/docker-conf.png)
### Docker Container
### Docker Select Container
![GoShell Docker](screenshot/docker-container.png)
### K8S Config
![GoShell Docker](screenshot/k8s-conf.png)
### K8S Select Container
![GoShell Docker](screenshot/k8s-container.png)

# TODOs

- UI/UX optimization
- Configuration encryption
- Supports K8S pod.
- Configuration encryption
- ~~Supports K8S pod.~~
14 changes: 9 additions & 5 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ import (
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
"github.com/jinzhu/copier"
"log"
)

type Config interface {
Name() string
Type() string
Load(string) error
Data() interface{}
Form() *widget.Form
Term(*Window)
Expand Down Expand Up @@ -97,15 +97,19 @@ func (w *Window) load() {
var cfg Config
switch t {
case "ssh":
cfg = &SSHConfig{}
cfg = &SSHConfig{data: &SSHConfigData{}}
case "docker":
cfg = &DockerConfig{}
cfg = &DockerConfig{data: &DockerConfigData{}}
case "k8s":
cfg = &K8SConfig{}
cfg = &K8SConfig{data: &K8SConfigData{}}
default:
continue
}
err := copier.Copy(cfg, data)
bytes, err := json.Marshal(data)
if err != nil {
continue
}
err = cfg.Load(string(bytes))
if err != nil {
continue
}
Expand Down
29 changes: 20 additions & 9 deletions docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ package main

import (
"context"
"encoding/json"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/fyne-io/terminal"
)

type DockerConfigData struct {
Name string
Type string
Host string
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Host string `json:"host,omitempty"`
}

type DockerConfig struct {
Expand All @@ -27,9 +27,19 @@ func (c *DockerConfig) Name() string {
}

func (c *DockerConfig) Type() string {
return "ssh"
return "docker"
}

func (c *DockerConfig) Load(s string) error {
data := &DockerConfigData{}

err := json.Unmarshal([]byte(s), data)
if err != nil {
return err
}
c.data = data
return nil
}
func (c *DockerConfig) Data() interface{} {
return c.data
}
Expand Down Expand Up @@ -102,18 +112,19 @@ func (c *DockerConfig) Term(win *Window) {
win.showError(err)
return
}
term := terminal.New()

term := NewTerm(c.Name(), c)

go func() {
defer attach.Close()
err = term.RunWithConnection(attach.Conn, attach.Reader)
err = term.RunWithConnection(attach.Conn)
if err != nil {
win.showError(err)
return
}
}()

tab := &Term{name: c.Name(), term: term}
win.AddTermTab(tab)
win.AddTermTab(term)
if dlg != nil {
dlg.Hide()
}
Expand Down
8 changes: 5 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ require (
github.com/docker/docker v17.12.0-ce-rc1.0.20201201034508-7d75c1d40d88+incompatible
github.com/docker/go-units v0.5.0 // indirect
github.com/fyne-io/terminal v0.0.0-20210419192104-b6a609f9c1bd
github.com/gorilla/mux v1.8.0 // indirect
github.com/jinzhu/copier v0.3.5
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/srwiley/oksvg v0.0.0-20210320200257-875f767ac39a // indirect
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
github.com/tk103331/stream v1.0.2
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
google.golang.org/grpc v1.49.0 // indirect
k8s.io/api v0.22.5
k8s.io/apimachinery v0.22.5
k8s.io/client-go v0.22.5
)
31 changes: 26 additions & 5 deletions go.sum

Large diffs are not rendered by default.

211 changes: 203 additions & 8 deletions k8s.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
package main

import (
"context"
"encoding/json"
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/dialog"
"fyne.io/fyne/v2/widget"
"github.com/fyne-io/terminal"
"github.com/tk103331/stream"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/remotecommand"
)

type K8SConfigData struct {
Name string
Type string
Server string
Token string
User string
Pswd string
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Server string `json:"server,omitempty"`
Token string `json:"token,omitempty"`
}
type K8SConfig struct {
data *K8SConfigData
onOk func()
}

func (c *K8SConfig) Name() string {
Expand All @@ -23,19 +35,202 @@ func (c *K8SConfig) Name() string {
func (c *K8SConfig) Type() string {
return "k8s"
}
func (c *K8SConfig) Load(s string) error {
data := &K8SConfigData{}

err := json.Unmarshal([]byte(s), data)
if err != nil {
return err
}
c.data = data
return nil
}
func (c *K8SConfig) Data() interface{} {
return c.data
}

func (c *K8SConfig) Form() *widget.Form {
return widget.NewForm()
nameEntry := widget.NewEntry()
serverEntry := widget.NewEntry()
tokenEntry := widget.NewEntry()
tokenEntry.MultiLine = true
tokenEntry.Wrapping = fyne.TextWrapBreak
data := c.data
if data != nil {
nameEntry.Text = data.Name
nameEntry.Disable()
serverEntry.Text = data.Server
tokenEntry.Text = data.Token
}
c.onOk = func() {
if c.data == nil {
c.data = &K8SConfigData{Type: c.Type()}
}
c.data.Name = nameEntry.Text
c.data.Server = serverEntry.Text
c.data.Token = tokenEntry.Text
}
return widget.NewForm([]*widget.FormItem{
widget.NewFormItem("Name", nameEntry),
widget.NewFormItem("Server", serverEntry),
widget.NewFormItem("Token", tokenEntry),
}...)
}

func (c *K8SConfig) OnOk() {
c.onOk()
}

type ExecOpt struct {
Namespace string
PodName string
Container string
}

func (c *K8SConfig) Term(win *Window) {
panic("implement me")
cfg := c.data
restCfg := &rest.Config{
Host: cfg.Server,
BearerToken: cfg.Token,
BearerTokenFile: "",
TLSClientConfig: rest.TLSClientConfig{
Insecure: true,
},
}
clientset, err := kubernetes.NewForConfig(restCfg)
if err != nil {
win.showError(err)
return
}
namespaceList, err := clientset.CoreV1().Namespaces().List(context.Background(), metav1.ListOptions{})
if err != nil {
win.showError(err)
return
}
s, _ := stream.New(namespaceList.Items)
namespaces := make([]string, 0)
s.Map(func(n corev1.Namespace) string {
return n.ObjectMeta.Name
}).ToSlice(&namespaces)

execOpt := &ExecOpt{}

containerSelect := widget.NewSelect(namespaces, func(container string) {
execOpt.Container = container
})

podSelect := widget.NewSelect(namespaces, func(pod string) {
execOpt.PodName = pod
containerSelect.ClearSelected()
containerSelect.Options = []string{}

go func() {
pod, err2 := clientset.CoreV1().Pods(execOpt.Namespace).Get(context.Background(), pod, metav1.GetOptions{})
if err2 != nil {
win.showError(err2)
return
}
s, _ := stream.New(pod.Spec.Containers)
containers := make([]string, 0)
s.Map(func(n corev1.Container) string {
return n.Name
}).Limit(20).ToSlice(&containers)
containerSelect.Options = containers
}()

})

namespaceSelect := widget.NewSelect(namespaces, func(namespace string) {
execOpt.Namespace = namespace
podSelect.ClearSelected()
podSelect.Options = []string{}

go func() {
podList, err2 := clientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{})
if err2 != nil {
win.showError(err2)
return
}
s, _ := stream.New(podList.Items)
pods := make([]string, 0)
s.Map(func(n corev1.Pod) string {
return n.ObjectMeta.Name
}).Limit(20).ToSlice(&pods)
podSelect.Options = pods
}()
})
namespaceSelect.Resize(fyne.Size{Height: 200})

form := widget.NewForm(widget.NewFormItem("Namespace", namespaceSelect), widget.NewFormItem("Pod", podSelect), widget.NewFormItem("Container", containerSelect))
dlg := dialog.NewCustomConfirm("Select a container", "Connect", "Cancel", form, func(b bool) {
if b {
req := clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name(execOpt.PodName).
Namespace(execOpt.Namespace).
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Container: execOpt.Container,
Command: []string{"bash"},
Stdin: true,
Stdout: true,
Stderr: true,
TTY: true,
}, scheme.ParameterCodec)

executor, err := remotecommand.NewSPDYExecutor(restCfg, "POST", req.URL())
if err != nil {
win.showError(err)
return
}

termCfgChan := make(chan terminal.Config)

term := NewTerm(execOpt.PodName, c)

writer, reader := term.StartWithPipe(func(err error) {
if err != nil {
fmt.Println(err.Error())
win.showError(err)
}
})

go func() {

err = executor.Stream(remotecommand.StreamOptions{
Stdin: reader,
Stdout: writer,
Stderr: writer,
Tty: true,
TerminalSizeQueue: TermConfigSizeQueue(termCfgChan),
})
if err != nil {
win.showError(err)
return
}
}()

term.AddConfigListener(func(config *terminal.Config) {
if config != nil {
go func() {
termCfgChan <- *config
}()
}
})
win.AddTermTab(term)
}
}, win.win)
dlg.Resize(fyne.Size{Width: 400})
dlg.Show()
}

type TermConfigSizeQueue chan terminal.Config

func (t TermConfigSizeQueue) Next() *remotecommand.TerminalSize {
cfg := <-t
return &remotecommand.TerminalSize{Width: uint16(cfg.Columns), Height: uint16(cfg.Rows)}
}

func (t TermConfigSizeQueue) Send(cfg terminal.Config) {
t <- cfg
}
Loading

0 comments on commit f73a579

Please sign in to comment.