├── goshell.png ├── screenshot ├── main.png ├── k8s-conf.png ├── ssh-conf.png ├── docker-conf.png ├── k8s-container.png └── docker-container.png ├── local.go ├── model.go ├── .gitignore ├── .github └── dependabot.yml ├── cmd.go ├── main.go ├── LICENSE ├── README.md ├── settings.go ├── term.go ├── settings_dialog.go ├── config.go ├── docker.go ├── ssh.go ├── window.go ├── go.mod ├── k8s.go └── go.sum /goshell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tk103331/goshell/HEAD/goshell.png -------------------------------------------------------------------------------- /screenshot/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tk103331/goshell/HEAD/screenshot/main.png -------------------------------------------------------------------------------- /screenshot/k8s-conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tk103331/goshell/HEAD/screenshot/k8s-conf.png -------------------------------------------------------------------------------- /screenshot/ssh-conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tk103331/goshell/HEAD/screenshot/ssh-conf.png -------------------------------------------------------------------------------- /screenshot/docker-conf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tk103331/goshell/HEAD/screenshot/docker-conf.png -------------------------------------------------------------------------------- /screenshot/k8s-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tk103331/goshell/HEAD/screenshot/k8s-container.png -------------------------------------------------------------------------------- /screenshot/docker-container.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tk103331/goshell/HEAD/screenshot/docker-container.png -------------------------------------------------------------------------------- /local.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func (w *Window) createLocalTermTab() { 4 | tab := NewLocalTerm() 5 | w.AddTermTab(tab) 6 | } 7 | -------------------------------------------------------------------------------- /model.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | type Cmd struct { 4 | Name string 5 | Text string 6 | Icon string 7 | } 8 | 9 | func newCmd(name, text string) *Cmd { 10 | return &Cmd{Name: name, Text: text} 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | .idea 14 | cmd/goshell/goshell 15 | # Dependency directories (remove the comment below to include it) 16 | # vendor/ 17 | 18 | .claude/ 19 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /cmd.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fyne.io/fyne/v2" 5 | "fyne.io/fyne/v2/dialog" 6 | "fyne.io/fyne/v2/widget" 7 | ) 8 | 9 | func (w *Window) showNewCmdDialog() { 10 | nameEntry := widget.NewEntry() 11 | textEntry := widget.NewEntry() 12 | textEntry.MultiLine = true 13 | 14 | icons := make([]string, len(iconMap)) 15 | i := 0 16 | for k, _ := range iconMap { 17 | icons[i] = k 18 | i++ 19 | } 20 | iconSelect := widget.NewSelectEntry(icons) 21 | 22 | dlg := dialog.NewForm("New Command", "OK", "Cancel", []*widget.FormItem{ 23 | widget.NewFormItem("Name", nameEntry), 24 | widget.NewFormItem("Text", textEntry), 25 | widget.NewFormItem("Icon", iconSelect), 26 | }, func(b bool) { 27 | if b { 28 | cmd := &Cmd{Name: nameEntry.Text, Text: textEntry.Text, Icon: iconSelect.Text} 29 | w.AddCmd(cmd) 30 | } 31 | }, w.win) 32 | 33 | dlg.Resize(fyne.NewSize(400, 300)) 34 | dlg.Show() 35 | } 36 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | "os/signal" 6 | "syscall" 7 | ) 8 | 9 | func main() { 10 | 11 | stopCh := SetupSignalHandler() 12 | 13 | (&Window{}).Run(stopCh) 14 | 15 | } 16 | 17 | var shutdownSignals = []os.Signal{os.Interrupt, syscall.SIGTERM} 18 | var onlyOneSignalHandler = make(chan struct{}) 19 | 20 | // SetupSignalHandler registered for SIGTERM and SIGINT. A stop channel is returned 21 | // which is closed on one of these signals. If a second signal is caught, the program 22 | // is terminated with exit code 1. 23 | func SetupSignalHandler() (stopCh <-chan struct{}) { 24 | close(onlyOneSignalHandler) // panics when called twice 25 | 26 | stop := make(chan struct{}) 27 | c := make(chan os.Signal, 2) 28 | signal.Notify(c, shutdownSignals...) 29 | go func() { 30 | <-c 31 | close(stop) 32 | <-c 33 | os.Exit(1) // second signal. Exit directly. 34 | }() 35 | 36 | return stop 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 tk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoShell(WIP) 2 | 3 | GoShell is a simple terminal GUI client, written in Go,via [Fyne](https://fyne.io). Supports SSH、Docker、K8S. 4 | 5 | 6 | # Features 7 | 8 | - Supports SSH、Docker、K8S. 9 | - Supports Windows、Linux、MacOS platform.(thanks [Fyne](https://fyne.io)) 10 | - Supports shortcut command. 11 | 12 | # Screenshots 13 | ### Main 14 | ![GoShell Main](screenshot/main.png) 15 | ### SSH Config 16 | ![GoShell SSH](screenshot/ssh-conf.png) 17 | ### Docker Config 18 | ![GoShell Docker](screenshot/docker-conf.png) 19 | ### Docker Select Container 20 | ![GoShell Docker](screenshot/docker-container.png) 21 | ### K8S Config 22 | ![GoShell Docker](screenshot/k8s-conf.png) 23 | ### K8S Select Container 24 | ![GoShell Docker](screenshot/k8s-container.png) 25 | 26 | ### Building 27 | 28 | - Linux / MACOS 29 | ``` shell 30 | git clone https://github.com/tk103331/goshell.git 31 | cd goshell 32 | go build 33 | sudo ./goshell 34 | ``` 35 | - Windows (Need to run with administrator rights) 36 | ``` shell 37 | git clone https://github.com/tk103331/goshell.git 38 | cd goshell 39 | go build 40 | goshell 41 | ``` 42 | 43 | # TODOs 44 | 45 | - UI/UX optimization 46 | - Configuration encryption 47 | -------------------------------------------------------------------------------- /settings.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fyne.io/fyne/v2" 6 | "fyne.io/fyne/v2/theme" 7 | ) 8 | 9 | // AppSettings 应用设置 10 | type AppSettings struct { 11 | ThemeType string `json:"themeType"` 12 | FontName string `json:"fontName"` 13 | FontSize float32 `json:"fontSize"` 14 | } 15 | 16 | const ( 17 | ThemeTypeDark = "dark" 18 | ThemeTypeLight = "light" 19 | ThemeTypeSystem = "system" 20 | 21 | // 设置存储键 22 | APP_SETTINGS = "app_settings" 23 | ) 24 | 25 | // DefaultSettings 返回默认设置 26 | func DefaultSettings() *AppSettings { 27 | return &AppSettings{ 28 | ThemeType: ThemeTypeDark, 29 | FontName: "", 30 | FontSize: 0, // 0表示使用默认字体大小 31 | } 32 | } 33 | 34 | // GetTheme 根据设置获取主题 35 | func (s *AppSettings) GetTheme() fyne.Theme { 36 | switch s.ThemeType { 37 | case ThemeTypeLight: 38 | return theme.LightTheme() 39 | case ThemeTypeDark: 40 | return theme.DarkTheme() 41 | default: 42 | return theme.DarkTheme() 43 | } 44 | } 45 | 46 | // SaveSettings 保存设置到应用偏好 47 | func (w *Window) SaveSettings(settings *AppSettings) error { 48 | data, err := json.Marshal(settings) 49 | if err != nil { 50 | return err 51 | } 52 | w.app.Preferences().SetString(APP_SETTINGS, string(data)) 53 | return nil 54 | } 55 | 56 | // LoadSettings 从应用偏好加载设置 57 | func (w *Window) LoadSettings() *AppSettings { 58 | settingsJson := w.app.Preferences().String(APP_SETTINGS) 59 | if settingsJson == "" { 60 | return DefaultSettings() 61 | } 62 | 63 | var settings AppSettings 64 | err := json.Unmarshal([]byte(settingsJson), &settings) 65 | if err != nil { 66 | return DefaultSettings() 67 | } 68 | 69 | return &settings 70 | } 71 | 72 | // ApplySettings 应用设置 73 | func (w *Window) ApplySettings(settings *AppSettings) { 74 | // 应用主题 75 | w.app.Settings().SetTheme(settings.GetTheme()) 76 | 77 | // TODO: 应用字体设置需要进一步实现 78 | // Fyne目前不直接支持全局字体设置,但可以在控件级别设置 79 | } -------------------------------------------------------------------------------- /term.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/fyne-io/terminal" 5 | "io" 6 | "log" 7 | "net" 8 | ) 9 | 10 | type Term struct { 11 | name string 12 | term *terminal.Terminal 13 | stat string 14 | local bool 15 | sessionConfig Config 16 | termConfig *terminal.Config 17 | configListeners []func(*terminal.Config) 18 | closeListeners []func() 19 | } 20 | 21 | func NewTerm(name string, cfg Config) *Term { 22 | term := terminal.New() 23 | tab := &Term{name: name, term: term, sessionConfig: cfg} 24 | tab.watchConfig() 25 | return tab 26 | } 27 | 28 | func (t *Term) Name() string { 29 | return t.name 30 | } 31 | 32 | func (t *Term) TermConfig() *terminal.Config { 33 | return t.termConfig 34 | } 35 | 36 | func (t *Term) SessionConfig() Config { 37 | return t.sessionConfig 38 | } 39 | 40 | func (t *Term) StartWithPipe(callback func(err error)) (io.WriteCloser, io.Reader) { 41 | 42 | pipe1Reader, pipe1Writer := io.Pipe() 43 | pipe2Reader, pipe2Writer := io.Pipe() 44 | 45 | go func() { 46 | err := t.term.RunWithConnection(pipe1Writer, pipe2Reader) 47 | if err != nil { 48 | callback(err) 49 | } 50 | }() 51 | return pipe2Writer, pipe1Reader 52 | } 53 | 54 | func (t *Term) RunWithConnection(conn net.Conn) error { 55 | return t.term.RunWithConnection(conn, conn) 56 | } 57 | 58 | func (t *Term) RunWithReaderAndWriter(in io.WriteCloser, out io.Reader) error { 59 | return t.term.RunWithConnection(in, out) 60 | } 61 | 62 | func (t *Term) RunWithReadWriteCloser(wr io.ReadWriteCloser) error { 63 | return t.term.RunWithConnection(wr, wr) 64 | } 65 | 66 | func (t *Term) Exit() { 67 | t.term.Exit() 68 | for _, listener := range t.closeListeners { 69 | listener() 70 | } 71 | } 72 | 73 | func (t *Term) Send(txt string) { 74 | t.term.Write([]byte(txt)) 75 | } 76 | 77 | func (t *Term) FocusGained() { 78 | if t.term != nil { 79 | t.term.FocusGained() 80 | } 81 | } 82 | 83 | func (t *Term) AddConfigListener(fn func(config *terminal.Config)) { 84 | if fn != nil { 85 | t.configListeners = append(t.configListeners, fn) 86 | } 87 | } 88 | 89 | func (t *Term) AddCloseListener(fn func()) { 90 | if fn != nil { 91 | t.closeListeners = append(t.closeListeners, fn) 92 | } 93 | } 94 | 95 | func (t *Term) watchConfig() { 96 | cfgChan := make(chan terminal.Config) 97 | t.term.AddListener(cfgChan) 98 | go func() { 99 | for { 100 | select { 101 | case cfg := <-cfgChan: 102 | t.termConfig = &cfg 103 | for _, listener := range t.configListeners { 104 | listener(t.termConfig) 105 | } 106 | } 107 | } 108 | }() 109 | } 110 | 111 | func NewLocalTerm() *Term { 112 | term := terminal.New() 113 | go func() { 114 | err := term.RunLocalShell() 115 | if err != nil { 116 | log.Println(err) 117 | return 118 | } 119 | }() 120 | 121 | t := &Term{name: "local", term: term} 122 | t.watchConfig() 123 | return t 124 | } 125 | -------------------------------------------------------------------------------- /settings_dialog.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "fyne.io/fyne/v2" 6 | "fyne.io/fyne/v2/container" 7 | "fyne.io/fyne/v2/dialog" 8 | "fyne.io/fyne/v2/layout" 9 | "fyne.io/fyne/v2/widget" 10 | ) 11 | 12 | // showSettingsDialog 显示设置对话框 13 | func (w *Window) showSettingsDialog() { 14 | // 加载当前设置 15 | currentSettings := w.LoadSettings() 16 | 17 | // 主题选择 18 | themeSelect := widget.NewSelect([]string{"Dark", "Light", "System"}, func(selected string) { 19 | switch selected { 20 | case "Dark": 21 | currentSettings.ThemeType = ThemeTypeDark 22 | case "Light": 23 | currentSettings.ThemeType = ThemeTypeLight 24 | case "System": 25 | currentSettings.ThemeType = ThemeTypeSystem 26 | } 27 | }) 28 | 29 | // 设置当前选中的主题 30 | switch currentSettings.ThemeType { 31 | case ThemeTypeDark: 32 | themeSelect.SetSelected("Dark") 33 | case ThemeTypeLight: 34 | themeSelect.SetSelected("Light") 35 | case ThemeTypeSystem: 36 | themeSelect.SetSelected("System") 37 | default: 38 | themeSelect.SetSelected("Dark") 39 | } 40 | 41 | // 字体名称设置 42 | fontNameEntry := widget.NewEntry() 43 | fontNameEntry.SetPlaceHolder("Font name (leave empty for default)") 44 | fontNameEntry.SetText(currentSettings.FontName) 45 | fontNameEntry.OnChanged = func(text string) { 46 | currentSettings.FontName = text 47 | } 48 | 49 | // 字体大小设置 50 | fontSizeSlider := widget.NewSlider(8, 24) 51 | fontSizeSlider.SetValue(float64(currentSettings.FontSize)) 52 | 53 | fontSizeValueLabel := widget.NewLabel("Default") 54 | if currentSettings.FontSize > 0 { 55 | fontSizeValueLabel.SetText(fmt.Sprintf("%.0f", currentSettings.FontSize)) 56 | } 57 | 58 | // 更新字体大小显示 59 | fontSizeSlider.OnChanged = func(value float64) { 60 | currentSettings.FontSize = float32(value) 61 | if value > 0 { 62 | fontSizeValueLabel.SetText(fmt.Sprintf("%.0f", value)) 63 | } else { 64 | fontSizeValueLabel.SetText("Default") 65 | } 66 | } 67 | 68 | // 创建字体大小滑块布局,使其更宽更易操作 69 | fontSizeContainer := container.NewBorder( 70 | nil, // top 71 | nil, // bottom 72 | widget.NewLabel("Font Size:"), // left 73 | fontSizeValueLabel, // right 74 | fontSizeSlider, // center 75 | ) 76 | 77 | // 重置按钮 78 | resetButton := widget.NewButton("Reset to Defaults", func() { 79 | defaultSettings := DefaultSettings() 80 | themeSelect.SetSelected("Dark") 81 | fontNameEntry.SetText("") 82 | fontSizeSlider.SetValue(0) 83 | fontSizeValueLabel.SetText("Default") 84 | 85 | // 更新设置对象 86 | currentSettings.ThemeType = defaultSettings.ThemeType 87 | currentSettings.FontName = defaultSettings.FontName 88 | currentSettings.FontSize = defaultSettings.FontSize 89 | }) 90 | 91 | // 设置内容 92 | content := container.NewVBox( 93 | widget.NewCard("", "Theme Settings", widget.NewForm( 94 | widget.NewFormItem("Theme", themeSelect), 95 | )), 96 | 97 | widget.NewCard("", "Font Settings", container.NewVBox( 98 | widget.NewForm( 99 | widget.NewFormItem("Font Name", fontNameEntry), 100 | ), 101 | fontSizeContainer, 102 | )), 103 | 104 | container.NewHBox( 105 | layout.NewSpacer(), 106 | resetButton, 107 | ), 108 | ) 109 | 110 | // 创建对话框 111 | dlg := dialog.NewCustomConfirm("Settings", "Apply", "Cancel", content, func(apply bool) { 112 | if apply { 113 | // 保存并应用设置 114 | w.SaveSettings(currentSettings) 115 | w.ApplySettings(currentSettings) 116 | } 117 | }, w.win) 118 | 119 | dlg.Resize(fyne.NewSize(500, 350)) 120 | dlg.Show() 121 | } -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fyne.io/fyne/v2" 6 | "fyne.io/fyne/v2/container" 7 | "fyne.io/fyne/v2/dialog" 8 | "fyne.io/fyne/v2/widget" 9 | "log" 10 | ) 11 | 12 | type Config interface { 13 | Name() string 14 | Type() string 15 | Load(string) error 16 | Data() interface{} 17 | Form() *widget.Form 18 | Term(*Window) 19 | OnOk() 20 | } 21 | 22 | func (w *Window) showCreateConfigDialog() { 23 | sshConf := &SSHConfig{} 24 | dockerConf := &DockerConfig{} 25 | k8sConf := &K8SConfig{} 26 | 27 | sshForm := sshConf.Form() 28 | dockerForm := dockerConf.Form() 29 | k8sForm := k8sConf.Form() 30 | typeSelect := widget.NewSelect([]string{"SSH", "Docker", "K8S"}, func(s string) { 31 | sshForm.Hide() 32 | dockerForm.Hide() 33 | k8sForm.Hide() 34 | switch s { 35 | case "SSH": 36 | sshForm.Show() 37 | case "Docker": 38 | dockerForm.Show() 39 | case "K8S": 40 | k8sForm.Show() 41 | default: 42 | 43 | } 44 | }) 45 | title := "Create Config" 46 | typeSelect.SetSelectedIndex(0) 47 | box := container.NewVBox(typeSelect, sshForm, dockerForm, k8sForm) 48 | 49 | dlg := dialog.NewCustomConfirm(title, "OK", "Cancel", box, func(b bool) { 50 | if b { 51 | switch typeSelect.Selected { 52 | case "SSH": 53 | sshConf.OnOk() 54 | w.confs = append(w.confs, sshConf) 55 | case "Docker": 56 | dockerConf.OnOk() 57 | w.confs = append(w.confs, dockerConf) 58 | case "K8S": 59 | k8sConf.OnOk() 60 | w.confs = append(w.confs, k8sConf) 61 | default: 62 | return 63 | } 64 | w.save() 65 | } 66 | }, w.win) 67 | dlg.Resize(fyne.Size{Width: 300}) 68 | dlg.Show() 69 | } 70 | 71 | func (w *Window) showModifyConfigDialog(cfg Config) { 72 | form := cfg.Form() 73 | title := "Modify Config: " + cfg.Name() 74 | dlg := dialog.NewCustomConfirm(title, "OK", "Cancel", form, func(b bool) { 75 | if b { 76 | cfg.OnOk() 77 | w.save() 78 | } 79 | }, w.win) 80 | dlg.Resize(fyne.Size{Width: 300}) 81 | dlg.Show() 82 | } 83 | 84 | func (w *Window) load() { 85 | confJson := w.app.Preferences().String(APP_SESSIONS) 86 | 87 | confArr := []map[string]interface{}{} 88 | err := json.Unmarshal([]byte(confJson), &confArr) 89 | if err != nil { 90 | log.Println(err) 91 | } 92 | 93 | w.confs = make([]Config, 0) 94 | for _, data := range confArr { 95 | if v, e := data["type"]; e { 96 | if t, ok := v.(string); ok { 97 | var cfg Config 98 | switch t { 99 | case "ssh": 100 | cfg = &SSHConfig{data: &SSHConfigData{}} 101 | case "docker": 102 | cfg = &DockerConfig{data: &DockerConfigData{}} 103 | case "k8s": 104 | cfg = &K8SConfig{data: &K8SConfigData{}} 105 | default: 106 | continue 107 | } 108 | bytes, err := json.Marshal(data) 109 | if err != nil { 110 | continue 111 | } 112 | err = cfg.Load(string(bytes)) 113 | if err != nil { 114 | continue 115 | } 116 | w.confs = append(w.confs, cfg) 117 | } 118 | } 119 | 120 | } 121 | 122 | cmdJson := w.app.Preferences().String(APP_COMMANDS) 123 | err = json.Unmarshal([]byte(cmdJson), &w.cmds) 124 | if err != nil { 125 | log.Println(err) 126 | } 127 | } 128 | 129 | func (w *Window) save() { 130 | 131 | confData := make([]interface{}, len(w.confs)) 132 | for i := 0; i < len(w.confs); i++ { 133 | confData[i] = w.confs[i].Data() 134 | } 135 | 136 | confJson, err := json.Marshal(confData) 137 | if err != nil { 138 | log.Println(err) 139 | return 140 | } 141 | w.app.Preferences().SetString(APP_SESSIONS, string(confJson)) 142 | 143 | cmdJson, err := json.Marshal(w.cmds) 144 | if err != nil { 145 | log.Println(err) 146 | return 147 | } 148 | w.app.Preferences().SetString(APP_COMMANDS, string(cmdJson)) 149 | 150 | } 151 | -------------------------------------------------------------------------------- /docker.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | 7 | "fyne.io/fyne/v2" 8 | "fyne.io/fyne/v2/container" 9 | "fyne.io/fyne/v2/dialog" 10 | "fyne.io/fyne/v2/widget" 11 | containerTypes "github.com/docker/docker/api/types/container" 12 | "github.com/docker/docker/client" 13 | ) 14 | 15 | type DockerConfigData struct { 16 | Name string `json:"name,omitempty"` 17 | Type string `json:"type,omitempty"` 18 | Host string `json:"host,omitempty"` 19 | } 20 | 21 | type DockerConfig struct { 22 | data *DockerConfigData 23 | onOk func() 24 | } 25 | 26 | func (c *DockerConfig) Name() string { 27 | return c.data.Name 28 | } 29 | 30 | func (c *DockerConfig) Type() string { 31 | return "docker" 32 | } 33 | 34 | func (c *DockerConfig) Load(s string) error { 35 | data := &DockerConfigData{} 36 | 37 | err := json.Unmarshal([]byte(s), data) 38 | if err != nil { 39 | return err 40 | } 41 | c.data = data 42 | return nil 43 | } 44 | func (c *DockerConfig) Data() interface{} { 45 | return c.data 46 | } 47 | 48 | func (c *DockerConfig) Form() *widget.Form { 49 | 50 | nameEntry := widget.NewEntry() 51 | hostEntry := widget.NewEntry() 52 | 53 | data := c.data 54 | if data != nil { 55 | nameEntry.Text = data.Name 56 | nameEntry.Disable() 57 | hostEntry.Text = data.Host 58 | } 59 | c.onOk = func() { 60 | if c.data == nil { 61 | c.data = &DockerConfigData{Type: c.Type()} 62 | } 63 | c.data.Name = nameEntry.Text 64 | c.data.Host = hostEntry.Text 65 | } 66 | return widget.NewForm([]*widget.FormItem{ 67 | widget.NewFormItem("Name", nameEntry), 68 | widget.NewFormItem("Host", hostEntry), 69 | }...) 70 | } 71 | 72 | func (c *DockerConfig) OnOk() { 73 | c.onOk() 74 | } 75 | func (c *DockerConfig) Term(win *Window) { 76 | opts := make([]client.Opt, 0) 77 | if len(c.data.Host) > 0 { 78 | opts = append(opts, client.WithHost(c.data.Host)) 79 | } 80 | dockerCli, err := client.NewClientWithOpts(opts...) 81 | if err != nil { 82 | win.showError(err) 83 | return 84 | } 85 | contList, err := dockerCli.ContainerList(context.Background(), containerTypes.ListOptions{}) 86 | if err != nil { 87 | win.showError(err) 88 | return 89 | } 90 | var dlg dialog.Dialog 91 | list := widget.NewList(func() int { 92 | return len(contList) 93 | }, func() fyne.CanvasObject { 94 | return container.NewHBox(widget.NewLabel(""), widget.NewButton("Connect", func() { 95 | })) 96 | }, func(id widget.ListItemID, object fyne.CanvasObject) { 97 | box := object.(*fyne.Container) 98 | cont := contList[id] 99 | 100 | label := box.Objects[0].(*widget.Label) 101 | btn := box.Objects[1].(*widget.Button) 102 | label.SetText(cont.ID[:12]) 103 | btn.OnTapped = func() { 104 | 105 | execId, err := dockerCli.ContainerExecCreate(context.Background(), cont.ID, containerTypes.ExecOptions{Tty: true, Detach: true, AttachStdin: true, AttachStderr: true, AttachStdout: true, Cmd: []string{"/bin/sh"}}) 106 | if err != nil { 107 | win.showError(err) 108 | return 109 | } 110 | 111 | attach, err := dockerCli.ContainerExecAttach(context.Background(), execId.ID, containerTypes.ExecAttachOptions{Tty: true, Detach: false}) 112 | if err != nil { 113 | win.showError(err) 114 | return 115 | } 116 | 117 | term := NewTerm(c.Name(), c) 118 | 119 | go func() { 120 | defer attach.Close() 121 | err = term.RunWithConnection(attach.Conn) 122 | if err != nil { 123 | win.showError(err) 124 | return 125 | } 126 | }() 127 | 128 | win.AddTermTab(term) 129 | if dlg != nil { 130 | dlg.Hide() 131 | } 132 | } 133 | }) 134 | 135 | list.Resize(fyne.Size{Width: 400, Height: 500}) 136 | 137 | dlg = dialog.NewCustom("Select a container", "Cancel", list, win.win) 138 | dlg.Resize(fyne.Size{Width: 400, Height: 500}) 139 | dlg.Show() 140 | } 141 | -------------------------------------------------------------------------------- /ssh.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fyne.io/fyne/v2/widget" 6 | "golang.org/x/crypto/ssh" 7 | "log" 8 | "net" 9 | "strconv" 10 | ) 11 | 12 | type SSHConfigData struct { 13 | Name string `json:"name,omitempty"` 14 | Type string `json:"type,omitempty"` 15 | Host string `json:"host,omitempty"` 16 | Port int `json:"port,omitempty"` 17 | User string `json:"user,omitempty"` 18 | Pswd string `json:"pswd,omitempty"` 19 | } 20 | 21 | type SSHConfigForm struct { 22 | nameEntry *widget.Entry 23 | hostEntry *widget.Entry 24 | portEntry *widget.Entry 25 | userEntry *widget.Entry 26 | pswdEntry *widget.Entry 27 | } 28 | 29 | type SSHConfig struct { 30 | data *SSHConfigData 31 | form *SSHConfigForm 32 | onOk func() 33 | } 34 | 35 | func (c *SSHConfig) Name() string { 36 | return c.data.Name 37 | } 38 | 39 | func (c *SSHConfig) Type() string { 40 | return "ssh" 41 | } 42 | 43 | func (c *SSHConfig) Load(s string) error { 44 | data := &SSHConfigData{} 45 | 46 | err := json.Unmarshal([]byte(s), data) 47 | if err != nil { 48 | return err 49 | } 50 | c.data = data 51 | return nil 52 | } 53 | 54 | func (c *SSHConfig) Data() interface{} { 55 | return c.data 56 | } 57 | 58 | func (c *SSHConfig) Form() *widget.Form { 59 | c.form = &SSHConfigForm{} 60 | nameEntry := widget.NewEntry() 61 | hostEntry := widget.NewEntry() 62 | portEntry := widget.NewEntry() 63 | userEntry := widget.NewEntry() 64 | pswdEntry := widget.NewEntry() 65 | 66 | portEntry.Text = "22" 67 | portEntry.Validator = func(s string) error { 68 | _, err := strconv.Atoi(s) 69 | return err 70 | } 71 | pswdEntry.Password = true 72 | data := c.data 73 | if data != nil { 74 | nameEntry.Text = data.Name 75 | nameEntry.Disable() 76 | hostEntry.Text = data.Host 77 | portEntry.Text = strconv.Itoa(data.Port) 78 | userEntry.Text = data.User 79 | pswdEntry.Text = data.Pswd 80 | } 81 | c.onOk = func() { 82 | if c.data == nil { 83 | c.data = &SSHConfigData{Type: c.Type()} 84 | } 85 | c.data.Name = nameEntry.Text 86 | c.data.Host = hostEntry.Text 87 | c.data.Port, _ = strconv.Atoi(portEntry.Text) 88 | c.data.User = userEntry.Text 89 | c.data.Pswd = pswdEntry.Text 90 | } 91 | return widget.NewForm([]*widget.FormItem{ 92 | widget.NewFormItem("Name", nameEntry), 93 | widget.NewFormItem("Host", hostEntry), 94 | widget.NewFormItem("Port", portEntry), 95 | widget.NewFormItem("Username", userEntry), 96 | widget.NewFormItem("Password", pswdEntry), 97 | }...) 98 | } 99 | 100 | func (c *SSHConfig) OnOk() { 101 | c.onOk() 102 | } 103 | 104 | func (c *SSHConfig) Term(win *Window) { 105 | conf := c.data 106 | cli := ssh.ClientConfig{User: conf.User, Auth: []ssh.AuthMethod{ 107 | ssh.Password(conf.Pswd), 108 | }, HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { 109 | return nil 110 | }} 111 | 112 | addr := conf.Host + ":" + strconv.Itoa(conf.Port) 113 | conn, err := ssh.Dial("tcp", addr, &cli) 114 | if err != nil { 115 | log.Println(err) 116 | win.showError(err) 117 | return 118 | } 119 | session, err := conn.NewSession() 120 | if err != nil { 121 | log.Println(err) 122 | win.showError(err) 123 | return 124 | } 125 | modes := ssh.TerminalModes{ 126 | ssh.ECHO: 0, // disable echoing 127 | ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud 128 | ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud 129 | } 130 | err = session.RequestPty("xterm-color", 24, 80, modes) 131 | if err != nil { 132 | log.Println(err) 133 | win.showError(err) 134 | return 135 | } 136 | in, err := session.StdinPipe() 137 | if err != nil { 138 | log.Println(err) 139 | win.showError(err) 140 | return 141 | } 142 | out, err := session.StdoutPipe() 143 | if err != nil { 144 | log.Println(err) 145 | win.showError(err) 146 | return 147 | } 148 | 149 | term := NewTerm(conf.Name, c) 150 | 151 | go func() { 152 | defer session.Close() 153 | err = term.RunWithReaderAndWriter(in, out) 154 | if err != nil { 155 | log.Println(err) 156 | } 157 | session.Close() 158 | }() 159 | 160 | go func() { 161 | err := session.Shell() 162 | if err != nil { 163 | log.Println(err) 164 | } 165 | }() 166 | 167 | win.AddTermTab(term) 168 | } 169 | -------------------------------------------------------------------------------- /window.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fyne.io/fyne/v2" 5 | "fyne.io/fyne/v2/app" 6 | "fyne.io/fyne/v2/container" 7 | "fyne.io/fyne/v2/dialog" 8 | "fyne.io/fyne/v2/layout" 9 | "fyne.io/fyne/v2/theme" 10 | "fyne.io/fyne/v2/widget" 11 | "github.com/fyne-io/terminal" 12 | ) 13 | 14 | const APP_NAME = "Go Shell" 15 | const APP_KEY = "com.github.tk103331.goshell" 16 | const APP_SESSIONS = "sessions" 17 | const APP_COMMANDS = "commands" 18 | 19 | var iconMap map[string]fyne.Resource 20 | 21 | func init() { 22 | iconMap = make(map[string]fyne.Resource) 23 | iconMap["file"] = theme.FileIcon() 24 | iconMap["document"] = theme.DocumentIcon() 25 | iconMap["computer"] = theme.ComputerIcon() 26 | } 27 | 28 | type Window struct { 29 | app fyne.App 30 | win fyne.Window 31 | tabs *container.DocTabs 32 | terms map[*container.TabItem]*Term 33 | confs []Config 34 | cmds []*Cmd 35 | 36 | cmdbar *fyne.Container 37 | } 38 | 39 | func (w *Window) AddTermTab(tab *Term) { 40 | tabItem := container.TabItem{Text: tab.Name(), Icon: theme.ComputerIcon(), Content: tab.term} 41 | tab.AddConfigListener(func(config *terminal.Config) { 42 | if len(config.Title) > 0 { 43 | tabItem.Text = config.Title 44 | } else { 45 | tabItem.Text = tab.Name() 46 | } 47 | }) 48 | w.tabs.Append(&tabItem) 49 | w.terms[&tabItem] = tab 50 | w.tabs.Select(&tabItem) 51 | } 52 | 53 | func (w *Window) AddConfig(conf *SSHConfig) { 54 | w.confs = append(w.confs, conf) 55 | w.save() 56 | } 57 | 58 | func (w *Window) AddCmd(cmd *Cmd) { 59 | w.cmds = append(w.cmds, cmd) 60 | w.save() 61 | icon := iconMap[cmd.Icon] 62 | w.cmdbar.Add(widget.NewButtonWithIcon(cmd.Text, icon, func() { 63 | w.sendCmd(cmd) 64 | })) 65 | } 66 | 67 | func (w *Window) RemoveConfig(index int) { 68 | if index < 0 || index > len(w.confs) { 69 | return 70 | } 71 | w.confs = append(w.confs[:index], w.confs[index+1:]...) 72 | w.save() 73 | } 74 | 75 | func (w *Window) Run(stop <-chan struct{}) { 76 | 77 | w.app = app.NewWithID(APP_KEY) 78 | 79 | // 加载用户设置 80 | settings := w.LoadSettings() 81 | w.ApplySettings(settings) 82 | 83 | go func() { 84 | defer w.app.Quit() 85 | <-stop 86 | }() 87 | 88 | w.load() 89 | w.terms = make(map[*container.TabItem]*Term) 90 | w.win = w.app.NewWindow(APP_NAME) 91 | w.win.Resize(fyne.NewSize(800, 600)) 92 | w.initUI() 93 | 94 | w.win.ShowAndRun() 95 | } 96 | 97 | func (w *Window) initUI() { 98 | toolbar := widget.NewToolbar(widget.NewToolbarAction(theme.ComputerIcon(), func() { 99 | tab := NewLocalTerm() 100 | w.AddTermTab(tab) 101 | }), widget.NewToolbarAction(theme.DocumentIcon(), func() { 102 | w.showCreateConfigDialog() 103 | }), widget.NewToolbarAction(theme.ContentAddIcon(), func() { 104 | w.showNewCmdDialog() 105 | }), 106 | widget.NewToolbarSpacer(), 107 | widget.NewToolbarAction(theme.SettingsIcon(), func() { 108 | w.showSettingsDialog() 109 | }), 110 | widget.NewToolbarAction(theme.InfoIcon(), func() { 111 | w.showAboutDialog() 112 | })) 113 | 114 | buttons := make([]fyne.CanvasObject, len(w.cmds)) 115 | for i, cmd := range w.cmds { 116 | if icon, ok := iconMap[cmd.Icon]; ok { 117 | buttons[i] = widget.NewButtonWithIcon(cmd.Name, icon, func() { 118 | w.sendCmd(cmd) 119 | }) 120 | } else { 121 | buttons[i] = widget.NewButton(cmd.Name, func() { 122 | w.sendCmd(cmd) 123 | }) 124 | } 125 | } 126 | w.cmdbar = container.NewHBox(buttons...) 127 | 128 | sidebar := widget.NewList(func() int { 129 | return len(w.confs) 130 | }, func() fyne.CanvasObject { 131 | return container.NewHBox(widget.NewLabel(""), layout.NewSpacer(), 132 | widget.NewButtonWithIcon("", theme.DocumentCreateIcon(), nil), 133 | widget.NewButtonWithIcon("", theme.DeleteIcon(), nil), 134 | widget.NewButtonWithIcon("", theme.ComputerIcon(), nil)) 135 | }, func(id widget.ListItemID, object fyne.CanvasObject) { 136 | box := object.(*fyne.Container) 137 | label := box.Objects[0].(*widget.Label) 138 | edit := box.Objects[2].(*widget.Button) 139 | del := box.Objects[3].(*widget.Button) 140 | open := box.Objects[4].(*widget.Button) 141 | 142 | conf := w.confs[id] 143 | label.Text = conf.Name() 144 | edit.OnTapped = func() { 145 | w.showModifyConfigDialog(conf) 146 | } 147 | del.OnTapped = func() { 148 | w.RemoveConfig(id) 149 | } 150 | open.OnTapped = func() { 151 | conf.Term(w) 152 | } 153 | }) 154 | 155 | w.tabs = container.NewDocTabs() 156 | w.createLocalTermTab() 157 | w.tabs.OnClosed = func(item *container.TabItem) { 158 | if term, ok := w.terms[item]; ok { 159 | term.Exit() 160 | } 161 | } 162 | center := container.NewHSplit(sidebar, w.tabs) 163 | center.Offset = 0.2 164 | 165 | content := container.NewBorder(toolbar, w.cmdbar, nil, nil, center) 166 | 167 | w.win.SetContent(content) 168 | } 169 | 170 | func (w *Window) showAboutDialog() { 171 | dialog.NewInformation(APP_NAME, "GoShell is a simple terminal GUI client, written in Go,via Fyne. ", w.win).Show() 172 | } 173 | 174 | func (w *Window) showError(e error) { 175 | dialog.ShowError(e, w.win) 176 | } 177 | 178 | func (w *Window) sendCmd(cmd *Cmd) { 179 | tabItem := w.tabs.Selected() 180 | if tabItem != nil { 181 | if term, ok := w.terms[tabItem]; ok { 182 | term.Send(cmd.Text) 183 | } 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tk103331/goshell 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | fyne.io/fyne/v2 v2.7.0 7 | github.com/docker/docker v28.5.1+incompatible 8 | github.com/fyne-io/terminal v0.0.0-20250418150501-61f2dac1c2ad 9 | github.com/tk103331/stream v1.0.2 10 | golang.org/x/crypto v0.42.0 11 | k8s.io/api v0.34.1 12 | k8s.io/apimachinery v0.34.1 13 | k8s.io/client-go v0.34.1 14 | ) 15 | 16 | require ( 17 | fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 // indirect 18 | github.com/ActiveState/termtest/conpty v0.5.0 // indirect 19 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect 20 | github.com/BurntSushi/toml v1.5.0 // indirect 21 | github.com/Microsoft/go-winio v0.4.14 // indirect 22 | github.com/containerd/errdefs v1.0.0 // indirect 23 | github.com/containerd/errdefs/pkg v0.3.0 // indirect 24 | github.com/containerd/log v0.1.0 // indirect 25 | github.com/creack/pty v1.1.21 // indirect 26 | github.com/davecgh/go-spew v1.1.1 // indirect 27 | github.com/distribution/reference v0.6.0 // indirect 28 | github.com/docker/go-connections v0.5.0 // indirect 29 | github.com/docker/go-units v0.5.0 // indirect 30 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 31 | github.com/felixge/httpsnoop v1.0.4 // indirect 32 | github.com/fredbi/uri v1.1.1 // indirect 33 | github.com/fsnotify/fsnotify v1.9.0 // indirect 34 | github.com/fxamacker/cbor/v2 v2.9.0 // indirect 35 | github.com/fyne-io/gl-js v0.2.0 // indirect 36 | github.com/fyne-io/glfw-js v0.3.0 // indirect 37 | github.com/fyne-io/image v0.1.1 // indirect 38 | github.com/fyne-io/oksvg v0.2.0 // indirect 39 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect 40 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect 41 | github.com/go-logr/logr v1.4.2 // indirect 42 | github.com/go-logr/stdr v1.2.2 // indirect 43 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 44 | github.com/go-openapi/jsonreference v0.20.2 // indirect 45 | github.com/go-openapi/swag v0.23.0 // indirect 46 | github.com/go-text/render v0.2.0 // indirect 47 | github.com/go-text/typesetting v0.2.1 // indirect 48 | github.com/godbus/dbus/v5 v5.1.0 // indirect 49 | github.com/gogo/protobuf v1.3.2 // indirect 50 | github.com/golang/protobuf v1.5.4 // indirect 51 | github.com/google/gnostic-models v0.7.0 // indirect 52 | github.com/google/uuid v1.6.0 // indirect 53 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect 54 | github.com/hack-pad/go-indexeddb v0.3.2 // indirect 55 | github.com/hack-pad/safejs v0.1.0 // indirect 56 | github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect 57 | github.com/josharian/intern v1.0.0 // indirect 58 | github.com/json-iterator/go v1.1.12 // indirect 59 | github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect 60 | github.com/mailru/easyjson v0.7.7 // indirect 61 | github.com/moby/docker-image-spec v1.3.1 // indirect 62 | github.com/moby/spdystream v0.5.0 // indirect 63 | github.com/moby/sys/atomicwriter v0.1.0 // indirect 64 | github.com/moby/term v0.5.2 // indirect 65 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 66 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect 67 | github.com/morikuni/aec v1.0.0 // indirect 68 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 69 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 70 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect 71 | github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect 72 | github.com/opencontainers/go-digest v1.0.0 // indirect 73 | github.com/opencontainers/image-spec v1.1.1 // indirect 74 | github.com/pkg/errors v0.9.1 // indirect 75 | github.com/pmezard/go-difflib v1.0.0 // indirect 76 | github.com/rymdport/portal v0.4.2 // indirect 77 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect 78 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect 79 | github.com/stretchr/testify v1.11.1 // indirect 80 | github.com/x448/float16 v0.8.4 // indirect 81 | github.com/yuin/goldmark v1.7.8 // indirect 82 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 83 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect 84 | go.opentelemetry.io/otel v1.35.0 // indirect 85 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect 86 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 87 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 88 | go.yaml.in/yaml/v2 v2.4.2 // indirect 89 | go.yaml.in/yaml/v3 v3.0.4 // indirect 90 | golang.org/x/image v0.24.0 // indirect 91 | golang.org/x/net v0.43.0 // indirect 92 | golang.org/x/oauth2 v0.27.0 // indirect 93 | golang.org/x/sys v0.36.0 // indirect 94 | golang.org/x/term v0.35.0 // indirect 95 | golang.org/x/text v0.29.0 // indirect 96 | golang.org/x/time v0.9.0 // indirect 97 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 // indirect 98 | google.golang.org/protobuf v1.36.5 // indirect 99 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 100 | gopkg.in/inf.v0 v0.9.1 // indirect 101 | gopkg.in/yaml.v3 v3.0.1 // indirect 102 | gotest.tools/v3 v3.5.2 // indirect 103 | k8s.io/klog/v2 v2.130.1 // indirect 104 | k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect 105 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect 106 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 107 | sigs.k8s.io/randfill v1.0.0 // indirect 108 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect 109 | sigs.k8s.io/yaml v1.6.0 // indirect 110 | ) 111 | -------------------------------------------------------------------------------- /k8s.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "fyne.io/fyne/v2" 8 | "fyne.io/fyne/v2/dialog" 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" 18 | ) 19 | 20 | type K8SConfigData struct { 21 | Name string `json:"name,omitempty"` 22 | Type string `json:"type,omitempty"` 23 | Server string `json:"server,omitempty"` 24 | Token string `json:"token,omitempty"` 25 | } 26 | type K8SConfig struct { 27 | data *K8SConfigData 28 | onOk func() 29 | } 30 | 31 | func (c *K8SConfig) Name() string { 32 | return c.data.Name 33 | } 34 | 35 | func (c *K8SConfig) Type() string { 36 | return "k8s" 37 | } 38 | func (c *K8SConfig) Load(s string) error { 39 | data := &K8SConfigData{} 40 | 41 | err := json.Unmarshal([]byte(s), data) 42 | if err != nil { 43 | return err 44 | } 45 | c.data = data 46 | return nil 47 | } 48 | func (c *K8SConfig) Data() interface{} { 49 | return c.data 50 | } 51 | 52 | func (c *K8SConfig) Form() *widget.Form { 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 | }...) 78 | } 79 | 80 | func (c *K8SConfig) OnOk() { 81 | c.onOk() 82 | } 83 | 84 | type ExecOpt struct { 85 | Namespace string 86 | PodName string 87 | Container string 88 | } 89 | 90 | func (c *K8SConfig) Term(win *Window) { 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 236 | } 237 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | fyne.io/fyne/v2 v2.7.0 h1:GvZSpE3X0liU/fqstInVvRsaboIVpIWQ4/sfjDGIGGQ= 3 | fyne.io/fyne/v2 v2.7.0/go.mod h1:xClVlrhxl7D+LT+BWYmcrW4Nf+dJTvkhnPgji7spAwE= 4 | fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 h1:eA5/u2XRd8OUkoMqEv3IBlFYSruNlXD8bRHDiqm0VNI= 5 | fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs= 6 | github.com/ActiveState/termtest/conpty v0.5.0 h1:JLUe6YDs4Jw4xNPCU+8VwTpniYOGeKzQg4SM2YHQNA8= 7 | github.com/ActiveState/termtest/conpty v0.5.0/go.mod h1:LO4208FLsxw6DcNZ1UtuGUMW+ga9PFtX4ntv8Ymg9og= 8 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 9 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= 10 | github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 11 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 12 | github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= 13 | github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= 14 | github.com/Microsoft/go-winio v0.4.14 h1:+hMXMk01us9KgxGb7ftKQt2Xpf5hH/yky+TDA+qxleU= 15 | github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= 16 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 17 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 18 | github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 19 | github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 20 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 21 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 22 | github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= 23 | github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= 24 | github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= 25 | github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= 26 | github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= 27 | github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= 28 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 29 | github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= 30 | github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 31 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 32 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 33 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= 35 | github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= 36 | github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= 37 | github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 38 | github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= 39 | github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= 40 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 41 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 42 | github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= 43 | github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 44 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 45 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 46 | github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= 47 | github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= 48 | github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 49 | github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 50 | github.com/fredbi/uri v1.1.1 h1:xZHJC08GZNIUhbP5ImTHnt5Ya0T8FI2VAwI/37kh2Ko= 51 | github.com/fredbi/uri v1.1.1/go.mod h1:4+DZQ5zBjEwQCDmXW5JdIjz0PUA+yJbvtBv+u+adr5o= 52 | github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= 53 | github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 54 | github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= 55 | github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 56 | github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs= 57 | github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI= 58 | github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk= 59 | github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk= 60 | github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA= 61 | github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM= 62 | github.com/fyne-io/oksvg v0.2.0 h1:mxcGU2dx6nwjJsSA9PCYZDuoAcsZ/OuJlvg/Q9Njfo8= 63 | github.com/fyne-io/oksvg v0.2.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI= 64 | github.com/fyne-io/terminal v0.0.0-20250418150501-61f2dac1c2ad h1:M5/qTfU+yJcSBMNi1J3OEQxFHXXl2BY+VlLMbS1Cfn8= 65 | github.com/fyne-io/terminal v0.0.0-20250418150501-61f2dac1c2ad/go.mod h1:CY2M2CZ8Kz4wkhorz1ys0zStiSogubVwe8LM7HAuxe0= 66 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA= 67 | github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= 68 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a h1:vxnBhFDDT+xzxf1jTJKMKZw3H0swfWk9RpWbBbDK5+0= 69 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 70 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 71 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 72 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 73 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 74 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 75 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 76 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 77 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 78 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 79 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 80 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 81 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 82 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 83 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 84 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 85 | github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc= 86 | github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU= 87 | github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8= 88 | github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M= 89 | github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0= 90 | github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= 91 | github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= 92 | github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 93 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 94 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 95 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 96 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 97 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 98 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 99 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 100 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 101 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 102 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 103 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 104 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 105 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 106 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 107 | github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= 108 | github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= 109 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 110 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 111 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 112 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 113 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 114 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 115 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 116 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= 117 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 118 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 119 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 120 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= 121 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= 122 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= 123 | github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= 124 | github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A= 125 | github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0= 126 | github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8= 127 | github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio= 128 | github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE= 129 | github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o= 130 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 131 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 132 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 133 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 134 | github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M= 135 | github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw= 136 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 137 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 138 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 139 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 140 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 141 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 142 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 143 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 144 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 145 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 146 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 147 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 148 | github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= 149 | github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= 150 | github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= 151 | github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= 152 | github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= 153 | github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= 154 | github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= 155 | github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= 156 | github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= 157 | github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= 158 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 159 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 160 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 161 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 162 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= 163 | github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 164 | github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= 165 | github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= 166 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 167 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 168 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 169 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 170 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= 171 | github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= 172 | github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk= 173 | github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ= 174 | github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= 175 | github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 176 | github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= 177 | github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 178 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 179 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 180 | github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= 181 | github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= 182 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 183 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 184 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 185 | github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= 186 | github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= 187 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 188 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 189 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 190 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 191 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 192 | github.com/rymdport/portal v0.4.2 h1:7jKRSemwlTyVHHrTGgQg7gmNPJs88xkbKcIL3NlcmSU= 193 | github.com/rymdport/portal v0.4.2/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4= 194 | github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= 195 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 196 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 197 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 198 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 199 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE= 200 | github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q= 201 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ= 202 | github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE= 203 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 204 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 205 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 206 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 207 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 208 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 209 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 210 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 211 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 212 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 213 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 214 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 215 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 216 | github.com/tk103331/stream v1.0.2 h1:+yf8gdvhdYlAo42SFmuy2LiS2Lnem33RPfhlnrSMRM8= 217 | github.com/tk103331/stream v1.0.2/go.mod h1:Sr7nLQQwyJrBZl1gwzFzdjTn6OVkMf2ZvmvZn+LKlM0= 218 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 219 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 220 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 221 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 222 | github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= 223 | github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= 224 | go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 225 | go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 226 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= 227 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= 228 | go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= 229 | go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= 230 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= 231 | go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= 232 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= 233 | go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= 234 | go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= 235 | go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= 236 | go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= 237 | go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= 238 | go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= 239 | go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= 240 | go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= 241 | go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= 242 | go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= 243 | go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= 244 | go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= 245 | go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= 246 | go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= 247 | go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= 248 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 249 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 250 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 251 | golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= 252 | golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= 253 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 254 | golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= 255 | golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= 256 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 257 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 258 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 259 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 260 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 261 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 262 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 263 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 264 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 265 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 266 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 267 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 268 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 269 | golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= 270 | golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= 271 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 272 | golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= 273 | golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 274 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 275 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 276 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 277 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 278 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 279 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 280 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 281 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 282 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 283 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 284 | golang.org/x/sys v0.0.0-20200428200454-593003d681fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 285 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 286 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 287 | golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= 288 | golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 289 | golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= 290 | golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= 291 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 292 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 293 | golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= 294 | golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= 295 | golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= 296 | golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 297 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 298 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 299 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 300 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 301 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 302 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 303 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 304 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 305 | golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= 306 | golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= 307 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 308 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 309 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 310 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 311 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 312 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 313 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 314 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 315 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 316 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705 h1:PYBmACG+YEv8uQPW0r1kJj8tR+gkF0UWq7iFdUezwEw= 317 | google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 318 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 319 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 320 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 321 | google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg= 322 | google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec= 323 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 324 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 325 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 326 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 327 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 328 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 329 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 330 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 331 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 332 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 333 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 334 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 335 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 336 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 337 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 338 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 339 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 340 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 341 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 342 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 343 | gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 344 | gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 345 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 346 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 347 | k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= 348 | k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= 349 | k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= 350 | k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= 351 | k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= 352 | k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= 353 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 354 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 355 | k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= 356 | k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= 357 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= 358 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 359 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= 360 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 361 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 362 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 363 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= 364 | sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= 365 | sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= 366 | sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= 367 | --------------------------------------------------------------------------------