diff --git a/cmd/check.go b/cmd/check.go new file mode 100644 index 0000000..14c9cdc --- /dev/null +++ b/cmd/check.go @@ -0,0 +1,149 @@ +package cmd + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "git.apinb.com/bsm-sdk/engine/env" + "git.apinb.com/bsm-sdk/engine/utils" + "github.com/spf13/cobra" +) + +type LocalServices struct { + Srv Service + LocalVersion string +} + +var checkCmd = &cobra.Command{ + Use: "check", + Short: "check microservice.", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + fmt.Println("Please input the name of the microservice to be updated!") + return + } + + subcmd := strings.ToLower(args[0]) + switch subcmd { + case "etc": + // 检测并更新本地已经安装的所有微服务的配置文件 + ls := getLocalServices() + etcExecute(ls) + case "service": + // 检测并更新本地已经安装的所有微服务 + ls := getLocalServices() + serviceExecute(ls) + case "registry": + // 检测并更新Registry已经发布的所有微服务 + rs := getRegistryServices() + registryExecute(rs) + default: + fmt.Println("Please input options: etc/server/registry ") + return + } + }, +} + +func etcExecute(ls []LocalServices) { + for _, service := range ls { + yamlUrl := fmt.Sprintf("%s%s/%s/%s_%s.yaml", service.Srv.OssUrl, service.Srv.EtcPath, env.MeshEnv.Workspace, service.Srv.ServiceKey, env.MeshEnv.RuntimeMode) + body, err := utils.HttpGet(yamlUrl) + checkError(err) + fmt.Println("Check YAML Configure:", yamlUrl, " [OK]") + yamlPath := filepath.Join(env.MeshEnv.Prefix, service.Srv.EtcPath, fmt.Sprintf("%s_%s.yaml", service.Srv.ServiceKey, env.MeshEnv.RuntimeMode)) + utils.StringToFile(yamlPath, string(body)) + } +} + +func getLocalServices() []LocalServices { + reles := getRegistryReleases() + + dirFs, err := os.ReadDir(env.MeshEnv.Prefix) + checkError(err) + + ls := make([]LocalServices, 0) + for _, v := range dirFs { + version := getCurrentVersion(v.Name()) + if version != " - " { + for _, srv := range reles.Data { + ls = append(ls, LocalServices{ + Srv: srv, + LocalVersion: version, + }) + } + } + + } + + return ls +} + +func getRegistryServices() []Service { + reles := getRegistryReleases() + + rs := make([]Service, 0) + for _, srv := range reles.Data { + localVersion := getCurrentVersion(srv.ServiceKey) + if localVersion == " - " || srv.Version != localVersion { + rs = append(rs, srv) + } + } + + return rs +} + +func serviceExecute(ls []LocalServices) { + for _, service := range ls { + if service.LocalVersion == service.Srv.Version { + fmt.Println("Skip [", service.Srv.ServiceKey, "], already latest version :", service.Srv.Version) + } else { + service.Srv.Stop() + downUrl := service.Srv.OssUrl + service.Srv.FilePath + fmt.Println("Check Microservice", service.Srv.ServiceKey, service.Srv.Version, downUrl) + binPath := filepath.Join(env.MeshEnv.Prefix, service.Srv.ServiceKey) + DownloadFile(downUrl, binPath, func(length, downLen int64) { + fmt.Fprintf(os.Stdout, "Total:%d KB, Current:%d KB, Percent:%.2f%%\r", length, downLen, (float32(downLen)/float32(length))*100) + }) + service.Srv.Start() + } + } +} + +func registryExecute(rs []Service) { + if !utils.PathExists(env.MeshEnv.Prefix) { + utils.CreateDir(env.MeshEnv.Prefix) + } + logsPath := path.Join(env.MeshEnv.Prefix, "logs") + if !utils.PathExists(logsPath) { + utils.CreateDir(logsPath) + } + etcPath := path.Join(env.MeshEnv.Prefix, "etc") + if !utils.PathExists(etcPath) { + utils.CreateDir(etcPath) + } + + for _, service := range rs { + service.Stop() + + yamlUrl := fmt.Sprintf("%s%s/%s/%s_%s.yaml", service.OssUrl, service.EtcPath, env.MeshEnv.Workspace, service.ServiceKey, env.MeshEnv.RuntimeMode) + body, err := utils.HttpGet(yamlUrl) + checkError(err) + fmt.Println("Found Registry YAML Configure:", yamlUrl, " [OK]") + yamlPath := filepath.Join(env.MeshEnv.Prefix, service.EtcPath, fmt.Sprintf("%s_%s.yaml", service.ServiceKey, env.MeshEnv.RuntimeMode)) + utils.StringToFile(yamlPath, string(body)) + + downUrl := service.OssUrl + service.FilePath + fmt.Println("Found Registry Microservice:", service.ServiceKey, service.Version) + binPath := filepath.Join(env.MeshEnv.Prefix, service.ServiceKey) + DownloadFile(downUrl, binPath, func(length, downLen int64) { + fmt.Fprintf(os.Stdout, "Total:%d KB, Current:%d KB, Percent:%.2f%%\r", length, downLen, (float32(downLen)/float32(length))*100) + }) + + fmt.Println("Restart Microservice:", service.ServiceKey) + service.Start() + + } +} diff --git a/cmd/ctl.go b/cmd/ctl.go new file mode 100644 index 0000000..c1e19c2 --- /dev/null +++ b/cmd/ctl.go @@ -0,0 +1,47 @@ +package cmd + +import ( + "fmt" + "path/filepath" + + "git.apinb.com/bsm-sdk/engine/env" + "github.com/gookit/goutil/cliutil" +) + +func getFilePath(srv *Service) (binPath, logsPath string) { + binPath = filepath.Join(env.MeshEnv.Prefix, srv.ServiceKey) + logsPath = filepath.Join(env.MeshEnv.Prefix, "logs", srv.ServiceKey+"@"+srv.Version+".log") + return +} + +func (srv *Service) Start() { + bin, log := getFilePath(srv) + + cmd := "chmod +x " + bin + _, err := cliutil.ShellExec(cmd) + checkError(err) + + //nohup ./passport >log/passport.log & + cmd = "nohup " + bin + " > " + log + " 2>&1 &" + out, err := cliutil.ShellExec(cmd) + checkError(err) + fmt.Println(string(out)) +} + +func (srv *Service) Stop() { + + bin, _ := getFilePath(srv) + //killall -9 passport /usr/local/bsm/passport + cmd := "-9 " + srv.ServiceKey + " " + bin + out, err := cliutil.ExecCmd("killall", cliutil.StringToOSArgs(cmd), env.MeshEnv.Prefix) + if err != nil { + fmt.Printf("stop error:%s\n", err) + } else { + fmt.Println(out) + } +} + +func (srv *Service) Restart() { + srv.Stop() + srv.Start() +} diff --git a/cmd/ext.go b/cmd/ext.go new file mode 100644 index 0000000..8548584 --- /dev/null +++ b/cmd/ext.go @@ -0,0 +1,177 @@ +package cmd + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + + "git.apinb.com/bsm-sdk/engine/env" + "git.apinb.com/bsm-sdk/engine/utils" +) + +func getRegistryReleases() Releases { + body, err := utils.HttpGet(registryUrl) + checkError(err) + + var ms Releases + err = json.Unmarshal(body, &ms) + checkError(err) + + return ms +} + +func getService(srv string) *Service { + reles := getRegistryReleases() + for _, v := range reles.Data { + if v.ServiceKey == srv { + return &v + } + } + + return nil +} + +func getCurrentVersion(srv string) string { + var data map[string]string + + execbin := filepath.Join(env.MeshEnv.Prefix, srv) + cmd := exec.Command(execbin, "--json") // 替换为适合操作系统的命令 + output, err := cmd.Output() + if err != nil { + return " - " + } + + err = json.Unmarshal(output, &data) + if err != nil { + return " - " + } + + if val, ok := data["version"]; ok { + return val + } + + return " - " +} + +func getProcessInfo(processName string) (string, string) { + var info string = " - " + cmd := exec.Command("ps", "-eo", "pid,%cpu,%mem,cmd") // 使用ps命令查询进程信息 + output, err := cmd.Output() // 获取命令输出结果 + if err != nil { + return info, info + } + + // 将输出按换行符分隔成多行字符串数组 + lines := strings.Split(string(output), "\n")[1:] + + for _, line := range lines { + if strings.Contains(line, processName) { + fields := strings.Fields(line) // 将每行按空格分隔为字段数组 + if len(fields) >= 3 { + pid := fields[0] // PID(进程标识) + cpuUsage := fields[1] // CPU使用情况 + memoryUsage := fields[2] // 内存使用情况 + info = fmt.Sprintf("CPU:%s%% / MEM:%s%%", cpuUsage, memoryUsage) + return pid, info + } + } + } + + return info, info +} + +func checkProcessRunning(processName string) bool { + cmd := exec.Command("ps", "-ef") // 替换为适合操作系统的命令 + output, err := cmd.Output() + if err != nil { + return false + } + + // 将输出按行分割成切片 + lines := strings.Split(string(output), "\n") + + for _, line := range lines { + if strings.Contains(line, processName) { + return true + } + } + + return false +} + +func DownloadFile(url, saveTo string, fb func(length, downLen int64)) { + var ( + fsize int64 + buf = make([]byte, 32*1024) + written int64 + ) + //创建一个http client + client := new(http.Client) + //get方法获取资源 + resp, err := client.Get(url) + if err != nil { + fmt.Printf("download %s error:%s\n", url, err) + os.Exit(0) + } + //读取服务器返回的文件大小 + fsize, err = strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 32) + if err != nil { + fmt.Println(err) + } + //创建文件 + file, err := os.Create(saveTo) + file.Chmod(0777) + if err != nil { + fmt.Printf("Create %s error:%s\n", saveTo, err) + os.Exit(0) + } + defer file.Close() + if resp.Body == nil { + fmt.Printf("resp %s error:%s\n", url, err) + os.Exit(0) + } + defer resp.Body.Close() + //下面是 io.copyBuffer() 的简化版本 + for { + //读取bytes + nr, er := resp.Body.Read(buf) + if nr > 0 { + //写入bytes + nw, ew := file.Write(buf[0:nr]) + //数据长度大于0 + if nw > 0 { + written += int64(nw) + } + //写入出错 + if ew != nil { + err = ew + break + } + //读取是数据长度不等于写入的数据长度 + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er != nil { + if er != io.EOF { + err = er + } + break + } + //没有错误了快使用 callback + + fb(fsize, written) + } + + if err != nil { + fmt.Printf("callback error:%s\n", err) + os.Exit(0) + } +} diff --git a/cmd/install.go b/cmd/install.go new file mode 100644 index 0000000..ecd7d0c --- /dev/null +++ b/cmd/install.go @@ -0,0 +1,84 @@ +package cmd + +import ( + "fmt" + "os" + "path" + "path/filepath" + + "git.apinb.com/bsm-sdk/engine/env" + "git.apinb.com/bsm-sdk/engine/utils" + "github.com/spf13/cobra" +) + +var installCmd = &cobra.Command{ + Use: "install", + Short: "install microservice.", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + fmt.Println("Please input the name of the microservice to be installed!") + return + } + + srv := args[0] + execbin := filepath.Join(env.MeshEnv.Prefix, srv) + binExists := utils.PathExists(execbin) + if binExists { + fmt.Println(srv, "microservice already exists") + } else { + installExecute(srv) + } + }, +} + +func installExecute(srv string) { + fmt.Println("[1/6] Check local direct[logs,etc].") + if !utils.PathExists(env.MeshEnv.Prefix) { + utils.CreateDir(env.MeshEnv.Prefix) + } + logsPath := path.Join(env.MeshEnv.Prefix, "logs") + if !utils.PathExists(logsPath) { + utils.CreateDir(logsPath) + } + etcPath := path.Join(env.MeshEnv.Prefix, "etc") + if !utils.PathExists(etcPath) { + utils.CreateDir(etcPath) + } + + fmt.Println("[2/6] Check OS ENV BlocksMesh_Workspace,BlocksMesh_RuntimeMode:", env.MeshEnv.Workspace, ",", env.MeshEnv.RuntimeMode) + service, info := getMs(srv) + fmt.Println("[3/6] Search Microservice:", info) + + if service != nil { + + yamlUrl := fmt.Sprintf("%s%s/%s/%s_%s.yaml", service.OssUrl, service.EtcPath, env.MeshEnv.Workspace, srv, env.MeshEnv.RuntimeMode) + body, err := utils.HttpGet(yamlUrl) + checkError(err) + fmt.Println("[4/6] Download YAML Configure:", yamlUrl, " [OK]") + yamlPath := filepath.Join(env.MeshEnv.Prefix, service.EtcPath, fmt.Sprintf("%s_%s.yaml", srv, env.MeshEnv.RuntimeMode)) + utils.StringToFile(yamlPath, string(body)) + + downUrl := service.OssUrl + service.FilePath + fmt.Println("[5/6] Download Binrary File:", downUrl) + binPath := filepath.Join(env.MeshEnv.Prefix, srv) + DownloadFile(downUrl, binPath, func(length, downLen int64) { + fmt.Fprintf(os.Stdout, "Total:%d KB, Current:%d KB, Percent:%.2f%%\r", length, downLen, (float32(downLen)/float32(length))*100) + }) + + fmt.Println("[6/6] Restart Microservice:", srv) + service.Start() + fmt.Println("Install Successful!") + } + +} + +func getMs(srv string) (*Service, string) { + reles := getRegistryReleases() + for _, v := range reles.Data { + if v.ServiceKey == srv { + return &v, fmt.Sprintf("Ident:%s, Version:%s, Oss:%s", v.Identity, v.Version, v.OssUrl) + } + } + + return nil, "Not Found." +} diff --git a/cmd/ps.go b/cmd/ps.go index 1ba05ec..31a3388 100644 --- a/cmd/ps.go +++ b/cmd/ps.go @@ -3,9 +3,7 @@ package cmd import ( "encoding/json" "fmt" - "os/exec" "path/filepath" - "strings" "time" "git.apinb.com/bsm-sdk/engine/env" @@ -15,13 +13,13 @@ import ( "github.com/spf13/cobra" ) -type JSONData struct { - Msg string `json:"msg"` - Code int `json:"code"` - Data []ActionsReleases `json:"data"` +type Releases struct { + Msg string `json:"msg"` + Code int `json:"code"` + Data []Service `json:"data"` } -type ActionsReleases struct { +type Service struct { ID uint `gorm:"primarykey"` CreatedAt time.Time UpdatedAt time.Time @@ -61,7 +59,7 @@ func psExecute() { body, err := utils.HttpGet(registryUrl) checkError(err) - var ms JSONData + var ms Releases err = json.Unmarshal(body, &ms) checkError(err) @@ -85,7 +83,8 @@ func psExecute() { func getSrvStatus(srv string) PsTable { var status string = " - " - binExists := utils.PathExists(env.MeshEnv.Prefix + srv) + execbin := filepath.Join(env.MeshEnv.Prefix, srv) + binExists := utils.PathExists(execbin) if binExists { //status = "\033[32mRunning\033[0m" status = "Running" @@ -107,71 +106,3 @@ func getSrvStatus(srv string) PsTable { } } - -func getCurrentVersion(srv string) string { - var data map[string]string - - execbin := filepath.Join(env.MeshEnv.Prefix, srv) - cmd := exec.Command(execbin, "--json") // 替换为适合操作系统的命令 - output, err := cmd.Output() - if err != nil { - return " - " - } - - err = json.Unmarshal(output, &data) - if err != nil { - return " - " - } - - if val, ok := data["version"]; ok { - return val - } - - return " - " -} - -func getProcessInfo(processName string) (string, string) { - var info string = " - " - cmd := exec.Command("ps", "-eo", "pid,%cpu,%mem,cmd") // 使用ps命令查询进程信息 - output, err := cmd.Output() // 获取命令输出结果 - if err != nil { - return info, info - } - - // 将输出按换行符分隔成多行字符串数组 - lines := strings.Split(string(output), "\n")[1:] - - for _, line := range lines { - if strings.Contains(line, processName) { - fields := strings.Fields(line) // 将每行按空格分隔为字段数组 - if len(fields) >= 3 { - pid := fields[0] // PID(进程标识) - cpuUsage := fields[1] // CPU使用情况 - memoryUsage := fields[2] // 内存使用情况 - info = fmt.Sprintf("CPU:%s%% / MEM:%s%%", cpuUsage, memoryUsage) - return pid, info - } - } - } - - return info, info -} - -func checkProcessRunning(processName string) bool { - cmd := exec.Command("ps", "-ef") // 替换为适合操作系统的命令 - output, err := cmd.Output() - if err != nil { - return false - } - - // 将输出按行分割成切片 - lines := strings.Split(string(output), "\n") - - for _, line := range lines { - if strings.Contains(line, processName) { - return true - } - } - - return false -} diff --git a/cmd/root.go b/cmd/root.go index 102f398..cbb0b0f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -22,7 +22,7 @@ func init() { registryUrl = env.GetEnvDefault("BlocksMesh_Registry", "http://registry.apinb.com") } - rootCmd.AddCommand(psCmd) + rootCmd.AddCommand(psCmd, installCmd) } var rootCmd = &cobra.Command{ diff --git a/cmd/update.go b/cmd/update.go new file mode 100644 index 0000000..b052721 --- /dev/null +++ b/cmd/update.go @@ -0,0 +1,67 @@ +package cmd + +import ( + "fmt" + "os" + "path/filepath" + + "git.apinb.com/bsm-sdk/engine/env" + "git.apinb.com/bsm-sdk/engine/utils" + "github.com/spf13/cobra" +) + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "update microservice.", + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + fmt.Println("Please input the name of the microservice to be updated!") + return + } + + srv := args[0] + execbin := filepath.Join(env.MeshEnv.Prefix, srv) + binExists := utils.PathExists(execbin) + if !binExists { + fmt.Println(srv, "microservice not install") + } else { + updateExecute(srv) + } + }, +} + +func updateExecute(srv string) { + localVersion := getCurrentVersion(srv) + fmt.Println("[1/5] Check local microservice version:", localVersion) + + service := getService(srv) + if service == nil { + fmt.Println("ERR:", srv, "Not Found!") + os.Exit(0) + } + + fmt.Println("[2/5] Check registry microservice version:", service.Version) + + if localVersion != service.Version { + service.Stop() + + yamlUrl := fmt.Sprintf("%s%s/%s/%s_%s.yaml", service.OssUrl, service.EtcPath, env.MeshEnv.Workspace, srv, env.MeshEnv.RuntimeMode) + body, err := utils.HttpGet(yamlUrl) + checkError(err) + fmt.Println("[3/5] Download YAML Configure:", yamlUrl, " [OK]") + yamlPath := filepath.Join(env.MeshEnv.Prefix, service.EtcPath, fmt.Sprintf("%s_%s.yaml", srv, env.MeshEnv.RuntimeMode)) + utils.StringToFile(yamlPath, string(body)) + + downUrl := service.OssUrl + service.FilePath + fmt.Println("[4/5] Download Binrary File:", downUrl) + binPath := filepath.Join(env.MeshEnv.Prefix, srv) + DownloadFile(downUrl, binPath, func(length, downLen int64) { + fmt.Fprintf(os.Stdout, "Total:%d KB, Current:%d KB, Percent:%.2f%%\r", length, downLen, (float32(downLen)/float32(length))*100) + }) + + fmt.Println("[5/5] Start Microservice:", srv) + service.Start() + fmt.Println("Install Successful!") + } + +} diff --git a/go.mod b/go.mod index 459d5ec..77318ca 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,20 @@ go 1.22.0 require git.apinb.com/bsm-sdk/engine v1.0.9 require ( + github.com/gookit/color v1.5.4 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + golang.org/x/sync v0.5.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/term v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect ) require ( github.com/golistic/boxed v0.0.0-20231227175750-bd89723124e7 github.com/google/uuid v1.6.0 // indirect + github.com/gookit/goutil v0.6.15 github.com/jaevor/go-nanoid v1.3.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect diff --git a/go.sum b/go.sum index 93f1d00..c4004b0 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,10 @@ github.com/golistic/boxed v0.0.0-20231227175750-bd89723124e7 h1:nof2QnZZ42zxwzrW github.com/golistic/boxed v0.0.0-20231227175750-bd89723124e7/go.mod h1:FB8Aa5H1xlLhPbpXIfP9JcwTt287TCI29X4xCEXaIBg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= +github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gookit/goutil v0.6.15 h1:mMQ0ElojNZoyPD0eVROk5QXJPh2uKR4g06slgPDF5Jo= +github.com/gookit/goutil v0.6.15/go.mod h1:qdKdYEHQdEtyH+4fNdQNZfJHhI0jUZzHxQVAV3DaMDY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jaevor/go-nanoid v1.3.0 h1:nD+iepesZS6pr3uOVf20vR9GdGgJW1HPaR46gtrxzkg= @@ -24,6 +28,16 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/subchen/go-tableify v1.1.0 h1:VUCCHfXlttR5LhjjEmCThdI0UdYKr/7OnOey78UClrY= github.com/subchen/go-tableify v1.1.0/go.mod h1:a4dTYdYueMaCd4MU4V7q9XDjkDhQ/EhlKyQb4WaSCWw= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=