diff --git a/lib/command_test.go b/lib/command_test.go
index a4043339..9a3197e1 100644
--- a/lib/command_test.go
+++ b/lib/command_test.go
@@ -58,6 +58,7 @@ var (
downloadFileName = "ossutil_test.download_file" + randStr(5)
downloadDir = "ossutil_test.download_dir" + randStr(5)
inputFileName = "ossutil_test.input_file" + randStr(5)
+ objectFileName = "ossutil_test.object_file" + randStr(5)
content = "abc"
cm = CommandManager{}
out = os.Stdout
@@ -1415,6 +1416,16 @@ func (s *OssutilCommandSuite) initSetMetaWithArgs(args []string, cmdline string,
cmdline = cmdline[0:pos] + cmdline[pos+len("--delete"):]
}
+ var disableIgnoreError bool
+ if pos := strings.Index(cmdline, "--disable-ignore-error"); pos != -1 {
+ disableIgnoreError = true
+ cmdline = cmdline[0:pos] + cmdline[pos+len("--disable-ignore-error"):]
+ }
+
+ objectFile := ""
+ snapshotPath := ""
+ objectFile, snapshotPath, cmdline = s.handleObjectFileSnapshot(cmdline)
+
parameter := strings.Split(cmdline, "-")
r := false
f := false
@@ -1426,17 +1437,20 @@ func (s *OssutilCommandSuite) initSetMetaWithArgs(args []string, cmdline string,
str := ""
routines := strconv.Itoa(Routines)
options := OptionMapType{
- "endpoint": &str,
- "accessKeyID": &str,
- "accessKeySecret": &str,
- "stsToken": &str,
- "configFile": &configFile,
- "update": &update,
- "delete": &delete,
- "recursive": &r,
- "force": &f,
- "routines": &routines,
- "encodingType": &encodingType,
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "configFile": &configFile,
+ "update": &update,
+ "delete": &delete,
+ "recursive": &r,
+ "force": &f,
+ "routines": &routines,
+ "encodingType": &encodingType,
+ "objectFile": &objectFile,
+ "snapshotPath": &snapshotPath,
+ "disableIgnoreError": &disableIgnoreError,
}
err := setMetaCommand.Init(args, options)
return err
@@ -1582,6 +1596,33 @@ func (s *OssutilCommandSuite) getFileList(dpath string) ([]string, error) {
return fileList, err
}
+func (s *OssutilCommandSuite) handleObjectFileSnapshot(cmdline string) (objectFile, snapshotPath, newCmdline string) {
+ cmds := strings.Split(cmdline, " ")
+ for i := 0; i < len(cmds); i++ {
+ if cmds[i] == "--object-file" && !strings.HasPrefix(cmds[i+1], "-") {
+ objectFile = cmds[i+1]
+ } else {
+ continue
+ }
+ cmds = append(cmds[:i], cmds[i+2:]...)
+ }
+ for i := 0; i < len(cmds); i++ {
+ if cmds[i] == "--snapshot-path" && !strings.HasPrefix(cmds[i+1], "-") {
+ snapshotPath = cmds[i+1]
+ } else {
+ continue
+ }
+ cmds = append(cmds[:i], cmds[i+2:]...)
+ }
+
+ // remake cmdline
+ cmdline = ""
+ for i := 0; i < len(cmds); i++ {
+ cmdline = cmdline + " " + cmds[i]
+ }
+ return objectFile, snapshotPath, cmdline
+}
+
func (s *OssutilCommandSuite) initRestoreObject(args []string, cmdline string, outputDir string) error {
encodingType := ""
if pos := strings.Index(cmdline, "--encoding-type url"); pos != -1 {
@@ -1589,6 +1630,16 @@ func (s *OssutilCommandSuite) initRestoreObject(args []string, cmdline string, o
cmdline = cmdline[0:pos] + cmdline[pos+len("--encoding-type url"):]
}
+ var disableIgnoreError bool
+ if pos := strings.Index(cmdline, "--disable-ignore-error"); pos != -1 {
+ disableIgnoreError = true
+ cmdline = cmdline[0:pos] + cmdline[pos+len("--disable-ignore-error"):]
+ }
+
+ objectFile := ""
+ snapshotPath := ""
+ objectFile, snapshotPath, cmdline = s.handleObjectFileSnapshot(cmdline)
+
parameter := strings.Split(cmdline, "-")
r := false
f := false
@@ -1600,16 +1651,19 @@ func (s *OssutilCommandSuite) initRestoreObject(args []string, cmdline string, o
str := ""
routines := strconv.Itoa(Routines)
options := OptionMapType{
- "endpoint": &str,
- "accessKeyID": &str,
- "accessKeySecret": &str,
- "stsToken": &str,
- "configFile": &configFile,
- "recursive": &r,
- "force": &f,
- "encodingType": &encodingType,
- "routines": &routines,
- "outputDir": &outputDir,
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "configFile": &configFile,
+ "recursive": &r,
+ "force": &f,
+ "encodingType": &encodingType,
+ "routines": &routines,
+ "outputDir": &outputDir,
+ "objectFile": &objectFile,
+ "snapshotPath": &snapshotPath,
+ "disableIgnoreError": &disableIgnoreError,
}
err := restoreCommand.Init(args, options)
return err
diff --git a/lib/const.go b/lib/const.go
index a61ecd14..0c62e407 100644
--- a/lib/const.go
+++ b/lib/const.go
@@ -99,6 +99,7 @@ const (
OptionSkipVerfiyCert = "skipVerifyCert"
OptionItem = "item"
OptionUserAgent = "userAgent"
+ OptionObjectFile = "objectFile"
)
// the elements show in stat object
diff --git a/lib/monitor.go b/lib/monitor.go
index 29d91e7c..8f33cacf 100644
--- a/lib/monitor.go
+++ b/lib/monitor.go
@@ -36,6 +36,7 @@ type Monitorer interface {
type MonitorSnap struct {
okNum int64
errNum int64
+ skipNum int64
dealNum int64
}
@@ -49,6 +50,7 @@ type Monitor struct {
totalNum int64
okNum int64
errNum int64
+ skipNum int64
seekAheadError error
seekAheadEnd bool
finish bool
@@ -62,6 +64,7 @@ func (m *Monitor) init(opStr string) {
m.seekAheadError = nil
m.okNum = 0
m.errNum = 0
+ m.skipNum = 0
m.finish = false
}
@@ -90,6 +93,7 @@ func (m *Monitor) getSnapshot() *MonitorSnap {
var snap MonitorSnap
snap.okNum = m.okNum
snap.errNum = m.errNum
+ snap.skipNum = m.skipNum
snap.dealNum = snap.okNum + snap.errNum
return &snap
}
@@ -141,24 +145,24 @@ func (m *Monitor) getWholeFinishBar() string {
snap := m.getSnapshot()
if m.seekAheadEnd && m.seekAheadError == nil {
if snap.errNum == 0 {
- return getClearStr(fmt.Sprintf("Succeed: Total %d objects. %s %d objects.\n", m.totalNum, m.opStr, snap.okNum))
+ return getClearStr(fmt.Sprintf("Succeed: Total %d objects. %s %d objects(skip %d objects).\n", m.totalNum, m.opStr, snap.okNum, snap.skipNum))
}
- return getClearStr(fmt.Sprintf("FinishWithError: Total %d objects. %s %d objects, Error %d objects.\n", m.totalNum, m.opStr, snap.okNum, snap.errNum))
+ return getClearStr(fmt.Sprintf("FinishWithError: Total %d objects. %s %d objects(skip %d objects), Error %d objects.\n", m.totalNum, m.opStr, snap.okNum, snap.skipNum, snap.errNum))
}
scanNum := max(m.totalNum, snap.dealNum)
if snap.errNum == 0 {
- return getClearStr(fmt.Sprintf("Succeed: Total %d objects. %s %d objects.\n", scanNum, m.opStr, snap.okNum))
+ return getClearStr(fmt.Sprintf("Succeed: Total %d objects. %s %d objects(skip %d objects).\n", scanNum, m.opStr, snap.okNum, snap.skipNum))
}
- return getClearStr(fmt.Sprintf("FinishWithError: Scanned %d objects. %s %d objects, Error %d objects.\n", scanNum, m.opStr, snap.okNum, snap.errNum))
+ return getClearStr(fmt.Sprintf("FinishWithError: Scanned %d objects. %s %d objects(skip %d objects), Error %d objects.\n", scanNum, m.opStr, snap.okNum, snap.skipNum, snap.errNum))
}
func (m *Monitor) getDefeatBar() string {
snap := m.getSnapshot()
if m.seekAheadEnd && m.seekAheadError == nil {
- return getClearStr(fmt.Sprintf("Total %d objects. %s %d objects, when error happens.\n", m.totalNum, m.opStr, snap.okNum))
+ return getClearStr(fmt.Sprintf("Total %d objects. %s %d objects(skip %d objects), when error happens.\n", m.totalNum, m.opStr, snap.okNum, snap.skipNum))
}
scanNum := max(m.totalNum, snap.dealNum)
- return getClearStr(fmt.Sprintf("Scanned %d objects. %s %d objects, when error happens.\n", scanNum, m.opStr, snap.okNum))
+ return getClearStr(fmt.Sprintf("Scanned %d objects. %s %d objects(skip %d objects), when error happens.\n", scanNum, m.opStr, snap.okNum, snap.skipNum))
}
// For rm
diff --git a/lib/option.go b/lib/option.go
index 56e528af..5fa7afb7 100644
--- a/lib/option.go
+++ b/lib/option.go
@@ -265,6 +265,9 @@ var OptionMap = map[string]Option{
OptionUserAgent: Option{"", "--ua", "", OptionTypeString, "", "",
"指定http请求中的user agent, 会在缺省值后面加上指定值",
"Specify the user agent in the http request, and the specified value will be added after the default value"},
+ OptionObjectFile: Option{"", "--object-file", "", OptionTypeString, "", "",
+ "表示所有待处理的objects,取值为一个存在的文件路径",
+ "Specify all the objects that need to be operated, and the specified value should be a exists file path"},
}
func (T *Option) getHelp(language string) string {
diff --git a/lib/restore.go b/lib/restore.go
index fabfec5d..dd57d38b 100644
--- a/lib/restore.go
+++ b/lib/restore.go
@@ -1,17 +1,22 @@
package lib
import (
+ "bufio"
"fmt"
+ oss "github.com/aliyun/aliyun-oss-go-sdk/oss"
+ "github.com/syndtr/goleveldb/leveldb"
"io/ioutil"
"os"
"strings"
-
- oss "github.com/aliyun/aliyun-oss-go-sdk/oss"
+ "sync/atomic"
+ "time"
)
type batchOptionType struct {
- ctnu bool
- reporter *Reporter
+ ctnu bool
+ reporter *Reporter
+ snapshotPath string
+ snapshotldb *leveldb.DB
}
var specChineseRestore = SpecText{
@@ -21,7 +26,7 @@ var specChineseRestore = SpecText{
paramText: "cloud_url [local_xml_file] [options]",
syntaxText: `
- ossutil restore cloud_url [local_xml_file] [--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] [--object-file file] [--snapshot-path dir] [--disable-ignore-error]
`,
detailHelpText: `
@@ -64,6 +69,18 @@ var specChineseRestore = SpecText{
Bulk
+
+ 3) ossutil restore oss://bucket --object-file file [--snapshot-path dir] [--disable-ignore-error] [--output-dir=odir] [local_xml_file]
+ 该用法可批量恢复多个冷冻状态的objects为可读状态,与-r的区别在于,-r匹配所有符合
+ 指定前缀的objects,而--object-file指定对某些object进行处理。ossutil会读取文件中
+ 的objects,恢复它们为可读状态。当一个object操作出现错误时,会将出错object的错误信息
+ 记录到report文件,并继续操作其他object,成功操作的object信息将不会被记录到report
+ 文件中(更多信息见cp命令的帮助)。如果--force选项被指定,则不会进行询问提示。
+
+ 上面的file是本地文件, 里面罗列了所有需要处理的object,之间以"\n"隔开, 举例如下
+ object1
+ object2
+ object3
`,
sampleText: `
@@ -73,6 +90,8 @@ var specChineseRestore = SpecText{
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
+ 7) ossutil restore oss://bucket-restore --object-file file -f local_xml_file
+ 8) ossutil restore oss://bucket-restore --object-file file --snapshot-path dir -f local_xml_file
`,
}
@@ -83,7 +102,7 @@ var specEnglishRestore = SpecText{
paramText: "cloud_url [local_xml_file] [options]",
syntaxText: `
- ossutil restore cloud_url [local_xml_file] [--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] [--object-file file] [--snapshot-path dir] [--disable-ignore-error]
`,
detailHelpText: `
@@ -130,6 +149,20 @@ Usage:
Bulk
+
+ 3) ossutil restore oss://bucket --object-file file [--snapshot-path dir] [--disable-ignore-error] [--output-dir=odir] [local_xml_file]
+ The usage restore the objects with the specified prefix and in frozen state to readable status.
+ The difference with -r is that -r matches all objects that match the specified prefix, while
+ --object-file specifies some objects to be restored. Ossutil reads the objects in the file and
+ restores them to a readable state. 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 file is a local file, which lists all the objects to be restored, separated by "\n". For example:
+ object1
+ object2
+ object3
`,
sampleText: `
@@ -139,6 +172,8 @@ Usage:
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
+ 7) ossutil restore oss://bucket-restore --object-file file -f local_xml_file
+ 8) ossutil restore oss://bucket-restore --object-file file --snapshot-path dir -f local_xml_file
`,
}
@@ -151,6 +186,8 @@ type RestoreCommand struct {
restoreConfig oss.RestoreConfiguration
hasConfig bool
configXml string
+ hasObjFile bool
+ objFilePath string
}
var restoreCommand = RestoreCommand{
@@ -191,6 +228,9 @@ var restoreCommand = RestoreCommand{
OptionSTSRegion,
OptionSkipVerfiyCert,
OptionUserAgent,
+ OptionObjectFile,
+ OptionSnapshotPath,
+ OptionDisableIgnoreError,
},
},
}
@@ -211,11 +251,13 @@ func (rc *RestoreCommand) Init(args []string, options OptionMapType) error {
// RunCommand simulate inheritance, and polymorphism
func (rc *RestoreCommand) RunCommand() error {
- rc.monitor.init("Restored")
-
+ rc.monitor.init("Restore")
encodingType, _ := GetString(OptionEncodingType, rc.command.options)
recursive, _ := GetBool(OptionRecursion, rc.command.options)
versionid, _ := GetString(OptionVersionId, rc.command.options)
+ force, _ := GetBool(OptionForce, rc.command.options)
+ objFileXml, _ := GetString(OptionObjectFile, rc.command.options)
+ snapshotPath, _ := GetString(OptionSnapshotPath, rc.command.options)
payer, _ := GetString(OptionRequestPayer, rc.command.options)
if payer != "" {
@@ -225,78 +267,132 @@ func (rc *RestoreCommand) RunCommand() error {
rc.commonOptions = append(rc.commonOptions, oss.RequestPayer(oss.PayerType(payer)))
}
+ var err error
+ // load snapshot
+ rc.reOption.snapshotPath = snapshotPath
+ if rc.reOption.snapshotPath != "" {
+ if rc.reOption.snapshotldb, err = leveldb.OpenFile(rc.reOption.snapshotPath, nil); err != nil {
+ return fmt.Errorf("load snapshot error, reason: %s", err.Error())
+ }
+ defer rc.reOption.snapshotldb.Close()
+ }
+
cloudURL, err := CloudURLFromString(rc.command.args[0], encodingType)
if err != nil {
return err
}
-
- if err = rc.checkArgs(cloudURL, recursive, versionid); err != nil {
+ if err = rc.checkOptions(cloudURL, recursive, force, versionid, objFileXml); err != nil {
+ if err.Error() == "operation is canceled" {
+ return nil
+ }
+ return err
+ }
+ bucket, err := rc.command.ossBucket(cloudURL.bucket)
+ if err != nil {
return err
}
- if len(rc.command.args) == 2 {
- xmlFile := rc.command.args[1]
- fileInfo, err := os.Stat(xmlFile)
+ // parse restore config
+ if len(rc.command.args) > 1 {
+ restoreConf := rc.command.args[1]
+ err := rc.parseRestoreConf(restoreConf)
if err != nil {
return err
}
+ } else {
+ rc.hasConfig = false
+ }
- if fileInfo.IsDir() {
- return fmt.Errorf("%s is dir,not the expected file", xmlFile)
+ if objFileXml != "" {
+ // check objFileXml and parse it
+ if err := rc.checkObjectFile(objFileXml); err != nil {
+ return err
}
-
- if fileInfo.Size() == 0 {
- return fmt.Errorf("%s is empty file", xmlFile)
+ recursive = true
+ return rc.batchRestoreObjects(bucket, cloudURL, recursive)
+ } else {
+ if !recursive {
+ return rc.ossRestoreObject(bucket, cloudURL.object, versionid, false)
}
- // parsing the xml file
- file, err := os.Open(xmlFile)
- if err != nil {
- return err
+ return rc.batchRestoreObjects(bucket, cloudURL, recursive)
+ }
+}
+
+func (rc *RestoreCommand) checkOptions(cloudURL CloudURL, recursive, force bool, versionid, objectFile string) error {
+ if cloudURL.bucket == "" {
+ return fmt.Errorf("invalid cloud url: %s, miss bucket", cloudURL.urlStr)
+ }
+ if cloudURL.object == "" {
+ if !recursive && objectFile == "" {
+ return fmt.Errorf("restore object invalid cloud url: %s, object empty. Restore bucket is not supported, if you mean batch restore objects, please use --recursive or --object-file", rc.command.args[0])
}
- defer file.Close()
- text, err := ioutil.ReadAll(file)
- if err != nil {
- return err
+ } else {
+ if objectFile != "" {
+ return fmt.Errorf("the first arg of `ossutil restore` only support oss://bucket when set option --object-file.")
}
- rc.hasConfig = true
- rc.configXml = string(text)
}
- bucket, err := rc.command.ossBucket(cloudURL.bucket)
- if err != nil {
- return err
+ if (recursive && len(versionid) > 0) || (objectFile != "" && len(versionid) > 0) {
+ return fmt.Errorf("restore bucket dose not support the --version-id=%s argument.", versionid)
}
- if !recursive {
- return rc.ossRestoreObject(bucket, cloudURL.object, versionid)
+ if !force {
+ var val string
+ if recursive || objectFile != "" {
+ fmt.Printf("Do you really mean to recursivlly restore objects of %s(y or N)? ", rc.command.args[0])
+ if _, err := fmt.Scanln(&val); err != nil || (strings.ToLower(val) != "yes" && strings.ToLower(val) != "y") {
+ return fmt.Errorf("operation is canceled")
+ }
+ }
}
- return rc.batchRestoreObjects(bucket, cloudURL)
+
+ return nil
}
-func (rc *RestoreCommand) checkArgs(cloudURL CloudURL, recursive bool, versionid string) error {
- if cloudURL.bucket == "" {
- return fmt.Errorf("invalid cloud url: %s, miss bucket", rc.command.args[0])
+func (rc *RestoreCommand) ossRestoreObject(bucket *oss.Bucket, object, versionid string, batchOperate bool) error {
+ nowt := time.Now().Unix()
+ spath := ""
+ msg := "restore"
+
+ if batchOperate && rc.reOption.snapshotPath != "" {
+ spath = rc.formatSnapshotKey(bucket.BucketName, object, msg)
+ if skip := rc.skipRestore(spath); skip {
+ rc.updateSkip(1)
+ LogInfo("restore obj skip: %s\n", object)
+ return nil
+ }
}
- if !recursive && cloudURL.object == "" {
- return fmt.Errorf("restore object invalid cloud url: %s, object empty. Restore bucket is not supported, if you mean batch restore objects, please use --recursive", rc.command.args[0])
+
+ var options []oss.Option
+ if len(versionid) > 0 {
+ options = append(options, oss.VersionId(versionid))
}
- if recursive && len(versionid) > 0 {
- return fmt.Errorf("restore bucket dose not support the --version-id=%s argument.", versionid)
+ options = append(options, rc.commonOptions...)
+
+ err := rc.ossRestoreObjectRetry(bucket, object, options...)
+ if batchOperate && rc.reOption.snapshotPath != "" {
+ if err != nil {
+ _ = rc.updateSnapshot(err, spath, nowt)
+ return err
+ } else {
+ err = rc.updateSnapshot(err, spath, nowt)
+ if err != nil {
+ return err
+ }
+ }
+ } else {
+ return err
}
+
return nil
}
-func (rc *RestoreCommand) ossRestoreObject(bucket *oss.Bucket, object string, versionid string) error {
+func (rc *RestoreCommand) ossRestoreObjectRetry(bucket *oss.Bucket, object string, options ...oss.Option) error {
+ var err error
retryTimes, _ := GetInt(OptionRetryTimes, rc.command.options)
- for i := 1; ; i++ {
- var options []oss.Option
- if len(versionid) > 0 {
- options = append(options, oss.VersionId(versionid))
- }
- options = append(options, rc.commonOptions...)
- var err error
+ for i := 1; ; i++ {
if rc.hasConfig {
err = bucket.RestoreObjectXML(object, rc.configXml, options...)
} else {
@@ -304,7 +400,7 @@ func (rc *RestoreCommand) ossRestoreObject(bucket *oss.Bucket, object string, ve
}
if err == nil {
- return err
+ return nil
}
switch err.(type) {
@@ -320,18 +416,12 @@ func (rc *RestoreCommand) ossRestoreObject(bucket *oss.Bucket, object string, ve
}
}
-func (rc *RestoreCommand) batchRestoreObjects(bucket *oss.Bucket, cloudURL CloudURL) error {
- force, _ := GetBool(OptionForce, rc.command.options)
- if !force {
- var val string
- fmt.Printf("Do you really mean to recursivlly restore objects of %s(y or N)? ", rc.command.args[0])
- if _, err := fmt.Scanln(&val); err != nil || (strings.ToLower(val) != "yes" && strings.ToLower(val) != "y") {
- fmt.Println("operation is canceled.")
- return nil
- }
- }
-
+func (rc *RestoreCommand) batchRestoreObjects(bucket *oss.Bucket, cloudURL CloudURL, recursive bool) error {
rc.reOption.ctnu = true
+ if recursive || rc.hasObjFile {
+ disableIgnoreError, _ := GetBool(OptionDisableIgnoreError, rc.command.options)
+ rc.reOption.ctnu = !disableIgnoreError
+ }
outputDir, _ := GetString(OptionOutputDir, rc.command.options)
// init reporter
@@ -341,12 +431,14 @@ func (rc *RestoreCommand) batchRestoreObjects(bucket *oss.Bucket, cloudURL Cloud
}
defer rc.reOption.reporter.Clear()
+ if rc.hasObjFile {
+ return rc.restoreObjectsFromFile(bucket, cloudURL, rc.objFilePath)
+ }
return rc.restoreObjects(bucket, cloudURL)
}
func (rc *RestoreCommand) restoreObjects(bucket *oss.Bucket, cloudURL CloudURL) error {
routines, _ := GetInt(OptionRoutines, rc.command.options)
-
chObjects := make(chan string, ChannelBuf)
chError := make(chan error, routines+1)
chListError := make(chan error, 1)
@@ -359,6 +451,69 @@ func (rc *RestoreCommand) restoreObjects(bucket *oss.Bucket, cloudURL CloudURL)
return rc.waitRoutinueComplete(chError, chListError, routines)
}
+func (rc *RestoreCommand) restoreObjectsFromFile(bucket *oss.Bucket, cloudURL CloudURL, objectFile string) error {
+ routines, _ := GetInt(OptionRoutines, rc.command.options)
+
+ chObjects := make(chan string, ChannelBuf)
+ chError := make(chan error, routines+1)
+ chListError := make(chan error, 1)
+ go rc.restoreStatistic(objectFile, &rc.monitor, []filterOptionType{}, rc.commonOptions...)
+ go rc.restoreProducer(objectFile, chObjects, chListError, []filterOptionType{}, rc.commonOptions...)
+ for i := 0; int64(i) < routines; i++ {
+ go rc.restoreConsumer(bucket, cloudURL, chObjects, chError)
+ }
+ return rc.waitRoutinueComplete(chError, chListError, routines)
+}
+
+func (rc *RestoreCommand) restoreStatistic(objectFile string, monitor Monitorer, filters []filterOptionType, options ...oss.Option) {
+ if monitor == nil {
+ return
+ }
+
+ file, err := os.Open(objectFile)
+ if err != nil {
+ monitor.setScanError(err)
+ return
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ object := scanner.Text()
+ object = strings.Trim(object, " ")
+ if object == "" {
+ monitor.setScanError(fmt.Errorf("object can't be '' in --object-file"))
+ return
+ }
+ monitor.updateScanNum(1)
+ }
+
+ monitor.setScanEnd()
+}
+
+func (rc *RestoreCommand) restoreProducer(objectFile string, chObjects chan<- string, chError chan<- error, filters []filterOptionType, options ...oss.Option) {
+ file, err := os.Open(objectFile)
+ if err != nil {
+ chError <- err
+ return
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ object := scanner.Text()
+ object = strings.Trim(object, " ")
+ if object == "" {
+ chError <- fmt.Errorf("object can't be '' in --object-file")
+ break
+ }
+ chObjects <- object
+ }
+
+ defer close(chObjects)
+ chError <- nil
+}
+
func (rc *RestoreCommand) restoreConsumer(bucket *oss.Bucket, cloudURL CloudURL, chObjects <-chan string, chError chan<- error) {
for object := range chObjects {
err := rc.restoreObjectWithReport(bucket, object)
@@ -375,7 +530,7 @@ func (rc *RestoreCommand) restoreConsumer(bucket *oss.Bucket, cloudURL CloudURL,
}
func (rc *RestoreCommand) restoreObjectWithReport(bucket *oss.Bucket, object string) error {
- err := rc.ossRestoreObject(bucket, object, "")
+ err := rc.ossRestoreObject(bucket, object, "", true)
rc.command.updateMonitor(err, &rc.monitor)
msg := fmt.Sprintf("restore %s", CloudURLToString(bucket.BucketName, object))
rc.command.report(msg, err, &rc.reOption)
@@ -414,3 +569,80 @@ func (rc *RestoreCommand) formatResultPrompt(err error) error {
}
return err
}
+
+func (rc *RestoreCommand) parseRestoreConf(xmlFile string) error {
+ 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
+ }
+
+ rc.hasConfig = true
+ rc.configXml = string(text)
+ return nil
+}
+
+func (rc *RestoreCommand) formatSnapshotKey(bucket, object, msg string) string {
+ return CloudURLToString(bucket, object) + SnapshotConnector + msg
+}
+
+func (rc *RestoreCommand) skipRestore(spath string) bool {
+ if rc.reOption.snapshotPath != "" {
+ _, err := rc.reOption.snapshotldb.Get([]byte(spath), nil)
+ if err == nil {
+ return true
+ }
+ }
+ return false
+}
+
+func (rc *RestoreCommand) updateSnapshot(err error, spath string, srct int64) error {
+ if rc.reOption.snapshotPath != "" && err == nil {
+ srctstr := fmt.Sprintf("%d", srct)
+ err := rc.reOption.snapshotldb.Put([]byte(spath), []byte(srctstr), nil)
+ if err != nil {
+ return fmt.Errorf("dump snapshot error: %s", err.Error())
+ }
+ }
+ return nil
+}
+
+func (rc *RestoreCommand) checkObjectFile(objFileXml string) error {
+ // check file if exists
+ fileInfo, err := os.Stat(objFileXml)
+ if err != nil {
+ return err
+ }
+ if fileInfo.IsDir() {
+ return fmt.Errorf("%s is dir, not the expected file", objFileXml)
+ }
+ if fileInfo.Size() == 0 {
+ return fmt.Errorf("%s is empty file", objFileXml)
+ }
+
+ rc.hasObjFile = true
+ rc.objFilePath = objFileXml
+ return nil
+}
+
+func (rc *RestoreCommand) updateSkip(num int64) {
+ atomic.AddInt64(&rc.monitor.skipNum, num)
+}
diff --git a/lib/restore_test.go b/lib/restore_test.go
index 378a34c6..3f9d644d 100644
--- a/lib/restore_test.go
+++ b/lib/restore_test.go
@@ -3,19 +3,18 @@ package lib
import (
"encoding/xml"
"fmt"
+ oss "github.com/aliyun/aliyun-oss-go-sdk/oss"
"net/url"
"os"
+ "strconv"
"strings"
-
- oss "github.com/aliyun/aliyun-oss-go-sdk/oss"
- . "gopkg.in/check.v1"
)
func (s *OssutilCommandSuite) TestRestoreObject(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
s.putBucketWithStorageClass(bucketName, StorageArchive, c)
- // put object to archive bucket
+ //put object to archive bucket
object := "恢复文件" + randStr(5)
data := randStr(20)
s.createFile(uploadFileName, data, c)
@@ -64,6 +63,647 @@ func (s *OssutilCommandSuite) TestRestoreObject(c *C) {
s.removeBucket(bucketName, true, c)
}
+func (s *OssutilCommandSuite) TestRestoreObjectErrorObj(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageColdArchive, c)
+ bucketNameIA := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketNameIA, StorageIA, c)
+
+ //put object to cold archive bucket
+ object := "恢复文件" + randStr(5)
+ s.putObject(bucketName, object, uploadFileName, c)
+ s.putObject(bucketNameIA, object, uploadFileName, c)
+
+ objectStat := s.getStat(bucketName, object, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageColdArchive)
+ _, ok := objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketNameIA, object, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageIA)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ // not exist bucket
+ err := s.initRestoreObject([]string{CloudURLToString("xx", object)}, "", DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // not exist object
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, object+"xx")}, "", DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+ c.Assert(strings.Contains(err.Error(), "StatusCode=404"), Equals, true)
+
+ // bucket is none
+ err = s.initRestoreObject([]string{CloudURLToString("", "")}, "", DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // error days
+ restoreXml := `
+
+ 99
+
+ xxx
+
+ `
+ restoreConfName := "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreConfName, restoreXml, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, object), restoreConfName}, "", DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // restore object(class ia)
+ err = s.initRestoreObject([]string{CloudURLToString(bucketNameIA, object)}, "", DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ s.removeBucket(bucketName, true, c)
+ s.removeBucket(bucketNameIA, true, c)
+}
+
+func (s *OssutilCommandSuite) TestRestoreObjectFileBasic(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageArchive, c)
+
+ data := randStr(20)
+ s.createFile(uploadFileName, data, c)
+ object1 := "restore" + randStr(5)
+ object2 := "restore" + randStr(5)
+ object3 := "restore" + randStr(5)
+ object4 := "restore" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+ s.putObject(bucketName, object3, uploadFileName, c)
+ s.putObject(bucketName, object4, uploadFileName, c)
+
+ objectStat := s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok := objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ content := object1 + "\n" + object2 + "\n" + object3 + "\n" + object4
+ s.createFile(objectFileName, content, c)
+
+ err := s.initRestoreObject([]string{CloudURLToString(bucketName, "")}, fmt.Sprintf("--object-file %s -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ objectStat = s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ objectStat = s.getStat(bucketName, object3, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ objectStat = s.getStat(bucketName, object4, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ os.Remove(objectFileName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestRestoreObjectFileErrorObjFile(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageArchive, c)
+ bucketNameIA := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketNameIA, StorageIA, c)
+
+ data := randStr(20)
+ s.createFile(uploadFileName, data, c)
+ object1 := "restore" + randStr(5)
+ object2 := "restore" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+ s.putObject(bucketNameIA, object1, uploadFileName, c)
+
+ objectStat := s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok := objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketNameIA, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageIA)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ // file size 0
+ s.createFile(objectFileName, "", c)
+
+ err := s.initRestoreObject([]string{CloudURLToString(bucketName, "")}, fmt.Sprintf("--object-file %s -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+
+ // receive file but give dir
+ err = os.Mkdir(fmt.Sprintf("./%s", objectFileName), os.ModePerm)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, "")}, fmt.Sprintf("--object-file %s -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+
+ // not exist file
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, "")}, fmt.Sprintf("--object-file %s -f", objectFileName+"xxx"), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // without -f
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, "")}, fmt.Sprintf("--object-file %s", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ objectStat = s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketNameIA, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageIA)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ os.Remove(objectFileName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestRestoreObjectFileErrVer(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageArchive, c)
+
+ object1 := "restore" + randStr(5)
+ object2 := "restore" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ command := "restore"
+ args := []string{CloudURLToString(bucketName, "")}
+ versionId := "xxx"
+ str := ""
+ routines := strconv.Itoa(Routines)
+ options := OptionMapType{
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "configFile": &configFile,
+ "versionId": &versionId,
+ "routines": &routines,
+ "objectFile": &objectFileName,
+ }
+ _, err := cm.RunCommand(command, args, options)
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestRestoreObjectFileWithConfCA(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageColdArchive, c)
+
+ // put object to coldArchive bucket
+ object1 := "restore" + randStr(5)
+ object2 := "restore" + randStr(5)
+ object3 := "restore" + randStr(5)
+ object4 := "restore" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+ s.putObject(bucketName, object3, uploadFileName, c)
+ s.putObject(bucketName, object4, uploadFileName, c)
+
+ // get object status
+ objectStat := s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageColdArchive)
+ _, ok := objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object2, 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)
+
+ restoreConfName := "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreConfName, restoreXml, c)
+
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, ""), restoreConfName}, fmt.Sprintf("--object-file %s -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ // get object status
+ objectStat = s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageColdArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageColdArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ os.Remove(objectFileName)
+ os.Remove(restoreConfName)
+
+ // conf only with days
+ restoreXml = `
+
+ 7
+ `
+ restoreConfName = "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreConfName, restoreXml, c)
+
+ content = object3
+ s.createFile(objectFileName, content, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, ""), restoreConfName}, fmt.Sprintf("--object-file %s -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ objectStat = s.getStat(bucketName, object3, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageColdArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ os.Remove(objectFileName)
+ os.Remove(restoreConfName)
+
+ // error Tier
+ restoreXml = `
+
+ 99
+
+ xxx
+
+ `
+ restoreConfName = "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreConfName, restoreXml, c)
+
+ content = object4
+ s.createFile(objectFileName, content, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, ""), restoreConfName}, fmt.Sprintf("--object-file %s -f --disable-ignore-error", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // error Days
+ restoreXml = `
+
+ 399
+
+ Bulk
+
+ `
+ restoreConfName = "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreConfName, restoreXml, c)
+
+ content = object4
+ s.createFile(objectFileName, content, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, ""), restoreConfName}, fmt.Sprintf("--object-file %s -f --disable-ignore-error", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // error xml
+ restoreXml = `
+
+ 4`
+ restoreConfName = "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreConfName, restoreXml, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, ""), restoreConfName}, fmt.Sprintf("--object-file %s -f --disable-ignore-error", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+ os.Remove(restoreConfName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestRestoreObjectFileWithConfAr(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageArchive, c)
+
+ object1 := "restore" + randStr(5)
+ object2 := "restore" + randStr(5)
+ object3 := "restore" + randStr(5)
+ object4 := "restore" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+ s.putObject(bucketName, object3, uploadFileName, c)
+ s.putObject(bucketName, object4, uploadFileName, c)
+
+ // get object status
+ objectStat := s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok := objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ restoreXml := `
+
+ 7
+ `
+
+ rulesConfigSrc := oss.RestoreConfiguration{}
+ err := xml.Unmarshal([]byte(restoreXml), &rulesConfigSrc)
+ c.Assert(err, IsNil)
+
+ restoreConfName := "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreConfName, restoreXml, c)
+
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, ""), restoreConfName}, fmt.Sprintf("--object-file %s -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ // get object status
+ objectStat = s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ os.Remove(restoreConfName)
+
+ // err conf
+ restoreXml = `
+
+ 2
+
+ Bulk
+
+ `
+
+ restoreConfName = "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreConfName, restoreXml, c)
+
+ content = object3
+ s.createFile(objectFileName, content, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, ""), restoreConfName}, fmt.Sprintf("--object-file %s -f --disable-ignore-error", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+ c.Assert(strings.Contains(err.Error(), "StatusCode=400"), Equals, true)
+
+ os.Remove(restoreConfName)
+
+ // err days
+ restoreXml = `
+
+ 99
+ `
+
+ rulesConfigSrc = oss.RestoreConfiguration{}
+ err = xml.Unmarshal([]byte(restoreXml), &rulesConfigSrc)
+ c.Assert(err, IsNil)
+
+ restoreConfName = "test-ossutil-" + randLowStr(12)
+ s.createFile(restoreConfName, restoreXml, c)
+
+ content = object4
+ s.createFile(objectFileName, content, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, ""), restoreConfName}, fmt.Sprintf("--object-file %s -f --disable-ignore-error", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+ c.Assert(strings.Contains(err.Error(), "StatusCode=400"), Equals, true)
+
+ os.Remove(objectFileName)
+ os.Remove(restoreConfName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestRestoreObjectFileSnap(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageArchive, c)
+
+ object1 := "restore_1"
+ object2 := "restore_2"
+ object3 := "restore_3"
+ object4 := "restore_4"
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+ s.putObject(bucketName, object3, uploadFileName, c)
+ s.putObject(bucketName, object4, uploadFileName, c)
+
+ objectStat := s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok := objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object3, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object4, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ snapShotDir := "ossutil_test_snapshot" + randStr(5)
+ cmd := fmt.Sprintf("--object-file %s -f --snapshot-path %s", objectFileName, snapShotDir)
+
+ err := s.initRestoreObject([]string{CloudURLToString(bucketName, "")}, cmd, DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ // get object status
+ objectStat = s.getStat(bucketName, object1, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ objectStat = s.getStat(bucketName, object3, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object4, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ _, ok = objectStat["X-Oss-Restore"]
+ c.Assert(ok, Equals, false)
+
+ os.Remove(objectFileName)
+
+ content = object1 + "\n" + object2 + "\n" + object3 + "\n" + object4
+ s.createFile(objectFileName, content, c)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketName, "")}, cmd, DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ // get object status
+ objectStat = s.getStat(bucketName, object3, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ objectStat = s.getStat(bucketName, object4, c)
+ c.Assert(objectStat["X-Oss-Storage-Class"], Equals, StorageArchive)
+ c.Assert(objectStat["X-Oss-Restore"], Equals, "ongoing-request=\"true\"")
+
+ os.Remove(objectFileName)
+ os.Remove(snapShotDir)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestRestoreObjectFileErrorSnap(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageArchive, c)
+ bucketNameIA := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketNameIA, StorageIA, c)
+
+ object1 := "restore_1"
+ object2 := "restore_2"
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+ s.putObject(bucketNameIA, object1, uploadFileName, c)
+
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ // create file which name same as snapshotPath
+ snapShotDir := "ossutil_test_snapshot" + randStr(5)
+ s.createFile(snapShotDir, content, c)
+ cmd := fmt.Sprintf("--object-file %s -f --snapshot-path %s", objectFileName, snapShotDir)
+
+ err := s.initRestoreObject([]string{CloudURLToString(bucketName, "")}, cmd, DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+ os.Remove(snapShotDir)
+
+ // restore object(class ia)
+ content = object1
+ s.createFile(objectFileName, content, c)
+
+ snapShotDir = "ossutil_test_snapshot" + randStr(5)
+
+ err = s.initRestoreObject([]string{CloudURLToString(bucketNameIA, "")}, fmt.Sprintf("--object-file %s -f --snapshot-path %s --disable-ignore-error", objectFileName, snapShotDir), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = restoreCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+ os.Remove(snapShotDir)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestRestoreObjectWithVersionError(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageArchive, c)
+
+ //put object to archive bucket
+ object := "恢复文件" + randStr(5)
+ s.putObject(bucketName, object, uploadFileName, c)
+
+ // -r & --version-id error
+ command := "restore"
+ args := []string{CloudURLToString(bucketName, object)}
+ str := ""
+ versionId := "xxx"
+ r := true
+ options := OptionMapType{
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "configFile": &configFile,
+ "recursive": &r,
+ "versionId": &versionId,
+ }
+ _, err := cm.RunCommand(command, args, options)
+ c.Assert(err, NotNil)
+
+ // only --version-id
+ options = OptionMapType{
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "configFile": &configFile,
+ "versionId": &versionId,
+ }
+ _, err = cm.RunCommand(command, args, options)
+ c.Assert(err, NotNil)
+
+ s.removeBucket(bucketName, true, c)
+}
+
func (s *OssutilCommandSuite) TestRestoreObjectErrArgs(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
object := randStr(20)
@@ -419,6 +1059,53 @@ func (s *OssutilCommandSuite) TestRestoreObjectWithPayerError400(c *C) {
c.Assert(strings.Contains(err.Error(), "StatusCode=400"), Equals, true)
}
+func (s *OssutilCommandSuite) TestRestoreObjectWithPayer(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucketWithStorageClass(bucketName, StorageArchive, c)
+ s.createFile(uploadFileName, content, c)
+
+ //put object, with --payer=requester
+ args := []string{uploadFileName, CloudURLToString(bucketName, "")}
+ showElapse, err := s.rawCPWithPayer(args, false, true, false, DefaultBigFileThreshold, "requester")
+ c.Assert(err, IsNil)
+ c.Assert(showElapse, Equals, true)
+
+ // stat with payer
+ command := "restore"
+ args = []string{CloudURLToString(bucketName, uploadFileName)}
+ str := ""
+ requester := "requester"
+ options := OptionMapType{
+ "endpoint": &payerBucketEndPoint,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "configFile": &configFile,
+ "payer": &requester,
+ }
+ _, err = cm.RunCommand(command, args, options)
+ c.Assert(err, IsNil)
+
+ //put object, with --payer=requester
+ args = []string{uploadFileName, CloudURLToString(bucketName, "")}
+ showElapse, err = s.rawCPWithPayer(args, false, true, false, DefaultBigFileThreshold, "requester")
+ c.Assert(err, IsNil)
+ c.Assert(showElapse, Equals, true)
+
+ // stat with payer
+ requester = "request"
+ options = OptionMapType{
+ "endpoint": &payerBucketEndPoint,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "configFile": &configFile,
+ "payer": &requester,
+ }
+ _, err = cm.RunCommand(command, args, options)
+ c.Assert(err, NotNil)
+}
+
func (s *OssutilCommandSuite) TestRestoreObjectWithConfigColdArchiveSuccess(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
s.putBucketWithStorageClass(bucketName, StorageColdArchive, c)
diff --git a/lib/set_meta.go b/lib/set_meta.go
index 38e3b8be..6ef3137c 100644
--- a/lib/set_meta.go
+++ b/lib/set_meta.go
@@ -1,7 +1,9 @@
package lib
import (
+ "bufio"
"fmt"
+ "github.com/syndtr/goleveldb/leveldb"
"net/http"
"os"
"strings"
@@ -85,7 +87,7 @@ var specChineseSetMeta = SpecText{
paramText: "cloud_url [meta] [options]",
syntaxText: `
- ossutil set-meta oss://bucket[/prefix] [header:value#header:value...] [--update] [--delete] [-r] [-f] [-c file] [--version-id versionId]
+ ossutil set-meta oss://bucket[/prefix] [header:value#header:value...] [--update] [--delete] [-r] [-f] [-c file] [--version-id versionId] [--object-file file] [--snapshot-path dir] [--disable-ignore-error]
`,
detailHelpText: `
@@ -137,6 +139,16 @@ Headers:
--include和--exclude选项说明,请参考cp命令帮助。
如果--force选项被指定,则不会进行询问提示。
--update选项和--delete选项的用法参考上文。
+
+ 3) ossutil set-meta oss://bucket [header:value#header:value...] --object-file file [--snapshot-path dir] [--disable-ignore-error] [--update] [--delete] [-f]
+ 如果指定了--object-file选项,ossutil会读取指定文件中的所有objects,批量设置
+ 这些objects的meta信息。当一个object操作出现错误时会将出错object的错误信息记录到report
+ 文件,并继续操作其他object,成功操作的object信息将不会被记录到report文件中(更多信息
+ 见cp命令的帮助)。
+ 如果--snapshot-path选项被指定,则会对本次操作的object进行快照,如果操作对象已经存在
+ 快照,则忽略本次操作。(仅支持在-r、--object-file基础上)
+ 如果--force选项被指定,则不会进行询问提示。
+ --update选项和--delete选项的用法参考上文。
`,
sampleText: `
@@ -161,8 +173,14 @@ Headers:
(7)ossutil set-meta oss://bucket1/%e4%b8%ad%e6%96%87 X-Oss-Meta-delete --delete --encoding-type url
删除oss://bucket1/中文的X-Oss-Meta-delete头域
- (6)ossutil set-meta oss://bucket1/obj1 X-Oss-Meta-delete --delete --version-id versionId
+ (8)ossutil set-meta oss://bucket1/obj1 X-Oss-Meta-delete --delete --version-id versionId
删除指定版本obj1的X-Oss-Meta-delete头域,并生成最新版本
+
+ (9)ossutil set-meta oss://bucket1 X-Oss-Meta-empty:#Content-Type:plain/text --update --object-file file
+ 批量更新file文件中所有objects的X-Oss-Meta-empty和Content-Type头域
+
+ (10)ossutil set-meta oss://bucket1 X-Oss-Meta-empty:#Content-Type:plain/text --update --object-file file --snapshot-path dir
+ 批量更新file文件中所有objects的X-Oss-Meta-empty和Content-Type头域,并开启快照
`,
}
@@ -173,7 +191,7 @@ var specEnglishSetMeta = SpecText{
paramText: "cloud_url [meta] [options]",
syntaxText: `
- ossutil set-meta oss://bucket[/prefix] [header:value#header:value...] [--update] [--delete] [-r] [-f] [-c file] [--version-id versionId]
+ ossutil set-meta oss://bucket[/prefix] [header:value#header:value...] [--update] [--delete] [-r] [-f] [-c file] [--version-id versionId] [--object-file file] [--snapshot-path dir] [--disable-ignore-error]
`,
detailHelpText: `
@@ -233,6 +251,25 @@ Usage:
--include and --exclude option, please refer cp command help.
If --force option is specified, ossutil will not show prompt question.
The usage of --update option and --delete option is showed in detailHelpText.
+
+ 3) ossutil set-meta oss://bucket [header:value#header:value...] --object-file file [--snapshot-path dir] [--disable-ignore-error] [--update] [--delete] [-f]
+ 如果指定了--object-file选项,ossutil会读取指定文件中的所有objects,批量设置
+ 这些objects的meta信息。当一个object操作出现错误时会将出错object的错误信息记录到report
+ 文件,并继续操作其他object,成功操作的object信息将不会被记录到report文件中(更多信息
+ 见cp命令的帮助)。
+ 如果--snapshot-path选项被指定,则会对本次操作的object进行快照,如果操作对象已经存在
+ 快照,则忽略本次操作。(仅支持在-r、--object-file基础上)
+ 如果--force选项被指定,则不会进行询问提示。
+ --update选项和--delete选项的用法参考上文。
+
+ If --object-file option is specified, ossutil will read objects in file, then
+ set meta on these objects. If an error occurs, 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 --snapshot-path option is specified, ossutil will create snapshot for this operation,
+ and if the snapshot exists, then cancel this operate.
+ If --force option is specified, ossutil will not show prompt question.
+ The usage of --update option and --delete option is showed in detailHelpText.
`,
sampleText: `
@@ -259,16 +296,24 @@ Usage:
(8)ossutil set-meta oss://bucket1/obj1 X-Oss-Meta-delete --delete --version-id versionId
Delete X-Oss-Meta-delete header of a specific version of obj1,and generate the latest version obj1
+
+ (9)ossutil set-meta oss://bucket1 X-Oss-Meta-empty:#Content-Type:plain/text --update --object-file file
+ Batch update X-Oss-Meta-empty and Content-Type header on objects that in file
+
+ (10)ossutil set-meta oss://bucket1 X-Oss-Meta-empty:#Content-Type:plain/text --update --object-file file --snapshot-path dir
+ Batch update X-Oss-Meta-empty and Content-Type header on objects that in file, and open snapshot
`,
}
// SetMetaCommand is the command set meta for object
type SetMetaCommand struct {
- monitor Monitor //Put first for atomic op on some fileds
- command Command
- smOption batchOptionType
- filters []filterOptionType
- skipCount uint64
+ monitor Monitor //Put first for atomic op on some fileds
+ command Command
+ smOption batchOptionType
+ filters []filterOptionType
+ skipCount uint64
+ hasObjFile bool
+ objFilePath string
}
var setMetaCommand = SetMetaCommand{
@@ -313,6 +358,9 @@ var setMetaCommand = SetMetaCommand{
OptionSTSRegion,
OptionSkipVerfiyCert,
OptionUserAgent,
+ OptionObjectFile,
+ OptionSnapshotPath,
+ OptionDisableIgnoreError,
},
},
}
@@ -334,7 +382,6 @@ func (sc *SetMetaCommand) Init(args []string, options OptionMapType) error {
// RunCommand simulate inheritance, and polymorphism
func (sc *SetMetaCommand) RunCommand() error {
sc.monitor.init("Setted meta on")
-
isUpdate, _ := GetBool(OptionUpdate, sc.command.options)
isDelete, _ := GetBool(OptionDelete, sc.command.options)
recursive, _ := GetBool(OptionRecursion, sc.command.options)
@@ -344,35 +391,31 @@ func (sc *SetMetaCommand) RunCommand() error {
language = strings.ToLower(language)
encodingType, _ := GetString(OptionEncodingType, sc.command.options)
versionId, _ := GetString(OptionVersionId, sc.command.options)
+ objFileXml, _ := GetString(OptionObjectFile, sc.command.options)
+ snapshotPath, _ := GetString(OptionSnapshotPath, sc.command.options)
- var res bool
- res, sc.filters = getFilter(os.Args)
- if !res {
- return fmt.Errorf("--include or --exclude does not support format containing dir info")
- }
-
- if !recursive && len(sc.filters) > 0 {
- return fmt.Errorf("--include or --exclude only work with --recursive")
- }
-
- if recursive && len(versionId) > 0 {
- return fmt.Errorf("--version-id only work on single object")
+ var err error
+ // load snapshot
+ sc.smOption.snapshotPath = snapshotPath
+ if sc.smOption.snapshotPath != "" {
+ if sc.smOption.snapshotldb, err = leveldb.OpenFile(sc.smOption.snapshotPath, nil); err != nil {
+ return fmt.Errorf("load snapshot error, reason: %s", err.Error())
+ }
+ defer sc.smOption.snapshotldb.Close()
}
cloudURL, err := CloudURLFromString(sc.command.args[0], encodingType)
if err != nil {
return err
}
-
- if err = sc.checkArgs(cloudURL, recursive, isUpdate, isDelete); err != nil {
+ if err := sc.checkOptions(cloudURL, isUpdate, isDelete, force, recursive, language, versionId, objFileXml); err != nil {
+ if err.Error() == "operation is canceled by user" {
+ return nil
+ }
return err
}
-
- if !sc.confirmOP(recursive, force) {
- return nil
- }
-
- if err := sc.checkOption(isUpdate, isDelete, force, language); err != nil {
+ bucket, err := sc.command.ossBucket(cloudURL.bucket)
+ if err != nil {
return err
}
@@ -380,21 +423,27 @@ func (sc *SetMetaCommand) RunCommand() error {
if err != nil {
return err
}
-
headers, err := sc.command.parseHeaders(str, isDelete)
if err != nil {
return err
}
- bucket, err := sc.command.ossBucket(cloudURL.bucket)
- if err != nil {
- return err
- }
+ sc.smOption.ctnu = true
- if !recursive {
- err = sc.setObjectMeta(bucket, cloudURL.object, headers, isUpdate, isDelete, versionId)
+ // check --object-file mode
+ if objFileXml != "" {
+ // check objFileXml and parse it
+ if err := sc.checkObjectFile(objFileXml); err != nil {
+ return err
+ }
+ recursive = true
+ err = sc.batchSetObjectsMetaFromFile(bucket, cloudURL, headers, isUpdate, isDelete, recursive, routines)
} else {
- err = sc.batchSetObjectMeta(bucket, cloudURL, headers, isUpdate, isDelete, force, routines)
+ if !recursive {
+ err = sc.setObjectMeta(bucket, cloudURL.object, headers, isUpdate, isDelete, false, versionId)
+ } else {
+ err = sc.batchSetObjectMeta(bucket, cloudURL, headers, isUpdate, isDelete, force, routines)
+ }
}
if isUpdate {
@@ -403,20 +452,47 @@ func (sc *SetMetaCommand) RunCommand() error {
return err
}
-func (sc *SetMetaCommand) checkArgs(cloudURL CloudURL, recursive, isUpdate, isDelete bool) error {
+func (sc *SetMetaCommand) checkOptions(cloudURL CloudURL, isUpdate, isDelete, force, recursive bool, language, versionId, objFileXml string) error {
if cloudURL.bucket == "" {
- return fmt.Errorf("invalid cloud url: %s, miss bucket", sc.command.args[0])
+ return fmt.Errorf("invalid cloud url: %s, miss bucket", cloudURL.urlStr)
+ }
+ if cloudURL.object == "" {
+ if !recursive && objFileXml == "" {
+ return fmt.Errorf("set object meta invalid cloud url: %s, object empty. Set bucket meta is not supported, if you mean batch set meta on objects, please use --recursive or --object-file", sc.command.args[0])
+ }
+ } else {
+ if objFileXml != "" {
+ return fmt.Errorf("the first arg of `ossutil set-meta` only support oss://bucket when set option --object-file")
+ }
+ }
+
+ var res bool
+ res, sc.filters = getFilter(os.Args)
+ if !res {
+ return fmt.Errorf("--include or --exclude does not support format containing dir info")
}
- if !recursive && cloudURL.object == "" {
- return fmt.Errorf("set object meta invalid cloud url: %s, object empty. Set bucket meta is not supported, if you mean batch set meta on objects, please use --recursive", sc.command.args[0])
+
+ if !recursive && len(sc.filters) > 0 {
+ return fmt.Errorf("--include or --exclude only work with --recursive")
}
+
+ if (recursive && len(versionId) > 0) || (objFileXml != "" && len(versionId) > 0) {
+ return fmt.Errorf("--version-id only work on single object")
+ }
+
+ if !force {
+ var val string
+ if recursive || objFileXml != "" {
+ fmt.Printf("Do you really mean to recursivlly set meta on objects of %s(y or N)? ", sc.command.args[0])
+ if _, err := fmt.Scanln(&val); err != nil || (strings.ToLower(val) != "yes" && strings.ToLower(val) != "y") {
+ return fmt.Errorf("operation is canceled by user")
+ }
+ }
+ }
+
if isUpdate && isDelete {
return fmt.Errorf("--update option and --delete option are not supported for %s at the same time, please check", sc.command.args[0])
}
- return nil
-}
-
-func (sc *SetMetaCommand) checkOption(isUpdate, isDelete, force bool, language string) error {
if !isUpdate && !isDelete && !force {
if language == LEnglishLanguage {
fmt.Printf("Warning: --update option means update the specified header, --delete option means delete the specified header, miss both options means update the whole meta info, continue to update the whole meta info(y or N)? ")
@@ -432,18 +508,6 @@ func (sc *SetMetaCommand) checkOption(isUpdate, isDelete, force bool, language s
return nil
}
-func (sc *SetMetaCommand) confirmOP(recursive, force bool) bool {
- if recursive && !force {
- var val string
- fmt.Printf("Do you really mean to recursivlly set meta on objects of %s(y or N)? ", sc.command.args[0])
- if _, err := fmt.Scanln(&val); err != nil || (strings.ToLower(val) != "yes" && strings.ToLower(val) != "y") {
- fmt.Println("operation is canceled.")
- return false
- }
- }
- return true
-}
-
func (sc *SetMetaCommand) getMetaData(force bool, language string) (string, error) {
if len(sc.command.args) > 1 {
return strings.TrimSpace(sc.command.args[1]), nil
@@ -494,7 +558,7 @@ func (cmd *Command) parseHeaders(str string, isDelete bool) (map[string]string,
if isDelete && value != "" {
return nil, fmt.Errorf("delete meta for object do no support value for header:%s, please set value:%s to empty", name, value)
}
- if _, err := fetchHeaderOptionMap(headerOptionMap, name); err != nil && !strings.HasPrefix(strings.ToLower(name), "x-oss-") {
+ if _, err := fetchHeaderOptionMap(headerOptionMap, name); err != nil && !strings.HasPrefix(strings.ToLower(name), strings.ToLower(oss.HTTPHeaderOssMetaPrefix)) {
return nil, fmt.Errorf("unsupported header:%s, please try \"help %s\" to see supported headers", name, cmd.name)
}
headers[name] = value
@@ -502,9 +566,22 @@ func (cmd *Command) parseHeaders(str string, isDelete bool) (map[string]string,
return headers, nil
}
-func (sc *SetMetaCommand) setObjectMeta(bucket *oss.Bucket, object string, headers map[string]string, isUpdate, isDelete bool, versionId string) error {
+func (sc *SetMetaCommand) setObjectMeta(bucket *oss.Bucket, object string, headers map[string]string, isUpdate, isDelete, batchOperate bool, versionId string) error {
allheaders := headers
isSkip := false
+ spath := ""
+ msg := "set_meta"
+ nowt := time.Now().Unix()
+
+ if batchOperate && sc.smOption.snapshotPath != "" {
+ spath = sc.formatSnapshotKey(bucket.BucketName, object, msg)
+ if skip := sc.skipSetMeta(spath); skip {
+ sc.updateSkip(1)
+ LogInfo("restore obj skip: %s\n", object)
+ return nil
+ }
+ }
+
if isUpdate || isDelete {
var options []oss.Option
if len(versionId) > 0 {
@@ -539,7 +616,23 @@ func (sc *SetMetaCommand) setObjectMeta(bucket *oss.Bucket, object string, heade
if len(versionId) > 0 {
options = append(options, oss.VersionId(versionId))
}
- return sc.ossSetObjectMetaRetry(bucket, object, options...)
+
+ err = sc.ossSetObjectMetaRetry(bucket, object, options...)
+ if batchOperate && sc.smOption.snapshotPath != "" {
+ if err != nil {
+ _ = sc.updateSnapshot(err, spath, nowt)
+ return err
+ } else {
+ err = sc.updateSnapshot(err, spath, nowt)
+ if err != nil {
+ return err
+ }
+ }
+ } else {
+ return err
+ }
+
+ return nil
}
func (sc *SetMetaCommand) mergeHeader(props http.Header, headers map[string]string, isUpdate, isDelete bool) (map[string]string, bool) {
@@ -582,10 +675,11 @@ func (sc *SetMetaCommand) mergeHeader(props http.Header, headers map[string]stri
func (sc *SetMetaCommand) ossSetObjectMetaRetry(bucket *oss.Bucket, object string, options ...oss.Option) error {
retryTimes, _ := GetInt(OptionRetryTimes, sc.command.options)
cpOptions := append(options, oss.MetadataDirective(oss.MetaReplace))
+
for i := 1; ; i++ {
_, err := bucket.CopyObject(object, object, cpOptions...)
if err == nil {
- return err
+ return nil
}
if int64(i) >= retryTimes {
return ObjectError{err, bucket.BucketName, object}
@@ -594,7 +688,6 @@ func (sc *SetMetaCommand) ossSetObjectMetaRetry(bucket *oss.Bucket, object strin
}
func (sc *SetMetaCommand) batchSetObjectMeta(bucket *oss.Bucket, cloudURL CloudURL, headers map[string]string, isUpdate, isDelete, force bool, routines int64) error {
- sc.smOption.ctnu = true
outputDir, _ := GetString(OptionOutputDir, sc.command.options)
// init reporter
@@ -639,7 +732,7 @@ func (sc *SetMetaCommand) setObjectMetaConsumer(bucket *oss.Bucket, headers map[
}
func (sc *SetMetaCommand) setObjectMetaWithReport(bucket *oss.Bucket, object string, headers map[string]string, isUpdate, isDelete bool) error {
- err := sc.setObjectMeta(bucket, object, headers, isUpdate, isDelete, "")
+ err := sc.setObjectMeta(bucket, object, headers, isUpdate, isDelete, true, "")
sc.command.updateMonitor(err, &sc.monitor)
msg := fmt.Sprintf("set meta on %s", CloudURLToString(bucket.BucketName, object))
sc.command.report(msg, err, &sc.smOption)
@@ -678,3 +771,131 @@ func (sc *SetMetaCommand) formatResultPrompt(err error) error {
}
return err
}
+
+func (sc *SetMetaCommand) checkObjectFile(objFileXml string) error {
+ // check file if exists
+ fileInfo, err := os.Stat(objFileXml)
+ if err != nil {
+ return err
+ }
+ if fileInfo.IsDir() {
+ return fmt.Errorf("%s is dir, not the expected file", objFileXml)
+ }
+ if fileInfo.Size() == 0 {
+ return fmt.Errorf("%s is empty file", objFileXml)
+ }
+
+ sc.hasObjFile = true
+ sc.objFilePath = objFileXml
+ return nil
+}
+
+func (sc *SetMetaCommand) batchSetObjectsMetaFromFile(bucket *oss.Bucket, cloudURL CloudURL, headers map[string]string, isUpdate, isDelete, recursive bool, routines int64) error {
+ if sc.hasObjFile || recursive {
+ disableIgnoreError, _ := GetBool(OptionDisableIgnoreError, sc.command.options)
+ sc.smOption.ctnu = !disableIgnoreError
+ }
+ outputDir, _ := GetString(OptionOutputDir, sc.command.options)
+
+ // init reporter
+ var err error
+ if sc.smOption.reporter, err = GetReporter(sc.smOption.ctnu, outputDir, commandLine); err != nil {
+ return err
+ }
+ defer sc.smOption.reporter.Clear()
+
+ return sc.setObjectsMetaFromFile(bucket, cloudURL, sc.objFilePath, headers, isUpdate, isDelete, routines)
+}
+
+func (sc *SetMetaCommand) setObjectsMetaFromFile(bucket *oss.Bucket, cloudURL CloudURL, objectFile string, headers map[string]string, isUpdate, isDelete bool, routines int64) error {
+ // producer list objects
+ // consumer set meta
+ chObjects := make(chan string, ChannelBuf)
+ chError := make(chan error, routines+1)
+ chListError := make(chan error, 1)
+ go sc.setObjectMetaStatistic(objectFile, &sc.monitor, sc.filters)
+ go sc.setObjectMetaProducer(objectFile, chObjects, chListError, sc.filters)
+ for i := 0; int64(i) < routines; i++ {
+ go sc.setObjectMetaConsumer(bucket, headers, isUpdate, isDelete, chObjects, chError)
+ }
+
+ return sc.waitRoutinueComplete(chError, chListError, routines)
+}
+
+func (sc *SetMetaCommand) setObjectMetaStatistic(objectFile string, monitor Monitorer, filters []filterOptionType, options ...oss.Option) {
+ if monitor == nil {
+ return
+ }
+
+ file, err := os.Open(objectFile)
+ if err != nil {
+ monitor.setScanError(err)
+ return
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ object := scanner.Text()
+ object = strings.Trim(object, " ")
+ if object == "" {
+ monitor.setScanError(fmt.Errorf("object can't be '' in --object-file"))
+ return
+ }
+ monitor.updateScanNum(1)
+ }
+
+ monitor.setScanEnd()
+}
+
+func (sc *SetMetaCommand) setObjectMetaProducer(objectFile string, chObjects chan<- string, chError chan<- error, filters []filterOptionType, options ...oss.Option) {
+ file, err := os.Open(objectFile)
+ if err != nil {
+ chError <- err
+ return
+ }
+ defer file.Close()
+
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ object := scanner.Text()
+ object = strings.Trim(object, " ")
+ if object == "" {
+ chError <- fmt.Errorf("object can't be '' in --object-file")
+ break
+ }
+ chObjects <- object
+ }
+
+ defer close(chObjects)
+ chError <- nil
+}
+
+func (sc *SetMetaCommand) formatSnapshotKey(bucket, object, msg string) string {
+ return CloudURLToString(bucket, object) + SnapshotConnector + msg
+}
+
+func (sc *SetMetaCommand) skipSetMeta(spath string) bool {
+ if sc.smOption.snapshotPath != "" {
+ _, err := sc.smOption.snapshotldb.Get([]byte(spath), nil)
+ if err == nil {
+ return true
+ }
+ }
+ return false
+}
+
+func (sc *SetMetaCommand) updateSnapshot(err error, spath string, srct int64) error {
+ if sc.smOption.snapshotPath != "" && err == nil {
+ srctstr := fmt.Sprintf("%d", srct)
+ err := sc.smOption.snapshotldb.Put([]byte(spath), []byte(srctstr), nil)
+ if err != nil {
+ return fmt.Errorf("dump snapshot error: %s", err.Error())
+ }
+ }
+ return nil
+}
+
+func (sc *SetMetaCommand) updateSkip(num int64) {
+ atomic.AddInt64(&sc.monitor.skipNum, num)
+}
diff --git a/lib/set_meta_test.go b/lib/set_meta_test.go
index d4a90174..8861b138 100644
--- a/lib/set_meta_test.go
+++ b/lib/set_meta_test.go
@@ -2,16 +2,15 @@ package lib
import (
"fmt"
+ oss "github.com/aliyun/aliyun-oss-go-sdk/oss"
+ . "gopkg.in/check.v1"
"net/url"
"os"
"strconv"
"strings"
-
- oss "github.com/aliyun/aliyun-oss-go-sdk/oss"
- . "gopkg.in/check.v1"
)
-func (s *OssutilCommandSuite) TestSetBucketMeta(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaSetBucketMeta(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
s.putBucket(bucketName, c)
@@ -79,6 +78,11 @@ func (s *OssutilCommandSuite) TestSetObjectMetaBasic(c *C) {
c.Assert(err, NotNil)
c.Assert(showElapse, Equals, false)
+ // with update and delete
+ showElapse, err = s.rawSetMeta(bucketName, object, "x-oss-object-acl:default#X-Oss-Meta-A:A", true, true, false, true, DefaultLanguage)
+ c.Assert(err, NotNil)
+ c.Assert(showElapse, Equals, false)
+
// miss meta
s.setObjectMeta(bucketName, object, "", true, false, false, true, c)
@@ -135,7 +139,358 @@ func (s *OssutilCommandSuite) TestSetObjectMetaBasic(c *C) {
s.removeBucket(bucketName, true, c)
}
-func (s *OssutilCommandSuite) TestSetNotExistObjectMeta(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaObjectFile(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucket(bucketName, c)
+
+ object1 := "setMeta" + randStr(5)
+ object2 := "setMeta" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+
+ objectStat := s.getStat(bucketName, object1, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok := objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok = objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ meta := "x-oss-object-acl:private#X-Oss-Meta-A:A#Expires:2006-01-02T15:04:05Z"
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ // --object-file without -f
+ err := s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, fmt.Sprintf("--object-file %s", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ objectStat = s.getStat(bucketName, object1, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok = objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok = objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ err = s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, fmt.Sprintf("--object-file %s -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ objectStat = s.getStat(bucketName, object1, c)
+ c.Assert(objectStat[StatACL], Equals, "private")
+ c.Assert(objectStat["X-Oss-Meta-A"], Equals, "A")
+ c.Assert(objectStat["Expires"], Equals, "Mon, 02 Jan 2006 15:04:05 GMT")
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat[StatACL], Equals, "private")
+ c.Assert(objectStat["X-Oss-Meta-A"], Equals, "A")
+ c.Assert(objectStat["Expires"], Equals, "Mon, 02 Jan 2006 15:04:05 GMT")
+
+ os.Remove(objectFileName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestSetObjectMetaObjectFileErrMeta(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucket(bucketName, c)
+
+ object1 := "setMeta" + randStr(5)
+ object2 := "setMeta" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+
+ meta := "x-oss-server-side-encryption:xxx"
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ err := s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, fmt.Sprintf("--object-file %s --disable-ignore-error -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, NotNil)
+ c.Assert(strings.Contains(err.Error(), "StatusCode=400"), Equals, true)
+
+ os.Remove(objectFileName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestSetObjectMetaObjectFileErrObj(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucket(bucketName, c)
+
+ object1 := "setMeta" + randStr(5)
+ object2 := "setMeta" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+
+ meta := "x-oss-object-acl:private#X-Oss-Meta-A:A#Expires:2006-01-02T15:04:05Z"
+ content := object1 + "xx" + "\n" + object2 + "ss"
+ s.createFile(objectFileName, content, c)
+
+ // not exist object
+ err := s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, fmt.Sprintf("--object-file %s --disable-ignore-error -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // bucket is none
+ err = s.initSetMetaWithArgs([]string{CloudURLToString("", ""), meta}, fmt.Sprintf("--object-file %s --disable-ignore-error -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // object is none
+ content = "\n"
+ s.createFile(objectFileName, content, c)
+
+ err = s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, fmt.Sprintf("--object-file %s --disable-ignore-error -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // file size 0
+ s.createFile(objectFileName, "", c)
+
+ err = s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, fmt.Sprintf("--object-file %s --disable-ignore-error -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+
+ // receive file but get dir
+ err = os.Mkdir(fmt.Sprintf("./%s", objectFileName), os.ModePerm)
+
+ err = s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, fmt.Sprintf("--object-file %s --disable-ignore-error -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+
+ // not exist file
+ err = s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, fmt.Sprintf("--object-file %s --disable-ignore-error -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ // object exist while --object-file is set
+ err = s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, "xx"), meta}, fmt.Sprintf("--object-file %s --disable-ignore-error -f", objectFileName), DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestSetObjectMetaObjectFileErrVer(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucket(bucketName, c)
+
+ object1 := "setMeta" + randStr(5)
+ object2 := "setMeta" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+
+ meta := "x-oss-object-acl:private#X-Oss-Meta-A:A#Expires:2006-01-02T15:04:05Z"
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ command := "set-meta"
+ args := []string{CloudURLToString(bucketName, ""), meta}
+ versionId := "xxx"
+ str := ""
+ ok := true
+ routines := strconv.Itoa(Routines)
+ options := OptionMapType{
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "update": &ok,
+ "force": &ok,
+ "versionId": &versionId,
+ "routines": &routines,
+ "objectFile": &objectFileName,
+ "disableIgnoreError": &ok,
+ }
+ _, err := cm.RunCommand(command, args, options)
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestSetObjectMetaObjectFileSnap(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucket(bucketName, c)
+
+ object1 := "setMeta" + randStr(5)
+ object2 := "setMeta" + randStr(5)
+ object3 := "setMeta" + randStr(5)
+ object4 := "setMeta" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+ s.putObject(bucketName, object3, uploadFileName, c)
+ s.putObject(bucketName, object4, uploadFileName, c)
+
+ objectStat := s.getStat(bucketName, object1, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok := objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok = objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object3, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok = objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object4, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok = objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ meta := "x-oss-object-acl:private#X-Oss-Meta-A:A#Expires:2006-01-02T15:04:05Z"
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ snapShotDir := "ossutil_test_snapshot" + randStr(5)
+ cmd := fmt.Sprintf("--object-file %s --snapshot-path %s -f", objectFileName, snapShotDir)
+
+ err := s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, cmd, DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ objectStat = s.getStat(bucketName, object1, c)
+ c.Assert(objectStat[StatACL], Equals, "private")
+ c.Assert(objectStat["X-Oss-Meta-A"], Equals, "A")
+ c.Assert(objectStat["Expires"], Equals, "Mon, 02 Jan 2006 15:04:05 GMT")
+
+ objectStat = s.getStat(bucketName, object2, c)
+ c.Assert(objectStat[StatACL], Equals, "private")
+ c.Assert(objectStat["X-Oss-Meta-A"], Equals, "A")
+ c.Assert(objectStat["Expires"], Equals, "Mon, 02 Jan 2006 15:04:05 GMT")
+
+ objectStat = s.getStat(bucketName, object3, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok = objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ objectStat = s.getStat(bucketName, object4, c)
+ c.Assert(objectStat[StatACL], Equals, "default")
+ _, ok = objectStat["X-Oss-Meta-A"]
+ c.Assert(ok, Equals, false)
+
+ content = object1 + "\n" + object2 + "\n" + object3 + "\n" + object4
+ s.createFile(objectFileName, content, c)
+
+ err = s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, cmd, DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, IsNil)
+
+ objectStat = s.getStat(bucketName, object3, c)
+ c.Assert(objectStat[StatACL], Equals, "private")
+ c.Assert(objectStat["X-Oss-Meta-A"], Equals, "A")
+ c.Assert(objectStat["Expires"], Equals, "Mon, 02 Jan 2006 15:04:05 GMT")
+
+ objectStat = s.getStat(bucketName, object4, c)
+ c.Assert(objectStat[StatACL], Equals, "private")
+ c.Assert(objectStat["X-Oss-Meta-A"], Equals, "A")
+ c.Assert(objectStat["Expires"], Equals, "Mon, 02 Jan 2006 15:04:05 GMT")
+
+ os.Remove(objectFileName)
+ os.Remove(snapShotDir)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestSetObjectMetaObjectFileErrorSnap(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucket(bucketName, c)
+
+ object1 := "setMeta_" + randStr(5)
+ object2 := "setMeta_" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+
+ meta := "x-oss-object-acl:private#X-Oss-Meta-A:A#Expires:2006-01-02T15:04:05Z"
+ content := object1 + "\n" + object2
+ s.createFile(objectFileName, content, c)
+
+ // create file which name same as snapshotPath
+ snapShotDir := "ossutil_test_snapshot" + randStr(5)
+ s.createFile(snapShotDir, content, c)
+ cmd := fmt.Sprintf("--object-file %s --snapshot-path %s --disable-ignore-error -f", objectFileName, snapShotDir)
+
+ err := s.initSetMetaWithArgs([]string{CloudURLToString(bucketName, ""), meta}, cmd, DefaultOutputDir)
+ c.Assert(err, IsNil)
+ err = setMetaCommand.RunCommand()
+ c.Assert(err, NotNil)
+
+ os.Remove(objectFileName)
+ os.Remove(snapShotDir)
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestSetObjectMetaWithVersionError(c *C) {
+ bucketName := bucketNamePrefix + randLowStr(10)
+ s.putBucket(bucketName, c)
+
+ object1 := "setMeta" + randStr(5)
+ object2 := "setMeta" + randStr(5)
+ s.putObject(bucketName, object1, uploadFileName, c)
+ s.putObject(bucketName, object2, uploadFileName, c)
+
+ meta := "x-oss-object-acl:private#X-Oss-Meta-A:A#Expires:2006-01-02T15:04:05Z"
+
+ // -r & --version-id error
+ command := "set-meta"
+ args := []string{CloudURLToString(bucketName, ""), meta}
+ str := ""
+ versionId := "xxx"
+ r := true
+ options := OptionMapType{
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "recursive": &r,
+ "update": &r,
+ "force": &r,
+ "versionId": &versionId,
+ }
+ _, err := cm.RunCommand(command, args, options)
+ c.Assert(err, NotNil)
+
+ // wrong version-id
+ options = OptionMapType{
+ "endpoint": &str,
+ "accessKeyID": &str,
+ "accessKeySecret": &str,
+ "stsToken": &str,
+ "update": &r,
+ "force": &r,
+ "versionId": &versionId,
+ }
+ _, err = cm.RunCommand(command, args, options)
+ c.Assert(err, NotNil)
+
+ s.removeBucket(bucketName, true, c)
+}
+
+func (s *OssutilCommandSuite) TestSetObjectMetaSetNotExistObjectMeta(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
s.putBucket(bucketName, c)
@@ -164,7 +519,7 @@ func (s *OssutilCommandSuite) TestSetNotExistObjectMeta(c *C) {
s.removeBucket(bucketName, true, c)
}
-func (s *OssutilCommandSuite) TestErrBatchSetMeta(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaErrBatchSetMeta(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
s.putBucket(bucketName, c)
@@ -209,7 +564,7 @@ func (s *OssutilCommandSuite) TestErrBatchSetMeta(c *C) {
s.removeBucket(bucketName, true, c)
}
-func (s *OssutilCommandSuite) TestErrSetMeta(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaErrSetMeta(c *C) {
args := []string{"os:/", ""}
showElapse, err := s.rawSetMetaWithArgs(args, false, false, false, true, DefaultLanguage)
c.Assert(err, NotNil)
@@ -267,12 +622,12 @@ func (s *OssutilCommandSuite) TestErrSetMeta(c *C) {
s.removeBucket(bucketName, true, c)
}
-func (s *OssutilCommandSuite) TestGetOSSOption(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaGetOSSOption(c *C) {
_, err := getOSSOption(headerOptionMap, "unknown", "a")
c.Assert(err, NotNil)
}
-func (s *OssutilCommandSuite) TestSetMetaIDKey(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaSetMetaIDKey(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
s.putBucket(bucketName, c)
@@ -320,7 +675,7 @@ func (s *OssutilCommandSuite) TestSetMetaIDKey(c *C) {
s.removeBucket(bucketName, true, c)
}
-func (s *OssutilCommandSuite) TestSetMetaURLEncoding(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaSetMetaURLEncoding(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
s.putBucket(bucketName, c)
@@ -357,7 +712,7 @@ func (s *OssutilCommandSuite) TestSetMetaURLEncoding(c *C) {
s.removeBucket(bucketName, true, c)
}
-func (s *OssutilCommandSuite) TestSetMetaErrArgs(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaSetMetaErrArgs(c *C) {
object := randStr(20)
meta := "x-oss-object-acl:public-read-write"
@@ -372,7 +727,7 @@ func (s *OssutilCommandSuite) TestSetMetaErrArgs(c *C) {
c.Assert(err, NotNil)
}
-func (s *OssutilCommandSuite) TestBatchSetMetaNotExistBucket(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaBatchSetMetaNotExistBucket(c *C) {
// set acl notexist bucket
meta := "x-oss-object-acl:public-read-write"
err := s.initSetMetaWithArgs([]string{CloudURLToString(bucketNamePrefix+randLowStr(10), ""), meta}, "-rf", DefaultOutputDir)
@@ -381,7 +736,7 @@ func (s *OssutilCommandSuite) TestBatchSetMetaNotExistBucket(c *C) {
c.Assert(err, NotNil)
}
-func (s *OssutilCommandSuite) TestBatchSetMetaErrorContinue(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaBatchSetMetaErrorContinue(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
s.putBucket(bucketName, c)
@@ -478,7 +833,7 @@ func (s *OssutilCommandSuite) TestBatchSetMetaErrorContinue(c *C) {
s.removeBucket(bucketName, true, c)
}
-func (s *OssutilCommandSuite) TestBatchSetMetaErrorBreak(c *C) {
+func (s *OssutilCommandSuite) TestSetObjectMetaBatchSetMetaErrorBreak(c *C) {
bucketName := bucketNamePrefix + randLowStr(10)
s.putBucketWithStorageClass(bucketName, StorageArchive, c)
@@ -610,6 +965,16 @@ func (s *OssutilCommandSuite) TestSetObjectMetaWithNormalInclude(c *C) {
c.Assert(ok, Equals, false)
}
+ // --include without -r
+ showElapse, err = s.rawSetMetaWithFilter(args, true, false, false, true, DefaultLanguage, []string{"ossutil", "set-meta", bucketStr, meta, "-f", "--include", "*.txt"})
+ c.Assert(err, NotNil)
+ c.Assert(showElapse, Equals, false)
+
+ // error value of --include
+ showElapse, err = s.rawSetMetaWithFilter(args, true, false, false, true, DefaultLanguage, []string{"ossutil", "set-meta", bucketStr, meta, "-f", "--include", "/usr/test/*.txt"})
+ c.Assert(err, NotNil)
+ c.Assert(showElapse, Equals, false)
+
os.RemoveAll(dir)
s.removeBucket(bucketName, true, c)
}
@@ -1343,8 +1708,6 @@ func (s *OssutilCommandSuite) TestSetObjectMetaWithInvalidIncExc(c *C) {
func (s *OssutilCommandSuite) TestSetObjectMetaVersionBasic(c *C) {
bucketName := bucketNamePrefix + "-set-meta-" + randLowStr(10)
- s.putBucket(bucketName, c)
-
s.putBucket(bucketName, c)
s.putBucketVersioning(bucketName, "enabled", c)