├── README.md ├── config └── config.go ├── controller ├── configmap.go ├── daemonset.go ├── deployment.go ├── ingress.go ├── namespace.go ├── node.go ├── pod.go ├── pv.go ├── pvc.go ├── router.go ├── secret.go ├── service.go ├── statefulset.go └── workflow.go ├── dao └── workflow.go ├── db ├── init.go └── workflow.sql ├── go.mod ├── go.sum ├── main.go ├── middle ├── cors.go └── jwt.go ├── model └── workflow.go ├── service ├── configmap.go ├── daemonset.go ├── dataselector.go ├── deployment.go ├── ingress.go ├── init.go ├── namespace.go ├── node.go ├── pod.go ├── pv.go ├── pvc.go ├── secret.go ├── service.go ├── statefulset.go ├── terminal.go └── workflow.go └── utils └── jwt.go /README.md: -------------------------------------------------------------------------------- 1 | # k8s-platform 2 | K8s管理系统后端: go+gin 3 | 项目背景,整体设计,Client-go,框架搭建 4 | 5 | 一、项目背景 6 | 随着容器技术的广泛应用,kubernetes逐渐成为业内的核心技术,是容器编排技术的首选工具。而k8s管理平台在日常的容器维护中也发挥着举足轻重的作用,但随着k8s的定制化功能越来越多,dashboard已经无法满足日常的维护需求,且dashboard的源码学习成本较高,抽象程度较高,二次发成本也就比较高。 7 | 本项目使用当下较主流的前端vue+element plus及后端go+gin框架,打造与dashboard对标的k8s管理功能,且可定制化程度高,可根据自身需求,进行灵活定制开发。 8 | 9 | 二、前后端分离概述 10 | 前后端分离已成为互联网项目开发的业界标准使用方式,通过nginx+tomcat的方式( 也可以中间加一个node.js)有效的进行解耦,并且前后端分离会为以后的大型分布式架构、弹性计算架构、微服务架构、多端化服务( 多种客户端,例如:浏览器,车载终端,安卓,IOS等等)打下坚实的基础。这个步要是系统架构从进化成人的必经之路。 11 |  前后分离的优势 : 12 | 1.可以实现真正的前后端解耦,前端服务器使用nginx。 13 | 2.发现bug,可以快速定位是谁的问题,不会出现互相踢皮球的现象 14 | 15 | 3.在大并发情况下,可以同时水平扩展前后端服务器 16 | 4.增加代码的维护性&易读性(前后端耦在一起的代码读起来相当费劲 ) 17 | 18 | 5.提升开发效率,因为可以前后端并行开发,而不是像以前的强依赖 19 | 20 | 三、功能设计 21 |  四、client-go介绍 22 | 1、简介 23 | client-go是kubernetes官方提供的go语言的客户端库,go应用使用该库可以访问kubernetes的API Server,这样我们就能通过编程来对kubermetes资源进行增删改查操作: 24 | 除了提供丰富的API用于操kubernetes资源,client-go还为controller和operator提供了重要支持client-go的informer机制可以将controller关注的资源变化及时带给此controller,使controller能够及时响应变化。 25 | 通过client-go提供的客户端对象与kubernetes的API Server进行交豆,而client-go提供了以下四种客户端对象 26 | (1)RESTClient:这是最基础的客户端对象,仅对HTTPRequest进行了封装,实现RESTFul风格API,这个对象的使用并不方便,因为很多参数都要使用者来设置,于是client-go基于RESTClient又实现了三种新的客户端对象; 27 | (2)ClientSet;把Resoure和Version也封装成方法了,用起亲更简单直接,一个资源是一个客户端,多个资源就对应了多个客户端所以ClientSet就是多个客户端的集合了,这样就理解了,不过ClientSet只能访问内置资源,访问不了自定义资源 28 | (3)DynamiClient;可以访问内置资源和自定义资源,个人感觉有点像Java的集合操作,拿出的内容是Object类型,按实际情况自己去做强制转换,当然了也会有强转失败的风险: 29 | (4)DiscoveryClient: 用于发现kubernetes的API Server支持的Group、Version、Resources等信息; 30 | 31 | // kubectl api-resources 32 | 33 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "time" 4 | 5 | const ( 6 | ListenAddr = "0.0.0.0:9090" 7 | KubeConfig = "C:\\Users\\Administrator\\.kube\\config" 8 | // tail的日志行数 9 | // tail -n 2000 10 | PodLogTailLine = 2000 11 | //登录账号密码 12 | AdminUser = "admin" 13 | AdminPwd = "123456" 14 | 15 | // DB Config 16 | DbType = "mysql" 17 | DbHost = "192.168.204.130" 18 | DbPort = 3306 19 | DbName = "k8s_dashboard" 20 | DbUser = "root" 21 | DbPass = "1234" 22 | // 打印mysql debug的sql日志 23 | LogMode = false 24 | // 连接池配置 25 | MaxIdleConns = 10 // 最大空闲连接 26 | MaxOpenConns = 100 // 最大连接数 27 | MaxLifeTime = 30 * time.Second // 会话时间 28 | ) 29 | -------------------------------------------------------------------------------- /controller/configmap.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var ConfigMap configMap 12 | 13 | type configMap struct{} 14 | 15 | // 获取configmap列表,支持过滤、排序、分页 16 | func (c *configMap) GetConfigMaps(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Namespace string `form:"namespace"` 20 | Page int `form:"page"` 21 | Limit int `form:"limit"` 22 | }) 23 | if err := ctx.Bind(params); err != nil { 24 | logger.Error("Bind请求参数失败, " + err.Error()) 25 | ctx.JSON(http.StatusInternalServerError, gin.H{ 26 | "msg": err.Error(), 27 | "data": nil, 28 | }) 29 | return 30 | } 31 | 32 | data, err := service.ConfigMap.GetConfigMaps(params.FilterName, params.Namespace, params.Limit, params.Page) 33 | if err != nil { 34 | ctx.JSON(http.StatusInternalServerError, gin.H{ 35 | "msg": err.Error(), 36 | "data": nil, 37 | }) 38 | return 39 | } 40 | 41 | ctx.JSON(http.StatusOK, gin.H{ 42 | "msg": "获取ConfigMap列表成功", 43 | "data": data, 44 | }) 45 | } 46 | 47 | // 获取configmap详情 48 | func (c *configMap) GetConfigMapDetail(ctx *gin.Context) { 49 | params := new(struct { 50 | ConfigMapName string `form:"configmap_name"` 51 | Namespace string `form:"namespace"` 52 | }) 53 | if err := ctx.Bind(params); err != nil { 54 | logger.Error("Bind请求参数失败, " + err.Error()) 55 | ctx.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": err.Error(), 57 | "data": nil, 58 | }) 59 | return 60 | } 61 | 62 | data, err := service.ConfigMap.GetConfigMapDetail(params.ConfigMapName, params.Namespace) 63 | if err != nil { 64 | ctx.JSON(http.StatusInternalServerError, gin.H{ 65 | "msg": err.Error(), 66 | "data": nil, 67 | }) 68 | return 69 | } 70 | 71 | ctx.JSON(http.StatusOK, gin.H{ 72 | "msg": "获取ConfigMap详情成功", 73 | "data": data, 74 | }) 75 | } 76 | 77 | // 删除configmap 78 | func (c *configMap) DeleteConfigMap(ctx *gin.Context) { 79 | params := new(struct { 80 | ConfigMapName string `json:"configmap_name"` 81 | Namespace string `json:"namespace"` 82 | }) 83 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 84 | if err := ctx.ShouldBindJSON(params); err != nil { 85 | logger.Error("Bind请求参数失败, " + err.Error()) 86 | ctx.JSON(http.StatusInternalServerError, gin.H{ 87 | "msg": err.Error(), 88 | "data": nil, 89 | }) 90 | return 91 | } 92 | 93 | err := service.ConfigMap.DeleteConfigMap(params.ConfigMapName, params.Namespace) 94 | if err != nil { 95 | ctx.JSON(http.StatusInternalServerError, gin.H{ 96 | "msg": err.Error(), 97 | "data": nil, 98 | }) 99 | return 100 | } 101 | ctx.JSON(http.StatusOK, gin.H{ 102 | "msg": "删除ConfigMap成功", 103 | "data": nil, 104 | }) 105 | } 106 | 107 | // 更新configmap 108 | func (c *configMap) UpdateConfigMap(ctx *gin.Context) { 109 | params := new(struct { 110 | Namespace string `json:"namespace"` 111 | Content string `json:"content"` 112 | }) 113 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 114 | if err := ctx.ShouldBindJSON(params); err != nil { 115 | logger.Error("Bind请求参数失败, " + err.Error()) 116 | ctx.JSON(http.StatusInternalServerError, gin.H{ 117 | "msg": err.Error(), 118 | "data": nil, 119 | }) 120 | return 121 | } 122 | 123 | err := service.ConfigMap.UpdateConfigMap(params.Namespace, params.Content) 124 | if err != nil { 125 | ctx.JSON(http.StatusInternalServerError, gin.H{ 126 | "msg": err.Error(), 127 | "data": nil, 128 | }) 129 | return 130 | } 131 | ctx.JSON(http.StatusOK, gin.H{ 132 | "msg": "更新ConfigMap成功", 133 | "data": nil, 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /controller/daemonset.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var DaemonSet daemonSet 12 | 13 | type daemonSet struct{} 14 | 15 | //获取daemonset列表,支持过滤、排序、分页 16 | func (d *daemonSet) GetDaemonSets(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Namespace string `form:"namespace"` 20 | Page int `form:"page"` 21 | Limit int `form:"limit"` 22 | }) 23 | if err := ctx.Bind(params); err != nil { 24 | logger.Error("Bind请求参数失败, " + err.Error()) 25 | ctx.JSON(http.StatusInternalServerError, gin.H{ 26 | "msg": err.Error(), 27 | "data": nil, 28 | }) 29 | return 30 | } 31 | 32 | data, err := service.DaemonSet.GetDaemonSets(params.FilterName, params.Namespace, params.Limit, params.Page) 33 | if err != nil { 34 | ctx.JSON(http.StatusInternalServerError, gin.H{ 35 | "msg": err.Error(), 36 | "data": nil, 37 | }) 38 | return 39 | } 40 | 41 | ctx.JSON(http.StatusOK, gin.H{ 42 | "msg": "获取DaemonSet列表成功", 43 | "data": data, 44 | }) 45 | } 46 | 47 | //获取daemonset详情 48 | func (d *daemonSet) GetDaemonSetDetail(ctx *gin.Context) { 49 | params := new(struct { 50 | DaemonSetName string `form:"daemonset_name"` 51 | Namespace string `form:"namespace"` 52 | }) 53 | if err := ctx.Bind(params); err != nil { 54 | logger.Error("Bind请求参数失败, " + err.Error()) 55 | ctx.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": err.Error(), 57 | "data": nil, 58 | }) 59 | return 60 | } 61 | 62 | data, err := service.DaemonSet.GetDaemonSetDetail(params.DaemonSetName, params.Namespace) 63 | if err != nil { 64 | ctx.JSON(http.StatusInternalServerError, gin.H{ 65 | "msg": err.Error(), 66 | "data": nil, 67 | }) 68 | return 69 | } 70 | 71 | ctx.JSON(http.StatusOK, gin.H{ 72 | "msg": "获取DaemonSet详情成功", 73 | "data": data, 74 | }) 75 | } 76 | 77 | //删除daemonset 78 | func (d *daemonSet) DeleteDaemonSet(ctx *gin.Context) { 79 | params := new(struct { 80 | DaemonSetName string `json:"daemonset_name"` 81 | Namespace string `json:"namespace"` 82 | }) 83 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 84 | if err := ctx.ShouldBindJSON(params); err != nil { 85 | logger.Error("Bind请求参数失败, " + err.Error()) 86 | ctx.JSON(http.StatusInternalServerError, gin.H{ 87 | "msg": err.Error(), 88 | "data": nil, 89 | }) 90 | return 91 | } 92 | 93 | err := service.DaemonSet.DeleteDaemonSet(params.DaemonSetName, params.Namespace) 94 | if err != nil { 95 | ctx.JSON(http.StatusInternalServerError, gin.H{ 96 | "msg": err.Error(), 97 | "data": nil, 98 | }) 99 | return 100 | } 101 | ctx.JSON(http.StatusOK, gin.H{ 102 | "msg": "删除DaemonSet成功", 103 | "data": nil, 104 | }) 105 | } 106 | 107 | //更新daemonset 108 | func (d *daemonSet) UpdateDaemonSet(ctx *gin.Context) { 109 | params := new(struct { 110 | Namespace string `json:"namespace"` 111 | Content string `json:"content"` 112 | }) 113 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 114 | if err := ctx.ShouldBindJSON(params); err != nil { 115 | logger.Error("Bind请求参数失败, " + err.Error()) 116 | ctx.JSON(http.StatusInternalServerError, gin.H{ 117 | "msg": err.Error(), 118 | "data": nil, 119 | }) 120 | return 121 | } 122 | 123 | err := service.DaemonSet.UpdateDaemonSet(params.Namespace, params.Content) 124 | if err != nil { 125 | ctx.JSON(http.StatusInternalServerError, gin.H{ 126 | "msg": err.Error(), 127 | "data": nil, 128 | }) 129 | return 130 | } 131 | ctx.JSON(http.StatusOK, gin.H{ 132 | "msg": "更新DaemonSet成功", 133 | "data": nil, 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /controller/deployment.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "k8s-platform/service" 6 | "net/http" 7 | 8 | "github.com/gin-gonic/gin" 9 | "github.com/wonderivan/logger" 10 | ) 11 | 12 | var Deployment deployment 13 | 14 | type deployment struct{} 15 | 16 | // 获取deployment列表,支持过滤、排序、分页 17 | func (d *deployment) GetDeployments(ctx *gin.Context) { 18 | params := new(struct { 19 | FilterName string `form:"filter_name"` 20 | Namespace string `form:"namespace"` 21 | Page int `form:"page"` 22 | Limit int `form:"limit"` 23 | }) 24 | if err := ctx.Bind(params); err != nil { 25 | logger.Error("Bind请求参数失败, " + err.Error()) 26 | ctx.JSON(http.StatusInternalServerError, gin.H{ 27 | "msg": err.Error(), 28 | "data": nil, 29 | }) 30 | return 31 | } 32 | 33 | data, err := service.Deployment.GetDeployments(params.FilterName, params.Namespace, params.Limit, params.Page) 34 | if err != nil { 35 | ctx.JSON(http.StatusInternalServerError, gin.H{ 36 | "msg": err.Error(), 37 | "data": nil, 38 | }) 39 | return 40 | } 41 | 42 | ctx.JSON(http.StatusOK, gin.H{ 43 | "msg": "获取Deployment列表成功", 44 | "data": data, 45 | }) 46 | } 47 | 48 | // 获取deployment详情 49 | func (d *deployment) GetDeploymentDetail(ctx *gin.Context) { 50 | params := new(struct { 51 | DeploymentName string `form:"deployment_name"` 52 | Namespace string `form:"namespace"` 53 | }) 54 | if err := ctx.Bind(params); err != nil { 55 | logger.Error("Bind请求参数失败, " + err.Error()) 56 | ctx.JSON(http.StatusInternalServerError, gin.H{ 57 | "msg": err.Error(), 58 | "data": nil, 59 | }) 60 | return 61 | } 62 | data, err := service.Deployment.GetDeploymentDetail(params.DeploymentName, params.Namespace) 63 | if err != nil { 64 | ctx.JSON(http.StatusInternalServerError, gin.H{ 65 | "msg": err.Error(), 66 | "data": nil, 67 | }) 68 | return 69 | } 70 | ctx.JSON(http.StatusOK, gin.H{ 71 | "msg": "获取Deployment详情成功", 72 | "data": data, 73 | }) 74 | } 75 | 76 | // 创建deployment 77 | func (d *deployment) CreateDeployment(ctx *gin.Context) { 78 | var ( 79 | deployCreate = new(service.DeployCreate) 80 | err error 81 | ) 82 | 83 | if err = ctx.ShouldBindJSON(deployCreate); err != nil { 84 | logger.Error("Bind请求参数失败, " + err.Error()) 85 | ctx.JSON(http.StatusInternalServerError, gin.H{ 86 | "msg": err.Error(), 87 | "data": nil, 88 | }) 89 | return 90 | } 91 | 92 | if err = service.Deployment.CreateDeployment(deployCreate); err != nil { 93 | ctx.JSON(http.StatusInternalServerError, gin.H{ 94 | "msg": err.Error(), 95 | "data": nil, 96 | }) 97 | } 98 | 99 | ctx.JSON(http.StatusOK, gin.H{ 100 | "msg": "创建Deployment成功", 101 | "data": nil, 102 | }) 103 | } 104 | 105 | // 设置deployment副本数 106 | func (d *deployment) ScaleDeployment(ctx *gin.Context) { 107 | params := new(struct { 108 | DeploymentName string `json:"deployment_name"` 109 | Namespace string `json:"namespace"` 110 | ScaleNum int `json:"scale_num"` 111 | }) 112 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 113 | if err := ctx.ShouldBindJSON(params); err != nil { 114 | logger.Error("Bind请求参数失败, " + err.Error()) 115 | ctx.JSON(http.StatusInternalServerError, gin.H{ 116 | "msg": err.Error(), 117 | "data": nil, 118 | }) 119 | return 120 | } 121 | 122 | data, err := service.Deployment.ScaleDeployment(params.DeploymentName, params.Namespace, params.ScaleNum) 123 | if err != nil { 124 | ctx.JSON(http.StatusInternalServerError, gin.H{ 125 | "msg": err.Error(), 126 | "data": nil, 127 | }) 128 | return 129 | } 130 | ctx.JSON(http.StatusOK, gin.H{ 131 | "msg": "设置Deployment副本数成功", 132 | "data": fmt.Sprintf("最新副本数: %d", data), 133 | }) 134 | } 135 | 136 | // 删除deployment 137 | func (d *deployment) DeleteDeployment(ctx *gin.Context) { 138 | params := new(struct { 139 | DeploymentName string `json:"deployment_name"` 140 | Namespace string `json:"namespace"` 141 | }) 142 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 143 | if err := ctx.ShouldBindJSON(params); err != nil { 144 | logger.Error("Bind请求参数失败, " + err.Error()) 145 | ctx.JSON(http.StatusInternalServerError, gin.H{ 146 | "msg": err.Error(), 147 | "data": nil, 148 | }) 149 | return 150 | } 151 | 152 | err := service.Deployment.DeleteDeployment(params.DeploymentName, params.Namespace) 153 | if err != nil { 154 | ctx.JSON(http.StatusInternalServerError, gin.H{ 155 | "msg": err.Error(), 156 | "data": nil, 157 | }) 158 | return 159 | } 160 | ctx.JSON(http.StatusOK, gin.H{ 161 | "msg": "删除Deployment成功", 162 | "data": nil, 163 | }) 164 | } 165 | 166 | // 重启deployment 167 | func (d *deployment) RestartDeployment(ctx *gin.Context) { 168 | params := new(struct { 169 | DeploymentName string `json:"deployment_name"` 170 | Namespace string `json:"namespace"` 171 | }) 172 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 173 | if err := ctx.ShouldBindJSON(params); err != nil { 174 | logger.Error("Bind请求参数失败, " + err.Error()) 175 | ctx.JSON(http.StatusInternalServerError, gin.H{ 176 | "msg": err.Error(), 177 | "data": nil, 178 | }) 179 | return 180 | } 181 | 182 | err := service.Deployment.RestartDeployment(params.DeploymentName, params.Namespace) 183 | if err != nil { 184 | ctx.JSON(http.StatusInternalServerError, gin.H{ 185 | "msg": err.Error(), 186 | "data": nil, 187 | }) 188 | return 189 | } 190 | ctx.JSON(http.StatusOK, gin.H{ 191 | "msg": "重启Deployment成功", 192 | "data": nil, 193 | }) 194 | } 195 | 196 | // 更新deployment 197 | func (d *deployment) UpdateDeployment(ctx *gin.Context) { 198 | params := new(struct { 199 | Namespace string `json:"namespace"` 200 | Content string `json:"content"` 201 | }) 202 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 203 | if err := ctx.ShouldBindJSON(params); err != nil { 204 | logger.Error("Bind请求参数失败, " + err.Error()) 205 | ctx.JSON(http.StatusInternalServerError, gin.H{ 206 | "msg": err.Error(), 207 | "data": nil, 208 | }) 209 | return 210 | } 211 | 212 | err := service.Deployment.UpdateDeployment(params.Namespace, params.Content) 213 | if err != nil { 214 | ctx.JSON(http.StatusInternalServerError, gin.H{ 215 | "msg": err.Error(), 216 | "data": nil, 217 | }) 218 | return 219 | } 220 | ctx.JSON(http.StatusOK, gin.H{ 221 | "msg": "更新Deployment成功", 222 | "data": nil, 223 | }) 224 | } 225 | 226 | // 获取每个namespace的pod数量 227 | func (d *deployment) GetDeployNumPerNp(ctx *gin.Context) { 228 | data, err := service.Deployment.GetDeployNumPerNp() 229 | if err != nil { 230 | ctx.JSON(http.StatusInternalServerError, gin.H{ 231 | "msg": err.Error(), 232 | "data": nil, 233 | }) 234 | return 235 | } 236 | 237 | ctx.JSON(http.StatusOK, gin.H{ 238 | "msg": "获取每个namespace的deployment数量成功", 239 | "data": data, 240 | }) 241 | } 242 | -------------------------------------------------------------------------------- /controller/ingress.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Ingress ingress 12 | 13 | type ingress struct{} 14 | 15 | //获取ingress列表,支持过滤、排序、分页 16 | func (i *ingress) GetIngresses(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Namespace string `form:"namespace"` 20 | Page int `form:"page"` 21 | Limit int `form:"limit"` 22 | }) 23 | if err := ctx.Bind(params); err != nil { 24 | logger.Error("Bind请求参数失败, " + err.Error()) 25 | ctx.JSON(http.StatusInternalServerError, gin.H{ 26 | "msg": err.Error(), 27 | "data": nil, 28 | }) 29 | return 30 | } 31 | 32 | data, err := service.Ingress.GetIngresses(params.FilterName, params.Namespace, params.Limit, params.Page) 33 | if err != nil { 34 | ctx.JSON(http.StatusInternalServerError, gin.H{ 35 | "msg": err.Error(), 36 | "data": nil, 37 | }) 38 | return 39 | } 40 | 41 | ctx.JSON(http.StatusOK, gin.H{ 42 | "msg": "获取Ingresst列表成功", 43 | "data": data, 44 | }) 45 | } 46 | 47 | //获取ingress详情 48 | func (i *ingress) GetIngressDetail(ctx *gin.Context) { 49 | params := new(struct { 50 | IngressName string `form:"ingress_name"` 51 | Namespace string `form:"namespace"` 52 | }) 53 | if err := ctx.Bind(params); err != nil { 54 | logger.Error("Bind请求参数失败, " + err.Error()) 55 | ctx.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": err.Error(), 57 | "data": nil, 58 | }) 59 | return 60 | } 61 | 62 | data, err := service.Ingress.GetIngresstDetail(params.IngressName, params.Namespace) 63 | if err != nil { 64 | ctx.JSON(http.StatusInternalServerError, gin.H{ 65 | "msg": err.Error(), 66 | "data": nil, 67 | }) 68 | return 69 | } 70 | 71 | ctx.JSON(http.StatusOK, gin.H{ 72 | "msg": "获取Ingress详情成功", 73 | "data": data, 74 | }) 75 | } 76 | 77 | //删除ingress 78 | func (i *ingress) DeleteIngress(ctx *gin.Context) { 79 | params := new(struct { 80 | IngressName string `json:"ingress_name"` 81 | Namespace string `json:"namespace"` 82 | }) 83 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 84 | if err := ctx.ShouldBindJSON(params); err != nil { 85 | logger.Error("Bind请求参数失败, " + err.Error()) 86 | ctx.JSON(http.StatusInternalServerError, gin.H{ 87 | "msg": err.Error(), 88 | "data": nil, 89 | }) 90 | return 91 | } 92 | 93 | err := service.Ingress.DeleteIngress(params.IngressName, params.Namespace) 94 | if err != nil { 95 | ctx.JSON(http.StatusInternalServerError, gin.H{ 96 | "msg": err.Error(), 97 | "data": nil, 98 | }) 99 | return 100 | } 101 | ctx.JSON(http.StatusOK, gin.H{ 102 | "msg": "删除Ingress成功", 103 | "data": nil, 104 | }) 105 | } 106 | 107 | //创建ingress 108 | func (i *ingress) CreateIngress(ctx *gin.Context) { 109 | var ( 110 | ingressCreate = new(service.IngressCreate) 111 | err error 112 | ) 113 | 114 | if err = ctx.ShouldBindJSON(ingressCreate); err != nil { 115 | logger.Error("Bind请求参数失败, " + err.Error()) 116 | ctx.JSON(http.StatusInternalServerError, gin.H{ 117 | "msg": err.Error(), 118 | "data": nil, 119 | }) 120 | return 121 | } 122 | 123 | if err = service.Ingress.CreateIngress(ingressCreate); err != nil { 124 | ctx.JSON(http.StatusInternalServerError, gin.H{ 125 | "msg": err.Error(), 126 | "data": nil, 127 | }) 128 | return 129 | } 130 | 131 | ctx.JSON(http.StatusOK, gin.H{ 132 | "msg": "创建Ingress成功", 133 | "data": nil, 134 | }) 135 | } 136 | 137 | //更新ingress 138 | func (i *ingress) UpdateIngress(ctx *gin.Context) { 139 | params := new(struct { 140 | Namespace string `json:"namespace"` 141 | Content string `json:"content"` 142 | }) 143 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 144 | if err := ctx.ShouldBindJSON(params); err != nil { 145 | logger.Error("Bind请求参数失败, " + err.Error()) 146 | ctx.JSON(http.StatusInternalServerError, gin.H{ 147 | "msg": err.Error(), 148 | "data": nil, 149 | }) 150 | return 151 | } 152 | 153 | err := service.Ingress.UpdateIngress(params.Namespace, params.Content) 154 | if err != nil { 155 | ctx.JSON(http.StatusInternalServerError, gin.H{ 156 | "msg": err.Error(), 157 | "data": nil, 158 | }) 159 | return 160 | } 161 | ctx.JSON(http.StatusOK, gin.H{ 162 | "msg": "更新Ingress成功", 163 | "data": nil, 164 | }) 165 | } 166 | -------------------------------------------------------------------------------- /controller/namespace.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Namespace namespace 12 | 13 | type namespace struct{} 14 | 15 | // 获取namespace列表,支持过滤、排序、分页 16 | func (n *namespace) GetNamespaces(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Page int `form:"page"` 20 | Limit int `form:"limit"` 21 | }) 22 | if err := ctx.Bind(params); err != nil { 23 | logger.Error("Bind请求参数失败, " + err.Error()) 24 | ctx.JSON(http.StatusInternalServerError, gin.H{ 25 | "msg": err.Error(), 26 | "data": nil, 27 | }) 28 | return 29 | } 30 | 31 | data, err := service.Namespace.GetNamespaces(params.FilterName, params.Limit, params.Page) 32 | if err != nil { 33 | ctx.JSON(http.StatusInternalServerError, gin.H{ 34 | "msg": err.Error(), 35 | "data": nil, 36 | }) 37 | return 38 | } 39 | 40 | ctx.JSON(http.StatusOK, gin.H{ 41 | "msg": "获取Namespace列表成功", 42 | "data": data, 43 | }) 44 | } 45 | 46 | // 获取namespace详情 47 | func (n *namespace) GetNamespaceDetail(ctx *gin.Context) { 48 | params := new(struct { 49 | NamespaceName string `form:"namespace_name"` 50 | }) 51 | if err := ctx.Bind(params); err != nil { 52 | logger.Error("Bind请求参数失败, " + err.Error()) 53 | ctx.JSON(http.StatusInternalServerError, gin.H{ 54 | "msg": err.Error(), 55 | "data": nil, 56 | }) 57 | return 58 | } 59 | 60 | data, err := service.Namespace.GetNamespaceDetail(params.NamespaceName) 61 | if err != nil { 62 | ctx.JSON(http.StatusInternalServerError, gin.H{ 63 | "msg": err.Error(), 64 | "data": nil, 65 | }) 66 | return 67 | } 68 | 69 | ctx.JSON(http.StatusOK, gin.H{ 70 | "msg": "获取Namespace详情成功", 71 | "data": data, 72 | }) 73 | } 74 | 75 | // 删除namespace 76 | func (n *namespace) DeleteNamespace(ctx *gin.Context) { 77 | params := new(struct { 78 | NamespaceName string `json:"namespace_name"` 79 | }) 80 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 81 | if err := ctx.ShouldBindJSON(params); err != nil { 82 | logger.Error("Bind请求参数失败, " + err.Error()) 83 | ctx.JSON(http.StatusInternalServerError, gin.H{ 84 | "msg": err.Error(), 85 | "data": nil, 86 | }) 87 | return 88 | } 89 | 90 | err := service.Namespace.DeleteNamespace(params.NamespaceName) 91 | if err != nil { 92 | ctx.JSON(http.StatusInternalServerError, gin.H{ 93 | "msg": err.Error(), 94 | "data": nil, 95 | }) 96 | return 97 | } 98 | ctx.JSON(http.StatusOK, gin.H{ 99 | "msg": "删除Namespace成功", 100 | "data": nil, 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /controller/node.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Node node 12 | 13 | type node struct{} 14 | 15 | //获取node列表,支持过滤、排序、分页 16 | func (n *node) GetNodes(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Page int `form:"page"` 20 | Limit int `form:"limit"` 21 | }) 22 | if err := ctx.Bind(params); err != nil { 23 | logger.Error("Bind请求参数失败, " + err.Error()) 24 | ctx.JSON(http.StatusInternalServerError, gin.H{ 25 | "msg": err.Error(), 26 | "data": nil, 27 | }) 28 | return 29 | } 30 | 31 | data, err := service.Node.GetNodes(params.FilterName, params.Limit, params.Page) 32 | if err != nil { 33 | ctx.JSON(http.StatusInternalServerError, gin.H{ 34 | "msg": err.Error(), 35 | "data": nil, 36 | }) 37 | return 38 | } 39 | 40 | ctx.JSON(http.StatusOK, gin.H{ 41 | "msg": "获取Node列表成功", 42 | "data": data, 43 | }) 44 | } 45 | 46 | //获取node详情 47 | func (n *node) GetNodeDetail(ctx *gin.Context) { 48 | params := new(struct { 49 | NodeName string `form:"node_name"` 50 | }) 51 | if err := ctx.Bind(params); err != nil { 52 | logger.Error("Bind请求参数失败, " + err.Error()) 53 | ctx.JSON(http.StatusInternalServerError, gin.H{ 54 | "msg": err.Error(), 55 | "data": nil, 56 | }) 57 | return 58 | } 59 | 60 | data, err := service.Node.GetNodeDetail(params.NodeName) 61 | if err != nil { 62 | ctx.JSON(http.StatusInternalServerError, gin.H{ 63 | "msg": err.Error(), 64 | "data": nil, 65 | }) 66 | return 67 | } 68 | 69 | ctx.JSON(http.StatusOK, gin.H{ 70 | "msg": "获取Node详情成功", 71 | "data": data, 72 | }) 73 | } 74 | -------------------------------------------------------------------------------- /controller/pod.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Pod pod 12 | 13 | type pod struct{} 14 | 15 | // Controller中的方法入参是gin.Context,用于从上下文中获取请求参数及定义响应内容 16 | // 流程:绑定参数->调用service代码->根据调用结果响应具体内容 17 | // 获取Pod列表,支持分页、过滤、排序 18 | func (p *pod) GetPods(ctx *gin.Context) { 19 | // 处理入参 20 | // 匿名结构体,用于定义入参,get请求为form格式,其他请求为json格式 21 | params := new(struct { 22 | FilterName string `form:"filter_name"` 23 | Namespace string `form:"namespace"` 24 | Limit int `form:"limit"` 25 | Page int `form:"page"` 26 | }) 27 | // form格式使用Bind方法,json格式使用ShouldBindJSON方法 28 | if err := ctx.Bind(params); err != nil { 29 | logger.Error("Bind绑定参数失败" + err.Error()) 30 | ctx.JSON(http.StatusInternalServerError, gin.H{ 31 | "msg": "Bind绑定参数失败" + err.Error(), 32 | "data": nil, 33 | }) 34 | return 35 | } 36 | data, err := service.Pod.GetPods(params.FilterName, params.Namespace, params.Limit, params.Page) 37 | if err != nil { 38 | ctx.JSON(http.StatusInternalServerError, gin.H{ 39 | "msg": "Bind绑定参数失败" + err.Error(), 40 | "data": nil, 41 | }) 42 | return 43 | } 44 | ctx.JSON(http.StatusOK, gin.H{ 45 | "msg": "获取Pod列表成功", 46 | "data": data, 47 | }) 48 | } 49 | 50 | // 获取pod详情 51 | func (p *pod) GetPodDetail(ctx *gin.Context) { 52 | // 处理入参 53 | // 匿名结构体,用于定义入参,get请求为form格式,其他请求为json格式 54 | params := new(struct { 55 | PodName string `form:"pod_name"` 56 | Namespace string `form:"namespace"` 57 | }) 58 | // form格式使用Bind方法,json格式使用ShouldBindJSON方法 59 | if err := ctx.Bind(params); err != nil { 60 | logger.Error("Bind绑定参数失败" + err.Error()) 61 | ctx.JSON(http.StatusInternalServerError, gin.H{ 62 | "msg": "Bind绑定参数失败" + err.Error(), 63 | "data": nil, 64 | }) 65 | return 66 | } 67 | data, err := service.Pod.GetPodDetail(params.PodName, params.Namespace) 68 | if err != nil { 69 | ctx.JSON(http.StatusInternalServerError, gin.H{ 70 | "msg": err.Error(), 71 | "data": nil, 72 | }) 73 | return 74 | } 75 | ctx.JSON(http.StatusOK, gin.H{ 76 | "msg": "获取Pod列表成功", 77 | "data": data, 78 | }) 79 | } 80 | 81 | // 删除pod 82 | func (p *pod) DeletePod(ctx *gin.Context) { 83 | // 处理入参 84 | // 匿名结构体,用于定义入参,get请求为form格式,其他请求为json格式 85 | params := new(struct { 86 | PodName string `form:"pod_name"` 87 | Namespace string `form:"namespace"` 88 | }) 89 | // form格式使用Bind方法,json格式使用ShouldBindJSON方法 90 | if err := ctx.ShouldBindJSON(params); err != nil { 91 | logger.Error("Bind绑定参数失败" + err.Error()) 92 | ctx.JSON(http.StatusInternalServerError, gin.H{ 93 | "msg": "Bind绑定参数失败" + err.Error(), 94 | "data": nil, 95 | }) 96 | return 97 | } 98 | err := service.Pod.DeletePod(params.PodName, params.Namespace) 99 | if err != nil { 100 | ctx.JSON(http.StatusInternalServerError, gin.H{ 101 | "msg": err.Error(), 102 | "data": nil, 103 | }) 104 | return 105 | } 106 | ctx.JSON(http.StatusOK, gin.H{ 107 | "msg": "删除Pod成功", 108 | "data": nil, 109 | }) 110 | } 111 | 112 | // 更新pod 113 | func (p *pod) UpdatePod(ctx *gin.Context) { 114 | params := new(struct { 115 | // PodName string `json:"pod_name"` 116 | Namespace string `json:"namespace"` 117 | Content string `json:"content"` 118 | }) 119 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 120 | if err := ctx.ShouldBindJSON(params); err != nil { 121 | logger.Error("Bind请求参数失败, " + err.Error()) 122 | ctx.JSON(http.StatusInternalServerError, gin.H{ 123 | "msg": err.Error(), 124 | "data": nil, 125 | }) 126 | return 127 | } 128 | // err := service.Pod.UpdatePod(params.PodName, params.Namespace, params.Content) 129 | err := service.Pod.UpdatePod(params.Namespace, params.Content) 130 | 131 | if err != nil { 132 | ctx.JSON(http.StatusInternalServerError, gin.H{ 133 | "msg": err.Error(), 134 | "data": nil, 135 | }) 136 | return 137 | } 138 | ctx.JSON(http.StatusOK, gin.H{ 139 | "msg": "更新Pod成功", 140 | "data": nil, 141 | }) 142 | } 143 | 144 | // 获取pod容器 145 | func (p *pod) GetPodContainer(ctx *gin.Context) { 146 | params := new(struct { 147 | PodName string `form:"pod_name"` 148 | Namespace string `form:"namespace"` 149 | }) 150 | //GET请求,绑定参数方法改为ctx.Bind 151 | if err := ctx.Bind(params); err != nil { 152 | logger.Error("Bind请求参数失败, " + err.Error()) 153 | ctx.JSON(http.StatusInternalServerError, gin.H{ 154 | "msg": err.Error(), 155 | "data": nil, 156 | }) 157 | return 158 | } 159 | data, err := service.Pod.GetPodContainer(params.PodName, params.Namespace) 160 | if err != nil { 161 | ctx.JSON(http.StatusInternalServerError, gin.H{ 162 | "msg": err.Error(), 163 | "data": nil, 164 | }) 165 | return 166 | } 167 | ctx.JSON(http.StatusOK, gin.H{ 168 | "msg": "获取Pod容器成功", 169 | "data": data, 170 | }) 171 | } 172 | 173 | // 获取pod中容器日志 174 | func (p *pod) GetPodLog(ctx *gin.Context) { 175 | params := new(struct { 176 | ContainerName string `form:"container_name"` 177 | PodName string `form:"pod_name"` 178 | Namespace string `form:"namespace"` 179 | }) 180 | //GET请求,绑定参数方法改为ctx.Bind 181 | if err := ctx.Bind(params); err != nil { 182 | logger.Error("Bind请求参数失败, " + err.Error()) 183 | ctx.JSON(http.StatusInternalServerError, gin.H{ 184 | "msg": err.Error(), 185 | "data": nil, 186 | }) 187 | return 188 | } 189 | data, err := service.Pod.GetPodLog(params.ContainerName, params.PodName, params.Namespace) 190 | if err != nil { 191 | ctx.JSON(http.StatusInternalServerError, gin.H{ 192 | "msg": err.Error(), 193 | "data": nil, 194 | }) 195 | return 196 | } 197 | ctx.JSON(http.StatusOK, gin.H{ 198 | "msg": "获取Pod中容器日志成功", 199 | "data": data, 200 | }) 201 | } 202 | 203 | // 获取每个namespace的pod数量 204 | func (p *pod) GetPodNumPerNp(ctx *gin.Context) { 205 | data, err := service.Pod.GetPodNumPerNp() 206 | if err != nil { 207 | ctx.JSON(http.StatusInternalServerError, gin.H{ 208 | "msg": err.Error(), 209 | "data": nil, 210 | }) 211 | return 212 | } 213 | 214 | ctx.JSON(http.StatusOK, gin.H{ 215 | "msg": "获取每个namespace的pod数量成功", 216 | "data": data, 217 | }) 218 | } 219 | -------------------------------------------------------------------------------- /controller/pv.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Pv pv 12 | 13 | type pv struct{} 14 | 15 | //获取pv列表,支持过滤、排序、分页 16 | func (p *pv) GetPvs(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Page int `form:"page"` 20 | Limit int `form:"limit"` 21 | }) 22 | if err := ctx.Bind(params); err != nil { 23 | logger.Error("Bind请求参数失败, " + err.Error()) 24 | ctx.JSON(http.StatusInternalServerError, gin.H{ 25 | "msg": err.Error(), 26 | "data": nil, 27 | }) 28 | return 29 | } 30 | 31 | data, err := service.Pv.GetPvs(params.FilterName, params.Limit, params.Page) 32 | if err != nil { 33 | ctx.JSON(http.StatusInternalServerError, gin.H{ 34 | "msg": err.Error(), 35 | "data": nil, 36 | }) 37 | return 38 | } 39 | 40 | ctx.JSON(http.StatusOK, gin.H{ 41 | "msg": "获取Pv列表成功", 42 | "data": data, 43 | }) 44 | } 45 | 46 | //获取pv详情 47 | func (p *pv) GetPvDetail(ctx *gin.Context) { 48 | params := new(struct { 49 | PvName string `form:"pv_name"` 50 | }) 51 | if err := ctx.Bind(params); err != nil { 52 | logger.Error("Bind请求参数失败, " + err.Error()) 53 | ctx.JSON(http.StatusInternalServerError, gin.H{ 54 | "msg": err.Error(), 55 | "data": nil, 56 | }) 57 | return 58 | } 59 | 60 | data, err := service.Pv.GetPvDetail(params.PvName) 61 | if err != nil { 62 | ctx.JSON(http.StatusInternalServerError, gin.H{ 63 | "msg": err.Error(), 64 | "data": nil, 65 | }) 66 | return 67 | } 68 | 69 | ctx.JSON(http.StatusOK, gin.H{ 70 | "msg": "获取Pv详情成功", 71 | "data": data, 72 | }) 73 | } 74 | 75 | //删除pv 76 | func (p *pv) DeletePv(ctx *gin.Context) { 77 | params := new(struct { 78 | PvName string `json:"pv_name"` 79 | }) 80 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 81 | if err := ctx.ShouldBindJSON(params); err != nil { 82 | logger.Error("Bind请求参数失败, " + err.Error()) 83 | ctx.JSON(http.StatusInternalServerError, gin.H{ 84 | "msg": err.Error(), 85 | "data": nil, 86 | }) 87 | return 88 | } 89 | 90 | err := service.Pv.DeletePv(params.PvName) 91 | if err != nil { 92 | ctx.JSON(http.StatusInternalServerError, gin.H{ 93 | "msg": err.Error(), 94 | "data": nil, 95 | }) 96 | return 97 | } 98 | ctx.JSON(http.StatusOK, gin.H{ 99 | "msg": "删除Pv成功", 100 | "data": nil, 101 | }) 102 | } 103 | -------------------------------------------------------------------------------- /controller/pvc.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Pvc pvc 12 | 13 | type pvc struct{} 14 | 15 | // 获取pvc列表,支持过滤、排序、分页 16 | func (p *pvc) GetPvcs(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Namespace string `form:"namespace"` 20 | Page int `form:"page"` 21 | Limit int `form:"limit"` 22 | }) 23 | if err := ctx.Bind(params); err != nil { 24 | logger.Error("Bind请求参数失败, " + err.Error()) 25 | ctx.JSON(http.StatusInternalServerError, gin.H{ 26 | "msg": err.Error(), 27 | "data": nil, 28 | }) 29 | return 30 | } 31 | 32 | data, err := service.Pvc.GetPvcs(params.FilterName, params.Namespace, params.Limit, params.Page) 33 | if err != nil { 34 | ctx.JSON(http.StatusInternalServerError, gin.H{ 35 | "msg": err.Error(), 36 | "data": nil, 37 | }) 38 | return 39 | } 40 | 41 | ctx.JSON(http.StatusOK, gin.H{ 42 | "msg": "获取Pvc列表成功", 43 | "data": data, 44 | }) 45 | } 46 | 47 | // 获取pvc详情 48 | func (p *pvc) GetPvcDetail(ctx *gin.Context) { 49 | params := new(struct { 50 | PvcName string `form:"pvc_name"` 51 | Namespace string `form:"namespace"` 52 | }) 53 | if err := ctx.Bind(params); err != nil { 54 | logger.Error("Bind请求参数失败, " + err.Error()) 55 | ctx.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": err.Error(), 57 | "data": nil, 58 | }) 59 | return 60 | } 61 | 62 | data, err := service.Pvc.GetPvcDetail(params.PvcName, params.Namespace) 63 | if err != nil { 64 | ctx.JSON(http.StatusInternalServerError, gin.H{ 65 | "msg": err.Error(), 66 | "data": nil, 67 | }) 68 | return 69 | } 70 | 71 | ctx.JSON(http.StatusOK, gin.H{ 72 | "msg": "获取Pvc详情成功", 73 | "data": data, 74 | }) 75 | } 76 | 77 | // 删除pvc 78 | func (p *pvc) DeletePvc(ctx *gin.Context) { 79 | params := new(struct { 80 | PvcName string `json:"pvc_name"` 81 | Namespace string `json:"namespace"` 82 | }) 83 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 84 | if err := ctx.ShouldBindJSON(params); err != nil { 85 | logger.Error("Bind请求参数失败, " + err.Error()) 86 | ctx.JSON(http.StatusInternalServerError, gin.H{ 87 | "msg": err.Error(), 88 | "data": nil, 89 | }) 90 | return 91 | } 92 | 93 | err := service.Pvc.DeletePvc(params.PvcName, params.Namespace) 94 | if err != nil { 95 | ctx.JSON(http.StatusInternalServerError, gin.H{ 96 | "msg": err.Error(), 97 | "data": nil, 98 | }) 99 | return 100 | } 101 | ctx.JSON(http.StatusOK, gin.H{ 102 | "msg": "删除Pvc成功", 103 | "data": nil, 104 | }) 105 | } 106 | 107 | // 更新pvc 108 | func (p *pvc) UpdatePvc(ctx *gin.Context) { 109 | params := new(struct { 110 | Namespace string `json:"namespace"` 111 | Content string `json:"content"` 112 | }) 113 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 114 | if err := ctx.ShouldBindJSON(params); err != nil { 115 | logger.Error("Bind请求参数失败, " + err.Error()) 116 | ctx.JSON(http.StatusInternalServerError, gin.H{ 117 | "msg": err.Error(), 118 | "data": nil, 119 | }) 120 | return 121 | } 122 | 123 | err := service.Pvc.UpdatePvc(params.Namespace, params.Content) 124 | if err != nil { 125 | ctx.JSON(http.StatusInternalServerError, gin.H{ 126 | "msg": err.Error(), 127 | "data": nil, 128 | }) 129 | return 130 | } 131 | ctx.JSON(http.StatusOK, gin.H{ 132 | "msg": "更新Pvc成功", 133 | "data": nil, 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /controller/router.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | ) 6 | 7 | // // 初始化router类型对象,首字母大写,用于跨包调用 8 | // var Router router 9 | 10 | // // 声明一个router的结构体 11 | // type router struct{} 12 | 13 | // func (r *router) InitApiRouter(router *gin.Engine) { 14 | // router.GET("/", Index) 15 | // } 16 | 17 | // func Index(ctx *gin.Context) { 18 | // ctx.JSON(200, gin.H{ 19 | // "code": 200, 20 | // "msg": "In index", 21 | // }) 22 | // } 23 | 24 | // 实例化router结构体,可使用该对象点出首字母大写的方法(包外调用) 25 | var Router router 26 | 27 | // 创建router的结构体 28 | type router struct{} 29 | 30 | // // 初始化路由规则,创建测试api接口 31 | // func (r *router) InitApiRouter(router *gin.Engine) { 32 | // router.GET("/testapi", func(ctx *gin.Context) { 33 | // ctx.JSON(http.StatusOK, gin.H{ 34 | // "msg": "testapi success!", 35 | // "data": nil, 36 | // }) 37 | // }) 38 | // } 39 | // 初始化路由规则 40 | // func (r *router) InitApiRouter(router *gin.Engine) { 41 | // router. 42 | // GET("/api/k8s/pods", Pod.GetPods). 43 | // GET("/api/k8s/pod/detail", Pod.GetPodDetail). 44 | // POST("/api/k8s/pods", Pod.DeletePod). 45 | func (r *router) InitApiRouter(router *gin.Engine) { 46 | router. 47 | // login 48 | // POST("/api/login", ). 49 | // Pods 50 | GET("/api/k8s/pods", Pod.GetPods). 51 | GET("/api/k8s/pod/detail", Pod.GetPodDetail). 52 | DELETE("/api/k8s/pod/del", Pod.DeletePod). 53 | PUT("/api/k8s/pod/update", Pod.UpdatePod). 54 | GET("/api/k8s/pod/container", Pod.GetPodContainer). 55 | GET("/api/k8s/pod/log", Pod.GetPodLog). 56 | GET("/api/k8s/pod/numnp", Pod.GetPodNumPerNp). 57 | //deployment操作 58 | GET("/api/k8s/deployments", Deployment.GetDeployments). 59 | GET("/api/k8s/deployment/detail", Deployment.GetDeploymentDetail). 60 | PUT("/api/k8s/deployment/scale", Deployment.ScaleDeployment). 61 | DELETE("/api/k8s/deployment/del", Deployment.DeleteDeployment). 62 | PUT("/api/k8s/deployment/restart", Deployment.RestartDeployment). 63 | PUT("/api/k8s/deployment/update", Deployment.UpdateDeployment). 64 | GET("/api/k8s/deployment/numnp", Deployment.GetDeployNumPerNp). 65 | POST("/api/k8s/deployment/create", Deployment.CreateDeployment). 66 | //daemonset操作 67 | GET("/api/k8s/daemonsets", DaemonSet.GetDaemonSets). 68 | GET("/api/k8s/daemonset/detail", DaemonSet.GetDaemonSetDetail). 69 | DELETE("/api/k8s/daemonset/del", DaemonSet.DeleteDaemonSet). 70 | PUT("/api/k8s/daemonset/update", DaemonSet.UpdateDaemonSet). 71 | // workflows 72 | GET("/api/k8s/workflows", Workflow.GetList). 73 | GET("/api/k8s/workflow/detail", Workflow.GetById). 74 | POST("/api/k8s/workflow/create", Workflow.Create). 75 | DELETE("/api/k8s/workflow/del", Workflow.DelById). 76 | //statefulset操作 77 | GET("/api/k8s/statefulsets", StatefulSet.GetStatefulSets). 78 | GET("/api/k8s/statefulset/detail", StatefulSet.GetStatefulSetDetail). 79 | DELETE("/api/k8s/statefulset/del", StatefulSet.DeleteStatefulSet). 80 | PUT("/api/k8s/statefulset/update", StatefulSet.UpdateStatefulSet). 81 | //node操作 82 | GET("/api/k8s/nodes", Node.GetNodes). 83 | GET("/api/k8s/node/detail", Node.GetNodeDetail). 84 | //namespace操作 85 | GET("/api/k8s/namespaces", Namespace.GetNamespaces). 86 | GET("/api/k8s/namespace/detail", Namespace.GetNamespaceDetail). 87 | DELETE("/api/k8s/namespace/del", Namespace.DeleteNamespace). 88 | //pv操作 89 | GET("/api/k8s/pvs", Pv.GetPvs). 90 | GET("/api/k8s/pv/detail", Pv.GetPvDetail). 91 | //service操作 92 | GET("/api/k8s/services", Servicev1.GetServices). 93 | GET("/api/k8s/service/detail", Servicev1.GetServiceDetail). 94 | DELETE("/api/k8s/service/del", Servicev1.DeleteService). 95 | PUT("/api/k8s/service/update", Servicev1.UpdateService). 96 | POST("/api/k8s/service/create", Servicev1.CreateService). 97 | //ingress操作 98 | GET("/api/k8s/ingresses", Ingress.GetIngresses). 99 | GET("/api/k8s/ingress/detail", Ingress.GetIngressDetail). 100 | DELETE("/api/k8s/ingress/del", Ingress.DeleteIngress). 101 | PUT("/api/k8s/ingress/update", Ingress.UpdateIngress). 102 | POST("/api/k8s/ingress/create", Ingress.CreateIngress). 103 | // Configmaps 104 | GET("/api/k8s/configmaps", ConfigMap.GetConfigMaps). 105 | GET("/api/k8s/configmap/detail", ConfigMap.GetConfigMapDetail). 106 | DELETE("/api/k8s/configmap/del", ConfigMap.DeleteConfigMap). 107 | PUT("/api/k8s/configmap/update", ConfigMap.UpdateConfigMap). 108 | // secret 109 | GET("/api/k8s/secrets", Secret.GetSecrets). 110 | GET("/api/k8s/secret/detail", Secret.GetSecretDetail). 111 | DELETE("/api/k8s/secret/del", Secret.DeleteSecret). 112 | PUT("/api/k8s/secret/update", Secret.UpdateSecret). 113 | //pvc操作 114 | GET("/api/k8s/pvcs", Pvc.GetPvcs). 115 | GET("/api/k8s/pvc/detail", Pvc.GetPvcDetail). 116 | DELETE("/api/k8s/pvc/del", Pvc.DeletePvc). 117 | PUT("/api/k8s/pvc/update", Pvc.UpdatePvc) 118 | 119 | } 120 | -------------------------------------------------------------------------------- /controller/secret.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Secret secret 12 | 13 | type secret struct{} 14 | 15 | // 获取secret列表,支持过滤、排序、分页 16 | func (s *secret) GetSecrets(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Namespace string `form:"namespace"` 20 | Page int `form:"page"` 21 | Limit int `form:"limit"` 22 | }) 23 | if err := ctx.Bind(params); err != nil { 24 | logger.Error("Bind请求参数失败, " + err.Error()) 25 | ctx.JSON(http.StatusInternalServerError, gin.H{ 26 | "msg": err.Error(), 27 | "data": nil, 28 | }) 29 | return 30 | } 31 | 32 | data, err := service.Secret.GetSecrets(params.FilterName, params.Namespace, params.Limit, params.Page) 33 | if err != nil { 34 | ctx.JSON(http.StatusInternalServerError, gin.H{ 35 | "msg": err.Error(), 36 | "data": nil, 37 | }) 38 | return 39 | } 40 | 41 | ctx.JSON(http.StatusOK, gin.H{ 42 | "msg": "获取Secret列表成功", 43 | "data": data, 44 | }) 45 | } 46 | 47 | // 获取secret详情 48 | func (s *secret) GetSecretDetail(ctx *gin.Context) { 49 | params := new(struct { 50 | SecretName string `form:"secret_name"` 51 | Namespace string `form:"namespace"` 52 | }) 53 | if err := ctx.Bind(params); err != nil { 54 | logger.Error("Bind请求参数失败, " + err.Error()) 55 | ctx.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": err.Error(), 57 | "data": nil, 58 | }) 59 | return 60 | } 61 | 62 | data, err := service.Secret.GetSecretDetail(params.SecretName, params.Namespace) 63 | if err != nil { 64 | ctx.JSON(http.StatusInternalServerError, gin.H{ 65 | "msg": err.Error(), 66 | "data": nil, 67 | }) 68 | return 69 | } 70 | 71 | ctx.JSON(http.StatusOK, gin.H{ 72 | "msg": "获取Secret详情成功", 73 | "data": data, 74 | }) 75 | } 76 | 77 | // 删除secret 78 | func (s *secret) DeleteSecret(ctx *gin.Context) { 79 | params := new(struct { 80 | SecretName string `json:"secret_name"` 81 | Namespace string `json:"namespace"` 82 | }) 83 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 84 | if err := ctx.ShouldBindJSON(params); err != nil { 85 | logger.Error("Bind请求参数失败, " + err.Error()) 86 | ctx.JSON(http.StatusInternalServerError, gin.H{ 87 | "msg": err.Error(), 88 | "data": nil, 89 | }) 90 | return 91 | } 92 | 93 | err := service.Secret.DeleteSecret(params.SecretName, params.Namespace) 94 | if err != nil { 95 | ctx.JSON(http.StatusInternalServerError, gin.H{ 96 | "msg": err.Error(), 97 | "data": nil, 98 | }) 99 | return 100 | } 101 | ctx.JSON(http.StatusOK, gin.H{ 102 | "msg": "删除Secret成功", 103 | "data": nil, 104 | }) 105 | } 106 | 107 | // 更新secret 108 | func (s *secret) UpdateSecret(ctx *gin.Context) { 109 | params := new(struct { 110 | Namespace string `json:"namespace"` 111 | Content string `json:"content"` 112 | }) 113 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 114 | if err := ctx.ShouldBindJSON(params); err != nil { 115 | logger.Error("Bind请求参数失败, " + err.Error()) 116 | ctx.JSON(http.StatusInternalServerError, gin.H{ 117 | "msg": err.Error(), 118 | "data": nil, 119 | }) 120 | return 121 | } 122 | 123 | err := service.Secret.UpdateSecret(params.Namespace, params.Content) 124 | if err != nil { 125 | ctx.JSON(http.StatusInternalServerError, gin.H{ 126 | "msg": err.Error(), 127 | "data": nil, 128 | }) 129 | return 130 | } 131 | ctx.JSON(http.StatusOK, gin.H{ 132 | "msg": "更新Secret成功", 133 | "data": nil, 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /controller/service.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Servicev1 servicev1 12 | 13 | type servicev1 struct{} 14 | 15 | //获取service列表,支持过滤、排序、分页 16 | func (s *servicev1) GetServices(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Namespace string `form:"namespace"` 20 | Page int `form:"page"` 21 | Limit int `form:"limit"` 22 | }) 23 | if err := ctx.Bind(params); err != nil { 24 | logger.Error("Bind请求参数失败, " + err.Error()) 25 | ctx.JSON(http.StatusInternalServerError, gin.H{ 26 | "msg": err.Error(), 27 | "data": nil, 28 | }) 29 | return 30 | } 31 | 32 | data, err := service.Servicev1.GetServices(params.FilterName, params.Namespace, params.Limit, params.Page) 33 | if err != nil { 34 | ctx.JSON(http.StatusInternalServerError, gin.H{ 35 | "msg": err.Error(), 36 | "data": nil, 37 | }) 38 | return 39 | } 40 | 41 | ctx.JSON(http.StatusOK, gin.H{ 42 | "msg": "获取Servicet列表成功", 43 | "data": data, 44 | }) 45 | } 46 | 47 | //获取service详情 48 | func (s *servicev1) GetServiceDetail(ctx *gin.Context) { 49 | params := new(struct { 50 | ServiceName string `form:"service_name"` 51 | Namespace string `form:"namespace"` 52 | }) 53 | if err := ctx.Bind(params); err != nil { 54 | logger.Error("Bind请求参数失败, " + err.Error()) 55 | ctx.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": err.Error(), 57 | "data": nil, 58 | }) 59 | return 60 | } 61 | 62 | data, err := service.Servicev1.GetServicetDetail(params.ServiceName, params.Namespace) 63 | if err != nil { 64 | ctx.JSON(http.StatusInternalServerError, gin.H{ 65 | "msg": err.Error(), 66 | "data": nil, 67 | }) 68 | return 69 | } 70 | 71 | ctx.JSON(http.StatusOK, gin.H{ 72 | "msg": "获取Service详情成功", 73 | "data": data, 74 | }) 75 | } 76 | 77 | //创建service 78 | func (s *servicev1) CreateService(ctx *gin.Context) { 79 | var ( 80 | serviceCreate = new(service.ServiceCreate) 81 | err error 82 | ) 83 | 84 | if err = ctx.ShouldBindJSON(serviceCreate); err != nil { 85 | logger.Error("Bind请求参数失败, " + err.Error()) 86 | ctx.JSON(http.StatusInternalServerError, gin.H{ 87 | "msg": err.Error(), 88 | "data": nil, 89 | }) 90 | return 91 | } 92 | 93 | if err = service.Servicev1.CreateService(serviceCreate); err != nil { 94 | ctx.JSON(http.StatusInternalServerError, gin.H{ 95 | "msg": err.Error(), 96 | "data": nil, 97 | }) 98 | } 99 | 100 | ctx.JSON(http.StatusOK, gin.H{ 101 | "msg": "创建Service成功", 102 | "data": nil, 103 | }) 104 | } 105 | 106 | //删除service 107 | func (s *servicev1) DeleteService(ctx *gin.Context) { 108 | params := new(struct { 109 | ServiceName string `json:"service_name"` 110 | Namespace string `json:"namespace"` 111 | }) 112 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 113 | if err := ctx.ShouldBindJSON(params); err != nil { 114 | logger.Error("Bind请求参数失败, " + err.Error()) 115 | ctx.JSON(http.StatusInternalServerError, gin.H{ 116 | "msg": err.Error(), 117 | "data": nil, 118 | }) 119 | return 120 | } 121 | 122 | err := service.Servicev1.DeleteService(params.ServiceName, params.Namespace) 123 | if err != nil { 124 | ctx.JSON(http.StatusInternalServerError, gin.H{ 125 | "msg": err.Error(), 126 | "data": nil, 127 | }) 128 | return 129 | } 130 | ctx.JSON(http.StatusOK, gin.H{ 131 | "msg": "删除Service成功", 132 | "data": nil, 133 | }) 134 | } 135 | 136 | //更新service 137 | func (s *servicev1) UpdateService(ctx *gin.Context) { 138 | params := new(struct { 139 | Namespace string `json:"namespace"` 140 | Content string `json:"content"` 141 | }) 142 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 143 | if err := ctx.ShouldBindJSON(params); err != nil { 144 | logger.Error("Bind请求参数失败, " + err.Error()) 145 | ctx.JSON(http.StatusInternalServerError, gin.H{ 146 | "msg": err.Error(), 147 | "data": nil, 148 | }) 149 | return 150 | } 151 | 152 | err := service.Servicev1.UpdateService(params.Namespace, params.Content) 153 | if err != nil { 154 | ctx.JSON(http.StatusInternalServerError, gin.H{ 155 | "msg": err.Error(), 156 | "data": nil, 157 | }) 158 | return 159 | } 160 | ctx.JSON(http.StatusOK, gin.H{ 161 | "msg": "更新Service成功", 162 | "data": nil, 163 | }) 164 | } 165 | -------------------------------------------------------------------------------- /controller/statefulset.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var StatefulSet statefulSet 12 | 13 | type statefulSet struct{} 14 | 15 | //获取statefulset列表,支持过滤、排序、分页 16 | func (s *statefulSet) GetStatefulSets(ctx *gin.Context) { 17 | params := new(struct { 18 | FilterName string `form:"filter_name"` 19 | Namespace string `form:"namespace"` 20 | Page int `form:"page"` 21 | Limit int `form:"limit"` 22 | }) 23 | if err := ctx.Bind(params); err != nil { 24 | logger.Error("Bind请求参数失败, " + err.Error()) 25 | ctx.JSON(http.StatusInternalServerError, gin.H{ 26 | "msg": err.Error(), 27 | "data": nil, 28 | }) 29 | return 30 | } 31 | 32 | data, err := service.StatefulSet.GetStatefulSets(params.FilterName, params.Namespace, params.Limit, params.Page) 33 | if err != nil { 34 | ctx.JSON(http.StatusInternalServerError, gin.H{ 35 | "msg": err.Error(), 36 | "data": nil, 37 | }) 38 | return 39 | } 40 | 41 | ctx.JSON(http.StatusOK, gin.H{ 42 | "msg": "获取StatefulSet列表成功", 43 | "data": data, 44 | }) 45 | } 46 | 47 | //获取statefulset详情 48 | func (s *statefulSet) GetStatefulSetDetail(ctx *gin.Context) { 49 | params := new(struct { 50 | StatefulSetName string `form:"statefulset_name"` 51 | Namespace string `form:"namespace"` 52 | }) 53 | if err := ctx.Bind(params); err != nil { 54 | logger.Error("Bind请求参数失败, " + err.Error()) 55 | ctx.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": err.Error(), 57 | "data": nil, 58 | }) 59 | return 60 | } 61 | 62 | data, err := service.StatefulSet.GetStatefulSetDetail(params.StatefulSetName, params.Namespace) 63 | if err != nil { 64 | ctx.JSON(http.StatusInternalServerError, gin.H{ 65 | "msg": err.Error(), 66 | "data": nil, 67 | }) 68 | return 69 | } 70 | 71 | ctx.JSON(http.StatusOK, gin.H{ 72 | "msg": "获取StatefulSet详情成功", 73 | "data": data, 74 | }) 75 | } 76 | 77 | //删除statefulset 78 | func (s *statefulSet) DeleteStatefulSet(ctx *gin.Context) { 79 | params := new(struct { 80 | StatefulSetName string `json:"statefulset_name"` 81 | Namespace string `json:"namespace"` 82 | }) 83 | //DELETE请求,绑定参数方法改为ctx.ShouldBindJSON 84 | if err := ctx.ShouldBindJSON(params); err != nil { 85 | logger.Error("Bind请求参数失败, " + err.Error()) 86 | ctx.JSON(http.StatusInternalServerError, gin.H{ 87 | "msg": err.Error(), 88 | "data": nil, 89 | }) 90 | return 91 | } 92 | 93 | err := service.StatefulSet.DeleteStatefulSet(params.StatefulSetName, params.Namespace) 94 | if err != nil { 95 | ctx.JSON(http.StatusInternalServerError, gin.H{ 96 | "msg": err.Error(), 97 | "data": nil, 98 | }) 99 | return 100 | } 101 | ctx.JSON(http.StatusOK, gin.H{ 102 | "msg": "删除StatefulSet成功", 103 | "data": nil, 104 | }) 105 | } 106 | 107 | //更新statefulSet 108 | func (s *statefulSet) UpdateStatefulSet(ctx *gin.Context) { 109 | params := new(struct { 110 | Namespace string `json:"namespace"` 111 | Content string `json:"content"` 112 | }) 113 | //PUT请求,绑定参数方法改为ctx.ShouldBindJSON 114 | if err := ctx.ShouldBindJSON(params); err != nil { 115 | logger.Error("Bind请求参数失败, " + err.Error()) 116 | ctx.JSON(http.StatusInternalServerError, gin.H{ 117 | "msg": err.Error(), 118 | "data": nil, 119 | }) 120 | return 121 | } 122 | 123 | err := service.StatefulSet.UpdateStatefulSet(params.Namespace, params.Content) 124 | if err != nil { 125 | ctx.JSON(http.StatusInternalServerError, gin.H{ 126 | "msg": err.Error(), 127 | "data": nil, 128 | }) 129 | return 130 | } 131 | ctx.JSON(http.StatusOK, gin.H{ 132 | "msg": "更新StatefulSet成功", 133 | "data": nil, 134 | }) 135 | } 136 | -------------------------------------------------------------------------------- /controller/workflow.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "k8s-platform/service" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Workflow workflow 12 | 13 | type workflow struct{} 14 | 15 | // 获取列表分页查询 16 | func (w *workflow) GetList(ctx *gin.Context) { 17 | params := new(struct { 18 | Name string `form:"name"` 19 | Namespace string `form:"namespace"` 20 | Page int `form:"page"` 21 | Limit int `form:"limit"` 22 | }) 23 | if err := ctx.Bind(params); err != nil { 24 | logger.Error("Bind请求参数失败, " + err.Error()) 25 | ctx.JSON(http.StatusInternalServerError, gin.H{ 26 | "msg": err.Error(), 27 | "data": nil, 28 | }) 29 | return 30 | } 31 | 32 | data, err := service.Workflow.GetList(params.Name, params.Namespace, params.Limit, params.Page) 33 | if err != nil { 34 | logger.Error("获取Workflow列表失败, " + err.Error()) 35 | ctx.JSON(http.StatusInternalServerError, gin.H{ 36 | "msg": err.Error(), 37 | "data": nil, 38 | }) 39 | return 40 | } 41 | 42 | ctx.JSON(http.StatusOK, gin.H{ 43 | "msg": "获取Workflow列表成功", 44 | "data": data, 45 | }) 46 | } 47 | 48 | // 查询workflow单条数据 49 | func (w *workflow) GetById(ctx *gin.Context) { 50 | params := new(struct { 51 | ID int `form:"id"` 52 | }) 53 | if err := ctx.Bind(params); err != nil { 54 | logger.Error("Bind请求参数失败, " + err.Error()) 55 | ctx.JSON(http.StatusInternalServerError, gin.H{ 56 | "msg": err.Error(), 57 | "data": nil, 58 | }) 59 | return 60 | } 61 | 62 | data, err := service.Workflow.GetById(params.ID) 63 | if err != nil { 64 | logger.Error("查询Workflow单条数据失败, " + err.Error()) 65 | ctx.JSON(http.StatusInternalServerError, gin.H{ 66 | "msg": err.Error(), 67 | "data": nil, 68 | }) 69 | return 70 | } 71 | 72 | ctx.JSON(http.StatusOK, gin.H{ 73 | "msg": "查询Workflow单条数据成功", 74 | "data": data, 75 | }) 76 | } 77 | 78 | // 创建workflow 79 | func (w *workflow) Create(ctx *gin.Context) { 80 | var ( 81 | wc = &service.WorkflowCreate{} 82 | err error 83 | ) 84 | 85 | if err = ctx.ShouldBindJSON(wc); err != nil { 86 | logger.Error("Bind请求参数dc失败, " + err.Error()) 87 | ctx.JSON(http.StatusInternalServerError, gin.H{ 88 | "msg": err.Error(), 89 | "data": nil, 90 | }) 91 | return 92 | } 93 | if err = service.Workflow.CreateWorkFlow(wc); err != nil { 94 | logger.Error("创建Workflow失败, " + err.Error()) 95 | ctx.JSON(http.StatusInternalServerError, gin.H{ 96 | "msg": err.Error(), 97 | "data": nil, 98 | }) 99 | return 100 | } 101 | 102 | ctx.JSON(http.StatusOK, gin.H{ 103 | "msg": "创建Workflow成功", 104 | "data": nil, 105 | }) 106 | 107 | } 108 | 109 | // 删除workflow 110 | func (w *workflow) DelById(ctx *gin.Context) { 111 | params := new(struct { 112 | ID int `json:"id"` 113 | }) 114 | if err := ctx.ShouldBindJSON(params); err != nil { 115 | logger.Error("Bind请求参数失败, " + err.Error()) 116 | ctx.JSON(http.StatusInternalServerError, gin.H{ 117 | "msg": err.Error(), 118 | "data": nil, 119 | }) 120 | return 121 | } 122 | 123 | if err := service.Workflow.DelById(params.ID); err != nil { 124 | logger.Error("删除Workflow失败, " + err.Error()) 125 | ctx.JSON(http.StatusInternalServerError, gin.H{ 126 | "msg": err.Error(), 127 | "data": nil, 128 | }) 129 | return 130 | } 131 | 132 | ctx.JSON(http.StatusOK, gin.H{ 133 | "msg": "删除Workflow成功", 134 | "data": nil, 135 | }) 136 | } 137 | -------------------------------------------------------------------------------- /dao/workflow.go: -------------------------------------------------------------------------------- 1 | package dao 2 | 3 | import ( 4 | "errors" 5 | "k8s-platform/db" 6 | "k8s-platform/model" 7 | 8 | "github.com/wonderivan/logger" 9 | ) 10 | 11 | var Workflow workflow 12 | 13 | type workflow struct{} 14 | 15 | //定义列表的返回内容,Items是workflow元素列表,Total为workflow元素数量 16 | type WorkflowResp struct { 17 | Items []*model.Workflow `json:"items"` 18 | Total int `json:"total"` 19 | } 20 | 21 | // 获取workflow列表 22 | func (w *workflow) GetWorkflows(filterName, namespace string, limit, page int) (data *WorkflowResp, err error) { 23 | //定义分页的起始位置 24 | startSet := (page - 1) * limit 25 | //定义数据库查询返回的内容 26 | var ( 27 | workflowList []*model.Workflow 28 | total int 29 | ) 30 | //数据库查询,Limit方法用于限制条数,Offset方法用于设置起始位置 31 | tx := db.GORM. 32 | Model(&model.Workflow{}). 33 | Where("name like ?", "%"+filterName+"%"). 34 | Count(&total). 35 | Limit(limit). 36 | Offset(startSet). 37 | Order("id desc"). 38 | Find(&workflowList) 39 | if tx.Error != nil && tx.Error.Error() != "record not found" { 40 | logger.Error("获取Workflow列表失败, " + tx.Error.Error()) 41 | return nil, errors.New("获取Workflow列表失败, " + tx.Error.Error()) 42 | } 43 | return &WorkflowResp{ 44 | Items: workflowList, 45 | Total: total, 46 | }, nil 47 | } 48 | 49 | // 获取详情 50 | func (w *workflow) GetById(id int) (workflow *model.Workflow, err error) { 51 | workflow = &model.Workflow{} 52 | tx := db.GORM.Where("id = ?", id).First(&workflow) 53 | if tx.Error != nil && tx.Error.Error() != "record not found" { 54 | logger.Error("获取Workflow详情失败, " + tx.Error.Error()) 55 | return nil, errors.New("获取Workflow详情失败, " + tx.Error.Error()) 56 | } 57 | return workflow, nil 58 | } 59 | 60 | // 创建 61 | func (w *workflow) Add(workflow *model.Workflow) (err error) { 62 | tx := db.GORM.Create(&workflow) 63 | if tx.Error != nil && tx.Error.Error() != "record not found" { 64 | logger.Error("创建Workflow失败, " + tx.Error.Error()) 65 | return errors.New("创建Workflow失败, " + tx.Error.Error()) 66 | } 67 | return nil 68 | } 69 | 70 | // 删除 71 | func (w *workflow) DelById(id int) (err error) { 72 | tx := db.GORM.Where("id = ?", id).Delete(&model.Workflow{}) 73 | if tx.Error != nil && tx.Error.Error() != "record not found" { 74 | logger.Error("获取Workflow详情失败, " + tx.Error.Error()) 75 | return errors.New("获取Workflow详情失败, " + tx.Error.Error()) 76 | } 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /db/init.go: -------------------------------------------------------------------------------- 1 | package db 2 | 3 | import ( 4 | "fmt" 5 | "k8s-platform/config" 6 | "time" 7 | 8 | "github.com/wonderivan/logger" 9 | 10 | "github.com/jinzhu/gorm" 11 | _ "github.com/jinzhu/gorm/dialects/mysql" 12 | ) 13 | 14 | var ( 15 | isInit bool 16 | GORM *gorm.DB 17 | err error 18 | ) 19 | 20 | // DB的初始化函数,与数据库建立连接 21 | func Init() { 22 | // 判断是否已经初始化 23 | if isInit { 24 | return 25 | } 26 | // 组装连接配置 27 | dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local", 28 | config.DbUser, 29 | config.DbPass, 30 | config.DbHost, 31 | config.DbPort, 32 | config.DbName) 33 | GORM, err := gorm.Open(config.DbType, dsn) 34 | if err != nil { 35 | panic("数据库连接失败," + err.Error()) 36 | } 37 | // 打印sql语句 38 | GORM.LogMode(config.LogMode) 39 | // 开启连接池 40 | GORM.DB().SetMaxIdleConns(config.MaxIdleConns) 41 | GORM.DB().SetMaxOpenConns(config.MaxOpenConns) 42 | GORM.DB().SetConnMaxLifetime(time.Duration(config.MaxLifeTime)) 43 | isInit = true 44 | logger.Info("数据库初始化成功") 45 | } 46 | 47 | // 关闭数据库连接 48 | func Close() error { 49 | return GORM.Close() 50 | } 51 | -------------------------------------------------------------------------------- /db/workflow.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE `workflow` ( 2 | `id` int NOT NULL AUTO_INCREMENT, 3 | `name` varchar(32) COLLATE utf8mb4_general_ci NOT NULL, 4 | `namespace` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL, 5 | `replicas` int DEFAULT NULL, 6 | `deployment` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL, 7 | `service` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL, 8 | `ingress` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL, 9 | `type` varchar(32) COLLATE utf8mb4_general_ci DEFAULT NULL, 10 | `created_at` datetime DEFAULT NULL, 11 | `updated_at` datetime DEFAULT NULL, 12 | `deleted_at` datetime DEFAULT NULL, 13 | PRIMARY KEY (`id`) USING BTREE, 14 | UNIQUE KEY `name` (`name`) 15 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; 16 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module k8s-platform 2 | 3 | go 1.18 4 | 5 | require github.com/gin-gonic/gin v1.9.0 6 | 7 | require ( 8 | github.com/emicklei/go-restful v2.9.5+incompatible // indirect 9 | github.com/go-sql-driver/mysql v1.5.0 // indirect 10 | github.com/imdario/mergo v0.3.5 // indirect 11 | github.com/jinzhu/inflection v1.0.0 // indirect 12 | github.com/jinzhu/now v1.1.5 // indirect 13 | github.com/moby/spdystream v0.2.0 // indirect 14 | k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 // indirect 15 | ) 16 | 17 | require ( 18 | github.com/davecgh/go-spew v1.1.1 // indirect 19 | github.com/go-logr/logr v1.2.3 // indirect 20 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 21 | github.com/go-openapi/jsonreference v0.20.1 // indirect 22 | github.com/go-openapi/swag v0.22.3 // indirect 23 | github.com/gogo/protobuf v1.3.2 // indirect 24 | github.com/golang/protobuf v1.5.3 // indirect 25 | github.com/google/gnostic v0.5.7-v3refs // indirect 26 | github.com/google/go-cmp v0.5.9 // indirect 27 | github.com/google/gofuzz v1.1.0 // indirect 28 | github.com/josharian/intern v1.0.0 // indirect 29 | github.com/mailru/easyjson v0.7.7 // indirect 30 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 31 | github.com/spf13/pflag v1.0.5 // indirect 32 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect 33 | golang.org/x/term v0.6.0 // indirect 34 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect 35 | google.golang.org/appengine v1.6.7 // indirect 36 | gopkg.in/inf.v0 v0.9.1 // indirect 37 | gopkg.in/yaml.v2 v2.4.0 // indirect 38 | k8s.io/apimachinery v0.24.2 39 | k8s.io/klog/v2 v2.90.1 // indirect 40 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect 41 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 42 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 43 | sigs.k8s.io/yaml v1.3.0 // indirect 44 | ) 45 | 46 | require ( 47 | github.com/bytedance/sonic v1.8.0 // indirect 48 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 49 | github.com/dgrijalva/jwt-go v3.2.0+incompatible 50 | github.com/form3tech-oss/jwt-go v3.2.5+incompatible 51 | github.com/gin-contrib/sse v0.1.0 // indirect 52 | github.com/go-playground/locales v0.14.1 // indirect 53 | github.com/go-playground/universal-translator v0.18.1 // indirect 54 | github.com/go-playground/validator/v10 v10.11.2 // indirect 55 | github.com/goccy/go-json v0.10.0 // indirect 56 | github.com/gorilla/websocket v1.5.0 57 | github.com/jinzhu/gorm v1.9.16 58 | github.com/json-iterator/go v1.1.12 // indirect 59 | github.com/klauspost/cpuid/v2 v2.0.9 // indirect 60 | github.com/leodido/go-urn v1.2.1 // indirect 61 | github.com/mattn/go-isatty v0.0.17 // indirect 62 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 63 | github.com/modern-go/reflect2 v1.0.2 // indirect 64 | github.com/pelletier/go-toml/v2 v2.0.6 // indirect 65 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 66 | github.com/ugorji/go/codec v1.2.9 // indirect 67 | github.com/wonderivan/logger v1.0.0 68 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect 69 | golang.org/x/crypto v0.5.0 // indirect 70 | golang.org/x/net v0.8.0 // indirect 71 | golang.org/x/sys v0.6.0 // indirect 72 | golang.org/x/text v0.8.0 // indirect 73 | google.golang.org/protobuf v1.28.1 // indirect 74 | gopkg.in/yaml.v3 v3.0.1 // indirect 75 | gorm.io/gorm v1.25.0 76 | k8s.io/api v0.24.2 77 | k8s.io/client-go v0.24.2 78 | ) 79 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= 17 | cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= 18 | cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= 19 | cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= 20 | cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= 21 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 22 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 23 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 24 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 25 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 26 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 27 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 28 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 29 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 30 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 31 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 32 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 33 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 34 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 35 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 36 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 37 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 38 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 39 | github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 40 | github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= 41 | github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= 42 | github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= 43 | github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= 44 | github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= 45 | github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= 46 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 47 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 48 | github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= 49 | github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= 50 | github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= 51 | github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= 52 | github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 53 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 54 | github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= 55 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 56 | github.com/bytedance/sonic v1.8.0 h1:ea0Xadu+sHlu7x5O3gKhRpQ1IKiMrSiHttPF0ybECuA= 57 | github.com/bytedance/sonic v1.8.0/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 58 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 59 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 60 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 61 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 62 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 63 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 64 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 65 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 66 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 67 | github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 68 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= 69 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 70 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 71 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 72 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 73 | github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= 74 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 75 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 76 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 77 | github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= 78 | github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 79 | github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= 80 | github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= 81 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 82 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 83 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 84 | github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= 85 | github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= 86 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 87 | github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= 88 | github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 89 | github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= 90 | github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= 91 | github.com/form3tech-oss/jwt-go v3.2.5+incompatible h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8= 92 | github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= 93 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 94 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 95 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 96 | github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= 97 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 98 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 99 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 100 | github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8= 101 | github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k= 102 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 103 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 104 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 105 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 106 | github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= 107 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 108 | github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= 109 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 110 | github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 111 | github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= 112 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 113 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 114 | github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= 115 | github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= 116 | github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= 117 | github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 118 | github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= 119 | github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= 120 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= 121 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 122 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 123 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 124 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 125 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 126 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 127 | github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU= 128 | github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s= 129 | github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= 130 | github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 131 | github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA= 132 | github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 133 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 134 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 135 | github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= 136 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 137 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 138 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 139 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 140 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 141 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 142 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 143 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 144 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 145 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 146 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 147 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 148 | github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= 149 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 150 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 151 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 152 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 153 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 154 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 155 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 156 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 157 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 158 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 159 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 160 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 161 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 162 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 163 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 164 | github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= 165 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 166 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 167 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 168 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 169 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 170 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 171 | github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= 172 | github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= 173 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 174 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 175 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 176 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 177 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 178 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 179 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 180 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 181 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 182 | github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 183 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 184 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 185 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 186 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 187 | github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= 188 | github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 189 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 190 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 191 | github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 192 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 193 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 194 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 195 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 196 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 197 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 198 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 199 | github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 200 | github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 201 | github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 202 | github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 203 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 204 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 205 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 206 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 207 | github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 208 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 209 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 210 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 211 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 212 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 213 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 214 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 215 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 216 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 217 | github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q= 218 | github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 219 | github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= 220 | github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= 221 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 222 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 223 | github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 224 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 225 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 226 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 227 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 228 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 229 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 230 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 231 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 232 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 233 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 234 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 235 | github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= 236 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 237 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 238 | github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 239 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 240 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= 241 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 242 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 243 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 244 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 245 | github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= 246 | github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= 247 | github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 248 | github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 249 | github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= 250 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 251 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 252 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 253 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 254 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 255 | github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= 256 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 257 | github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= 258 | github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= 259 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 260 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 261 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 262 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 263 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 264 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 265 | github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 266 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 267 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 268 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 269 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 270 | github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= 271 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 272 | github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 273 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 274 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 275 | github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA= 276 | github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= 277 | github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 278 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 279 | github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= 280 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 281 | github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= 282 | github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= 283 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 284 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 285 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 286 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 287 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 288 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 289 | github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= 290 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 291 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 292 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 293 | github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 294 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 295 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 296 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 297 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 298 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 299 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 300 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 301 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 302 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 303 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 304 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 305 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 306 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 307 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 308 | github.com/ugorji/go/codec v1.2.9 h1:rmenucSohSTiyL09Y+l2OCk+FrMxGMzho2+tjr5ticU= 309 | github.com/ugorji/go/codec v1.2.9/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 310 | github.com/wonderivan/logger v1.0.0 h1:Z6Nz+3SNcizolx3ARH11axdD4DXjFpb2J+ziGUVlv/U= 311 | github.com/wonderivan/logger v1.0.0/go.mod h1:NObMfQ3WOLKfYEZuGeZQfuQfSPE5+QNgRddVMzsAT/k= 312 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 313 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 314 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 315 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 316 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 317 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 318 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 319 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 320 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 321 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 322 | go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= 323 | go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= 324 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU= 325 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 326 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 327 | golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 328 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 329 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 330 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 331 | golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 332 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 333 | golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 334 | golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 335 | golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= 336 | golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= 337 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 338 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 339 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 340 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 341 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 342 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 343 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 344 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 345 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 346 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 347 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 348 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 349 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 350 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 351 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 352 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 353 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 354 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 355 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 356 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 357 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 358 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 359 | golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 360 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 361 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 362 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 363 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 364 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 365 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 366 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 367 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 368 | golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 369 | golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 370 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 371 | golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 372 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 373 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 374 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 375 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 376 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 377 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 378 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 379 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 380 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 381 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 382 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 383 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 384 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 385 | golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 386 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 387 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 388 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 389 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 390 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 391 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 392 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 393 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 394 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 395 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 396 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 397 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 398 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 399 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 400 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 401 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 402 | golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 403 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 404 | golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 405 | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 406 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 407 | golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= 408 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 409 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 410 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 411 | golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= 412 | golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= 413 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 414 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 415 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 416 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 417 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 418 | golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 419 | golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 420 | golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 421 | golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 422 | golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 423 | golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 424 | golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 425 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg= 426 | golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= 427 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 428 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 429 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 430 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 431 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 432 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 433 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 434 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 435 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 436 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 437 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 438 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 439 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 440 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 441 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 442 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 443 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 444 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 445 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 446 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 447 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 448 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 449 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 450 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 451 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 452 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 453 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 454 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 455 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 456 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 457 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 458 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 459 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 460 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 461 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 462 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 463 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 464 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 465 | golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 466 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 467 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 468 | golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 469 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 470 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 471 | golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 472 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 473 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 474 | golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 475 | golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 476 | golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 477 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 478 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 479 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 480 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 481 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 482 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 483 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 484 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 485 | golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= 486 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 487 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 488 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 489 | golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= 490 | golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= 491 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 492 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 493 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 494 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 495 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 496 | golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 497 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 498 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 499 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 500 | golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= 501 | golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 502 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 503 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 504 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 505 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44= 506 | golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 507 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 508 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 509 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 510 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 511 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 512 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 513 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 514 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 515 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 516 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 517 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 518 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 519 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 520 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 521 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 522 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 523 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 524 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 525 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 526 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 527 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 528 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 529 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 530 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 531 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 532 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 533 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 534 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 535 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 536 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 537 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 538 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 539 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 540 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 541 | golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 542 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 543 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 544 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 545 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 546 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 547 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 548 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 549 | golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= 550 | golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 551 | golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 552 | golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 553 | golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 554 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 555 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= 556 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 557 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 558 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 559 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 560 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 561 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 562 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 563 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 564 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 565 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 566 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 567 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 568 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 569 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 570 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 571 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 572 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 573 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 574 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 575 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 576 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 577 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 578 | google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= 579 | google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= 580 | google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= 581 | google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= 582 | google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= 583 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 584 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 585 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 586 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 587 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 588 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 589 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 590 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 591 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 592 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 593 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 594 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 595 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 596 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 597 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 598 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 599 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 600 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 601 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 602 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 603 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 604 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 605 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 606 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 607 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 608 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 609 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 610 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 611 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 612 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 613 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 614 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 615 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 616 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 617 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 618 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 619 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 620 | google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 621 | google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 622 | google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 623 | google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 624 | google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 625 | google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 626 | google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 627 | google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 628 | google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 629 | google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 630 | google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= 631 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 632 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 633 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 634 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 635 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 636 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 637 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 638 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 639 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 640 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 641 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 642 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 643 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 644 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= 645 | google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= 646 | google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 647 | google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 648 | google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= 649 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 650 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 651 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 652 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 653 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 654 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 655 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 656 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 657 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 658 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 659 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 660 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 661 | google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 662 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= 663 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 664 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 665 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 666 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 667 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 668 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 669 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 670 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 671 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 672 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 673 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 674 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 675 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 676 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 677 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 678 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 679 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 680 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 681 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 682 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 683 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 684 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 685 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 686 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 687 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 688 | gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU= 689 | gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= 690 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 691 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 692 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 693 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 694 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 695 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 696 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 697 | k8s.io/api v0.24.2 h1:g518dPU/L7VRLxWfcadQn2OnsiGWVOadTLpdnqgY2OI= 698 | k8s.io/api v0.24.2/go.mod h1:AHqbSkTm6YrQ0ObxjO3Pmp/ubFF/KuM7jU+3khoBsOg= 699 | k8s.io/apimachinery v0.24.2 h1:5QlH9SL2C8KMcrNJPor+LbXVTaZRReml7svPEh4OKDM= 700 | k8s.io/apimachinery v0.24.2/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= 701 | k8s.io/client-go v0.24.2 h1:CoXFSf8if+bLEbinDqN9ePIDGzcLtqhfd6jpfnwGOFA= 702 | k8s.io/client-go v0.24.2/go.mod h1:zg4Xaoo+umDsfCWr4fCnmLEtQXyCNXCvJuSsglNcV30= 703 | k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= 704 | k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= 705 | k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= 706 | k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 707 | k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= 708 | k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 709 | k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42 h1:Gii5eqf+GmIEwGNKQYQClCayuJCe2/4fZUvF7VG99sU= 710 | k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= 711 | k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 712 | k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= 713 | k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= 714 | k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 715 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 716 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 717 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 718 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 719 | sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= 720 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 721 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 722 | sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= 723 | sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= 724 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 725 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 726 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 727 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 728 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 729 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "k8s-platform/config" 5 | "k8s-platform/controller" 6 | "k8s-platform/db" 7 | "k8s-platform/middle" 8 | "k8s-platform/service" 9 | "net/http" 10 | 11 | "github.com/gin-gonic/gin" 12 | ) 13 | 14 | func main() { 15 | // 初始化k8s client 16 | service.K8s.Init() // 可以使用service.K8s.clientset 进行跨包调用 17 | 18 | // 初始化数据库 19 | db.Init() 20 | // 初始化gin对象/路由配置 21 | r := gin.Default() 22 | // 加载jwt中间件 23 | //r.Use(middle.JWTAuth()) 24 | // 加载跨域中间件 25 | r.Use(middle.Cors()) 26 | // 初始化路由规则 27 | controller.Router.InitApiRouter(r) 28 | // 启动websocket 29 | go func() { 30 | http.HandleFunc("/ws", service.Terminal.WsHandler) 31 | http.ListenAndServe(":8081", nil) 32 | }() 33 | // gin程序启动 34 | r.Run(config.ListenAddr) 35 | 36 | // 关闭数据库 37 | db.Close() 38 | } 39 | -------------------------------------------------------------------------------- /middle/cors.go: -------------------------------------------------------------------------------- 1 | package middle 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | func Cors() gin.HandlerFunc { 10 | return func(ctx *gin.Context) { 11 | // 获取请求方法 12 | method := ctx.Request.Method 13 | 14 | // 添加跨域响应头 15 | ctx.Header("Content-Type", "application/json") 16 | ctx.Header("Access-Control-Allow-Origin", "*") 17 | ctx.Header("Access-Control-Max-Age", "86400") 18 | ctx.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, UPDATE") 19 | ctx.Header("Access-Control-Allow-Headers", "X-Token, Content-Type, Context-Length, Accept-Encoding, X-CSRF-Token, Authorization, X-MAX") 20 | ctx.Header("Access-Control-Allow-Credentials", "false") 21 | 22 | // 放行OPTIONS方法 23 | if method == "OPTIONS" { 24 | ctx.AbortWithStatus(http.StatusNoContent) 25 | } 26 | 27 | // 处理请求 28 | ctx.Next() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /middle/jwt.go: -------------------------------------------------------------------------------- 1 | package middle 2 | 3 | import ( 4 | "k8s-platform/utils" 5 | "net/http" 6 | 7 | "github.com/gin-gonic/gin" 8 | ) 9 | 10 | func JWTAuth() gin.HandlerFunc { 11 | return func(ctx *gin.Context) { 12 | // 对登录接口放行 13 | if len(ctx.Request.URL.String()) >= 10 && ctx.Request.URL.String()[0:10] == "/api/login" { 14 | ctx.Next() 15 | } else { 16 | // 处理验证逻辑 17 | token := ctx.Request.Header.Get("Authorization") 18 | if token == "" { 19 | ctx.JSON(http.StatusBadRequest, gin.H{ 20 | "msg": "请求未携带token,无权限访问", 21 | "data": nil, 22 | }) 23 | ctx.Abort() 24 | return 25 | } 26 | // 解析token内容 27 | claims, err := utils.JWTToken.ParseToken(token) 28 | if err != nil { 29 | // token过期错误 30 | if err.Error() == "TokenExpired" { 31 | ctx.JSON(http.StatusBadRequest, gin.H{ 32 | "msg": "授权已过期", 33 | "data": nil, 34 | }) 35 | ctx.Abort() 36 | return 37 | } 38 | // 其他解析错误 39 | ctx.JSON(http.StatusBadRequest, gin.H{ 40 | "msg": err.Error(), 41 | "data": nil, 42 | }) 43 | ctx.Abort() 44 | return 45 | } 46 | ctx.Set("claims", claims) 47 | ctx.Next() 48 | } 49 | 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /model/workflow.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | // 定义结构体,属性与mysql表字段对齐 6 | type Workflow struct { 7 | // gorm:"primarykey"用于声明主键 8 | ID uint `json:"id" gorm:"primaryKey"` 9 | CreateAt *time.Time `json:"created_at"` 10 | UpdateAt *time.Time `json:"update_at"` 11 | DeleteAt *time.Time `json:"deleted_at"` 12 | 13 | Name string `json:"name"` 14 | Namespace string `json:"namespace"` 15 | Replicas int32 `json:"replicas"` 16 | Deployment string `json:"deployment"` 17 | Service string `json:"service"` 18 | Ingress string `json:"ingress"` 19 | // gorm:"column:type"用于声明mysql中表的字段名 20 | Type string `json:"type" gorm:"column:type"` 21 | } 22 | 23 | // 定义TableName方法,返回mysql表名,以次定义mysql中的表名 24 | func (*Workflow) TableName() string { 25 | return "workflow" 26 | } 27 | -------------------------------------------------------------------------------- /service/configmap.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/wonderivan/logger" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | var ConfigMap configMap 14 | 15 | type configMap struct{} 16 | 17 | type ConfigMapsResp struct { 18 | Items []corev1.ConfigMap `json:"items"` 19 | Total int `json:"total"` 20 | } 21 | 22 | // 获取configmap列表,支持过滤、排序、分页 23 | func (c *configMap) GetConfigMaps(filterName, namespace string, limit, page int) (configMapsResp *ConfigMapsResp, err error) { 24 | //获取configMapList类型的configMap列表 25 | configMapList, err := K8s.ClientSet.CoreV1().ConfigMaps(namespace).List(context.TODO(), metav1.ListOptions{}) 26 | if err != nil { 27 | logger.Error(errors.New("获取ConfigMap列表失败, " + err.Error())) 28 | return nil, errors.New("获取ConfigMap列表失败, " + err.Error()) 29 | } 30 | //将configMapList中的configMap列表(Items),放进dataselector对象中,进行排序 31 | selectableData := &dataSelector{ 32 | GenericDataList: c.toCells(configMapList.Items), 33 | DataSelect: &DataSelectQuery{ 34 | Filter: &FilterQuery{Name: filterName}, 35 | Paginate: &PaginateQuery{ 36 | Limit: limit, 37 | Page: page, 38 | }, 39 | }, 40 | } 41 | 42 | filtered := selectableData.Filter() 43 | total := len(filtered.GenericDataList) 44 | data := filtered.Sort().Paginate() 45 | 46 | //将[]DataCell类型的configmap列表转为v1.configmap列表 47 | configMaps := c.fromCells(data.GenericDataList) 48 | 49 | return &ConfigMapsResp{ 50 | Items: configMaps, 51 | Total: total, 52 | }, nil 53 | } 54 | 55 | // 获取configmap详情 56 | func (c *configMap) GetConfigMapDetail(configMapName, namespace string) (configMap *corev1.ConfigMap, err error) { 57 | configMap, err = K8s.ClientSet.CoreV1().ConfigMaps(namespace).Get(context.TODO(), configMapName, metav1.GetOptions{}) 58 | if err != nil { 59 | logger.Error(errors.New("获取ConfigMap详情失败, " + err.Error())) 60 | return nil, errors.New("获取ConfigMap详情失败, " + err.Error()) 61 | } 62 | 63 | return configMap, nil 64 | } 65 | 66 | // 删除configmap 67 | func (c *configMap) DeleteConfigMap(configMapName, namespace string) (err error) { 68 | err = K8s.ClientSet.CoreV1().ConfigMaps(namespace).Delete(context.TODO(), configMapName, metav1.DeleteOptions{}) 69 | if err != nil { 70 | logger.Error(errors.New("删除ConfigMap失败, " + err.Error())) 71 | return errors.New("删除ConfigMap失败, " + err.Error()) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | // 更新configmap 78 | func (c *configMap) UpdateConfigMap(namespace, content string) (err error) { 79 | var configMap = &corev1.ConfigMap{} 80 | 81 | err = json.Unmarshal([]byte(content), configMap) 82 | if err != nil { 83 | logger.Error(errors.New("反序列化失败, " + err.Error())) 84 | return errors.New("反序列化失败, " + err.Error()) 85 | } 86 | 87 | _, err = K8s.ClientSet.CoreV1().ConfigMaps(namespace).Update(context.TODO(), configMap, metav1.UpdateOptions{}) 88 | if err != nil { 89 | logger.Error(errors.New("更新ConfigMap失败, " + err.Error())) 90 | return errors.New("更新ConfigMap失败, " + err.Error()) 91 | } 92 | return nil 93 | } 94 | 95 | func (c *configMap) toCells(std []corev1.ConfigMap) []DataCell { 96 | cells := make([]DataCell, len(std)) 97 | for i := range std { 98 | cells[i] = configMapCell(std[i]) 99 | } 100 | return cells 101 | } 102 | 103 | func (c *configMap) fromCells(cells []DataCell) []corev1.ConfigMap { 104 | configMaps := make([]corev1.ConfigMap, len(cells)) 105 | for i := range cells { 106 | configMaps[i] = corev1.ConfigMap(cells[i].(configMapCell)) 107 | } 108 | 109 | return configMaps 110 | } 111 | -------------------------------------------------------------------------------- /service/daemonset.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/wonderivan/logger" 9 | appsv1 "k8s.io/api/apps/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | var DaemonSet daemonSet 14 | 15 | type daemonSet struct{} 16 | 17 | type DaemonSetsResp struct { 18 | Items []appsv1.DaemonSet `json:"items"` 19 | Total int `json:"total"` 20 | } 21 | 22 | // 获取daemonset列表,支持过滤、排序、分页 23 | func (d *daemonSet) GetDaemonSets(filterName, namespace string, limit, page int) (daemonSetsResp *DaemonSetsResp, err error) { 24 | //获取daemonSetList类型的daemonSet列表 25 | daemonSetList, err := K8s.ClientSet.AppsV1().DaemonSets(namespace).List(context.TODO(), metav1.ListOptions{}) 26 | if err != nil { 27 | logger.Error(errors.New("获取DaemonSet列表失败, " + err.Error())) 28 | return nil, errors.New("获取DaemonSet列表失败, " + err.Error()) 29 | } 30 | //将daemonSetList中的daemonSet列表(Items),放进dataselector对象中,进行排序 31 | selectableData := &dataSelector{ 32 | GenericDataList: d.toCells(daemonSetList.Items), 33 | DataSelect: &DataSelectQuery{ 34 | Filter: &FilterQuery{Name: filterName}, 35 | Paginate: &PaginateQuery{ 36 | Limit: limit, 37 | Page: page, 38 | }, 39 | }, 40 | } 41 | 42 | filtered := selectableData.Filter() 43 | total := len(filtered.GenericDataList) 44 | data := filtered.Sort().Paginate() 45 | 46 | //将[]DataCell类型的daemonset列表转为v1.daemonset列表 47 | daemonSets := d.fromCells(data.GenericDataList) 48 | 49 | return &DaemonSetsResp{ 50 | Items: daemonSets, 51 | Total: total, 52 | }, nil 53 | } 54 | 55 | // 获取daemonset详情 56 | func (d *daemonSet) GetDaemonSetDetail(daemonSetName, namespace string) (daemonSet *appsv1.DaemonSet, err error) { 57 | daemonSet, err = K8s.ClientSet.AppsV1().DaemonSets(namespace).Get(context.TODO(), daemonSetName, metav1.GetOptions{}) 58 | if err != nil { 59 | logger.Error(errors.New("获取DaemonSet详情失败, " + err.Error())) 60 | return nil, errors.New("获取DaemonSet详情失败, " + err.Error()) 61 | } 62 | 63 | return daemonSet, nil 64 | } 65 | 66 | // 删除daemonset 67 | func (d *daemonSet) DeleteDaemonSet(daemonSetName, namespace string) (err error) { 68 | err = K8s.ClientSet.AppsV1().DaemonSets(namespace).Delete(context.TODO(), daemonSetName, metav1.DeleteOptions{}) 69 | if err != nil { 70 | logger.Error(errors.New("删除DaemonSet失败, " + err.Error())) 71 | return errors.New("删除DaemonSet失败, " + err.Error()) 72 | } 73 | return nil 74 | } 75 | 76 | // 更新daemonset 77 | func (d *daemonSet) UpdateDaemonSet(namespace, content string) (err error) { 78 | var daemonSet = &appsv1.DaemonSet{} 79 | 80 | err = json.Unmarshal([]byte(content), daemonSet) 81 | if err != nil { 82 | logger.Error(errors.New("反序列化失败, " + err.Error())) 83 | return errors.New("反序列化失败, " + err.Error()) 84 | } 85 | 86 | _, err = K8s.ClientSet.AppsV1().DaemonSets(namespace).Update(context.TODO(), daemonSet, metav1.UpdateOptions{}) 87 | if err != nil { 88 | logger.Error(errors.New("更新DaemonSet失败, " + err.Error())) 89 | return errors.New("更新DaemonSet失败, " + err.Error()) 90 | } 91 | return nil 92 | } 93 | 94 | func (d *daemonSet) toCells(std []appsv1.DaemonSet) []DataCell { 95 | cells := make([]DataCell, len(std)) 96 | for i := range std { 97 | cells[i] = daemonSetCell(std[i]) 98 | } 99 | return cells 100 | } 101 | 102 | func (d *daemonSet) fromCells(cells []DataCell) []appsv1.DaemonSet { 103 | daemonSets := make([]appsv1.DaemonSet, len(cells)) 104 | for i := range cells { 105 | daemonSets[i] = appsv1.DaemonSet(cells[i].(daemonSetCell)) 106 | } 107 | 108 | return daemonSets 109 | } 110 | -------------------------------------------------------------------------------- /service/dataselector.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "sort" 5 | "strings" 6 | "time" 7 | 8 | appsv1 "k8s.io/api/apps/v1" 9 | corev1 "k8s.io/api/core/v1" 10 | nwv1 "k8s.io/api/networking/v1" 11 | ) 12 | 13 | // dataselector用于排序,过滤,分页的数据类型 14 | type dataSelector struct { 15 | GenericDataList []DataCell 16 | DataSelect *DataSelectQuery 17 | } 18 | 19 | // DataCell接口,用于各种资源List的类型转换,转换后可以使用dataselector的排序,过滤,分页方法 20 | type DataCell interface { 21 | GetCreation() time.Time 22 | GetName() string 23 | } 24 | 25 | // DataSelectQuery 定义过滤和分页的结构体,过滤:Name 分页:Limit和Page 26 | type DataSelectQuery struct { 27 | Filter *FilterQuery 28 | Paginate *PaginateQuery 29 | } 30 | 31 | // FilterQuery用于查询 过滤:Name 32 | type FilterQuery struct { 33 | Name string 34 | } 35 | 36 | // 分页:Limit和Page Limit是单页的数据条数,Page是第几页 37 | type PaginateQuery struct { 38 | Page int 39 | Limit int 40 | } 41 | 42 | // 实现自定义结构的排序,需要重写Len、Swap、Less方法 43 | // Len方法用于获取数组的长度 44 | func (d *dataSelector) Len() int { 45 | return len(d.GenericDataList) 46 | } 47 | 48 | // Swap用于数据比较大小后的位置变更 49 | func (d *dataSelector) Swap(i, j int) { 50 | d.GenericDataList[i], d.GenericDataList[j] = d.GenericDataList[j], d.GenericDataList[i] 51 | } 52 | 53 | // Less用于比较大小 54 | func (d *dataSelector) Less(i, j int) bool { 55 | return d.GenericDataList[i].GetCreation().Before(d.GenericDataList[j].GetCreation()) 56 | } 57 | 58 | // 重写以上三个方法,用sort.Sort 方法触发排序 59 | func (d *dataSelector) Sort() *dataSelector { 60 | sort.Sort(d) 61 | return d 62 | } 63 | 64 | // Filter方法用于过滤,比较数据Name属性,若包含则返回 65 | func (d *dataSelector) Filter() *dataSelector { 66 | if d.DataSelect.Filter.Name == "" { 67 | return d 68 | } 69 | 70 | filtered := []DataCell{} 71 | for _, value := range d.GenericDataList { 72 | // 定义是否匹配的标签变量,默认是匹配的 73 | matches := true 74 | objName := value.GetName() 75 | if !strings.Contains(objName, d.DataSelect.Filter.Name) { 76 | matches = false 77 | continue 78 | } 79 | if matches { 80 | filtered = append(filtered, value) 81 | } 82 | } 83 | d.GenericDataList = filtered 84 | return d 85 | } 86 | 87 | // Paginate分页,根据Limit和Page的传参,取一定范围内的数据返回 88 | func (d *dataSelector) Paginate() *dataSelector { 89 | limit := d.DataSelect.Paginate.Limit 90 | page := d.DataSelect.Paginate.Page 91 | // 验证参数合法,若参数不合法,则返回所有数据 92 | if limit <= 0 || page <= 0 { 93 | return d 94 | } 95 | // 举例:25个元素的数组,limit是10, page是3,startIndex是20,endIndex是29(实际上endIndex是24) 96 | startIndex := limit * (page - 1) 97 | endIndex := limit*page - 1 98 | 99 | // 处理最后一页,这时候就把endIndex由30改为25了 100 | if len(d.GenericDataList) < endIndex { 101 | endIndex = len(d.GenericDataList) 102 | } 103 | d.GenericDataList = d.GenericDataList[startIndex:endIndex] 104 | return d 105 | } 106 | 107 | // 定义podCell, 重写GetCreation和GetName方法后,可以进行数据转换 108 | // covev1.Pod --> podCell --> DataCell 109 | // appsv1.Deployment --> deployCell --> DataCell 110 | type podCell corev1.Pod 111 | 112 | // 重写DataCell接口的两个方法 113 | func (p podCell) GetCreation() time.Time { 114 | return p.CreationTimestamp.Time 115 | } 116 | 117 | func (p podCell) GetName() string { 118 | return p.Name 119 | } 120 | 121 | type deploymentCell appsv1.Deployment 122 | 123 | func (d deploymentCell) GetCreation() time.Time { 124 | return d.CreationTimestamp.Time 125 | } 126 | 127 | func (d deploymentCell) GetName() string { 128 | return d.Name 129 | } 130 | 131 | // daemonCell 132 | type daemonSetCell appsv1.DaemonSet 133 | 134 | func (d daemonSetCell) GetCreation() time.Time { 135 | return d.CreationTimestamp.Time 136 | } 137 | 138 | func (d daemonSetCell) GetName() string { 139 | return d.Name 140 | } 141 | 142 | // statefulSetCell 143 | type statefulSetCell appsv1.StatefulSet 144 | 145 | func (s statefulSetCell) GetCreation() time.Time { 146 | return s.CreationTimestamp.Time 147 | } 148 | 149 | func (s statefulSetCell) GetName() string { 150 | return s.Name 151 | } 152 | 153 | type namespaceCell corev1.Namespace 154 | 155 | func (n namespaceCell) GetCreation() time.Time { 156 | return n.CreationTimestamp.Time 157 | } 158 | 159 | func (n namespaceCell) GetName() string { 160 | return n.Name 161 | } 162 | 163 | type pvCell corev1.PersistentVolume 164 | 165 | func (p pvCell) GetCreation() time.Time { 166 | return p.CreationTimestamp.Time 167 | } 168 | 169 | func (p pvCell) GetName() string { 170 | return p.Name 171 | } 172 | 173 | // service 174 | type serviceCell corev1.Service 175 | 176 | func (s serviceCell) GetCreation() time.Time { 177 | return s.CreationTimestamp.Time 178 | } 179 | 180 | func (s serviceCell) GetName() string { 181 | return s.Name 182 | } 183 | 184 | // Ingress 185 | type ingressCell nwv1.Ingress 186 | 187 | func (i ingressCell) GetCreation() time.Time { 188 | return i.CreationTimestamp.Time 189 | } 190 | 191 | func (i ingressCell) GetName() string { 192 | return i.Name 193 | } 194 | 195 | type nodeCell corev1.Node 196 | 197 | func (n nodeCell) GetCreation() time.Time { 198 | return n.CreationTimestamp.Time 199 | } 200 | 201 | func (n nodeCell) GetName() string { 202 | return n.Name 203 | } 204 | 205 | // configmap 206 | type configMapCell corev1.ConfigMap 207 | 208 | func (c configMapCell) GetCreation() time.Time { 209 | return c.CreationTimestamp.Time 210 | } 211 | 212 | func (c configMapCell) GetName() string { 213 | return c.Name 214 | } 215 | 216 | // secret 217 | type secretCell corev1.Secret 218 | 219 | func (s secretCell) GetCreation() time.Time { 220 | return s.CreationTimestamp.Time 221 | } 222 | 223 | func (s secretCell) GetName() string { 224 | return s.Name 225 | } 226 | 227 | // pvc 228 | type pvcCell corev1.PersistentVolumeClaim 229 | 230 | func (p pvcCell) GetCreation() time.Time { 231 | return p.CreationTimestamp.Time 232 | } 233 | 234 | func (p pvcCell) GetName() string { 235 | return p.Name 236 | } 237 | -------------------------------------------------------------------------------- /service/deployment.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "strconv" 8 | "time" 9 | 10 | "github.com/wonderivan/logger" 11 | appsv1 "k8s.io/api/apps/v1" 12 | corev1 "k8s.io/api/core/v1" 13 | "k8s.io/apimachinery/pkg/api/resource" 14 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 | "k8s.io/apimachinery/pkg/util/intstr" 16 | ) 17 | 18 | var Deployment deployment 19 | 20 | type deployment struct{} 21 | 22 | // 定义列表的返回内容,Items是deployment元素列表,Total为deployment元素数量 23 | type DeploymentsResp struct { 24 | Items []appsv1.Deployment `json:"items"` 25 | Total int `json:"total"` 26 | } 27 | 28 | // 定义DeployCreate结构体,用于创建deployment需要的参数属性的定义 29 | type DeployCreate struct { 30 | Name string `json:"name"` 31 | Namespace string `json:"namespace"` 32 | Replicas int32 `json:"replicas"` 33 | Image string `json:"image"` 34 | Label map[string]string `json:"label"` 35 | Cpu string `json:"cpu"` 36 | Memory string `json:"memory"` 37 | ContainerPort int32 `json:"container_port"` 38 | HealthCheck bool `json:"health_check"` 39 | HealthPath string `json:"health_path"` 40 | } 41 | 42 | // 定义DeploysNp类型,用于返回namespace中deployment的数量 43 | type DeploysNp struct { 44 | Namespace string `json:"namespace"` 45 | DeployNum int `json:"deployment_num"` 46 | } 47 | 48 | // 获取deployment列表,支持过滤、排序、分页 49 | func (d *deployment) GetDeployments(filterName, namespace string, limit, page int) (deploymentsResp *DeploymentsResp, err error) { 50 | //获取deploymentList类型的deployment列表 51 | deploymentList, err := K8s.ClientSet.AppsV1().Deployments(namespace).List(context.TODO(), metav1.ListOptions{}) 52 | if err != nil { 53 | logger.Error(errors.New("获取Deployment列表失败, " + err.Error())) 54 | return nil, errors.New("获取Deployment列表失败, " + err.Error()) 55 | } 56 | //将deploymentList中的deployment列表(Items),放进dataselector对象中,进行排序 57 | selectableData := &dataSelector{ 58 | GenericDataList: d.toCells(deploymentList.Items), 59 | DataSelect: &DataSelectQuery{ 60 | Filter: &FilterQuery{Name: filterName}, 61 | Paginate: &PaginateQuery{ 62 | Limit: limit, 63 | Page: page, 64 | }, 65 | }, 66 | } 67 | filtered := selectableData.Filter() 68 | total := len(filtered.GenericDataList) 69 | data := filtered.Sort().Paginate() 70 | //将[]DataCell类型的deployment列表转为appsv1.deployment列表 71 | deployments := d.fromCells(data.GenericDataList) 72 | return &DeploymentsResp{ 73 | Items: deployments, 74 | Total: total, 75 | }, nil 76 | } 77 | 78 | // 获取deployment详情 79 | func (d *deployment) GetDeploymentDetail(deploymentName, namespace string) (deployment *appsv1.Deployment, err error) { 80 | deployment, err = K8s.ClientSet.AppsV1().Deployments(namespace).Get(context.TODO(), deploymentName, metav1.GetOptions{}) 81 | if err != nil { 82 | logger.Error(errors.New("获取Deployment详情失败, " + err.Error())) 83 | return nil, errors.New("获取Deployment详情失败, " + err.Error()) 84 | } 85 | return deployment, nil 86 | } 87 | 88 | // 设置deployment副本数 89 | func (d *deployment) ScaleDeployment(deploymentName, namespace string, scaleNum int) (replica int32, err error) { 90 | //获取autoscalingv1.Scale类型的对象,能点出当前的副本数 91 | scale, err := K8s.ClientSet.AppsV1().Deployments(namespace).GetScale(context.TODO(), deploymentName, metav1.GetOptions{}) 92 | if err != nil { 93 | logger.Error(errors.New("获取Deployment副本数信息失败, " + err.Error())) 94 | return 0, errors.New("获取Deployment副本数信息失败, " + err.Error()) 95 | } 96 | //修改副本数 97 | scale.Spec.Replicas = int32(scaleNum) 98 | //更新副本数,传入scale对象 99 | newScale, err := K8s.ClientSet.AppsV1().Deployments(namespace).UpdateScale(context.TODO(), deploymentName, scale, metav1.UpdateOptions{}) 100 | if err != nil { 101 | logger.Error(errors.New("更新Deployment副本数信息失败, " + err.Error())) 102 | return 0, errors.New("更新Deployment副本数信息失败, " + err.Error()) 103 | } 104 | return newScale.Spec.Replicas, nil 105 | } 106 | 107 | // 创建deployment,接收DeployCreate对象 108 | func (d *deployment) CreateDeployment(data *DeployCreate) (err error) { 109 | //将data中的属性组装成appsv1.Deployment对象,并将入参数据放入 110 | deployment := &appsv1.Deployment{ 111 | //ObjectMeta中定义资源名、命名空间以及标签 112 | ObjectMeta: metav1.ObjectMeta{ 113 | Name: data.Name, 114 | Namespace: data.Namespace, 115 | Labels: data.Label, 116 | }, 117 | //Spec中定义副本数、选择器、以及pod属性 118 | Spec: appsv1.DeploymentSpec{ 119 | Replicas: &data.Replicas, 120 | Selector: &metav1.LabelSelector{ 121 | MatchLabels: data.Label, 122 | }, 123 | Template: corev1.PodTemplateSpec{ 124 | //定义pod名和标签 125 | ObjectMeta: metav1.ObjectMeta{ 126 | Name: data.Name, 127 | Labels: data.Label, 128 | }, 129 | //定义容器名、镜像和端口 130 | Spec: corev1.PodSpec{ 131 | Containers: []corev1.Container{ 132 | { 133 | Name: data.Name, 134 | Image: data.Image, 135 | Ports: []corev1.ContainerPort{ 136 | { 137 | Name: "http", 138 | Protocol: corev1.ProtocolTCP, 139 | ContainerPort: data.ContainerPort, 140 | }, 141 | }, 142 | }, 143 | }, 144 | }, 145 | }, 146 | }, 147 | //Status定义资源的运行状态,这里由于是新建,传入空的appsv1.DeploymentStatus{}对象即可 148 | Status: appsv1.DeploymentStatus{}, 149 | } 150 | //判断是否打开健康检查功能,若打开,则定义ReadinessProbe和LivenessProbe 151 | if data.HealthCheck { 152 | //设置第一个容器的ReadinessProbe,因为我们pod中只有一个容器,所以直接使用index 0即可 153 | //若pod中有多个容器,则这里需要使用for循环去定义了 154 | deployment.Spec.Template.Spec.Containers[0].ReadinessProbe = &corev1.Probe{ 155 | ProbeHandler: corev1.ProbeHandler{ 156 | HTTPGet: &corev1.HTTPGetAction{ 157 | Path: data.HealthPath, 158 | //intstr.IntOrString的作用是端口可以定义为整型,也可以定义为字符串 159 | //Type=0则表示表示该结构体实例内的数据为整型,转json时只使用IntVal的数据 160 | //Type=1则表示表示该结构体实例内的数据为字符串,转json时只使用StrVal的数据 161 | Port: intstr.IntOrString{ 162 | Type: 0, 163 | IntVal: data.ContainerPort, 164 | }, 165 | }, 166 | }, 167 | //初始化等待时间 168 | InitialDelaySeconds: 5, 169 | //超时时间 170 | TimeoutSeconds: 5, 171 | //执行间隔 172 | PeriodSeconds: 5, 173 | } 174 | deployment.Spec.Template.Spec.Containers[0].LivenessProbe = &corev1.Probe{ 175 | ProbeHandler: corev1.ProbeHandler{ 176 | HTTPGet: &corev1.HTTPGetAction{ 177 | Path: data.HealthPath, 178 | Port: intstr.IntOrString{ 179 | Type: 0, 180 | IntVal: data.ContainerPort, 181 | }, 182 | }, 183 | }, 184 | InitialDelaySeconds: 15, 185 | TimeoutSeconds: 5, 186 | PeriodSeconds: 5, 187 | } 188 | //定义容器的limit和request资源 189 | deployment.Spec.Template.Spec.Containers[0].Resources.Limits = map[corev1.ResourceName]resource.Quantity{ 190 | corev1.ResourceCPU: resource.MustParse(data.Cpu), 191 | corev1.ResourceMemory: resource.MustParse(data.Memory), 192 | } 193 | deployment.Spec.Template.Spec.Containers[0].Resources.Requests = map[corev1.ResourceName]resource.Quantity{ 194 | corev1.ResourceCPU: resource.MustParse(data.Cpu), 195 | corev1.ResourceMemory: resource.MustParse(data.Memory), 196 | } 197 | } 198 | //调用sdk创建deployment 199 | _, err = K8s.ClientSet.AppsV1().Deployments(data.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{}) 200 | if err != nil { 201 | logger.Error(errors.New("创建Deployment失败, " + err.Error())) 202 | return errors.New("创建Deployment失败, " + err.Error()) 203 | } 204 | return nil 205 | } 206 | 207 | // 删除deployment 208 | func (d *deployment) DeleteDeployment(deploymentName, namespace string) (err error) { 209 | err = K8s.ClientSet.AppsV1().Deployments(namespace).Delete(context.TODO(), deploymentName, metav1.DeleteOptions{}) 210 | if err != nil { 211 | logger.Error(errors.New("删除Deployment失败, " + err.Error())) 212 | return errors.New("删除Deployment失败, " + err.Error()) 213 | } 214 | return nil 215 | } 216 | 217 | // 重启deployment 218 | func (d *deployment) RestartDeployment(deploymentName, namespace string) (err error) { 219 | //此功能等同于一下kubectl命令 220 | //kubectl deployment ${service} -p \ 221 | //'{"spec":{"template":{"spec":{"containers":[{"name":"'"${service}"'","env":[{"name":"RESTART_","value":"'$(date +%s)'"}]}]}}}}' 222 | 223 | //使用patchData Map组装数据 224 | patchData := map[string]interface{}{ 225 | "spec": map[string]interface{}{ 226 | "template": map[string]interface{}{ 227 | "spec": map[string]interface{}{ 228 | "containers": []map[string]interface{}{ 229 | {"name": deploymentName, 230 | "env": []map[string]string{{ 231 | "name": "RESTART_", 232 | "value": strconv.FormatInt(time.Now().Unix(), 10), 233 | }}, 234 | }, 235 | }, 236 | }, 237 | }, 238 | }, 239 | } 240 | //序列化为字节,因为patch方法只接收字节类型参数 241 | patchByte, err := json.Marshal(patchData) 242 | if err != nil { 243 | logger.Error(errors.New("json序列化失败, " + err.Error())) 244 | return errors.New("json序列化失败, " + err.Error()) 245 | } 246 | //调用patch方法更新deployment 247 | _, err = K8s.ClientSet.AppsV1().Deployments(namespace).Patch(context.TODO(), deploymentName, "application/strategic-merge-patch+json", patchByte, metav1.PatchOptions{}) 248 | if err != nil { 249 | logger.Error(errors.New("重启Deployment失败, " + err.Error())) 250 | return errors.New("重启Deployment失败, " + err.Error()) 251 | } 252 | return nil 253 | } 254 | 255 | // 更新deployment 256 | func (d *deployment) UpdateDeployment(namespace, content string) (err error) { 257 | var deploy = &appsv1.Deployment{} 258 | 259 | err = json.Unmarshal([]byte(content), deploy) 260 | if err != nil { 261 | logger.Error(errors.New("反序列化失败, " + err.Error())) 262 | return errors.New("反序列化失败, " + err.Error()) 263 | } 264 | _, err = K8s.ClientSet.AppsV1().Deployments(namespace).Update(context.TODO(), deploy, metav1.UpdateOptions{}) 265 | if err != nil { 266 | logger.Error(errors.New("更新Deployment失败, " + err.Error())) 267 | return errors.New("更新Deployment失败, " + err.Error()) 268 | } 269 | return nil 270 | } 271 | 272 | // 获取每个namespace的deployment数量 273 | func (d *deployment) GetDeployNumPerNp() (deploysNps []*DeploysNp, err error) { 274 | namespaceList, err := K8s.ClientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) 275 | if err != nil { 276 | return nil, err 277 | } 278 | for _, namespace := range namespaceList.Items { 279 | deploymentList, err := K8s.ClientSet.AppsV1().Deployments(namespace.Name).List(context.TODO(), metav1.ListOptions{}) 280 | if err != nil { 281 | return nil, err 282 | } 283 | 284 | deploysNp := &DeploysNp{ 285 | Namespace: namespace.Name, 286 | DeployNum: len(deploymentList.Items), 287 | } 288 | deploysNps = append(deploysNps, deploysNp) 289 | } 290 | return deploysNps, nil 291 | } 292 | 293 | // 类型转换 294 | func (d *deployment) toCells(deployments []appsv1.Deployment) []DataCell { 295 | cells := make([]DataCell, len(deployments)) 296 | for i := range deployments { 297 | cells[i] = deploymentCell(deployments[i]) 298 | } 299 | return cells 300 | } 301 | 302 | func (d *deployment) fromCells(cells []DataCell) []appsv1.Deployment { 303 | deployments := make([]appsv1.Deployment, len(cells)) 304 | for i := range cells { 305 | // 是将DataCell类型转成podCell 306 | deployments[i] = appsv1.Deployment(cells[i].(deploymentCell)) 307 | } 308 | return deployments 309 | } 310 | -------------------------------------------------------------------------------- /service/ingress.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/wonderivan/logger" 9 | nwv1 "k8s.io/api/networking/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | var Ingress ingress 14 | 15 | type ingress struct{} 16 | 17 | type IngressesResp struct { 18 | Items []nwv1.Ingress `json:"items"` 19 | Total int `json:"total"` 20 | } 21 | 22 | // 定义ServiceCreate结构体,用于创建service需要的参数属性的定义 23 | type IngressCreate struct { 24 | Name string `json:"name"` 25 | Namespace string `json:"namespace"` 26 | Label map[string]string `json:"label"` 27 | Hosts map[string][]*HttpPath `json:"hosts"` 28 | } 29 | 30 | // 定义ingress的path结构体 31 | type HttpPath struct { 32 | Path string `json:"path"` 33 | PathType nwv1.PathType `json:"path_type"` 34 | ServiceName string `json:"service_name"` 35 | ServicePort int32 `json:"service_port"` 36 | } 37 | 38 | // 获取ingress列表,支持过滤、排序、分页 39 | func (i *ingress) GetIngresses(filterName, namespace string, limit, page int) (ingressesResp *IngressesResp, err error) { 40 | //获取ingressList类型的ingress列表 41 | ingressList, err := K8s.ClientSet.NetworkingV1().Ingresses(namespace).List(context.TODO(), metav1.ListOptions{}) 42 | if err != nil { 43 | logger.Error(errors.New("获取Ingress列表失败, " + err.Error())) 44 | return nil, errors.New("获取Ingress列表失败, " + err.Error()) 45 | } 46 | //将ingressList中的ingress列表(Items),放进dataselector对象中,进行排序 47 | selectableData := &dataSelector{ 48 | GenericDataList: i.toCells(ingressList.Items), 49 | DataSelect: &DataSelectQuery{ 50 | Filter: &FilterQuery{Name: filterName}, 51 | Paginate: &PaginateQuery{ 52 | Limit: limit, 53 | Page: page, 54 | }, 55 | }, 56 | } 57 | 58 | filtered := selectableData.Filter() 59 | total := len(filtered.GenericDataList) 60 | data := filtered.Sort().Paginate() 61 | 62 | //将[]DataCell类型的ingress列表转为v1.ingress列表 63 | ingresss := i.fromCells(data.GenericDataList) 64 | 65 | return &IngressesResp{ 66 | Items: ingresss, 67 | Total: total, 68 | }, nil 69 | } 70 | 71 | // 获取ingress详情 72 | func (i *ingress) GetIngresstDetail(ingressName, namespace string) (ingress *nwv1.Ingress, err error) { 73 | ingress, err = K8s.ClientSet.NetworkingV1().Ingresses(namespace).Get(context.TODO(), ingressName, metav1.GetOptions{}) 74 | if err != nil { 75 | logger.Error(errors.New("获取Ingress详情失败, " + err.Error())) 76 | return nil, errors.New("获取Ingress详情失败, " + err.Error()) 77 | } 78 | 79 | return ingress, nil 80 | } 81 | 82 | // 创建ingress 83 | func (i *ingress) CreateIngress(data *IngressCreate) (err error) { 84 | //声明nwv1.IngressRule和nwv1.HTTPIngressPath变量,后面组装数据于鏊用到 85 | var ingressRules []nwv1.IngressRule 86 | var httpIngressPATHs []nwv1.HTTPIngressPath 87 | //将data中的数据组装成nwv1.Ingress对象 88 | ingress := &nwv1.Ingress{ 89 | ObjectMeta: metav1.ObjectMeta{ 90 | Name: data.Name, 91 | Namespace: data.Namespace, 92 | Labels: data.Label, 93 | }, 94 | Status: nwv1.IngressStatus{}, 95 | } 96 | //第一层for循环是将host组装成nwv1.IngressRule类型的对象 97 | // 一个host对应一个ingressrule,每个ingressrule中包含一个host和多个path 98 | for key, value := range data.Hosts { 99 | ir := nwv1.IngressRule{ 100 | Host: key, 101 | //这里现将nwv1.HTTPIngressRuleValue类型中的Paths置为空,后面组装好数据再赋值 102 | IngressRuleValue: nwv1.IngressRuleValue{ 103 | HTTP: &nwv1.HTTPIngressRuleValue{Paths: nil}, 104 | }, 105 | } 106 | //第二层for循环是将path组装成nwv1.HTTPIngressPath类型的对象 107 | for _, httpPath := range value { 108 | hip := nwv1.HTTPIngressPath{ 109 | Path: httpPath.Path, 110 | PathType: &httpPath.PathType, 111 | Backend: nwv1.IngressBackend{ 112 | Service: &nwv1.IngressServiceBackend{ 113 | Name: httpPath.ServiceName, 114 | Port: nwv1.ServiceBackendPort{ 115 | Number: httpPath.ServicePort, 116 | }, 117 | }, 118 | }, 119 | } 120 | //将每个hip对象组装成数组 121 | httpIngressPATHs = append(httpIngressPATHs, hip) 122 | } 123 | //给Paths赋值,前面置为空了 124 | ir.IngressRuleValue.HTTP.Paths = httpIngressPATHs 125 | //将每个ir对象组装成数组,这个ir对象就是IngressRule,每个元素是一个host和多个path 126 | ingressRules = append(ingressRules, ir) 127 | } 128 | //将ingressRules对象加入到ingress的规则中 129 | ingress.Spec.Rules = ingressRules 130 | //创建ingress 131 | _, err = K8s.ClientSet.NetworkingV1().Ingresses(data.Namespace).Create(context.TODO(), ingress, metav1.CreateOptions{}) 132 | if err != nil { 133 | logger.Error(errors.New("创建Ingress失败, " + err.Error())) 134 | return errors.New("创建Ingress失败, " + err.Error()) 135 | } 136 | 137 | return nil 138 | } 139 | 140 | // 删除ingress 141 | func (i *ingress) DeleteIngress(ingressName, namespace string) (err error) { 142 | err = K8s.ClientSet.NetworkingV1().Ingresses(namespace).Delete(context.TODO(), ingressName, metav1.DeleteOptions{}) 143 | if err != nil { 144 | logger.Error(errors.New("删除Ingress失败, " + err.Error())) 145 | return errors.New("删除Ingress失败, " + err.Error()) 146 | } 147 | 148 | return nil 149 | } 150 | 151 | // 更新ingress 152 | func (i *ingress) UpdateIngress(namespace, content string) (err error) { 153 | var ingress = &nwv1.Ingress{} 154 | 155 | err = json.Unmarshal([]byte(content), ingress) 156 | if err != nil { 157 | logger.Error(errors.New("反序列化失败, " + err.Error())) 158 | return errors.New("反序列化失败, " + err.Error()) 159 | } 160 | 161 | _, err = K8s.ClientSet.NetworkingV1().Ingresses(namespace).Update(context.TODO(), ingress, metav1.UpdateOptions{}) 162 | if err != nil { 163 | logger.Error(errors.New("更新ingress失败, " + err.Error())) 164 | return errors.New("更新ingress失败, " + err.Error()) 165 | } 166 | return nil 167 | } 168 | 169 | func (i *ingress) toCells(std []nwv1.Ingress) []DataCell { 170 | cells := make([]DataCell, len(std)) 171 | for i := range std { 172 | cells[i] = ingressCell(std[i]) 173 | } 174 | return cells 175 | } 176 | 177 | func (i *ingress) fromCells(cells []DataCell) []nwv1.Ingress { 178 | ingresss := make([]nwv1.Ingress, len(cells)) 179 | for i := range cells { 180 | ingresss[i] = nwv1.Ingress(cells[i].(ingressCell)) 181 | } 182 | 183 | return ingresss 184 | } 185 | -------------------------------------------------------------------------------- /service/init.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "k8s-platform/config" 5 | 6 | "github.com/wonderivan/logger" 7 | "k8s.io/client-go/kubernetes" 8 | "k8s.io/client-go/tools/clientcmd" 9 | ) 10 | 11 | // 用于初始化k8s client 12 | var K8s k8s 13 | 14 | type k8s struct { 15 | ClientSet *kubernetes.Clientset 16 | } 17 | 18 | // 初始化k8s 19 | func (k *k8s) Init() { 20 | // 将kubeconfig格式化为rest.config类型 21 | // config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig) 22 | config, err := clientcmd.BuildConfigFromFlags("", config.KubeConfig) 23 | if err != nil { 24 | panic("获取K8s配置失败:" + err.Error()) 25 | } else { 26 | logger.Info("获取K8s配置成功!") 27 | } 28 | // 通过config创建clientset 29 | clientset, err := kubernetes.NewForConfig(config) 30 | if err != nil { 31 | panic("创建K8s client失败:" + err.Error()) 32 | } else { 33 | logger.Info("创建K8s client 成功!") 34 | } 35 | k.ClientSet = clientset 36 | } 37 | -------------------------------------------------------------------------------- /service/namespace.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/wonderivan/logger" 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | var Namespace namespace 13 | 14 | type namespace struct{} 15 | 16 | type NamespacesResp struct { 17 | Items []corev1.Namespace `json:"items"` 18 | Total int `json:"total"` 19 | } 20 | 21 | // 获取namespace列表,支持过滤、排序、分页 22 | func (n *namespace) GetNamespaces(filterName string, limit, page int) (namespacesResp *NamespacesResp, err error) { 23 | //获取namespaceList类型的namespace列表 24 | namespaceList, err := K8s.ClientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) 25 | if err != nil { 26 | logger.Error(errors.New("获取Namespace列表失败, " + err.Error())) 27 | return nil, errors.New("获取Namespace列表失败, " + err.Error()) 28 | } 29 | //将namespaceList中的namespace列表(Items),放进dataselector对象中,进行排序 30 | selectableData := &dataSelector{ 31 | GenericDataList: n.toCells(namespaceList.Items), 32 | DataSelect: &DataSelectQuery{ 33 | Filter: &FilterQuery{Name: filterName}, 34 | Paginate: &PaginateQuery{ 35 | Limit: limit, 36 | Page: page, 37 | }, 38 | }, 39 | } 40 | 41 | filtered := selectableData.Filter() 42 | total := len(filtered.GenericDataList) 43 | data := filtered.Sort().Paginate() 44 | 45 | //将[]DataCell类型的namespace列表转为v1.namespace列表 46 | namespaces := n.fromCells(data.GenericDataList) 47 | 48 | return &NamespacesResp{ 49 | Items: namespaces, 50 | Total: total, 51 | }, nil 52 | } 53 | 54 | // 获取namespace详情 55 | func (n *namespace) GetNamespaceDetail(namespaceName string) (namespace *corev1.Namespace, err error) { 56 | namespace, err = K8s.ClientSet.CoreV1().Namespaces().Get(context.TODO(), namespaceName, metav1.GetOptions{}) 57 | if err != nil { 58 | logger.Error(errors.New("获取Namespace详情失败, " + err.Error())) 59 | return nil, errors.New("获取Namespace详情失败, " + err.Error()) 60 | } 61 | 62 | return namespace, nil 63 | } 64 | 65 | // 删除namespace 66 | func (n *namespace) DeleteNamespace(namespaceName string) (err error) { 67 | err = K8s.ClientSet.CoreV1().Namespaces().Delete(context.TODO(), namespaceName, metav1.DeleteOptions{}) 68 | if err != nil { 69 | logger.Error(errors.New("删除Namespace失败, " + err.Error())) 70 | return errors.New("删除Namespace失败, " + err.Error()) 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func (n *namespace) toCells(std []corev1.Namespace) []DataCell { 77 | cells := make([]DataCell, len(std)) 78 | for i := range std { 79 | cells[i] = namespaceCell(std[i]) 80 | } 81 | return cells 82 | } 83 | 84 | func (n *namespace) fromCells(cells []DataCell) []corev1.Namespace { 85 | namespaces := make([]corev1.Namespace, len(cells)) 86 | for i := range cells { 87 | namespaces[i] = corev1.Namespace(cells[i].(namespaceCell)) 88 | } 89 | 90 | return namespaces 91 | } 92 | -------------------------------------------------------------------------------- /service/node.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/wonderivan/logger" 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | var Node node 13 | 14 | type node struct{} 15 | 16 | type NodesResp struct { 17 | Items []corev1.Node `json:"items"` 18 | Total int `json:"total"` 19 | } 20 | 21 | // 获取node列表,支持过滤、排序、分页 22 | func (n *node) GetNodes(filterName string, limit, page int) (nodesResp *NodesResp, err error) { 23 | //获取nodeList类型的node列表 24 | nodeList, err := K8s.ClientSet.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) 25 | if err != nil { 26 | logger.Error(errors.New("获取Node列表失败, " + err.Error())) 27 | return nil, errors.New("获取Node列表失败, " + err.Error()) 28 | } 29 | //将nodeList中的node列表(Items),放进dataselector对象中,进行排序 30 | selectableData := &dataSelector{ 31 | GenericDataList: n.toCells(nodeList.Items), 32 | DataSelect: &DataSelectQuery{ 33 | Filter: &FilterQuery{Name: filterName}, 34 | Paginate: &PaginateQuery{ 35 | Limit: limit, 36 | Page: page, 37 | }, 38 | }, 39 | } 40 | 41 | filtered := selectableData.Filter() 42 | total := len(filtered.GenericDataList) 43 | data := filtered.Sort().Paginate() 44 | 45 | //将[]DataCell类型的node列表转为v1.node列表 46 | nodes := n.fromCells(data.GenericDataList) 47 | 48 | return &NodesResp{ 49 | Items: nodes, 50 | Total: total, 51 | }, nil 52 | } 53 | 54 | // 获取node详情 55 | func (n *node) GetNodeDetail(nodeName string) (node *corev1.Node, err error) { 56 | node, err = K8s.ClientSet.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{}) 57 | if err != nil { 58 | logger.Error(errors.New("获取Node详情失败, " + err.Error())) 59 | return nil, errors.New("获取Node详情失败, " + err.Error()) 60 | } 61 | 62 | return node, nil 63 | } 64 | 65 | func (n *node) toCells(std []corev1.Node) []DataCell { 66 | cells := make([]DataCell, len(std)) 67 | for i := range std { 68 | cells[i] = nodeCell(std[i]) 69 | } 70 | return cells 71 | } 72 | 73 | func (n *node) fromCells(cells []DataCell) []corev1.Node { 74 | nodes := make([]corev1.Node, len(cells)) 75 | for i := range cells { 76 | nodes[i] = corev1.Node(cells[i].(nodeCell)) 77 | } 78 | 79 | return nodes 80 | } 81 | -------------------------------------------------------------------------------- /service/pod.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "errors" 8 | "io" 9 | "k8s-platform/config" 10 | 11 | "github.com/wonderivan/logger" 12 | corev1 "k8s.io/api/core/v1" 13 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 | ) 15 | 16 | var Pod pod 17 | 18 | type pod struct{} 19 | 20 | // 定义列表的返回内容,Items是pod元素列表,Total是元素数量 21 | type PodsResp struct { 22 | Total int `json:"total"` 23 | Items []corev1.Pod `json:"items"` 24 | } 25 | 26 | type PodsNp struct { 27 | Namespace string 28 | PodNum int 29 | } 30 | 31 | // 获取pod列表,支持过滤、排序、分页 32 | func (p *pod) GetPods(filterName, namespace string, limit, page int) (podsResp *PodsResp, err error) { 33 | //context.TODO() 用于声明一个空的context上下文,用于List方法内设置这个请求超时 34 | //metav1.ListOptions{} 用于过滤List数据,如label,field等 35 | podList, err := K8s.ClientSet.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) 36 | if err != nil { 37 | logger.Info("获取Pod列表失败," + err.Error()) 38 | // 返回给上一层,最终返回给前端,前端捕获到后打印出来 39 | return nil, errors.New("获取Pod列表失败," + err.Error()) 40 | } 41 | // 实例化dataselector结构体,组装数据 42 | selectableData := &dataSelector{ 43 | GenericDataList: p.toCells(podList.Items), 44 | DataSelect: &DataSelectQuery{ 45 | Filter: &FilterQuery{Name: filterName}, 46 | Paginate: &PaginateQuery{ 47 | Limit: limit, 48 | Page: page, 49 | }, 50 | }, 51 | } 52 | // 先过滤 53 | filtered := selectableData.Filter() 54 | total := len(filtered.GenericDataList) 55 | // 排序和分页 56 | data := filtered.Sort().Paginate() 57 | 58 | // 将DataCell类型转成Pod 59 | pods := p.fromCells(data.GenericDataList) 60 | return &PodsResp{ 61 | Total: total, 62 | Items: pods, 63 | }, nil 64 | } 65 | 66 | // 获取pod详情 67 | func (p *pod) GetPodDetail(podName, namespace string) (pod *corev1.Pod, err error) { 68 | pod, err = K8s.ClientSet.CoreV1().Pods(namespace).Get(context.TODO(), podName, metav1.GetOptions{}) 69 | if err != nil { 70 | logger.Error("获取Pod详情失败," + err.Error()) 71 | return nil, errors.New("获取Pod详情失败," + err.Error()) 72 | } 73 | return pod, nil 74 | } 75 | 76 | // 删除Pod 77 | func (p *pod) DeletePod(podName, namespace string) (err error) { 78 | err = K8s.ClientSet.CoreV1().Pods(namespace).Delete(context.TODO(), podName, metav1.DeleteOptions{}) 79 | if err != nil { 80 | logger.Error("删除Pod详情失败," + err.Error()) 81 | return errors.New("删除Pod详情失败," + err.Error()) 82 | } 83 | return nil 84 | } 85 | 86 | // 更新Pod 87 | // func (p *pod) UpdatePod(podName string, namespace, content string) (err error) { 88 | func (p *pod) UpdatePod(namespace, content string) (err error) { 89 | var pod = &corev1.Pod{} 90 | // 反序列化为Pod对象 91 | err = json.Unmarshal([]byte(content), pod) 92 | if err != nil { 93 | logger.Error("反序列化失败," + err.Error()) 94 | return errors.New("反序列化失败" + err.Error()) 95 | } 96 | // 更新pod 97 | _, err = K8s.ClientSet.CoreV1().Pods(namespace).Update(context.TODO(), pod, metav1.UpdateOptions{}) 98 | if err != nil { 99 | logger.Error("更新Pod失败," + err.Error()) 100 | return errors.New("更新Pod失败," + err.Error()) 101 | } 102 | return nil 103 | } 104 | 105 | // 获取Pod中的容器名 106 | func (p *pod) GetPodContainer(podName string, namespace string) (containers []string, err error) { 107 | pod, err := p.GetPodDetail(podName, namespace) 108 | if err != nil { 109 | return nil, err 110 | } 111 | for _, container := range pod.Spec.Containers { 112 | containers = append(containers, container.Name) 113 | } 114 | return containers, nil 115 | } 116 | 117 | // 获取Pod内容器日志 118 | func (p *pod) GetPodLog(containerName, podName, namespace string) (log string, err error) { 119 | // 设置日志配置,容器名,获取内容的配置 120 | lineLimit := int64(config.PodLogTailLine) 121 | option := &corev1.PodLogOptions{ 122 | Container: containerName, 123 | TailLines: &lineLimit, 124 | } 125 | // 获取一个request实例 126 | req := K8s.ClientSet.CoreV1().Pods(namespace).GetLogs(podName, option) 127 | // 发起stream连接,获取到Response.body 128 | podLogs, err := req.Stream(context.TODO()) 129 | if err != nil { 130 | logger.Error("更新Pod失败," + err.Error()) 131 | return "", errors.New("更新Pod失败," + err.Error()) 132 | } 133 | defer podLogs.Close() 134 | // 将Response.body写入到缓存区,目的为了转换成string类型 135 | buf := new(bytes.Buffer) 136 | _, err = io.Copy(buf, podLogs) 137 | if err != nil { 138 | logger.Error("复制podLog失败," + err.Error()) 139 | return "", errors.New("复制podLog失败," + err.Error()) 140 | } 141 | return buf.String(), nil 142 | } 143 | 144 | // 获取每个namespace中pod数量 145 | func (p *pod) GetPodNumPerNp() (podsNps []*PodsNp, err error) { 146 | // 获取namespace列表 147 | namespaceList, err := K8s.ClientSet.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{}) 148 | if err != nil { 149 | return nil, err 150 | } 151 | for _, namespace := range namespaceList.Items { 152 | // 获取pod列表 153 | podList, err := K8s.ClientSet.CoreV1().Pods(namespace.Name).List(context.TODO(), metav1.ListOptions{}) 154 | if err != nil { 155 | return nil, err 156 | } 157 | // 组装数据 158 | podsNp := &PodsNp{ 159 | Namespace: namespace.Name, 160 | PodNum: len(podList.Items), 161 | } 162 | // 添加到podsNps数组中 163 | podsNps = append(podsNps, podsNp) 164 | } 165 | return podsNps, nil 166 | } 167 | 168 | // 类型转换方法corev1.Pod --> DataCell,DataCell-->corev1.Pod 169 | func (p *pod) toCells(pods []corev1.Pod) []DataCell { 170 | cells := make([]DataCell, len(pods)) 171 | for i := range pods { 172 | cells[i] = podCell(pods[i]) 173 | } 174 | return cells 175 | } 176 | 177 | func (p *pod) fromCells(cells []DataCell) []corev1.Pod { 178 | pods := make([]corev1.Pod, len(cells)) 179 | for i := range cells { 180 | // 是将DataCell类型转成podCell 181 | pods[i] = corev1.Pod(cells[i].(podCell)) 182 | } 183 | return pods 184 | } 185 | -------------------------------------------------------------------------------- /service/pv.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | 7 | "github.com/wonderivan/logger" 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | ) 11 | 12 | var Pv pv 13 | 14 | type pv struct{} 15 | 16 | type PvsResp struct { 17 | Items []corev1.PersistentVolume `json:"items"` 18 | Total int `json:"total"` 19 | } 20 | 21 | // 获取pv列表,支持过滤、排序、分页 22 | func (p *pv) GetPvs(filterName string, limit, page int) (pvsResp *PvsResp, err error) { 23 | //获取pvList类型的pv列表 24 | pvList, err := K8s.ClientSet.CoreV1().PersistentVolumes().List(context.TODO(), metav1.ListOptions{}) 25 | if err != nil { 26 | logger.Error(errors.New("获取Pv列表失败, " + err.Error())) 27 | return nil, errors.New("获取Pv列表失败, " + err.Error()) 28 | } 29 | //将pvList中的pv列表(Items),放进dataselector对象中,进行排序 30 | selectableData := &dataSelector{ 31 | GenericDataList: p.toCells(pvList.Items), 32 | DataSelect: &DataSelectQuery{ 33 | Filter: &FilterQuery{Name: filterName}, 34 | Paginate: &PaginateQuery{ 35 | Limit: limit, 36 | Page: page, 37 | }, 38 | }, 39 | } 40 | 41 | filtered := selectableData.Filter() 42 | total := len(filtered.GenericDataList) 43 | data := filtered.Sort().Paginate() 44 | 45 | //将[]DataCell类型的pv列表转为v1.pv列表 46 | pvs := p.fromCells(data.GenericDataList) 47 | 48 | return &PvsResp{ 49 | Items: pvs, 50 | Total: total, 51 | }, nil 52 | } 53 | 54 | // 获取pv详情 55 | func (p *pv) GetPvDetail(pvName string) (pv *corev1.PersistentVolume, err error) { 56 | pv, err = K8s.ClientSet.CoreV1().PersistentVolumes().Get(context.TODO(), pvName, metav1.GetOptions{}) 57 | if err != nil { 58 | logger.Error(errors.New("获取Pv详情失败, " + err.Error())) 59 | return nil, errors.New("获取Pv详情失败, " + err.Error()) 60 | } 61 | 62 | return pv, nil 63 | } 64 | 65 | // 删除pv 66 | func (p *pv) DeletePv(pvName string) (err error) { 67 | err = K8s.ClientSet.CoreV1().PersistentVolumes().Delete(context.TODO(), pvName, metav1.DeleteOptions{}) 68 | if err != nil { 69 | logger.Error(errors.New("删除Pv失败, " + err.Error())) 70 | return errors.New("删除Pv失败, " + err.Error()) 71 | } 72 | 73 | return nil 74 | } 75 | 76 | func (p *pv) toCells(std []corev1.PersistentVolume) []DataCell { 77 | cells := make([]DataCell, len(std)) 78 | for i := range std { 79 | cells[i] = pvCell(std[i]) 80 | } 81 | return cells 82 | } 83 | 84 | func (p *pv) fromCells(cells []DataCell) []corev1.PersistentVolume { 85 | pvs := make([]corev1.PersistentVolume, len(cells)) 86 | for i := range cells { 87 | pvs[i] = corev1.PersistentVolume(cells[i].(pvCell)) 88 | } 89 | 90 | return pvs 91 | } 92 | -------------------------------------------------------------------------------- /service/pvc.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/wonderivan/logger" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | var Pvc pvc 14 | 15 | type pvc struct{} 16 | 17 | type PvcsResp struct { 18 | Items []corev1.PersistentVolumeClaim `json:"items"` 19 | Total int `json:"total"` 20 | } 21 | 22 | // 获取pvc列表,支持过滤、排序、分页 23 | func (p *pvc) GetPvcs(filterName, namespace string, limit, page int) (pvcsResp *PvcsResp, err error) { 24 | //获取pvcList类型的pvc列表 25 | pvcList, err := K8s.ClientSet.CoreV1().PersistentVolumeClaims(namespace).List(context.TODO(), metav1.ListOptions{}) 26 | if err != nil { 27 | logger.Error(errors.New("获取Pvc列表失败, " + err.Error())) 28 | return nil, errors.New("获取Pvc列表失败, " + err.Error()) 29 | } 30 | //将pvcList中的pvc列表(Items),放进dataselector对象中,进行排序 31 | selectableData := &dataSelector{ 32 | GenericDataList: p.toCells(pvcList.Items), 33 | DataSelect: &DataSelectQuery{ 34 | Filter: &FilterQuery{Name: filterName}, 35 | Paginate: &PaginateQuery{ 36 | Limit: limit, 37 | Page: page, 38 | }, 39 | }, 40 | } 41 | 42 | filtered := selectableData.Filter() 43 | total := len(filtered.GenericDataList) 44 | data := filtered.Sort().Paginate() 45 | 46 | //将[]DataCell类型的pvc列表转为v1.pvc列表 47 | pvcs := p.fromCells(data.GenericDataList) 48 | 49 | return &PvcsResp{ 50 | Items: pvcs, 51 | Total: total, 52 | }, nil 53 | } 54 | 55 | // 获取pvc详情 56 | func (p *pvc) GetPvcDetail(pvcName, namespace string) (pvc *corev1.PersistentVolumeClaim, err error) { 57 | pvc, err = K8s.ClientSet.CoreV1().PersistentVolumeClaims(namespace).Get(context.TODO(), pvcName, metav1.GetOptions{}) 58 | if err != nil { 59 | logger.Error(errors.New("获取Pvc详情失败, " + err.Error())) 60 | return nil, errors.New("获取Pvc详情失败, " + err.Error()) 61 | } 62 | 63 | return pvc, nil 64 | } 65 | 66 | // 删除pvc 67 | func (p *pvc) DeletePvc(pvcName, namespace string) (err error) { 68 | err = K8s.ClientSet.CoreV1().PersistentVolumeClaims(namespace).Delete(context.TODO(), pvcName, metav1.DeleteOptions{}) 69 | if err != nil { 70 | logger.Error(errors.New("删除Pvc失败, " + err.Error())) 71 | return errors.New("删除Pvc失败, " + err.Error()) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | // 更新pvc 78 | func (p *pvc) UpdatePvc(namespace, content string) (err error) { 79 | var pvc = &corev1.PersistentVolumeClaim{} 80 | 81 | err = json.Unmarshal([]byte(content), pvc) 82 | if err != nil { 83 | logger.Error(errors.New("反序列化失败, " + err.Error())) 84 | return errors.New("反序列化失败, " + err.Error()) 85 | } 86 | 87 | _, err = K8s.ClientSet.CoreV1().PersistentVolumeClaims(namespace).Update(context.TODO(), pvc, metav1.UpdateOptions{}) 88 | if err != nil { 89 | logger.Error(errors.New("更新Pvc失败, " + err.Error())) 90 | return errors.New("更新Pvc失败, " + err.Error()) 91 | } 92 | return nil 93 | } 94 | 95 | func (p *pvc) toCells(std []corev1.PersistentVolumeClaim) []DataCell { 96 | cells := make([]DataCell, len(std)) 97 | for i := range std { 98 | cells[i] = pvcCell(std[i]) 99 | } 100 | return cells 101 | } 102 | 103 | func (p *pvc) fromCells(cells []DataCell) []corev1.PersistentVolumeClaim { 104 | pvcs := make([]corev1.PersistentVolumeClaim, len(cells)) 105 | for i := range cells { 106 | pvcs[i] = corev1.PersistentVolumeClaim(cells[i].(pvcCell)) 107 | } 108 | 109 | return pvcs 110 | } 111 | -------------------------------------------------------------------------------- /service/secret.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/wonderivan/logger" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | var Secret secret 14 | 15 | type secret struct{} 16 | 17 | type SecretsResp struct { 18 | Items []corev1.Secret `json:"items"` 19 | Total int `json:"total"` 20 | } 21 | 22 | // 获取secret列表,支持过滤、排序、分页 23 | func (s *secret) GetSecrets(filterName, namespace string, limit, page int) (secretsResp *SecretsResp, err error) { 24 | //获取secretList类型的secret列表 25 | secretList, err := K8s.ClientSet.CoreV1().Secrets(namespace).List(context.TODO(), metav1.ListOptions{}) 26 | if err != nil { 27 | logger.Error(errors.New("获取Secret列表失败, " + err.Error())) 28 | return nil, errors.New("获取Secret列表失败, " + err.Error()) 29 | } 30 | //将secretList中的secret列表(Items),放进dataselector对象中,进行排序 31 | selectableData := &dataSelector{ 32 | GenericDataList: s.toCells(secretList.Items), 33 | DataSelect: &DataSelectQuery{ 34 | Filter: &FilterQuery{Name: filterName}, 35 | Paginate: &PaginateQuery{ 36 | Limit: limit, 37 | Page: page, 38 | }, 39 | }, 40 | } 41 | 42 | filtered := selectableData.Filter() 43 | total := len(filtered.GenericDataList) 44 | data := filtered.Sort().Paginate() 45 | 46 | //将[]DataCell类型的secret列表转为v1.secret列表 47 | secrets := s.fromCells(data.GenericDataList) 48 | 49 | return &SecretsResp{ 50 | Items: secrets, 51 | Total: total, 52 | }, nil 53 | } 54 | 55 | // 获取secret详情 56 | func (s *secret) GetSecretDetail(secretName, namespace string) (secret *corev1.Secret, err error) { 57 | secret, err = K8s.ClientSet.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{}) 58 | if err != nil { 59 | logger.Error(errors.New("获取Secret详情失败, " + err.Error())) 60 | return nil, errors.New("获取Secret详情失败, " + err.Error()) 61 | } 62 | 63 | return secret, nil 64 | } 65 | 66 | // 删除secret 67 | func (s *secret) DeleteSecret(secretName, namespace string) (err error) { 68 | err = K8s.ClientSet.CoreV1().Secrets(namespace).Delete(context.TODO(), secretName, metav1.DeleteOptions{}) 69 | if err != nil { 70 | logger.Error(errors.New("删除Secret失败, " + err.Error())) 71 | return errors.New("删除Secret失败, " + err.Error()) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | // 更新secret 78 | func (s *secret) UpdateSecret(namespace, content string) (err error) { 79 | var secret = &corev1.Secret{} 80 | 81 | err = json.Unmarshal([]byte(content), secret) 82 | if err != nil { 83 | logger.Error(errors.New("反序列化失败, " + err.Error())) 84 | return errors.New("反序列化失败, " + err.Error()) 85 | } 86 | 87 | _, err = K8s.ClientSet.CoreV1().Secrets(namespace).Update(context.TODO(), secret, metav1.UpdateOptions{}) 88 | if err != nil { 89 | logger.Error(errors.New("更新Secret失败, " + err.Error())) 90 | return errors.New("更新Secret失败, " + err.Error()) 91 | } 92 | return nil 93 | } 94 | 95 | func (s *secret) toCells(std []corev1.Secret) []DataCell { 96 | cells := make([]DataCell, len(std)) 97 | for i := range std { 98 | cells[i] = secretCell(std[i]) 99 | } 100 | return cells 101 | } 102 | 103 | func (s *secret) fromCells(cells []DataCell) []corev1.Secret { 104 | secrets := make([]corev1.Secret, len(cells)) 105 | for i := range cells { 106 | secrets[i] = corev1.Secret(cells[i].(secretCell)) 107 | } 108 | 109 | return secrets 110 | } 111 | -------------------------------------------------------------------------------- /service/service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/wonderivan/logger" 9 | corev1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/util/intstr" 12 | ) 13 | 14 | var Servicev1 servicev1 15 | 16 | type servicev1 struct{} 17 | 18 | type ServicesResp struct { 19 | Items []corev1.Service `json:"items"` 20 | Total int `json:"total"` 21 | } 22 | 23 | type ServiceCreate struct { 24 | Name string `json:"name"` 25 | Namespace string `json:"namespace"` 26 | Type string `json:"type"` 27 | ContainerPort int32 `json:"container_port"` 28 | Port int32 `json:"port"` 29 | NodePort int32 `json:"node_port"` 30 | Label map[string]string `json:"label"` 31 | } 32 | 33 | // 获取service列表,支持过滤、排序、分页 34 | func (s *servicev1) GetServices(filterName, namespace string, limit, page int) (servicesResp *ServicesResp, err error) { 35 | //获取serviceList类型的service列表 36 | serviceList, err := K8s.ClientSet.CoreV1().Services(namespace).List(context.TODO(), metav1.ListOptions{}) 37 | if err != nil { 38 | logger.Error(errors.New("获取Service列表失败, " + err.Error())) 39 | return nil, errors.New("获取Service列表失败, " + err.Error()) 40 | } 41 | //将serviceList中的service列表(Items),放进dataselector对象中,进行排序 42 | selectableData := &dataSelector{ 43 | GenericDataList: s.toCells(serviceList.Items), 44 | DataSelect: &DataSelectQuery{ 45 | Filter: &FilterQuery{Name: filterName}, 46 | Paginate: &PaginateQuery{ 47 | Limit: limit, 48 | Page: page, 49 | }, 50 | }, 51 | } 52 | 53 | filtered := selectableData.Filter() 54 | total := len(filtered.GenericDataList) 55 | data := filtered.Sort().Paginate() 56 | 57 | //将[]DataCell类型的service列表转为v1.service列表 58 | services := s.fromCells(data.GenericDataList) 59 | 60 | return &ServicesResp{ 61 | Items: services, 62 | Total: total, 63 | }, nil 64 | } 65 | 66 | // 获取service详情 67 | func (s *servicev1) GetServicetDetail(serviceName, namespace string) (service *corev1.Service, err error) { 68 | service, err = K8s.ClientSet.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) 69 | if err != nil { 70 | logger.Error(errors.New("获取Service详情失败, " + err.Error())) 71 | return nil, errors.New("获取Service详情失败, " + err.Error()) 72 | } 73 | 74 | return service, nil 75 | } 76 | 77 | // 创建service,,接收ServiceCreate对象 78 | func (s *servicev1) CreateService(data *ServiceCreate) (err error) { 79 | //将data中的数据组装成corev1.Service对象 80 | service := &corev1.Service{ 81 | //ObjectMeta中定义资源名、命名空间以及标签 82 | ObjectMeta: metav1.ObjectMeta{ 83 | Name: data.Name, 84 | Namespace: data.Namespace, 85 | Labels: data.Label, 86 | }, 87 | //Spec中定义类型,端口,选择器 88 | Spec: corev1.ServiceSpec{ 89 | Type: corev1.ServiceType(data.Type), 90 | Ports: []corev1.ServicePort{ 91 | { 92 | Name: "http", 93 | Port: data.Port, 94 | Protocol: "TCP", 95 | TargetPort: intstr.IntOrString{ 96 | Type: 0, 97 | IntVal: data.ContainerPort, 98 | }, 99 | }, 100 | }, 101 | Selector: data.Label, 102 | }, 103 | } 104 | //默认ClusterIP,这里是判断NodePort,添加配置 105 | if data.NodePort != 0 && data.Type == "NodePort" { 106 | service.Spec.Ports[0].NodePort = data.NodePort 107 | } 108 | //创建Service 109 | _, err = K8s.ClientSet.CoreV1().Services(data.Namespace).Create(context.TODO(), service, metav1.CreateOptions{}) 110 | if err != nil { 111 | logger.Error(errors.New("创建Service失败, " + err.Error())) 112 | return errors.New("创建Service失败, " + err.Error()) 113 | } 114 | 115 | return nil 116 | } 117 | 118 | // 删除service 119 | func (s *servicev1) DeleteService(serviceName, namespace string) (err error) { 120 | err = K8s.ClientSet.CoreV1().Services(namespace).Delete(context.TODO(), serviceName, metav1.DeleteOptions{}) 121 | if err != nil { 122 | logger.Error(errors.New("删除Service失败, " + err.Error())) 123 | return errors.New("删除Service失败, " + err.Error()) 124 | } 125 | 126 | return nil 127 | } 128 | 129 | // 更新service 130 | func (s *servicev1) UpdateService(namespace, content string) (err error) { 131 | var service = &corev1.Service{} 132 | 133 | err = json.Unmarshal([]byte(content), service) 134 | if err != nil { 135 | logger.Error(errors.New("反序列化失败, " + err.Error())) 136 | return errors.New("反序列化失败, " + err.Error()) 137 | } 138 | 139 | _, err = K8s.ClientSet.CoreV1().Services(namespace).Update(context.TODO(), service, metav1.UpdateOptions{}) 140 | if err != nil { 141 | logger.Error(errors.New("更新service失败, " + err.Error())) 142 | return errors.New("更新service失败, " + err.Error()) 143 | } 144 | return nil 145 | } 146 | 147 | func (s *servicev1) toCells(std []corev1.Service) []DataCell { 148 | cells := make([]DataCell, len(std)) 149 | for i := range std { 150 | cells[i] = serviceCell(std[i]) 151 | } 152 | return cells 153 | } 154 | 155 | func (s *servicev1) fromCells(cells []DataCell) []corev1.Service { 156 | services := make([]corev1.Service, len(cells)) 157 | for i := range cells { 158 | services[i] = corev1.Service(cells[i].(serviceCell)) 159 | } 160 | 161 | return services 162 | } 163 | -------------------------------------------------------------------------------- /service/statefulset.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | 8 | "github.com/wonderivan/logger" 9 | appsv1 "k8s.io/api/apps/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | ) 12 | 13 | var StatefulSet statefulSet 14 | 15 | type statefulSet struct{} 16 | 17 | type StatusfulSetsResp struct { 18 | Items []appsv1.StatefulSet `json:"items"` 19 | Total int `json:"total"` 20 | } 21 | 22 | // 获取statefulset列表,支持过滤、排序、分页 23 | func (s *statefulSet) GetStatefulSets(filterName, namespace string, limit, page int) (statusfulSetsResp *StatusfulSetsResp, err error) { 24 | //获取statefulSetList类型的statefulSet列表 25 | statefulSetList, err := K8s.ClientSet.AppsV1().StatefulSets(namespace).List(context.TODO(), metav1.ListOptions{}) 26 | if err != nil { 27 | logger.Error(errors.New("获取StatefulSet列表失败, " + err.Error())) 28 | return nil, errors.New("获取StatefulSet列表失败, " + err.Error()) 29 | } 30 | //将statefulSetList中的StatefulSet列表(Items),放进dataselector对象中,进行排序 31 | selectableData := &dataSelector{ 32 | GenericDataList: s.toCells(statefulSetList.Items), 33 | DataSelect: &DataSelectQuery{ 34 | Filter: &FilterQuery{Name: filterName}, 35 | Paginate: &PaginateQuery{ 36 | Limit: limit, 37 | Page: page, 38 | }, 39 | }, 40 | } 41 | 42 | filtered := selectableData.Filter() 43 | total := len(filtered.GenericDataList) 44 | data := filtered.Sort().Paginate() 45 | 46 | //将[]DataCell类型的statefulset列表转为v1.statefulset列表 47 | statefulSets := s.fromCells(data.GenericDataList) 48 | 49 | return &StatusfulSetsResp{ 50 | Items: statefulSets, 51 | Total: total, 52 | }, nil 53 | } 54 | 55 | // 获取statefulset详情 56 | func (s *statefulSet) GetStatefulSetDetail(statefulSetName, namespace string) (statefulSet *appsv1.StatefulSet, err error) { 57 | statefulSet, err = K8s.ClientSet.AppsV1().StatefulSets(namespace).Get(context.TODO(), statefulSetName, metav1.GetOptions{}) 58 | if err != nil { 59 | logger.Error(errors.New("获取StatefulSet详情失败, " + err.Error())) 60 | return nil, errors.New("获取StatefulSet详情失败, " + err.Error()) 61 | } 62 | 63 | return statefulSet, nil 64 | } 65 | 66 | // 删除statefulset 67 | func (s *statefulSet) DeleteStatefulSet(statefulSetName, namespace string) (err error) { 68 | err = K8s.ClientSet.AppsV1().StatefulSets(namespace).Delete(context.TODO(), statefulSetName, metav1.DeleteOptions{}) 69 | if err != nil { 70 | logger.Error(errors.New("删除StatefulSet失败, " + err.Error())) 71 | return errors.New("删除StatefulSet失败, " + err.Error()) 72 | } 73 | 74 | return nil 75 | } 76 | 77 | // 更新statefulset 78 | func (s *statefulSet) UpdateStatefulSet(namespace, content string) (err error) { 79 | var statefulSet = &appsv1.StatefulSet{} 80 | 81 | err = json.Unmarshal([]byte(content), statefulSet) 82 | if err != nil { 83 | logger.Error(errors.New("反序列化失败, " + err.Error())) 84 | return errors.New("反序列化失败, " + err.Error()) 85 | } 86 | 87 | _, err = K8s.ClientSet.AppsV1().StatefulSets(namespace).Update(context.TODO(), statefulSet, metav1.UpdateOptions{}) 88 | if err != nil { 89 | logger.Error(errors.New("更新StatefulSet失败, " + err.Error())) 90 | return errors.New("更新StatefulSet失败, " + err.Error()) 91 | } 92 | return nil 93 | } 94 | 95 | func (s *statefulSet) toCells(std []appsv1.StatefulSet) []DataCell { 96 | cells := make([]DataCell, len(std)) 97 | for i := range std { 98 | cells[i] = statefulSetCell(std[i]) 99 | } 100 | return cells 101 | } 102 | 103 | func (s *statefulSet) fromCells(cells []DataCell) []appsv1.StatefulSet { 104 | statefulSets := make([]appsv1.StatefulSet, len(cells)) 105 | for i := range cells { 106 | statefulSets[i] = appsv1.StatefulSet(cells[i].(statefulSetCell)) 107 | } 108 | 109 | return statefulSets 110 | } 111 | -------------------------------------------------------------------------------- /service/terminal.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "k8s-platform/config" 8 | "log" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/gorilla/websocket" 13 | "github.com/wonderivan/logger" 14 | v1 "k8s.io/api/core/v1" 15 | "k8s.io/client-go/kubernetes/scheme" 16 | "k8s.io/client-go/tools/clientcmd" 17 | "k8s.io/client-go/tools/remotecommand" 18 | ) 19 | 20 | var Terminal terminal 21 | 22 | type terminal struct{} 23 | 24 | // wshanlder 25 | func (t *terminal) WsHandler(w http.ResponseWriter, r *http.Request) { 26 | //加载k8s配置 27 | conf, err := clientcmd.BuildConfigFromFlags("", config.KubeConfig) 28 | if err != nil { 29 | logger.Error("加载k8s配置失败, " + err.Error()) 30 | return 31 | } 32 | //解析form入参,获取namespace,pod,container参数 33 | if err := r.ParseForm(); err != nil { 34 | logger.Error("解析参数失败, " + err.Error()) 35 | return 36 | } 37 | namespace := r.Form.Get("namespace") 38 | podName := r.Form.Get("pod_name") 39 | containerName := r.Form.Get("container_name") 40 | logger.Info("exec pod: %s, container: %s, namespace: %s\n", podName, containerName, namespace) 41 | 42 | //new一个terminalsession 43 | pty, err := NewTerminalSession(w, r, nil) 44 | if err != nil { 45 | logger.Error("实例化TerminalSession失败, " + err.Error()) 46 | return 47 | } 48 | //处理关闭 49 | defer func() { 50 | logger.Info("关闭TerminalSession") 51 | pty.Close() 52 | }() 53 | //组装post请求 54 | req := K8s.ClientSet.CoreV1().RESTClient().Post(). 55 | Resource("pods"). 56 | Name(podName). 57 | Namespace(namespace). 58 | SubResource("exec"). 59 | VersionedParams(&v1.PodExecOptions{ 60 | Stdin: true, 61 | Stdout: true, 62 | Stderr: true, 63 | TTY: true, 64 | Container: containerName, 65 | Command: []string{"/bin/bash"}, 66 | }, scheme.ParameterCodec) 67 | logger.Info("exec post request url: ", req) 68 | 69 | //升级SPDY协议 70 | executor, err := remotecommand.NewSPDYExecutor(conf, "POST", req.URL()) 71 | if err != nil { 72 | logger.Error("建立SPDY连接失败, " + err.Error()) 73 | return 74 | } 75 | //与kubelet建立stream连接 76 | err = executor.Stream(remotecommand.StreamOptions{ 77 | Stdin: pty, 78 | Stdout: pty, 79 | Stderr: pty, 80 | Tty: true, 81 | TerminalSizeQueue: pty, 82 | }) 83 | 84 | if err != nil { 85 | logger.Error("执行 pod 命令失败, " + err.Error()) 86 | //将报错返回给web端 87 | pty.Write([]byte("执行 pod 命令失败, " + err.Error())) 88 | //标记关闭 89 | pty.Done() 90 | } 91 | } 92 | 93 | // 消息内容 94 | type terminalMessage struct { 95 | Operation string `json:"operation"` 96 | Data string `json:"data"` 97 | Rows uint16 `json:"rows"` 98 | Cols uint16 `json:"cols"` 99 | } 100 | 101 | // 交互的结构体,接管输入和输出 102 | type TerminalSession struct { 103 | wsConn *websocket.Conn 104 | sizeChan chan remotecommand.TerminalSize 105 | doneChan chan struct{} 106 | } 107 | 108 | // 初始化一个websocket.Upgrader类型的对象,用于http协议升级为ws协议 109 | var upgrader = func() websocket.Upgrader { 110 | upgrader := websocket.Upgrader{} 111 | upgrader.HandshakeTimeout = time.Second * 2 112 | upgrader.CheckOrigin = func(r *http.Request) bool { 113 | return true 114 | } 115 | return upgrader 116 | }() 117 | 118 | // 创建TerminalSession类型的对象并返回 119 | func NewTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*TerminalSession, error) { 120 | //升级ws协议 121 | conn, err := upgrader.Upgrade(w, r, responseHeader) 122 | if err != nil { 123 | return nil, errors.New("升级websocket失败," + err.Error()) 124 | } 125 | //new 126 | session := &TerminalSession{ 127 | wsConn: conn, 128 | sizeChan: make(chan remotecommand.TerminalSize), 129 | doneChan: make(chan struct{}), 130 | } 131 | 132 | return session, nil 133 | } 134 | 135 | // 读数据的方法 136 | // 返回值int是读成功了多少数据 137 | func (t *TerminalSession) Read(p []byte) (int, error) { 138 | //从ws中读取消息 139 | _, message, err := t.wsConn.ReadMessage() 140 | if err != nil { 141 | log.Printf("读取消息错误: %v", err) 142 | return 0, err 143 | } 144 | //反序列化 145 | var msg terminalMessage 146 | if err := json.Unmarshal(message, &msg); err != nil { 147 | log.Printf("读取消息语法错误: %v", err) 148 | return 0, err 149 | } 150 | //逻辑判断 151 | switch msg.Operation { 152 | case "stdin": 153 | return copy(p, msg.Data), nil 154 | case "resize": 155 | t.sizeChan <- remotecommand.TerminalSize{Width: msg.Cols, Height: msg.Rows} 156 | return 0, nil 157 | case "ping": 158 | return 0, nil 159 | default: 160 | log.Printf("消息类型错误'%s'", msg.Operation) 161 | return 0, fmt.Errorf("消息类型错误'%s'", msg.Operation) 162 | } 163 | } 164 | 165 | // 写数据的方法,拿到apiserver的返回内容,向web端输出 166 | func (t *TerminalSession) Write(p []byte) (int, error) { 167 | msg, err := json.Marshal(terminalMessage{ 168 | Operation: "stdout", 169 | Data: string(p), 170 | }) 171 | if err != nil { 172 | log.Printf("写消息语法错误: %v", err) 173 | return 0, err 174 | } 175 | if err := t.wsConn.WriteMessage(websocket.TextMessage, msg); err != nil { 176 | log.Printf("写消息错误: %v", err) 177 | return 0, err 178 | } 179 | 180 | return len(p), nil 181 | } 182 | 183 | // 标记关闭的方法 184 | func (t *TerminalSession) Done() { 185 | close(t.doneChan) 186 | } 187 | 188 | // 关闭的方法 189 | func (t *TerminalSession) Close() { 190 | t.wsConn.Close() 191 | } 192 | 193 | // resize方法,以及是否退出终端 194 | func (t *TerminalSession) Next() *remotecommand.TerminalSize { 195 | select { 196 | case size := <-t.sizeChan: 197 | return &size 198 | case <-t.doneChan: 199 | return nil 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /service/workflow.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "k8s-platform/dao" 5 | "k8s-platform/model" 6 | ) 7 | 8 | var Workflow workflow 9 | 10 | type workflow struct{} 11 | 12 | //定义workflowCreate类型 13 | type WorkflowCreate struct { 14 | Name string `json:"name"` 15 | Namespace string `json:"namespace"` 16 | Replicas int32 `json:"replicas"` 17 | Image string `json:"image"` 18 | Label map[string]string `json:"label"` 19 | Cpu string `json:"cpu"` 20 | Memory string `json:"memory"` 21 | ContainerPort int32 `json:"container_port"` 22 | HealthCheck bool `json:"health_check"` 23 | HealthPath string `json:"health_path"` 24 | Type string `json:"type"` 25 | Port int32 `json:"port"` 26 | NodePort int32 `json:"node_port"` 27 | Hosts map[string][]*HttpPath `json:"hosts"` 28 | } 29 | 30 | //获取列表分页查询 31 | func (w *workflow) GetList(name, namespace string, page, limit int) (data *dao.WorkflowResp, err error) { 32 | data, err = dao.Workflow.GetWorkflows(name, namespace, page, limit) 33 | if err != nil { 34 | return nil, err 35 | } 36 | return data, nil 37 | } 38 | 39 | //查询workflow单条数据 40 | func (w *workflow) GetById(id int) (data *model.Workflow, err error) { 41 | data, err = dao.Workflow.GetById(id) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return data, nil 46 | } 47 | 48 | //创建workflow 49 | func (w *workflow) CreateWorkFlow(data *WorkflowCreate) (err error) { 50 | //定义ingress名字 51 | var ingressName string 52 | if data.Type == "Ingress" { 53 | ingressName = getIngressName(data.Name) 54 | } else { 55 | ingressName = "" 56 | } 57 | 58 | //workflow数据落库 59 | workflow := &model.Workflow{ 60 | Name: data.Name, 61 | Namespace: data.Namespace, 62 | Replicas: data.Replicas, 63 | Deployment: data.Name, 64 | Service: getServiceName(data.Name), 65 | Ingress: ingressName, 66 | Type: data.Type, 67 | } 68 | err = dao.Workflow.Add(workflow) 69 | if err != nil { 70 | return err 71 | } 72 | //创建k8s资源 73 | err = createWorkflowRes(data) 74 | if err != nil { 75 | return err 76 | } 77 | 78 | return err 79 | } 80 | 81 | //删除workflow 82 | func (w *workflow) DelById(id int) (err error) { 83 | //获取数据库数据 84 | workflow, err := dao.Workflow.GetById(id) 85 | if err != nil { 86 | return err 87 | } 88 | //删除k8s资源 89 | err = delWorkflowRes(workflow) 90 | if err != nil { 91 | return err 92 | } 93 | //删除数据库数据 94 | err = dao.Workflow.DelById(id) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | return 100 | } 101 | 102 | //删除k8s资源 deployment service ingress 103 | func delWorkflowRes(workflow *model.Workflow) (err error) { 104 | err = Deployment.DeleteDeployment(workflow.Name, workflow.Namespace) 105 | if err != nil { 106 | return err 107 | } 108 | err = Servicev1.DeleteService(getServiceName(workflow.Name), workflow.Namespace) 109 | if err != nil { 110 | return err 111 | } 112 | 113 | if workflow.Type == "Ingress" { 114 | err = Ingress.DeleteIngress(getIngressName(workflow.Name), workflow.Namespace) 115 | if err != nil { 116 | return err 117 | } 118 | } 119 | 120 | return nil 121 | } 122 | 123 | //创建k8s资源 deployment service ingress 124 | func createWorkflowRes(data *WorkflowCreate) (err error) { 125 | 126 | //创建deployment 127 | dc := &DeployCreate{ 128 | Name: data.Name, 129 | Namespace: data.Namespace, 130 | Replicas: data.Replicas, 131 | Image: data.Image, 132 | Label: data.Label, 133 | Cpu: data.Cpu, 134 | Memory: data.Memory, 135 | ContainerPort: data.ContainerPort, 136 | HealthCheck: data.HealthCheck, 137 | HealthPath: data.HealthPath, 138 | } 139 | err = Deployment.CreateDeployment(dc) 140 | if err != nil { 141 | return err 142 | } 143 | var serviceType string 144 | if data.Type != "Ingress" { 145 | serviceType = data.Type 146 | } else { 147 | serviceType = "ClusterIP" 148 | } 149 | //创建service 150 | sc := &ServiceCreate{ 151 | Name: getServiceName(data.Name), 152 | Namespace: data.Namespace, 153 | Type: serviceType, 154 | ContainerPort: data.ContainerPort, 155 | Port: data.Port, 156 | NodePort: data.NodePort, 157 | Label: data.Label, 158 | } 159 | if err := Servicev1.CreateService(sc); err != nil { 160 | return err 161 | } 162 | //创建ingress 163 | var ic *IngressCreate 164 | if data.Type == "Ingress" { 165 | ic = &IngressCreate{ 166 | Name: getIngressName(data.Name), 167 | Namespace: data.Namespace, 168 | Label: data.Label, 169 | Hosts: data.Hosts, 170 | } 171 | err = Ingress.CreateIngress(ic) 172 | if err != nil { 173 | return err 174 | } 175 | } 176 | 177 | return nil 178 | } 179 | 180 | //workflow名字转换成service名字,添加-svc后缀 181 | func getServiceName(workflowName string) (serviceName string) { 182 | return workflowName + "-svc" 183 | } 184 | 185 | //workflow名字转换成ingress名字,添加-ing后缀 186 | func getIngressName(workflowName string) (ingressName string) { 187 | return workflowName + "-ing" 188 | } 189 | -------------------------------------------------------------------------------- /utils/jwt.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/dgrijalva/jwt-go" 7 | "github.com/wonderivan/logger" 8 | ) 9 | 10 | var JWTToken jwtToken 11 | 12 | type jwtToken struct{} 13 | 14 | //定义token中携带的信息 15 | type CustomClaims struct { 16 | Username string `json:"username"` 17 | Password string `json:"password"` 18 | jwt.StandardClaims 19 | } 20 | 21 | //加解密因子 22 | const ( 23 | SECRET = "adoodevops" 24 | ) 25 | 26 | //解析Token 27 | func (*jwtToken) ParseToken(tokenString string) (claims *CustomClaims, err error) { 28 | token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { 29 | return []byte(SECRET), nil 30 | }) 31 | if err != nil { 32 | logger.Error("parse token failed ", err) 33 | //处理token解析后的各种错误 34 | if ve, ok := err.(*jwt.ValidationError); ok { 35 | if ve.Errors&jwt.ValidationErrorMalformed != 0 { 36 | return nil, errors.New("TokenMalformed") 37 | } else if ve.Errors&jwt.ValidationErrorExpired != 0 { 38 | return nil, errors.New("TokenExpired") 39 | } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { 40 | return nil, errors.New("TokenNotValidYet") 41 | } else { 42 | return nil, errors.New("TokenInvalid") 43 | } 44 | } 45 | } 46 | 47 | if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid { 48 | return claims, nil 49 | } 50 | return nil, errors.New("解析Token失败") 51 | } 52 | --------------------------------------------------------------------------------