From c2dc8db3286dfe115c5dad8951c95583bf6725ca Mon Sep 17 00:00:00 2001 From: ysicing Date: Mon, 25 Sep 2023 17:30:24 +0800 Subject: [PATCH] feat(backup): add backup subcmd add backup subcmd Signed-off-by: ysicing --- cmd/backup.go | 24 ++++++++++ cmd/backup/app.go | 67 ++++++++++++++++++++++++++++ cmd/backup/{backup.go => cluster.go} | 2 +- cmd/cluster.go | 2 - cmd/root.go | 1 + common/const.go | 1 + internal/api/cne/cne.go | 56 +++++++++++++++++++++++ internal/api/cne/types.go | 36 +++++++++++++++ internal/pkg/util/kutil/kutil.go | 2 +- pkg/qucheng/upgrade/upgrade.go | 7 +++ 10 files changed, 194 insertions(+), 4 deletions(-) create mode 100644 cmd/backup.go create mode 100644 cmd/backup/app.go rename cmd/backup/{backup.go => cluster.go} (98%) create mode 100644 internal/api/cne/cne.go create mode 100644 internal/api/cne/types.go diff --git a/cmd/backup.go b/cmd/backup.go new file mode 100644 index 00000000..0e173e7b --- /dev/null +++ b/cmd/backup.go @@ -0,0 +1,24 @@ +// Copyright (c) 2021-2023 北京渠成软件有限公司(Beijing Qucheng Software Co., Ltd. www.qucheng.com) All rights reserved. +// Use of this source code is covered by the following dual licenses: +// (1) Z PUBLIC LICENSE 1.2 (ZPL 1.2) +// (2) Affero General Public License 3.0 (AGPL 3.0) +// license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/easysoft/qcadmin/cmd/backup" + "github.com/easysoft/qcadmin/internal/pkg/util/factory" + "github.com/spf13/cobra" +) + +func newCmdBackup(f factory.Factory) *cobra.Command { + backupCmd := &cobra.Command{ + Use: "backup", + Short: "Backup commands", + Version: "20230925", + } + backupCmd.AddCommand(backup.NewCmdBackupCluster(f)) + backupCmd.AddCommand(backup.NewCmdBackupApp(f)) + return backupCmd +} diff --git a/cmd/backup/app.go b/cmd/backup/app.go new file mode 100644 index 00000000..88fef1f3 --- /dev/null +++ b/cmd/backup/app.go @@ -0,0 +1,67 @@ +// Copyright (c) 2021-2023 北京渠成软件有限公司(Beijing Qucheng Software Co., Ltd. www.qucheng.com) All rights reserved. +// Use of this source code is covered by the following dual licenses: +// (1) Z PUBLIC LICENSE 1.2 (ZPL 1.2) +// (2) Affero General Public License 3.0 (AGPL 3.0) +// license that can be found in the LICENSE file. + +package backup + +import ( + "strings" + "time" + + "github.com/easysoft/qcadmin/internal/api/cne" + "github.com/easysoft/qcadmin/internal/pkg/util/factory" + "github.com/spf13/cobra" +) + +func NewCmdBackupApp(f factory.Factory) *cobra.Command { + var app, ns, backupName string + var err error + log := f.GetLog() + bc := &cobra.Command{ + Use: "app", + Short: "backup app", + Long: "backup app", + Run: func(cmd *cobra.Command, args []string) { + log.Infof("start backup app: %s", app) + cneClient := cne.NewCneAPI() + if backupName == "" { + backupName, err = cneClient.AppBackUP(ns, app) + if err != nil { + log.Errorf("backup app %s failed, reason: %v", app, err) + return + } + } + + timeout := 5 * time.Minute + costtime := time.Now() + deadline := time.Now().Add(timeout) + for { + backupStatus, err := cneClient.AppBackUPStatus(ns, app, backupName) + if err != nil { + log.Errorf("backup app %s failed, reason: %v", app, err) + return + } + if strings.ToLower(backupStatus.Status) == "completed" { + log.Infof("backup app %s(%s) success, cost: %vs", app, backupName, time.Since(costtime).Seconds()) + return + } + if strings.ToLower(backupStatus.Status) == "failed" { + log.Errorf("backup app %s(%s) failed, reason: %s", app, backupName, backupStatus.Reason) + return + } + if time.Now().After(deadline) { + log.Errorf("backup app %s(%s) timeout", app, backupName) + return + } + log.Infof("backup app %s(%s) status: %s", app, backupName, backupStatus.Status) + time.Sleep(5 * time.Second) + } + }, + } + bc.Flags().StringVar(&app, "app", "", "app chart name") + bc.Flags().StringVarP(&ns, "namespace", "n", "", "namespace") + bc.Flags().StringVarP(&backupName, "backupName", "b", "", "existing backup name") + return bc +} diff --git a/cmd/backup/backup.go b/cmd/backup/cluster.go similarity index 98% rename from cmd/backup/backup.go rename to cmd/backup/cluster.go index 65720edc..bccb04b0 100644 --- a/cmd/backup/backup.go +++ b/cmd/backup/cluster.go @@ -18,7 +18,7 @@ import ( func NewCmdBackupCluster(f factory.Factory) *cobra.Command { log := f.GetLog() bc := &cobra.Command{ - Use: "backup", + Use: "cluster", Short: "backup cluster", Long: "backup cluster", Aliases: []string{"snapshot"}, diff --git a/cmd/cluster.go b/cmd/cluster.go index 258a4559..03e582d4 100644 --- a/cmd/cluster.go +++ b/cmd/cluster.go @@ -7,7 +7,6 @@ package cmd import ( - "github.com/easysoft/qcadmin/cmd/backup" "github.com/easysoft/qcadmin/cmd/cluster" "github.com/easysoft/qcadmin/cmd/precheck" "github.com/easysoft/qcadmin/cmd/storage" @@ -46,6 +45,5 @@ func newCmdCluster(f factory.Factory) *cobra.Command { clusterCmd.AddCommand(cluster.StatusCommand(f)) clusterCmd.AddCommand(cluster.StopCommand(f)) clusterCmd.AddCommand(storage.NewCmdStorage(f)) - clusterCmd.AddCommand(backup.NewCmdBackupCluster(f)) return clusterCmd } diff --git a/cmd/root.go b/cmd/root.go index dee4e065..2455b829 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -68,6 +68,7 @@ func BuildRoot(f factory.Factory) *cobra.Command { rootCmd.AddCommand(newCmdUpgrade(f)) rootCmd.AddCommand(newCmdCluster(f)) rootCmd.AddCommand(newCmdPlatform(f)) + rootCmd.AddCommand(newCmdBackup(f)) // Add plugin commands rootCmd.AddCommand(newCmdExperimental(f)) rootCmd.AddCommand(newManCmd()) diff --git a/common/const.go b/common/const.go index 231aee68..5120ad23 100644 --- a/common/const.go +++ b/common/const.go @@ -109,6 +109,7 @@ const ( MiuiGenerate204URL = "https://connect.rom.miui.com/generate_204" V2exGenerate204URL = "https://captive.v2ex.co/generate_204" CloudflareEdgeTraceURL = "https://www.cloudflare.com/cdn-cgi/trace" + CneAPITokenHeader = "X-Auth-Token" ) const ( diff --git a/internal/api/cne/cne.go b/internal/api/cne/cne.go new file mode 100644 index 00000000..7a303d8d --- /dev/null +++ b/internal/api/cne/cne.go @@ -0,0 +1,56 @@ +// Copyright (c) 2021-2023 北京渠成软件有限公司(Beijing Qucheng Software Co., Ltd. www.qucheng.com) All rights reserved. +// Use of this source code is covered by the following dual licenses: +// (1) Z PUBLIC LICENSE 1.2 (ZPL 1.2) +// (2) Affero General Public License 3.0 (AGPL 3.0) +// license that can be found in the LICENSE file. + +package cne + +import ( + "fmt" + + "github.com/easysoft/qcadmin/common" + "github.com/easysoft/qcadmin/internal/app/config" + "github.com/imroc/req/v3" +) + +type CneAPI struct { + Req *req.Request + Endpoint string +} + +func NewCneAPI() *CneAPI { + config, _ := config.LoadConfig() + c := req.C().R().SetHeader(common.CneAPITokenHeader, config.APIToken) + return &CneAPI{ + Req: c, + Endpoint: fmt.Sprintf("http://%s:32380", config.Cluster.InitNode), + } +} + +func (c *CneAPI) AppBackUP(ns, chartName string) (string, error) { + var result AppBackUPResp + appbackup := &AppBackUPReq{Namespace: ns, Name: chartName} + resp, err := c.Req.SetBody(appbackup). + SetSuccessResult(&result). + Post(c.Endpoint + "/api/cne/app/backup") + if resp.IsSuccessState() { + return result.Data.BackupName, nil + } + return "", err +} + +func (c *CneAPI) AppBackUPStatus(ns, chartName, backupName string) (*AppBackUPStatus, error) { + var result AppBackUPStatusResp + resp, err := c.Req.SetQueryParams(map[string]string{ + "namespace": ns, + "name": chartName, + "backup_name": backupName, + }). + SetSuccessResult(&result). + Get(c.Endpoint + "/api/cne/app/backup/status") + if resp.IsSuccessState() { + return &result.Data, nil + } + return nil, err +} diff --git a/internal/api/cne/types.go b/internal/api/cne/types.go new file mode 100644 index 00000000..030f2ba6 --- /dev/null +++ b/internal/api/cne/types.go @@ -0,0 +1,36 @@ +// Copyright (c) 2021-2023 北京渠成软件有限公司(Beijing Qucheng Software Co., Ltd. www.qucheng.com) All rights reserved. +// Use of this source code is covered by the following dual licenses: +// (1) Z PUBLIC LICENSE 1.2 (ZPL 1.2) +// (2) Affero General Public License 3.0 (AGPL 3.0) +// license that can be found in the LICENSE file. + +package cne + +type AppBackUPReq struct { + Cluster string `json:"cluster,omitempty"` + Namespace string `json:"namespace"` + Name string `json:"name"` // chartname + UserName string `json:"username,omitempty"` // 操作用户 +} + +type AppBackUPResp struct { + Code int `json:"code"` + Message string `json:"message"` + Data AppBackUPData `json:"data"` +} + +type AppBackUPData struct { + BackupName string `json:"backup_name"` + CreateTime int64 `json:"create_time"` +} + +type AppBackUPStatusResp struct { + Code int `json:"code"` + Message string `json:"message"` + Data AppBackUPStatus `json:"data"` +} + +type AppBackUPStatus struct { + Reason string `json:"reason,omitempty"` + Status string `json:"status"` +} diff --git a/internal/pkg/util/kutil/kutil.go b/internal/pkg/util/kutil/kutil.go index c2e2cfcb..234d1205 100644 --- a/internal/pkg/util/kutil/kutil.go +++ b/internal/pkg/util/kutil/kutil.go @@ -38,7 +38,7 @@ func NeedCacheHelmFile() bool { data, _ := file.ReadAll(cachefile) old := time.Unix(exstr.Str2Int64(string(data)), 0) now := time.Now() - if now.Sub(old) > 10*time.Minute { + if now.Sub(old) > 2*time.Minute { os.Remove(cachefile) file.WriteFile(cachefile, ztime.NowUnixString(), true) return true diff --git a/pkg/qucheng/upgrade/upgrade.go b/pkg/qucheng/upgrade/upgrade.go index 6755cb02..958f1dbd 100644 --- a/pkg/qucheng/upgrade/upgrade.go +++ b/pkg/qucheng/upgrade/upgrade.go @@ -105,6 +105,13 @@ func Upgrade(flagVersion string, log log.Logger) error { for _, cv := range qv.Components { if cv.CanUpgrade { defaultValue, _ := helmClient.GetValues(cv.Name) + if devops && cv.Name == common.DefaultZentaoPaasName { + deploy := defaultValue["deploy"] + product := deploy.(map[string]interface{})["product"] + versions := deploy.(map[string]interface{})["versions"] + appVersion := versions.(map[string]interface{})[product.(string)] + log.Infof("devops mode, product: %v, version: %v", product, appVersion) + } if _, err := helmClient.Upgrade(cv.Name, common.DefaultHelmRepoName, cv.Name, "", defaultValue); err != nil { log.Warnf("upgrade %s failed, reason: %v", cv.Name, err) } else {