From 674ddb4ec0d2ad1df4043e8f67a3ce5e7e7cd78d Mon Sep 17 00:00:00 2001 From: hangzws Date: Sat, 13 Apr 2019 11:41:08 +0800 Subject: [PATCH 01/15] add put/get/delete object tagging api & Tagging,TaggingDirective options --- oss/bucket.go | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ oss/conn.go | 2 +- oss/const.go | 15 ++++++++++- oss/option.go | 12 ++++++++- oss/type.go | 13 +++++++++ oss/upload.go | 2 +- 6 files changed, 115 insertions(+), 4 deletions(-) diff --git a/oss/bucket.go b/oss/bucket.go index 067855e0..5359f02d 100644 --- a/oss/bucket.go +++ b/oss/bucket.go @@ -927,6 +927,81 @@ func (bucket Bucket) ProcessObject(objectKey string, process string) (ProcessObj return out, err } +// +// PutObjectTagging add tagging to object +// +// objectKey object key to add tagging +// tagging tagging to be added +// +// error nil if success, otherwise error +// +func (bucket Bucket) PutObjectTagging(objectKey string, tagging ObjectTagging) error { + bs, err := xml.Marshal(tagging) + if err != nil { + return err + } + + buffer := new(bytes.Buffer) + buffer.Write(bs) + + params := map[string]interface{}{} + params["tagging"] = nil + resp, err := bucket.do("PUT", objectKey, params, nil, buffer, nil) + if err != nil { + return err + } + defer resp.Body.Close() + + return nil +} + +// +// GetObjectTagging get tagging of the object +// +// objectKey object key to get tagging +// +// Tagging +// error nil if success, otherwise error +// +func (bucket Bucket) GetObjectTagging(objectKey string) (ObjectTagging, error) { + var out ObjectTagging + params := map[string]interface{}{} + params["tagging"] = nil + + resp, err := bucket.do("GET", objectKey, params, nil, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// +// DeleteObjectTagging delete object taggging +// +// objectKey object key to delete tagging +// +// error nil if success, otherwise error +// +func (bucket Bucket) DeleteObjectTagging(objectKey string) error { + params := map[string]interface{}{} + params["tagging"] = nil + + if objectKey == "" { + return fmt.Errorf("invalid argument: object name is empty") + } + + resp, err := bucket.do("DELETE", objectKey, params, nil, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + // Private func (bucket Bucket) do(method, objectName string, params map[string]interface{}, options []Option, data io.Reader, listener ProgressListener) (*Response, error) { diff --git a/oss/conn.go b/oss/conn.go index 3722d686..6686a9ac 100644 --- a/oss/conn.go +++ b/oss/conn.go @@ -27,7 +27,7 @@ type Conn struct { client *http.Client } -var signKeyList = []string{"acl", "uploads", "location", "cors", "logging", "website", "referer", "lifecycle", "delete", "append", "tagging", "objectMeta", "uploadId", "partNumber", "security-token", "position", "img", "style", "styleName", "replication", "replicationProgress", "replicationLocation", "cname", "bucketInfo", "comp", "qos", "live", "status", "vod", "startTime", "endTime", "symlink", "x-oss-process", "response-content-type", "response-content-language", "response-expires", "response-cache-control", "response-content-disposition", "response-content-encoding", "udf", "udfName", "udfImage", "udfId", "udfImageDesc", "udfApplication", "comp", "udfApplicationLog", "restore", "callback", "callback-var", "policy"} +var signKeyList = []string{"acl", "uploads", "location", "cors", "logging", "website", "referer", "lifecycle", "delete", "append", "tagging", "objectMeta", "uploadId", "partNumber", "security-token", "position", "img", "style", "styleName", "replication", "replicationProgress", "replicationLocation", "cname", "bucketInfo", "comp", "qos", "live", "status", "vod", "startTime", "endTime", "symlink", "x-oss-process", "response-content-type", "response-content-language", "response-expires", "response-cache-control", "response-content-disposition", "response-content-encoding", "udf", "udfName", "udfImage", "udfId", "udfImageDesc", "udfApplication", "comp", "udfApplicationLog", "restore", "callback", "callback-var", "policy", "tagging"} // init initializes Conn func (conn *Conn) init(config *Config, urlMaker *urlMaker, client *http.Client) error { diff --git a/oss/const.go b/oss/const.go index d1eb4b5f..2252bf84 100644 --- a/oss/const.go +++ b/oss/const.go @@ -30,6 +30,17 @@ const ( MetaReplace MetadataDirectiveType = "REPLACE" ) +// TaggingDirectiveType specifying whether use the tagging of source object when copying object. +type TaggingDirectiveType string + +const ( + // TaggingCopy the target object's tagging is copied from the source one + TaggingCopy TaggingDirectiveType = "COPY" + + // TaggingReplace the target object's tagging is created as part of the copy request (not same as the source one) + TaggingReplace TaggingDirectiveType = "REPLACE" +) + // StorageClassType bucket storage type type StorageClassType string @@ -118,7 +129,9 @@ const ( HTTPHeaderOssStorageClass = "X-Oss-Storage-Class" HTTPHeaderOssCallback = "X-Oss-Callback" HTTPHeaderOssCallbackVar = "X-Oss-Callback-Var" - HTTPHeaderOSSRequester = "X-Oss-Request-Payer" + HTTPHeaderOssRequester = "X-Oss-Request-Payer" + HTTPHeaderOssTagging = "X-Oss-Tagging" + HTTPHeaderOssTaggingDirective = "X-Oss-Tagging-Directive" ) // HTTP Param diff --git a/oss/option.go b/oss/option.go index 5952f8ae..7fe0eea6 100644 --- a/oss/option.go +++ b/oss/option.go @@ -199,7 +199,17 @@ func CallbackVar(callbackVar string) Option { // RequestPayer is an option to set payer who pay for the request func RequestPayer(payerType PayerType) Option { - return setHeader(HTTPHeaderOSSRequester, string(payerType)) + return setHeader(HTTPHeaderOssRequester, string(payerType)) +} + +// Tagging is an option to set object tagging, notice: the value is url.QueryEscape(TagA)=url.QueryEscape(A) & url.QueryEscape(TagB)=url.QueryEscape(B)... +func Tagging(value string) Option { + return setHeader(HTTPHeaderOssTagging, value) +} + +// TaggingDirective is an option to set X-Oss-Metadata-Directive header +func TaggingDirective(directive TaggingDirectiveType) Option { + return setHeader(HTTPHeaderOssTaggingDirective, string(directive)) } // Delimiter is an option to set delimiler parameter diff --git a/oss/type.go b/oss/type.go index ef7bd054..434767f1 100644 --- a/oss/type.go +++ b/oss/type.go @@ -600,3 +600,16 @@ type LiveChannelInfo struct { PublishUrls []string `xml:"PublishUrls>Url"` //push urls list PlayUrls []string `xml:"PlayUrls>Url"` //play urls list } + +// Tag a tag for the object +type Tag struct { + XMLName xml.Name `xml:"Tag"` + Key string `xml:"Key"` + Value string `xml:"Value"` +} + +// ObjectTagging tagset for the object +type ObjectTagging struct { + XMLName xml.Name `xml:"Tagging"` + tags []Tag `xml:"TagSet>Tag,omitempty"` +} diff --git a/oss/upload.go b/oss/upload.go index 80371447..b9e88651 100644 --- a/oss/upload.go +++ b/oss/upload.go @@ -94,7 +94,7 @@ func getRoutines(options []Option) int { // getPayer return the payer of the request func getPayer(options []Option) string { - payerOpt, err := findOption(options, HTTPHeaderOSSRequester, nil) + payerOpt, err := findOption(options, HTTPHeaderOssRequester, nil) if err != nil || payerOpt == nil { return "" } From 167d453a422bd50e7a8c61c97f7d3c759d59a4cb Mon Sep 17 00:00:00 2001 From: hangzws Date: Mon, 15 Apr 2019 09:49:04 +0800 Subject: [PATCH 02/15] add verfiyLifecycleRules method --- oss/client.go | 4 + oss/client_test.go | 63 ++++++++++---- oss/multipart_test.go | 2 +- oss/type.go | 74 +++++++--------- oss/type_test.go | 174 +++++++++++++++++++++++++++++++++---- sample/bucket_lifecycle.go | 34 +++++--- 6 files changed, 258 insertions(+), 93 deletions(-) diff --git a/oss/client.go b/oss/client.go index 5637093e..0104cf0f 100644 --- a/oss/client.go +++ b/oss/client.go @@ -260,6 +260,10 @@ func (client Client) GetBucketACL(bucketName string) (GetBucketACLResult, error) // error it's nil if no error, otherwise it's an error object. // func (client Client) SetBucketLifecycle(bucketName string, rules []LifecycleRule) error { + err := verifyLifecycleRules(rules) + if err != nil { + return err + } lifecycleCfg := LifecycleConfiguration{Rules: rules} bs, err := xml.Marshal(lifecycleCfg) if err != nil { diff --git a/oss/client_test.go b/oss/client_test.go index c13ba6bf..9c56a938 100644 --- a/oss/client_test.go +++ b/oss/client_test.go @@ -665,34 +665,55 @@ func (s *OssClientSuite) TestSetBucketLifecycleNew(c *C) { err = client.CreateBucket(bucketNameTest) c.Assert(err, IsNil) + //invalid value of CreatedBeforeDate expiration := LifecycleExpiration{ CreatedBeforeDate: randStr(10), } - rule, err := NewLifecycleRule("rule1", "one", true, &expiration, nil) + rule := LifecycleRule{ + ID: "rule1", + Prefix: "one", + Status: "Enabled", + Expiration: &expiration, + } c.Assert(err, IsNil) - rules := []LifecycleRule{*rule} + rules := []LifecycleRule{rule} err = client.SetBucketLifecycle(bucketNameTest, rules) c.Assert(err, NotNil) + //invalid value of Days abortMPU := LifecycleAbortMultipartUpload{ Days: -30, } - rule, err = NewLifecycleRule("rule2", "two", true, nil, &abortMPU) - c.Assert(err, IsNil) - rules = []LifecycleRule{*rule} + rule = LifecycleRule{ + ID: "rule1", + Prefix: "one", + Status: "Enabled", + AbortMultipartUpload: &abortMPU, + } + rules = []LifecycleRule{rule} err = client.SetBucketLifecycle(bucketNameTest, rules) c.Assert(err, NotNil) expiration = LifecycleExpiration{ CreatedBeforeDate: "2015-11-11T00:00:00.000Z", } - rule1, err := NewLifecycleRule("rule1", "one", true, &expiration, nil) - c.Assert(err, IsNil) + rule1 := LifecycleRule{ + ID: "rule1", + Prefix: "one", + Status: "Enabled", + Expiration: &expiration, + } + abortMPU = LifecycleAbortMultipartUpload{ Days: 30, } - rule2, err := NewLifecycleRule("rule2", "two", true, &expiration, &abortMPU) - c.Assert(err, IsNil) + rule2 := LifecycleRule{ + ID: "rule2", + Prefix: "two", + Status: "Enabled", + Expiration: &expiration, + AbortMultipartUpload: &abortMPU, + } transition1 := LifecycleTransition{ Days: 3, @@ -702,11 +723,17 @@ func (s *OssClientSuite) TestSetBucketLifecycleNew(c *C) { Days: 30, StorageClass: StorageArchive, } - rule3, err := NewLifecycleRule("rule3", "three", true, nil, &abortMPU, &transition1, &transition2) - c.Assert(err, IsNil) + transitions := []LifecycleTransition{transition1, transition2} + rule3 := LifecycleRule{ + ID: "rule3", + Prefix: "three", + Status: "Enabled", + AbortMultipartUpload: &abortMPU, + Transitions: transitions, + } // Set single rule - rules = []LifecycleRule{*rule1} + rules = []LifecycleRule{rule1} err = client.SetBucketLifecycle(bucketNameTest, rules) c.Assert(err, IsNil) @@ -721,7 +748,7 @@ func (s *OssClientSuite) TestSetBucketLifecycleNew(c *C) { c.Assert(err, IsNil) // Set three rules - rules = []LifecycleRule{*rule1, *rule2, *rule3} + rules = []LifecycleRule{rule1, rule2, rule3} err = client.SetBucketLifecycle(bucketNameTest, rules) c.Assert(err, IsNil) @@ -739,11 +766,11 @@ func (s *OssClientSuite) TestSetBucketLifecycleNew(c *C) { c.Assert(res.Rules[2].ID, Equals, "rule3") c.Assert(res.Rules[2].AbortMultipartUpload, NotNil) c.Assert(res.Rules[2].AbortMultipartUpload.Days, Equals, 30) - c.Assert(res.Rules[2].Transition, NotNil) - c.Assert(res.Rules[2].Transition[0].StorageClass, Equals, StorageIA) - c.Assert(res.Rules[2].Transition[0].Days, Equals, 3) - c.Assert(res.Rules[2].Transition[1].StorageClass, Equals, StorageArchive) - c.Assert(res.Rules[2].Transition[1].Days, Equals, 30) + c.Assert(len(res.Rules[2].Transitions), Equals, 2) + c.Assert(res.Rules[2].Transitions[0].StorageClass, Equals, StorageIA) + c.Assert(res.Rules[2].Transitions[0].Days, Equals, 3) + c.Assert(res.Rules[2].Transitions[1].StorageClass, Equals, StorageArchive) + c.Assert(res.Rules[2].Transitions[1].Days, Equals, 30) err = client.DeleteBucket(bucketNameTest) c.Assert(err, IsNil) diff --git a/oss/multipart_test.go b/oss/multipart_test.go index fe6c8262..8fc99cd3 100644 --- a/oss/multipart_test.go +++ b/oss/multipart_test.go @@ -603,7 +603,7 @@ func (s *OssBucketMultipartSuite) TestListMultipartUploads(c *C) { c.Assert(err, IsNil) checkNum := 15 for _, im := range imurs { - if im.Key == objectName+"12" && im.UploadID > "upLoadIDStr" { + if im.Key == objectName+"12" && im.UploadID > upLoadIDStr { checkNum = 16 break } diff --git a/oss/type.go b/oss/type.go index 434767f1..b0fe149a 100644 --- a/oss/type.go +++ b/oss/type.go @@ -44,11 +44,11 @@ type LifecycleConfiguration struct { // LifecycleRule defines Lifecycle rules type LifecycleRule struct { XMLName xml.Name `xml:"Rule"` - ID string `xml:"ID"` // The rule ID + ID string `xml:"ID,omitempty"` // The rule ID Prefix string `xml:"Prefix"` // The object key prefix Status string `xml:"Status"` // The rule status (enabled or not) Expiration *LifecycleExpiration `xml:"Expiration,omitempty"` // The expiration property - Transition []*LifecycleTransition `xml:"Transition,omitempty"` // The transition property + Transitions []LifecycleTransition `xml:"Transition,omitempty"` // The transition property AbortMultipartUpload *LifecycleAbortMultipartUpload `xml:"AbortMultipartUpload,omitempty"` // The AbortMultipartUpload property } @@ -98,57 +98,47 @@ func BuildLifecycleRuleByDate(id, prefix string, status bool, year, month, day i Expiration: &LifecycleExpiration{Date: date}} } -// NewLifecycleRule build a lifecycle rule -func NewLifecycleRule(id, prefix string, status bool, expiration *LifecycleExpiration, abortMPU *LifecycleAbortMultipartUpload, transitions ...*LifecycleTransition) (*LifecycleRule, error) { - statusStr := "Enabled" - if !status { - statusStr = "Disabled" - } - rule := LifecycleRule{ - ID: id, - Prefix: prefix, - Status: statusStr, - } - - if expiration != nil { - if (expiration.Days != 0 && expiration.CreatedBeforeDate != "") || (expiration.Days == 0 && expiration.CreatedBeforeDate == "") { - return nil, fmt.Errorf("invalid expiration lifecycle, must be set one of CreatedBeforeDate and Days") +// ValidateLifecycleRule Determine if a lifecycle rule is valid, if it is invalid, it will return an error. +func verifyLifecycleRules(rules []LifecycleRule) error { + for _, rule := range rules { + if rule.Status != "Enabled" && rule.Status != "Disabled" { + return fmt.Errorf("invalid rule, the value of status must be Enabled or Disabled") } - rule.Expiration = expiration - } - if abortMPU != nil { - if (abortMPU.Days != 0 && abortMPU.CreatedBeforeDate != "") || (abortMPU.Days == 0 && abortMPU.CreatedBeforeDate == "") { - return nil, fmt.Errorf("invalid abort multipart upload lifecycle, must be set one of CreatedBeforeDate and Days") + expiration := rule.Expiration + if expiration != nil { + if (expiration.Days != 0 && expiration.CreatedBeforeDate != "") || (expiration.Days != 0 && expiration.Date != "") || (expiration.CreatedBeforeDate != "" && expiration.Date != "") || (expiration.Days == 0 && expiration.CreatedBeforeDate == "" && expiration.Date == "") { + return fmt.Errorf("invalid expiration lifecycle, must be set one of CreatedBeforeDate, Days and Date") + } } - rule.AbortMultipartUpload = abortMPU - } - if len(transitions) > 0 { - if len(transitions) > 2 { - return nil, fmt.Errorf("invalid count of transition lifecycles, the count must than less than 3") + abortMPU := rule.AbortMultipartUpload + if abortMPU != nil { + if (abortMPU.Days != 0 && abortMPU.CreatedBeforeDate != "") || (abortMPU.Days == 0 && abortMPU.CreatedBeforeDate == "") { + return fmt.Errorf("invalid abort multipart upload lifecycle, must be set one of CreatedBeforeDate and Days") + } } - for _, transition := range transitions { - if transition == nil { - return nil, fmt.Errorf("invalid transitions, there is a transition not be initiated") - } - if (transition.Days != 0 && transition.CreatedBeforeDate != "") || (transition.Days == 0 && transition.CreatedBeforeDate == "") { - return nil, fmt.Errorf("invalid transition lifecycle, must be set one of CreatedBeforeDate and Days") + transitions := rule.Transitions + if len(transitions) > 0 { + if len(transitions) > 2 { + return fmt.Errorf("invalid count of transition lifecycles, the count must than less than 3") } - if transition.StorageClass != StorageIA && transition.StorageClass != StorageArchive { - return nil, fmt.Errorf("invalid transition lifecylce, the value of storage class must be IA or Archive") + + for _, transition := range transitions { + if (transition.Days != 0 && transition.CreatedBeforeDate != "") || (transition.Days == 0 && transition.CreatedBeforeDate == "") { + return fmt.Errorf("invalid transition lifecycle, must be set one of CreatedBeforeDate and Days") + } + if transition.StorageClass != StorageIA && transition.StorageClass != StorageArchive { + return fmt.Errorf("invalid transition lifecylce, the value of storage class must be IA or Archive") + } } + } else if expiration == nil && abortMPU == nil { + return fmt.Errorf("invalid rule, must set one of Expiration, AbortMultipartUplaod and Transitions") } - - rule.Transition = transitions } - if rule.Expiration == nil && rule.AbortMultipartUpload == nil && len(rule.Transition) == 0 { - return nil, fmt.Errorf("invalid lifecycle rule, must be set one of Expiration, AbortMultipartUpload and Transition") - } - - return &rule, nil + return nil } // GetBucketLifecycleResult defines GetBucketLifecycle's result object diff --git a/oss/type_test.go b/oss/type_test.go index ef3b905d..3fe1b706 100644 --- a/oss/type_test.go +++ b/oss/type_test.go @@ -107,33 +107,76 @@ func (s *OssTypeSuite) TestSortUploadPart(c *C) { c.Assert(parts[4].ETag, Equals, "E5") } -func (s *OssTypeSuite) TestNewLifecleRule(c *C) { +func (s *OssTypeSuite) TestValidateLifecleRules(c *C) { expiration := LifecycleExpiration{ Days: 30, CreatedBeforeDate: "2015-11-11T00:00:00.000Z", } - _, err := NewLifecycleRule("ruleID", "prefix", true, &expiration, nil) + rule := LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Expiration: &expiration, + } + rules := []LifecycleRule{rule} + err := verifyLifecycleRules(rules) + c.Assert(err, NotNil) + + expiration = LifecycleExpiration{ + Date: "2015-11-11T00:00:00.000Z", + CreatedBeforeDate: "2015-11-11T00:00:00.000Z", + } + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Expiration: &expiration, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) expiration = LifecycleExpiration{ Days: 0, CreatedBeforeDate: "", + Date: "", + } + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Expiration: &expiration, } - _, err = NewLifecycleRule("ruleID", "prefix", true, &expiration, nil) + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) abortMPU := LifecycleAbortMultipartUpload{ Days: 30, CreatedBeforeDate: "2015-11-11T00:00:00.000Z", } - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, &abortMPU) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + AbortMultipartUpload: &abortMPU, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) abortMPU = LifecycleAbortMultipartUpload{ Days: 0, CreatedBeforeDate: "", } - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, &abortMPU) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + AbortMultipartUpload: &abortMPU, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) transition := LifecycleTransition{ @@ -141,7 +184,14 @@ func (s *OssTypeSuite) TestNewLifecleRule(c *C) { CreatedBeforeDate: "2015-11-11T00:00:00.000Z", StorageClass: StorageIA, } - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, nil, &transition) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Transitions: []LifecycleTransition{transition}, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) transition = LifecycleTransition{ @@ -149,21 +199,42 @@ func (s *OssTypeSuite) TestNewLifecleRule(c *C) { CreatedBeforeDate: "", StorageClass: StorageIA, } - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, nil, &transition) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Transitions: []LifecycleTransition{transition}, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) transition = LifecycleTransition{ Days: 30, StorageClass: StorageStandard, } - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, nil, &transition) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Transitions: []LifecycleTransition{transition}, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) transition = LifecycleTransition{ CreatedBeforeDate: "2015-11-11T00:00:00.000Z", StorageClass: StorageStandard, } - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, nil, &transition) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Transitions: []LifecycleTransition{transition}, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) transition1 := LifecycleTransition{ @@ -178,34 +249,75 @@ func (s *OssTypeSuite) TestNewLifecleRule(c *C) { Days: 100, StorageClass: StorageArchive, } - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, nil, &transition1, &transition2, &transition3) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Transitions: []LifecycleTransition{transition1, transition2, transition3}, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, nil) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, NotNil) expiration = LifecycleExpiration{ Days: 30, } - _, err = NewLifecycleRule("ruleID", "prefix", true, &expiration, nil) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Expiration: &expiration, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, IsNil) expiration = LifecycleExpiration{ CreatedBeforeDate: "2015-11-11T00:00:00.000Z", } - _, err = NewLifecycleRule("ruleID", "prefix", true, &expiration, nil) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Expiration: &expiration, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, IsNil) abortMPU = LifecycleAbortMultipartUpload{ Days: 30, } - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, &abortMPU) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + AbortMultipartUpload: &abortMPU, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, IsNil) abortMPU = LifecycleAbortMultipartUpload{ CreatedBeforeDate: "2015-11-11T00:00:00.000Z", } - _, err = NewLifecycleRule("ruleID", "prefix", true, nil, &abortMPU) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + AbortMultipartUpload: &abortMPU, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, IsNil) expiration = LifecycleExpiration{ @@ -214,7 +326,15 @@ func (s *OssTypeSuite) TestNewLifecleRule(c *C) { abortMPU = LifecycleAbortMultipartUpload{ Days: 30, } - _, err = NewLifecycleRule("ruleID", "prefix", true, &expiration, &abortMPU) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Expiration: &expiration, + AbortMultipartUpload: &abortMPU, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, IsNil) expiration = LifecycleExpiration{ @@ -227,7 +347,16 @@ func (s *OssTypeSuite) TestNewLifecleRule(c *C) { Days: 30, StorageClass: StorageIA, } - _, err = NewLifecycleRule("ruleID", "prefix", true, &expiration, &abortMPU) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Expiration: &expiration, + AbortMultipartUpload: &abortMPU, + Transitions: []LifecycleTransition{transition}, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, IsNil) expiration = LifecycleExpiration{ @@ -244,6 +373,15 @@ func (s *OssTypeSuite) TestNewLifecleRule(c *C) { Days: 60, StorageClass: StorageArchive, } - _, err = NewLifecycleRule("ruleID", "prefix", true, &expiration, &abortMPU, &transition1, &transition2) + rule = LifecycleRule{ + ID: "ruleID", + Prefix: "prefix", + Status: "Enabled", + Expiration: &expiration, + AbortMultipartUpload: &abortMPU, + Transitions: []LifecycleTransition{transition1, transition2}, + } + rules = []LifecycleRule{rule} + err = verifyLifecycleRules(rules) c.Assert(err, IsNil) } diff --git a/sample/bucket_lifecycle.go b/sample/bucket_lifecycle.go index afabdcfc..a4dc9622 100644 --- a/sample/bucket_lifecycle.go +++ b/sample/bucket_lifecycle.go @@ -24,11 +24,13 @@ func BucketLifecycleSample() { expriation := oss.LifecycleExpiration{ CreatedBeforeDate: "2015-11-11T00:00:00.000Z", } - rule1, err := oss.NewLifecycleRule("rule1", "one", true, &expriation, nil) - if err != nil { - HandleError(err) + rule1 := oss.LifecycleRule{ + ID: "rule1", + Prefix: "one", + Status: "Enabled", + Expiration: &expriation, } - var rules = []oss.LifecycleRule{*rule1} + var rules = []oss.LifecycleRule{rule1} err = client.SetBucketLifecycle(bucketName, rules) if err != nil { HandleError(err) @@ -50,11 +52,13 @@ func BucketLifecycleSample() { Days: 30, StorageClass: oss.StorageArchive, } - rule2, err := oss.NewLifecycleRule("rule2", "two", true, nil, nil, &transitionIA, &transitionArch) - if err != nil { - HandleError(err) + rule2 := oss.LifecycleRule{ + ID: "rule2", + Prefix: "two", + Status: "Enabled", + Transitions: []oss.LifecycleTransition{transitionIA, transitionArch}, } - rules = []oss.LifecycleRule{*rule2} + rules = []oss.LifecycleRule{rule2} err = client.SetBucketLifecycle(bucketName, rules) if err != nil { HandleError(err) @@ -65,16 +69,18 @@ func BucketLifecycleSample() { if err != nil { HandleError(err) } - fmt.Printf("Bucket Lifecycle:%v, %v, %v\n", lc.Rules, *lc.Rules[0].Transition[0], *lc.Rules[0].Transition[1]) + fmt.Printf("Bucket Lifecycle:%v\n", lc.Rules) abortMPU := oss.LifecycleAbortMultipartUpload{ Days: 3, } - rule3, err := oss.NewLifecycleRule("rule3", "three", true, nil, &abortMPU) - if err != nil { - HandleError(err) + rule3 := oss.LifecycleRule{ + ID: "rule3", + Prefix: "three", + Status: "Enabled", + AbortMultipartUpload: &abortMPU, } - rules = append(lc.Rules, *rule3) + rules = append(lc.Rules, rule3) err = client.SetBucketLifecycle(bucketName, rules) if err != nil { HandleError(err) @@ -85,7 +91,7 @@ func BucketLifecycleSample() { if err != nil { HandleError(err) } - fmt.Printf("Bucket Lifecycle:%v, %v, %v, %v\n", lc.Rules, *lc.Rules[0].Transition[0], *lc.Rules[0].Transition[1], *lc.Rules[1].AbortMultipartUpload) + fmt.Printf("Bucket Lifecycle:%v, %v\n", lc.Rules, *lc.Rules[1].AbortMultipartUpload) // Delete bucket's Lifecycle err = client.DeleteBucketLifecycle(bucketName) From 1868bd801957c88a4e7137e10c7411c2127ddd51 Mon Sep 17 00:00:00 2001 From: hangzws Date: Mon, 15 Apr 2019 11:47:37 +0800 Subject: [PATCH 03/15] add unit test while status of lifecycle rules is invalid --- oss/client_test.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/oss/client_test.go b/oss/client_test.go index 9c56a938..cab0266a 100644 --- a/oss/client_test.go +++ b/oss/client_test.go @@ -665,21 +665,34 @@ func (s *OssClientSuite) TestSetBucketLifecycleNew(c *C) { err = client.CreateBucket(bucketNameTest) c.Assert(err, IsNil) - //invalid value of CreatedBeforeDate + //invalid status of lifecyclerule expiration := LifecycleExpiration{ - CreatedBeforeDate: randStr(10), + Days: 30, } rule := LifecycleRule{ ID: "rule1", Prefix: "one", - Status: "Enabled", + Status: "Invalid", Expiration: &expiration, } - c.Assert(err, IsNil) rules := []LifecycleRule{rule} err = client.SetBucketLifecycle(bucketNameTest, rules) c.Assert(err, NotNil) + //invalid value of CreatedBeforeDate + expiration = LifecycleExpiration{ + CreatedBeforeDate: randStr(10), + } + rule = LifecycleRule{ + ID: "rule1", + Prefix: "one", + Status: "Enabled", + Expiration: &expiration, + } + rules = []LifecycleRule{rule} + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, NotNil) + //invalid value of Days abortMPU := LifecycleAbortMultipartUpload{ Days: -30, From d796a42bd2016f1f8d235cebfdc0c8a4724aadc8 Mon Sep 17 00:00:00 2001 From: hangzws Date: Mon, 15 Apr 2019 16:12:39 +0800 Subject: [PATCH 04/15] add test cases & return error while the rules array is empty in function verifLifecycleRules --- oss/client_test.go | 70 ++++++++++++++++++++++++++++++++++++++++++++-- oss/type.go | 3 ++ oss/type_test.go | 4 +++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/oss/client_test.go b/oss/client_test.go index cab0266a..89beebda 100644 --- a/oss/client_test.go +++ b/oss/client_test.go @@ -64,8 +64,6 @@ var ( timeoutInOperation = 3 * time.Second ) -//var randMarker = rand.New(rand.NewSource(time.Now().UnixNano())) - func randStr(n int) string { b := make([]rune, n) randMarker := rand.New(rand.NewSource(time.Now().UnixNano())) @@ -760,6 +758,74 @@ func (s *OssClientSuite) TestSetBucketLifecycleNew(c *C) { err = client.DeleteBucketLifecycle(bucketNameTest) c.Assert(err, IsNil) + // Set two rule: rule1 and rule2 + rules = []LifecycleRule{rule1, rule2} + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, IsNil) + + res, err = client.GetBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(res.Rules), Equals, 2) + c.Assert(res.Rules[0].ID, Equals, "rule1") + c.Assert(res.Rules[0].Expiration, NotNil) + c.Assert(res.Rules[0].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z") + c.Assert(res.Rules[1].ID, Equals, "rule2") + c.Assert(res.Rules[1].Expiration, NotNil) + c.Assert(res.Rules[1].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z") + c.Assert(res.Rules[1].AbortMultipartUpload, NotNil) + c.Assert(res.Rules[1].AbortMultipartUpload.Days, Equals, 30) + + err = client.DeleteBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + + // Set two rule: rule2 and rule3 + rules = []LifecycleRule{rule2, rule3} + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, IsNil) + + res, err = client.GetBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(res.Rules), Equals, 2) + c.Assert(res.Rules[0].ID, Equals, "rule2") + c.Assert(res.Rules[0].Expiration, NotNil) + c.Assert(res.Rules[0].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z") + c.Assert(res.Rules[0].AbortMultipartUpload, NotNil) + c.Assert(res.Rules[0].AbortMultipartUpload.Days, Equals, 30) + c.Assert(res.Rules[1].ID, Equals, "rule3") + c.Assert(res.Rules[1].AbortMultipartUpload, NotNil) + c.Assert(res.Rules[1].AbortMultipartUpload.Days, Equals, 30) + c.Assert(len(res.Rules[1].Transitions), Equals, 2) + c.Assert(res.Rules[1].Transitions[0].StorageClass, Equals, StorageIA) + c.Assert(res.Rules[1].Transitions[0].Days, Equals, 3) + c.Assert(res.Rules[1].Transitions[1].StorageClass, Equals, StorageArchive) + c.Assert(res.Rules[1].Transitions[1].Days, Equals, 30) + + err = client.DeleteBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + + // Set two rule: rule1 and rule3 + rules = []LifecycleRule{rule1, rule3} + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, IsNil) + + res, err = client.GetBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(res.Rules), Equals, 2) + c.Assert(res.Rules[0].ID, Equals, "rule1") + c.Assert(res.Rules[0].Expiration, NotNil) + c.Assert(res.Rules[0].Expiration.CreatedBeforeDate, Equals, "2015-11-11T00:00:00.000Z") + c.Assert(res.Rules[1].ID, Equals, "rule3") + c.Assert(res.Rules[1].AbortMultipartUpload, NotNil) + c.Assert(res.Rules[1].AbortMultipartUpload.Days, Equals, 30) + c.Assert(len(res.Rules[1].Transitions), Equals, 2) + c.Assert(res.Rules[1].Transitions[0].StorageClass, Equals, StorageIA) + c.Assert(res.Rules[1].Transitions[0].Days, Equals, 3) + c.Assert(res.Rules[1].Transitions[1].StorageClass, Equals, StorageArchive) + c.Assert(res.Rules[1].Transitions[1].Days, Equals, 30) + + err = client.DeleteBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + // Set three rules rules = []LifecycleRule{rule1, rule2, rule3} err = client.SetBucketLifecycle(bucketNameTest, rules) diff --git a/oss/type.go b/oss/type.go index b0fe149a..31d070ef 100644 --- a/oss/type.go +++ b/oss/type.go @@ -100,6 +100,9 @@ func BuildLifecycleRuleByDate(id, prefix string, status bool, year, month, day i // ValidateLifecycleRule Determine if a lifecycle rule is valid, if it is invalid, it will return an error. func verifyLifecycleRules(rules []LifecycleRule) error { + if len(rules) == 0 { + return fmt.Errorf("invalid rules, the length of rules is zero") + } for _, rule := range rules { if rule.Status != "Enabled" && rule.Status != "Disabled" { return fmt.Errorf("invalid rule, the value of status must be Enabled or Disabled") diff --git a/oss/type_test.go b/oss/type_test.go index 3fe1b706..0029c416 100644 --- a/oss/type_test.go +++ b/oss/type_test.go @@ -268,6 +268,10 @@ func (s *OssTypeSuite) TestValidateLifecleRules(c *C) { err = verifyLifecycleRules(rules) c.Assert(err, NotNil) + rules = []LifecycleRule{} + err1 := verifyLifecycleRules(rules) + c.Assert(err1, NotNil) + expiration = LifecycleExpiration{ Days: 30, } From 6c24c5c3980678acf884e42d764d552f51a25e76 Mon Sep 17 00:00:00 2001 From: hangzws Date: Mon, 15 Apr 2019 16:42:51 +0800 Subject: [PATCH 05/15] release v1.9.6 --- CHANGELOG.md | 12 ++++++++++++ README.md | 3 --- oss/const.go | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 350c9e60..794c53f3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,16 @@ # ChangeLog - Aliyun OSS SDK for Go +## 版本号:v1.9.6 日期:2019-04-15 +### 变更内容 +- 变更:扩展lifecycle功能,提供设置AbortMutipartUpload和Transitions两种规则的生命周期管理的处理 +- 修复:测试用例BucketName使用固定前缀+随机的字符串 +- 修复:测试用例ObjectName使用固定前缀+随机字符串 +- 修复:测试用例有关bucket相关的异步操作,统一定义sleep时间 +- 修复:测试集结束后,列出bucket内的所有对象并删除所有测试的对象 +- 修复:测试集结束后,列出bucket内的所有未上传完成的分片并删除所有测试过程中产生的为上传完成的分片 +- 修复:支持上传webp类型的对象时从对象的后缀名字自动解析对应的content-type并设置content-type +- 变更:增加在put/copy/append等接口时时设置对象的存储类型的sample +- 修复:sample示例中的配置项的值改为直接从环境变量读取 + ## 版本号:1.9.5 日期:2019-03-08 ### 变更内容 - 变更:增加了限速上传功能 diff --git a/README.md b/README.md index a2218c4a..89456a01 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,6 @@ > - The OSS can store any type of files and therefore applies to various websites, development enterprises and developers. > - With this SDK, you can upload, download and manage data on any app anytime and anywhere conveniently. -## Version -> - Current version: 1.9.5 - ## Running Environment > - Go 1.5 or above. diff --git a/oss/const.go b/oss/const.go index 2252bf84..0af39dee 100644 --- a/oss/const.go +++ b/oss/const.go @@ -155,5 +155,5 @@ const ( CheckpointFileSuffix = ".cp" // Checkpoint file suffix - Version = "1.9.5" // Go SDK version + Version = "v1.9.6" // Go SDK version ) From f90d3f2c9762eafbb77c4e6ebf6a7453cc8e698d Mon Sep 17 00:00:00 2001 From: hangzws Date: Tue, 16 Apr 2019 14:18:40 +0800 Subject: [PATCH 06/15] use Tags instead tags --- oss/type.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oss/type.go b/oss/type.go index 31d070ef..04457bcd 100644 --- a/oss/type.go +++ b/oss/type.go @@ -604,5 +604,5 @@ type Tag struct { // ObjectTagging tagset for the object type ObjectTagging struct { XMLName xml.Name `xml:"Tagging"` - tags []Tag `xml:"TagSet>Tag,omitempty"` + Tags []Tag `xml:"TagSet>Tag,omitempty"` } From 6cbae959a135cafdf06d45a3511f1f675daabe95 Mon Sep 17 00:00:00 2001 From: hangzws Date: Fri, 19 Apr 2019 17:43:00 +0800 Subject: [PATCH 07/15] add object tagging related apis --- oss/bucket_test.go | 192 +++++++++++++++++++++++++++++++++++++ oss/option.go | 18 +++- oss/type.go | 1 + sample.go | 3 +- sample/bucket_lifecycle.go | 47 ++++++--- sample/copy_object.go | 21 +++- sample/object_tagging.go | 75 +++++++++++++++ 7 files changed, 337 insertions(+), 20 deletions(-) create mode 100644 sample/object_tagging.go diff --git a/oss/bucket_test.go b/oss/bucket_test.go index 1f2748b9..5a2e2425 100644 --- a/oss/bucket_test.go +++ b/oss/bucket_test.go @@ -2778,3 +2778,195 @@ func (s *OssBucketSuite) TestUploadObjectWithWebpFormat(c *C) { bucket.DeleteObject(objectName) client.DeleteBucket(bucketName) } + +func (s *OssBucketSuite) TestPutObjectTagging(c *C) { + // put object with tagging + objectName := objectNamePrefix + randStr(8) + tag1 := Tag{ + Key: randStr(8), + Value: randStr(16), + } + tag2 := Tag{ + Key: randStr(8), + Value: randStr(16), + } + tagging := ObjectTagging{ + Tags: []Tag{tag1, tag2}, + } + err := s.bucket.PutObject(objectName, strings.NewReader(randStr(1024)), Tagging(tagging)) + c.Assert(err, IsNil) + + headers, err := s.bucket.GetObjectDetailedMeta(objectName) + taggingCount, err := strconv.Atoi(headers["x-oss-tagging-count"][0]) + c.Assert(err, IsNil) + c.Assert(taggingCount, Equals, 2) + + // put tagging + tag := Tag{ + Key: randStr(8), + Value: randStr(16), + } + tagging.Tags = []Tag{tag} + err = s.bucket.PutObjectTagging(objectName, tagging) + c.Assert(err, IsNil) + + tagging, err = s.bucket.GetObjectTagging(objectName) + c.Assert(len(tagging.Tags), Equals, 1) + c.Assert(tagging.Tags[0].Key, Equals, tag.Key) + c.Assert(tagging.Tags[0].Value, Equals, tag.Value) + + //put tagging, the length of the key exceeds 128 + tag = Tag{ + Key: randStr(129), + Value: randStr(16), + } + tagging.Tags = []Tag{tag} + err = s.bucket.PutObjectTagging(objectName, tagging) + c.Assert(err, NotNil) + + //put tagging, the length of the value exceeds 256 + tag = Tag{ + Key: randStr(8), + Value: randStr(257), + } + tagging.Tags = []Tag{tag} + err = s.bucket.PutObjectTagging(objectName, tagging) + c.Assert(err, NotNil) + + //put tagging, the lens of tags exceed 10 + tagging.Tags = []Tag{} + for i := 0; i < 11; i++ { + tag = Tag{ + Key: randStr(8), + Value: randStr(16), + } + tagging.Tags = append(tagging.Tags, tag) + } + err = s.bucket.PutObjectTagging(objectName, tagging) + c.Assert(err, NotNil) + + //put tagging, invalid value of tag key + tag = Tag{ + Key: randStr(8) + "&", + Value: randStr(16), + } + tagging.Tags = []Tag{tag} + err = s.bucket.PutObjectTagging(objectName, tagging) + c.Assert(err, NotNil) + + //put tagging, invalid value of tag value + tag = Tag{ + Key: randStr(8), + Value: randStr(16) + "&", + } + tagging.Tags = []Tag{tag} + err = s.bucket.PutObjectTagging(objectName, tagging) + c.Assert(err, NotNil) + + //put tagging, repeated tag keys + tag1 = Tag{ + Key: randStr(8), + Value: randStr(16), + } + tag2 = Tag{ + Key: tag1.Key, + Value: randStr(16), + } + tagging.Tags = []Tag{tag1, tag2} + err = s.bucket.PutObjectTagging(objectName, tagging) + c.Assert(err, NotNil) + + s.bucket.DeleteObject(objectName) +} + +func (s *OssBucketSuite) TestGetObjectTagging(c *C) { + // get object which has 2 tags + objectName := objectNamePrefix + randStr(8) + tag1 := Tag{ + Key: randStr(8), + Value: randStr(16), + } + tag2 := Tag{ + Key: randStr(8), + Value: randStr(16), + } + tagging := ObjectTagging{ + Tags: []Tag{tag1, tag2}, + } + err := s.bucket.PutObject(objectName, strings.NewReader(randStr(1024)), Tagging(tagging)) + c.Assert(err, IsNil) + + tagging, err = s.bucket.GetObjectTagging(objectName) + c.Assert(len(tagging.Tags), Equals, 2) + c.Assert(tagging.Tags[0].Key, Equals, tag1.Key) + c.Assert(tagging.Tags[0].Value, Equals, tag1.Value) + c.Assert(tagging.Tags[1].Key, Equals, tag2.Key) + c.Assert(tagging.Tags[1].Value, Equals, tag2.Value) + + // get tagging of an object that is not exist + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + tagging, err = s.bucket.GetObjectTagging(objectName) + c.Assert(err, NotNil) + c.Assert(len(tagging.Tags), Equals, 0) + + // get object which has no tag + objectName = objectNamePrefix + randStr(8) + err = s.bucket.PutObject(objectName, strings.NewReader(randStr(1024))) + c.Assert(err, IsNil) + tagging, err = s.bucket.GetObjectTagging(objectName) + c.Assert(err, IsNil) + c.Assert(len(tagging.Tags), Equals, 0) + + // copy object, with tagging option + destObjectName := objectName + "-dest" + tagging.Tags = []Tag{tag1, tag2} + _, err = s.bucket.CopyObject(objectName, destObjectName, Tagging(tagging)) + c.Assert(err, IsNil) + tagging, err = s.bucket.GetObjectTagging(objectName) + c.Assert(err, IsNil) + c.Assert(len(tagging.Tags), Equals, 0) + + // copy object, with tagging option, the value of tagging directive is "REPLACE" + _, err = s.bucket.CopyObject(objectName, destObjectName, Tagging(tagging), TaggingDirective(TaggingReplace)) + c.Assert(err, IsNil) + tagging, err = s.bucket.GetObjectTagging(objectName) + c.Assert(err, IsNil) + c.Assert(len(tagging.Tags), Equals, 2) + c.Assert(tagging.Tags[0].Key, Equals, tag1.Key) + c.Assert(tagging.Tags[0].Value, Equals, tag1.Value) + c.Assert(tagging.Tags[1].Key, Equals, tag2.Key) + c.Assert(tagging.Tags[1].Value, Equals, tag2.Value) + + s.bucket.DeleteObject(objectName) + s.bucket.DeleteObject(destObjectName) +} + +func (s *OssBucketSuite) TestDeleteObjectTagging(c *C) { + // delete object tagging, the object is not exist + objectName := objectNamePrefix + randStr(8) + err := s.bucket.DeleteObjectTagging(objectName) + c.Assert(err, NotNil) + + // delete object tagging + tag := Tag{ + Key: randStr(8), + Value: randStr(16), + } + tagging := ObjectTagging{ + Tags: []Tag{tag}, + } + err = s.bucket.PutObject(objectName, strings.NewReader(randStr(1024)), Tagging(tagging)) + c.Assert(err, IsNil) + err = s.bucket.DeleteObjectTagging(objectName) + c.Assert(err, IsNil) + tagging, err = s.bucket.GetObjectTagging(objectName) + c.Assert(err, IsNil) + c.Assert(len(tagging.Tags), Equals, 0) + + //delete object tagging again + err = s.bucket.DeleteObjectTagging(objectName) + c.Assert(err, IsNil) + + s.bucket.DeleteObject(objectName) +} diff --git a/oss/option.go b/oss/option.go index 7fe0eea6..0b6ceec7 100644 --- a/oss/option.go +++ b/oss/option.go @@ -3,6 +3,7 @@ package oss import ( "fmt" "net/http" + "net/url" "strconv" "strings" "time" @@ -202,9 +203,20 @@ func RequestPayer(payerType PayerType) Option { return setHeader(HTTPHeaderOssRequester, string(payerType)) } -// Tagging is an option to set object tagging, notice: the value is url.QueryEscape(TagA)=url.QueryEscape(A) & url.QueryEscape(TagB)=url.QueryEscape(B)... -func Tagging(value string) Option { - return setHeader(HTTPHeaderOssTagging, value) +// Tagging is an option to set object tagging +func Tagging(tagging ObjectTagging) Option { + if len(tagging.Tags) == 0 { + return nil + } + + taggingValue := "" + for index, tag := range tagging.Tags { + if index != 0 { + taggingValue += "&" + } + taggingValue += url.QueryEscape(tag.Key) + "=" + url.QueryEscape(tag.Value) + } + return setHeader(HTTPHeaderOssTagging, taggingValue) } // TaggingDirective is an option to set X-Oss-Metadata-Directive header diff --git a/oss/type.go b/oss/type.go index 04457bcd..f72a49bd 100644 --- a/oss/type.go +++ b/oss/type.go @@ -47,6 +47,7 @@ type LifecycleRule struct { ID string `xml:"ID,omitempty"` // The rule ID Prefix string `xml:"Prefix"` // The object key prefix Status string `xml:"Status"` // The rule status (enabled or not) + Tags []Tag `xml:"Tag,omitempty"` // the tags property Expiration *LifecycleExpiration `xml:"Expiration,omitempty"` // The expiration property Transitions []LifecycleTransition `xml:"Transition,omitempty"` // The transition property AbortMultipartUpload *LifecycleAbortMultipartUpload `xml:"AbortMultipartUpload,omitempty"` // The AbortMultipartUpload property diff --git a/sample.go b/sample.go index 8fd8cc81..872ce442 100644 --- a/sample.go +++ b/sample.go @@ -4,7 +4,7 @@ package main import ( "fmt" - + "github.com/aliyun/aliyun-oss-go-sdk/sample" ) @@ -31,6 +31,7 @@ func main() { sample.SignURLSample() sample.ArchiveSample() + sample.ObjectTaggingSample() fmt.Println("All samples completed") } diff --git a/sample/bucket_lifecycle.go b/sample/bucket_lifecycle.go index a4dc9622..80d68507 100644 --- a/sample/bucket_lifecycle.go +++ b/sample/bucket_lifecycle.go @@ -20,7 +20,7 @@ func BucketLifecycleSample() { HandleError(err) } - // Case 1: Set the lifecycle. The rule ID is rule1 and the applied objects' prefix is one and expired time is 11/11/2015 + // Case 1: Set the lifecycle. The rule ID is rule1 and the applied objects' prefix is one and the last modified Date is before 2015/11/11 expriation := oss.LifecycleExpiration{ CreatedBeforeDate: "2015-11-11T00:00:00.000Z", } @@ -36,14 +36,14 @@ func BucketLifecycleSample() { HandleError(err) } - // Get the bucket's lifecycle + // Case 2: Get the bucket's lifecycle lc, err := client.GetBucketLifecycle(bucketName) if err != nil { HandleError(err) } fmt.Printf("Bucket Lifecycle:%v, %v\n", lc.Rules, *lc.Rules[0].Expiration) - // Case 2: Set the lifecycle, The rule ID is id2 and the applied objects' prefix is two and the expired time is three days after the object created. + // Case 3: Set the lifecycle, The rule ID is rule2 and the applied objects' prefix is two. The object start with the prefix will be transited to IA storage Type 3 days latter, and to archive storage type 30 days latter transitionIA := oss.LifecycleTransition{ Days: 3, StorageClass: oss.StorageIA, @@ -64,13 +64,7 @@ func BucketLifecycleSample() { HandleError(err) } - // Get the bucket's lifecycle - lc, err = client.GetBucketLifecycle(bucketName) - if err != nil { - HandleError(err) - } - fmt.Printf("Bucket Lifecycle:%v\n", lc.Rules) - + // Case 4: Set the lifecycle, The rule ID is rule3 and the applied objects' prefix is three. The object start with the prefix will be transited to IA storage Type 3 days latter, and to archive storage type 30 days latter, the uncompleted multipart upload will be abort 3 days latter. abortMPU := oss.LifecycleAbortMultipartUpload{ Days: 3, } @@ -86,14 +80,39 @@ func BucketLifecycleSample() { HandleError(err) } - // Get the bucket's lifecycle - lc, err = client.GetBucketLifecycle(bucketName) + // Case 5: Set the lifecycle. The rule ID is rule4 and the applied objects' has the tagging which prefix is four and the last modified Date is before 2015/11/11 + expriation = oss.LifecycleExpiration{ + CreatedBeforeDate: "2015-11-11T00:00:00.000Z", + } + tag1 := oss.Tag{ + Key: "key1", + Value: "value1", + } + tag2 := oss.Tag{ + Key: "key2", + Value: "value2", + } + rule4 := oss.LifecycleRule{ + ID: "rule4", + Prefix: "four", + Status: "Enabled", + Tags: []oss.Tag{tag1, tag2}, + Expiration: &expriation, + } + rules = []oss.LifecycleRule{rule4} + err = client.SetBucketLifecycle(bucketName, rules) if err != nil { HandleError(err) } - fmt.Printf("Bucket Lifecycle:%v, %v\n", lc.Rules, *lc.Rules[1].AbortMultipartUpload) - // Delete bucket's Lifecycle + // for Debug only + //lc, err = client.GetBucketLifecycle(bucketName) + //if err != nil { + // HandleError(err) + //} + //fmt.Printf("Bucket Lifecycle:%v, %v\n", lc.Rules, *lc.Rules[0].Expiration) + + // Case 6: Delete bucket's Lifecycle err = client.DeleteBucketLifecycle(bucketName) if err != nil { HandleError(err) diff --git a/sample/copy_object.go b/sample/copy_object.go index f3b8b82f..970eeed4 100644 --- a/sample/copy_object.go +++ b/sample/copy_object.go @@ -106,8 +106,25 @@ func CopyObjectSample() { // Case 7: Set the storage classes.OSS provides three storage classes: Standard, Infrequent Access, and Archive. // Copy a object in the same bucket, and set object's storage-class to Archive. - _, rr := bucket.CopyObject(objectKey, objectKey+"DestArchive", oss.ObjectStorageClass("Archive")) - if rr != nil { + _, err = bucket.CopyObject(objectKey, objectKey+"DestArchive", oss.ObjectStorageClass("Archive")) + if err != nil { + HandleError(err) + } + + // Case 8: Copy object with tagging, the value of tagging directive is REPLACE + tag1 := oss.Tag{ + Key: "key1", + Value: "value1", + } + tag2 := oss.Tag{ + Key: "key2", + Value: "value2", + } + tagging := oss.ObjectTagging{ + Tags: []oss.Tag{tag1, tag2}, + } + _, err = bucket.CopyObject(objectKey, objectKey+"WithTagging", oss.Tagging(tagging), oss.TaggingDirective(oss.TaggingReplace)) + if err != nil { HandleError(err) } diff --git a/sample/object_tagging.go b/sample/object_tagging.go new file mode 100644 index 00000000..d9f2c747 --- /dev/null +++ b/sample/object_tagging.go @@ -0,0 +1,75 @@ +package sample + +import ( + "fmt" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// ObjectTaggingSample shows how to set and get object Tagging +func ObjectTaggingSample() { + // Create bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Create object + err = bucket.PutObject(objectKey, strings.NewReader("ObjectTaggingSample")) + if err != nil { + HandleError(err) + } + + // Case 1: Set Tagging of object + tag1 := oss.Tag{ + Key: "key1", + Value: "value1", + } + tag2 := oss.Tag{ + Key: "key2", + Value: "value2", + } + tagging := oss.ObjectTagging{ + Tags: []oss.Tag{tag1, tag2}, + } + err = bucket.PutObjectTagging(objectKey, tagging) + if err != nil { + HandleError(err) + } + + // Case 2: Get Tagging of object + tagging, err = bucket.GetObjectTagging(objectKey) + if err != nil { + HandleError(err) + } + fmt.Printf("Object Tagging: %v\n", tagging) + + tag3 := oss.Tag{ + Key: "key3", + Value: "value3", + } + + // Case 3: Put object with tagging + tagging = oss.ObjectTagging{ + Tags: []oss.Tag{tag1, tag2, tag3}, + } + err = bucket.PutObject(objectKey, strings.NewReader("ObjectTaggingSample"), oss.Tagging(tagging)) + if err != nil { + HandleError(err) + } + + // Case 4: Delete Tagging of object + err = bucket.DeleteObjectTagging(objectKey) + if err != nil { + HandleError(err) + } + + // Delete object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("ObjectACLSample completed") +} From 656d5662ec0b302d80386be78492ec75b0986abd Mon Sep 17 00:00:00 2001 From: hangzws Date: Fri, 19 Apr 2019 18:00:45 +0800 Subject: [PATCH 08/15] set bucket acl with parameter "acl" --- oss/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/oss/client.go b/oss/client.go index 0104cf0f..8a0e7ec4 100644 --- a/oss/client.go +++ b/oss/client.go @@ -219,6 +219,7 @@ func (client Client) GetBucketLocation(bucketName string) (string, error) { func (client Client) SetBucketACL(bucketName string, bucketACL ACLType) error { headers := map[string]string{HTTPHeaderOssACL: string(bucketACL)} params := map[string]interface{}{} + params["acl"] = nil resp, err := client.do("PUT", bucketName, params, headers, nil) if err != nil { return err From f4e51a76cb8d6cddf4132939f27b527ef1c5ff70 Mon Sep 17 00:00:00 2001 From: hangzws Date: Tue, 7 May 2019 13:53:33 +0800 Subject: [PATCH 09/15] fix ut errors --- oss/bucket_test.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/oss/bucket_test.go b/oss/bucket_test.go index 5a2e2425..b956d4d6 100644 --- a/oss/bucket_test.go +++ b/oss/bucket_test.go @@ -2797,7 +2797,7 @@ func (s *OssBucketSuite) TestPutObjectTagging(c *C) { c.Assert(err, IsNil) headers, err := s.bucket.GetObjectDetailedMeta(objectName) - taggingCount, err := strconv.Atoi(headers["x-oss-tagging-count"][0]) + taggingCount, err := strconv.Atoi(headers["X-Oss-Tagging-Count"][0]) c.Assert(err, IsNil) c.Assert(taggingCount, Equals, 2) @@ -2898,10 +2898,16 @@ func (s *OssBucketSuite) TestGetObjectTagging(c *C) { tagging, err = s.bucket.GetObjectTagging(objectName) c.Assert(len(tagging.Tags), Equals, 2) - c.Assert(tagging.Tags[0].Key, Equals, tag1.Key) - c.Assert(tagging.Tags[0].Value, Equals, tag1.Value) - c.Assert(tagging.Tags[1].Key, Equals, tag2.Key) - c.Assert(tagging.Tags[1].Value, Equals, tag2.Value) + if tagging.Tags[0].Key == tag1.Key { + c.Assert(tagging.Tags[0].Value, Equals, tag1.Value) + c.Assert(tagging.Tags[1].Key, Equals, tag2.Key) + c.Assert(tagging.Tags[1].Value, Equals, tag2.Value) + } else { + c.Assert(tagging.Tags[0].Key, Equals, tag2.Key) + c.Assert(tagging.Tags[0].Value, Equals, tag2.Value) + c.Assert(tagging.Tags[1].Key, Equals, tag1.Key) + c.Assert(tagging.Tags[1].Value, Equals, tag1.Value) + } // get tagging of an object that is not exist err = s.bucket.DeleteObject(objectName) @@ -2928,15 +2934,22 @@ func (s *OssBucketSuite) TestGetObjectTagging(c *C) { c.Assert(len(tagging.Tags), Equals, 0) // copy object, with tagging option, the value of tagging directive is "REPLACE" + tagging.Tags = []Tag{tag1, tag2} _, err = s.bucket.CopyObject(objectName, destObjectName, Tagging(tagging), TaggingDirective(TaggingReplace)) c.Assert(err, IsNil) - tagging, err = s.bucket.GetObjectTagging(objectName) + tagging, err = s.bucket.GetObjectTagging(destObjectName) c.Assert(err, IsNil) c.Assert(len(tagging.Tags), Equals, 2) - c.Assert(tagging.Tags[0].Key, Equals, tag1.Key) - c.Assert(tagging.Tags[0].Value, Equals, tag1.Value) - c.Assert(tagging.Tags[1].Key, Equals, tag2.Key) - c.Assert(tagging.Tags[1].Value, Equals, tag2.Value) + if tagging.Tags[0].Key == tag1.Key { + c.Assert(tagging.Tags[0].Value, Equals, tag1.Value) + c.Assert(tagging.Tags[1].Key, Equals, tag2.Key) + c.Assert(tagging.Tags[1].Value, Equals, tag2.Value) + } else { + c.Assert(tagging.Tags[0].Key, Equals, tag2.Key) + c.Assert(tagging.Tags[0].Value, Equals, tag2.Value) + c.Assert(tagging.Tags[1].Key, Equals, tag1.Key) + c.Assert(tagging.Tags[1].Value, Equals, tag1.Value) + } s.bucket.DeleteObject(objectName) s.bucket.DeleteObject(destObjectName) From 04ecc8d76b4422015a4b7f310811f4f42ca15cae Mon Sep 17 00:00:00 2001 From: hangzws Date: Tue, 7 May 2019 16:23:57 +0800 Subject: [PATCH 10/15] remove debug code in samples and fix travis config --- .travis.yml | 22 ++++++++++++++++------ sample/bucket_lifecycle.go | 7 ------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7cf6c430..749fa1fb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,13 +10,14 @@ install: - go get gopkg.in/check.v1 - go get github.com/satori/go.uuid - go get github.com/baiyubin/aliyun-sts-go-sdk/sts - -- if [[ $TRAVIS_GO_VERSION = '1.7' || $TRAVIS_GO_VERSION > '1.7' ]]; then go get golang.org/x/time/rate ; fi - +- if [[ $TRAVIS_GO_VERSION = '1.7' || $TRAVIS_GO_VERSION > '1.7' ]]; then go get golang.org/x/time/rate + ; fi script: -- cd oss -- travis_wait 30 go test -v -covermode=count -coverprofile=coverage.out -timeout=30m -- "$HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci" +- if [ -n "$OSS_TEST_ACCESS_KEY_ID" ]; then + cd oss + travis_wait 30 go test -v -covermode=count -coverprofile=coverage.out -timeout=30m + $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci + fi env: global: - secure: ZCL5egxJmZA+o1ujsaJe//AIh1ag/exSUJ2FzoKPF3O9c84hMc2k5EYO2rGzTNn1ML6M89Mo5hAvHQhyJEHjRuMtjc1QrfxAaA3mqm4scGXIXesPMqYGuvuPSh++6/fkAwaVBAhrk5GaDG1/FuxHE5zusGx3SvGegnCwO7n/2YCfXco6DWgVCdrz4p1EpPkAM3JIdHFUzsDWiimVuiNAvJmAT8+IeOPTT+WgusCJj4ORS3X3LddTjttBP+hRrp/pGSoNqPMzfysWybtaL2SJ8URtvsxW0Mo5BwocHAxAhPP+M2OscQbDzthSAezCLngYvrfBplfIyWlahlgzNz/FjXz5pQwWdYVNoibyxLLMOH685n75LNONN/xVO/GFmVPx7DMGapkN5NzIWS62D4v8QrRkwtms42OUkyEUHjDh8Evui3K2MNJVXA3TI9zOAR+C0krD7OEyS37qrppodhRxJSqFUlgXnk//wLldMC7vleDd7L2UQSWjqyBHqFOgsVaiLU2KRTY3zvv7ke+dqb5VF31mH6qAr8lJTR9un8M1att0VwCEKxoIRT4cKJCpEtZd8ovXOVt1uE695ThVXE9I5e00GXdTzqXOuv6zT4hv/dgmbz9JN9MYeCwmokEoIUmJKNYERa/bNVVefdnJt7h+dm+KpyPAS+XvPLzjbnWdYNA= @@ -28,3 +29,12 @@ env: - secure: NMVS9EU+ahQXGiyTCHyZ44rf+8b3me3UXD1DozMm04lCvnWoBqJE4aXBGQsDAWuOL4NTTm0SaVu6sBY6ZTXOYYF59mwEbxt4qpmVjZ+vBrtMbMiqoxv145blquR9JKedkdP6IGSd7VSQwSba71f/RVv5VeGvxUSEhCwA04kKxToOPwmnORmT6qwb7PkPCMNHxz4VpsUIsKx8jRrY6Gmp6FvQJBHfKEHnDQohB1ReIYEYi39ijLvpbCZqrB5u1N9oF6WlpBiNIX3kQizn7ftUyewJgoZMnfpW/Lta6e91yzFInWg75bZdW3faa30Qy0yw0zlQIPLs89c8A/XH1fGVECH9At9VNmdYrb0fD9aWnH7zdX6Im+Bw7Ptph4x6tB7zPeFoZR5cVZT7L06/HbnW7NeQk4tg/N4I1tOaO7AQl+ofhCzesZ56bSxETiNFn9QiNwWFTzjlkG7jxN1iAAkdYsZEQHwtEK63R//NJtXpbbtNA831QqgDqBK+IxyKeLhmxmu17dWcUw9tm4jlZ7d6nPB9bzJcVM6K2uRJyW07SlBqd65WJTXPV1PFww8zh+chAC4ZkLDhupn+7ZSG2ylLYGgepmABoC/CXHkXEsNzdQ8wPX/pDIz2WNmwEXyC/Nv+WNpFS/tWIAryIPOLMuETIgbaOLbD5vZDSKxDZVGDvPE= - secure: cNr4PiK6ZZADoRMacL4lvdMYWgM9H4lKN3u+nfMA/MrVrnSjeRonkO7RjMJWs9HicPanEks10R1w/T/6nWyFQV2CPkEBSNSLf3DAD+dlRekKfWogGXzYnvqeiki1HzsIPYTImiD5BtPn6SbJmO/ErJt3zuogspyBws/X7XfZ+u8FIpPsYEmvHslT2jARuput0yNfldUgxxyI0IvgkuCEcCTFwePspjbn6zR6Df67e+r5ibFqkdPYdXkQVpvfA90RPpfBUuuaEO7kkFlKbPK+Nl/jbUnjcfbe8zJRpSb8j/S2USAiBUjFsqsdvFqZ9WumjXJLrrNFt/UgIXaMyG3Y8xJl9kzCcx36wcNoaP2mx2qucYTdC0ey49g0uywvOVDdykmctQRF7uYQS+UkMqs5jRLgAjQ1/wJISrvtcpaK/4DyhLBUFrOf9chep2hzWBFaWPto1IUpWu9Sjtz5rtjsAm5LR7zvIxcorvRall5kRokAspHb9+TaQKSDOpYNF+PB77cb9H0OLZBLVPGo0WJHq5dv0NVZSH9JVq4AiJDmvMWI6weBis+tLbECMwbeTezo6hDOuII7VZt/KcHgzlt3KCDwv8krQq71q7ySDt7SxrvLeDjeiEFkjwA0lN7Cin1yqjON83LsIsWkKxbf+xNJRngSE4bPn95j3pHELdo3uqY= - secure: iDkNjibPknbawHN+jobw1AEqhQDhqFvGPBUX7FkxlrIanNR71Tj8CPAFtDpJbYaBMdPt4IzOmD2JVo9w7E1TfNX4DsOpkb2MbOr55TxfXQ/+y7TBdJ9B/62BvhFWk8Hvq8TWVPTCgNIaVXNfqBAj6WQSX3AbUVHWSOM9Aey5joBZuhdWXSmKo46lLOradacDDPZYjrLEsMTS2CotzduKQ4C8dGCVcMEbBnS3O2WRj3oR0wiiP3X0jbnxJkOV2MCoadZxSu5B+gaaJ+Yv7EKT0vy6ASp6LYrWkjY0eKbTqy8NtCvCFlliND/iaq4LEv838hQfO/o0WeB2b7/2MH2EW1v8XLacV12ak5wJgb7b+L6fG+lMKMta5Re+vjdRYgoU5EVeWQNxrnX1chEdzFXb/q2+5DVp43qH5i1Tu4FR/kSBobQeSAbT7HTkWAVz3kg8HmubYZ3P0eXToZA/DlX0dphWxO9ShR3H+XTJhh3tSqzxMZxxhGqPcN4DPSfOTnJQ0v0NPz016lulCr9SuIOSM3f7HpeGXo5SeQbrY3yCnBG8Qxpx2kYaZZlT4J6fx3iFl77SY/lQu6H/Y8ZtufWEogPSkGEh+NLLWuwwBQFC3vH8l3J26vcqHZR3N9+GyqX13CSqWEUysMF4nBOi52ckhwJRF8hAeX+DIqxoLfjUkDc= + - secure: iZmWFdPBt9JCDJqWGeX0gznM22ulUrz15Z7YnDfWNhniMs759YWgIdt3MGr2blMmo5MpDZiticaBcE9aX3p7aniPZkbXcebswz5DwCKVRUf1dhtrzwbnDebSwKrlYG8SuGgMkKH/7F+sEkHtTy+SFDkFwEAwcGjPdPntEX15U6KPrlpD5AOvq/d83xNV6QYleJmkRjsTiUjgGzbYfm6Lu/bzBsckYjwWjcz5rxkeSIQHV8/nnw7xs0figTVcb0K5p7Rpvhg3TCAlSJcljg9IDOEuvILQGNDpdgzXajTTNOHYm9nOBrNQhTn35c8DRDl0uQFe+c6PBvBNhSvSKZ2t9REjok70gR/IKJNTuecS/rppYG8wfjlGg1axoJMZJG1OwOJxWiMHwa9WM/diJ2e5D/WTbQ1T9DLRp8Aco3LnWSfZ+tZH+4tAS9FE4oXEPLlHtRyRIV+MzPK3HinIXYdwGH7pDaAQDnfH562br3tl3CovjAPSrIlDgJh92qatLk+1fBQT/7paMNdelAWLjcujRRI9MWsKzi/aEUVbTfUOSCJH/vWQRJR9or2saOobHE+G4f2+k77r21YRztrZaJF6aiob/qf3altKW5rQRdj/7PgNtG3N/bwFOgRjpOQ8dgNn0J+xK4PQTtSVH7kD4aIcJma23erS+K29dcY/wk6fuBM= + - secure: IkTwVHWb8yGufpyBaQDL1WpTywnMb5FhhtXgP8ycxK9MIuKSfgxPlQm6Oel/8Xjy2HhfKTUX0TOwQ4yAuZtRuU/T5NdjGgBYdLjGWwd/Wgkxt7JkpkFd8lvMrh2moAgvf/Qfk0P73OdAyn2y2CZhm7F7GI3lD/K4NmbFnR5AkDGDBB7J81zc1eTGjMfYRELHMFLJTg8hKr64SljXuRBmyxAVImaBROXzml2pP3UywWKNsw0eEOaeJaVJFkStBh89IOYaznckdstdbTuMw9+hZywBTcQHqxaaI5stdtyKgJcyEcjdJOqNGwCf1Hqd18SH7aCfXsUUrwx62ZSheiQRapOz+XNMiKC+1nimNvaYIFEuPZUGjYfi2/c27Z6paBI9ylGpoUXSvCH2rmaCYUIJBRXVmqzvhi0am5FKBh9NovkKUE24B8rMhm1xV82HQLTCho6MnemXFfji2AXaJzYAa6lU31b8BAzQfy+Nn9lh8I/MMjd15Y+8nHFjKqeKZIBi5qO4B8AGNYwCBFhrIOF6uHGubOuvyKv8DSBxcaDhCoyOLa5QLT4vQzQCX17xUAFomXkX9mHbJA8kTxMaFOQPeVCHkfjZxuui57vbPt4CafGzbymi/SE03oNUheQbuHdqy+jkjj7XU6d4FdNYHxp4rvhtvjYrPJQ5gr6A3sdurd4= + - secure: WNjlMEt8xGRc6blhZF9HkyWEQukDzEwbk/hIljEz+K00W+ltDxPqFZ4fsgnjCPLS0fTL1zgtg3Rji6Mi8hSQd9Z5Yh98fCHN/gBfGxziKB0BOlVQDAl8yaUchiHZ6biM+HC2AocbZXlaNcSOHJy7vQk981y48PNPFadZgJZWWF4eFMkAn+74sPMW53V9J4J2ot3AZ7+ABg2vHTbqdA1s09/6fjlTcKVSxm7eDkkoOYycxrD5k2lm3N0I3YvkWum3ZMcCi1bWk4WXQSgy8q0dy7QBya7+BnRWh/EO2HNFAO4l/tqdev7GC3/tSgTbepNV5GUHazvsrANzZGnWP5kpjjis+Z4GiB3TMNLN1c+KeH2kbBkxi0vyDP5rzzI/rORRp5a3SBDAPkz0u/CHB9sltriErp86v9w6YxDRx0QcFheEXbcsk+3Vajy3+v1ly2PgMfLXcfojCfc27ymUCCymXMHrTG8DeUMnfIyUoP2xDx59tZH8rRlbUQmtlLhBYxPojsw5I2YhnMv4ThHZ5RhGSbNQuZ2yHsdQeGcTqSZ+bVT6xHbQbP4nk6ZtLHC6eq/pAQjFFUkZhfQLH8fes/2fVbdiRjYeX4P8tGnc2NB/fw3C77klizMYIGNHDAK8pKbfov5Q1zeg5/QB7weOvv9xpizUAjQsW5+jwQ1tkdxC0iU= + - secure: kllkCVsDmsu5Z4ZBMFN98VS5LNoyy0z8mpkreV1sAfPH8mmy/q1ZJAGkXLoXL5vFGOTBumMsDs6a7S74d1dXyBkQGYiegs2G5wqIvWb/hsq4dTudEMPqwWnVPCFs/YmtxtzjX788sS7ivkZsHA2pXh1w6glEZykXHJZgWWGSL6XFofR5Gmvpwc8SfQBP/iBi1xb0EA9YWUqjfW43FAuRMp3QDKQU2gcRoAr1EuSIvmMmarnK9CEMJunK/RY94OW1AwtUcJbUVLnK/PvHBIazY18BTez/o45gATH37eMk0G3JKxMP8yfk+wipAAVyZbloWeTdBW8ik1r/B9NWSI4ZXi/PysXMZzSkrede3Y0TE0TqW3mVkdtdcq4yFXD/B21dMmeNoBLxQGi290p/KNfnJLIcYB04CU+Ll9RoXWnVJH6RhxCK6JGEuiYqJKm/UZkCW5dXMhtFmjK3XH3iBRC9kw0jYI7fcTgstLzYzKzgAXBP7LSiiwxDUFsvq480US+lECGdDIJYL646H1kHogi2Cr3BBgXHvMoBbUFyLVXTHidzi4Vk6PFaT8fMXcJIO/j46WUcP5nyEh+7qiJmgqtZx6AbVIuank5vnDSKg9X5fsiww4cT6Xcf+E9rxcpiLhcfBjsbkUvKiVVcCNQHOIZxYLwEwelghz+VlWrJCFVPwT0= + - secure: g0Gm0G5oj892VmJeX8s2MMNttIR1GW9DX8+DYR2qXs5C8pjT8A30dYxCFiv4a1QHIrPEAhIGIosgtWpHXt8JwCDNLbPaHs6Jp9EkCB6doqLqQ9eOrvdxM67fyp8fJVcd4IH5JenG6FoaE7ofnu4QKoLo5w96Cd2UthwxrSCSQXWniutX8/N7XUt2cE74ISqQbJyslPWJ2UvKYL16HHtB8EzRmxD+EZgSYU/vtlds39Knrk85ogK5h6PKDmne8qoIH1nTPs8vzAu5uzeG6zai5m1qYETYbkiyJPTDs6sdy+DkYLLDaE5nLkNyfC/sHQvbPQ/H/CAW1OTZvSPsjxDN4hksovLaVuiH1DTHTM2+4fZsEab6fhx1itPOVijskoBqMzmksvah0nogIaw/qKmCtXGNBk7I30xeSju0W6c55miYUEEczF3eUn9oxHUSUDhQNs8UuosW/MJph7WTS2ESvmIjU7R6QuQjKmxTK7kZTaLAPpSwj/f77F7RgLUu3v6tO3Db1+i1OxdHvbaIVDiCNPB2J1LNmN4LFQstzJepYRadEUxYiExQzNulmC/FpkyPGwajec06z1r5NvTcWSOgxcTU/5OY6ggRCAsFoZTH4VzAst5nOBLRRGT+WAXpewGLhRlGLhev45eGgL2ctNVZY/6MA0ZBwnRPreczO7f3Nno= + - secure: iJ9fPWg8j+q19cUNqvwAbULZTnmK25wFuF5uBCxaSDuM1qMtbiq3M2l1Gvl0xZWdnq12PLMsUZtfLYNO1r4SRaarMPtnq4pK68DnuH49vkjpwGSy5bLIrcPQBZvzWKqzyG1R8+eOpg43BmXdau8Wo7pOSDw/8Bra7rtvA8Kjua/7MXaiZP08L71DtHaHFOoYlvgGHmuscdvtV3v4rNPntGR54ClzyNgnrRz6DP/NwEUF2a6EF2uhhbbe5XQvCNDTW0sqgxirZ4pDFPgywuWQaPcqqSXcwF78wrA0nLSzmQ1PsW9g/rUkDPbXE3uZuwje8QntNsSa8doTLw6Fizj7ZD8DuyvD8z3ljrLvhym+I00r8k6BpD5vlVhasUEEix/3+7lW4cpbE+ofwVz0Ge96y3Ei/uXkSI4kEhwPVfsU9sCRX/Kupdx9fLCQzm7F/BhkfHUCd403MjpbAtoHN3ACZfzC2QQ5fm14mxtLyMSawsClqwO7vVmc7+5p4f13TTkiUtWllKghMw8zTqxAZLwTiPFRKG6Y7MkX9tQUDBb9auQm/+7+pBFEUHibwoPvsSRwYGqdlZV7OXIFYPgt+WgUt9ZYblzSfZ8YLcGPfVTlGbLHkvW2SgIGL1gvHyXC2xd7kd7yec3M7AutDCiYg2kSzANVuJqGQCZSNr/6/S3QQrY= + - secure: IM9VEzf9MrtQnzK3VSD+YxmZWycxTgB5YbINDwE0LsAwWUWucDq95+2ZZRjKLrip5uYvoMohC5X2lxKuLhW8uDN3M6MxondHcCjgBCh8hWP7JociXWMxSIWA5ctU0oCgobm6rKvbarDuO8Bx+NE4QDDAWgjy9oOywE7mVi1G2//LY+2zC+iwXlkLs7VRdo9wtKgIG+FvswiJHhvFbU6GYxOeh1xclLnjwoWf66zzzbIW9Z4bFY0Zun/3u1ySQJWfF03/6ZzlCi3JPvfXDQ35T6PbD/5BvMDPmiXyWGqKUpZfRNus8VkG1G8TWwvHsVct9M47sZqLDuyzWVcwx613i8uxDsEIp9pVy4IxVl2UxvDJH1CPnNdvEhS3NeD9XfkMKMJtShHu1iVzB+j668W5SXsiv/N+psa2SIs+wuDY7FSirRg2Uwk9mlsXS+lSgV9B8BEcEIPEngA40MZCrEzNUdI0iYxaaqB6GNWeHiszOWz94rMkXk+FF3Ic75O4RyCmfXJmR8wYQs+rH4j7JJGzwTHXpYpV7H5V0OtMVIh6EUEvu3Veu/lkT4rsop55OHmcBmpKv8gGllX1P+g7H0+g35wn6Hz+FaSlkPvAuuSudgHFznOtfCowYhDUVp0TxTkcraLhkiROD4Qidw8r/HWh5CzrkfM9LWEBvziWFM77X/M= + - secure: SfJARcGrIyoemZ1jUENyX272acqmDTqQjk0LBjuzWxR5gqWp0Bn5TJS6x7niD8qzVC/n2FwR7F9FG3ll0cmToBgb8vh9763aL+ciA8cfk8iIUdjjQHNkywdhiLLw1EiR4TuUGOB3pV03WxMXl5JJTopJHScHgvTHfyhHS4vnQdrU3j6PwFDvVLsECyFiTCflDUgZ6zHocbIT4fqVjoBxy+5R0t9vyb4eE3IE3Lqjnac5oYVXm4KCpGUYDdCL0W2OlwPYgaBtWGetoWj/v6Kg4cjUVbKJHGK1q2nKyrCGPRvoKTqw83Pkfw1zkSdC0kCozMKr/gYsYBpt5IFXHaON/OkjvgdLbVKrpAHoauQ5KkWcTg9ntuUNndtg5pMaitBmd/J2AaMIxEde8ZvgfDR3sZdv0umFzLXJeAu/tbLR6ozP/BwOFpj89Lw0jNyNxJjdkLHP/4M2W5Ka0W1s+F6xNTahTuXdV32laPGN8SSmBrjzXwFOLOQTCgwlbD+YbTqAXUs+DLq/zIT6ZTaEfOdas+HTsu6H+cQfsmv9JlMP7PoT/CbL2tYe8kA2tjmxsQwUlVTcE1nP5ni5kHX+yXuClTzLimY+BOqU9SLJBKv6umygFqt/9nXk0YU6+UqfeDFCVzrMN0vnzWkmB0SQ3ElDC6pbb6NE/2RgmIy1pAUMDzY= + - secure: NPgEmEHVxJrLtaQ35IJnmQSbEHMqzSJt1l0OHX+a6VzfBiAP9Z4Qv1Ita/SQZpA4roLK2YkGr6TALndXgiY1EXU3e7ebcPnCgaQ6mCSJpAKtXhSGmArvysEmmxKSJjMxc8aBlJAeeEQlngXb0/pqA//kMw5KC+pPo+8O3UUbBs0qY9Q4FX3/cU+chv04tk5cSVR2P9nhOqY7LYm+FXjwF4vfjGKF7mWEcQUzTS8E2dRsertl/4law5O9/0gEW/9MaqyylPxq+0pS38sXP25zYotgEL3L8t0fupi58loPioDndardxVl35SXaatPuOxEdHr3Q0Q3dKVjbrTEx8bP7NgLY4nh7r5hqcEtvlM0xCAsfiOmGe0Dd0BFqo7kxzK/Fl6oI4MoEfvAYR+1QyBy9zOAJmjoj1hu/NEDAtT9NPxXJKl4A/iJPIYIt3lrMw34ewTSLl16ERojx6CZfSqg58eyUndmmOPBB8b4rIX0fQj5/ShsoLbM/rmOmDpPXN0QGu2EHhtR2eCbpdan84VfPifi5LjPH42PjdUxpJ+rOHZJHLesJ5JwQLFLrnDf7Bg96iucyz1QMpNFoqN+q4YDARot2zdw+1ISjgPx3bhHu8ly8oT1UYNl5mMjv7zfNDvsmBycpQEBQWGvIioRfx6dM/EtZy2E/og5Ci0j9v19YI2E= diff --git a/sample/bucket_lifecycle.go b/sample/bucket_lifecycle.go index 80d68507..29e83caf 100644 --- a/sample/bucket_lifecycle.go +++ b/sample/bucket_lifecycle.go @@ -105,13 +105,6 @@ func BucketLifecycleSample() { HandleError(err) } - // for Debug only - //lc, err = client.GetBucketLifecycle(bucketName) - //if err != nil { - // HandleError(err) - //} - //fmt.Printf("Bucket Lifecycle:%v, %v\n", lc.Rules, *lc.Rules[0].Expiration) - // Case 6: Delete bucket's Lifecycle err = client.DeleteBucketLifecycle(bucketName) if err != nil { From 970924dd432a150a935c2032bb0a1e7aeb4534f7 Mon Sep 17 00:00:00 2001 From: alzhang Date: Mon, 15 Apr 2019 16:22:52 +0800 Subject: [PATCH 11/15] modify the sample main function, let the implementation of a single sample --- sample.go | 67 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/sample.go b/sample.go index 872ce442..fa01123f 100644 --- a/sample.go +++ b/sample.go @@ -3,12 +3,38 @@ package main import ( + "flag" "fmt" + "os" "github.com/aliyun/aliyun-oss-go-sdk/sample" ) +// sampleMap contains all samples +var sampleMap = map[string]interface{}{ + "CreateBucketSample": sample.CreateBucketSample, + "NewBucketSample": sample.NewBucketSample, + "ListBucketsSample": sample.ListBucketsSample, + "BucketACLSample": sample.BucketACLSample, + "BucketLifecycleSample": sample.BucketLifecycleSample, + "BucketRefererSample": sample.BucketRefererSample, + "BucketLoggingSample": sample.BucketLoggingSample, + "BucketCORSSample": sample.BucketCORSSample, + "ObjectACLSample": sample.ObjectACLSample, + "ObjectMetaSample": sample.ObjectMetaSample, + "ListObjectsSample": sample.ListObjectsSample, + "DeleteObjectSample": sample.DeleteObjectSample, + "AppendObjectSample": sample.AppendObjectSample, + "CopyObjectSample": sample.CopyObjectSample, + "PutObjectSample": sample.PutObjectSample, + "GetObjectSample": sample.GetObjectSample, + "CnameSample": sample.CnameSample, + "SignURLSample": sample.SignURLSample, + "ArchiveSample": sample.ArchiveSample, +} + func main() { +<<<<<<< HEAD sample.CreateBucketSample() sample.NewBucketSample() sample.ListBucketsSample() @@ -34,4 +60,45 @@ func main() { sample.ObjectTaggingSample() fmt.Println("All samples completed") +======= + var name string + flag.StringVar(&name, "name", "", "the sample name") + flag.Parse() + + if len(name) <= 0 { + fmt.Println("please enter your sample's name\n like '-name CreateBucketSample'") + os.Exit(-1) + } else { + if sampleMap[name] == nil { + fmt.Println("the " + name + "is not exist.") + os.Exit(-1) + } + sampleMap[name].(func())() + } + + // sample.CreateBucketSample() + // sample.NewBucketSample() + // sample.ListBucketsSample() + // sample.BucketACLSample() + // sample.BucketLifecycleSample() + // sample.BucketRefererSample() + // sample.BucketLoggingSample() + // sample.BucketCORSSample() + + // sample.ObjectACLSample() + // sample.ObjectMetaSample() + // sample.ListObjectsSample() + // sample.DeleteObjectSample() + // sample.AppendObjectSample() + // sample.CopyObjectSample() + // sample.PutObjectSample() + // sample.GetObjectSample() + + // sample.CnameSample() + // sample.SignURLSample() + + // sample.ArchiveSample() + + // fmt.Println("All samples completed") +>>>>>>> 86eaf1f... modify the sample main function, let the implementation of a single sample } From 2bd67ffbb140983da422e1d3dc38e4f8075bd953 Mon Sep 17 00:00:00 2001 From: alzhang Date: Mon, 15 Apr 2019 16:36:35 +0800 Subject: [PATCH 12/15] Modify the sample main of description --- sample.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sample.go b/sample.go index fa01123f..27b61ab7 100644 --- a/sample.go +++ b/sample.go @@ -62,11 +62,11 @@ func main() { fmt.Println("All samples completed") ======= var name string - flag.StringVar(&name, "name", "", "the sample name") + flag.StringVar(&name, "name", "", "Waiting for a sample of execution") flag.Parse() if len(name) <= 0 { - fmt.Println("please enter your sample's name\n like '-name CreateBucketSample'") + fmt.Println("please enter your sample's name. like '-name CreateBucketSample'") os.Exit(-1) } else { if sampleMap[name] == nil { From c90d275da4074a853bbb919524fc4d8111da5528 Mon Sep 17 00:00:00 2001 From: hangzws Date: Tue, 7 May 2019 16:36:08 +0800 Subject: [PATCH 13/15] fix conflict --- sample.go | 54 +----------------------------------------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/sample.go b/sample.go index 27b61ab7..a47daf79 100644 --- a/sample.go +++ b/sample.go @@ -31,36 +31,10 @@ var sampleMap = map[string]interface{}{ "CnameSample": sample.CnameSample, "SignURLSample": sample.SignURLSample, "ArchiveSample": sample.ArchiveSample, + "ObjectTaggingSample": sample.ObjectTaggingSample, } func main() { -<<<<<<< HEAD - sample.CreateBucketSample() - sample.NewBucketSample() - sample.ListBucketsSample() - sample.BucketACLSample() - sample.BucketLifecycleSample() - sample.BucketRefererSample() - sample.BucketLoggingSample() - sample.BucketCORSSample() - - sample.ObjectACLSample() - sample.ObjectMetaSample() - sample.ListObjectsSample() - sample.DeleteObjectSample() - sample.AppendObjectSample() - sample.CopyObjectSample() - sample.PutObjectSample() - sample.GetObjectSample() - - sample.CnameSample() - sample.SignURLSample() - - sample.ArchiveSample() - sample.ObjectTaggingSample() - - fmt.Println("All samples completed") -======= var name string flag.StringVar(&name, "name", "", "Waiting for a sample of execution") flag.Parse() @@ -75,30 +49,4 @@ func main() { } sampleMap[name].(func())() } - - // sample.CreateBucketSample() - // sample.NewBucketSample() - // sample.ListBucketsSample() - // sample.BucketACLSample() - // sample.BucketLifecycleSample() - // sample.BucketRefererSample() - // sample.BucketLoggingSample() - // sample.BucketCORSSample() - - // sample.ObjectACLSample() - // sample.ObjectMetaSample() - // sample.ListObjectsSample() - // sample.DeleteObjectSample() - // sample.AppendObjectSample() - // sample.CopyObjectSample() - // sample.PutObjectSample() - // sample.GetObjectSample() - - // sample.CnameSample() - // sample.SignURLSample() - - // sample.ArchiveSample() - - // fmt.Println("All samples completed") ->>>>>>> 86eaf1f... modify the sample main function, let the implementation of a single sample } From b233b4072b02115bb48ccb5293fd7edcce035f78 Mon Sep 17 00:00:00 2001 From: hangzws Date: Tue, 7 May 2019 16:45:11 +0800 Subject: [PATCH 14/15] fix syntax error of travis config --- .travis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 749fa1fb..c97e4d48 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,9 +14,9 @@ install: ; fi script: - if [ -n "$OSS_TEST_ACCESS_KEY_ID" ]; then - cd oss - travis_wait 30 go test -v -covermode=count -coverprofile=coverage.out -timeout=30m - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci + cd oss; + travis_wait 30 go test -v -covermode=count -coverprofile=coverage.out -timeout=30m; + $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci; fi env: global: From e02c7c7d5545f68ada02a20bba5a69fd35f7a496 Mon Sep 17 00:00:00 2001 From: "taowei.wtw" Date: Thu, 9 May 2019 15:52:08 +0800 Subject: [PATCH 15/15] add for versioning && bucket tagging && bucket encryption --- .travis.yml | 12 +- oss/bucket.go | 207 +++++- oss/bucket_test.go | 1351 +++++++++++++++++++++++++++++++++++++- oss/client.go | 261 ++++++++ oss/client_test.go | 352 +++++++++- oss/conn.go | 17 +- oss/const.go | 23 +- oss/download.go | 23 +- oss/download_test.go | 203 ++++++ oss/multicopy.go | 32 +- oss/multicopy_test.go | 92 +++ oss/multipart.go | 16 +- oss/multipart_test.go | 2 +- oss/option.go | 70 +- oss/option_test.go | 18 + oss/type.go | 192 +++++- oss/type_test.go | 12 +- oss/upload.go | 24 +- oss/upload_test.go | 61 ++ sample/copy_object.go | 4 +- sample/object_tagging.go | 10 +- 21 files changed, 2804 insertions(+), 178 deletions(-) diff --git a/.travis.yml b/.travis.yml index c97e4d48..e7d04a11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,11 +13,13 @@ install: - if [[ $TRAVIS_GO_VERSION = '1.7' || $TRAVIS_GO_VERSION > '1.7' ]]; then go get golang.org/x/time/rate ; fi script: -- if [ -n "$OSS_TEST_ACCESS_KEY_ID" ]; then - cd oss; - travis_wait 30 go test -v -covermode=count -coverprofile=coverage.out -timeout=30m; - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci; - fi +- if [[ ! -n "$OSS_TEST_ACCESS_KEY_ID" ]]; then exit 0 + ; fi + +- cd oss +- travis_wait 30 go test -v -covermode=count -coverprofile=coverage.out -timeout=30m +- "$HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci" + env: global: - secure: ZCL5egxJmZA+o1ujsaJe//AIh1ag/exSUJ2FzoKPF3O9c84hMc2k5EYO2rGzTNn1ML6M89Mo5hAvHQhyJEHjRuMtjc1QrfxAaA3mqm4scGXIXesPMqYGuvuPSh++6/fkAwaVBAhrk5GaDG1/FuxHE5zusGx3SvGegnCwO7n/2YCfXco6DWgVCdrz4p1EpPkAM3JIdHFUzsDWiimVuiNAvJmAT8+IeOPTT+WgusCJj4ORS3X3LddTjttBP+hRrp/pGSoNqPMzfysWybtaL2SJ8URtvsxW0Mo5BwocHAxAhPP+M2OscQbDzthSAezCLngYvrfBplfIyWlahlgzNz/FjXz5pQwWdYVNoibyxLLMOH685n75LNONN/xVO/GFmVPx7DMGapkN5NzIWS62D4v8QrRkwtms42OUkyEUHjDh8Evui3K2MNJVXA3TI9zOAR+C0krD7OEyS37qrppodhRxJSqFUlgXnk//wLldMC7vleDd7L2UQSWjqyBHqFOgsVaiLU2KRTY3zvv7ke+dqb5VF31mH6qAr8lJTR9un8M1att0VwCEKxoIRT4cKJCpEtZd8ovXOVt1uE695ThVXE9I5e00GXdTzqXOuv6zT4hv/dgmbz9JN9MYeCwmokEoIUmJKNYERa/bNVVefdnJt7h+dm+KpyPAS+XvPLzjbnWdYNA= diff --git a/oss/bucket.go b/oss/bucket.go index 5359f02d..477fbcd5 100644 --- a/oss/bucket.go +++ b/oss/bucket.go @@ -233,7 +233,17 @@ func (bucket Bucket) DoGetObject(request *GetObjectRequest, options []Option) (* // func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) { var out CopyObjectResult - options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey))) + + //first find version id + versionIdKey := "versionId" + versionId, _ := findOption(options, versionIdKey, nil) + if versionId == nil { + options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey))) + } else { + options = deleteOption(options, versionIdKey) + options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string))) + } + params := map[string]interface{}{} resp, err := bucket.do("PUT", destObjectKey, params, options, nil, nil) if err != nil { @@ -281,7 +291,17 @@ func (bucket Bucket) CopyObjectFrom(srcBucketName, srcObjectKey, destObjectKey s func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) { var out CopyObjectResult - options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey))) + + //first find version id + versionIdKey := "versionId" + versionId, _ := findOption(options, versionIdKey, nil) + if versionId == nil { + options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey))) + } else { + options = deleteOption(options, versionIdKey) + options = append(options, CopySourceVersion(bucket.BucketName, url.QueryEscape(srcObjectKey), versionId.(string))) + } + headers := make(map[string]string) err := handleOptions(headers, options) if err != nil { @@ -289,6 +309,14 @@ func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, op } params := map[string]interface{}{} resp, err := bucket.Client.Conn.Do("PUT", destBucketName, destObjectKey, params, headers, nil, 0, nil) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + if err != nil { return out, err } @@ -357,6 +385,14 @@ func (bucket Bucket) DoAppendObject(request *AppendObjectRequest, options []Opti handleOptions(headers, opts) resp, err := bucket.Client.Conn.Do("POST", bucket.BucketName, request.ObjectKey, params, headers, request.Reader, initCRC, listener) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + if err != nil { return nil, err } @@ -384,9 +420,9 @@ func (bucket Bucket) DoAppendObject(request *AppendObjectRequest, options []Opti // // error it's nil if no error, otherwise it's an error object. // -func (bucket Bucket) DeleteObject(objectKey string) error { - params := map[string]interface{}{} - resp, err := bucket.do("DELETE", objectKey, params, nil, nil, nil) +func (bucket Bucket) DeleteObject(objectKey string, options ...Option) error { + params, _ := getRawParams(options) + resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil) if err != nil { return err } @@ -409,6 +445,63 @@ func (bucket Bucket) DeleteObjects(objectKeys []string, options ...Option) (Dele for _, key := range objectKeys { dxml.Objects = append(dxml.Objects, DeleteObject{Key: key}) } + + isQuiet, _ := findOption(options, deleteObjectsQuiet, false) + dxml.Quiet = isQuiet.(bool) + + bs, err := xml.Marshal(dxml) + if err != nil { + return out, err + } + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + options = append(options, ContentType(contentType)) + sum := md5.Sum(bs) + b64 := base64.StdEncoding.EncodeToString(sum[:]) + options = append(options, ContentMD5(b64)) + + params := map[string]interface{}{} + params["delete"] = nil + params["encoding-type"] = "url" + + resp, err := bucket.do("POST", "", params, options, buffer, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + deletedResult := DeleteObjectVersionsResult{} + if !dxml.Quiet { + if err = xmlUnmarshal(resp.Body, &deletedResult); err == nil { + err = decodeDeleteObjectsResult(&deletedResult) + } + } + + // Keep compatibility:need convert to struct DeleteObjectsResult + out.XMLName = deletedResult.XMLName + for _, v := range deletedResult.DeletedObjectsDetail { + out.DeletedObjects = append(out.DeletedObjects, v.Key) + } + + return out, err +} + +// DeleteObjectVersions deletes multiple object versions. +// +// objectVersions the object keys and versions to delete. +// options the options for deleting objects. +// Supported option is DeleteObjectsQuiet which means it will not return error even deletion failed (not recommended). By default it's not used. +// +// DeleteObjectVersionsResult the result object. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) DeleteObjectVersions(objectVersions []DeleteObject, options ...Option) (DeleteObjectVersionsResult, error) { + out := DeleteObjectVersionsResult{} + dxml := deleteXML{} + dxml.Objects = objectVersions + isQuiet, _ := findOption(options, deleteObjectsQuiet, false) dxml.Quiet = isQuiet.(bool) @@ -509,6 +602,32 @@ func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) { return out, err } +// ListObjectVersions lists objects of all versions under the current bucket. +func (bucket Bucket) ListObjectVersions(options ...Option) (ListObjectVersionsResult, error) { + var out ListObjectVersionsResult + + options = append(options, EncodingType("url")) + params, err := getRawParams(options) + if err != nil { + return out, err + } + params["versions"] = nil + + resp, err := bucket.do("GET", "", params, options, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + if err != nil { + return out, err + } + + err = decodeListObjectVersionsResult(&out) + return out, err +} + // SetObjectMeta sets the metadata of the Object. // // objectKey object @@ -533,7 +652,7 @@ func (bucket Bucket) SetObjectMeta(objectKey string, options ...Option) error { // error it's nil if no error, otherwise it's an error object. // func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) (http.Header, error) { - params := map[string]interface{}{} + params, _ := getRawParams(options) resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil) if err != nil { return nil, err @@ -554,7 +673,7 @@ func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) // error it's nil if no error, otherwise it's an error object. // func (bucket Bucket) GetObjectMeta(objectKey string, options ...Option) (http.Header, error) { - params := map[string]interface{}{} + params, _ := getRawParams(options) params["objectMeta"] = nil //resp, err := bucket.do("GET", objectKey, "?objectMeta", "", nil, nil, nil) resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil) @@ -582,9 +701,9 @@ func (bucket Bucket) GetObjectMeta(objectKey string, options ...Option) (http.He // // error it's nil if no error, otherwise it's an error object. // -func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType) error { - options := []Option{ObjectACL(objectACL)} - params := map[string]interface{}{} +func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType, options ...Option) error { + options = append(options, ObjectACL(objectACL)) + params, _ := getRawParams(options) params["acl"] = nil resp, err := bucket.do("PUT", objectKey, params, options, nil, nil) if err != nil { @@ -601,11 +720,11 @@ func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType) error { // GetObjectACLResult the result object when error is nil. GetObjectACLResult.Acl is the object ACL. // error it's nil if no error, otherwise it's an error object. // -func (bucket Bucket) GetObjectACL(objectKey string) (GetObjectACLResult, error) { +func (bucket Bucket) GetObjectACL(objectKey string, options ...Option) (GetObjectACLResult, error) { var out GetObjectACLResult - params := map[string]interface{}{} + params, _ := getRawParams(options) params["acl"] = nil - resp, err := bucket.do("GET", objectKey, params, nil, nil, nil) + resp, err := bucket.do("GET", objectKey, params, options, nil, nil) if err != nil { return out, err } @@ -630,7 +749,7 @@ func (bucket Bucket) GetObjectACL(objectKey string) (GetObjectACLResult, error) // func (bucket Bucket) PutSymlink(symObjectKey string, targetObjectKey string, options ...Option) error { options = append(options, symlinkTarget(url.QueryEscape(targetObjectKey))) - params := map[string]interface{}{} + params, _ := getRawParams(options) params["symlink"] = nil resp, err := bucket.do("PUT", symObjectKey, params, options, nil, nil) if err != nil { @@ -648,10 +767,10 @@ func (bucket Bucket) PutSymlink(symObjectKey string, targetObjectKey string, opt // error it's nil if no error, otherwise it's an error object. // When error is nil, the target file key is in the X-Oss-Symlink-Target header of the returned object. // -func (bucket Bucket) GetSymlink(objectKey string) (http.Header, error) { - params := map[string]interface{}{} +func (bucket Bucket) GetSymlink(objectKey string, options ...Option) (http.Header, error) { + params, _ := getRawParams(options) params["symlink"] = nil - resp, err := bucket.do("GET", objectKey, params, nil, nil, nil) + resp, err := bucket.do("GET", objectKey, params, options, nil, nil) if err != nil { return nil, err } @@ -678,10 +797,10 @@ func (bucket Bucket) GetSymlink(objectKey string) (http.Header, error) { // // error it's nil if no error, otherwise it's an error object. // -func (bucket Bucket) RestoreObject(objectKey string) error { - params := map[string]interface{}{} +func (bucket Bucket) RestoreObject(objectKey string, options ...Option) error { + params, _ := getRawParams(options) params["restore"] = nil - resp, err := bucket.do("POST", objectKey, params, nil, nil, nil) + resp, err := bucket.do("POST", objectKey, params, options, nil, nil) if err != nil { return err } @@ -911,9 +1030,9 @@ func (bucket Bucket) DoGetObjectWithURL(signedURL string, options []Option) (*Ge // // error it's nil if no error, otherwise it's an error object. // -func (bucket Bucket) ProcessObject(objectKey string, process string) (ProcessObjectResult, error) { +func (bucket Bucket) ProcessObject(objectKey string, process string, options ...Option) (ProcessObjectResult, error) { var out ProcessObjectResult - params := map[string]interface{}{} + params, _ := getRawParams(options) params["x-oss-process"] = nil processData := fmt.Sprintf("%v=%v", "x-oss-process", process) data := strings.NewReader(processData) @@ -935,7 +1054,7 @@ func (bucket Bucket) ProcessObject(objectKey string, process string) (ProcessObj // // error nil if success, otherwise error // -func (bucket Bucket) PutObjectTagging(objectKey string, tagging ObjectTagging) error { +func (bucket Bucket) PutObjectTagging(objectKey string, tagging Tagging, options ...Option) error { bs, err := xml.Marshal(tagging) if err != nil { return err @@ -944,9 +1063,9 @@ func (bucket Bucket) PutObjectTagging(objectKey string, tagging ObjectTagging) e buffer := new(bytes.Buffer) buffer.Write(bs) - params := map[string]interface{}{} + params, _ := getRawParams(options) params["tagging"] = nil - resp, err := bucket.do("PUT", objectKey, params, nil, buffer, nil) + resp, err := bucket.do("PUT", objectKey, params, options, buffer, nil) if err != nil { return err } @@ -963,12 +1082,12 @@ func (bucket Bucket) PutObjectTagging(objectKey string, tagging ObjectTagging) e // Tagging // error nil if success, otherwise error // -func (bucket Bucket) GetObjectTagging(objectKey string) (ObjectTagging, error) { - var out ObjectTagging - params := map[string]interface{}{} +func (bucket Bucket) GetObjectTagging(objectKey string, options ...Option) (GetObjectTaggingResult, error) { + var out GetObjectTaggingResult + params, _ := getRawParams(options) params["tagging"] = nil - resp, err := bucket.do("GET", objectKey, params, nil, nil, nil) + resp, err := bucket.do("GET", objectKey, params, options, nil, nil) if err != nil { return out, err } @@ -985,15 +1104,15 @@ func (bucket Bucket) GetObjectTagging(objectKey string) (ObjectTagging, error) { // // error nil if success, otherwise error // -func (bucket Bucket) DeleteObjectTagging(objectKey string) error { - params := map[string]interface{}{} +func (bucket Bucket) DeleteObjectTagging(objectKey string, options ...Option) error { + params, _ := getRawParams(options) params["tagging"] = nil if objectKey == "" { return fmt.Errorf("invalid argument: object name is empty") } - resp, err := bucket.do("DELETE", objectKey, params, nil, nil, nil) + resp, err := bucket.do("DELETE", objectKey, params, options, nil, nil) if err != nil { return err } @@ -1010,8 +1129,18 @@ func (bucket Bucket) do(method, objectName string, params map[string]interface{} if err != nil { return nil, err } - return bucket.Client.Conn.Do(method, bucket.BucketName, objectName, + + resp, err := bucket.Client.Conn.Do(method, bucket.BucketName, objectName, params, headers, data, 0, listener) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + return resp, err } func (bucket Bucket) doURL(method HTTPMethod, signedURL string, params map[string]interface{}, options []Option, @@ -1021,7 +1150,17 @@ func (bucket Bucket) doURL(method HTTPMethod, signedURL string, params map[strin if err != nil { return nil, err } - return bucket.Client.Conn.DoURL(method, signedURL, headers, data, 0, listener) + + resp, err := bucket.Client.Conn.DoURL(method, signedURL, headers, data, 0, listener) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + return resp, err } func (bucket Bucket) getConfig() *Config { diff --git a/oss/bucket_test.go b/oss/bucket_test.go index b956d4d6..6fe0c484 100644 --- a/oss/bucket_test.go +++ b/oss/bucket_test.go @@ -126,13 +126,14 @@ func (s *OssBucketSuite) TearDownTest(c *C) { } // TestPutObject -func (s *OssBucketSuite) TestPutObject(c *C) { +func (s *OssBucketSuite) TestPutObjectOnly(c *C) { objectName := objectNamePrefix + randStr(8) objectValue := "大江东去,浪淘尽,千古风流人物。 故垒西边,人道是、三国周郎赤壁。 乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。" + "遥想公谨当年,小乔初嫁了,雄姿英发。 羽扇纶巾,谈笑间、樯橹灰飞烟灭。故国神游,多情应笑我,早生华发,人生如梦,一尊还酹江月。" // Put string - err := s.bucket.PutObject(objectName, strings.NewReader(objectValue)) + var respHeader http.Header + err := s.bucket.PutObject(objectName, strings.NewReader(objectValue), GetResponseHeader(&respHeader)) c.Assert(err, IsNil) // Check @@ -2384,7 +2385,6 @@ func putObjectRoutin(bucket *Bucket, object string, textBuffer *string, notifyCh } func (s *OssBucketSuite) TestPutManyObjectLimitSpeed(c *C) { - // create client and bucket client, err := New(endpoint, accessID, accessKey) c.Assert(err, IsNil) @@ -2790,10 +2790,10 @@ func (s *OssBucketSuite) TestPutObjectTagging(c *C) { Key: randStr(8), Value: randStr(16), } - tagging := ObjectTagging{ + tagging := Tagging{ Tags: []Tag{tag1, tag2}, } - err := s.bucket.PutObject(objectName, strings.NewReader(randStr(1024)), Tagging(tagging)) + err := s.bucket.PutObject(objectName, strings.NewReader(randStr(1024)), SetTagging(tagging)) c.Assert(err, IsNil) headers, err := s.bucket.GetObjectDetailedMeta(objectName) @@ -2810,10 +2810,10 @@ func (s *OssBucketSuite) TestPutObjectTagging(c *C) { err = s.bucket.PutObjectTagging(objectName, tagging) c.Assert(err, IsNil) - tagging, err = s.bucket.GetObjectTagging(objectName) - c.Assert(len(tagging.Tags), Equals, 1) - c.Assert(tagging.Tags[0].Key, Equals, tag.Key) - c.Assert(tagging.Tags[0].Value, Equals, tag.Value) + taggingResult, err := s.bucket.GetObjectTagging(objectName) + c.Assert(len(taggingResult.Tags), Equals, 1) + c.Assert(taggingResult.Tags[0].Key, Equals, tag.Key) + c.Assert(taggingResult.Tags[0].Value, Equals, tag.Value) //put tagging, the length of the key exceeds 128 tag = Tag{ @@ -2890,13 +2890,15 @@ func (s *OssBucketSuite) TestGetObjectTagging(c *C) { Key: randStr(8), Value: randStr(16), } - tagging := ObjectTagging{ + + taggingInfo := Tagging{ Tags: []Tag{tag1, tag2}, } - err := s.bucket.PutObject(objectName, strings.NewReader(randStr(1024)), Tagging(tagging)) + + err := s.bucket.PutObject(objectName, strings.NewReader(randStr(1024)), SetTagging(taggingInfo)) c.Assert(err, IsNil) - tagging, err = s.bucket.GetObjectTagging(objectName) + tagging, err := s.bucket.GetObjectTagging(objectName) c.Assert(len(tagging.Tags), Equals, 2) if tagging.Tags[0].Key == tag1.Key { c.Assert(tagging.Tags[0].Value, Equals, tag1.Value) @@ -2927,7 +2929,7 @@ func (s *OssBucketSuite) TestGetObjectTagging(c *C) { // copy object, with tagging option destObjectName := objectName + "-dest" tagging.Tags = []Tag{tag1, tag2} - _, err = s.bucket.CopyObject(objectName, destObjectName, Tagging(tagging)) + _, err = s.bucket.CopyObject(objectName, destObjectName, SetTagging(taggingInfo)) c.Assert(err, IsNil) tagging, err = s.bucket.GetObjectTagging(objectName) c.Assert(err, IsNil) @@ -2935,7 +2937,7 @@ func (s *OssBucketSuite) TestGetObjectTagging(c *C) { // copy object, with tagging option, the value of tagging directive is "REPLACE" tagging.Tags = []Tag{tag1, tag2} - _, err = s.bucket.CopyObject(objectName, destObjectName, Tagging(tagging), TaggingDirective(TaggingReplace)) + _, err = s.bucket.CopyObject(objectName, destObjectName, SetTagging(taggingInfo), TaggingDirective(TaggingReplace)) c.Assert(err, IsNil) tagging, err = s.bucket.GetObjectTagging(destObjectName) c.Assert(err, IsNil) @@ -2966,16 +2968,16 @@ func (s *OssBucketSuite) TestDeleteObjectTagging(c *C) { Key: randStr(8), Value: randStr(16), } - tagging := ObjectTagging{ + tagging := Tagging{ Tags: []Tag{tag}, } - err = s.bucket.PutObject(objectName, strings.NewReader(randStr(1024)), Tagging(tagging)) + err = s.bucket.PutObject(objectName, strings.NewReader(randStr(1024)), SetTagging(tagging)) c.Assert(err, IsNil) err = s.bucket.DeleteObjectTagging(objectName) c.Assert(err, IsNil) - tagging, err = s.bucket.GetObjectTagging(objectName) + taggingResult, err := s.bucket.GetObjectTagging(objectName) c.Assert(err, IsNil) - c.Assert(len(tagging.Tags), Equals, 0) + c.Assert(len(taggingResult.Tags), Equals, 0) //delete object tagging again err = s.bucket.DeleteObjectTagging(objectName) @@ -2983,3 +2985,1316 @@ func (s *OssBucketSuite) TestDeleteObjectTagging(c *C) { s.bucket.DeleteObject(objectName) } + +func (s *OssBucketSuite) TestVersioningBucketVerison(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + // Get default bucket info + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + + c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "") + c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "") + c.Assert(bucketResult.BucketInfo.Versioning, Equals, "") + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err = client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled)) + + // put bucket version:Suspended + versioningConfig.Status = string(VersionSuspended) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err = client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionSuspended)) + + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningPutAndGetObject(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled)) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + // get object v1 + body, err := bucket.GetObject(objectName, VersionId(versionIdV1)) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + body.Close() + c.Assert(str, Equals, contextV1) + + // get object v2 + body, err = bucket.GetObject(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + body.Close() + c.Assert(str, Equals, contextV2) + + // get object without version + body, err = bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + body.Close() + c.Assert(str, Equals, contextV2) + + err = bucket.DeleteObject(objectName, VersionId(versionIdV1)) + err = bucket.DeleteObject(objectName, VersionId(versionIdV2)) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningHeadObject(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled)) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + // head object v1 + headResultV1, err := bucket.GetObjectMeta(objectName, VersionId(versionIdV1)) + objLen, err := strconv.Atoi(headResultV1.Get("Content-Length")) + c.Assert(objLen, Equals, len(contextV1)) + + headResultV1, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV1)) + objLen, err = strconv.Atoi(headResultV1.Get("Content-Length")) + c.Assert(objLen, Equals, len(contextV1)) + + // head object v2 + headResultV2, err := bucket.GetObjectMeta(objectName, VersionId(versionIdV2)) + objLen, err = strconv.Atoi(headResultV2.Get("Content-Length")) + c.Assert(objLen, Equals, len(contextV2)) + + headResultV2, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV2)) + objLen, err = strconv.Atoi(headResultV2.Get("Content-Length")) + c.Assert(objLen, Equals, len(contextV2)) + + // head object without version + // head object v2 + headResult, err := bucket.GetObjectMeta(objectName) + objLen, err = strconv.Atoi(headResult.Get("Content-Length")) + c.Assert(objLen, Equals, len(contextV2)) + + headResult, err = bucket.GetObjectDetailedMeta(objectName) + objLen, err = strconv.Atoi(headResultV2.Get("Content-Length")) + c.Assert(objLen, Equals, len(contextV2)) + + err = bucket.DeleteObject(objectName, VersionId(versionIdV1)) + err = bucket.DeleteObject(objectName, VersionId(versionIdV2)) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningDeleteLatestVersionObject(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled)) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + // delete v2 object:permently delete + options := []Option{VersionId(versionIdV2), GetResponseHeader(&respHeader)} + err = bucket.DeleteObject(objectName, options...) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, versionIdV2) + + // get v2 object failure + body, err := bucket.GetObject(objectName, VersionId(versionIdV2)) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "NoSuchVersion") + + // get v1 object success + body, err = bucket.GetObject(objectName, VersionId(versionIdV1)) + c.Assert(err, IsNil) + str, err := readBody(body) + body.Close() + c.Assert(err, IsNil) + c.Assert(str, Equals, contextV1) + + // get default object success:v1 + body, err = bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + body.Close() + c.Assert(err, IsNil) + c.Assert(str, Equals, contextV1) + + err = bucket.DeleteObject(objectName, VersionId(versionIdV1)) + err = bucket.DeleteObject(objectName, VersionId(versionIdV2)) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningDeleteOldVersionObject(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled)) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + // delete v1 object:permently delete + options := []Option{VersionId(versionIdV1), GetResponseHeader(&respHeader)} + err = bucket.DeleteObject(objectName, options...) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, versionIdV1) + + // get v2 object success + body, err := bucket.GetObject(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + str, err := readBody(body) + body.Close() + c.Assert(err, IsNil) + c.Assert(str, Equals, contextV2) + + // get v1 object faliure + body, err = bucket.GetObject(objectName, VersionId(versionIdV1)) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "NoSuchVersion") + + // get default object success:v2 + body, err = bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + body.Close() + c.Assert(err, IsNil) + c.Assert(str, Equals, contextV2) + + err = bucket.DeleteObject(objectName, VersionId(versionIdV1)) + err = bucket.DeleteObject(objectName, VersionId(versionIdV2)) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningDeleteDefaultVersionObject(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled)) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + // delete default object:mark delete v2 + options := []Option{GetResponseHeader(&respHeader)} + err = bucket.DeleteObject(objectName, options...) + c.Assert(err, IsNil) + + markVersionId := GetVersionId(respHeader) + c.Assert(len(markVersionId) > 0, Equals, true) + c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true") + + // get v2 object success + body, err := bucket.GetObject(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + str, err := readBody(body) + body.Close() + c.Assert(err, IsNil) + c.Assert(str, Equals, contextV2) + + // get v1 object success + body, err = bucket.GetObject(objectName, VersionId(versionIdV1)) + c.Assert(err, IsNil) + str, err = readBody(body) + body.Close() + c.Assert(err, IsNil) + c.Assert(str, Equals, contextV1) + + // get default object failure:marker v2 + body, err = bucket.GetObject(objectName, GetResponseHeader(&respHeader)) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "NoSuchKey") + c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true") + + // delete mark v2 + options = []Option{VersionId(markVersionId), GetResponseHeader(&respHeader)} + err = bucket.DeleteObject(objectName, options...) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, markVersionId) + + // get default object success:v2 + body, err = bucket.GetObject(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + str, err = readBody(body) + body.Close() + c.Assert(err, IsNil) + c.Assert(str, Equals, contextV2) + + err = bucket.DeleteObject(objectName, VersionId(versionIdV1)) + err = bucket.DeleteObject(objectName, VersionId(versionIdV2)) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningListObjectVersions(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled)) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + // delete default object:mark delete v2 + options := []Option{GetResponseHeader(&respHeader)} + err = bucket.DeleteObject(objectName, options...) + c.Assert(err, IsNil) + + markVersionId := GetVersionId(respHeader) + c.Assert(len(markVersionId) > 0, Equals, true) + c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true") + + // delete default object again:mark delete v2 + err = bucket.DeleteObject(objectName, options...) + c.Assert(err, IsNil) + markVersionIdAgain := GetVersionId(respHeader) + c.Assert(len(markVersionIdAgain) > 0, Equals, true) + c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true") + c.Assert(markVersionId != markVersionIdAgain, Equals, true) + + // list bucket versions + listResult, err := bucket.ListObjectVersions() + c.Assert(err, IsNil) + c.Assert(len(listResult.ObjectDeleteMarkers), Equals, 2) + c.Assert(len(listResult.ObjectVersions), Equals, 2) + mapMarkVersion := map[string]string{} + mapMarkVersion[listResult.ObjectDeleteMarkers[0].VersionId] = listResult.ObjectDeleteMarkers[0].VersionId + mapMarkVersion[listResult.ObjectDeleteMarkers[1].VersionId] = listResult.ObjectDeleteMarkers[1].VersionId + + // check delete mark + _, ok := mapMarkVersion[markVersionId] + c.Assert(ok == true, Equals, true) + _, ok = mapMarkVersion[markVersionIdAgain] + c.Assert(ok == true, Equals, true) + + // check versionId + mapVersion := map[string]string{} + mapVersion[listResult.ObjectVersions[0].VersionId] = listResult.ObjectVersions[0].VersionId + mapVersion[listResult.ObjectVersions[1].VersionId] = listResult.ObjectVersions[1].VersionId + _, ok = mapVersion[versionIdV1] + c.Assert(ok == true, Equals, true) + _, ok = mapVersion[versionIdV2] + c.Assert(ok == true, Equals, true) + + // delete deleteMark v2 + options = []Option{VersionId(markVersionId), GetResponseHeader(&respHeader)} + err = bucket.DeleteObject(objectName, options...) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, markVersionId) + + // delete deleteMark v2 again + options = []Option{VersionId(markVersionIdAgain), GetResponseHeader(&respHeader)} + err = bucket.DeleteObject(objectName, options...) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, markVersionIdAgain) + + // delete versionId + bucket.DeleteObject(objectName, VersionId(versionIdV1)) + bucket.DeleteObject(objectName, VersionId(versionIdV2)) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningBatchDeleteVersionObjects(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled)) + + // put object v1 + objectName1 := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + objectName2 := objectNamePrefix + randStr(8) + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + //batch delete objects + versionIds := []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1}, + DeleteObject{Key: objectName2, VersionId: versionIdV2}} + deleteResult, err := bucket.DeleteObjectVersions(versionIds) + c.Assert(err, IsNil) + c.Assert(len(deleteResult.DeletedObjectsDetail), Equals, 2) + + // check delete detail info:key + deleteMap := map[string]string{} + deleteMap[deleteResult.DeletedObjectsDetail[0].Key] = deleteResult.DeletedObjectsDetail[0].VersionId + deleteMap[deleteResult.DeletedObjectsDetail[1].Key] = deleteResult.DeletedObjectsDetail[1].VersionId + id1, ok := deleteMap[objectName1] + c.Assert(ok, Equals, true) + c.Assert(id1, Equals, versionIdV1) + + id2, ok := deleteMap[objectName2] + c.Assert(ok, Equals, true) + c.Assert(id2, Equals, versionIdV2) + + // list bucket versions + listResult, err := bucket.ListObjectVersions() + c.Assert(err, IsNil) + c.Assert(len(listResult.ObjectDeleteMarkers), Equals, 0) + c.Assert(len(listResult.ObjectVersions), Equals, 0) + + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningBatchDeleteDefaultVersionObjects(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + c.Assert(bucketResult.BucketInfo.Versioning, Equals, string(VersionEnabled)) + + // put object v1 + objectName1 := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + objectName2 := objectNamePrefix + randStr(8) + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + //batch delete objects + versionIds := []DeleteObject{DeleteObject{Key: objectName1, VersionId: ""}, + DeleteObject{Key: objectName2, VersionId: ""}} + deleteResult, err := bucket.DeleteObjectVersions(versionIds) + c.Assert(err, IsNil) + + // check delete detail info:key + deleteDetailMap := map[string]DeletedKeyInfo{} + deleteDetailMap[deleteResult.DeletedObjectsDetail[0].Key] = deleteResult.DeletedObjectsDetail[0] + deleteDetailMap[deleteResult.DeletedObjectsDetail[1].Key] = deleteResult.DeletedObjectsDetail[1] + keyInfo1, ok := deleteDetailMap[objectName1] + c.Assert(ok, Equals, true) + c.Assert(keyInfo1.Key, Equals, objectName1) + c.Assert(keyInfo1.VersionId, Equals, "") + c.Assert(keyInfo1.DeleteMarker, Equals, true) + c.Assert(keyInfo1.DeleteMarkerVersionId != versionIdV1, Equals, true) + + keyInfo2, ok := deleteDetailMap[objectName2] + c.Assert(ok, Equals, true) + c.Assert(keyInfo2.Key, Equals, objectName2) + c.Assert(keyInfo2.VersionId, Equals, "") + c.Assert(keyInfo2.DeleteMarker, Equals, true) + c.Assert(keyInfo2.DeleteMarkerVersionId != versionIdV2, Equals, true) + + // list bucket versions + listResult, err := bucket.ListObjectVersions() + c.Assert(err, IsNil) + c.Assert(len(listResult.ObjectDeleteMarkers), Equals, 2) + c.Assert(len(listResult.ObjectVersions), Equals, 2) + + // delete version object + versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: versionIdV1}, + DeleteObject{Key: objectName2, VersionId: versionIdV2}} + deleteResult, err = bucket.DeleteObjectVersions(versionIds) + c.Assert(err, IsNil) + + // delete deleteMark object + versionIds = []DeleteObject{DeleteObject{Key: objectName1, VersionId: keyInfo1.DeleteMarkerVersionId}, + DeleteObject{Key: objectName2, VersionId: keyInfo2.DeleteMarkerVersionId}} + deleteResult, err = bucket.DeleteObjectVersions(versionIds) + c.Assert(err, IsNil) + + forceDeleteBucket(client, bucketName, c) +} + +// bucket has no versioning flag +func (s *OssBucketSuite) TestVersioningBatchDeleteNormalObjects(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + // not put bucket versioning + + bucket, err := client.Bucket(bucketName) + + // put object v1 + objectName1 := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1), Equals, 0) + + // put object v2 + objectName2 := objectNamePrefix + randStr(8) + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2), Equals, 0) + + //batch delete objects + keys := []string{objectName1, objectName2} + deleteResult, err := bucket.DeleteObjects(keys) + c.Assert(len(deleteResult.DeletedObjects), Equals, 2) + + // check delete info + deleteMap := map[string]string{} + deleteMap[deleteResult.DeletedObjects[0]] = deleteResult.DeletedObjects[0] + deleteMap[deleteResult.DeletedObjects[1]] = deleteResult.DeletedObjects[1] + _, ok := deleteMap[objectName1] + c.Assert(ok, Equals, true) + _, ok = deleteMap[objectName2] + c.Assert(ok, Equals, true) + + forceDeleteBucket(client, bucketName, c) + c.Assert(err, IsNil) +} + +func (s *OssBucketSuite) TestVersioningSymlink(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // put object 1 + objectName1 := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName1, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object 2 + objectName2 := objectNamePrefix + randStr(8) + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName2, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + // put symlink for object 1 + linkName := objectNamePrefix + randStr(8) + err = bucket.PutSymlink(linkName, objectName1, GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + linkVersionIdV1 := GetVersionId(respHeader) + + // PutSymlink for object 2 + err = bucket.PutSymlink(linkName, objectName2, GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + linkVersionIdV2 := GetVersionId(respHeader) + + // check v1 and v2 + c.Assert(linkVersionIdV1 != linkVersionIdV2, Equals, true) + + // GetSymlink for object1 + getResult, err := bucket.GetSymlink(linkName, VersionId(linkVersionIdV1)) + c.Assert(err, IsNil) + c.Assert(getResult.Get("x-oss-symlink-target"), Equals, objectName1) + + // GetSymlink for object2 + getResult, err = bucket.GetSymlink(linkName, VersionId(linkVersionIdV2)) + c.Assert(err, IsNil) + c.Assert(getResult.Get("x-oss-symlink-target"), Equals, objectName2) + + bucket.DeleteObject(linkName) + bucket.DeleteObject(objectName1) + bucket.DeleteObject(objectName2) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningObjectAcl(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + // put Acl for v1 + err = bucket.SetObjectACL(objectName, ACLPublicRead, VersionId(versionIdV1)) + c.Assert(err, IsNil) + + // put Acl for v2 + err = bucket.SetObjectACL(objectName, ACLPublicReadWrite, VersionId(versionIdV2)) + c.Assert(err, IsNil) + + // GetAcl for v1 + getResult, err := bucket.GetObjectACL(objectName, VersionId(versionIdV1)) + c.Assert(err, IsNil) + c.Assert(getResult.ACL, Equals, string(ACLPublicRead)) + + // GetAcl for v2 + getResult, err = bucket.GetObjectACL(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + c.Assert(getResult.ACL, Equals, string(ACLPublicReadWrite)) + + // delete default version + err = bucket.DeleteObject(objectName, GetResponseHeader(&respHeader)) + c.Assert(len(GetVersionId(respHeader)) > 0, Equals, true) + c.Assert(respHeader.Get("x-oss-delete-marker"), Equals, "true") + + // GetAcl for v1 agagin + getResult, err = bucket.GetObjectACL(objectName, VersionId(versionIdV1)) + c.Assert(err, IsNil) + c.Assert(getResult.ACL, Equals, string(ACLPublicRead)) + + // GetAcl for v2 again + getResult, err = bucket.GetObjectACL(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + c.Assert(getResult.ACL, Equals, string(ACLPublicReadWrite)) + + // GetAcl for default failure + getResult, err = bucket.GetObjectACL(objectName) + c.Assert(err, NotNil) + + bucket.DeleteObject(objectName) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningAppendObject(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // append object + var nextPos int64 = 0 + var respHeader http.Header + objectName := objectNamePrefix + randStr(8) + nextPos, err = bucket.AppendObject(objectName, strings.NewReader("123"), nextPos, GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, NullVersion) + + nextPos, err = bucket.AppendObject(objectName, strings.NewReader("456"), nextPos, GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, NullVersion) + + // delete object + err = bucket.DeleteObject(objectName, GetResponseHeader(&respHeader)) + markVersionId := GetVersionId(respHeader) + + // get default object failure + _, err = bucket.GetObject(objectName) + c.Assert(err, NotNil) + + // get null version success + body, err := bucket.GetObject(objectName, VersionId(NullVersion)) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, "123456") + + // append object again:failure + nextPos, err = bucket.AppendObject(objectName, strings.NewReader("789"), nextPos, GetResponseHeader(&respHeader)) + c.Assert(err, NotNil) + + // delete deletemark + options := []Option{VersionId(markVersionId), GetResponseHeader(&respHeader)} + err = bucket.DeleteObject(objectName, options...) + c.Assert(markVersionId, Equals, GetVersionId(respHeader)) + + // append object again:success + nextPos, err = bucket.AppendObject(objectName, strings.NewReader("789"), nextPos, GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + c.Assert(int(nextPos), Equals, 9) + + bucket.DeleteObject(objectName) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningCopyObject(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // check v1 and v2 + c.Assert(versionIdV1 != versionIdV2, Equals, true) + + destObjectKey := objectNamePrefix + randStr(8) + + // copyobject default + _, err = bucket.CopyObject(objectName, destObjectKey, GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + srcVersionId := GetCopySrcVersionId(respHeader) + c.Assert(srcVersionId, Equals, versionIdV2) + + body, err := bucket.GetObject(destObjectKey) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, contextV2) + + // copyobject v1 + options := []Option{VersionId(versionIdV1), GetResponseHeader(&respHeader)} + _, err = bucket.CopyObject(objectName, destObjectKey, options...) + c.Assert(err, IsNil) + srcVersionId = GetCopySrcVersionId(respHeader) + c.Assert(srcVersionId, Equals, versionIdV1) + + body, err = bucket.GetObject(destObjectKey) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, contextV1) + + // delete object + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // default copyobject again,failuer + _, err = bucket.CopyObject(objectName, destObjectKey, GetResponseHeader(&respHeader)) + c.Assert(err, NotNil) + + bucket.DeleteObject(objectName) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningCompleteMultipartUpload(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + objectName := objectNamePrefix + randStr(8) + var fileName = "test-file-" + randStr(8) + content := randStr(500 * 1024) + createFile(fileName, content, c) + + chunks, err := SplitFileByPartNum(fileName, 3) + c.Assert(err, IsNil) + + options := []Option{ + Expires(futureDate), Meta("my", "myprop"), + } + + fd, err := os.Open(fileName) + c.Assert(err, IsNil) + defer fd.Close() + + imur, err := bucket.InitiateMultipartUpload(objectName, options...) + c.Assert(err, IsNil) + var parts []UploadPart + for _, chunk := range chunks { + fd.Seek(chunk.Offset, os.SEEK_SET) + part, err := bucket.UploadPart(imur, fd, chunk.Size, chunk.Number) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + var respHeader http.Header + _, err = bucket.CompleteMultipartUpload(imur, parts, GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + + //get versionId + versionIdV1 := GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + meta, err := bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop") + c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat)) + c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart") + + // put object agagin + err = bucket.PutObject(objectName, strings.NewReader(""), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 := GetVersionId(respHeader) + c.Assert(versionIdV1 == versionIdV2, Equals, false) + + // get meta v1 + meta, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV1)) + c.Assert(err, IsNil) + c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(len(content))) + + // get meta v2 + meta, err = bucket.GetObjectDetailedMeta(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(0)) + + os.Remove(fileName) + bucket.DeleteObject(objectName) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningUploadPartCopy(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // upload mutlipart object with v1 + multiName := objectNamePrefix + randStr(8) + var parts []UploadPart + imur, err := bucket.InitiateMultipartUpload(multiName) + c.Assert(err, IsNil) + + part, err := bucket.UploadPartCopy(imur, bucketName, objectName, 0, int64(len(contextV1)), 1, VersionId(versionIdV1)) + parts = []UploadPart{part} + c.Assert(err, IsNil) + + _, err = bucket.CompleteMultipartUpload(imur, parts, GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + + //get versionId + partVersionIdV1 := GetVersionId(respHeader) + c.Assert(len(partVersionIdV1) > 0, Equals, true) + + // get meta v1 + meta, err := bucket.GetObjectDetailedMeta(multiName, VersionId(partVersionIdV1)) + c.Assert(err, IsNil) + c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(len(contextV1))) + + // upload mutlipart object with v2 + imur, err = bucket.InitiateMultipartUpload(multiName) + part, err = bucket.UploadPartCopy(imur, bucketName, objectName, 0, int64(len(contextV2)), 1, VersionId(versionIdV2)) + parts = []UploadPart{part} + + _, err = bucket.CompleteMultipartUpload(imur, parts, GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + + //get versionId + partVersionIdV2 := GetVersionId(respHeader) + c.Assert(len(partVersionIdV2) > 0, Equals, true) + + // get meta v2 + meta, err = bucket.GetObjectDetailedMeta(multiName, VersionId(partVersionIdV2)) + c.Assert(err, IsNil) + c.Assert(meta.Get("content-length"), Equals, strconv.Itoa(len(contextV2))) + + bucket.DeleteObject(objectName) + bucket.DeleteObject(multiName) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningRestoreObject(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName, StorageClass(StorageArchive)) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // RestoreObject v1 + options := []Option{GetResponseHeader(&respHeader), VersionId(versionIdV1)} + err = bucket.RestoreObject(objectName, options...) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, versionIdV1) + + // RestoreObject v2 + options = []Option{GetResponseHeader(&respHeader), VersionId(versionIdV2)} + err = bucket.RestoreObject(objectName, options...) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, versionIdV2) + + bucket.DeleteObject(objectName) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssBucketSuite) TestVersioningObjectTagging(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName, StorageClass(StorageArchive)) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // put object v1 + objectName := objectNamePrefix + randStr(8) + contextV1 := randStr(100) + versionIdV1 := "" + + var respHeader http.Header + err = bucket.PutObject(objectName, strings.NewReader(contextV1), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV1 = GetVersionId(respHeader) + c.Assert(len(versionIdV1) > 0, Equals, true) + + // put object v2 + contextV2 := randStr(200) + versionIdV2 := "" + err = bucket.PutObject(objectName, strings.NewReader(contextV2), GetResponseHeader(&respHeader)) + c.Assert(err, IsNil) + versionIdV2 = GetVersionId(respHeader) + c.Assert(len(versionIdV2) > 0, Equals, true) + + // ObjectTagging v1 + var tagging1 Tagging + tagging1.Tags = []Tag{Tag{Key: "testkey1", Value: "testvalue1"}} + err = bucket.PutObjectTagging(objectName, tagging1, VersionId(versionIdV1)) + c.Assert(err, IsNil) + getResult, err := bucket.GetObjectTagging(objectName, VersionId(versionIdV1)) + c.Assert(err, IsNil) + c.Assert(getResult.Tags[0].Key, Equals, tagging1.Tags[0].Key) + c.Assert(getResult.Tags[0].Value, Equals, tagging1.Tags[0].Value) + + // ObjectTagging v2 + var tagging2 Tagging + tagging2.Tags = []Tag{Tag{Key: "testkey2", Value: "testvalue2"}} + err = bucket.PutObjectTagging(objectName, tagging2, VersionId(versionIdV2)) + c.Assert(err, IsNil) + getResult, err = bucket.GetObjectTagging(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + c.Assert(getResult.Tags[0].Key, Equals, tagging2.Tags[0].Key) + c.Assert(getResult.Tags[0].Value, Equals, tagging2.Tags[0].Value) + + // delete ObjectTagging v2 + err = bucket.DeleteObjectTagging(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + + getResult, err = bucket.GetObjectTagging(objectName, VersionId(versionIdV2)) + c.Assert(err, IsNil) + c.Assert(len(getResult.Tags), Equals, 0) + + bucket.DeleteObject(objectName) + forceDeleteBucket(client, bucketName, c) +} diff --git a/oss/client.go b/oss/client.go index 8a0e7ec4..ea0c8094 100644 --- a/oss/client.go +++ b/oss/client.go @@ -113,6 +113,14 @@ func (client Client) CreateBucket(bucketName string, options ...Option) error { params := map[string]interface{}{} resp, err := client.do("PUT", bucketName, params, headers, buffer) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + if err != nil { return err } @@ -140,6 +148,14 @@ func (client Client) ListBuckets(options ...Option) (ListBucketsResult, error) { } resp, err := client.do("GET", "", params, nil, nil) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + if err != nil { return out, err } @@ -651,10 +667,255 @@ func (client Client) GetBucketInfo(bucketName string) (GetBucketInfoResult, erro } defer resp.Body.Close() + err = xmlUnmarshal(resp.Body, &out) + + // convert None to "" + if err == nil { + if out.BucketInfo.SseRule.KMSMasterKeyID == "None" { + out.BucketInfo.SseRule.KMSMasterKeyID = "" + } + + if out.BucketInfo.SseRule.SSEAlgorithm == "None" { + out.BucketInfo.SseRule.SSEAlgorithm = "" + } + } + return out, err +} + +// SetBucketVersioning set bucket versioning:Enabled、Suspended +// bucketName the bucket name. +// error it's nil if no error, otherwise it's an error object. +func (client Client) SetBucketVersioning(bucketName string, versioningConfig VersioningConfig, options ...Option) error { + var err error + var bs []byte + bs, err = xml.Marshal(versioningConfig) + + if err != nil { + return err + } + + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + headers := map[string]string{} + headers[HTTPHeaderContentType] = contentType + + params := map[string]interface{}{} + params["versioning"] = nil + resp, err := client.do("PUT", bucketName, params, headers, buffer) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// GetBucketVersioning get bucket versioning status:Enabled、Suspended +// bucketName the bucket name. +// error it's nil if no error, otherwise it's an error object. +func (client Client) GetBucketVersioning(bucketName string, options ...Option) (GetBucketVersioningResult, error) { + var out GetBucketVersioningResult + params := map[string]interface{}{} + params["versioning"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + if err != nil { + return out, err + } + defer resp.Body.Close() + err = xmlUnmarshal(resp.Body, &out) return out, err } +// SetBucketEncryption set bucket encryption config +// bucketName the bucket name. +// error it's nil if no error, otherwise it's an error object. +func (client Client) SetBucketEncryption(bucketName string, encryptionRule ServerEncryptionRule, options ...Option) error { + var err error + var bs []byte + bs, err = xml.Marshal(encryptionRule) + + if err != nil { + return err + } + + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + headers := map[string]string{} + headers[HTTPHeaderContentType] = contentType + + params := map[string]interface{}{} + params["encryption"] = nil + resp, err := client.do("PUT", bucketName, params, headers, buffer) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// GetBucketEncryption get bucket encryption +// bucketName the bucket name. +// error it's nil if no error, otherwise it's an error object. +func (client Client) GetBucketEncryption(bucketName string, options ...Option) (GetBucketEncryptionResult, error) { + var out GetBucketEncryptionResult + params := map[string]interface{}{} + params["encryption"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// DeleteBucketEncryption delete bucket encryption config +// bucketName the bucket name. +// error it's nil if no error, otherwise it's an error bucket +func (client Client) DeleteBucketEncryption(bucketName string, options ...Option) error { + params := map[string]interface{}{} + params["encryption"] = nil + resp, err := client.do("DELETE", bucketName, params, nil, nil) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + +// +// SetBucketTagging add tagging to bucket +// bucketName name of bucket +// tagging tagging to be added +// error nil if success, otherwise error +func (client Client) SetBucketTagging(bucketName string, tagging Tagging, options ...Option) error { + var err error + var bs []byte + bs, err = xml.Marshal(tagging) + + if err != nil { + return err + } + + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + headers := map[string]string{} + headers[HTTPHeaderContentType] = contentType + + params := map[string]interface{}{} + params["tagging"] = nil + resp, err := client.do("PUT", bucketName, params, headers, buffer) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// GetBucketTagging get tagging of the bucket +// bucketName name of bucket +// error nil if success, otherwise error +func (client Client) GetBucketTagging(bucketName string, options ...Option) (GetBucketTaggingResult, error) { + var out GetBucketTaggingResult + params := map[string]interface{}{} + params["tagging"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// +// DeleteBucketTagging delete bucket tagging +// bucketName name of bucket +// error nil if success, otherwise error +// +func (client Client) DeleteBucketTagging(bucketName string, options ...Option) error { + params := map[string]interface{}{} + params["tagging"] = nil + resp, err := client.do("DELETE", bucketName, params, nil, nil) + + // get response header + respHeader, _ := findOption(options, responseHeader, nil) + if respHeader != nil { + pRespHeader := respHeader.(*http.Header) + *pRespHeader = resp.Headers + } + + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + // LimitUploadSpeed set upload bandwidth limit speed,default is 0,unlimited // upSpeed KB/s, 0 is unlimited,default is 0 // error it's nil if success, otherwise failure diff --git a/oss/client_test.go b/oss/client_test.go index 89beebda..c066ddda 100644 --- a/oss/client_test.go +++ b/oss/client_test.go @@ -85,37 +85,7 @@ func randLowStr(n int) string { return strings.ToLower(randStr(n)) } -// SetUpSuite runs once when the suite starts running -func (s *OssClientSuite) SetUpSuite(c *C) { - client, err := New(endpoint, accessID, accessKey) - c.Assert(err, IsNil) - - lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000)) - c.Assert(err, IsNil) - - for _, bucket := range lbr.Buckets { - s.deleteBucket(client, bucket.Name, c) - } - - testLogger.Println("test client started") -} - -// TearDownSuite runs before each test or benchmark starts running -func (s *OssClientSuite) TearDownSuite(c *C) { - client, err := New(endpoint, accessID, accessKey) - c.Assert(err, IsNil) - - lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000)) - c.Assert(err, IsNil) - - for _, bucket := range lbr.Buckets { - s.deleteBucket(client, bucket.Name, c) - } - - testLogger.Println("test client completed") -} - -func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) { +func forceDeleteBucket(client *Client, bucketName string, c *C) { bucket, err := client.Bucket(bucketName) c.Assert(err, IsNil) @@ -134,8 +104,37 @@ func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) { } } - // Delete Part + // Delete Object Versions and DeleteMarks keyMarker := KeyMarker("") + versionIdMarker := VersionIdMarker("") + options := []Option{keyMarker, versionIdMarker} + for { + lor, err := bucket.ListObjectVersions(options...) + if err != nil { + break + } + + for _, object := range lor.ObjectDeleteMarkers { + err = bucket.DeleteObject(object.Key, VersionId(object.VersionId)) + c.Assert(err, IsNil) + } + + for _, object := range lor.ObjectVersions { + err = bucket.DeleteObject(object.Key, VersionId(object.VersionId)) + c.Assert(err, IsNil) + } + + keyMarker = KeyMarker(lor.NextKeyMarker) + versionIdMarker := VersionIdMarker(lor.NextVersionIdMarker) + options = []Option{keyMarker, versionIdMarker} + + if !lor.IsTruncated { + break + } + } + + // Delete Part + keyMarker = KeyMarker("") uploadIDMarker := UploadIDMarker("") for { lmur, err := bucket.ListMultipartUploads(keyMarker, uploadIDMarker) @@ -158,6 +157,40 @@ func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) { c.Assert(err, IsNil) } +// SetUpSuite runs once when the suite starts running +func (s *OssClientSuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000)) + c.Assert(err, IsNil) + + for _, bucket := range lbr.Buckets { + s.deleteBucket(client, bucket.Name, c) + } + + testLogger.Println("test client started") +} + +// TearDownSuite runs before each test or benchmark starts running +func (s *OssClientSuite) TearDownSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000)) + c.Assert(err, IsNil) + + for _, bucket := range lbr.Buckets { + s.deleteBucket(client, bucket.Name, c) + } + + testLogger.Println("test client completed") +} + +func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) { + forceDeleteBucket(client, bucketName, c) +} + // SetUpTest runs after each test or benchmark runs func (s *OssClientSuite) SetUpTest(c *C) { } @@ -1839,3 +1872,258 @@ func (s *OssClientSuite) TestSetLimitUploadSpeed(c *C) { c.Assert(err, NotNil) } } + +func (s *OssClientSuite) TestBucketEncyptionError(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + // SetBucketEncryption:AES256 ,"123" + encryptionRule := ServerEncryptionRule{} + encryptionRule.SSEDefault.SSEAlgorithm = string(AESAlgorithm) + encryptionRule.SSEDefault.KMSMasterKeyID = "123" + + var responseHeader http.Header + err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader)) + c.Assert(err, NotNil) + requestId := GetRequestId(responseHeader) + c.Assert(len(requestId) > 0, Equals, true) + + // GetBucketEncryption + _, err = client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader)) + c.Assert(err, NotNil) + requestId = GetRequestId(responseHeader) + c.Assert(len(requestId) > 0, Equals, true) + + // Get default bucket info + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + + c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "") + c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "") + c.Assert(bucketResult.BucketInfo.Versioning, Equals, "") + + err = client.DeleteBucket(bucketName) + c.Assert(err, IsNil) +} + +func (s *OssClientSuite) TestBucketEncyptionPutAndGetAndDelete(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + // SetBucketEncryption:KMS ,"" + encryptionRule := ServerEncryptionRule{} + encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm) + + var responseHeader http.Header + err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader)) + c.Assert(err, IsNil) + requestId := GetRequestId(responseHeader) + c.Assert(len(requestId) > 0, Equals, true) + + // GetBucketEncryption + getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader)) + c.Assert(err, IsNil) + requestId = GetRequestId(responseHeader) + c.Assert(len(requestId) > 0, Equals, true) + + // check encryption value + c.Assert(encryptionRule.SSEDefault.SSEAlgorithm, Equals, getResult.SSEDefault.SSEAlgorithm) + c.Assert(encryptionRule.SSEDefault.KMSMasterKeyID, Equals, getResult.SSEDefault.KMSMasterKeyID) + + // delete bucket encyption + err = client.DeleteBucketEncryption(bucketName) + c.Assert(err, IsNil) + + // GetBucketEncryption failure + _, err = client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader)) + c.Assert(err, NotNil) + requestId = GetRequestId(responseHeader) + c.Assert(len(requestId) > 0, Equals, true) + + // Get default bucket info + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + + c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "") + c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "") + c.Assert(bucketResult.BucketInfo.Versioning, Equals, "") + + err = client.DeleteBucket(bucketName) + c.Assert(err, IsNil) +} + +func (s *OssClientSuite) TestBucketEncyptionPutObjectSuccess(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + // SetBucketEncryption:KMS ,"" + encryptionRule := ServerEncryptionRule{} + encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm) + + var responseHeader http.Header + err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader)) + c.Assert(err, IsNil) + requestId := GetRequestId(responseHeader) + c.Assert(len(requestId) > 0, Equals, true) + + // GetBucketEncryption + getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader)) + c.Assert(err, IsNil) + requestId = GetRequestId(responseHeader) + c.Assert(len(requestId) > 0, Equals, true) + + // check encryption value + c.Assert(encryptionRule.SSEDefault.SSEAlgorithm, Equals, getResult.SSEDefault.SSEAlgorithm) + c.Assert(encryptionRule.SSEDefault.KMSMasterKeyID, Equals, getResult.SSEDefault.KMSMasterKeyID) + + // Get default bucket info + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + + c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "KMS") + c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "") + c.Assert(bucketResult.BucketInfo.Versioning, Equals, "") + + // put and get object success + //bucket, err := client.Bucket(bucketName) + //c.Assert(err, IsNil) + + // put object success + //objectName := objectNamePrefix + randStr(8) + //context := randStr(100) + //err = bucket.PutObject(objectName, strings.NewReader(context)) + //c.Assert(err, IsNil) + + // get object success + //body, err := bucket.GetObject(objectName) + //c.Assert(err, IsNil) + //str, err := readBody(body) + //c.Assert(err, IsNil) + //body.Close() + //c.Assert(str, Equals, context) + + //bucket.DeleteObject(objectName) + err = client.DeleteBucket(bucketName) + c.Assert(err, IsNil) +} + +func (s *OssClientSuite) TestBucketEncyptionPutObjectError(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + // SetBucketEncryption:KMS ,"" + encryptionRule := ServerEncryptionRule{} + encryptionRule.SSEDefault.SSEAlgorithm = string(KMSAlgorithm) + encryptionRule.SSEDefault.KMSMasterKeyID = "123" + + var responseHeader http.Header + err = client.SetBucketEncryption(bucketName, encryptionRule, GetResponseHeader(&responseHeader)) + c.Assert(err, IsNil) + requestId := GetRequestId(responseHeader) + c.Assert(len(requestId) > 0, Equals, true) + + // GetBucketEncryption + getResult, err := client.GetBucketEncryption(bucketName, GetResponseHeader(&responseHeader)) + c.Assert(err, IsNil) + requestId = GetRequestId(responseHeader) + c.Assert(len(requestId) > 0, Equals, true) + + // check encryption value + c.Assert(encryptionRule.SSEDefault.SSEAlgorithm, Equals, getResult.SSEDefault.SSEAlgorithm) + c.Assert(encryptionRule.SSEDefault.KMSMasterKeyID, Equals, getResult.SSEDefault.KMSMasterKeyID) + + // Get default bucket info + bucketResult, err := client.GetBucketInfo(bucketName) + c.Assert(err, IsNil) + + c.Assert(bucketResult.BucketInfo.SseRule.SSEAlgorithm, Equals, "KMS") + c.Assert(bucketResult.BucketInfo.SseRule.KMSMasterKeyID, Equals, "123") + c.Assert(bucketResult.BucketInfo.Versioning, Equals, "") + + // put and get object failure + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + // put object failure + objectName := objectNamePrefix + randStr(8) + context := randStr(100) + err = bucket.PutObject(objectName, strings.NewReader(context)) + c.Assert(err, NotNil) + + err = client.DeleteBucket(bucketName) + c.Assert(err, IsNil) +} + +func (s *OssClientSuite) TestBucketTaggingOperation(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + // Bucket Tagging + var tagging Tagging + tagging.Tags = []Tag{Tag{Key: "testkey2", Value: "testvalue2"}} + err = client.SetBucketTagging(bucketName, tagging) + c.Assert(err, IsNil) + + getResult, err := client.GetBucketTagging(bucketName) + c.Assert(err, IsNil) + c.Assert(getResult.Tags[0].Key, Equals, tagging.Tags[0].Key) + c.Assert(getResult.Tags[0].Value, Equals, tagging.Tags[0].Value) + + // delete BucketTagging + err = client.DeleteBucketTagging(bucketName) + c.Assert(err, IsNil) + getResult, err = client.GetBucketTagging(bucketName) + c.Assert(err, IsNil) + c.Assert(len(getResult.Tags), Equals, 0) + + err = client.DeleteBucket(bucketName) + c.Assert(err, IsNil) +} + +func (s *OssClientSuite) TestListBucketsTagging(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName1 := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName1) + c.Assert(err, IsNil) + + bucketName2 := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName2) + c.Assert(err, IsNil) + + // Bucket Tagging + var tagging Tagging + tagging.Tags = []Tag{Tag{Key: "testkey", Value: "testvalue"}} + err = client.SetBucketTagging(bucketName1, tagging) + c.Assert(err, IsNil) + + // list bucket + listResult, err := client.ListBuckets(TagKey("testkey")) + c.Assert(err, IsNil) + c.Assert(len(listResult.Buckets), Equals, 1) + c.Assert(listResult.Buckets[0].Name, Equals, bucketName1) + + client.DeleteBucket(bucketName1) + client.DeleteBucket(bucketName2) +} diff --git a/oss/conn.go b/oss/conn.go index 6686a9ac..6ed7419f 100644 --- a/oss/conn.go +++ b/oss/conn.go @@ -27,7 +27,22 @@ type Conn struct { client *http.Client } -var signKeyList = []string{"acl", "uploads", "location", "cors", "logging", "website", "referer", "lifecycle", "delete", "append", "tagging", "objectMeta", "uploadId", "partNumber", "security-token", "position", "img", "style", "styleName", "replication", "replicationProgress", "replicationLocation", "cname", "bucketInfo", "comp", "qos", "live", "status", "vod", "startTime", "endTime", "symlink", "x-oss-process", "response-content-type", "response-content-language", "response-expires", "response-cache-control", "response-content-disposition", "response-content-encoding", "udf", "udfName", "udfImage", "udfId", "udfImageDesc", "udfApplication", "comp", "udfApplicationLog", "restore", "callback", "callback-var", "policy", "tagging"} +var signKeyList = []string{"acl", "uploads", "location", "cors", + "logging", "website", "referer", "lifecycle", + "delete", "append", "tagging", "objectMeta", + "uploadId", "partNumber", "security-token", + "position", "img", "style", "styleName", + "replication", "replicationProgress", + "replicationLocation", "cname", "bucketInfo", + "comp", "qos", "live", "status", "vod", + "startTime", "endTime", "symlink", + "x-oss-process", "response-content-type", + "response-content-language", "response-expires", + "response-cache-control", "response-content-disposition", + "response-content-encoding", "udf", "udfName", "udfImage", + "udfId", "udfImageDesc", "udfApplication", "comp", + "udfApplicationLog", "restore", "callback", "callback-var", + "policy", "encryption", "versions", "versioning", "versionId"} // init initializes Conn func (conn *Conn) init(config *Config, urlMaker *urlMaker, client *http.Client) error { diff --git a/oss/const.go b/oss/const.go index 0af39dee..abc8909b 100644 --- a/oss/const.go +++ b/oss/const.go @@ -19,6 +19,17 @@ const ( ACLDefault ACLType = "default" ) +// bucket versioning status +type VersioningStatus string + +const ( + // Versioning Status definition: Enabled + VersionEnabled VersioningStatus = "Enabled" + + // Versioning Status definition: Suspended + VersionSuspended VersioningStatus = "Suspended" +) + // MetadataDirectiveType specifying whether use the metadata of source object when copying object. type MetadataDirectiveType string @@ -41,6 +52,14 @@ const ( TaggingReplace TaggingDirectiveType = "REPLACE" ) +// AlgorithmType specifying the server side encryption algorithm name +type AlgorithmType string + +const ( + KMSAlgorithm AlgorithmType = "KMS" + AESAlgorithm AlgorithmType = "AES256" +) + // StorageClassType bucket storage type type StorageClassType string @@ -155,5 +174,7 @@ const ( CheckpointFileSuffix = ".cp" // Checkpoint file suffix - Version = "v1.9.6" // Go SDK version + NullVersion = "null" + + Version = "v1.9.7" // Go SDK version ) diff --git a/oss/download.go b/oss/download.go index f0f0857b..32cbe513 100644 --- a/oss/download.go +++ b/oss/download.go @@ -225,12 +225,6 @@ func (bucket Bucket) downloadFile(objectKey, filePath string, partSize int64, op tempFilePath := filePath + TempFileSuffix listener := getProgressListener(options) - payerOptions := []Option{} - payer := getPayer(options) - if payer != "" { - payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) - } - // If the file does not exist, create one. If exists, the download will overwrite it. fd, err := os.OpenFile(tempFilePath, os.O_WRONLY|os.O_CREATE, FilePermMode) if err != nil { @@ -238,7 +232,10 @@ func (bucket Bucket) downloadFile(objectKey, filePath string, partSize int64, op } fd.Close() - meta, err := bucket.GetObjectDetailedMeta(objectKey, payerOptions...) + // Get the object detailed meta for object whole size + // must delete header:range to get whole object size + skipOptions := deleteOption(options, HTTPHeaderRange) + meta, err := bucket.GetObjectDetailedMeta(objectKey, skipOptions...) if err != nil { return err } @@ -474,12 +471,6 @@ func (bucket Bucket) downloadFileWithCp(objectKey, filePath string, partSize int tempFilePath := filePath + TempFileSuffix listener := getProgressListener(options) - payerOptions := []Option{} - payer := getPayer(options) - if payer != "" { - payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) - } - // Load checkpoint data. dcp := downloadCheckpoint{} err := dcp.load(cpFilePath) @@ -487,8 +478,10 @@ func (bucket Bucket) downloadFileWithCp(objectKey, filePath string, partSize int os.Remove(cpFilePath) } - // Get the object detailed meta. - meta, err := bucket.GetObjectDetailedMeta(objectKey, payerOptions...) + // Get the object detailed meta for object whole size + // must delete header:range to get whole object size + skipOptions := deleteOption(options, HTTPHeaderRange) + meta, err := bucket.GetObjectDetailedMeta(objectKey, skipOptions...) if err != nil { return err } diff --git a/oss/download_test.go b/oss/download_test.go index b7c0b609..023120d8 100644 --- a/oss/download_test.go +++ b/oss/download_test.go @@ -3,7 +3,9 @@ package oss import ( "bytes" "fmt" + "net/http" "os" + "strings" "time" . "gopkg.in/check.v1" @@ -673,3 +675,204 @@ func compareFilesWithRange(fileL string, offsetL int64, fileR string, offsetR in return true, nil } + +func (s *OssDownloadSuite) TestVersioningDownloadWithoutCheckPoint(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // begin test + objectName := objectNamePrefix + randStr(8) + fileName := "test-file-" + randStr(8) + fileData := randStr(500 * 1024) + createFile(fileName, fileData, c) + + newFile := randStr(8) + ".jpg" + newFileGet := randStr(8) + "-.jpg" + + // Upload a file + var respHeader http.Header + options := []Option{Routines(3), GetResponseHeader(&respHeader)} + err = bucket.UploadFile(objectName, fileName, 100*1024, options...) + c.Assert(err, IsNil) + versionId := GetVersionId(respHeader) + c.Assert(len(versionId) > 0, Equals, true) + + fileSize, err := getFileSize(fileName) + c.Assert(err, IsNil) + + // overwrite emtpy object + err = bucket.PutObject(objectName, strings.NewReader("")) + c.Assert(err, IsNil) + + // Download with range, from 1024 to 4096 + os.Remove(newFile) + options = []Option{Routines(3), Range(1024, 4095), VersionId(versionId)} + err = bucket.DownloadFile(objectName, newFile, 100*1024, options...) + c.Assert(err, IsNil) + + // Check + eq, err := compareFilesWithRange(fileName, 1024, newFile, 0, 3072) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + options = []Option{Range(1024, 4095), VersionId(versionId)} + err = bucket.GetObjectToFile(objectName, newFileGet, options...) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download with range, from 1024 to 4096 + os.Remove(newFile) + options = []Option{Routines(3), NormalizedRange("1024-4095"), VersionId(versionId)} + err = bucket.DownloadFile(objectName, newFile, 1024, options...) + c.Assert(err, IsNil) + + // Check + eq, err = compareFilesWithRange(fileName, 1024, newFile, 0, 3072) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + options = []Option{NormalizedRange("1024-4095"), VersionId(versionId)} + err = bucket.GetObjectToFile(objectName, newFileGet, options...) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download with range, from 2048 to the end + os.Remove(newFile) + options = []Option{NormalizedRange("2048-"), VersionId(versionId)} + err = bucket.DownloadFile(objectName, newFile, 1024*1024, options...) + c.Assert(err, IsNil) + + // Check + eq, err = compareFilesWithRange(fileName, 2048, newFile, 0, fileSize-2048) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + options = []Option{NormalizedRange("2048-"), VersionId(versionId)} + err = bucket.GetObjectToFile(objectName, newFileGet, options...) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download with range, the last 4096 + os.Remove(newFile) + options = []Option{Routines(3), NormalizedRange("-4096"), VersionId(versionId)} + err = bucket.DownloadFile(objectName, newFile, 1024, options...) + c.Assert(err, IsNil) + + // Check + eq, err = compareFilesWithRange(fileName, fileSize-4096, newFile, 0, 4096) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + options = []Option{NormalizedRange("-4096"), VersionId(versionId)} + err = bucket.GetObjectToFile(objectName, newFileGet, options...) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // download whole file + os.Remove(newFileGet) + options = []Option{Routines(3), VersionId(versionId)} + err = bucket.GetObjectToFile(objectName, newFileGet, options...) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(fileName, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(fileName) + os.Remove(newFileGet) + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + forceDeleteBucket(client, bucketName, c) +} + +func (s *OssDownloadSuite) TestVersioningDownloadWithCheckPoint(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // begin test + objectName := objectNamePrefix + randStr(8) + fileName := "test-file-" + randStr(8) + fileData := randStr(500 * 1024) + createFile(fileName, fileData, c) + newFile := randStr(8) + ".jpg" + + // Upload a file + var respHeader http.Header + options := []Option{Routines(3), GetResponseHeader(&respHeader)} + err = bucket.UploadFile(objectName, fileName, 100*1024, options...) + c.Assert(err, IsNil) + versionId := GetVersionId(respHeader) + c.Assert(len(versionId) > 0, Equals, true) + + // Resumable download with checkpoint dir + os.Remove(newFile) + downloadPartHooker = DownErrorHooker + options = []Option{CheckpointDir(true, "./"), VersionId(versionId)} + err = bucket.DownloadFile(objectName, newFile, 100*1024, options...) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + + // download again + downloadPartHooker = defaultDownloadPartHook + options = []Option{CheckpointDir(true, "./"), VersionId(versionId), GetResponseHeader(&respHeader)} + err = bucket.DownloadFile(objectName, newFile, 100*1024, options...) + c.Assert(err, IsNil) + c.Assert(GetVersionId(respHeader), Equals, versionId) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(fileName) + os.Remove(newFile) + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + forceDeleteBucket(client, bucketName, c) +} diff --git a/oss/multicopy.go b/oss/multicopy.go index e2597c24..2370a6e7 100644 --- a/oss/multicopy.go +++ b/oss/multicopy.go @@ -142,13 +142,9 @@ func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destO srcBucket, err := bucket.Client.Bucket(srcBucketName) listener := getProgressListener(options) - payerOptions := []Option{} - payer := getPayer(options) - if payer != "" { - payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) - } - - meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, payerOptions...) + // for get whole length + skipOptions := deleteOption(options, HTTPHeaderRange) + meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, skipOptions...) if err != nil { return err } @@ -177,7 +173,7 @@ func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destO publishProgress(listener, event) // Start to copy workers - arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, payerOptions, copyPartHooker} + arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, options, copyPartHooker} for w := 1; w <= routines; w++ { go copyWorker(w, arg, jobs, results, failed, die) } @@ -198,7 +194,7 @@ func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destO publishProgress(listener, event) case err := <-failed: close(die) - descBucket.AbortMultipartUpload(imur, payerOptions...) + descBucket.AbortMultipartUpload(imur, options...) event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes) publishProgress(listener, event) return err @@ -213,9 +209,9 @@ func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destO publishProgress(listener, event) // Complete the multipart upload - _, err = descBucket.CompleteMultipartUpload(imur, ups, payerOptions...) + _, err = descBucket.CompleteMultipartUpload(imur, ups, options...) if err != nil { - bucket.AbortMultipartUpload(imur, payerOptions...) + bucket.AbortMultipartUpload(imur, options...) return err } return nil @@ -385,12 +381,6 @@ func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, srcBucket, err := bucket.Client.Bucket(srcBucketName) listener := getProgressListener(options) - payerOptions := []Option{} - payer := getPayer(options) - if payer != "" { - payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) - } - // Load CP data ccp := copyCheckpoint{} err = ccp.load(cpFilePath) @@ -399,7 +389,9 @@ func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, } // Make sure the object is not updated. - meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, payerOptions...) + // get whole length + skipOptions := deleteOption(options, HTTPHeaderRange) + meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, skipOptions...) if err != nil { return err } @@ -430,7 +422,7 @@ func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, publishProgress(listener, event) // Start the worker coroutines - arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, payerOptions, copyPartHooker} + arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, options, copyPartHooker} for w := 1; w <= routines; w++ { go copyWorker(w, arg, jobs, results, failed, die) } @@ -464,5 +456,5 @@ func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, event = newProgressEvent(TransferCompletedEvent, completedBytes, ccp.ObjStat.Size) publishProgress(listener, event) - return ccp.complete(descBucket, ccp.CopyParts, cpFilePath, payerOptions) + return ccp.complete(descBucket, ccp.CopyParts, cpFilePath, options) } diff --git a/oss/multicopy_test.go b/oss/multicopy_test.go index 0bc44f6d..33c1b256 100644 --- a/oss/multicopy_test.go +++ b/oss/multicopy_test.go @@ -2,7 +2,9 @@ package oss import ( "fmt" + "net/http" "os" + "strings" "time" . "gopkg.in/check.v1" @@ -496,3 +498,93 @@ func (s *OssCopySuite) TestCopyFileCrossBucket(c *C) { err = s.client.DeleteBucket(destBucketName) c.Assert(err, IsNil) } + +func (s *OssCopySuite) TestVersioningCopyFileCrossBucket(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // begin test + objectName := objectNamePrefix + randStr(8) + fileName := "test-file-" + randStr(8) + fileData := randStr(500 * 1024) + createFile(fileName, fileData, c) + newFile := "test-file-" + randStr(8) + destBucketName := bucketName + "-desc" + srcObjectName := objectNamePrefix + randStr(8) + destObjectName := srcObjectName + "-dest" + + // Create dest bucket + err = client.CreateBucket(destBucketName) + c.Assert(err, IsNil) + destBucket, err := client.Bucket(destBucketName) + c.Assert(err, IsNil) + + err = client.SetBucketVersioning(destBucketName, versioningConfig) + c.Assert(err, IsNil) + + // Upload source file + var respHeader http.Header + options := []Option{Routines(3), GetResponseHeader(&respHeader)} + err = bucket.UploadFile(srcObjectName, fileName, 100*1024, options...) + versionId := GetVersionId(respHeader) + c.Assert(len(versionId) > 0, Equals, true) + + c.Assert(err, IsNil) + os.Remove(newFile) + + // overwrite emtpy object + err = bucket.PutObject(srcObjectName, strings.NewReader("")) + c.Assert(err, IsNil) + + // Copy files + var respCopyHeader http.Header + options = []Option{Routines(5), Checkpoint(true, destObjectName+".cp"), GetResponseHeader(&respCopyHeader), VersionId(versionId)} + err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, options...) + c.Assert(err, IsNil) + versionIdCopy := GetVersionId(respCopyHeader) + c.Assert(len(versionIdCopy) > 0, Equals, true) + + err = destBucket.GetObjectToFile(destObjectName, newFile, VersionId(versionIdCopy)) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = destBucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Copy file with options meta + options = []Option{Routines(10), Checkpoint(true, "copy.cp"), Meta("myprop", "mypropval"), GetResponseHeader(&respCopyHeader), VersionId(versionId)} + err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, options...) + c.Assert(err, IsNil) + versionIdCopy = GetVersionId(respCopyHeader) + + err = destBucket.GetObjectToFile(destObjectName, newFile, VersionId(versionIdCopy)) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(fileName) + os.Remove(newFile) + destBucket.DeleteObject(destObjectName) + bucket.DeleteObject(objectName) + forceDeleteBucket(client, bucketName, c) + forceDeleteBucket(client, destBucketName, c) +} diff --git a/oss/multipart.go b/oss/multipart.go index b5a3a05b..657cb68f 100644 --- a/oss/multipart.go +++ b/oss/multipart.go @@ -151,10 +151,22 @@ func (bucket Bucket) UploadPartCopy(imur InitiateMultipartUploadResult, srcBucke startPosition, partSize int64, partNumber int, options ...Option) (UploadPart, error) { var out UploadPartCopyResult var part UploadPart + var opts []Option + + //first find version id + versionIdKey := "versionId" + versionId, _ := findOption(options, versionIdKey, nil) + if versionId == nil { + opts = []Option{CopySource(srcBucketName, url.QueryEscape(srcObjectKey)), + CopySourceRange(startPosition, partSize)} + } else { + opts = []Option{CopySourceVersion(srcBucketName, url.QueryEscape(srcObjectKey), versionId.(string)), + CopySourceRange(startPosition, partSize)} + options = deleteOption(options, versionIdKey) + } - opts := []Option{CopySource(srcBucketName, url.QueryEscape(srcObjectKey)), - CopySourceRange(startPosition, partSize)} opts = append(opts, options...) + params := map[string]interface{}{} params["partNumber"] = strconv.Itoa(partNumber) params["uploadId"] = imur.UploadID diff --git a/oss/multipart_test.go b/oss/multipart_test.go index 8fc99cd3..8a73853b 100644 --- a/oss/multipart_test.go +++ b/oss/multipart_test.go @@ -843,7 +843,7 @@ func (s *OssBucketMultipartSuite) TestUploadFile(c *C) { acl, err := s.bucket.GetObjectACL(objectName) c.Assert(err, IsNil) testLogger.Println("GetObjectAcl:", acl) - c.Assert(acl.ACL, Equals, "default") + c.Assert(acl.ACL, Equals, "public-read") meta, err := s.bucket.GetObjectDetailedMeta(objectName) c.Assert(err, IsNil) diff --git a/oss/option.go b/oss/option.go index 0b6ceec7..3de4a8c6 100644 --- a/oss/option.go +++ b/oss/option.go @@ -24,6 +24,7 @@ const ( initCRC64 = "init-crc64" progressListener = "x-progress-listener" storageClass = "storage-class" + responseHeader = "x-response-header" ) type ( @@ -126,6 +127,11 @@ func CopySource(sourceBucket, sourceObject string) Option { return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject) } +// CopySourceVersion is an option to set X-Oss-Copy-Source header,include versionId +func CopySourceVersion(sourceBucket, sourceObject string, versionId string) Option { + return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject+"?"+"versionId="+versionId) +} + // CopySourceRange is an option to set X-Oss-Copy-Source header func CopySourceRange(startPosition, partSize int64) Option { val := "bytes=" + strconv.FormatInt(startPosition, 10) + "-" + @@ -204,7 +210,7 @@ func RequestPayer(payerType PayerType) Option { } // Tagging is an option to set object tagging -func Tagging(tagging ObjectTagging) Option { +func SetTagging(tagging Tagging) Option { if len(tagging.Tags) == 0 { return nil } @@ -259,6 +265,26 @@ func KeyMarker(value string) Option { return addParam("key-marker", value) } +// VersionIdMarker is an option to set version-id-marker parameter +func VersionIdMarker(value string) Option { + return addParam("version-id-marker", value) +} + +// VersionId is an option to set versionId parameter +func VersionId(value string) Option { + return addParam("versionId", value) +} + +// TagKey is an option to set tag key parameter +func TagKey(value string) Option { + return addParam("tag-key", value) +} + +// TagValue is an option to set tag value parameter +func TagValue(value string) Option { + return addParam("tag-value", value) +} + // UploadIDMarker is an option to set upload-id-marker parameter func UploadIDMarker(value string) Option { return addParam("upload-id-marker", value) @@ -316,6 +342,11 @@ func Progress(listener ProgressListener) Option { return addArg(progressListener, listener) } +// GetResponseHeader for get response http header +func GetResponseHeader(respHeader *http.Header) Option { + return addArg(responseHeader, respHeader) +} + // ResponseContentType is an option to set response-content-type param func ResponseContentType(value string) Option { return addParam("response-content-type", value) @@ -453,3 +484,40 @@ func isOptionSet(options []Option, option string) (bool, interface{}, error) { } return false, nil, nil } + +func deleteOption(options []Option, strKey string) []Option { + var outOption []Option + params := map[string]optionValue{} + for _, option := range options { + if option != nil { + option(params) + _, exist := params[strKey] + if !exist { + outOption = append(outOption, option) + } else { + delete(params, strKey) + } + } + } + return outOption +} + +func GetRequestId(header http.Header) string { + return header.Get("x-oss-request-id") +} + +func GetVersionId(header http.Header) string { + return header.Get("x-oss-version-id") +} + +func GetCopySrcVersionId(header http.Header) string { + return header.Get("x-oss-copy-source-version-id") +} + +func GetDeleteMark(header http.Header) bool { + value := header.Get("x-oss-delete-marker") + if strings.ToUpper(value) == "TRUE" { + return true + } + return false +} diff --git a/oss/option_test.go b/oss/option_test.go index 24213b59..158be4e5 100644 --- a/oss/option_test.go +++ b/oss/option_test.go @@ -297,3 +297,21 @@ func (s *OssOptionSuite) TestFindOption(c *C) { c.Assert(err, IsNil) c.Assert(str, Equals, "") } + +func (s *OssOptionSuite) TestDeleteOption(c *C) { + options := []Option{VersionId("123"), VersionIdMarker("456"), KeyMarker("789")} + str, err := findOption(options, "versionId", "") + c.Assert(str, Equals, "123") + c.Assert(err, IsNil) + + skipOption := deleteOption(options, "versionId") + str, err = findOption(skipOption, "versionId", "") + c.Assert(str, Equals, "") + + str, err = findOption(skipOption, "version-id-marker", "") + c.Assert(str, Equals, "456") + + str, err = findOption(skipOption, "key-marker", "") + c.Assert(str, Equals, "789") + +} diff --git a/oss/type.go b/oss/type.go index f72a49bd..8aee81a8 100644 --- a/oss/type.go +++ b/oss/type.go @@ -228,14 +228,22 @@ type GetBucketInfoResult struct { // BucketInfo defines Bucket information type BucketInfo struct { XMLName xml.Name `xml:"Bucket"` - Name string `xml:"Name"` // Bucket name - Location string `xml:"Location"` // Bucket datacenter - CreationDate time.Time `xml:"CreationDate"` // Bucket creation time - ExtranetEndpoint string `xml:"ExtranetEndpoint"` // Bucket external endpoint - IntranetEndpoint string `xml:"IntranetEndpoint"` // Bucket internal endpoint - ACL string `xml:"AccessControlList>Grant"` // Bucket ACL - Owner Owner `xml:"Owner"` // Bucket owner - StorageClass string `xml:"StorageClass"` // Bucket storage class + Name string `xml:"Name"` // Bucket name + Location string `xml:"Location"` // Bucket datacenter + CreationDate time.Time `xml:"CreationDate"` // Bucket creation time + ExtranetEndpoint string `xml:"ExtranetEndpoint"` // Bucket external endpoint + IntranetEndpoint string `xml:"IntranetEndpoint"` // Bucket internal endpoint + ACL string `xml:"AccessControlList>Grant"` // Bucket ACL + Owner Owner `xml:"Owner"` // Bucket owner + StorageClass string `xml:"StorageClass"` // Bucket storage class + SseRule SSERule `xml:"ServerSideEncryptionRule"` // Bucket ServerSideEncryptionRule + Versioning string `xml:"Versioning"` // Bucket Versioning +} + +type SSERule struct { + XMLName xml.Name `xml:"ServerSideEncryptionRule"` // Bucket ServerSideEncryptionRule + KMSMasterKeyID string `xml:"KMSMasterKeyID"` // Bucket KMSMasterKeyID + SSEAlgorithm string `xml:"SSEAlgorithm"` // Bucket SSEAlgorithm } // ListObjectsResult defines the result from ListObjects request @@ -263,6 +271,46 @@ type ObjectProperties struct { StorageClass string `xml:"StorageClass"` // Object storage class (Standard, IA, Archive) } +// ListObjectVersionsResult defines the result from ListObjectVersions request +type ListObjectVersionsResult struct { + XMLName xml.Name `xml:"ListVersionsResult"` + Name string `xml:"Name"` // The Bucket Name + Owner Owner `xml:"Owner"` // The owner of bucket + Prefix string `xml:"Prefix"` // The object prefix + KeyMarker string `xml:"KeyMarker"` // The start marker filter. + VersionIdMarker string `xml:"VersionIdMarker"` // The start VersionIdMarker filter. + MaxKeys int `xml:"MaxKeys"` // Max keys to return + Delimiter string `xml:"Delimiter"` // The delimiter for grouping objects' name + IsTruncated bool `xml:"IsTruncated"` // Flag indicates if all results are returned (when it's false) + NextKeyMarker string `xml:"NextKeyMarker"` // The start point of the next query + NextVersionIdMarker string `xml:"NextVersionIdMarker"` // The start point of the next query + CommonPrefixes []string `xml:"CommonPrefixes>Prefix"` // You can think of commonprefixes as "folders" whose names end with the delimiter + ObjectDeleteMarkers []ObjectDeleteMarkerProperties `xml:"DeleteMarker"` // DeleteMarker list + ObjectVersions []ObjectVersionProperties `xml:"Version"` // version list +} + +type ObjectDeleteMarkerProperties struct { + XMLName xml.Name `xml:"DeleteMarker"` + Key string `xml:"Key"` // The Object Key + VersionId string `xml:"VersionId"` // The Object VersionId + IsLatest bool `xml:"IsLatest"` // is current version or not + LastModified time.Time `xml:"LastModified"` // Object last modified time + Owner Owner `xml:"Owner"` // bucket owner element +} + +type ObjectVersionProperties struct { + XMLName xml.Name `xml:"Version"` + Key string `xml:"Key"` // The Object Key + VersionId string `xml:"VersionId"` // The Object VersionId + IsLatest bool `xml:"IsLatest"` // is latest version or not + LastModified time.Time `xml:"LastModified"` // Object last modified time + Type string `xml:"Type"` // Object type + Size int64 `xml:"Size"` // Object size + ETag string `xml:"ETag"` // Object ETag + StorageClass string `xml:"StorageClass"` // Object storage class (Standard, IA, Archive) + Owner Owner `xml:"Owner"` // bucket owner element +} + // Owner defines Bucket/Object's owner type Owner struct { XMLName xml.Name `xml:"Owner"` @@ -288,14 +336,30 @@ type deleteXML struct { // DeleteObject defines the struct for deleting object type DeleteObject struct { - XMLName xml.Name `xml:"Object"` - Key string `xml:"Key"` // Object name + XMLName xml.Name `xml:"Object"` + Key string `xml:"Key"` // Object name + VersionId string `xml:"VersionId,omitempty"` // Object VersionId } // DeleteObjectsResult defines result of DeleteObjects request type DeleteObjectsResult struct { - XMLName xml.Name `xml:"DeleteResult"` - DeletedObjects []string `xml:"Deleted>Key"` // Deleted object list + XMLName xml.Name + DeletedObjects []string // Deleted object key list +} + +// DeleteObjectsResult_inner defines result of DeleteObjects request +type DeleteObjectVersionsResult struct { + XMLName xml.Name `xml:"DeleteResult"` + DeletedObjectsDetail []DeletedKeyInfo `xml:"Deleted"` // Deleted object detail info +} + +// DeleteKeyInfo defines object delete info +type DeletedKeyInfo struct { + XMLName xml.Name `xml:"Deleted"` + Key string `xml:"Key"` // Object key + VersionId string `xml:"VersionId"` // VersionId + DeleteMarker bool `xml:"DeleteMarker"` // Object DeleteMarker + DeleteMarkerVersionId string `xml:"DeleteMarkerVersionId"` // Object DeleteMarkerVersionId } // InitiateMultipartUploadResult defines result of InitiateMultipartUpload request @@ -402,10 +466,10 @@ type ProcessObjectResult struct { } // decodeDeleteObjectsResult decodes deleting objects result in URL encoding -func decodeDeleteObjectsResult(result *DeleteObjectsResult) error { +func decodeDeleteObjectsResult(result *DeleteObjectVersionsResult) error { var err error - for i := 0; i < len(result.DeletedObjects); i++ { - result.DeletedObjects[i], err = url.QueryUnescape(result.DeletedObjects[i]) + for i := 0; i < len(result.DeletedObjectsDetail); i++ { + result.DeletedObjectsDetail[i].Key, err = url.QueryUnescape(result.DeletedObjectsDetail[i].Key) if err != nil { return err } @@ -447,6 +511,73 @@ func decodeListObjectsResult(result *ListObjectsResult) error { return nil } +// decodeListObjectVersionsResult decodes list version objects result in URL encoding +func decodeListObjectVersionsResult(result *ListObjectVersionsResult) error { + var err error + + // decode:Delimiter + result.Delimiter, err = url.QueryUnescape(result.Delimiter) + if err != nil { + return err + } + + // decode Prefix + result.Prefix, err = url.QueryUnescape(result.Prefix) + if err != nil { + return err + } + + // decode KeyMarker + result.KeyMarker, err = url.QueryUnescape(result.KeyMarker) + if err != nil { + return err + } + + // decode VersionIdMarker + result.VersionIdMarker, err = url.QueryUnescape(result.VersionIdMarker) + if err != nil { + return err + } + + // decode NextKeyMarker + result.NextKeyMarker, err = url.QueryUnescape(result.NextKeyMarker) + if err != nil { + return err + } + + // decode NextVersionIdMarker + result.NextVersionIdMarker, err = url.QueryUnescape(result.NextVersionIdMarker) + if err != nil { + return err + } + + // decode CommonPrefixes + for i := 0; i < len(result.CommonPrefixes); i++ { + result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i]) + if err != nil { + return err + } + } + + // decode deleteMarker + for i := 0; i < len(result.ObjectDeleteMarkers); i++ { + result.ObjectDeleteMarkers[i].Key, err = url.QueryUnescape(result.ObjectDeleteMarkers[i].Key) + if err != nil { + return err + } + } + + // decode ObjectVersions + for i := 0; i < len(result.ObjectVersions); i++ { + result.ObjectVersions[i].Key, err = url.QueryUnescape(result.ObjectVersions[i].Key) + if err != nil { + return err + } + } + + return nil +} + // decodeListUploadedPartsResult decodes func decodeListUploadedPartsResult(result *ListUploadedPartsResult) error { var err error @@ -602,8 +733,35 @@ type Tag struct { Value string `xml:"Value"` } -// ObjectTagging tagset for the object -type ObjectTagging struct { +// Tagging tagset for the object +type Tagging struct { XMLName xml.Name `xml:"Tagging"` Tags []Tag `xml:"TagSet>Tag,omitempty"` } + +// for GetObjectTagging return value +type GetObjectTaggingResult Tagging + +// VersioningConfig for the bucket +type VersioningConfig struct { + XMLName xml.Name `xml:"VersioningConfiguration"` + Status string `xml:"Status"` +} + +type GetBucketVersioningResult VersioningConfig + +// Server Encryption rule for the bucket +type ServerEncryptionRule struct { + XMLName xml.Name `xml:"ServerSideEncryptionRule"` + SSEDefault SSEDefaultRule `xml:"ApplyServerSideEncryptionByDefault"` +} + +// Server Encryption deafult rule for the bucket +type SSEDefaultRule struct { + XMLName xml.Name `xml:"ApplyServerSideEncryptionByDefault"` + SSEAlgorithm string `xml:"SSEAlgorithm"` + KMSMasterKeyID string `xml:"KMSMasterKeyID"` +} + +type GetBucketEncryptionResult ServerEncryptionRule +type GetBucketTaggingResult Tagging diff --git a/oss/type_test.go b/oss/type_test.go index 0029c416..5be3beb3 100644 --- a/oss/type_test.go +++ b/oss/type_test.go @@ -19,20 +19,20 @@ var ( ) func (s *OssTypeSuite) TestDecodeDeleteObjectsResult(c *C) { - var res DeleteObjectsResult + var res DeleteObjectVersionsResult err := decodeDeleteObjectsResult(&res) c.Assert(err, IsNil) - res.DeletedObjects = []string{""} + res.DeletedObjectsDetail = []DeletedKeyInfo{DeletedKeyInfo{Key: ""}} err = decodeDeleteObjectsResult(&res) c.Assert(err, IsNil) - c.Assert(res.DeletedObjects[0], Equals, "") + c.Assert(res.DeletedObjectsDetail[0].Key, Equals, "") - res.DeletedObjects = []string{goURLStr, chnURLStr} + res.DeletedObjectsDetail = []DeletedKeyInfo{DeletedKeyInfo{Key: goURLStr}, DeletedKeyInfo{Key: chnURLStr}} err = decodeDeleteObjectsResult(&res) c.Assert(err, IsNil) - c.Assert(res.DeletedObjects[0], Equals, goStr) - c.Assert(res.DeletedObjects[1], Equals, chnStr) + c.Assert(res.DeletedObjectsDetail[0].Key, Equals, goStr) + c.Assert(res.DeletedObjectsDetail[1].Key, Equals, chnStr) } func (s *OssTypeSuite) TestDecodeListObjectsResult(c *C) { diff --git a/oss/upload.go b/oss/upload.go index b9e88651..6037cfe8 100644 --- a/oss/upload.go +++ b/oss/upload.go @@ -175,12 +175,6 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti return err } - payerOptions := []Option{} - payer := getPayer(options) - if payer != "" { - payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) - } - // Initialize the multipart upload imur, err := bucket.InitiateMultipartUpload(objectKey, options...) if err != nil { @@ -198,7 +192,7 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti publishProgress(listener, event) // Start the worker coroutine - arg := workerArg{&bucket, filePath, imur, payerOptions, uploadPartHooker} + arg := workerArg{&bucket, filePath, imur, options, uploadPartHooker} for w := 1; w <= routines; w++ { go worker(w, arg, jobs, results, failed, die) } @@ -221,7 +215,7 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti close(die) event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes) publishProgress(listener, event) - bucket.AbortMultipartUpload(imur, payerOptions...) + bucket.AbortMultipartUpload(imur, options...) return err } @@ -234,9 +228,9 @@ func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, opti publishProgress(listener, event) // Complete the multpart upload - _, err = bucket.CompleteMultipartUpload(imur, parts, payerOptions...) + _, err = bucket.CompleteMultipartUpload(imur, parts, options...) if err != nil { - bucket.AbortMultipartUpload(imur, payerOptions...) + bucket.AbortMultipartUpload(imur, options...) return err } return nil @@ -448,12 +442,6 @@ func complete(cp *uploadCheckpoint, bucket *Bucket, parts []UploadPart, cpFilePa func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64, options []Option, cpFilePath string, routines int) error { listener := getProgressListener(options) - payerOptions := []Option{} - payer := getPayer(options) - if payer != "" { - payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) - } - // Load CP data ucp := uploadCheckpoint{} err := ucp.load(cpFilePath) @@ -486,7 +474,7 @@ func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64 publishProgress(listener, event) // Start the workers - arg := workerArg{&bucket, filePath, imur, payerOptions, uploadPartHooker} + arg := workerArg{&bucket, filePath, imur, options, uploadPartHooker} for w := 1; w <= routines; w++ { go worker(w, arg, jobs, results, failed, die) } @@ -521,6 +509,6 @@ func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64 publishProgress(listener, event) // Complete the multipart upload - err = complete(&ucp, &bucket, ucp.allParts(), cpFilePath, payerOptions) + err = complete(&ucp, &bucket, ucp.allParts(), cpFilePath, options) return err } diff --git a/oss/upload_test.go b/oss/upload_test.go index 1ca6ead7..452b68d5 100644 --- a/oss/upload_test.go +++ b/oss/upload_test.go @@ -3,6 +3,7 @@ package oss import ( "fmt" "io" + "net/http" "os" "time" @@ -474,3 +475,63 @@ func copyFile(src, dst string) error { _, err = io.Copy(dstFile, srcFile) return err } + +func (s *OssUploadSuite) TestVersioningUploadRoutineWithRecovery(c *C) { + // create a bucket with default proprety + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + bucketName := bucketNamePrefix + randLowStr(6) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + + // put bucket version:enabled + var versioningConfig VersioningConfig + versioningConfig.Status = string(VersionEnabled) + err = client.SetBucketVersioning(bucketName, versioningConfig) + c.Assert(err, IsNil) + + // begin test + objectName := objectNamePrefix + randStr(8) + fileName := "test-file-" + randStr(8) + fileData := randStr(500 * 1024) + createFile(fileName, fileData, c) + newFile := "test-file-" + randStr(8) + + // Use default routines and default CP file path (fileName+.cp)Header + // First upload for 4 parts + var respHeader http.Header + uploadPartHooker = ErrorHooker + options := []Option{Checkpoint(true, fileName+".cp"), GetResponseHeader(&respHeader)} + err = bucket.UploadFile(objectName, fileName, 100*1024, options...) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + c.Assert(GetVersionId(respHeader), Equals, "") + + uploadPartHooker = defaultUploadPart + + // Second upload, finish the remaining part + options = []Option{Checkpoint(true, fileName+".cp"), GetResponseHeader(&respHeader)} + err = bucket.UploadFile(objectName, fileName, 100*1024, options...) + c.Assert(err, IsNil) + versionIdUp := GetVersionId(respHeader) + c.Assert(len(versionIdUp) > 0, Equals, true) + + os.Remove(newFile) + var respHeaderDown http.Header + err = bucket.GetObjectToFile(objectName, newFile, GetResponseHeader(&respHeaderDown)) + versionIdDown := GetVersionId(respHeaderDown) + c.Assert(err, IsNil) + c.Assert(versionIdUp, Equals, versionIdDown) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(fileName) + os.Remove(newFile) + bucket.DeleteObject(objectName) + forceDeleteBucket(client, bucketName, c) +} diff --git a/sample/copy_object.go b/sample/copy_object.go index 970eeed4..c25dcd50 100644 --- a/sample/copy_object.go +++ b/sample/copy_object.go @@ -120,10 +120,10 @@ func CopyObjectSample() { Key: "key2", Value: "value2", } - tagging := oss.ObjectTagging{ + tagging := oss.Tagging{ Tags: []oss.Tag{tag1, tag2}, } - _, err = bucket.CopyObject(objectKey, objectKey+"WithTagging", oss.Tagging(tagging), oss.TaggingDirective(oss.TaggingReplace)) + _, err = bucket.CopyObject(objectKey, objectKey+"WithTagging", oss.SetTagging(tagging), oss.TaggingDirective(oss.TaggingReplace)) if err != nil { HandleError(err) } diff --git a/sample/object_tagging.go b/sample/object_tagging.go index d9f2c747..67271e90 100644 --- a/sample/object_tagging.go +++ b/sample/object_tagging.go @@ -30,7 +30,7 @@ func ObjectTaggingSample() { Key: "key2", Value: "value2", } - tagging := oss.ObjectTagging{ + tagging := oss.Tagging{ Tags: []oss.Tag{tag1, tag2}, } err = bucket.PutObjectTagging(objectKey, tagging) @@ -39,11 +39,11 @@ func ObjectTaggingSample() { } // Case 2: Get Tagging of object - tagging, err = bucket.GetObjectTagging(objectKey) + taggingResult, err := bucket.GetObjectTagging(objectKey) if err != nil { HandleError(err) } - fmt.Printf("Object Tagging: %v\n", tagging) + fmt.Printf("Object Tagging: %v\n", taggingResult) tag3 := oss.Tag{ Key: "key3", @@ -51,10 +51,10 @@ func ObjectTaggingSample() { } // Case 3: Put object with tagging - tagging = oss.ObjectTagging{ + tagging = oss.Tagging{ Tags: []oss.Tag{tag1, tag2, tag3}, } - err = bucket.PutObject(objectKey, strings.NewReader("ObjectTaggingSample"), oss.Tagging(tagging)) + err = bucket.PutObject(objectKey, strings.NewReader("ObjectTaggingSample"), oss.SetTagging(tagging)) if err != nil { HandleError(err) }