diff --git a/lib/bucket_inventory_test.go b/lib/bucket_inventory_test.go
index ca83a4ff..356c6212 100644
--- a/lib/bucket_inventory_test.go
+++ b/lib/bucket_inventory_test.go
@@ -26,7 +26,6 @@ func (s *OssutilCommandSuite) TestInventoryPutSuccess(c *C) {
RoleArn: stsARN,
Bucket: "acs:oss:::" + bucketName,
Prefix: "prefix1",
- KeyId: "keyId",
},
Frequency: "Daily",
IncludedObjectVersions: "All",
@@ -109,9 +108,7 @@ func (s *OssutilCommandSuite) TestInventoryGetErrorResultFileOpenError(c *C) {
AccountId: accountID,
RoleArn: stsARN,
Bucket: "acs:oss:::" + bucketName,
- Prefix: "prefix1",
- KeyId: "keyId",
- },
+ Prefix: "prefix1"},
Frequency: "Daily",
IncludedObjectVersions: "All",
OptionalFields: oss.OptionalFields{
@@ -315,7 +312,6 @@ func (s *OssutilCommandSuite) TestInventoryGetConfirm(c *C) {
RoleArn: stsARN,
Bucket: "acs:oss:::" + bucketName,
Prefix: "prefix1",
- KeyId: "keyId",
},
Frequency: "Daily",
IncludedObjectVersions: "All",
@@ -393,7 +389,6 @@ func (s *OssutilCommandSuite) TestInventoryDelete(c *C) {
RoleArn: stsARN,
Bucket: "acs:oss:::" + bucketName,
Prefix: "prefix1",
- KeyId: "keyId",
},
Frequency: "Daily",
IncludedObjectVersions: "All",
@@ -478,7 +473,6 @@ func (s *OssutilCommandSuite) TestInventoryListSuccess(c *C) {
RoleArn: stsARN,
Bucket: "acs:oss:::" + bucketName,
Prefix: "prefix1",
- KeyId: "keyId",
},
Frequency: "Daily",
IncludedObjectVersions: "All",
@@ -557,7 +551,6 @@ func (s *OssutilCommandSuite) TestInventoryListMarkerSuccess(c *C) {
RoleArn: stsARN,
Bucket: "acs:oss:::" + bucketName,
Prefix: "prefix1",
- KeyId: "keyId",
},
Frequency: "Daily",
IncludedObjectVersions: "All",
@@ -629,7 +622,7 @@ func (s *OssutilCommandSuite) TestInventoryListMarkerSuccess(c *C) {
c.Assert(err, IsNil)
// list error,result file open error
- inventoryArgs = []string{CloudURLToString(bucketName, ""), string(os.PathSeparator) + "root1" + string(os.PathListSeparator) + "test-file"}
+ inventoryArgs = []string{CloudURLToString(bucketName, ""), string(os.PathSeparator) + "root1" + string(os.PathSeparator) + "test-file"}
_, err = cm.RunCommand("inventory", inventoryArgs, options)
c.Assert(err, NotNil)
diff --git a/lib/const.go b/lib/const.go
index 11e7d1f8..d242a169 100644
--- a/lib/const.go
+++ b/lib/const.go
@@ -125,7 +125,7 @@ const (
const (
Package string = "ossutil"
ChannelBuf int = 1000
- Version string = "v1.6.11"
+ Version string = "v1.6.12"
DefaultEndpoint string = "oss.aliyuncs.com"
ChineseLanguage = "CH"
EnglishLanguage = "EN"
@@ -170,6 +170,7 @@ const (
StorageStandard = string(oss.StorageStandard)
StorageIA = string(oss.StorageIA)
StorageArchive = string(oss.StorageArchive)
+ StorageColdArchive = string(oss.StorageColdArchive)
DefaultStorageClass = StorageStandard
DefaultMethod = string(oss.HTTPGet)
DefaultTimeout = 60
diff --git a/lib/cp.go b/lib/cp.go
index 51912631..7110e7bd 100644
--- a/lib/cp.go
+++ b/lib/cp.go
@@ -2133,7 +2133,7 @@ func (cc *CopyCommand) ossPutObjectRetry(bucket *oss.Bucket, objectName string,
retryTimes, _ := GetInt(OptionRetryTimes, cc.command.options)
for i := 1; ; i++ {
if i > 1 {
- time.Sleep(time.Duration(1) * time.Second)
+ time.Sleep(time.Duration(3) * time.Second)
if int64(i) >= retryTimes {
fmt.Printf("\nretry count:%d:put object:%s.\n", i-1, objectName)
}
@@ -2157,7 +2157,7 @@ func (cc *CopyCommand) ossUploadFileRetry(bucket *oss.Bucket, objectName string,
retryTimes, _ := GetInt(OptionRetryTimes, cc.command.options)
for i := 1; ; i++ {
if i > 1 {
- time.Sleep(time.Duration(1) * time.Second)
+ time.Sleep(time.Duration(3) * time.Second)
if int64(i) >= retryTimes {
fmt.Printf("\nretry count:%d:upload file:%s\n", i-1, filePath)
}
@@ -2238,7 +2238,7 @@ func (cc *CopyCommand) ossResumeUploadRetry(bucket *oss.Bucket, objectName strin
retryTimes, _ := GetInt(OptionRetryTimes, cc.command.options)
for i := 1; ; i++ {
if i > 1 {
- time.Sleep(time.Duration(1) * time.Second)
+ time.Sleep(time.Duration(3) * time.Second)
if int64(i) >= retryTimes {
fmt.Printf("\nretry count:%d,multipart upload file:%s.\n", i-1, filePath)
}
@@ -2525,7 +2525,7 @@ func (cc *CopyCommand) ossDownloadFileRetry(bucket *oss.Bucket, objectName, file
retryTimes, _ := GetInt(OptionRetryTimes, cc.command.options)
for i := 1; ; i++ {
if i > 1 {
- time.Sleep(time.Duration(1) * time.Second)
+ time.Sleep(time.Duration(3) * time.Second)
if int64(i) >= retryTimes {
fmt.Printf("\nretry count:%d:get object to file:%s.\n", i-1, fileName)
}
@@ -2555,7 +2555,7 @@ func (cc *CopyCommand) ossResumeDownloadRetry(bucket *oss.Bucket, objectName str
retryTimes, _ := GetInt(OptionRetryTimes, cc.command.options)
for i := 1; ; i++ {
if i > 1 {
- time.Sleep(time.Duration(1) * time.Second)
+ time.Sleep(time.Duration(3) * time.Second)
if int64(i) >= retryTimes {
fmt.Printf("\nretry count:%d:mulitpart download file:%s.\n", i-1, objectName)
}
@@ -2963,7 +2963,7 @@ func (cc *CopyCommand) ossCopyObjectRetry(bucket *oss.Bucket, objectName, destBu
options = append(options, oss.TaggingDirective(oss.TaggingReplace))
for i := 1; ; i++ {
if i > 1 {
- time.Sleep(time.Duration(1) * time.Second)
+ time.Sleep(time.Duration(3) * time.Second)
if int64(i) >= retryTimes {
fmt.Printf("\nretry count:%d,copy object:%s.\n", i-1, objectName)
}
@@ -2990,7 +2990,7 @@ func (cc *CopyCommand) ossResumeCopyRetry(bucketName, objectName, destBucketName
retryTimes, _ := GetInt(OptionRetryTimes, cc.command.options)
for i := 1; ; i++ {
if i > 1 {
- time.Sleep(time.Duration(1) * time.Second)
+ time.Sleep(time.Duration(3) * time.Second)
if int64(i) >= retryTimes {
fmt.Printf("\nretry count:%d, resume copy object:%s.\n", i-1, objectName)
}
diff --git a/lib/mb.go b/lib/mb.go
index 257eb093..161d98bc 100644
--- a/lib/mb.go
+++ b/lib/mb.go
@@ -11,6 +11,7 @@ var storageClassList = []string{
StorageStandard,
StorageIA,
StorageArchive,
+ StorageColdArchive,
}
func formatStorageClassString(sep string) string {
@@ -46,7 +47,7 @@ ACL:
StorageClass:
- bucket的StorageClass有三种:
+ bucket的StorageClass有四种:
` + formatStorageClassString("\n ") + `
关于StorageClass的更多信息请参考:https://help.aliyun.com/document_detail/31959.html?spm=5176.doc31957.6.839.E1ifnh
@@ -104,7 +105,7 @@ ACL:
StorageClass:
- There are three kinds of StorageClass:
+ There are four kinds of StorageClass:
` + formatStorageClassString("\n ") + `
More information about StorageClass see: https://help.aliyun.com/document_detail/31959.html?spm=5176.doc31957.6.839.E1ifnh
@@ -270,5 +271,8 @@ func (mc *MakeBucketCommand) getStorageClass() oss.StorageClassType {
if strings.EqualFold(storageClass, StorageArchive) {
return oss.StorageArchive
}
+ if strings.EqualFold(storageClass, StorageColdArchive) {
+ return oss.StorageColdArchive
+ }
return oss.StorageStandard
}
diff --git a/lib/option.go b/lib/option.go
index de6d5252..7819feb4 100644
--- a/lib/option.go
+++ b/lib/option.go
@@ -55,9 +55,9 @@ var OptionMap = map[string]Option{
OptionAllType: Option{"-a", "--all-type", "", OptionTypeFlagTrue, "", "", "指定操作的对象为bucket中的object和未完成的Multipart事件。", "Indicate that the subject of the command contains both objects and uncompleted Multipart Uploads."},
OptionRecursion: Option{"-r", "--recursive", "", OptionTypeFlagTrue, "", "", "递归进行操作。对于支持该选项的命令,当指定该选项时,命令会对bucket下所有符合条件的objects进行操作,否则只对url中指定的单个object进行操作。", "operate recursively, for those commands which support the option, when use them, if the option is specified, the command will operate on all match objects under the bucket, else we will search the specified object and operate on the single object."},
OptionBucket: Option{"-b", "--bucket", "", OptionTypeFlagTrue, "", "", "对bucket进行操作,该选项用于确认操作作用于bucket", "the option used to make sure the operation will operate on bucket"},
- OptionStorageClass: Option{"", "--storage-class", DefaultStorageClass, OptionTypeAlternative, fmt.Sprintf("%s/%s/%s", StorageStandard, StorageIA, StorageArchive), "",
- fmt.Sprintf("设置对象的存储方式,默认值:%s,取值范围:%s/%s/%s。", DefaultStorageClass, StorageStandard, StorageIA, StorageArchive),
- fmt.Sprintf("set the storage class of bucket(default: %s), value range is: %s/%s/%s.", DefaultStorageClass, StorageStandard, StorageIA, StorageArchive)},
+ OptionStorageClass: Option{"", "--storage-class", DefaultStorageClass, OptionTypeAlternative, fmt.Sprintf("%s/%s/%s/%s", StorageStandard, StorageIA, StorageArchive, StorageColdArchive), "",
+ fmt.Sprintf("设置对象的存储方式,默认值:%s,取值范围:%s/%s/%s/%s。", DefaultStorageClass, StorageStandard, StorageIA, StorageArchive, StorageColdArchive),
+ fmt.Sprintf("set the storage class of bucket(default: %s), value range is: %s/%s/%s/%s.", DefaultStorageClass, StorageStandard, StorageIA, StorageArchive, StorageColdArchive)},
OptionForce: Option{"-f", "--force", "", OptionTypeFlagTrue, "", "", "强制操作,不进行询问提示。", "operate silently without asking user to confirm the operation."},
OptionUpdate: Option{"-u", "--update", "", OptionTypeFlagTrue, "", "", "更新操作", "update"},
OptionDelete: Option{"", "--delete", "", OptionTypeFlagTrue, "", "", "删除操作", "delete"},
diff --git a/lib/restore.go b/lib/restore.go
index dc785309..a616c6ba 100644
--- a/lib/restore.go
+++ b/lib/restore.go
@@ -1,7 +1,10 @@
package lib
import (
+ "encoding/xml"
"fmt"
+ "io/ioutil"
+ "os"
"strings"
oss "github.com/aliyun/aliyun-oss-go-sdk/oss"
@@ -16,17 +19,16 @@ var specChineseRestore = SpecText{
synopsisText: "恢复冷冻状态的Objects为可读状态",
- paramText: "cloud_url [options]",
+ paramText: "cloud_url [local_xml_file] [options]",
syntaxText: `
- ossutil restore cloud_url [--encoding-type url] [-r] [-f] [--output-dir=odir] [--version-id versionId] [--payer requester] [-c file]
+ ossutil restore cloud_url [local_xml_file] [--encoding-type url] [-r] [-f] [--output-dir=odir] [--version-id versionId] [--payer requester] [-c file]
`,
detailHelpText: `
- 该命令恢复处于冷冻状态的object进入可读状态,即操作对象object必须为` + StorageArchive + `存储类型
- 的object。
+ 该命令恢复处于冷冻状态的object进入可读状态,即操作对象object的存储类型为StorageArchive、StorageColdArchive
- 一个Archive类型的object初始时处于冷冻状态。
+ 一个Archive、StorageColdArchive类型的object初始时处于冷冻状态。
针对处于冷冻状态的object调用restore命令,返回成功。object处于解冻中,服务端执行解
冻,在此期间再次调用restore命令,同样成功,且不会延长object可读状态持续时间。
@@ -43,18 +45,26 @@ var specChineseRestore = SpecText{
该命令有两种用法:
- 1) ossutil restore oss://bucket/object [--encoding-type url]
+ 1) ossutil restore oss://bucket/object [--encoding-type url] [local_xml_file]
该用法恢复单个冷冻状态object为可读状态,当指定object不存在时,ossutil会提示错
误,此时请确保指定的url精确匹配需要设置acl的object,并且不要指定--recursive选项(
否则ossutil会进行前缀匹配,恢复多个冷冻状态的objects为可读状态)。无论--force选项
是否指定,都不会进行询问提示。
- 2) ossutil restore oss://bucket[/prefix] -r [--encoding-type url] [-f] [--output-dir=odir]
+ 2) ossutil restore oss://bucket[/prefix] -r [--encoding-type url] [-f] [--output-dir=odir] [local_xml_file]
该用法可批量恢复多个冷冻状态的objects为可读状态,此时必须输入--recursive选项,
ossutil会查找所有前缀匹配url的objects,恢复它们为可读状态。当一个object操作出现错
误时,会将出错object的错误信息记录到report文件,并继续操作其他object,成功操作的
object信息将不会被记录到report文件中(更多信息见cp命令的帮助)。如果--force选项被
指定,则不会进行询问提示。
+
+ 上面的local_xml_file是本地xml格式文件, 支持设置更多的restore参数, 举例如下
+
+ 2
+
+ Bulk
+
+
`,
sampleText: `
@@ -63,6 +73,7 @@ var specChineseRestore = SpecText{
3) ossutil restore oss://bucket-restore/object-prefix -r -f
4) ossutil restore oss://bucket-restore/%e4%b8%ad%e6%96%87 --encoding-type url
5) ossutil restore oss://bucket-restore/object-store --payer requester
+ 6) ossutil restore oss://bucket-restore/object-prefix -r -f local_xml_file
`,
}
@@ -70,15 +81,15 @@ var specEnglishRestore = SpecText{
synopsisText: "Restore Frozen State Object to Read Ready Status",
- paramText: "cloud_url [options]",
+ paramText: "cloud_url [local_xml_file] [options]",
syntaxText: `
- ossutil restore cloud_url [--encoding-type url] [-r] [-f] [--output-dir=odir] [--version-id versionId] [--payer requester] [-c file]
+ ossutil restore cloud_url [local_xml_file] [--encoding-type url] [-r] [-f] [--output-dir=odir] [--version-id versionId] [--payer requester] [-c file]
`,
detailHelpText: `
The command restore frozen state object to read ready status, the object must be in the storage
- class of ` + StorageArchive + `.
+ class of StorageArchive、StorageColdArchive
An object of Archive storage class will be in frozen state at first.
@@ -99,19 +110,27 @@ Usage:
There are two usages:
- 1) ossutil restore oss://bucket/object [--encoding-type url]
+ 1) ossutil restore oss://bucket/object [--encoding-type url] [local_xml_file]
If --recursive option is not specified, ossutil restore the specified frozen state object
to readable status. In the usage, please make sure url exactly specified the object you want to
restore, if object not exist, error occurs. No matter --force option is specified or not, ossutil
will not show prompt question.
- 2) ossutil restore oss://bucket[/prefix] -r [--encoding-type url] [-f] [--output-dir=odir]
+ 2) ossutil restore oss://bucket[/prefix] -r [--encoding-type url] [-f] [--output-dir=odir] [local_xml_file]
The usage restore the objects with the specified prefix and in frozen state to readable status.
--recursive option is required for the usage, and ossutil will search for prefix-matching objects
and restore those objects. When an error occurs when restore an object, ossutil will record the
error message to report file, and ossutil will continue to attempt to set acl on the remaining
objects(more information see help of cp command). If --force option is specified, ossutil will
not show prompt question.
+
+ The local_xml_file is a local XML format file, which supports setting more restore configurations. For example:
+
+ 2
+
+ Bulk
+
+
`,
sampleText: `
@@ -120,6 +139,7 @@ Usage:
3) ossutil restore oss://bucket-restore/object-prefix -r -f
4) ossutil restore oss://bucket-restore/%e4%b8%ad%e6%96%87 --encoding-type url
5) ossutil restore oss://bucket-restore/object-store --payer requester
+ 6) ossutil restore oss://bucket-restore/object-prefix -r -f local_xml_file
`,
}
@@ -129,6 +149,8 @@ type RestoreCommand struct {
command Command
reOption batchOptionType
commonOptions []oss.Option
+ restoreConfig oss.RestoreConfiguration
+ hasConfig bool
}
var restoreCommand = RestoreCommand{
@@ -136,7 +158,7 @@ var restoreCommand = RestoreCommand{
name: "restore",
nameAlias: []string{},
minArgc: 1,
- maxArgc: 1,
+ maxArgc: 2,
specChinese: specChineseRestore,
specEnglish: specEnglishRestore,
group: GroupTypeNormalCommand,
@@ -201,6 +223,40 @@ func (rc *RestoreCommand) RunCommand() error {
return err
}
+ if len(rc.command.args) == 2 {
+ xmlFile := rc.command.args[1]
+ fileInfo, err := os.Stat(xmlFile)
+ if err != nil {
+ return err
+ }
+
+ if fileInfo.IsDir() {
+ return fmt.Errorf("%s is dir,not the expected file", xmlFile)
+ }
+
+ if fileInfo.Size() == 0 {
+ return fmt.Errorf("%s is empty file", xmlFile)
+ }
+
+ // parsing the xml file
+ file, err := os.Open(xmlFile)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+ text, err := ioutil.ReadAll(file)
+ if err != nil {
+ return err
+ }
+
+ err = xml.Unmarshal(text, &(rc.restoreConfig))
+ if err != nil {
+ fmt.Printf("xml.Unmarshal error,%s is not valid xml file\n", xmlFile)
+ return err
+ }
+ rc.hasConfig = true
+ }
+
bucket, err := rc.command.ossBucket(cloudURL.bucket)
if err != nil {
return err
@@ -234,7 +290,13 @@ func (rc *RestoreCommand) ossRestoreObject(bucket *oss.Bucket, object string, ve
}
options = append(options, rc.commonOptions...)
- err := bucket.RestoreObject(object, options...)
+ var err error
+ if rc.hasConfig {
+ err = bucket.RestoreObjectDetail(object, rc.restoreConfig, options...)
+ } else {
+ err = bucket.RestoreObject(object, options...)
+ }
+
if err == nil {
return err
}
diff --git a/lib/restore_test.go b/lib/restore_test.go
index 335803fd..1c1f3033 100644
--- a/lib/restore_test.go
+++ b/lib/restore_test.go
@@ -1,10 +1,13 @@
package lib
import (
+ "encoding/xml"
"fmt"
"net/url"
+ "os"
"strings"
+ oss "github.com/aliyun/aliyun-oss-go-sdk/oss"
. "gopkg.in/check.v1"
)
@@ -415,3 +418,102 @@ func (s *OssutilCommandSuite) TestRestoreObjectWithPayerError400(c *C) {
c.Assert(err, NotNil)
c.Assert(strings.Contains(err.Error(), "StatusCode=400"), Equals, true)
}
+
+func (s *OssutilCommandSuite) TestRestoreObjectWithConfigSuccess(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageColdArchive, c)
+
+ // put object to archive bucket
+ objectName := "ossutil_test_object" + randStr(5)
+ testFileName := "ossutil_test_file" + randStr(5)
+
+ data := randStr(20)
+ s.createFile(testFileName, data, c)
+ s.putObject(bucketName, objectName, testFileName, c)
+ os.Remove(testFileName)
+
+ // get object status
+ objectStat := s.getStat(bucketName, objectName, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageColdArchive)
+ _, ok := objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ restoreXml := `
+
+ 2
+
+ Bulk
+
+ `
+
+ rulesConfigSrc := oss.RestoreConfiguration{}
+ err := xml.Unmarshal([]byte(restoreXml), &rulesConfigSrc)
+ c.Assert(err, IsNil)
+
+ restoreFileName := "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreFileName, restoreXml, c)
+
+ //restore command test
+ var str string
+ options := OptionMapType{
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "configFile": &configFile,
+ }
+
+ restoreArgs := []string{CloudURLToString(bucketName, objectName), restoreFileName}
+ _, err = cm.RunCommand("restore", restoreArgs, options)
+ c.Assert(err, IsNil)
+
+ // get object status
+ objectStat = s.getStat(bucketName, objectName, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageColdArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ os.Remove(restoreFileName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestRestoreObjectWithConfigError(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageColdArchive, c)
+
+ // file is not exist
+ objectName := "test-ossutil-" + randLowStr(12)
+ restoreFileName := "test-ossutil-" + randLowStr(12)
+
+ //restore command test
+ var str string
+ options := OptionMapType{
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "configFile": &configFile,
+ }
+
+ restoreArgs := []string{CloudURLToString(bucketName, objectName), restoreFileName}
+ _, err := cm.RunCommand("restore", restoreArgs, options)
+ c.Assert(err, NotNil)
+
+ // empty file
+ s.createFile(restoreFileName, "", c)
+ _, err = cm.RunCommand("restore", restoreArgs, options)
+ c.Assert(err, NotNil)
+
+ // invalid xml file
+ os.Remove(restoreFileName)
+ s.createFile(restoreFileName, "abc", c)
+ _, err = cm.RunCommand("restore", restoreArgs, options)
+ c.Assert(err, NotNil)
+
+ // is dir
+ os.Remove(restoreFileName)
+ os.MkdirAll(restoreFileName, 0755)
+ _, err = cm.RunCommand("restore", restoreArgs, options)
+ c.Assert(err, NotNil)
+ os.RemoveAll(restoreFileName)
+ s.removeBucket(bucketName, true, c)
+}
diff --git a/lib/update.go b/lib/update.go
index 444282a0..8980a202 100644
--- a/lib/update.go
+++ b/lib/update.go
@@ -128,6 +128,12 @@ func (uc *UpdateCommand) RunCommand() error {
} else {
fmt.Printf("当前版本为:%s,最新版本为:%s", vVersion, version)
}
+
+ // version is X.X.X
+ // vVersion is vX.X.X
+ if vVersion[0] < '0' || vVersion[0] > '9' {
+ vVersion = vVersion[1:len(vVersion)]
+ }
if version == vVersion {
if language == LEnglishLanguage {
fmt.Println(", current version is the lastest version, no need to update.")