2022-05-02 00:27:01

k9s项目pod视图源码分析

k9s项目pod视图源码分析

k9s

终端可视化的k8s管理工具

核心组件

  • k8s client

目录结构

├─cmd //命令入口
├─internal
│  ├─client //**操作客户端
│  ├─color //调色盘-存放一些常用的颜色
│  ├─config //各种配置信息,可以读取文件/环境变量 (默认视图、主题样式、日志格式、快捷键、插件等)
│  ├─dao //**类似操作数据库获取数据
│  ├─health //资源健康检测记录
│  ├─model //** 数据结构的深层封装(定义一些监听事件、刷新事件、过滤方法、其它辅助方法) 
│  ├─perf //Benchmark性能测试
│  ├─port //端口转发
│  ├─render //**生成表头(header),渲染每一行给定的数据(render)
│  ├─tchart //自定义图表 tview 组件
│  ├─ui //** 自封装的ui组件(菜单、主视图、logo、路由、下拉选择、表格、树形、面包屑) 并包含了样式变动的监听事件(StylesChanged) 部分包含updete(更新显示)、部分实现了StackListener接口
│  │  └─dialog //封装的弹窗
│  ├─view //**各种资源的表格视图
│  ├─watch //k8s informer
│  └─xray //**各种资源的树形依赖视图 渲染树形节点(Render)
├─plugins //扩展插件
│  ├─kubectl
│  └─kubectl-plugins
└─skins //适配的皮肤样式

流程代码分析

  • 程序入口
func main() { // cmd/root.go:56 // run cmd.Execute() }
  • 默认命令 k9s 入口
func run(cmd *cobra.Command, args []string) { // 1. 确保日志目录存在 config.EnsurePath(*k9sFlags.LogFile, config.DefaultDirMod) // 2. 日志初始化 f, _ := os.Create("app.log") log.Logger = log.Output(zerolog.ConsoleWriter{Out: f}) zerolog.SetGlobalLevel(zerolog.DebugLevel) // 3. 加载配置信息 // 4. 实例化ViewApp app := view.NewApp(loadConfiguration()) // 5. 初始化应用-go if err := app.Init(version, *k9sFlags.RefreshRate); err != nil { panic(fmt.Sprintf("app init failed -- %v", err)) } // 6. 事件循环-go if err := app.Run(); err != nil { panic(fmt.Sprintf("app run failed %v", err)) } if view.ExitStatus != "" { panic(fmt.Sprintf("view exit status %s", view.ExitStatus)) } }
  • 初始化
func (a *App) Init(version string, rate int) error { a.version = model.NormalizeVersion(version) ctx := context.WithValue(context.Background(), internal.KeyApp, a) // 5.1 视图页面堆栈初始化 // Content PageStack 每次跳转的时候会调用 Start 开启视图页面内的数据刷新 if err := a.Content.Init(ctx); err != nil { return err } // 5.2 将面包屑导航和菜单导航都加入监听事件中 // 每次跳转都会触发更新 a.Content.Stack.AddListener(a.Crumbs()) a.Content.Stack.AddListener(a.Menu()) // 5.3 UI APP初始化 // 设置按键绑定、视图监听、样式监听、Tview设置主页面 a.App.Init() // 5.4 捕获全局按键事件 a.SetInputCapture(a.keyboard) // 5.5 全局按键事件注册和5.3中类似,当按键不同 a.bindKeys() if a.Conn() == nil { return errors.New("No client connection detected") } // 5.6 获取当前命名空间 ns := a.Config.ActiveNamespace() // 5.7 创建k8s informer a.factory = watch.NewFactory(a.Conn()) ok, err := a.isValidNS(ns) if !ok && err == nil { return fmt.Errorf("Invalid namespace %s", ns) } // 5.8 初始化 informer 加载各种资源信息 a.initFactory(ns) // 5.9 辅助信息监听刷新 a.clusterModel = model.NewClusterInfo(a.factory, a.version) a.clusterModel.AddListener(a.clusterInfo()) a.clusterModel.AddListener(a.statusIndicator()) if a.Conn().ConnectionOK() { a.clusterModel.Refresh() a.clusterInfo().Init() } // 5.9 实例并初始化命令-go a.command = NewCommand(a) if err := a.command.Init(); err != nil { return err } // 5.10 输入命令时提示 a.CmdBuff().SetSuggestionFn(a.suggestCommand()) // 5.11 初始化页面布局 a.layout(ctx) // 5.12 监听结束退出信号 a.initSignals() return nil }
  • 初始化命令
// Init initializes the command. func (c *Command) Init() error { // 5.9.1 初始化 c.alias = dao.NewAlias(c.app.factory) // 5.9.2 定义资源组 if _, err := c.alias.Ensure(); err != nil { log.Error().Err(err).Msgf("command init failed!") return err } // 5.9.3 注册子页面 组/版本/资源 -视图 // 一个接口:包含 viewerFn 实例化视图页面 // enterFn 在这个页面里,回车确认时执行的方法 customViewers = loadCustomViewers() return nil }
  • 事件循环
// Run starts the application loop. func (a *App) Run() error { // 6.1 初次触发当前显示页面的 Start 刷新数据 a.Resume() go func() { <-time.After(splashDelay) a.QueueUpdateDraw(func() { a.Main.SwitchToPage("main") }) }() // 6.2 执行第一个命令 -go if err := a.command.defaultCmd(); err != nil { return err } a.SetRunning(true) // 6.3 事件循环交互 Tview 自带 if err := a.Application.Run(); err != nil { return err } return nil }
  • 默认执行的命令
func (c *Command) defaultCmd() error { // 6.2.1 k8s连接失败,结束 if !c.app.Conn().ConnectionOK() { return c.run("ctx", "", true) } // 6.2.2 获取默认页面视图 po view := c.app.Config.ActiveView() if view == "" { //和默认一个效果 return c.run("pod", "", true) } tokens := strings.Split(view, " ") cmd := view if len(tokens) == 1 { if !isContextCmd(tokens[0]) { cmd = tokens[0] + " " + c.app.Config.ActiveNamespace() } } // 6.2.3 切换页面视图 po all if err := c.run(cmd, "", true); err != nil { log.Error().Err(err).Msgf("Default run command failed %q", cmd) c.app.cowCmd(err.Error()) return err } return nil }
  • 执行命令
// Exec the Command by showing associated display. // 切换页面 func (c *Command) run(cmd, path string, clearStack bool) error { // 6.2.3.1 判断是否是特殊命令并执行 if c.specialCmd(cmd, path) { return nil } cmds := strings.Split(cmd, " ") // 6.2.3.2 通过命令获取标准资源表示方式 组/版本/资源 // 通过标准表示方式获取注册的视图页面接口变量值 // 一个接口:包含 viewerFn 实例化视图页面 // enterFn 在这个页面里,回车确认时执行的方法 gvr, v, err := c.viewMetaFor(cmds[0]) if err != nil { return err } // 执行命令 // 6.2.3.5 c.componentFor(gvr, path, v) 使用 MetaViewer 接口的 viewerFn 方法实列化视图组件 // gvr v1/pods return c.exec(cmd, gvr, c.componentFor(gvr, path, v), clearStack) }
  • 执行命令
func (c *Command) exec(cmd, gvr string, comp model.Component, clearStack bool) (err error) { // 6.2.3.5.1 激活当前视图 c.app.Config.SetActiveView(cmd) // 6.2.3.5.2 保存记录 if err := c.app.Config.Save(); err != nil { log.Error().Err(err).Msg("Config save failed!") } if clearStack { c.app.Content.Stack.Clear() } // 6.2.3.5.3 注入当前页面视图组件到 main 主视图 if err := c.app.inject(comp); err != nil { return err } // 6.2.3.5.4 记录历史命令更好的提示或返回返回页面 c.app.cmdHistory.Push(cmd) return }
  • 页面跳转
// 初始化子组件并进入 func (a *App) inject(c model.Component) error { ctx := context.WithValue(context.Background(), internal.KeyApp, a) // 6.2.3.5.3.1 具体页面初始化 // 注册页面按键子命令 // folder view file:browser.go l:46 if err := c.Init(ctx); err != nil { log.Error().Err(err).Msgf("component init failed for %q", c.Name()) dialog.ShowError(a.Styles.Dialog(), a.Content.Pages, err.Error()) } // 6.2.3.5.3.2 类似 vue-router push() 切换路由 并会调用 c.Start()开启数据刷新 a.Content.Push(c) return nil }

本文链接:https://blog.zxysilent.com/post/k9s-pod-view-code.html

-- EOF --