From 294cc504b5bc78ceb59487d6a8499da36544b871 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=96=AF=E9=AD=94=E6=85=95=E8=96=87?= Date: Fri, 11 Oct 2024 19:48:42 +0800 Subject: [PATCH] recovery deleted object to special version. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 疯魔慕薇 --- cmd/rov.go | 60 ++++++++++++++++ util/recovery_object_version.go | 118 ++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 cmd/rov.go create mode 100644 util/recovery_object_version.go diff --git a/cmd/rov.go b/cmd/rov.go new file mode 100644 index 0000000..9208b4f --- /dev/null +++ b/cmd/rov.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "coscli/util" + "fmt" + + "github.com/spf13/cobra" +) + +var rovCmd = &cobra.Command{ + Use: "rov", + Short: "recovery object version", + Long: `recovery object version`, + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + limit, _ := cmd.Flags().GetInt("limit") + dryrun, _ := cmd.Flags().GetBool("dryrun") + previous, _ := cmd.Flags().GetInt("previous") + + if limit == 0 { + limit = 10000 + } else if limit < 0 { + return fmt.Errorf("flag --limit should be greater than 0") + } + + cosPath := "" + if len(args) != 0 { + cosPath = args[0] + } + + cosUrl, err := util.FormatUrl(cosPath) + if err != nil { + return fmt.Errorf("cos url format error:%v", err) + } + + // 无参数,则列出当前账号下的所有存储桶 + if cosPath == "" { + return fmt.Errorf("bucket name is required") + } else if cosUrl.IsCosUrl() { + // 实例化cos client + bucketName := cosUrl.(*util.CosUrl).Bucket + c, err := util.NewClient(&config, ¶m, bucketName) + if err != nil { + return err + } + return util.RecoveryObjectVersion(c, cosUrl.(*util.CosUrl), previous, limit, dryrun) + } else { + return fmt.Errorf("cospath needs to contain cos://") + } + + }, +} + +func init() { + rootCmd.AddCommand(rovCmd) + + rovCmd.Flags().IntP("previous", "P", 1, "previous version to recovery. 1 means the latest version, 2 means the second latest version") + rovCmd.Flags().IntP("limit", "l", 10000, "limit the number of objects to list") + rovCmd.Flags().BoolP("not-dryrun", "n", false, "default dryrun mode, do not actually recovery object version") +} diff --git a/util/recovery_object_version.go b/util/recovery_object_version.go new file mode 100644 index 0000000..dd02cc9 --- /dev/null +++ b/util/recovery_object_version.go @@ -0,0 +1,118 @@ +package util + +import ( + "context" + "fmt" + + "github.com/sirupsen/logrus" + "github.com/tencentyun/cos-go-sdk-v5" +) + +// RecoveryObjectVersion recovery object version +func RecoveryObjectVersion(c *cos.Client, cosUrl StorageUrl, previous int, limit int, dryrun bool) error { + var total = 0 + var marker = "" + var versionMarker = "" + var isTruncated = true + + var ctx = context.Background() + var prefix = cosUrl.(*CosUrl).Object + + for isTruncated && total < limit { + var opt = &cos.BucketGetObjectVersionsOptions{ + Prefix: prefix, + KeyMarker: marker, + VersionIdMarker: versionMarker, + } + resp, _, err := c.Bucket.GetObjectVersions(ctx, opt) + if err != nil { + return err + } + + total += len(resp.Version) + total += len(resp.DeleteMarker) + + var notLatestVersions = versionKeyMap(false, resp.Version) + var log = logrus.WithField("versions", notLatestVersions). + WithField("previous", previous). + WithField("dryrun", dryrun) + + for key, versions := range notLatestVersions { + logrus.WithField("Key", key).WithField("versions", versions).Info("find versions") + } + + for _, marker := range resp.DeleteMarker { + var log = log.WithField("Key", marker.Key). + WithField("marker.VersionId", marker.VersionId) + + if marker.IsLatest { + versionIds, has := notLatestVersions[marker.Key] + if has && (len(versionIds) < previous || previous <= 0) { + log.Warn("version not found") + return nil + } + + if has { + err = recoveryObject(c, marker.Key, versionIds[previous-1].VersionId, dryrun) + if err != nil { + log.WithError(err).Warn("recovery failed") + } else { + log.Info("recovery success") + } + } else { + log.Warn("not found the latest version of delete marker") + } + } + } + + logrus.WithField("IsTruncated", resp.IsTruncated).WithField("NextKeyMarker", resp.NextKeyMarker).Info("DeleteMarkers") + marker = resp.NextKeyMarker + isTruncated = resp.IsTruncated + versionMarker = resp.NextVersionIdMarker + + if !isTruncated || total >= limit { + logrus.Info("no more") + break + } + } + return nil +} + +// map[Key]Version +func versionKeyMap(latest bool, versions []cos.ListVersionsResultVersion) map[string][]cos.ListVersionsResultVersion { + var versionMap = make(map[string][]cos.ListVersionsResultVersion) + for _, version := range versions { + if version.IsLatest == latest { + versionMap[version.Key] = append(versionMap[version.Key], version) + } else { + } + } + return versionMap +} + +func recoveryObject(c *cos.Client, key string, versionId string, dryrun bool) (err error) { + var destKey = key + if versionId == "" { + return nil + } + var srcUrl = fmt.Sprintf("%s/%s", c.BaseURL.BucketURL.Host, key) + + var log = logrus.WithField("srcUrl", srcUrl).WithField("VersionId", versionId).WithField("DestKey", destKey) + if dryrun { + log.Info("DryRun RecoveryObject") + return nil + } + + resp, _, err := c.Object.Copy(context.Background(), key, srcUrl, nil, versionId) + + var respVerId string + if resp != nil { + respVerId = resp.VersionId + } + + log.WithError(err). + WithField("respVerId", respVerId). + Warn("RecoveryObject") + + return err +}