s3gateway round 2

This commit is contained in:
Qiu Jian
2019-07-23 01:30:09 +08:00
parent 870813f04b
commit 93b8bac5bb
103 changed files with 5509 additions and 1632 deletions

View File

@@ -3,6 +3,8 @@ package shell
import (
"yunion.io/x/jsonutils"
"io"
"os"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/mcclient/options"
@@ -84,4 +86,88 @@ func init() {
printObject(result)
return nil
})
type BucketListObjectsOptions struct {
ID string `help:"ID or name of bucket" json:"-"`
Prefix string `help:"List objects with prefix"`
Recursive bool `help:"List objects recursively"`
}
R(&BucketListObjectsOptions{}, "bucket-object-list", "List objects in a bucket", func(s *mcclient.ClientSession, args *BucketListObjectsOptions) error {
params, err := options.StructToParams(args)
if err != nil {
return err
}
result, err := modules.Buckets.GetSpecific(s, args.ID, "objects", params)
if err != nil {
return err
}
arrays, _ := result.(*jsonutils.JSONArray).GetArray()
listResult := modules.ListResult{Data: arrays}
printList(&listResult, []string{})
return nil
})
type BucketDeleteObjectsOptions struct {
ID string `help:"ID or name of bucket" json:"-"`
KEYS []string `help:"List of objects to delete"`
}
R(&BucketDeleteObjectsOptions{}, "bucket-object-delete", "Delete objects in a bucket", func(s *mcclient.ClientSession, args *BucketDeleteObjectsOptions) error {
params, err := options.StructToParams(args)
if err != nil {
return err
}
result, err := modules.Buckets.PerformAction(s, args.ID, "delete", params)
if err != nil {
return err
}
printObject(result)
return nil
})
type BucketUploadObjectsOptions struct {
ID string `help:"ID or name of bucket" json:"-"`
KEY string `help:"Key of object to upload"`
Path string `help:"Path to file to upload"`
ContentType string `help:"Content type"`
StorageClass string `help:"storage CLass"`
}
R(&BucketUploadObjectsOptions{}, "bucket-object-upload", "Upload an object into a bucket", func(s *mcclient.ClientSession, args *BucketUploadObjectsOptions) error {
var body io.Reader
if len(args.Path) > 0 {
file, err := os.Open(args.Path)
if err != nil {
return err
}
defer file.Close()
body = file
} else {
body = os.Stdin
}
err := modules.Buckets.Upload(s, args.ID, args.KEY, body, args.ContentType, args.StorageClass)
if err != nil {
return err
}
return nil
})
type BucketPresignObjectsOptions struct {
ID string `help:"ID or name of bucket" json:"-"`
KEY string `help:"Key of object to upload"`
Method string `help:"Request method" choices:"GET|PUT|DELETE"`
ExpireSeconds int `help:"expire in seconds" default:"60"`
}
R(&BucketPresignObjectsOptions{}, "bucket-object-tempurl", "Get temporal URL for an object in a bucket", func(s *mcclient.ClientSession, args *BucketPresignObjectsOptions) error {
params, err := options.StructToParams(args)
if err != nil {
return err
}
result, err := modules.Buckets.PerformAction(s, args.ID, "temp-url", params)
if err != nil {
return err
}
printObject(result)
return nil
})
}

9
go.mod
View File

@@ -64,6 +64,7 @@ require (
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/golang/protobuf v1.3.1
github.com/google/btree v1.0.0 // indirect
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf // indirect
github.com/google/gopacket v1.1.17
github.com/google/uuid v1.1.0 // indirect
@@ -102,8 +103,8 @@ require (
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
github.com/moul/http2curl v1.0.0
github.com/mozillazg/go-httpheader v0.2.1 // indirect
github.com/mozillazg/go-pinyin v0.15.0
github.com/nelsonken/cos-go-sdk-v5 v0.0.0-20180622024522-5247afdb7a80
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 // indirect
github.com/opentracing/opentracing-go v1.0.2 // indirect
github.com/openzipkin/zipkin-go-opentracing v0.3.4 // indirect
@@ -123,6 +124,7 @@ require (
github.com/spf13/pflag v1.0.3 // indirect
github.com/stretchr/testify v1.3.0
github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181108132626-805d01dd0e2e
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190717101923-c5c1f9751e7f
github.com/tinylib/msgp v1.1.0 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
github.com/tredoe/osutil v0.0.0-20161130133508-7d3ee1afa71c
@@ -144,7 +146,10 @@ require (
google.golang.org/genproto v0.0.0-20181218023534-67d6565462c5 // indirect
google.golang.org/grpc v1.19.0
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.44.0 // indirect
gopkg.in/ldap.v3 v3.0.3
gopkg.in/yaml.v2 v2.2.2
k8s.io/api v0.0.0-20181004124137-fd83cbc87e76
@@ -157,5 +162,5 @@ require (
yunion.io/x/log v0.0.0-20190629062853-9f6483a7103d
yunion.io/x/pkg v0.0.0-20190628082551-f4033ba2ea30
yunion.io/x/sqlchemy v0.0.0-20190704155352-6aff6c803fda
yunion.io/x/structarg v0.0.0-20190625074850-3c0636a9fffe
yunion.io/x/structarg v0.0.0-20190717142057-5caf182cbb4d
)

18
go.sum
View File

@@ -179,6 +179,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck=
github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
github.com/google/gopacket v1.1.17 h1:rMrlX2ZY2UbvT+sdz3+6J+pp2z+msCq9MxTU6ymxbBY=
@@ -288,12 +290,12 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
github.com/mozillazg/go-pinyin v0.15.0 h1:sSwlnsogK/WMzcf0HnjgxyAI4GU6LFqwXnhr77q1Z80=
github.com/mozillazg/go-pinyin v0.15.0/go.mod h1:bO+dztNW6O2lSJdYLha7LO3bujXzjjU3UvKb2IGANfg=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae h1:VeRdUYdCw49yizlSbMEn2SZ+gT+3IUKx8BqxyQdz+BY=
github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg=
github.com/nelsonken/cos-go-sdk-v5 v0.0.0-20180622024522-5247afdb7a80 h1:1tGm26e9ktdEIa7LHD7xf7oMUCE0V2wP4URkUAvEttA=
github.com/nelsonken/cos-go-sdk-v5 v0.0.0-20180622024522-5247afdb7a80/go.mod h1:UZaoQ2hntRH4P8MwrKOxcVXvNgg1N4atxfa7NP+1wWg=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU=
@@ -362,6 +364,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/syncthing/syncthing v0.14.48-rc.4/go.mod h1:nw3siZwHPA6M8iSfjDCWQ402eqvEIasMQOE8nFOxy7M=
github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181108132626-805d01dd0e2e h1:CtKVGXKh2bfmepZ/YogAjvL/CrSy9NGZET500K7arf4=
github.com/tencentcloud/tencentcloud-sdk-go v0.0.0-20181108132626-805d01dd0e2e/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190717101923-c5c1f9751e7f h1:TzE7Cs9HhTyfot4WIoMnbD1rWfD4Jkwy2M3Zs66CaRE=
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190717101923-c5c1f9751e7f/go.mod h1:/4BhymH1yO6ljUGQgcKsd7L3W+pdKRxoRiOuoZPLnGg=
github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e h1:T5PdfK/M1xyrHwynxMIVMWLS7f/qHwfslZphxtGnw7s=
github.com/texttheater/golang-levenshtein v0.0.0-20180516184445-d188e65d659e/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
@@ -453,8 +457,14 @@ gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUy
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.44.0 h1:YRJzTUp0kSYWUVFF5XAbDFfyiqwsl0Vb9R8TVP5eRi0=
gopkg.in/ini.v1 v1.44.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ldap.v3 v3.0.3 h1:YKRHW/2sIl05JsCtx/5ZuUueFuJyoj/6+DGXe3wp6ro=
gopkg.in/ldap.v3 v3.0.3/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
@@ -488,5 +498,5 @@ yunion.io/x/pkg v0.0.0-20190628082551-f4033ba2ea30 h1:6CkrwtX4xeYFqqpdWtQPAVqEnD
yunion.io/x/pkg v0.0.0-20190628082551-f4033ba2ea30/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
yunion.io/x/sqlchemy v0.0.0-20190704155352-6aff6c803fda h1:i+/3Hh+kVmPZM1P+j3wfViEzb0XlttWyNvDSYA5DDDU=
yunion.io/x/sqlchemy v0.0.0-20190704155352-6aff6c803fda/go.mod h1:FTdwPdGhMgh4E+UFXc9klI1Ok34fMuybTT+jLhOaIjI=
yunion.io/x/structarg v0.0.0-20190625074850-3c0636a9fffe h1:Wr2EXv72ynwtW+x0VCqWwpsfUBWecaZWnLMgaP3UiTo=
yunion.io/x/structarg v0.0.0-20190625074850-3c0636a9fffe/go.mod h1:EP6NSv2C0zzqBDTKumv8hPWLb3XvgMZDHQRfyuOrQng=
yunion.io/x/structarg v0.0.0-20190717142057-5caf182cbb4d h1:00kGV39weRaYPldUUh5mllj4aHcGMOZZx4m3CotRESw=
yunion.io/x/structarg v0.0.0-20190717142057-5caf182cbb4d/go.mod h1:EP6NSv2C0zzqBDTKumv8hPWLb3XvgMZDHQRfyuOrQng=

View File

@@ -9,4 +9,7 @@ const (
BUCKET_STATUS_DELETING = "deleting"
BUCKET_STATUS_DELETED = "deleted"
BUCKET_STATUS_DELETE_FAIL = "delete_fail"
BUCKET_UPLOAD_OBJECT_KEY_HEADER = "X-Yunion-Bucket-Upload-Key"
BUCKET_UPLOAD_OBJECT_STORAGECLASS_HEADER = "X-Yunion-Bucket-Upload-Storageclass"
)

View File

@@ -250,7 +250,8 @@ func (app *Application) defaultHandle(w http.ResponseWriter, r *http.Request, ri
to = app.processTimeout
}
var (
ctx context.Context = app.context
ctx = app.context
cancel context.CancelFunc = nil
)
if to > 0 {

View File

@@ -26,27 +26,38 @@ type handlerRequestCounter struct {
duration float64
}
type TProcessTimeoutCallback func(*SHandlerInfo, *http.Request) time.Duration
type SHandlerInfo struct {
method string
path []string
name string
handler func(context.Context, http.ResponseWriter, *http.Request)
metadata map[string]interface{}
tags map[string]string
counter2XX handlerRequestCounter
counter4XX handlerRequestCounter
counter5XX handlerRequestCounter
processTimeout time.Duration
workerMan *SWorkerManager
skipLog bool
method string
path []string
name string
handler func(context.Context, http.ResponseWriter, *http.Request)
metadata map[string]interface{}
tags map[string]string
counter2XX handlerRequestCounter
counter4XX handlerRequestCounter
counter5XX handlerRequestCounter
workerMan *SWorkerManager
skipLog bool
processTimeout time.Duration
processTimeoutCallback TProcessTimeoutCallback
}
func (this *SHandlerInfo) FetchProcessTimeout(r *http.Request) time.Duration {
if r.Method == http.MethodGet && len(r.URL.Query().Get("export_keys")) > 0 {
return time.Hour * 2
} else {
return this.processTimeout
var tm time.Duration
if this.processTimeoutCallback != nil {
tm = this.processTimeoutCallback(this, r)
}
if tm < this.processTimeout {
tm = this.processTimeout
}
return tm
}
func (this *SHandlerInfo) SetProcessTimeoutCallback(callback TProcessTimeoutCallback) {
this.processTimeoutCallback = callback
}
func (this *SHandlerInfo) GetName(params map[string]string) string {

View File

@@ -21,6 +21,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/sqlchemy"
"time"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/object"
@@ -90,6 +91,8 @@ type IModelManager interface {
InitializeData() error
CustomizeHandlerInfo(info *appsrv.SHandlerInfo)
SetHandlerProcessTimeout(info *appsrv.SHandlerInfo, r *http.Request) time.Duration
FetchCreateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error)
FetchUpdateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error)
IsCustomizedGetDetailsBody() bool

View File

@@ -22,6 +22,8 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/sqlchemy"
"log"
"time"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudcommon/object"
"yunion.io/x/onecloud/pkg/httperrors"
@@ -53,7 +55,11 @@ func NewModelBaseManager(model interface{}, tableName string, keyword string, ke
}
func (manager *SModelBaseManager) GetIModelManager() IModelManager {
return manager.GetVirtualObject().(IModelManager)
virt := manager.GetVirtualObject()
if virt == nil {
log.Fatalf("%s.GetIModelManager got nil!", manager.keywordPlural)
}
return virt.(IModelManager)
}
func (manager *SModelBaseManager) SetAlias(alias string, aliasPlural string) {
@@ -203,7 +209,14 @@ func (manager *SModelBaseManager) GetExportExtraKeys(ctx context.Context, query
}
func (manager *SModelBaseManager) CustomizeHandlerInfo(info *appsrv.SHandlerInfo) {
// do nothing
info.SetProcessTimeoutCallback(manager.GetIModelManager().SetHandlerProcessTimeout)
}
func (manager *SModelBaseManager) SetHandlerProcessTimeout(info *appsrv.SHandlerInfo, r *http.Request) time.Duration {
if r.Method == http.MethodGet && len(r.URL.Query().Get("export_keys")) > 0 {
return time.Hour * 2
}
return -time.Second
}
func (manager *SModelBaseManager) FetchCreateHeaderData(ctx context.Context, header http.Header) (jsonutils.JSONObject, error) {

View File

@@ -1,8 +1,11 @@
package cloudprovider
import (
"context"
"io"
"time"
"strings"
"yunion.io/x/pkg/errors"
)
@@ -11,6 +14,22 @@ type SBucketAccessUrl struct {
Description string
}
type SBaseCloudObject struct {
Key string
SizeBytes int64
StorageClass string
ETag string
LastModified time.Time
ContentType string
}
type SListObjectResult struct {
Objects []ICloudObject
NextMarker string
CommonPrefixes []ICloudObject
IsTruncated bool
}
type ICloudBucket interface {
IVirtualResource
@@ -22,17 +41,125 @@ type ICloudBucket interface {
GetCreateAt() time.Time
GetStorageClass() string
GetAccessUrls() []SBucketAccessUrl
ListObjects(prefix string, marker string, delimiter string, maxCount int) (SListObjectResult, error)
GetIObjects(prefix string, isRecursive bool) ([]ICloudObject, error)
PutObject(ctx context.Context, key string, input io.ReadSeeker, contType string, storageClass string) error
DeleteObject(ctx context.Context, keys string) error
GetTempUrl(method string, key string, expire time.Duration) (string, error)
// ObjectExist(key string) (bool, error)
}
func GetIBucketByName(region ICloudRegion, name string) (ICloudBucket, error) {
type ICloudObject interface {
GetIBucket() ICloudBucket
GetKey() string
GetSizeBytes() int64
GetLastModified() time.Time
GetStorageClass() string
GetETag() string
GetContentType() string
}
func ICloudObject2BaseCloudObject(obj ICloudObject) SBaseCloudObject {
return SBaseCloudObject{
Key: obj.GetKey(),
SizeBytes: obj.GetSizeBytes(),
StorageClass: obj.GetStorageClass(),
ETag: obj.GetETag(),
LastModified: obj.GetLastModified(),
ContentType: obj.GetContentType(),
}
}
func (o *SBaseCloudObject) GetKey() string {
return o.Key
}
func (o *SBaseCloudObject) GetSizeBytes() int64 {
return o.SizeBytes
}
func (o *SBaseCloudObject) GetLastModified() time.Time {
return o.LastModified
}
func (o *SBaseCloudObject) GetStorageClass() string {
return o.StorageClass
}
func (o *SBaseCloudObject) GetETag() string {
return o.ETag
}
func (o *SBaseCloudObject) GetContentType() string {
return o.ContentType
}
func GetIBucketById(region ICloudRegion, name string) (ICloudBucket, error) {
buckets, err := region.GetIBuckets()
if err != nil {
return nil, errors.Wrap(err, "region.GetIBuckets")
}
for i := range buckets {
if buckets[i].GetName() == name {
if buckets[i].GetGlobalId() == name {
return buckets[i], nil
}
}
return nil, ErrNotFound
}
func GetIObjects(bucket ICloudBucket, objectPrefix string, isRecursive bool) ([]ICloudObject, error) {
delimiter := "/"
if isRecursive {
delimiter = ""
}
ret := make([]ICloudObject, 0)
// Save marker for next request.
var marker string
for {
// Get list of objects a maximum of 1000 per request.
result, err := bucket.ListObjects(objectPrefix, marker, delimiter, 1000)
if err != nil {
return nil, errors.Wrap(err, "bucket.ListObjects")
}
// Send all objects
if len(result.Objects) > 0 {
ret = append(ret, result.Objects...)
marker = result.Objects[len(result.Objects)-1].GetKey()
}
// Send all common prefixes if any.
// NOTE: prefixes are only present if the request is delimited.
if len(result.CommonPrefixes) > 0 {
ret = append(ret, result.CommonPrefixes...)
}
// If next marker present, save it for next request.
if result.NextMarker != "" {
marker = result.NextMarker
}
// Listing ends result is not truncated, break the loop
if !result.IsTruncated {
break
}
}
return ret, nil
}
func Makedir(ctx context.Context, bucket ICloudBucket, key string) error {
segs := make([]string, 0)
for _, seg := range strings.Split(key, "/") {
if len(seg) > 0 {
segs = append(segs, seg)
}
}
path := strings.Join(segs, "/") + "/"
err := bucket.PutObject(ctx, path, strings.NewReader(""), "", "")
if err != nil {
return errors.Wrap(err, "PutObject")
}
return nil
}

View File

@@ -114,7 +114,7 @@ type ICloudRegion interface {
CreateIBucket(name string, storageClassStr string, acl string) error
DeleteIBucket(name string) error
IBucketExist(name string) (bool, error)
GetIBucketByName(name string) (ICloudBucket, error)
GetIBucketById(name string) (ICloudBucket, error)
GetProvider() string
}

View File

@@ -3,7 +3,11 @@ package models
import (
"context"
"database/sql"
"regexp"
"net/http"
"strings"
"time"
"github.com/minio/minio-go/pkg/s3utils"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
@@ -11,7 +15,9 @@ import (
"yunion.io/x/pkg/util/compare"
"yunion.io/x/sqlchemy"
"io"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
@@ -19,6 +25,7 @@ import (
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/modules"
)
type SBucketManager struct {
@@ -52,6 +59,14 @@ type SBucket struct {
Acl string `width:"36" charset:"ascii" nullable:"false" list:"user"`
}
func (manager *SBucketManager) SetHandlerProcessTimeout(info *appsrv.SHandlerInfo, r *http.Request) time.Duration {
if r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/upload") && r.Header.Get(api.BUCKET_UPLOAD_OBJECT_KEY_HEADER) != "" {
log.Debugf("upload object, set process timeout to 2 hour!!!")
return 2 * time.Hour
}
return manager.SVirtualResourceBaseManager.SetHandlerProcessTimeout(info, r)
}
func (manager *SBucketManager) fetchBuckets(provider *SCloudprovider, region *SCloudregion) ([]SBucket, error) {
q := manager.Query()
if provider != nil {
@@ -267,10 +282,16 @@ func (bucket *SBucket) GetIRegion() (cloudprovider.ICloudRegion, error) {
return provider.GetIRegionById(region.GetExternalId())
}
var BUCKET_NAME_REG = regexp.MustCompile(`^[a-zA-Z0-9-]+$`)
func (bucket *SBucket) GetIBucket() (cloudprovider.ICloudBucket, error) {
iregion, err := bucket.GetIRegion()
if err != nil {
return nil, errors.Wrap(err, "bucket.GetIRegion")
}
return iregion.GetIBucketById(bucket.ExternalId)
}
func isValidBucketName(name string) bool {
return BUCKET_NAME_REG.MatchString(name)
func isValidBucketName(name string) error {
return s3utils.CheckValidBucketNameStrict(name)
}
func (manager *SBucketManager) ValidateCreateData(
@@ -293,8 +314,9 @@ func (manager *SBucketManager) ValidateCreateData(
if len(nameStr) == 0 {
return nil, httperrors.NewInputParameterError("missing name")
}
if !isValidBucketName(nameStr) {
return nil, httperrors.NewInputParameterError("invalid name, only alphabets, digits and hyphen(-) allowed")
err := isValidBucketName(nameStr)
if err != nil {
return nil, httperrors.NewInputParameterError("invalid bucket name: %s", err)
}
return manager.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data)
}
@@ -323,8 +345,9 @@ func (bucket *SBucket) ValidateUpdateData(
) (*jsonutils.JSONDict, error) {
nameStr, _ := data.GetString("name")
if len(nameStr) > 0 {
if !isValidBucketName(nameStr) {
return nil, httperrors.NewInputParameterError("invalid name, only alphabets, digits and hyphen(-) allowed")
err := isValidBucketName(nameStr)
if err != nil {
return nil, httperrors.NewInputParameterError("invalid bucket name: %s", err)
}
}
return bucket.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data)
@@ -343,7 +366,7 @@ func (bucket *SBucket) RemoteCreate(ctx context.Context, userCred mcclient.Token
if err != nil {
return errors.Wrap(err, "db.SetExternalId")
}
extBucket, err := iregion.GetIBucketByName(bucket.Name)
extBucket, err := iregion.GetIBucketById(bucket.Name)
if err != nil {
return errors.Wrap(err, "iregion.GetIBucketByName")
}
@@ -401,3 +424,186 @@ func (manager *SBucketManager) ListItemFilter(ctx context.Context, q *sqlchemy.S
return q, nil
}
func (bucket *SBucket) AllowGetDetailsObjects(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
) bool {
return bucket.IsOwner(userCred)
}
func (bucket *SBucket) GetDetailsObjects(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
iBucket, err := bucket.GetIBucket()
if err != nil {
return nil, httperrors.NewInternalServerError("fail to find external bucket: %s", err)
}
prefix, _ := query.GetString("prefix")
isRecursive := jsonutils.QueryBoolean(query, "recursive", false)
objects, err := iBucket.GetIObjects(prefix, isRecursive)
if err != nil {
return nil, httperrors.NewInternalServerError("fail to get objects: %s", err)
}
retArray := jsonutils.NewArray()
for i := range objects {
retArray.Add(jsonutils.Marshal(cloudprovider.ICloudObject2BaseCloudObject(objects[i])))
}
return retArray, nil
}
func (bucket *SBucket) AllowPerformTempUrl(ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) bool {
return bucket.IsOwner(userCred)
}
func (bucket *SBucket) PerformTempUrl(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
method, _ := data.GetString("method")
key, _ := data.GetString("key")
expire, _ := data.Int("expire_seconds")
if len(method) == 0 {
method = "GET"
}
if len(key) == 0 {
return nil, httperrors.NewInputParameterError("missing key")
}
if expire == 0 {
expire = 60 // default 60 seconds
}
iBucket, err := bucket.GetIBucket()
if err != nil {
return nil, httperrors.NewInternalServerError("fail to find external bucket: %s", err)
}
tmpUrl, err := iBucket.GetTempUrl(method, key, time.Duration(expire)*time.Second)
if err != nil {
return nil, httperrors.NewInternalServerError("fail to generate temp url: %s", err)
}
ret := jsonutils.NewDict()
ret.Add(jsonutils.NewString(tmpUrl), "url")
return ret, nil
}
func (bucket *SBucket) AllowPerformMakedir(ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) bool {
return bucket.IsOwner(userCred)
}
func (bucket *SBucket) PerformMakedir(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
key, _ := data.GetString("key")
if key[len(key)-1] != '/' {
return nil, httperrors.NewInputParameterError("directory must ends with /")
}
err := s3utils.CheckValidObjectName(key)
if err != nil {
return nil, httperrors.NewInputParameterError("invalid key: %s", err)
}
iBucket, err := bucket.GetIBucket()
if err != nil {
return nil, httperrors.NewInternalServerError("fail to find external bucket: %s", err)
}
err = cloudprovider.Makedir(ctx, iBucket, key)
if err != nil {
return nil, httperrors.NewInternalServerError("fail to mkdir: %s", err)
}
return nil, nil
}
func (bucket *SBucket) AllowPerformDelete(ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) bool {
return bucket.IsOwner(userCred)
}
func (bucket *SBucket) PerformDelete(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
keys, _ := data.Get("keys")
if keys == nil {
return nil, httperrors.NewInputParameterError("missing keys")
}
keyStrs := keys.(*jsonutils.JSONArray).GetStringArray()
if len(keyStrs) == 0 {
return nil, httperrors.NewInputParameterError("empty keys")
}
iBucket, err := bucket.GetIBucket()
if err != nil {
return nil, httperrors.NewInternalServerError("fail to find external bucket: %s", err)
}
ok := jsonutils.NewDict()
results := modules.BatchDo(keyStrs, func(key string) (jsonutils.JSONObject, error) {
err := iBucket.DeleteObject(ctx, key)
if err != nil {
return nil, err
} else {
return ok, nil
}
})
return modules.SubmitResults2JSON(results), nil
}
func (bucket *SBucket) AllowPerformUpload(ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) bool {
return bucket.IsOwner(userCred)
}
func (bucket *SBucket) PerformUpload(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
appParams := appsrv.AppContextGetParams(ctx)
key := appParams.Request.Header.Get(api.BUCKET_UPLOAD_OBJECT_KEY_HEADER)
err := s3utils.CheckValidObjectName(key)
if err != nil {
return nil, httperrors.NewInputParameterError("invalid object key: %s", err)
}
iBucket, err := bucket.GetIBucket()
if err != nil {
return nil, httperrors.NewInternalServerError("fail to find external bucket: %s", err)
}
contType := appParams.Request.Header.Get("Content-Type")
storageClass := appParams.Request.Header.Get(api.BUCKET_UPLOAD_OBJECT_STORAGECLASS_HEADER)
err = iBucket.PutObject(ctx, key, appParams.Request.Body.(io.ReadSeeker), contType, storageClass)
if err != nil {
return nil, httperrors.NewInternalServerError("put object error %s", err)
}
return nil, nil
}

View File

@@ -82,12 +82,17 @@ func (self *SGuest) GetDetailsVnc(ctx context.Context, userCred mcclient.TokenCr
func (self *SGuest) AllowPerformMonitor(ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject) bool {
data jsonutils.JSONObject,
) bool {
return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "monitor")
}
func (self *SGuest) PerformMonitor(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject,
data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
func (self *SGuest) PerformMonitor(
ctx context.Context,
userCred mcclient.TokenCredential,
query jsonutils.JSONObject,
data jsonutils.JSONObject,
) (jsonutils.JSONObject, error) {
if utils.IsInStringArray(self.Status, []string{api.VM_RUNNING, api.VM_BLOCK_STREAM}) {
cmd, err := data.GetString("command")
if err != nil {

View File

@@ -46,6 +46,7 @@ func init() {
"natdtables",
),
}
NatDTableManager.SetVirtualObject(NatDTableManager)
}
type SNatDEntry struct {

View File

@@ -46,6 +46,7 @@ func init() {
"natgateways",
),
}
NatGatewayManager.SetVirtualObject(NatGatewayManager)
}
type SNatGateway struct {

View File

@@ -46,6 +46,7 @@ func init() {
"natstables",
),
}
NatSTableManager.SetVirtualObject(NatSTableManager)
}
type SNatSEntry struct {

View File

@@ -62,6 +62,7 @@ func init() {
"snapshotpolicies",
),
}
SnapshotPolicyManager.SetVirtualObject(SnapshotPolicyManager)
}
func (manager *SSnapshotPolicyManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {

View File

@@ -122,6 +122,8 @@ type SImage struct {
}
func (manager *SImageManager) CustomizeHandlerInfo(info *appsrv.SHandlerInfo) {
manager.SVirtualResourceBaseManager.CustomizeHandlerInfo(info)
switch info.GetName(nil) {
case "get_details", "create", "update":
info.SetProcessTimeout(time.Minute * 120).SetWorkerManager(imgStreamingWorkerMan)

View File

@@ -14,17 +14,53 @@
package modules
import (
"fmt"
"io"
"net/http"
"github.com/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/httputils"
)
type SBucketManager struct {
ResourceManager
}
func (manager *SBucketManager) Upload(s *mcclient.ClientSession, bucketId string, key string, body io.Reader, contType string, storageClass string) error {
method := httputils.POST
path := fmt.Sprintf("/%s/%s/upload", manager.URLPath(), bucketId)
headers := http.Header{}
headers.Set(api.BUCKET_UPLOAD_OBJECT_KEY_HEADER, key)
if len(contType) > 0 {
headers.Set("Content-Type", contType)
}
if len(storageClass) > 0 {
headers.Set(api.BUCKET_UPLOAD_OBJECT_STORAGECLASS_HEADER, storageClass)
}
_, err := manager.rawRequest(s, method, path, headers, body)
if err != nil {
return errors.Wrap(err, "rawRequest")
}
return nil
}
var (
Buckets ResourceManager
Buckets SBucketManager
)
func init() {
Buckets = NewComputeManager("bucket", "buckets",
[]string{"ID", "Name", "Storage_Class",
"Status", "location", "acl",
"region",
},
[]string{})
Buckets = SBucketManager{
NewComputeManager("bucket", "buckets",
[]string{"ID", "Name", "Storage_Class",
"Status", "location", "acl",
"region", "manager_id",
},
[]string{}),
}
registerCompute(&Buckets)
}

View File

@@ -22,7 +22,7 @@ func (cli *SNoObjectStorageRegion) IBucketExist(name string) (bool, error) {
return false, cloudprovider.ErrNotSupported
}
func (cli *SNoObjectStorageRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) {
func (cli *SNoObjectStorageRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) {
return nil, cloudprovider.ErrNotSupported
}

View File

@@ -1,10 +1,16 @@
package objectstore
import (
"context"
"fmt"
"io"
"path"
"time"
"github.com/minio/minio-go"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -58,3 +64,71 @@ func (bucket *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
},
}
}
func (bucket *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) {
isRecursive := true
if delimiter == "/" {
isRecursive = false
}
result := cloudprovider.SListObjectResult{}
var err error
result.Objects, err = bucket.GetIObjects(prefix, isRecursive)
return result, err
}
func (bucket *SBucket) GetIObjects(prefix string, isRecursive bool) ([]cloudprovider.ICloudObject, error) {
doneCh := make(chan struct{})
defer close(doneCh)
ret := make([]cloudprovider.ICloudObject, 0)
objectCh := bucket.client.client.ListObjects(bucket.Name, prefix, isRecursive, doneCh)
for object := range objectCh {
if object.Err != nil {
return nil, errors.Wrap(object.Err, "ListObjects")
}
obj := &SObject{
bucket: bucket,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
StorageClass: object.StorageClass,
Key: object.Key,
SizeBytes: object.Size,
ETag: object.ETag,
LastModified: object.LastModified,
ContentType: object.ContentType,
},
}
ret = append(ret, obj)
}
return ret, nil
}
func (bucket *SBucket) PutObject(ctx context.Context, key string, input io.ReadSeeker, contType string, storageClass string) error {
opts := minio.PutObjectOptions{}
if len(contType) > 0 {
opts.ContentType = contType
}
if len(storageClass) > 0 {
opts.StorageClass = storageClass
}
_, err := bucket.client.client.PutObjectWithContext(ctx, bucket.Name, key, input, -1, opts)
return err
}
func (bucket *SBucket) DeleteObject(ctx context.Context, key string) error {
err := bucket.client.client.RemoveObject(bucket.Name, key)
if err != nil {
return errors.Wrap(err, "RemoveObject")
}
return nil
}
func (bucket *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) {
if method != "GET" && method != "PUT" && method != "DELETE" {
return "", errors.Error("unsupported method")
}
url, err := bucket.client.client.Presign(method, bucket.Name, key, expire, nil)
if err != nil {
return "", errors.Wrap(err, "Presign")
}
return url.String(), nil
}

View File

@@ -0,0 +1,15 @@
package objectstore
import (
"yunion.io/x/onecloud/pkg/cloudprovider"
)
type SObject struct {
bucket *SBucket
cloudprovider.SBaseCloudObject
}
func (o *SObject) GetIBucket() cloudprovider.ICloudBucket {
return o.bucket
}

View File

@@ -9,10 +9,12 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/util/secrules"
"time"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudcommon/object"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/multicloud"
"yunion.io/x/onecloud/pkg/util/httputils"
)
type SObjectStoreClient struct {
@@ -54,6 +56,9 @@ func NewObjectStoreClient(providerId string, providerName string, endpoint strin
return nil, errors.Wrap(err, "minio.New")
}
tr := httputils.GetTransport(true, time.Second*5)
cli.SetCustomTransport(tr)
client.client = cli
return &client, nil
@@ -319,6 +324,6 @@ func (cli *SObjectStoreClient) IBucketExist(name string) (bool, error) {
return exist, nil
}
func (cli *SObjectStoreClient) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) {
return cloudprovider.GetIBucketByName(cli, name)
func (cli *SObjectStoreClient) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) {
return cloudprovider.GetIBucketById(cli, name)
}

View File

@@ -0,0 +1,190 @@
package objectstore
import (
"context"
"fmt"
"io"
"os"
"time"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/util/printutils"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
func S3Shell() {
type BucketListOptions struct {
}
shellutils.R(&BucketListOptions{}, "bucket-list", "List all bucket", func(cli cloudprovider.ICloudRegion, args *BucketListOptions) error {
buckets, err := cli.GetIBuckets()
if err != nil {
return err
}
printutils.PrintGetterList(buckets, nil)
return nil
})
type BucketCreateOptions struct {
NAME string `help:"name of bucket to create"`
}
shellutils.R(&BucketCreateOptions{}, "bucket-create", "Create bucket", func(cli cloudprovider.ICloudRegion, args *BucketCreateOptions) error {
err := cli.CreateIBucket(args.NAME, "", "")
if err != nil {
return err
}
return nil
})
type BucketDeleteOptions struct {
NAME string `help:"name of bucket to delete"`
}
shellutils.R(&BucketDeleteOptions{}, "bucket-delete", "Delete bucket", func(cli cloudprovider.ICloudRegion, args *BucketDeleteOptions) error {
err := cli.DeleteIBucket(args.NAME)
if err != nil {
return err
}
return nil
})
type BucketObjectsOptions struct {
BUCKET string `help:"name of bucket to list objects"`
Prefix string `help:"prefix"`
Marker string `help:"marker"`
Demiliter string `help:"delimiter"`
Max int `help:"Max count"`
}
shellutils.R(&BucketObjectsOptions{}, "bucket-object", "List objects in a bucket", func(cli cloudprovider.ICloudRegion, args *BucketObjectsOptions) error {
bucket, err := cli.GetIBucketById(args.BUCKET)
if err != nil {
return err
}
result, err := bucket.ListObjects(args.Prefix, args.Marker, args.Demiliter, args.Max)
if err != nil {
return err
}
if result.IsTruncated {
fmt.Println("NextMarker: %s IsTruncated: %v", result.NextMarker, result.IsTruncated)
}
fmt.Println("Common prefixes:")
printutils.PrintGetterList(result.CommonPrefixes, []string{"key", "size_bytes"})
fmt.Println("Objects:")
printutils.PrintGetterList(result.Objects, []string{"key", "size_bytes"})
return nil
})
type BucketListObjectsOptions struct {
BUCKET string `help:"name of bucket to list objects"`
Prefix string `help:"prefix"`
}
shellutils.R(&BucketListObjectsOptions{}, "bucket-list-object", "List objects in a bucket", func(cli cloudprovider.ICloudRegion, args *BucketListObjectsOptions) error {
bucket, err := cli.GetIBucketById(args.BUCKET)
if err != nil {
return err
}
objects, err := bucket.GetIObjects(args.Prefix, true)
if err != nil {
return err
}
printutils.PrintGetterList(objects, []string{"key", "size_bytes"})
return nil
})
shellutils.R(&BucketListObjectsOptions{}, "bucket-dir-object", "List objects in a bucket like directory", func(cli cloudprovider.ICloudRegion, args *BucketListObjectsOptions) error {
bucket, err := cli.GetIBucketById(args.BUCKET)
if err != nil {
return err
}
objects, err := bucket.GetIObjects(args.Prefix, false)
if err != nil {
return err
}
printutils.PrintGetterList(objects, []string{"key", "size_bytes"})
return nil
})
type BucketMakrdirOptions struct {
BUCKET string `help:"name of bucket to put object"`
DIR string `help:"dir to make"`
}
shellutils.R(&BucketMakrdirOptions{}, "bucket-mkdir", "Mkdir in a bucket", func(cli cloudprovider.ICloudRegion, args *BucketMakrdirOptions) error {
bucket, err := cli.GetIBucketById(args.BUCKET)
if err != nil {
return err
}
err = cloudprovider.Makedir(context.Background(), bucket, args.DIR)
if err != nil {
return err
}
fmt.Printf("Mkdir success\n")
return nil
})
type BucketPutObjectOptions struct {
BUCKET string `help:"name of bucket to put object"`
KEY string `help:"key of object"`
Path string `help:"Path of file to upload"`
ContentType string `help:"content-type"`
StorageClass string `help:"storage class"`
}
shellutils.R(&BucketPutObjectOptions{}, "put-object", "Put object into a bucket", func(cli cloudprovider.ICloudRegion, args *BucketPutObjectOptions) error {
bucket, err := cli.GetIBucketById(args.BUCKET)
if err != nil {
return err
}
var input io.ReadSeeker
if len(args.Path) > 0 {
file, err := os.Open(args.Path)
if err != nil {
return err
}
defer file.Close()
input = file
} else {
input = os.Stdout
}
err = bucket.PutObject(context.Background(), args.KEY, input, args.ContentType, args.StorageClass)
if err != nil {
return err
}
fmt.Printf("Upload success\n")
return nil
})
type BucketDeleteObjectOptions struct {
BUCKET string `help:"name of bucket to put object"`
KEY string `help:"key of object"`
}
shellutils.R(&BucketDeleteObjectOptions{}, "delete-object", "Delete object from a bucket", func(cli cloudprovider.ICloudRegion, args *BucketDeleteObjectOptions) error {
bucket, err := cli.GetIBucketById(args.BUCKET)
if err != nil {
return err
}
err = bucket.DeleteObject(context.Background(), args.KEY)
if err != nil {
return err
}
fmt.Printf("Delete success\n")
return nil
})
type BucketTempUrlOption struct {
BUCKET string `help:"name of bucket to put object"`
METHOD string `help:"http method" choices:"GET|PUT|DELETE"`
KEY string `help:"key of object"`
Duration int `help:"duration in seconds" default:"60"`
}
shellutils.R(&BucketTempUrlOption{}, "temp-url", "generate temp url", func(cli cloudprovider.ICloudRegion, args *BucketTempUrlOption) error {
bucket, err := cli.GetIBucketById(args.BUCKET)
if err != nil {
return err
}
urlStr, err := bucket.GetTempUrl(args.METHOD, args.KEY, time.Duration(args.Duration)*time.Second)
if err != nil {
return err
}
fmt.Println(urlStr)
return nil
})
}

View File

@@ -1,63 +1,7 @@
package shell
import (
"fmt"
"yunion.io/x/onecloud/pkg/multicloud/objectstore"
"yunion.io/x/onecloud/pkg/util/shellutils"
)
import "yunion.io/x/onecloud/pkg/multicloud/objectstore"
func init() {
type BucketListOptions struct {
}
shellutils.R(&BucketListOptions{}, "bucket-list", "List all bucket", func(cli *objectstore.SObjectStoreClient, args *BucketListOptions) error {
buckets, err := cli.GetIBuckets()
if err != nil {
return err
}
printList(buckets, nil)
return nil
})
type BucketCreateOptions struct {
NAME string `help:"name of bucket to create"`
}
shellutils.R(&BucketCreateOptions{}, "bucket-create", "Create bucket", func(cli *objectstore.SObjectStoreClient, args *BucketCreateOptions) error {
err := cli.CreateIBucket(args.NAME, "", "")
if err != nil {
return err
}
return nil
})
type BucketDeleteOptions struct {
NAME string `help:"name of bucket to delete"`
}
shellutils.R(&BucketDeleteOptions{}, "bucket-delete", "Delete bucket", func(cli *objectstore.SObjectStoreClient, args *BucketDeleteOptions) error {
err := cli.DeleteIBucket(args.NAME)
if err != nil {
return err
}
return nil
})
type BucketPolicyOptions struct {
NAME string `help:"name of bucket to get policy"`
}
shellutils.R(&BucketPolicyOptions{}, "bucket-policy", "Get bucket policy", func(cli *objectstore.SObjectStoreClient, args *BucketPolicyOptions) error {
policy, err := cli.GetIBucketPolicy(args.NAME)
if err != nil {
return err
}
fmt.Println(policy)
return nil
})
shellutils.R(&BucketPolicyOptions{}, "bucket-lifecycle", "Get bucket lifecycle", func(cli *objectstore.SObjectStoreClient, args *BucketPolicyOptions) error {
lifecycle, err := cli.GetIBucketLiftcycle(args.NAME)
if err != nil {
return err
}
fmt.Println(lifecycle)
return nil
})
objectstore.S3Shell()
}

View File

@@ -1,9 +1,15 @@
package aliyun
import (
"context"
"fmt"
"io"
"time"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -61,3 +67,121 @@ func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
},
}
}
func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) {
result := cloudprovider.SListObjectResult{}
osscli, err := b.region.GetOssClient()
if err != nil {
return result, errors.Wrap(err, "GetOssClient")
}
bucket, err := osscli.Bucket(b.Name)
if err != nil {
return result, errors.Wrap(err, "Bucket")
}
opts := make([]oss.Option, 0)
if len(prefix) > 0 {
opts = append(opts, oss.Prefix(prefix))
}
if len(delimiter) > 0 {
opts = append(opts, oss.Delimiter(delimiter))
}
if len(marker) > 0 {
opts = append(opts, oss.Marker(marker))
}
if maxCount > 0 {
opts = append(opts, oss.MaxKeys(maxCount))
}
oResult, err := bucket.ListObjects(opts...)
if err != nil {
return result, errors.Wrap(err, "ListObjects")
}
result.Objects = make([]cloudprovider.ICloudObject, 0)
for _, object := range oResult.Objects {
obj := &SObject{
bucket: b,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
StorageClass: object.StorageClass,
Key: object.Key,
SizeBytes: object.Size,
ETag: object.ETag,
LastModified: object.LastModified,
ContentType: object.Type,
},
}
result.Objects = append(result.Objects, obj)
}
if oResult.CommonPrefixes != nil {
result.CommonPrefixes = make([]cloudprovider.ICloudObject, len(oResult.CommonPrefixes))
for i, commPrefix := range oResult.CommonPrefixes {
result.CommonPrefixes[i] = &SObject{
bucket: b,
SBaseCloudObject: cloudprovider.SBaseCloudObject{Key: commPrefix},
}
}
}
result.IsTruncated = oResult.IsTruncated
result.NextMarker = oResult.NextMarker
return result, nil
}
func (b *SBucket) GetIObjects(prefix string, isRecursive bool) ([]cloudprovider.ICloudObject, error) {
return cloudprovider.GetIObjects(b, prefix, isRecursive)
}
func (b *SBucket) PutObject(ctx context.Context, key string, input io.ReadSeeker, contType string, storageClassStr string) error {
osscli, err := b.region.GetOssClient()
if err != nil {
return errors.Wrap(err, "GetOssClient")
}
bucket, err := osscli.Bucket(b.Name)
if err != nil {
return errors.Wrap(err, "Bucket")
}
opts := make([]oss.Option, 0)
if len(contType) > 0 {
opts = append(opts, oss.ContentType(contType))
}
if len(storageClassStr) > 0 {
storageClass, err := str2StorageClass(storageClassStr)
if err != nil {
return errors.Wrap(err, "str2StorageClass")
}
opts = append(opts, oss.ObjectStorageClass(storageClass))
}
return bucket.PutObject(key, input, opts...)
}
func (b *SBucket) DeleteObject(ctx context.Context, key string) error {
osscli, err := b.region.GetOssClient()
if err != nil {
return errors.Wrap(err, "GetOssClient")
}
bucket, err := osscli.Bucket(b.Name)
if err != nil {
return errors.Wrap(err, "Bucket")
}
err = bucket.DeleteObject(key)
if err != nil {
return errors.Wrap(err, "DeleteObject")
}
return nil
}
func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) {
if method != "GET" && method != "PUT" && method != "DELETE" {
return "", errors.Error("unsupported method")
}
osscli, err := b.region.GetOssClient()
if err != nil {
return "", errors.Wrap(err, "GetOssClient")
}
bucket, err := osscli.Bucket(b.Name)
if err != nil {
return "", errors.Wrap(err, "Bucket")
}
urlStr, err := bucket.SignURL(key, oss.HTTPMethod(method), int64(expire/time.Second))
if err != nil {
return "", errors.Wrap(err, "SignURL")
}
return urlStr, nil
}

View File

@@ -0,0 +1,15 @@
package aliyun
import (
"yunion.io/x/onecloud/pkg/cloudprovider"
)
type SObject struct {
bucket *SBucket
cloudprovider.SBaseCloudObject
}
func (o *SObject) GetIBucket() cloudprovider.ICloudBucket {
return o.bucket
}

View File

@@ -953,6 +953,34 @@ func (region *SRegion) GetIBuckets() ([]cloudprovider.ICloudBucket, error) {
return ret, nil
}
func str2StorageClass(storageClassStr string) (oss.StorageClassType, error) {
storageClass := oss.StorageStandard
if strings.EqualFold(storageClassStr, string(oss.StorageStandard)) {
//
} else if strings.EqualFold(storageClassStr, string(oss.StorageIA)) {
storageClass = oss.StorageIA
} else if strings.EqualFold(storageClassStr, string(oss.StorageArchive)) {
storageClass = oss.StorageArchive
} else {
return storageClass, errors.Error("not supported storageClass")
}
return storageClass, nil
}
func str2Acl(aclStr string) (oss.ACLType, error) {
acl := oss.ACLPrivate
if strings.EqualFold(aclStr, string(oss.ACLPrivate)) {
// private, default
} else if strings.EqualFold(aclStr, string(oss.ACLPublicRead)) {
acl = oss.ACLPublicRead
} else if strings.EqualFold(aclStr, string(oss.ACLPublicReadWrite)) {
acl = oss.ACLPublicReadWrite
} else {
return acl, errors.Error("not supported acl")
}
return acl, nil
}
func (region *SRegion) CreateIBucket(name string, storageClassStr string, aclStr string) error {
osscli, err := region.GetOssClient()
if err != nil {
@@ -960,32 +988,18 @@ func (region *SRegion) CreateIBucket(name string, storageClassStr string, aclStr
}
opts := make([]oss.Option, 0)
if len(storageClassStr) > 0 {
storageClass := oss.StorageStandard
if strings.EqualFold(storageClassStr, string(oss.StorageStandard)) {
//
} else if strings.EqualFold(storageClassStr, string(oss.StorageIA)) {
storageClass = oss.StorageIA
} else if strings.EqualFold(storageClassStr, string(oss.StorageArchive)) {
storageClass = oss.StorageArchive
} else {
return errors.Error("not supported storageClass")
storageClass, err := str2StorageClass(storageClassStr)
if err != nil {
return err
}
opt := oss.StorageClass(storageClass)
opts = append(opts, opt)
opts = append(opts, oss.StorageClass(storageClass))
}
if len(aclStr) > 0 {
acl := oss.ACLPrivate
if strings.EqualFold(aclStr, string(oss.ACLPrivate)) {
// private, default
} else if strings.EqualFold(aclStr, string(oss.ACLPublicRead)) {
acl = oss.ACLPublicRead
} else if strings.EqualFold(aclStr, string(oss.ACLPublicReadWrite)) {
acl = oss.ACLPublicReadWrite
} else {
return errors.Error("not supported acl")
acl, err := str2Acl(aclStr)
if err != nil {
return err
}
opt := oss.ACL(acl)
opts = append(opts, opt)
opts = append(opts, oss.ACL(acl))
}
err = osscli.CreateBucket(name, opts...)
if err != nil {
@@ -1031,7 +1045,7 @@ func (region *SRegion) IBucketExist(name string) (bool, error) {
return exist, nil
}
func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) {
func (region *SRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) {
osscli, err := region.GetOssClient()
if err != nil {
return nil, errors.Wrap(err, "region.GetOssClient")

View File

@@ -0,0 +1,7 @@
package shell
import "yunion.io/x/onecloud/pkg/multicloud/objectstore"
func init() {
objectstore.S3Shell()
}

View File

@@ -1,9 +1,16 @@
package aws
import (
"context"
"fmt"
"io"
"time"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/s3"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -59,3 +66,129 @@ func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
},
}
}
func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) {
result := cloudprovider.SListObjectResult{}
s3cli, err := b.region.GetS3Client()
if err != nil {
return result, errors.Wrap(err, "GetS3Client")
}
input := &s3.ListObjectsInput{}
input.SetBucket(b.Name)
if len(prefix) > 0 {
input.SetPrefix(prefix)
}
if len(marker) > 0 {
input.SetMarker(marker)
}
if len(delimiter) > 0 {
input.SetDelimiter(delimiter)
}
if maxCount > 0 {
input.SetMaxKeys(int64(maxCount))
}
oResult, err := s3cli.ListObjects(input)
if err != nil {
return result, errors.Wrap(err, "ListObjects")
}
result.Objects = make([]cloudprovider.ICloudObject, 0)
for _, object := range oResult.Contents {
obj := &SObject{
bucket: b,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
StorageClass: *object.StorageClass,
Key: *object.Key,
SizeBytes: *object.Size,
ETag: *object.ETag,
LastModified: *object.LastModified,
ContentType: "",
},
}
result.Objects = append(result.Objects, obj)
}
if oResult.CommonPrefixes != nil {
result.CommonPrefixes = make([]cloudprovider.ICloudObject, len(oResult.CommonPrefixes))
for i, commPrefix := range oResult.CommonPrefixes {
result.CommonPrefixes[i] = &SObject{
bucket: b,
SBaseCloudObject: cloudprovider.SBaseCloudObject{Key: *commPrefix.Prefix},
}
}
}
if oResult.IsTruncated != nil {
result.IsTruncated = *oResult.IsTruncated
}
if oResult.NextMarker != nil {
result.NextMarker = *oResult.NextMarker
}
return result, nil
}
func (b *SBucket) GetIObjects(prefix string, isRecursive bool) ([]cloudprovider.ICloudObject, error) {
return cloudprovider.GetIObjects(b, prefix, isRecursive)
}
func (b *SBucket) PutObject(ctx context.Context, key string, reader io.ReadSeeker, contType string, storageClassStr string) error {
s3cli, err := b.region.GetS3Client()
if err != nil {
return errors.Wrap(err, "GetS3Client")
}
input := &s3.PutObjectInput{}
input.SetBucket(b.Name)
input.SetKey(key)
input.SetBody(reader)
if len(storageClassStr) > 0 {
input.SetStorageClass(storageClassStr)
}
if len(contType) > 0 {
input.SetContentType(contType)
}
_, err = s3cli.PutObjectWithContext(ctx, input)
if err != nil {
return errors.Wrap(err, "PutObjectWithContext")
}
return nil
}
func (b *SBucket) DeleteObject(ctx context.Context, key string) error {
s3cli, err := b.region.GetS3Client()
if err != nil {
return errors.Wrap(err, "GetS3Client")
}
input := &s3.DeleteObjectInput{}
input.SetBucket(b.Name)
input.SetKey(key)
_, err = s3cli.DeleteObjectWithContext(ctx, input)
if err != nil {
return errors.Wrap(err, "DeleteObject")
}
return nil
}
func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) {
s3cli, err := b.region.GetS3Client()
if err != nil {
return "", errors.Wrap(err, "GetS3Client")
}
var request *request.Request
switch method {
case "GET":
input := &s3.GetObjectInput{}
input.SetBucket(b.Name)
input.SetKey(key)
request, _ = s3cli.GetObjectRequest(input)
case "PUT":
input := &s3.PutObjectInput{}
input.SetBucket(b.Name)
input.SetKey(key)
request, _ = s3cli.PutObjectRequest(input)
case "DELETE":
input := &s3.DeleteObjectInput{}
input.SetBucket(b.Name)
input.SetKey(key)
request, _ = s3cli.DeleteObjectRequest(input)
default:
return "", errors.Error("unsupported method")
}
return request.Presign(expire)
}

View File

@@ -610,8 +610,8 @@ func (region *SRegion) IBucketExist(name string) (bool, error) {
return true, nil
}
func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) {
return cloudprovider.GetIBucketByName(region, name)
func (region *SRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) {
return cloudprovider.GetIBucketById(region, name)
}
func (region *SRegion) getBaseEndpoint() string {

13
pkg/util/aws/s3object.go Normal file
View File

@@ -0,0 +1,13 @@
package aws
import "yunion.io/x/onecloud/pkg/cloudprovider"
type SObject struct {
bucket *SBucket
cloudprovider.SBaseCloudObject
}
func (o *SObject) GetIBucket() cloudprovider.ICloudBucket {
return o.bucket
}

View File

@@ -0,0 +1,7 @@
package shell
import "yunion.io/x/onecloud/pkg/multicloud/objectstore"
func init() {
objectstore.S3Shell()
}

View File

@@ -0,0 +1,13 @@
package azure
import "yunion.io/x/onecloud/pkg/cloudprovider"
type SObject struct {
container *SContainer
cloudprovider.SBaseCloudObject
}
func (o *SObject) GetIBucket() cloudprovider.ICloudBucket {
return o.container.storageaccount
}

View File

@@ -22,6 +22,7 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"github.com/Azure/azure-sdk-for-go/storage"
billing_api "yunion.io/x/onecloud/pkg/apis/billing"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudprovider"
@@ -67,7 +68,7 @@ func (self *SRegion) GetStorageAccountDisksWithSnapshots(storageaccount SStorage
}
for _, container := range containers {
if container.Name == "vhds" {
files, err := container.ListFiles()
files, err := container.ListAllFiles(&storage.IncludeBlobDataset{Snapshots: true, Metadata: true})
if err != nil {
log.Errorf("List storage %s container %s files error: %v", storageaccount.Name, container.Name, err)
return nil, nil, err

View File

@@ -651,6 +651,6 @@ func (region *SRegion) IBucketExist(name string) (bool, error) {
return region.checkStorageAccountNameExist(name)
}
func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) {
return cloudprovider.GetIBucketByName(region, name)
func (region *SRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) {
return cloudprovider.GetIBucketById(region, name)
}

View File

@@ -1,55 +1,9 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/azure"
"yunion.io/x/onecloud/pkg/util/printutils"
"yunion.io/x/onecloud/pkg/util/shellutils"
"yunion.io/x/onecloud/pkg/multicloud/objectstore"
)
func init() {
type BucketListOptions struct {
}
shellutils.R(&BucketListOptions{}, "bucket-list", "List buckets", func(cli *azure.SRegion, args *BucketListOptions) error {
buckets, err := cli.GetIBuckets()
if err != nil {
return err
}
printutils.PrintGetterList(buckets, nil)
return nil
})
type BucketCreateOptions struct {
BUCKET string `help:"bucket name"`
STORAGECLASS string `help:"storage class"`
}
shellutils.R(&BucketCreateOptions{}, "bucket-create", "Create bucket", func(cli *azure.SRegion, args *BucketCreateOptions) error {
err := cli.CreateIBucket(args.BUCKET, args.STORAGECLASS, "")
if err != nil {
return err
}
return nil
})
type BucketDeleteOptions struct {
BUCKET string `help:"bucket name"`
}
shellutils.R(&BucketDeleteOptions{}, "bucket-delete", "Delete a bucket", func(cli *azure.SRegion, args *BucketDeleteOptions) error {
err := cli.DeleteIBucket(args.BUCKET)
if err != nil {
return err
}
return nil
})
type BucketShowOptions struct {
BUCKET string `help:"bucket name"`
}
shellutils.R(&BucketShowOptions{}, "bucket-show", "Show a bucket", func(cli *azure.SRegion, args *BucketShowOptions) error {
bucket, err := cli.GetIBucketByName(args.BUCKET)
if err != nil {
return err
}
printObject(bucket)
return nil
})
objectstore.S3Shell()
}

View File

@@ -98,7 +98,7 @@ func init() {
if err != nil {
return err
}
blobs, err := container.ListFiles()
blobs, err := container.ListAllFiles(nil)
if err != nil {
return err
}

View File

@@ -15,7 +15,9 @@
package azure
import (
"context"
"fmt"
"io"
"math/rand"
"path"
"strings"
@@ -27,9 +29,9 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/pkg/utils"
)
type SContainer struct {
@@ -75,7 +77,8 @@ type AccountProperties struct {
}
type SStorageAccount struct {
region *SRegion
region *SRegion
accountKey string
Sku SSku `json:"sku,omitempty"`
Kind string `json:"kind,omitempty"`
@@ -416,18 +419,52 @@ func (self *SStorageAccount) GetContainer(name string) (*SContainer, error) {
return nil, cloudprovider.ErrNotFound
}
func (self *SContainer) ListFiles() ([]storage.Blob, error) {
func (self *SContainer) ListAllFiles(include *storage.IncludeBlobDataset) ([]storage.Blob, error) {
blobs := make([]storage.Blob, 0)
var marker string
for {
result, err := self.ListFiles("", marker, "", 5000, include)
if err != nil {
return nil, errors.Wrap(err, "ListFiles")
}
if len(result.Blobs) > 0 {
blobs = append(blobs, result.Blobs...)
}
if len(result.NextMarker) == 0 {
break
} else {
marker = result.NextMarker
}
}
return blobs, nil
}
func (self *SContainer) ListFiles(prefix string, marker string, delimiter string, maxCount int, include *storage.IncludeBlobDataset) (storage.BlobListResponse, error) {
var result storage.BlobListResponse
storageaccount := self.storageaccount
client, err := storage.NewBasicClientOnSovereignCloud(storageaccount.Name, storageaccount.accountKey, storageaccount.region.client.env)
if err != nil {
return nil, err
return result, err
}
blobService := client.GetBlobService()
result, err := blobService.GetContainerReference(self.Name).ListBlobs(storage.ListBlobsParameters{Include: &storage.IncludeBlobDataset{Snapshots: true, Metadata: true}})
if err != nil {
return nil, err
params := storage.ListBlobsParameters{Include: include}
if len(prefix) > 0 {
params.Prefix = prefix
}
return result.Blobs, nil
if len(marker) > 0 {
params.Marker = marker
}
if len(delimiter) > 0 {
params.Delimiter = delimiter
}
if maxCount > 0 {
params.MaxResults = uint(maxCount)
}
result, err = blobService.GetContainerReference(self.Name).ListBlobs(params)
if err != nil {
return result, err
}
return result, nil
}
func (self *SContainer) getClient() (storage.Client, error) {
@@ -471,6 +508,51 @@ func (self *SContainer) CopySnapshot(snapshotId, fileName string) (*storage.Blob
return blobRef, blobRef.GetProperties(&storage.GetBlobPropertiesOptions{})
}
func (self *SContainer) UploadStream(key string, reader io.ReadSeeker, contType string) error {
storageaccount := self.storageaccount
client, err := storage.NewBasicClientOnSovereignCloud(storageaccount.Name, storageaccount.accountKey, storageaccount.region.client.env)
if err != nil {
return errors.Wrap(err, "NewBasicClientOnSovereignCloud")
}
blobService := client.GetBlobService()
containerRef := blobService.GetContainerReference(self.Name)
blobRef := containerRef.GetBlobReference(key)
blobRef.Properties.BlobType = storage.BlobTypeBlock
blobRef.Properties.ContentType = contType
return blobRef.CreateBlockBlobFromReader(reader, &storage.PutBlobOptions{})
}
func (self *SContainer) SignUrl(method string, key string, expire time.Duration) (string, error) {
storageaccount := self.storageaccount
client, err := storage.NewBasicClientOnSovereignCloud(storageaccount.Name, storageaccount.accountKey, storageaccount.region.client.env)
if err != nil {
return "", errors.Wrap(err, "NewBasicClientOnSovereignCloud")
}
blobService := client.GetBlobService()
containerRef := blobService.GetContainerReference(self.Name)
sas := storage.ContainerSASOptions{}
sas.Start = time.Now()
sas.Expiry = sas.Start.Add(expire)
sas.UseHTTPS = true
sas.Identifier = key
switch method {
case "GET":
sas.Read = true
case "PUT":
sas.Read = true
sas.Add = true
sas.Create = true
sas.Write = true
case "DELETE":
sas.Read = true
sas.Write = true
sas.Delete = true
default:
return "", errors.Error("unsupport method")
}
return containerRef.GetSASURI(sas)
}
func (self *SContainer) UploadFile(filePath string) (string, error) {
storageaccount := self.storageaccount
client, err := storage.NewBasicClientOnSovereignCloud(storageaccount.Name, storageaccount.accountKey, storageaccount.region.client.env)
@@ -525,11 +607,19 @@ func (self *SContainer) UploadFile(filePath string) (string, error) {
}
func (self *SStorageAccount) UploadFile(containerName string, filePath string) (string, error) {
container, err := self.getOrCreateContainer(containerName, true)
if err != nil {
return "", errors.Wrap(err, "getOrCreateContainer")
}
return container.UploadFile(filePath)
}
func (self *SStorageAccount) getOrCreateContainer(containerName string, create bool) (*SContainer, error) {
containers, err := self.GetContainers()
if err != nil {
return "", err
return nil, errors.Wrap(err, "GetContainers")
}
container := &SContainer{}
var container *SContainer
find := false
for i := 0; i < len(containers); i++ {
if containers[i].Name == containerName {
@@ -539,12 +629,23 @@ func (self *SStorageAccount) UploadFile(containerName string, filePath string) (
}
}
if !find {
if !create {
return nil, cloudprovider.ErrNotFound
}
container, err = self.CreateContainer(containerName)
if err != nil {
return "", err
return nil, errors.Wrap(err, "CreateContainer")
}
}
return container.UploadFile(filePath)
return container, nil
}
func (self *SStorageAccount) UploadStream(containerName string, key string, reader io.ReadSeeker, contType string) error {
container, err := self.getOrCreateContainer(containerName, true)
if err != nil {
return errors.Wrap(err, "getOrCreateContainer")
}
return container.UploadStream(key, reader, contType)
}
func (b *SStorageAccount) GetProjectId() string {
@@ -624,3 +725,178 @@ func (b *SStorageAccount) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
}
return primary
}
func (b *SStorageAccount) GetIObjects(prefix string, isRecursive bool) ([]cloudprovider.ICloudObject, error) {
return cloudprovider.GetIObjects(b, prefix, isRecursive)
}
func (b *SStorageAccount) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) {
result := cloudprovider.SListObjectResult{}
containers, err := b.GetContainers()
if err != nil {
return result, errors.Wrap(err, "GetContainers")
}
result.Objects = make([]cloudprovider.ICloudObject, 0)
result.CommonPrefixes = make([]cloudprovider.ICloudObject, 0)
for i := 0; i < len(containers); i += 1 {
container := containers[i]
matchLen := len(container.Name)
if matchLen > len(prefix) {
matchLen = len(prefix)
}
var subMarker string
if len(marker) < len(container.Name) {
if marker > container.Name {
continue
}
subMarker = ""
} else {
containerMarker := marker[:len(container.Name)]
if containerMarker > container.Name {
continue
}
subMarker = marker[len(container.Name)+1:]
}
if marker > container.Name {
continue
}
if maxCount <= 0 {
break
}
// container name matches prefix
if matchLen == 0 || container.Name[:matchLen] == prefix[:matchLen] {
if delimiter == "/" && (len(prefix) == 0 || prefix == container.Name+delimiter) {
// populate CommonPrefixes
o := &SObject{
container: &container,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
Key: container.Name + "/",
},
}
result.CommonPrefixes = append(result.CommonPrefixes, o)
maxCount -= 1
} else if len(prefix) <= len(container.Name)+1 {
// returns contain names only
o := &SObject{
container: &container,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
Key: container.Name + "/",
},
}
result.Objects = append(result.Objects, o)
maxCount -= 1
}
if delimiter == "" || len(prefix) >= len(container.Name)+1 {
subPrefix := ""
if len(prefix) >= len(container.Name) {
subPrefix = prefix[len(container.Name)+1:]
}
oResult, err := container.ListFiles(subPrefix, subMarker, delimiter, maxCount, nil)
if err != nil {
return result, errors.Wrap(err, "ListFiles")
}
for i := range oResult.Blobs {
blob := oResult.Blobs[i]
o := &SObject{
container: &container,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
Key: container.Name + "/" + blob.Name,
SizeBytes: blob.Properties.ContentLength,
StorageClass: "",
ETag: blob.Properties.Etag,
LastModified: time.Time(blob.Properties.LastModified),
ContentType: blob.Properties.ContentType,
},
}
result.Objects = append(result.Objects, o)
maxCount -= 1
if maxCount == 0 {
break
}
result.NextMarker = blob.Name
}
for i := range oResult.BlobPrefixes {
o := &SObject{
container: &container,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
Key: container.Name + "/" + oResult.BlobPrefixes[i],
},
}
result.CommonPrefixes = append(result.CommonPrefixes, o)
maxCount -= 1
}
if len(oResult.NextMarker) > 0 {
result.NextMarker = container.Name + "/" + oResult.NextMarker
result.IsTruncated = true
break
}
}
}
}
return result, nil
}
func splitKey(key string) (string, string, error) {
slashPos := strings.IndexByte(key, '/')
if slashPos <= 0 {
return "", "", errors.Error("cannot put object to root")
}
containerName := key[:slashPos]
key = key[slashPos+1:]
if len(key) == 0 {
return "", "", errors.Error("empty blob path")
}
return containerName, key, nil
}
func (b *SStorageAccount) PutObject(ctx context.Context, key string, reader io.ReadSeeker, contType string, storageClassStr string) error {
containerName, blob, err := splitKey(key)
if err != nil {
return errors.Wrap(err, "splitKey")
}
err = b.UploadStream(containerName, blob, reader, contType)
if err != nil {
return errors.Wrap(err, "UploadStream")
}
return nil
}
func (b *SStorageAccount) DeleteObject(ctx context.Context, key string) error {
containerName, blob, err := splitKey(key)
if err != nil {
return errors.Wrap(err, "splitKey")
}
client, err := storage.NewBasicClientOnSovereignCloud(b.Name, b.accountKey, b.region.client.env)
if err != nil {
return errors.Wrap(err, "storage.NewBasicClientOnSovereignCloud")
}
blobService := client.GetBlobService()
containerRef := blobService.GetContainerReference(containerName)
if len(blob) > 0 {
// delete object
blobRef := containerRef.GetBlobReference(blob)
_, err = blobRef.DeleteIfExists(nil)
if err != nil {
return errors.Wrap(err, "blobRef.DeleteIfExists")
}
} else {
// delete container
_, err = containerRef.DeleteIfExists(nil)
if err != nil {
return errors.Wrap(err, "containerRef.DeleteIfExists")
}
}
return nil
}
func (b *SStorageAccount) GetTempUrl(method string, key string, expire time.Duration) (string, error) {
containerName, blob, err := splitKey(key)
if err != nil {
return "", errors.Wrap(err, "splitKey")
}
container, err := b.getOrCreateContainer(containerName, false)
if err != nil {
return "", errors.Wrap(err, "getOrCreateContainer")
}
return container.SignUrl(method, blob, expire)
}

View File

@@ -127,8 +127,8 @@ func GetAddrPort(urlStr string) (string, int, error) {
}
}
func GetClient(insecure bool, timeout time.Duration) *http.Client {
tr := &http.Transport{
func GetTransport(insecure bool, timeout time.Duration) *http.Transport {
return &http.Transport{
DialContext: (&net.Dialer{
Timeout: 5 * time.Second,
}).DialContext,
@@ -137,6 +137,10 @@ func GetClient(insecure bool, timeout time.Duration) *http.Client {
ExpectContinueTimeout: 1 * time.Second,
TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure},
}
}
func GetClient(insecure bool, timeout time.Duration) *http.Client {
tr := GetTransport(insecure, timeout)
return &http.Client{
Transport: tr,
Timeout: timeout,

View File

@@ -4,7 +4,12 @@ import (
"fmt"
"time"
"yunion.io/x/pkg/errors"
"context"
"io"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/util/huawei/obs"
)
type SBucket struct {
@@ -73,3 +78,126 @@ func (b *SBucket) GetSizeByte() int64 {
func (b *SBucket) GetObjectNumber() int {
return b.ObjectNumber
}
func (b *SBucket) GetIObjects(prefix string, isRecursive bool) ([]cloudprovider.ICloudObject, error) {
return cloudprovider.GetIObjects(b, prefix, isRecursive)
}
func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) {
result := cloudprovider.SListObjectResult{}
obscli, err := b.region.getOBSClient()
if err != nil {
return result, errors.Wrap(err, "GetOBSClient")
}
input := &obs.ListObjectsInput{}
input.Bucket = b.Name
if len(prefix) > 0 {
input.Prefix = prefix
}
if len(marker) > 0 {
input.Marker = marker
}
if len(delimiter) > 0 {
input.Delimiter = delimiter
}
if maxCount > 0 {
input.MaxKeys = maxCount
}
oResult, err := obscli.ListObjects(input)
if err != nil {
return result, errors.Wrap(err, "ListObjects")
}
result.Objects = make([]cloudprovider.ICloudObject, 0)
for _, object := range oResult.Contents {
obj := &SObject{
bucket: b,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
StorageClass: string(object.StorageClass),
Key: object.Key,
SizeBytes: object.Size,
ETag: object.ETag,
LastModified: object.LastModified,
ContentType: "",
},
}
result.Objects = append(result.Objects, obj)
}
if oResult.CommonPrefixes != nil {
result.CommonPrefixes = make([]cloudprovider.ICloudObject, 0)
for _, commonPrefix := range oResult.CommonPrefixes {
obj := &SObject{
bucket: b,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
Key: commonPrefix,
},
}
result.CommonPrefixes = append(result.CommonPrefixes, obj)
}
}
result.IsTruncated = oResult.IsTruncated
result.NextMarker = oResult.NextMarker
return result, nil
}
func (b *SBucket) PutObject(ctx context.Context, key string, reader io.ReadSeeker, contType string, storageClassStr string) error {
obscli, err := b.region.getOBSClient()
if err != nil {
return errors.Wrap(err, "GetOBSClient")
}
input := &obs.PutObjectInput{}
input.Bucket = b.Name
input.Key = key
input.Body = reader
if len(storageClassStr) > 0 {
input.StorageClass, err = str2StorageClass(storageClassStr)
if err != nil {
return err
}
}
if len(contType) > 0 {
input.ContentType = contType
}
_, err = obscli.PutObject(input)
if err != nil {
return errors.Wrap(err, "PutObject")
}
return nil
}
func (b *SBucket) DeleteObject(ctx context.Context, key string) error {
obscli, err := b.region.getOBSClient()
if err != nil {
return errors.Wrap(err, "GetOBSClient")
}
input := &obs.DeleteObjectInput{}
input.Bucket = b.Name
input.Key = key
_, err = obscli.DeleteObject(input)
if err != nil {
return errors.Wrap(err, "DeleteObject")
}
return nil
}
func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) {
obscli, err := b.region.getOBSClient()
if err != nil {
return "", errors.Wrap(err, "GetOBSClient")
}
input := obs.CreateSignedUrlInput{}
input.Bucket = b.Name
input.Key = key
input.Expires = int(expire / time.Second)
switch method {
case "GET":
input.Method = obs.HttpMethodGet
case "PUT":
input.Method = obs.HttpMethodPut
case "DELETE":
input.Method = obs.HttpMethodDelete
default:
return "", errors.Error("unsupported method")
}
output, err := obscli.CreateSignedUrl(&input)
return output.SignedUrl, nil
}

13
pkg/util/huawei/object.go Normal file
View File

@@ -0,0 +1,13 @@
package huawei
import "yunion.io/x/onecloud/pkg/cloudprovider"
type SObject struct {
bucket *SBucket
cloudprovider.SBaseCloudObject
}
func (o *SObject) GetIBucket() cloudprovider.ICloudBucket {
return o.bucket
}

View File

@@ -705,6 +705,18 @@ func (region *SRegion) GetIBuckets() ([]cloudprovider.ICloudBucket, error) {
return ret, nil
}
func str2StorageClass(storageClassStr string) (obs.StorageClassType, error) {
if strings.EqualFold(storageClassStr, string(obs.StorageClassStandard)) {
return obs.StorageClassStandard, nil
} else if strings.EqualFold(storageClassStr, string(obs.StorageClassWarm)) {
return obs.StorageClassWarm, nil
} else if strings.EqualFold(storageClassStr, string(obs.StorageClassCold)) {
return obs.StorageClassCold, nil
} else {
return obs.StorageClassStandard, errors.Error("unsupported storageClass")
}
}
func (region *SRegion) CreateIBucket(name string, storageClassStr string, aclStr string) error {
obsClient, err := region.getOBSClient()
if err != nil {
@@ -725,14 +737,9 @@ func (region *SRegion) CreateIBucket(name string, storageClassStr string, aclStr
}
}
if len(storageClassStr) > 0 {
if strings.EqualFold(storageClassStr, string(obs.StorageClassStandard)) {
input.StorageClass = obs.StorageClassStandard
} else if strings.EqualFold(storageClassStr, string(obs.StorageClassWarm)) {
input.StorageClass = obs.StorageClassWarm
} else if strings.EqualFold(storageClassStr, string(obs.StorageClassCold)) {
input.StorageClass = obs.StorageClassCold
} else {
return errors.Error("unsupported storageClass")
input.StorageClass, err = str2StorageClass(storageClassStr)
if err != nil {
return err
}
}
_, err = obsClient.CreateBucket(input)
@@ -769,7 +776,7 @@ func (region *SRegion) IBucketExist(name string) (bool, error) {
return true, nil
}
func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) {
func (region *SRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) {
obsClient, err := region.getOBSClient()
if err != nil {
return nil, errors.Wrap(err, "region.getOBSClient")

View File

@@ -0,0 +1,9 @@
package shell
import (
"yunion.io/x/onecloud/pkg/multicloud/objectstore"
)
func init() {
objectstore.S3Shell()
}

View File

@@ -30,7 +30,7 @@ func init() {
BUCKET string `help:"bucket name to show"`
}
shellutils.R(&ObsBucketShowOptions{}, "obs-show", "Show bucket detail", func(cli *huawei.SRegion, args *ObsBucketShowOptions) error {
bucket, err := cli.GetIBucketByName(args.BUCKET)
bucket, err := cli.GetIBucketById(args.BUCKET)
if err != nil {
return err
}

View File

@@ -499,6 +499,6 @@ func (region *SRegion) IBucketExist(name string) (bool, error) {
return false, cloudprovider.ErrNotImplemented
}
func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) {
func (region *SRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) {
return nil, cloudprovider.ErrNotImplemented
}

View File

@@ -1,9 +1,16 @@
package qcloud
import (
"context"
"fmt"
"io"
"time"
"github.com/tencentyun/cos-go-sdk-v5"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/timeutils"
"yunion.io/x/onecloud/pkg/cloudprovider"
)
@@ -49,10 +56,14 @@ func (b *SBucket) GetAcl() string {
return b.Acl
}
func (b *SBucket) getBucketUrl() string {
return fmt.Sprintf("https://%s.%s", b.FullName, b.region.getCosEndpoint())
}
func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
return []cloudprovider.SBucketAccessUrl{
{
Url: fmt.Sprintf("https://%s.%s", b.FullName, b.region.getCosEndpoint()),
Url: b.getBucketUrl(),
Description: "bucket domain",
},
{
@@ -61,3 +72,112 @@ func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
},
}
}
func (b *SBucket) GetIObjects(prefix string, isRecursive bool) ([]cloudprovider.ICloudObject, error) {
return cloudprovider.GetIObjects(b, prefix, isRecursive)
}
func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) {
result := cloudprovider.SListObjectResult{}
coscli, err := b.region.GetCosClient(b)
if err != nil {
return result, errors.Wrap(err, "GetCosClient")
}
opts := &cos.BucketGetOptions{}
if len(prefix) > 0 {
opts.Prefix = prefix
}
if len(marker) > 0 {
opts.Marker = marker
}
if len(delimiter) > 0 {
opts.Delimiter = delimiter
}
if maxCount > 0 {
opts.MaxKeys = maxCount
}
oResult, _, err := coscli.Bucket.Get(context.Background(), opts)
if err != nil {
return result, errors.Wrap(err, "coscli.Bucket.Get")
}
result.Objects = make([]cloudprovider.ICloudObject, 0)
for _, object := range oResult.Contents {
lastModified, _ := timeutils.ParseTimeStr(object.LastModified)
obj := &SObject{
bucket: b,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
StorageClass: string(object.StorageClass),
Key: object.Key,
SizeBytes: int64(object.Size),
ETag: object.ETag,
LastModified: lastModified,
ContentType: "",
},
}
result.Objects = append(result.Objects, obj)
}
if oResult.CommonPrefixes != nil {
result.CommonPrefixes = make([]cloudprovider.ICloudObject, 0)
for _, commPrefix := range oResult.CommonPrefixes {
obj := &SObject{
bucket: b,
SBaseCloudObject: cloudprovider.SBaseCloudObject{
Key: commPrefix,
},
}
result.CommonPrefixes = append(result.CommonPrefixes, obj)
}
}
result.IsTruncated = oResult.IsTruncated
result.NextMarker = oResult.NextMarker
return result, nil
}
func (b *SBucket) PutObject(ctx context.Context, key string, reader io.ReadSeeker, contType string, storageClassStr string) error {
coscli, err := b.region.GetCosClient(b)
if err != nil {
return errors.Wrap(err, "GetCosClient")
}
opts := &cos.ObjectPutOptions{}
if len(contType) > 0 {
opts.ContentType = contType
}
if len(storageClassStr) > 0 {
opts.XCosStorageClass = storageClassStr
}
_, err = coscli.Object.Put(ctx, key, reader, opts)
if err != nil {
return errors.Wrap(err, "coscli.Object.Put")
}
return nil
}
func (b *SBucket) DeleteObject(ctx context.Context, key string) error {
coscli, err := b.region.GetCosClient(b)
if err != nil {
return errors.Wrap(err, "GetCosClient")
}
_, err = coscli.Object.Delete(ctx, key)
if err != nil {
return errors.Wrap(err, "coscli.Object.Delete")
}
return nil
}
func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) {
if method != "GET" && method != "PUT" && method != "DELETE" {
return "", errors.Error("unsupported method")
}
coscli, err := b.region.GetCosClient(b)
if err != nil {
return "", errors.Wrap(err, "GetCosClient")
}
url, err := coscli.Object.GetPresignedURL(context.Background(), method, key,
b.region.client.SecretKey,
b.region.client.SecretID,
expire, nil)
if err != nil {
return "", errors.Wrap(err, "coscli.Object.GetPresignedURL")
}
return url.String(), nil
}

13
pkg/util/qcloud/object.go Normal file
View File

@@ -0,0 +1,13 @@
package qcloud
import "yunion.io/x/onecloud/pkg/cloudprovider"
type SObject struct {
bucket *SBucket
cloudprovider.SBaseCloudObject
}
func (o *SObject) GetIBucket() cloudprovider.ICloudBucket {
return o.bucket
}

View File

@@ -20,9 +20,6 @@ import (
"strings"
"time"
"yunion.io/x/pkg/utils"
coslib "github.com/nelsonken/cos-go-sdk-v5/cos"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
sdkerrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
@@ -30,12 +27,9 @@ import (
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/appctx"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/pkg/util/timeutils"
)
const (
@@ -587,103 +581,3 @@ func (client *SQcloudClient) GetIProjects() ([]cloudprovider.ICloudProject, erro
}
return iprojects, nil
}
func (region *SRegion) GetIBuckets() ([]cloudprovider.ICloudBucket, error) {
ctx := appctx.Background
cos, err := region.GetCosClient()
if err != nil {
return nil, errors.Wrap(err, "GetCosClient")
}
result, err := cos.GetBucketList(ctx)
if err != nil {
return nil, errors.Wrap(err, "GetBucketList")
}
ret := make([]cloudprovider.ICloudBucket, 0)
for i := range result.Buckets.Bucket {
bInfo := result.Buckets.Bucket[i]
// ignore buckets not belong to this region
if bInfo.Location != region.GetId() {
continue
}
createAt, _ := timeutils.ParseTimeStr(bInfo.CreateDate)
name := bInfo.Name
// name = name[:len(name)-len(result.Owner.ID)-1]
name = name[:strings.LastIndexByte(name, '-')]
b := SBucket{
region: region,
Name: name,
FullName: bInfo.Name,
Location: bInfo.Location,
CreateDate: createAt,
}
ret = append(ret, &b)
}
return ret, nil
}
func (region *SRegion) CreateIBucket(name string, storageClassStr string, aclStr string) error {
ctx := appctx.Background
cos, err := region.GetCosClient()
if err != nil {
return errors.Wrap(err, "GetCosClient")
}
acl := &coslib.AccessControl{}
if len(aclStr) > 0 {
if utils.IsInStringArray(aclStr, []string{
"private", "public-read", "public-read-write", "authenticated-read",
}) {
acl.ACL = aclStr
} else {
return errors.Error("invalid acl")
}
}
err = cos.CreateBucket(ctx, name, acl)
if err != nil {
return errors.Wrap(err, "oss.CreateBucket")
}
return nil
}
func cosHttpCode(err error) int {
if httpErr, ok := err.(coslib.HTTPError); ok {
return httpErr.Code
}
if httpErr, ok := err.(*coslib.HTTPError); ok {
return httpErr.Code
}
return -1
}
func (region *SRegion) DeleteIBucket(name string) error {
ctx := appctx.Background
cos, err := region.GetCosClient()
if err != nil {
return errors.Wrap(err, "GetCosClient")
}
err = cos.DeleteBucket(ctx, name)
if err != nil {
if cosHttpCode(err) == 404 {
return nil
}
return errors.Wrap(err, "DeleteBucket")
}
return nil
}
func (region *SRegion) IBucketExist(name string) (bool, error) {
ctx := appctx.Background
cos, err := region.GetCosClient()
if err != nil {
return false, errors.Wrap(err, "GetCosClient")
}
err = cos.BucketExists(ctx, name)
if err != nil {
return false, errors.Wrap(err, "BucketExists")
}
return true, nil
}
func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) {
return cloudprovider.GetIBucketByName(region, name)
}

View File

@@ -15,15 +15,22 @@
package qcloud
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/nelsonken/cos-go-sdk-v5/cos"
"github.com/tencentyun/cos-go-sdk-v5"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/timeutils"
"yunion.io/x/pkg/utils"
"github.com/tencentyun/cos-go-sdk-v5/debug"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/multicloud"
@@ -32,8 +39,7 @@ import (
type SRegion struct {
multicloud.SRegion
client *SQcloudClient
cosClient *cos.Client
client *SQcloudClient
izones []cloudprovider.ICloudZone
ivpcs []cloudprovider.ICloudVpc
@@ -266,16 +272,30 @@ func (self *SRegion) CreateIVpc(name string, desc string, cidr string) (cloudpro
return self.GetIVpcById(vpcId)
}
func (self *SRegion) GetCosClient() (*cos.Client, error) {
if self.cosClient == nil {
self.cosClient = cos.New(&cos.Option{
AppID: self.client.AppID,
SecretID: self.client.SecretID,
SecretKey: self.client.SecretKey,
Region: self.Region,
})
func (self *SRegion) GetCosClient(bucket *SBucket) (*cos.Client, error) {
var baseUrl *cos.BaseURL
if bucket != nil {
u, _ := url.Parse(bucket.getBucketUrl())
baseUrl = &cos.BaseURL{
BucketURL: u,
}
}
return self.cosClient, nil
cosClient := cos.NewClient(
baseUrl,
&http.Client{
Transport: &cos.AuthorizationTransport{
SecretID: self.client.SecretID,
SecretKey: self.client.SecretKey,
Transport: &debug.DebugRequestTransport{
RequestHeader: self.client.Debug,
RequestBody: self.client.Debug,
ResponseHeader: self.client.Debug,
ResponseBody: self.client.Debug,
},
},
},
)
return cosClient, nil
}
func (self *SRegion) GetClient() *SQcloudClient {
@@ -820,3 +840,111 @@ func (self *SRegion) QueryAccountBalance() (*SAccountBalance, error) {
func (self *SRegion) getCosEndpoint() string {
return fmt.Sprintf("cos.%s.myqcloud.com", self.GetId())
}
func (region *SRegion) GetIBuckets() ([]cloudprovider.ICloudBucket, error) {
coscli, err := region.GetCosClient(nil)
if err != nil {
return nil, errors.Wrap(err, "GetCosClient")
}
s, _, err := coscli.Service.Get(context.Background())
if err != nil {
return nil, errors.Wrap(err, "coscli.Service.Get")
}
ret := make([]cloudprovider.ICloudBucket, 0)
for i := range s.Buckets {
bInfo := s.Buckets[i]
// ignore buckets not belong to this region
if bInfo.Region != region.GetId() {
continue
}
createAt, _ := timeutils.ParseTimeStr(bInfo.CreationDate)
name := bInfo.Name
// name = name[:len(name)-len(result.Owner.ID)-1]
name = name[:strings.LastIndexByte(name, '-')]
b := SBucket{
region: region,
Name: name,
FullName: bInfo.Name,
Location: bInfo.Region,
CreateDate: createAt,
}
ret = append(ret, &b)
}
return ret, nil
}
func (region *SRegion) CreateIBucket(name string, storageClassStr string, aclStr string) error {
bucket := &SBucket{
region: region,
Name: name,
FullName: fmt.Sprintf("%s-%s", name, region.client.AppID),
}
coscli, err := region.GetCosClient(bucket)
if err != nil {
return errors.Wrap(err, "GetCosClient")
}
opts := &cos.BucketPutOptions{}
if len(aclStr) > 0 {
if utils.IsInStringArray(aclStr, []string{
"private", "public-read", "public-read-write", "authenticated-read",
}) {
opts.XCosACL = aclStr
} else {
return errors.Error("invalid acl")
}
}
_, err = coscli.Bucket.Put(context.Background(), opts)
if err != nil {
return errors.Wrap(err, "coscli.Bucket.Put")
}
return nil
}
func cosHttpCode(err error) int {
if httpErr, ok := err.(*cos.ErrorResponse); ok {
return httpErr.Response.StatusCode
}
return -1
}
func (region *SRegion) DeleteIBucket(name string) error {
bucket := &SBucket{
region: region,
Name: name,
FullName: fmt.Sprintf("%s-%s", name, region.client.AppID),
}
coscli, err := region.GetCosClient(bucket)
if err != nil {
return errors.Wrap(err, "GetCosClient")
}
_, err = coscli.Bucket.Delete(context.Background())
if err != nil {
if cosHttpCode(err) == 404 {
return nil
}
return errors.Wrap(err, "DeleteBucket")
}
return nil
}
func (region *SRegion) IBucketExist(name string) (bool, error) {
bucket := &SBucket{
region: region,
Name: name,
FullName: fmt.Sprintf("%s-%s", name, region.client.AppID),
}
coscli, err := region.GetCosClient(bucket)
if err != nil {
return false, errors.Wrap(err, "GetCosClient")
}
_, err = coscli.Bucket.Head(context.Background())
if err != nil {
return false, errors.Wrap(err, "BucketExists")
}
return true, nil
}
func (region *SRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) {
return cloudprovider.GetIBucketById(region, name)
}

View File

@@ -15,136 +15,9 @@
package shell
import (
"context"
"os"
coslib "github.com/nelsonken/cos-go-sdk-v5/cos"
"yunion.io/x/onecloud/pkg/util/printutils"
"yunion.io/x/onecloud/pkg/util/qcloud"
"yunion.io/x/onecloud/pkg/util/shellutils"
"yunion.io/x/onecloud/pkg/multicloud/objectstore"
)
func init() {
type CosListOptions struct {
}
shellutils.R(&CosListOptions{}, "cos-list", "List COS buckets", func(cli *qcloud.SRegion, args *CosListOptions) error {
buckets, err := cli.GetIBuckets()
if err != nil {
return err
}
printList(buckets, 0, 0, 0, nil)
return nil
})
shellutils.R(&CosListOptions{}, "bucket-list", "List COS buckets", func(cli *qcloud.SRegion, args *CosListOptions) error {
buckets, err := cli.GetIBuckets()
if err != nil {
return err
}
printutils.PrintGetterList(buckets, nil)
return nil
})
type CosCreateBucketOptions struct {
BUCKET string `help:"name of bucket to create"`
Acl string `help:"Acl"`
}
shellutils.R(&CosCreateBucketOptions{}, "cos-create-bucket", "Create a COS bucket", func(cli *qcloud.SRegion, args *CosCreateBucketOptions) error {
err := cli.CreateIBucket(args.BUCKET, "", args.Acl)
if err != nil {
return err
}
return nil
})
type CosDeleteBucketOptions struct {
BUCKET string `help:"name of bucket to delete"`
}
shellutils.R(&CosDeleteBucketOptions{}, "cos-delete-bucket", "Delete a COS bucket", func(cli *qcloud.SRegion, args *CosDeleteBucketOptions) error {
err := cli.DeleteIBucket(args.BUCKET)
if err != nil {
return err
}
return nil
})
type CosListBucketOptions struct {
BUCKET string `help:"bucket name"`
}
shellutils.R(&CosListBucketOptions{}, "cos-bucket-list", "List content of a OSS bucket", func(cli *qcloud.SRegion, args *CosListBucketOptions) error {
cos, err := cli.GetCosClient()
if err != nil {
return err
}
result, err := cos.ListBucketContents(context.Background(), args.BUCKET, &coslib.QueryCondition{})
if err != nil {
return err
}
printList(result.Contents, len(result.Contents), 0, len(result.Contents), nil)
return nil
})
shellutils.R(&CosListBucketOptions{}, "cos-bucket-create", "Create a OSS bucket", func(cli *qcloud.SRegion, args *CosListBucketOptions) error {
cos, err := cli.GetCosClient()
if err != nil {
return err
}
return cos.CreateBucket(context.Background(), args.BUCKET, &coslib.AccessControl{})
})
type CosUploadOptions struct {
BUCKET string `help:"bucket name"`
KEY string `help:"Object key"`
FILE string `help:"Local file path"`
Acl string `help:"Object ACL" choices:"private|public-read|public-read-write"`
}
shellutils.R(&CosUploadOptions{}, "cos-upload", "Upload a file to a Cos bucket", func(cli *qcloud.SRegion, args *CosUploadOptions) error {
cos, err := cli.GetCosClient()
if err != nil {
return err
}
return cos.Bucket(args.BUCKET).UploadObjectBySlice(context.Background(), args.KEY, args.FILE, 3, nil)
})
type CosDownloadOptions struct {
BUCKET string `help:"bucket name"`
NAME string `help:"File name"`
}
shellutils.R(&CosDownloadOptions{}, "cos-download", "Download a file", func(cli *qcloud.SRegion, args *CosDownloadOptions) error {
cos, err := cli.GetCosClient()
if err != nil {
return err
}
//file
return cos.Bucket(args.BUCKET).DownloadObject(context.Background(), args.NAME, os.Stdout)
//return cos.Bucket(args.BUCKET).UploadObjectBySlice(context.Background(), args.KEY, args.FILE, 3, nil)
})
type CosObjectAclOptions struct {
BUCKET string `help:"bucket name"`
KEY string `help:"object key"`
ACL string `help:"ACL" choices:"private|public-read|public-read-write"`
}
shellutils.R(&CosObjectAclOptions{}, "cos-set-acl", "Set acl for a object", func(cli *qcloud.SRegion, args *CosObjectAclOptions) error {
cos, err := cli.GetCosClient()
if err != nil {
return err
}
return cos.SetBucketACL(context.Background(), args.KEY, &coslib.AccessControl{ACL: args.ACL})
})
type CosDeleteOptions struct {
BUCKET string `help:"bucket name"`
KEY string `help:"Object key"`
}
shellutils.R(&CosDeleteOptions{}, "cos-delete", "Delete a file from a Cos bucket", func(cli *qcloud.SRegion, args *CosDeleteOptions) error {
cos, err := cli.GetCosClient()
if err != nil {
return err
}
return cos.Bucket(args.BUCKET).DeleteObject(context.Background(), args.KEY)
})
objectstore.S3Shell()
}

View File

@@ -18,15 +18,13 @@ import (
"context"
"fmt"
"io"
"os"
"strings"
"time"
coslib "github.com/nelsonken/cos-go-sdk-v5/cos"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"github.com/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/compute"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/compute/options"
@@ -165,33 +163,16 @@ func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.To
// first upload image to oss
s := auth.GetAdminSession(ctx, options.Options.Region, "")
meta, reader, err := modules.Images.Download(s, image.ImageId, string(qemuimg.VMDK), false)
_, reader, err := modules.Images.Download(s, image.ImageId, string(qemuimg.VMDK), false)
if err != nil {
return "", err
}
tmpFile := fmt.Sprintf("%s/%s", options.Options.TempPath, image.ImageId)
defer os.Remove(tmpFile)
f, err := os.Create(tmpFile)
if err != nil {
return "", err
}
defer f.Close()
if _, err := io.Copy(f, reader); err != nil {
return "", err
}
log.Infof("meta data %s", meta)
cos, err := self.region.GetCosClient()
if err != nil {
log.Errorf("GetOssClient err %s", err)
return "", err
}
bucketName := strings.ToLower(fmt.Sprintf("imgcache-%s", self.region.GetId()))
err = cos.BucketExists(context.Background(), bucketName)
if err != nil {
bucketName := strings.ToLower(fmt.Sprintf("imgcache-%s-%s", self.region.GetId(), image.ImageId))
exists, _ := self.region.IBucketExist(bucketName)
if !exists {
log.Debugf("Bucket %s not exists, to create ...", bucketName)
err := cos.CreateBucket(context.Background(), bucketName, &coslib.AccessControl{ACL: "public-read"})
err := self.region.CreateIBucket(bucketName, "", "public-read")
if err != nil {
log.Errorf("Create bucket error %s", err)
return "", err
@@ -199,14 +180,19 @@ func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.To
} else {
log.Debugf("Bucket %s exists", bucketName)
}
defer self.region.DeleteIBucket(bucketName)
log.Debugf("To upload image to bucket %s ...", bucketName)
err = cos.Bucket(bucketName).UploadObjectBySlice(context.Background(), image.ImageId, tmpFile, 3, map[string]string{})
bucket, err := self.region.GetIBucketById(bucketName)
if err != nil {
return "", errors.Wrap(err, "GetIBucketByName")
}
err = bucket.PutObject(context.Background(), image.ImageId, reader.(io.ReadSeeker), "", "")
if err != nil {
log.Errorf("UploadObject error %s %s", image.ImageId, err)
return "", err
return "", errors.Wrap(err, "bucket.PutObject")
}
defer cos.Bucket(bucketName).DeleteObject(context.Background(), image.ImageId)
defer bucket.DeleteObject(context.Background(), image.ImageId)
// 腾讯云镜像名称需要小于20个字符
imageBaseName := image.ImageId[:10]

View File

@@ -636,6 +636,7 @@ func (self *SRegion) GetClient() *SUcloudClient {
return self.client
}
// https://docs.ucloud.cn/api/ufile-api/describe_bucket
func (region *SRegion) listBuckets(name string, offset int, limit int) ([]SBucket, error) {
params := NewUcloudParams()
if len(name) > 0 {
@@ -645,7 +646,8 @@ func (region *SRegion) listBuckets(name string, offset int, limit int) ([]SBucke
params.Set("Offset", offset)
}
buckets := make([]SBucket, 0)
err := region.DoAction("DescribeBucket", params, &buckets)
// request without RegionId
err := region.client.DoAction("DescribeBucket", params, &buckets)
if err != nil {
return nil, errors.Wrap(err, "DoAction DescribeBucket")
}
@@ -673,6 +675,7 @@ func (region *SRegion) GetIBuckets() ([]cloudprovider.ICloudBucket, error) {
ret := make([]cloudprovider.ICloudBucket, len(buckets))
for i := range buckets {
buckets[i].region = region
buckets[i].projectId = region.client.projectId
ret[i] = &buckets[i]
}
return ret, nil
@@ -707,15 +710,6 @@ func (region *SRegion) IBucketExist(name string) (bool, error) {
return true, nil
}
func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) {
parts, err := region.listBuckets(name, 0, 1)
if err != nil {
return nil, errors.Wrap(err, "region.listBuckets")
}
if len(parts) == 0 {
return nil, cloudprovider.ErrNotFound
}
bucket := parts[0]
bucket.region = region
return &bucket, nil
func (region *SRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) {
return cloudprovider.GetIBucketById(region, name)
}

View File

@@ -1,43 +1,9 @@
package shell
import (
"yunion.io/x/onecloud/pkg/util/printutils"
"yunion.io/x/onecloud/pkg/util/shellutils"
"yunion.io/x/onecloud/pkg/util/ucloud"
"yunion.io/x/onecloud/pkg/multicloud/objectstore"
)
func init() {
type UFileBucketListOptions struct {
}
shellutils.R(&UFileBucketListOptions{}, "bucket-list", "List buckets", func(cli *ucloud.SRegion, args *UFileBucketListOptions) error {
buckets, err := cli.GetIBuckets()
if err != nil {
return err
}
printutils.PrintGetterList(buckets, nil)
return nil
})
type UFileBucketCreateOptions struct {
BUCKET string `help:"Name of bucket"`
Acl string `help:"Acl" choices:"private|public"`
}
shellutils.R(&UFileBucketCreateOptions{}, "bucket-create", "create a bucket", func(cli *ucloud.SRegion, args *UFileBucketCreateOptions) error {
err := cli.CreateIBucket(args.BUCKET, "", args.Acl)
if err != nil {
return err
}
return nil
})
type UFileBucketDeleteOptions struct {
BUCKET string `help:"Name of bucket"`
}
shellutils.R(&UFileBucketDeleteOptions{}, "bucket-delete", "delete a bucket", func(cli *ucloud.SRegion, args *UFileBucketDeleteOptions) error {
err := cli.DeleteIBucket(args.BUCKET)
if err != nil {
return err
}
return nil
})
objectstore.S3Shell()
}

View File

@@ -143,10 +143,14 @@ func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.To
bucketName := GetBucketName(self.region.GetId(), image.ImageId)
// create bucket
if _, err := self.region.GetBucketDomain(bucketName); err != nil {
exist, err := self.region.IBucketExist(bucketName)
if err != nil {
return "", errors.Wrap(err, "self.region.IBucketExist")
}
if !exist {
err = self.region.CreateBucket(bucketName, "private")
if err != nil {
return "", err
return "", errors.Wrap(err, "CreateBucket")
}
}
defer func() {
@@ -165,7 +169,7 @@ func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.To
log.Debugf("Images meta data %s", meta)
minDiskMB, _ := meta.Int("min_disk")
minDiskGB := int64(math.Ceil(float64(minDiskMB) / 1024))
// 在使用OBS桶的外部镜像文件制作镜像时生效且为必选字段。取值为401024GB。
if minDiskGB < 40 {
minDiskGB = 40
} else if minDiskGB > 1024 {
@@ -175,13 +179,16 @@ func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.To
md5, _ := meta.GetString("checksum")
diskFormat, _ := meta.GetString("disk_format")
// upload to ucloud
bucket, err := self.region.GetIBucketById(bucketName)
if err != nil {
return "", errors.Wrap(err, "GetIBucketByName")
}
file := SFile{
region: self.region,
BucketName: bucketName,
File: reader,
FileSize: size,
FileName: image.ImageId,
FileMD5: md5,
bucket: bucket.(*SBucket),
File: reader,
FileSize: size,
FileName: image.ImageId,
FileMD5: md5,
}
err = file.Upload()
@@ -245,8 +252,7 @@ func (self *SRegion) createIImage(snapshotId, imageName, imageDesc string) (stri
return "", cloudprovider.ErrNotSupported
}
// https://docs.ucloud.cn/api/ufile-api/describe_bucket
func (self *SRegion) GetBucketDomain(name string) (string, error) {
/*func (self *SRegion) GetBucketDomain(name string) (string, error) {
params := NewUcloudParams()
params.Set("BucketName", name)
@@ -261,7 +267,7 @@ func (self *SRegion) GetBucketDomain(name string) (string, error) {
} else {
return "", fmt.Errorf("GetBucketDomain failed. %v", res)
}
}
}*/
// https://docs.ucloud.cn/api/ufile-api/create_bucket
func (self *SRegion) CreateBucket(name, bucketType string) error {

View File

@@ -27,6 +27,7 @@ import (
"yunion.io/x/log"
"context"
"yunion.io/x/onecloud/pkg/cloudprovider"
"yunion.io/x/onecloud/pkg/util/httputils"
)
@@ -34,6 +35,8 @@ import (
type SBucket struct {
region *SRegion
projectId string
Domain Domain `json:"Domain"`
BucketID string `json:"BucketId"`
Region string `json:"Region"`
@@ -55,13 +58,13 @@ type Domain struct {
}
type SFile struct {
region *SRegion
bucket *SBucket
BucketName string
File io.Reader
FileSize int64
FileName string
FileMD5 string
// BucketName string
File io.Reader
FileSize int64
FileName string
FileMD5 string
}
func (self *SFile) signHeader(httpMethod string) string {
@@ -76,24 +79,25 @@ func (self *SFile) signHeader(httpMethod string) string {
data += md5 + "\n"
data += contentType + "\n"
data += "\n"
data += "/" + self.BucketName + "/" + self.FileName
data += "/" + self.bucket.BucketName + "/" + self.FileName
h := hmac.New(sha1.New, []byte(self.region.client.accessKeySecret))
h := hmac.New(sha1.New, []byte(self.bucket.region.client.accessKeySecret))
h.Write([]byte(data))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func (self *SFile) auth(httpMethod string) string {
return "UCloud" + " " + self.region.client.accessKeyId + ":" + self.signHeader(httpMethod)
return "UCloud" + " " + self.bucket.region.client.accessKeyId + ":" + self.signHeader(httpMethod)
}
func (self *SFile) GetHost() string {
host, err := self.region.GetBucketDomain(self.BucketName)
return self.bucket.Domain.Src[0]
/*host, err := self.bucket.region.GetBucketDomain(self.BucketName)
if err != nil {
log.Errorf("SFile GetHost %s", err)
return ""
}
return host
return host*/
}
func (self *SFile) GetUrl() string {
@@ -105,13 +109,13 @@ func (self *SFile) FetchFileUrl() string {
expired := strconv.FormatInt(time.Now().Add(6*time.Hour).Unix(), 10)
// sign
data := "GET\n\n\n" + expired + "\n"
data += "/" + self.BucketName + "/" + self.FileName
h := hmac.New(sha1.New, []byte(self.region.client.accessKeySecret))
data += "/" + self.bucket.BucketName + "/" + self.FileName
h := hmac.New(sha1.New, []byte(self.bucket.region.client.accessKeySecret))
h.Write([]byte(data))
sign := base64.StdEncoding.EncodeToString(h.Sum(nil))
urlEncoder := url.Values{}
urlEncoder.Add("UCloudPublicKey", self.region.client.accessKeyId)
urlEncoder.Add("UCloudPublicKey", self.bucket.region.client.accessKeyId)
urlEncoder.Add("Signature", sign)
urlEncoder.Add("Expires", expired)
querys := urlEncoder.Encode()
@@ -135,7 +139,7 @@ func (self *SFile) Delete() error {
}
func (self *SFile) request(req *http.Request) error {
res, err := http.DefaultClient.Do(req)
res, err := httputils.GetDefaultClient().Do(req)
if err != nil {
return err
}
@@ -150,11 +154,11 @@ func (self *SFile) request(req *http.Request) error {
}
func (b *SBucket) GetProjectId() string {
return ""
return b.projectId
}
func (b *SBucket) GetGlobalId() string {
return b.BucketName
return b.BucketID
}
func (b *SBucket) GetName() string {
@@ -181,20 +185,51 @@ func (b *SBucket) GetAcl() string {
return b.Type
}
func (b *SBucket) getSrcUrl() string {
if len(b.Domain.Src) > 0 {
return b.Domain.Src[0]
}
return ""
}
func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl {
ret := make([]cloudprovider.SBucketAccessUrl, 0)
regionId := b.region.GetId()
// hack, remove trailing digits
for len(regionId) > 0 {
lastDigit := regionId[len(regionId)-1]
if lastDigit >= '0' && lastDigit <= '9' {
regionId = regionId[:len(regionId)-1]
} else {
break
}
for i, u := range b.Domain.Src {
ret = append(ret, cloudprovider.SBucketAccessUrl{
Url: u,
Description: fmt.Sprintf("src%d", i),
})
}
for i, u := range b.Domain.CDN {
ret = append(ret, cloudprovider.SBucketAccessUrl{
Url: u,
Description: fmt.Sprintf("cdn%d", i),
})
}
ret = append(ret, cloudprovider.SBucketAccessUrl{
Url: fmt.Sprintf("https://%s.%s.ufileos.com", b.BucketName, regionId),
})
return ret
}
func (b *SBucket) GetIObjects(prefix string, isRecursive bool) ([]cloudprovider.ICloudObject, error) {
return cloudprovider.GetIObjects(b, prefix, isRecursive)
}
func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) {
result := cloudprovider.SListObjectResult{}
return result, cloudprovider.ErrNotSupported
}
func (b *SBucket) PutObject(ctx context.Context, key string, reader io.ReadSeeker, contType string, storageClassStr string) error {
return cloudprovider.ErrNotSupported
}
func (b *SBucket) DeleteObject(ctx context.Context, key string) error {
file := SFile{
bucket: b,
FileName: key,
}
return file.Delete()
}
func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) {
return "", cloudprovider.ErrNotSupported
}

27
vendor/github.com/google/go-querystring/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) 2013 Google. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

320
vendor/github.com/google/go-querystring/query/encode.go generated vendored Normal file
View File

@@ -0,0 +1,320 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package query implements encoding of structs into URL query parameters.
//
// As a simple example:
//
// type Options struct {
// Query string `url:"q"`
// ShowAll bool `url:"all"`
// Page int `url:"page"`
// }
//
// opt := Options{ "foo", true, 2 }
// v, _ := query.Values(opt)
// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2"
//
// The exact mapping between Go values and url.Values is described in the
// documentation for the Values() function.
package query
import (
"bytes"
"fmt"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
var timeType = reflect.TypeOf(time.Time{})
var encoderType = reflect.TypeOf(new(Encoder)).Elem()
// Encoder is an interface implemented by any type that wishes to encode
// itself into URL values in a non-standard way.
type Encoder interface {
EncodeValues(key string, v *url.Values) error
}
// Values returns the url.Values encoding of v.
//
// Values expects to be passed a struct, and traverses it recursively using the
// following encoding rules.
//
// Each exported struct field is encoded as a URL parameter unless
//
// - the field's tag is "-", or
// - the field is empty and its tag specifies the "omitempty" option
//
// The empty values are false, 0, any nil pointer or interface value, any array
// slice, map, or string of length zero, and any time.Time that returns true
// for IsZero().
//
// The URL parameter name defaults to the struct field name but can be
// specified in the struct field's tag value. The "url" key in the struct
// field's tag value is the key name, followed by an optional comma and
// options. For example:
//
// // Field is ignored by this package.
// Field int `url:"-"`
//
// // Field appears as URL parameter "myName".
// Field int `url:"myName"`
//
// // Field appears as URL parameter "myName" and the field is omitted if
// // its value is empty
// Field int `url:"myName,omitempty"`
//
// // Field appears as URL parameter "Field" (the default), but the field
// // is skipped if empty. Note the leading comma.
// Field int `url:",omitempty"`
//
// For encoding individual field values, the following type-dependent rules
// apply:
//
// Boolean values default to encoding as the strings "true" or "false".
// Including the "int" option signals that the field should be encoded as the
// strings "1" or "0".
//
// time.Time values default to encoding as RFC3339 timestamps. Including the
// "unix" option signals that the field should be encoded as a Unix time (see
// time.Unix())
//
// Slice and Array values default to encoding as multiple URL values of the
// same name. Including the "comma" option signals that the field should be
// encoded as a single comma-delimited value. Including the "space" option
// similarly encodes the value as a single space-delimited string. Including
// the "semicolon" option will encode the value as a semicolon-delimited string.
// Including the "brackets" option signals that the multiple URL values should
// have "[]" appended to the value name. "numbered" will append a number to
// the end of each incidence of the value name, example:
// name0=value0&name1=value1, etc.
//
// Anonymous struct fields are usually encoded as if their inner exported
// fields were fields in the outer struct, subject to the standard Go
// visibility rules. An anonymous struct field with a name given in its URL
// tag is treated as having that name, rather than being anonymous.
//
// Non-nil pointer values are encoded as the value pointed to.
//
// Nested structs are encoded including parent fields in value names for
// scoping. e.g:
//
// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO"
//
// All other values are encoded using their default string representation.
//
// Multiple fields that encode to the same URL parameter name will be included
// as multiple URL values of the same name.
func Values(v interface{}) (url.Values, error) {
values := make(url.Values)
val := reflect.ValueOf(v)
for val.Kind() == reflect.Ptr {
if val.IsNil() {
return values, nil
}
val = val.Elem()
}
if v == nil {
return values, nil
}
if val.Kind() != reflect.Struct {
return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind())
}
err := reflectValue(values, val, "")
return values, err
}
// reflectValue populates the values parameter from the struct fields in val.
// Embedded structs are followed recursively (using the rules defined in the
// Values function documentation) breadth-first.
func reflectValue(values url.Values, val reflect.Value, scope string) error {
var embedded []reflect.Value
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
sf := typ.Field(i)
if sf.PkgPath != "" && !sf.Anonymous { // unexported
continue
}
sv := val.Field(i)
tag := sf.Tag.Get("url")
if tag == "-" {
continue
}
name, opts := parseTag(tag)
if name == "" {
if sf.Anonymous && sv.Kind() == reflect.Struct {
// save embedded struct for later processing
embedded = append(embedded, sv)
continue
}
name = sf.Name
}
if scope != "" {
name = scope + "[" + name + "]"
}
if opts.Contains("omitempty") && isEmptyValue(sv) {
continue
}
if sv.Type().Implements(encoderType) {
if !reflect.Indirect(sv).IsValid() {
sv = reflect.New(sv.Type().Elem())
}
m := sv.Interface().(Encoder)
if err := m.EncodeValues(name, &values); err != nil {
return err
}
continue
}
if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
var del byte
if opts.Contains("comma") {
del = ','
} else if opts.Contains("space") {
del = ' '
} else if opts.Contains("semicolon") {
del = ';'
} else if opts.Contains("brackets") {
name = name + "[]"
}
if del != 0 {
s := new(bytes.Buffer)
first := true
for i := 0; i < sv.Len(); i++ {
if first {
first = false
} else {
s.WriteByte(del)
}
s.WriteString(valueString(sv.Index(i), opts))
}
values.Add(name, s.String())
} else {
for i := 0; i < sv.Len(); i++ {
k := name
if opts.Contains("numbered") {
k = fmt.Sprintf("%s%d", name, i)
}
values.Add(k, valueString(sv.Index(i), opts))
}
}
continue
}
for sv.Kind() == reflect.Ptr {
if sv.IsNil() {
break
}
sv = sv.Elem()
}
if sv.Type() == timeType {
values.Add(name, valueString(sv, opts))
continue
}
if sv.Kind() == reflect.Struct {
reflectValue(values, sv, name)
continue
}
values.Add(name, valueString(sv, opts))
}
for _, f := range embedded {
if err := reflectValue(values, f, scope); err != nil {
return err
}
}
return nil
}
// valueString returns the string representation of a value.
func valueString(v reflect.Value, opts tagOptions) string {
for v.Kind() == reflect.Ptr {
if v.IsNil() {
return ""
}
v = v.Elem()
}
if v.Kind() == reflect.Bool && opts.Contains("int") {
if v.Bool() {
return "1"
}
return "0"
}
if v.Type() == timeType {
t := v.Interface().(time.Time)
if opts.Contains("unix") {
return strconv.FormatInt(t.Unix(), 10)
}
return t.Format(time.RFC3339)
}
return fmt.Sprint(v.Interface())
}
// isEmptyValue checks if a value should be considered empty for the purposes
// of omitting fields with the "omitempty" option.
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
if v.Type() == timeType {
return v.Interface().(time.Time).IsZero()
}
return false
}
// tagOptions is the string following a comma in a struct field's "url" tag, or
// the empty string. It does not include the leading comma.
type tagOptions []string
// parseTag splits a struct field's url tag into its name and comma-separated
// options.
func parseTag(tag string) (string, tagOptions) {
s := strings.Split(tag, ",")
return s[0], s[1:]
}
// Contains checks whether the tagOptions contains the specified option.
func (o tagOptions) Contains(option string) bool {
for _, s := range o {
if s == option {
return true
}
}
return false
}

View File

@@ -0,0 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 0.2.1
[bumpversion:file:encode.go]

27
vendor/github.com/mozillazg/go-httpheader/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,27 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
dist/
cover.html
cover.out

25
vendor/github.com/mozillazg/go-httpheader/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,25 @@
language: go
go:
- 1.6
- 1.7
- 1.8
- tip
sudo: false
before_install:
- go get github.com/mattn/goveralls
install:
- go get
- go build
script:
- make test
- $HOME/gopath/bin/goveralls -service=travis-ci -ignore=vendor/
matrix:
allow_failures:
- go: 1.6
- go: 1.7
- go: tip

15
vendor/github.com/mozillazg/go-httpheader/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,15 @@
# Changelog
## 0.2.1 (2018-11-03)
* add go.mod file to identify as a module
## 0.2.0 (2017-06-24)
* support http.Header field.
## 0.1.0 (2017-06-10)
* Initial Release

21
vendor/github.com/mozillazg/go-httpheader/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 mozillazg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

15
vendor/github.com/mozillazg/go-httpheader/Makefile generated vendored Normal file
View File

@@ -0,0 +1,15 @@
help:
@echo "test run test"
@echo "lint run lint"
.PHONY: test
test:
go test -v -cover -coverprofile cover.out
go tool cover -html=cover.out -o cover.html
.PHONY: lint
lint:
gofmt -s -w .
goimports -w .
golint .
go vet

63
vendor/github.com/mozillazg/go-httpheader/README.md generated vendored Normal file
View File

@@ -0,0 +1,63 @@
# go-httpheader
go-httpheader is a Go library for encoding structs into Header fields.
[![Build Status](https://img.shields.io/travis/mozillazg/go-httpheader/master.svg)](https://travis-ci.org/mozillazg/go-httpheader)
[![Coverage Status](https://img.shields.io/coveralls/mozillazg/go-httpheader/master.svg)](https://coveralls.io/r/mozillazg/go-httpheader?branch=master)
[![Go Report Card](https://goreportcard.com/badge/github.com/mozillazg/go-httpheader)](https://goreportcard.com/report/github.com/mozillazg/go-httpheader)
[![GoDoc](https://godoc.org/github.com/mozillazg/go-httpheader?status.svg)](https://godoc.org/github.com/mozillazg/go-httpheader)
## install
`go get -u github.com/mozillazg/go-httpheader`
## usage
```go
package main
import (
"fmt"
"net/http"
"github.com/mozillazg/go-httpheader"
)
type Options struct {
hide string
ContentType string `header:"Content-Type"`
Length int
XArray []string `header:"X-Array"`
TestHide string `header:"-"`
IgnoreEmpty string `header:"X-Empty,omitempty"`
IgnoreEmptyN string `header:"X-Empty-N,omitempty"`
CustomHeader http.Header
}
func main() {
opt := Options{
hide: "hide",
ContentType: "application/json",
Length: 2,
XArray: []string{"test1", "test2"},
TestHide: "hide",
IgnoreEmptyN: "n",
CustomHeader: http.Header{
"X-Test-1": []string{"233"},
"X-Test-2": []string{"666"},
},
}
h, _ := httpheader.Header(opt)
fmt.Printf("%#v", h)
// h:
// http.Header{
// "X-Test-1": []string{"233"},
// "X-Test-2": []string{"666"},
// "Content-Type": []string{"application/json"},
// "Length": []string{"2"},
// "X-Array": []string{"test1", "test2"},
// "X-Empty-N": []string{"n"},
//}
}
```

290
vendor/github.com/mozillazg/go-httpheader/encode.go generated vendored Normal file
View File

@@ -0,0 +1,290 @@
// Package query implements encoding of structs into http.Header fields.
//
// As a simple example:
//
// type Options struct {
// ContentType string `header:"Content-Type"`
// Length int
// }
//
// opt := Options{"application/json", 2}
// h, _ := httpheader.Header(opt)
// fmt.Printf("%#v", h)
// // will output:
// // http.Header{"Content-Type":[]string{"application/json"},"Length":[]string{"2"}}
//
// The exact mapping between Go values and http.Header is described in the
// documentation for the Header() function.
package httpheader
import (
"fmt"
"net/http"
"reflect"
"strconv"
"strings"
"time"
)
const tagName = "header"
// Version ...
const Version = "0.2.1"
var timeType = reflect.TypeOf(time.Time{})
var headerType = reflect.TypeOf(http.Header{})
var encoderType = reflect.TypeOf(new(Encoder)).Elem()
// Encoder is an interface implemented by any type that wishes to encode
// itself into Header fields in a non-standard way.
type Encoder interface {
EncodeHeader(key string, v *http.Header) error
}
// Header returns the http.Header encoding of v.
//
// Header expects to be passed a struct, and traverses it recursively using the
// following encoding rules.
//
// Each exported struct field is encoded as a Header field unless
//
// - the field's tag is "-", or
// - the field is empty and its tag specifies the "omitempty" option
//
// The empty values are false, 0, any nil pointer or interface value, any array
// slice, map, or string of length zero, and any time.Time that returns true
// for IsZero().
//
// The Header field name defaults to the struct field name but can be
// specified in the struct field's tag value. The "header" key in the struct
// field's tag value is the key name, followed by an optional comma and
// options. For example:
//
// // Field is ignored by this package.
// Field int `header:"-"`
//
// // Field appears as Header field "X-Name".
// Field int `header:"X-Name"`
//
// // Field appears as Header field "X-Name" and the field is omitted if
// // its value is empty
// Field int `header:"X-Name,omitempty"`
//
// // Field appears as Header field "Field" (the default), but the field
// // is skipped if empty. Note the leading comma.
// Field int `header:",omitempty"`
//
// For encoding individual field values, the following type-dependent rules
// apply:
//
// Boolean values default to encoding as the strings "true" or "false".
// Including the "int" option signals that the field should be encoded as the
// strings "1" or "0".
//
// time.Time values default to encoding as RFC1123("Mon, 02 Jan 2006 15:04:05 GMT")
// timestamps. Including the "unix" option signals that the field should be
// encoded as a Unix time (see time.Unix())
//
// Slice and Array values default to encoding as multiple Header values of the
// same name. example:
// X-Name: []string{"Tom", "Jim"}, etc.
//
// http.Header values will be used to extend the Header fields.
//
// Anonymous struct fields are usually encoded as if their inner exported
// fields were fields in the outer struct, subject to the standard Go
// visibility rules. An anonymous struct field with a name given in its Header
// tag is treated as having that name, rather than being anonymous.
//
// Non-nil pointer values are encoded as the value pointed to.
//
// All other values are encoded using their default string representation.
//
// Multiple fields that encode to the same Header filed name will be included
// as multiple Header values of the same name.
func Header(v interface{}) (http.Header, error) {
h := make(http.Header)
val := reflect.ValueOf(v)
for val.Kind() == reflect.Ptr {
if val.IsNil() {
return h, nil
}
val = val.Elem()
}
if v == nil {
return h, nil
}
if val.Kind() != reflect.Struct {
return nil, fmt.Errorf("httpheader: Header() expects struct input. Got %v", val.Kind())
}
err := reflectValue(h, val)
return h, err
}
// reflectValue populates the header fields from the struct fields in val.
// Embedded structs are followed recursively (using the rules defined in the
// Values function documentation) breadth-first.
func reflectValue(header http.Header, val reflect.Value) error {
var embedded []reflect.Value
typ := val.Type()
for i := 0; i < typ.NumField(); i++ {
sf := typ.Field(i)
if sf.PkgPath != "" && !sf.Anonymous { // unexported
continue
}
sv := val.Field(i)
tag := sf.Tag.Get(tagName)
if tag == "-" {
continue
}
name, opts := parseTag(tag)
if name == "" {
if sf.Anonymous && sv.Kind() == reflect.Struct {
// save embedded struct for later processing
embedded = append(embedded, sv)
continue
}
name = sf.Name
}
if opts.Contains("omitempty") && isEmptyValue(sv) {
continue
}
if sv.Type().Implements(encoderType) {
if !reflect.Indirect(sv).IsValid() {
sv = reflect.New(sv.Type().Elem())
}
m := sv.Interface().(Encoder)
if err := m.EncodeHeader(name, &header); err != nil {
return err
}
continue
}
if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array {
for i := 0; i < sv.Len(); i++ {
k := name
header.Add(k, valueString(sv.Index(i), opts))
}
continue
}
for sv.Kind() == reflect.Ptr {
if sv.IsNil() {
break
}
sv = sv.Elem()
}
if sv.Type() == timeType {
header.Add(name, valueString(sv, opts))
continue
}
if sv.Type() == headerType {
h := sv.Interface().(http.Header)
for k, vs := range h {
for _, v := range vs {
header.Add(k, v)
}
}
continue
}
if sv.Kind() == reflect.Struct {
reflectValue(header, sv)
continue
}
header.Add(name, valueString(sv, opts))
}
for _, f := range embedded {
if err := reflectValue(header, f); err != nil {
return err
}
}
return nil
}
// valueString returns the string representation of a value.
func valueString(v reflect.Value, opts tagOptions) string {
for v.Kind() == reflect.Ptr {
if v.IsNil() {
return ""
}
v = v.Elem()
}
if v.Kind() == reflect.Bool && opts.Contains("int") {
if v.Bool() {
return "1"
}
return "0"
}
if v.Type() == timeType {
t := v.Interface().(time.Time)
if opts.Contains("unix") {
return strconv.FormatInt(t.Unix(), 10)
}
return t.Format(http.TimeFormat)
}
return fmt.Sprint(v.Interface())
}
// isEmptyValue checks if a value should be considered empty for the purposes
// of omitting fields with the "omitempty" option.
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
if v.Type() == timeType {
return v.Interface().(time.Time).IsZero()
}
return false
}
// tagOptions is the string following a comma in a struct field's "header" tag, or
// the empty string. It does not include the leading comma.
type tagOptions []string
// parseTag splits a struct field's header tag into its name and comma-separated
// options.
func parseTag(tag string) (string, tagOptions) {
s := strings.Split(tag, ",")
return s[0], s[1:]
}
// Contains checks whether the tagOptions contains the specified option.
func (o tagOptions) Contains(option string) bool {
for _, s := range o {
if s == option {
return true
}
}
return false
}

1
vendor/github.com/mozillazg/go-httpheader/go.mod generated vendored Normal file
View File

@@ -0,0 +1 @@
module github.com/mozillazg/go-httpheader

View File

@@ -1,128 +0,0 @@
package cos
import (
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
)
func (conn *Conn) signHeader(req *http.Request, params map[string]interface{}, headers map[string]string) {
signTime := getSignTime()
signature := conn.getSignature(req, params, headers, signTime)
authStr := fmt.Sprintf("q-sign-algorithm=sha1&q-ak=%s&q-sign-time=%s&q-key-time=%s&q-header-list=%s&q-url-param-list=%s&q-signature=%s",
conn.conf.SecretID, signTime, signTime, getHeadKeys(headers), getParamKeys(params), signature)
req.Header.Set("Authorization", authStr)
}
func getSignTime() string {
now := time.Now()
expired := now.Add(time.Second * 1800)
return fmt.Sprintf("%d;%d", now.Unix(), expired.Unix())
}
func getHeadKeys(headers map[string]string) string {
if headers == nil || len(headers) == 0 {
return ""
}
tmp := []string{}
for k := range headers {
tmp = append(tmp, strings.ToLower(k))
}
sort.Strings(tmp)
return strings.Join(tmp, ";")
}
func getParamKeys(params map[string]interface{}) string {
if params == nil || len(params) == 0 {
return ""
}
tmp := []string{}
for k := range params {
tmp = append(tmp, strings.ToLower(k))
}
sort.Strings(tmp)
return strings.Join(tmp, ";")
}
func (conn *Conn) getSignature(req *http.Request, params map[string]interface{}, headers map[string]string, signTime string) string {
httpString := fmt.Sprintf("%s\n%s\n%s\n%s\n", strings.ToLower(req.Method),
req.URL.Path, getParamStr(params), getHeadStr(headers))
httpString = sha(httpString)
signKey := hmacSha(conn.conf.SecretKey, signTime)
signStr := fmt.Sprintf("sha1\n%s\n%s\n", signTime, httpString)
return hmacSha(signKey, signStr)
}
func interfaceToString(i interface{}) string {
switch x := i.(type) {
case string:
return x
case int:
return strconv.Itoa(x)
case int64:
return strconv.FormatInt(x, 10)
case uint64:
return strconv.FormatUint(x, 10)
default:
return ""
}
}
func getParamStr(params map[string]interface{}) string {
if params == nil || len(params) == 0 {
return ""
}
tmp := []string{}
for k, v := range params {
str := strings.ToLower(fmt.Sprintf("%s=%s", k, interfaceToString(v)))
tmp = append(tmp, str)
}
sort.Strings(tmp)
return strings.Join(tmp, "&")
}
func getHeadStr(headers map[string]string) string {
if headers == nil || len(headers) == 0 {
return ""
}
tmp := []string{}
for k, v := range headers {
str := fmt.Sprintf("%s=%s", strings.ToLower(k), escape(v))
tmp = append(tmp, str)
}
sort.Strings(tmp)
return strings.Join(tmp, "&")
}
func sha(s string) string {
sha := sha1.New()
sha.Write([]byte(s))
b := sha.Sum(nil)
return hex.EncodeToString(b)
}
func hmacSha(k, s string) string {
enc := hmac.New(sha1.New, []byte(k))
enc.Write([]byte(s))
b := enc.Sum(nil)
return hex.EncodeToString(b)
}

View File

@@ -1,315 +0,0 @@
package cos
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"encoding/xml"
"fmt"
"io"
"net/http"
"os"
"strings"
)
// Bucket bucket
type Bucket struct {
Name string
conn *Conn
}
// ObjectSlice object slice
type ObjectSlice struct {
UploadID string
Size int64
Offset int64
Number int
MD5 string
Dst string
Result bool
}
// 获得云存储上文件信息
func (b *Bucket) HeadObject(ctx context.Context, object string) error {
resq, err := b.conn.Do(ctx, http.MethodHead, b.Name, object, nil, nil, nil)
if err == nil {
defer resq.Body.Close()
} else {
for k, v := range resq.Header {
value := fmt.Sprintf("%s", v)
fmt.Printf("%-18s: %s\n", k, strings.Replace(strings.Replace(value, "[", "", -1), "]", "", -1))
}
}
return err
}
func (b *Bucket) UploadObject(ctx context.Context, object string, content io.Reader, acl *AccessControl) error {
res, err := b.conn.Do(ctx, http.MethodPut, b.Name, object, nil, acl.GenHead(), content)
if err == nil {
defer res.Body.Close()
}
return err
}
func (b *Bucket) CopyObject(ctx context.Context, src, dst string, acl *AccessControl) error {
srcURL := fmt.Sprintf("%s-%s.cos.%s.%s/%s", b.Name, b.conn.conf.AppID, b.conn.conf.Region, b.conn.conf.Domain, dst)
header := map[string]string{
"x-cos-source-url": srcURL,
}
res, err := b.conn.Do(ctx, http.MethodPut, b.Name, dst, nil, header, nil)
if err == nil {
defer res.Body.Close()
}
return err
}
func (b *Bucket) DeleteObject(ctx context.Context, obj string) error {
res, err := b.conn.Do(ctx, http.MethodDelete, b.Name, obj, nil, nil, nil)
if err == nil {
defer res.Body.Close()
}
return err
}
func (b *Bucket) DownloadObject(ctx context.Context, object string, w io.Writer) error {
res, err := b.conn.Do(ctx, http.MethodGet, b.Name, object, nil, nil, nil)
if err != nil {
return err
}
_, err = io.Copy(w, res.Body)
return err
}
// UploadObjectBySlice upload by slice
func (b *Bucket) UploadObjectBySlice(ctx context.Context, dst, src string, taskNum int, headers map[string]string) error {
if taskNum < 1 {
return ParamError{"taskNum 必须大于1"}
}
uploadID, err := b.InitSliceUpload(ctx, dst, headers)
if err != nil {
return err
}
fd, err := os.Open(src)
if err != nil {
return err
}
defer fd.Close()
slices, err := b.PerformSliceUpload(ctx, dst, uploadID, fd, taskNum)
if err != nil {
return err
}
err = b.CompleteSliceUpload(ctx, dst, uploadID, fd, slices)
return err
}
// InitSliceUpload init upload by slice
func (b *Bucket) InitSliceUpload(ctx context.Context, obj string, headers map[string]string) (string, error) {
param := map[string]interface{}{
"uploads": "",
}
res, err := b.conn.Do(ctx, http.MethodPost, b.Name, obj, param, headers, nil)
if err != nil {
return "", err
}
defer res.Body.Close()
imur := &InitiateMultipartUploadResult{}
err = XMLDecode(res.Body, imur)
if err != nil {
return "", err
}
return imur.UploadID, nil
}
// CompleteSliceUpload finish slice Upload
func (b *Bucket) CompleteSliceUpload(ctx context.Context, dst, uploadID string, fd *os.File, slice []*ObjectSlice) error {
cmu := &CompleteMultipartUpload{}
cmu.Part = []struct {
PartNumber int
ETag string
}{}
for _, osl := range slice {
cmu.Part = append(cmu.Part, struct {
PartNumber int
ETag string
}{PartNumber: osl.Number, ETag: osl.MD5})
}
cmuXML, err := xml.Marshal(cmu)
if err != nil {
return err
}
param := map[string]interface{}{
"uploadId": uploadID,
}
res, err := b.conn.Do(ctx, http.MethodPost, b.Name, dst, param, nil, bytes.NewReader(cmuXML))
if err == nil {
defer res.Body.Close()
}
return err
}
// PerformSliceUpload perform slice upload
func (b *Bucket) PerformSliceUpload(ctx context.Context, dst, uploadID string, fd *os.File, taskNum int) ([]*ObjectSlice, error) {
oss, err := b.getFileSlices(fd, uploadID, dst)
if err != nil {
return nil, err
}
jobNum := len(oss)
jobs := make(chan *ObjectSlice, jobNum)
result := make(chan *ObjectSlice, jobNum)
for i := 0; i < taskNum; i++ {
go b.Worker(ctx, fd, jobs, result)
}
for _, osl := range oss {
jobs <- osl
}
close(jobs)
for i := 0; i < jobNum; i++ {
res := <-result
if !res.Result {
return nil, SliceError{fmt.Sprintf("part info : num:%d, md5:%s", res.Number, res.MD5)}
}
}
return oss, nil
}
// Worker woker for slice upload
func (b *Bucket) Worker(ctx context.Context, fd *os.File, jobs <-chan *ObjectSlice, result chan<- *ObjectSlice) {
for job := range jobs {
content, err := getFilePartContent(fd, job.Offset, job.Size)
if err != nil {
continue
}
err = b.UploadSlice(ctx, job.UploadID, job.Dst, job.Number, job.MD5, content)
if err == nil {
job.Result = true
} else {
job.Result = false
}
result <- job
}
}
// UploadSlice upload one slice
func (b *Bucket) UploadSlice(ctx context.Context, uploadID, dst string, number int, etag string, content io.Reader) error {
param := map[string]interface{}{
"PartNumber": number,
"uploadId": uploadID,
}
res, err := b.conn.Do(ctx, http.MethodPut, b.Name, dst, param, nil, content)
if err != nil {
return FileError{"PUT数据错误:" + err.Error()}
}
defer res.Body.Close()
if strings.Trim(res.Header.Get("Etag"), "\"") != etag {
return FileError{"cos-etag与文件MD5不匹配"}
}
return err
}
func (b *Bucket) getFileSlices(fd *os.File, uploadID, dst string) ([]*ObjectSlice, error) {
sliceSize := b.conn.conf.PartSize
fi, err := fd.Stat()
if err != nil {
return nil, err
}
fileSize := fi.Size()
oss := []*ObjectSlice{}
var i int
var offset int64
for fileSize > 0 {
var size int64
if fileSize > sliceSize {
size = sliceSize
} else {
size = fileSize
}
i++
md5, err := getFileMD5(fd, offset, size)
if err != nil {
return nil, err
}
osl := &ObjectSlice{}
osl.Size = size
osl.Number = i
osl.Offset = offset
osl.UploadID = uploadID
osl.MD5 = md5
osl.Dst = dst
oss = append(oss, osl)
fileSize -= sliceSize
offset += sliceSize
}
return oss, nil
}
func getFileMD5(fd *os.File, offset, size int64) (string, error) {
buf := make([]byte, size)
_, err := fd.ReadAt(buf, offset)
if err != nil {
return "", err
}
encoder := md5.New()
encoder.Write(buf)
b := encoder.Sum(nil)
return hex.EncodeToString(b), nil
}
func getFilePartContent(fd *os.File, offset, size int64) (io.Reader, error) {
buf := make([]byte, size)
_, err := fd.ReadAt(buf, offset)
if err != nil {
return nil, err
}
return bytes.NewReader(buf), nil
}
func (b *Bucket) AbortUpload(ctx context.Context, obj, uploadID string) error {
param := map[string]interface{}{
"uploadId": uploadID,
}
_, err := b.conn.Do(ctx, http.MethodDelete, b.Name, obj, param, nil, nil)
return err
}
// ObjectExists object exists
func (b *Bucket) ObjectExists(ctx context.Context, obj string) error {
_, err := b.conn.Do(ctx, http.MethodHead, b.Name, obj, nil, nil, nil)
return err
}

View File

@@ -1,164 +0,0 @@
package cos
import (
"context"
"net/http"
"time"
)
// Client 客户端, cos的句柄
type Client struct {
conn *Conn
}
// New cos包的入口
func New(o *Option) *Client {
client := Client{}
conf := getDefaultConf()
conf.AppID = o.AppID
conf.SecretID = o.SecretID
conf.SecretKey = o.SecretKey
conf.Region = o.Region
if o.Domain != "" {
conf.Domain = o.Domain
}
conn := Conn{&http.Client{}, conf}
client.conn = &conn
return &client
}
// GetTimeoutCtx 获取一个带超时的context
func GetTimeoutCtx(timeout time.Duration) context.Context {
ctx, _ := context.WithTimeout(context.Background(), timeout)
return ctx
}
// Bucket get bucket
func (c *Client) Bucket(name string) *Bucket {
return &Bucket{name, c.conn}
}
// GetBucketList 获取bucketlist
func (c *Client) GetBucketList(ctx context.Context) (*ListAllMyBucketsResult, error) {
req, err := http.NewRequest(http.MethodGet, "http://service.cos.myqcloud.com/", nil)
if err != nil {
return nil, err
}
req = req.WithContext(ctx)
c.conn.signHeader(req, nil, nil)
res, err := c.conn.c.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
res, err = checkHTTPErr(res)
if err != nil {
return nil, err
}
labr := &ListAllMyBucketsResult{}
err = XMLDecode(res.Body, labr)
if err != nil {
return nil, err
}
return labr, err
}
// CreateBucket 建立bucket
func (c *Client) CreateBucket(ctx context.Context, name string, acl *AccessControl) error {
res, err := c.conn.Do(ctx, http.MethodPut, name, "", nil, acl.GenHead(), nil)
if err == nil {
defer res.Body.Close()
}
return err
}
// DeleteBucket delete a bucket
func (c *Client) DeleteBucket(ctx context.Context, name string) error {
_, err := c.conn.Do(ctx, http.MethodDelete, name, "", nil, nil, nil)
return err
}
// GetBucketACL get bucket's acl
func (c *Client) GetBucketACL(ctx context.Context, name string) (*AccessControlPolicy, error) {
params := map[string]interface{}{"acl": ""}
res, err := c.conn.Do(ctx, http.MethodGet, name, "", params, nil, nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
aclp := &AccessControlPolicy{}
err = XMLDecode(res.Body, aclp)
if err != nil {
return nil, err
}
return aclp, nil
}
// SetBucketACL set bucket's acl
func (c *Client) SetBucketACL(ctx context.Context, name string, acl *AccessControl) error {
params := map[string]interface{}{"acl": ""}
res, err := c.conn.Do(ctx, http.MethodPut, name, "", params, acl.GenHead(), nil)
if err == nil {
defer res.Body.Close()
}
return err
}
// BucketExists bucket exists?
func (c *Client) BucketExists(ctx context.Context, name string) error {
res, err := c.conn.Do(ctx, http.MethodHead, name, "", nil, nil, nil)
if err == nil {
defer res.Body.Close()
}
return err
}
// ListBucketContents list
func (c *Client) ListBucketContents(ctx context.Context, name string, qc *QueryCondition) (*ListBucketResult, error) {
resp, err := c.conn.Do(ctx, http.MethodGet, name, "", qc.GenParams(), nil, nil)
if err != nil {
return nil, err
}
defer resp.Body.Close()
lbr := &ListBucketResult{}
err = XMLDecode(resp.Body, lbr)
if err != nil {
return nil, err
}
return lbr, nil
}
// ListUploading list uploading task
func (c *Client) ListUploading(ctx context.Context, bucket string, lu *ListUploadParam) (*ListMultipartUploadsResult, error) {
res, err := c.conn.Do(ctx, http.MethodGet, bucket, "", lu.GenParams(), nil, nil)
if err != nil {
return nil, err
}
defer res.Body.Close()
lmur := &ListMultipartUploadsResult{}
err = XMLDecode(res.Body, lmur)
if err != nil {
return nil, err
}
return lmur, nil
}

View File

@@ -1,31 +0,0 @@
package cos
const (
defaultPartSize = 80 * 1024 * 1024
defaultRetryTimes = 3
defaultUA = "cos-go-sdk-v5.2.9"
defaultDomain = "myqcloud.com"
)
// Conf config struct
type Conf struct {
AppID string
SecretID string
SecretKey string
Region string
PartSize int64
RetryTimes int
UA string
Domain string
Bucket string
}
func getDefaultConf() *Conf {
conf := Conf{}
conf.PartSize = defaultPartSize
conf.RetryTimes = defaultRetryTimes
conf.UA = defaultUA
conf.Domain = defaultDomain
return &conf
}

View File

@@ -1,141 +0,0 @@
package cos
import (
"bytes"
"context"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// Conn http 请求类
type Conn struct {
c *http.Client
conf *Conf
}
func (conn *Conn) Do(ctx context.Context, method, bucket, object string, params map[string]interface{}, headers map[string]string, body io.Reader) (*http.Response, error) {
queryStr := getQueryStr(params)
url := conn.buildURL(bucket, object, queryStr)
switch body.(type) {
case *bytes.Buffer, *bytes.Reader, *strings.Reader:
default:
if body != nil {
b, err := ioutil.ReadAll(body)
if err != nil {
return nil, err
}
body = bytes.NewReader(b)
}
}
req, err := http.NewRequest(method, url, body)
if err != nil {
return nil, err
}
conn.signHeader(req, params, headers)
req.Header.Set("User-Agent", conn.conf.UA)
setHeader(req, headers)
res, err := conn.c.Do(req)
if err != nil {
return nil, err
}
if res.StatusCode < 200 || res.StatusCode >= 300 {
defer res.Body.Close()
return checkHTTPErr(res)
}
return res, nil
}
func getQueryStr(params map[string]interface{}) string {
if params == nil || len(params) == 0 {
return ""
}
buf := new(bytes.Buffer)
buf.WriteString("?")
for k, v := range params {
buf.WriteString(k)
vs := interfaceToString(v)
if vs == "" {
buf.WriteString("&")
continue
}
buf.WriteString("=")
buf.WriteString(vs)
buf.WriteString("&")
}
return strings.Trim(buf.String(), "&")
}
func (conn *Conn) buildURL(bucket, object, queryStr string) string {
domain := fmt.Sprintf("%s-%s.cos.%s.%s", bucket, conn.conf.AppID, conn.conf.Region, conn.conf.Domain)
url := fmt.Sprintf("http://%s/%s%s", domain, escape(object), queryStr)
return url
}
func escape(str string) string {
//go语言中将空格编码为+,需要改为%20
return strings.Replace(url.QueryEscape(str), "+", "%20", -1)
}
func setHeader(req *http.Request, headers map[string]string) {
if headers == nil {
return
}
for k, v := range headers {
req.Header.Set(k, v)
}
}
func checkHTTPErr(res *http.Response) (*http.Response, error) {
if res.StatusCode >= 200 && res.StatusCode < 300 {
return res, nil
}
err := HTTPError{}
err.Code = res.StatusCode
if res.StatusCode >= 300 && res.StatusCode < 400 {
err.Message = "资源被重定向"
}
if res.StatusCode >= 400 && res.StatusCode < 500 {
err.Message = "请求被拒绝"
}
if res.StatusCode >= 500 {
err.Message = "cos服务器错误"
}
if res.ContentLength > 0 {
resErr := &Error{}
e := XMLDecode(res.Body, resErr)
if e != nil {
return nil, err
}
err.Message += resErr.Message
}
return res, err
}
// XMLDecode xml解析方法
func XMLDecode(r io.Reader, i interface{}) error {
jd := xml.NewDecoder(r)
err := jd.Decode(i)
return err
}

View File

@@ -1,10 +0,0 @@
package cos
type Option struct {
AppID string `mapstructure:"app_id" json:"app_id"`
SecretID string `mapstructure:"secret_id" json:"secret_id"`
SecretKey string `mapstructure:"secret_key" json:"secret_key"`
Region string `mapstructure:"region" json:"region"`
Domain string `mapstructure:"domain" json:"domain"`
Bucket string `mapstructure:"bucket" json:"bucket"`
}

View File

@@ -1,102 +0,0 @@
package cos
// AccessControl privilige
type AccessControl struct {
ACL string
GrantRead string
GrantWrite string
FullControl string
}
// GenHead 生成http head
func (acl *AccessControl) GenHead() map[string]string {
header := map[string]string{
"x-cos-acl": acl.ACL,
"x-cos-grant-read": acl.GrantRead,
"x-cos-grant-write": acl.GrantWrite,
"x-cos-grant-full-control": acl.FullControl,
}
for k, v := range header {
if v == "" {
delete(header, k)
}
}
return header
}
// QueryCondition query condition
type QueryCondition struct {
Prefix string
Delimiter string
EncodingType string
Marker string
MaxKeys int
}
// GenParams generate params:map[string]interface{}
func (qc *QueryCondition) GenParams() map[string]interface{} {
params := map[string]interface{}{
"prefix": qc.Prefix,
"delimiter": qc.Delimiter,
"encoding-type": qc.EncodingType,
"marker": qc.Marker,
"max-keys": qc.MaxKeys,
}
for k, v := range params {
if v == "" {
delete(params, k)
}
if v == 0 {
delete(params, k)
}
}
return params
}
// ListUploadParam list upload param
type ListUploadParam struct {
Prefix string
Delimiter string
EncodingType string
MaxUploads int
KeyMarker string
UploadIDMarker string
}
// GenParams generate params for request
func (lup *ListUploadParam) GenParams() map[string]interface{} {
params := map[string]interface{}{
"prefix": lup.Prefix,
"delimiter": lup.Delimiter,
"encoding-type": lup.EncodingType,
"max-uploads": lup.MaxUploads,
"key-marker": lup.KeyMarker,
"upload-id-marker": lup.UploadIDMarker,
}
for k, v := range params {
if v == "" {
delete(params, k)
}
if v == 0 {
delete(params, k)
}
}
params["uploads"] = ""
return params
}
// CompleteMultipartUpload compelete slice upload
type CompleteMultipartUpload struct {
Part []struct {
PartNumber int
ETag string
}
}

View File

@@ -1,156 +0,0 @@
package cos
import (
"fmt"
)
// ListAllMyBucketsResult 获取bucket列表的结果
type ListAllMyBucketsResult struct {
Owner struct {
ID string
DisplayName string
}
Buckets struct {
Bucket []struct {
Name string
Location string
CreateDate string
}
}
}
// Error 错误消息
type Error struct {
Code string
Message string
Resource string
RequestID string `xml:"RequestId"`
TraceID string `xml:"TaceId"`
}
// HTTPError http error struct
type HTTPError struct {
Code int
Message string
}
// Error error interface
func (he HTTPError) Error() string {
return fmt.Sprintf("%d:%s", he.Code, he.Message)
}
// AccessControlPolicy acl return
type AccessControlPolicy struct {
Owner struct {
ID string
DisplayName string
}
AccessControlList struct {
Grant []struct {
Grantee struct {
ID string
DisplayName string
}
Permission string
}
}
}
// ListBucketResult list bucket contents result
type ListBucketResult struct {
Name string
EncodingType string `xml:"Encoding-Type"`
Prefix string
Marker string
MaxKeys int
IsTruncated bool
NextMarker string
Contents []struct {
Key string
LastModified string
ETag string
Size int64
Owner struct {
ID string
}
StorageClass string
}
CommonPrefixes []struct {
Prefix string
}
}
// ListMultipartUploadsResult list uploading task
type ListMultipartUploadsResult struct {
Bucket string
EncodingType string `xml:"Encoding-Type"`
KeyMarker string
UploadIDMarker string `xml:"UploadIdMarker"`
NextKeyMarker string
NextUploadIDMarker string `xml:"NextUploadIdMarker"`
MaxUploads int
IsTruncated bool
Prefix string
Delimiter string
Upload []struct {
Key string
UploadID string
StorageClass string
Initiator struct {
UIN string
}
Owner struct {
UID string
}
Initiated string
}
CommonPrefixes []struct {
Prefix string
}
}
// InitiateMultipartUploadResult init slice upload
type InitiateMultipartUploadResult struct {
Bucket string
Key string
UploadID string `xml:"UploadId"`
}
// CompleteMultipartUploadResult compeleted slice upload
type CompleteMultipartUploadResult struct {
Location string
Bucket string
Key string
ETag string
}
// SliceError slice upload err
type SliceError struct {
Message string
}
// Error implements error
func (se SliceError) Error() string {
return fmt.Sprintf("上传分片失败:%s", se.Message)
}
// ParamError slice upload err
type ParamError struct {
Message string
}
// Error implements error
func (pe ParamError) Error() string {
return fmt.Sprintf("参数错误:%s", pe.Message)
}
// FileError slice upload err
type FileError struct {
Message string
}
// Error implements error
func (fe FileError) Error() string {
return fmt.Sprintf("文件错误:%s", fe.Message)
}

View File

@@ -0,0 +1,7 @@
[bumpversion]
commit = True
tag = True
current_version = 0.7.0
[bumpversion:file:cos.go]

29
vendor/github.com/tencentyun/cos-go-sdk-v5/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,29 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
dist/
cover.html
cover.out
covprofile
coverage.html

29
vendor/github.com/tencentyun/cos-go-sdk-v5/.travis.yml generated vendored Normal file
View File

@@ -0,0 +1,29 @@
language: go
go:
- '1.7'
- '1.8'
- '1.9'
- 1.10.x
- master
sudo: false
before_install:
- go get github.com/mattn/goveralls
- go get github.com/stretchr/testify
install:
- go get
- go build
- go build github.com/mattn/goveralls
script:
- make test
- make ci-test
- go test -coverprofile=cover.out github.com/toranger/cos-go-sdk-v5
- "${TRAVIS_HOME}/gopath/bin/goveralls -service=travis-ci -coverprofile=cover.out"
matrix:
allow_failures:
- go: 1.7
- go: master
env:
global:
- secure: XXB/cFVnJcAzhOZ2/zplwjhhhireQQGGRbNscPgQ0kpUQCyPZ6oIHvJMafuP4TVTJHEdMiaDxm0HNvgARuopXVaQNmK2UZj6xw40Ud7OT7ZUnw88xkQkXOI5GwG8oz9LqxIXUSItHegKXRLW0e1PoBdjZNv6lxGFAtuOcl9ekAg/q2lGIIQFefz6NK7gCmGYULKe+4J15VFldoYNM0JesxxxArTvtv8+k+U53oUwy9dex6z5oIA1zGIeKLcOD2xXgbjid/Ett3t0B2w3GfJWoM9rGV0eHgveOAUGe5tQkMKvl5LK1hj+93ZmU0MAG7x7t9jYKrFPqU/eDNJRMb4Ro6L7lIXVEKaBUkLx28PnwFQ5D043GBVtQGqYNcldZXIfbyYEHQZlD/BWFOt5YqTpGg+7Wm4NC3Yffqsurzk54juT7FftzVy0A8MFkqO+c5RHrOSUlm01pWXkGLHgZhUP5gEZEuUaoluSQTZksmAUJZ7F8DxwpE4SYBqfN27PZ87rWDNyOqNv1w1trzwx2IfdHHA+vfCZ7UM5e85gxFWUO2tJCUai2q21v3gBrcAgBOb6BwVzbWAorM2zY20f0l21XxOWMakA+r4JJA3s3EmcczcQeeL6pkFIAh+qKdFEPuyQTjH1mGpPzYFNbWtvPXijQo5PqyGrKL8W1t3ovwXMXoE=
- secure: bep0PPD/oYW5zY0QpeeC+WgFIya5DNRVmR92MO+e5BdFlSJPhstoG8bRh91EeftzC/Hyd3PUEIglPqTgZPxwysqW/81plsU95wV3qJi9gPi7+ZtYXH4xZTnaqgZsTr7jsKSVoKHSu7XqCtbSytW8YMN9wRWzG19/9hX2Z79Q6yNy5l9856Oyj1E2IXDjdZLPsWDhnZ8Vvk1wAVy2fc2esqKzHAZwm8n9vee2yR8vz7GXUszzpKvn4R43eNzdlFEHCmN0ANmxLJZmnYDpZHHfNf4slts+0S6I7awFXppuXUDaJPBRCia4XoFeSw+01IW1Vi0kAwvGLhxjJCWc4M/4ZU0byXDT11tDFvWa19NmnbYiizWiXNVecn1oNWYJqIKe7TTAMAtHSXAPmLX0rXuXKzwM09W6yrLFufCxyix9IOnenEbe9WwSdBbhmeLF3Wu/uVGkDog/FsXJM75sk956vV9UKh9zF4B9/NR8szJMF7shEs0Fbru5UUWheqg4AadPl3dhAWuj2+6NANa1LpH3JVD3II9dlXeMmMvsSwDvrYUaX/S8tf6JwZG0zCJK0TYp05rjxH+NIzWaMUTY7+HwYqqK3pOW3San0SlZiMq8N7GSnKUZ7WRQXYSB4gXHrg+mWyeVC7XnqiRtCwVi+LtPMu+YUbg7dwVi0vtKjYZYIUY=
- secure: Ob28vrOuHMKNKEtChkWbsaVv2SwLhcxXMnvGe4XN+y3mFvdhYnwpt6NdgThF8OCZ0761tvTRmvALfiZnO0uORjTtoHKkVPrnVIxlCcode0NVJZNHGn2fqjemdLKCnSeX7hm+9zeLpCnIvC+Sp3iZ3t2AH4AzgFx6nirWO3HwT5l9rNL9Q1CfwlOpNJJ36r9JTHwQnXmOfOmszUNoZ3rtiFXJ8dCi+BgY0lsiIRSiDkAH7KAPf86REM+ww81AaXG4/RuYx1Vj5zQCtZN7XEOViSXEbqqb8SrIFOccDu5FV12djg+4QS7FSjLVGrdIUcn4oI6pS24Et3oXf8xFx6JLYyGGhgZ2BsyJEx5vLQvkTWnMTrwZVRtCQ+g6lMUQpJhL2rBrmVBUqBFb5IH69O7corQm53n5qLM8IiosAQLfbOtML/1PyEpKCG2aOx1377Fx2yzxXW3ucP1PBqCzli0oCM2T52LfiNvZTzkIU6XJebBnzkZXepzOIFSur86kxgvQFElw9ro2X6XXPKU5S25xVaUSvaN1kmqLSkToJ9S1rmDYXnJR4aH0R2GcLw+EkMHFJJoAjnRHxrB4/1vOJbzmfS+qy6ShRhUMSD8gk4YJ6Y7o9h7oekuWOEn+XGhl29U9T5OApzHfoPEGZwLnpHxAiKJtQtv/TNhBIOFCjigsF7U=

21
vendor/github.com/tencentyun/cos-go-sdk-v5/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 mozillazg
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

22
vendor/github.com/tencentyun/cos-go-sdk-v5/Makefile generated vendored Normal file
View File

@@ -0,0 +1,22 @@
help:
@echo "test run test"
@echo "lint run lint"
@echo "example run examples"
.PHONY: test
test:
go test -v -cover -coverprofile cover.out
go tool cover -html=cover.out -o cover.html
.PHONY: lint
lint:
gofmt -s -w .
goimports -w .
golint .
go vet
.PHONY: example
example:
cd example && sh test.sh
ci-test:
cd costesting && go test -v

95
vendor/github.com/tencentyun/cos-go-sdk-v5/README.md generated vendored Normal file
View File

@@ -0,0 +1,95 @@
# cos-go-sdk-v5
腾讯云对象存储服务 COS(Cloud Object Storage) Go SDKAPI 版本V5 版本的 XML API
## Install
`go get -u github.com/tencentyun/cos-go-sdk-v5`
## Usage
```go
package main
import (
"context"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"time"
"github.com/tencentyun/cos-go-sdk-v5"
)
func main() {
//将<bucket>和<region>修改为真实的信息
//bucket的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
u, _ := url.Parse("https://<bucket>.cos.<region>.myqcloud.com")
b := &cos.BaseURL{BucketURL: u}
c := cos.NewClient(b, &http.Client{
//设置超时时间
Timeout: 100 * time.Second,
Transport: &cos.AuthorizationTransport{
//如实填写账号和密钥,也可以设置为环境变量
SecretID: os.Getenv("COS_SECRETID"),
SecretKey: os.Getenv("COS_SECRETKEY"),
},
})
name := "test/hello.txt"
resp, err := c.Object.Get(context.Background(), name, nil)
if err != nil {
panic(err)
}
bs, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close()
fmt.Printf("%s\n", string(bs))
}
```
所有的 API 在 [example](./example/) 目录下都有对应的使用示例。
Service API:
* [x] Get Service使用示例[service/get.go](./example/service/get.go)
Bucket API:
* [x] Get Bucket使用示例[bucket/get.go](./example/bucket/get.go)
* [x] Get Bucket ACL使用示例[bucket/getACL.go](./example/bucket/getACL.go)
* [x] Get Bucket CORS使用示例[bucket/getCORS.go](./example/bucket/getCORS.go)
* [x] Get Bucket Location使用示例[bucket/getLocation.go](./example/bucket/getLocation.go)
* [x] Get Buket Lifecycle使用示例[bucket/getLifecycle.go](./example/bucket/getLifecycle.go)
* [x] Get Bucket Tagging使用示例[bucket/getTagging.go](./example/bucket/getTagging.go)
* [x] Put Bucket使用示例[bucket/put.go](./example/bucket/put.go)
* [x] Put Bucket ACL使用示例[bucket/putACL.go](./example/bucket/putACL.go)
* [x] Put Bucket CORS使用示例[bucket/putCORS.go](./example/bucket/putCORS.go)
* [x] Put Bucket Lifecycle使用示例[bucket/putLifecycle.go](./example/bucket/putLifecycle.go)
* [x] Put Bucket Tagging使用示例[bucket/putTagging.go](./example/bucket/putTagging.go)
* [x] Delete Bucket使用示例[bucket/delete.go](./example/bucket/delete.go)
* [x] Delete Bucket CORS使用示例[bucket/deleteCORS.go](./example/bucket/deleteCORS.go)
* [x] Delete Bucket Lifecycle使用示例[bucket/deleteLifecycle.go](./example/bucket/deleteLifecycle.go)
* [x] Delete Bucket Tagging使用示例[bucket/deleteTagging.go](./example/bucket/deleteTagging.go)
* [x] Head Bucket使用示例[bucket/head.go](./example/bucket/head.go)
* [x] List Multipart Uploads使用示例[bucket/listMultipartUploads.go](./example/bucket/listMultipartUploads.go)
Object API:
* [x] Get Object使用示例[object/get.go](./example/object/get.go)
* [x] Get Object ACL使用示例[object/getACL.go](./example/object/getACL.go)
* [x] Put Object使用示例[object/put.go](./example/object/put.go)
* [x] Put Object ACL使用示例[object/putACL.go](./example/object/putACL.go)
* [x] Put Object Copy使用示例[object/copy.go](./example/object/copy.go)
* [x] Delete Object使用示例[object/delete.go](./example/object/delete.go)
* [x] Delete Multiple Object使用示例[object/deleteMultiple.go](./example/object/deleteMultiple.go)
* [x] Head Object使用示例[object/head.go](./example/object/head.go)
* [x] Options Object使用示例[object/options.go](./example/object/options.go)
* [x] Initiate Multipart Upload使用示例[object/initiateMultipartUpload.go](./example/object/initiateMultipartUpload.go)
* [x] Upload Part使用示例[object/uploadPart.go](./example/object/uploadPart.go)
* [x] List Parts使用示例[object/listParts.go](./example/object/listParts.go)
* [x] Complete Multipart Upload使用示例[object/completeMultipartUpload.go](./example/object/completeMultipartUpload.go)
* [x] Abort Multipart Upload使用示例[object/abortMultipartUpload.go](./example/object/abortMultipartUpload.go)
* [x] Mutipart Upload使用示例[object/MutiUpload.go](./example/object/MutiUpload.go)

305
vendor/github.com/tencentyun/cos-go-sdk-v5/auth.go generated vendored Normal file
View File

@@ -0,0 +1,305 @@
package cos
import (
"crypto/hmac"
"crypto/sha1"
"fmt"
"hash"
"net/http"
"net/url"
"sort"
"strings"
"sync"
"time"
)
const sha1SignAlgorithm = "sha1"
const privateHeaderPrefix = "x-cos-"
const defaultAuthExpire = time.Hour
// 需要校验的 Headers 列表
var needSignHeaders = map[string]bool{
"host": true,
"range": true,
"x-cos-acl": true,
"x-cos-grant-read": true,
"x-cos-grant-write": true,
"x-cos-grant-full-control": true,
"response-content-type": true,
"response-content-language": true,
"response-expires": true,
"response-cache-control": true,
"response-content-disposition": true,
"response-content-encoding": true,
"cache-control": true,
"content-disposition": true,
"content-encoding": true,
"content-type": true,
"content-length": true,
"content-md5": true,
"expect": true,
"expires": true,
"x-cos-content-sha1": true,
"x-cos-storage-class": true,
"if-modified-since": true,
"origin": true,
"access-control-request-method": true,
"access-control-request-headers": true,
"x-cos-object-type": true,
}
func safeURLEncode(s string) string {
s = encodeURIComponent(s)
s = strings.Replace(s, "!", "%21", -1)
s = strings.Replace(s, "'", "%27", -1)
s = strings.Replace(s, "(", "%28", -1)
s = strings.Replace(s, ")", "%29", -1)
s = strings.Replace(s, "*", "%2A", -1)
return s
}
type valuesSignMap map[string][]string
func (vs valuesSignMap) Add(key, value string) {
key = strings.ToLower(key)
vs[key] = append(vs[key], value)
}
func (vs valuesSignMap) Encode() string {
var keys []string
for k := range vs {
keys = append(keys, k)
}
sort.Strings(keys)
var pairs []string
for _, k := range keys {
items := vs[k]
sort.Strings(items)
for _, val := range items {
pairs = append(
pairs,
fmt.Sprintf("%s=%s", safeURLEncode(k), safeURLEncode(val)))
}
}
return strings.Join(pairs, "&")
}
// AuthTime 用于生成签名所需的 q-sign-time 和 q-key-time 相关参数
type AuthTime struct {
SignStartTime time.Time
SignEndTime time.Time
KeyStartTime time.Time
KeyEndTime time.Time
}
// NewAuthTime 生成 AuthTime 的便捷函数
//
// expire: 从现在开始多久过期.
func NewAuthTime(expire time.Duration) *AuthTime {
signStartTime := time.Now()
keyStartTime := signStartTime
signEndTime := signStartTime.Add(expire)
keyEndTime := signEndTime
return &AuthTime{
SignStartTime: signStartTime,
SignEndTime: signEndTime,
KeyStartTime: keyStartTime,
KeyEndTime: keyEndTime,
}
}
// signString return q-sign-time string
func (a *AuthTime) signString() string {
return fmt.Sprintf("%d;%d", a.SignStartTime.Unix(), a.SignEndTime.Unix())
}
// keyString return q-key-time string
func (a *AuthTime) keyString() string {
return fmt.Sprintf("%d;%d", a.KeyStartTime.Unix(), a.KeyEndTime.Unix())
}
// newAuthorization 通过一系列步骤生成最终需要的 Authorization 字符串
func newAuthorization(secretID, secretKey string, req *http.Request, authTime *AuthTime) string {
signTime := authTime.signString()
keyTime := authTime.keyString()
signKey := calSignKey(secretKey, keyTime)
formatHeaders := *new(string)
signedHeaderList := *new([]string)
formatHeaders, signedHeaderList = genFormatHeaders(req.Header)
formatParameters, signedParameterList := genFormatParameters(req.URL.Query())
formatString := genFormatString(req.Method, *req.URL, formatParameters, formatHeaders)
stringToSign := calStringToSign(sha1SignAlgorithm, keyTime, formatString)
signature := calSignature(signKey, stringToSign)
return genAuthorization(
secretID, signTime, keyTime, signature, signedHeaderList,
signedParameterList,
)
}
// AddAuthorizationHeader 给 req 增加签名信息
func AddAuthorizationHeader(secretID, secretKey string, sessionToken string, req *http.Request, authTime *AuthTime) {
if secretID == "" {
return
}
auth := newAuthorization(secretID, secretKey, req,
authTime,
)
if len(sessionToken) > 0 {
req.Header.Set("x-cos-security-token", sessionToken)
}
req.Header.Set("Authorization", auth)
}
// calSignKey 计算 SignKey
func calSignKey(secretKey, keyTime string) string {
digest := calHMACDigest(secretKey, keyTime, sha1SignAlgorithm)
return fmt.Sprintf("%x", digest)
}
// calStringToSign 计算 StringToSign
func calStringToSign(signAlgorithm, signTime, formatString string) string {
h := sha1.New()
h.Write([]byte(formatString))
return fmt.Sprintf("%s\n%s\n%x\n", signAlgorithm, signTime, h.Sum(nil))
}
// calSignature 计算 Signature
func calSignature(signKey, stringToSign string) string {
digest := calHMACDigest(signKey, stringToSign, sha1SignAlgorithm)
return fmt.Sprintf("%x", digest)
}
// genAuthorization 生成 Authorization
func genAuthorization(secretID, signTime, keyTime, signature string, signedHeaderList, signedParameterList []string) string {
return strings.Join([]string{
"q-sign-algorithm=" + sha1SignAlgorithm,
"q-ak=" + secretID,
"q-sign-time=" + signTime,
"q-key-time=" + keyTime,
"q-header-list=" + strings.Join(signedHeaderList, ";"),
"q-url-param-list=" + strings.Join(signedParameterList, ";"),
"q-signature=" + signature,
}, "&")
}
// genFormatString 生成 FormatString
func genFormatString(method string, uri url.URL, formatParameters, formatHeaders string) string {
formatMethod := strings.ToLower(method)
formatURI := uri.Path
return fmt.Sprintf("%s\n%s\n%s\n%s\n", formatMethod, formatURI,
formatParameters, formatHeaders,
)
}
// genFormatParameters 生成 FormatParameters 和 SignedParameterList
// instead of the url.Values{}
func genFormatParameters(parameters url.Values) (formatParameters string, signedParameterList []string) {
ps := valuesSignMap{}
for key, values := range parameters {
key = strings.ToLower(key)
for _, value := range values {
ps.Add(key, value)
signedParameterList = append(signedParameterList, key)
}
}
//formatParameters = strings.ToLower(ps.Encode())
formatParameters = ps.Encode()
sort.Strings(signedParameterList)
return
}
// genFormatHeaders 生成 FormatHeaders 和 SignedHeaderList
func genFormatHeaders(headers http.Header) (formatHeaders string, signedHeaderList []string) {
hs := valuesSignMap{}
for key, values := range headers {
key = strings.ToLower(key)
for _, value := range values {
if isSignHeader(key) {
hs.Add(key, value)
signedHeaderList = append(signedHeaderList, key)
}
}
}
formatHeaders = hs.Encode()
sort.Strings(signedHeaderList)
return
}
// HMAC 签名
func calHMACDigest(key, msg, signMethod string) []byte {
var hashFunc func() hash.Hash
switch signMethod {
case "sha1":
hashFunc = sha1.New
default:
hashFunc = sha1.New
}
h := hmac.New(hashFunc, []byte(key))
h.Write([]byte(msg))
return h.Sum(nil)
}
func isSignHeader(key string) bool {
for k, v := range needSignHeaders {
if key == k && v {
return true
}
}
return strings.HasPrefix(key, privateHeaderPrefix)
}
// AuthorizationTransport 给请求增加 Authorization header
type AuthorizationTransport struct {
SecretID string
SecretKey string
SessionToken string
rwLocker sync.RWMutex
// 签名多久过期
Expire time.Duration
Transport http.RoundTripper
}
// SetCredential update the SecretID(ak), SercretKey(sk), sessiontoken
func (t *AuthorizationTransport) SetCredential(ak, sk, token string) {
t.rwLocker.Lock()
defer t.rwLocker.Unlock()
t.SecretID = ak
t.SecretKey = sk
t.SessionToken = token
}
// GetCredential get the ak, sk, token
func (t *AuthorizationTransport) GetCredential() (string, string, string) {
t.rwLocker.RLock()
defer t.rwLocker.RUnlock()
return t.SecretID, t.SecretKey, t.SessionToken
}
// RoundTrip implements the RoundTripper interface.
func (t *AuthorizationTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req = cloneRequest(req) // per RoundTrip contract
if t.Expire == time.Duration(0) {
t.Expire = defaultAuthExpire
}
ak, sk, token := t.GetCredential()
// 增加 Authorization header
authTime := NewAuthTime(t.Expire)
AddAuthorizationHeader(ak, sk, token, req, authTime)
resp, err := t.transport().RoundTrip(req)
return resp, err
}
func (t *AuthorizationTransport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}

104
vendor/github.com/tencentyun/cos-go-sdk-v5/bucket.go generated vendored Normal file
View File

@@ -0,0 +1,104 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// BucketService 相关 API
type BucketService service
// BucketGetResult is the result of GetBucket
type BucketGetResult struct {
XMLName xml.Name `xml:"ListBucketResult"`
Name string
Prefix string `xml:"Prefix,omitempty"`
Marker string `xml:"Marker,omitempty"`
NextMarker string `xml:"NextMarker,omitempty"`
Delimiter string `xml:"Delimiter,omitempty"`
MaxKeys int
IsTruncated bool
Contents []Object `xml:"Contents,omitempty"`
CommonPrefixes []string `xml:"CommonPrefixes>Prefix,omitempty"`
EncodingType string `xml:"Encoding-Type,omitempty"`
}
// BucketGetOptions is the option of GetBucket
type BucketGetOptions struct {
Prefix string `url:"prefix,omitempty"`
Delimiter string `url:"delimiter,omitempty"`
EncodingType string `url:"encoding-type,omitempty"`
Marker string `url:"marker,omitempty"`
MaxKeys int `url:"max-keys,omitempty"`
}
// Get Bucket请求等同于 List Object请求可以列出该Bucket下部分或者所有Object发起该请求需要拥有Read权限。
//
// https://www.qcloud.com/document/product/436/7734
func (s *BucketService) Get(ctx context.Context, opt *BucketGetOptions) (*BucketGetResult, *Response, error) {
var res BucketGetResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/",
method: http.MethodGet,
optQuery: opt,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// BucketPutOptions is same to the ACLHeaderOptions
type BucketPutOptions ACLHeaderOptions
// Put Bucket请求可以在指定账号下创建一个Bucket。
//
// https://www.qcloud.com/document/product/436/7738
func (s *BucketService) Put(ctx context.Context, opt *BucketPutOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/",
method: http.MethodPut,
optHeader: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// Delete Bucket请求可以在指定账号下删除Bucket删除之前要求Bucket为空。
//
// https://www.qcloud.com/document/product/436/7732
func (s *BucketService) Delete(ctx context.Context) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/",
method: http.MethodDelete,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// Head Bucket请求可以确认是否存在该Bucket是否有权限访问Head的权限与Read一致。
//
// 当其存在时,返回 HTTP 状态码200
// 当无权限时,返回 HTTP 状态码403
// 当不存在时,返回 HTTP 状态码404。
//
// https://www.qcloud.com/document/product/436/7735
func (s *BucketService) Head(ctx context.Context) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/",
method: http.MethodHead,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// Bucket is the meta info of Bucket
type Bucket struct {
Name string
Region string `xml:"Location,omitempty"`
CreationDate string `xml:",omitempty"`
}

View File

@@ -0,0 +1,62 @@
package cos
import (
"context"
"net/http"
)
// BucketGetACLResult is same to the ACLXml
type BucketGetACLResult ACLXml
// GetACL 使用API读取Bucket的ACL表只有所有者有权操作。
//
// https://www.qcloud.com/document/product/436/7733
func (s *BucketService) GetACL(ctx context.Context) (*BucketGetACLResult, *Response, error) {
var res BucketGetACLResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?acl",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// BucketPutACLOptions is the option of PutBucketACL
type BucketPutACLOptions struct {
Header *ACLHeaderOptions `url:"-" xml:"-"`
Body *ACLXml `url:"-" header:"-"`
}
// PutACL 使用API写入Bucket的ACL表您可以通过Header"x-cos-acl","x-cos-grant-read",
// "x-cos-grant-write","x-cos-grant-full-control"传入ACL信息也可以通过body以XML格式传入ACL信息
//
// 但是只能选择Header和Body其中一种否则返回冲突。
//
// Put Bucket ACL是一个覆盖操作传入新的ACL将覆盖原有ACL。只有所有者有权操作。
//
// "x-cos-acl"枚举值为public-readprivatepublic-read意味这个Bucket有公有读私有写的权限
// private意味这个Bucket有私有读写的权限。
//
// "x-cos-grant-read"意味被赋予权限的用户拥有该Bucket的读权限
// "x-cos-grant-write"意味被赋予权限的用户拥有该Bucket的写权限
// "x-cos-grant-full-control"意味被赋予权限的用户拥有该Bucket的读写权限
//
// https://www.qcloud.com/document/product/436/7737
func (s *BucketService) PutACL(ctx context.Context, opt *BucketPutACLOptions) (*Response, error) {
header := opt.Header
body := opt.Body
if body != nil {
header = nil
}
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?acl",
method: http.MethodPut,
body: body,
optHeader: header,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}

View File

@@ -0,0 +1,71 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// BucketCORSRule is the rule of BucketCORS
type BucketCORSRule struct {
ID string `xml:"ID,omitempty"`
AllowedMethods []string `xml:"AllowedMethod"`
AllowedOrigins []string `xml:"AllowedOrigin"`
AllowedHeaders []string `xml:"AllowedHeader,omitempty"`
MaxAgeSeconds int `xml:"MaxAgeSeconds,omitempty"`
ExposeHeaders []string `xml:"ExposeHeader,omitempty"`
}
// BucketGetCORSResult is the result of GetBucketCORS
type BucketGetCORSResult struct {
XMLName xml.Name `xml:"CORSConfiguration"`
Rules []BucketCORSRule `xml:"CORSRule,omitempty"`
}
// GetCORS 实现 Bucket 跨域访问配置读取。
//
// https://www.qcloud.com/document/product/436/8274
func (s *BucketService) GetCORS(ctx context.Context) (*BucketGetCORSResult, *Response, error) {
var res BucketGetCORSResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?cors",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// BucketPutCORSOptions is the option of PutBucketCORS
type BucketPutCORSOptions struct {
XMLName xml.Name `xml:"CORSConfiguration"`
Rules []BucketCORSRule `xml:"CORSRule,omitempty"`
}
// PutCORS 实现 Bucket 跨域访问设置您可以通过传入XML格式的配置文件实现配置文件大小限制为64 KB。
//
// https://www.qcloud.com/document/product/436/8279
func (s *BucketService) PutCORS(ctx context.Context, opt *BucketPutCORSOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?cors",
method: http.MethodPut,
body: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// DeleteCORS 实现 Bucket 跨域访问配置删除。
//
// https://www.qcloud.com/document/product/436/8283
func (s *BucketService) DeleteCORS(ctx context.Context) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?cors",
method: http.MethodDelete,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}

View File

@@ -0,0 +1,134 @@
package cos
import (
"context"
"encoding/xml"
"fmt"
"net/http"
)
// Notice bucket_inventory only for test. can not use
// BucketGetInventoryResult same struct to options
type BucketGetInventoryResult BucketPutInventoryOptions
// BucketListInventoryConfiguartion same struct to options
type BucketListInventoryConfiguartion BucketPutInventoryOptions
// BucketInventoryFilter ...
type BucketInventoryFilter struct {
Prefix string `xml:"Prefix,omitempty"`
}
// BucketInventoryOptionalFields ...
type BucketInventoryOptionalFields struct {
XMLName xml.Name `xml:"OptionalFields,omitempty"`
BucketInventoryFields []string `xml:"Field,omitempty"`
}
// BucketInventorySchedule ...
type BucketInventorySchedule struct {
Frequency string `xml:"Frequency"`
}
// BucketInventoryEncryption ...
type BucketInventoryEncryption struct {
XMLName xml.Name `xml:"Encryption"`
SSECOS string `xml:"SSE-COS,omitempty"`
}
// BucketInventoryDestinationContent ...
type BucketInventoryDestinationContent struct {
Bucket string `xml:"Bucket"`
AccountId string `xml:"AccountId,omitempty"`
Prefix string `xml:"Prefix,omitempty"`
Format string `xml:"Format"`
Encryption *BucketInventoryEncryption `xml:"Encryption,omitempty"`
}
// BucketInventoryDestination ...
type BucketInventoryDestination struct {
XMLName xml.Name `xml:"Destination"`
BucketDestination *BucketInventoryDestinationContent `xml:"COSBucketDestination"`
}
// BucketPutInventoryOptions ...
type BucketPutInventoryOptions struct {
XMLName xml.Name `xml:"InventoryConfiguration"`
ID string `xml:"Id"`
IsEnabled string `xml:"IsEnabled"`
IncludedObjectVersions string `xml:"IncludedObjectVersions"`
Filter *BucketInventoryFilter `xml:"Filter,omitempty"`
OptionalFields *BucketInventoryOptionalFields `xml:"OptionalFields,omitempty"`
Schedule *BucketInventorySchedule `xml:"Schedule"`
Destination *BucketInventoryDestination `xml:"Destination"`
}
// ListBucketInventoryConfigResult result of ListBucketInventoryConfiguration
type ListBucketInventoryConfigResult struct {
XMLName xml.Name `xml:"ListInventoryConfigurationResult"`
InventoryConfigurations []BucketListInventoryConfiguartion `xml:"InventoryConfiguration,omitempty"`
IsTruncated bool `xml:"IsTruncated,omitempty"`
ContinuationToken string `xml:"ContinuationToken,omitempty"`
NextContinuationToken string `xml:"NextContinuationToken,omitempty"`
}
// PutBucketInventory https://cloud.tencent.com/document/product/436/33707
func (s *BucketService) PutBucketInventoryTest(ctx context.Context, id string, opt *BucketPutInventoryOptions) (*Response, error) {
u := fmt.Sprintf("/?inventory&id=%s", id)
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodPut,
body: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// GetBucketInventory https://cloud.tencent.com/document/product/436/33705
func (s *BucketService) GetBucketInventoryTest(ctx context.Context, id string) (*BucketGetInventoryResult, *Response, error) {
u := fmt.Sprintf("/?inventory&id=%s", id)
var res BucketGetInventoryResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// DeleteBucketInventory https://cloud.tencent.com/document/product/436/33704
func (s *BucketService) DeleteBucketInventoryTest(ctx context.Context, id string) (*Response, error) {
u := fmt.Sprintf("/?inventory&id=%s", id)
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodDelete,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// ListBucketInventoryConfigurations https://cloud.tencent.com/document/product/436/33706
func (s *BucketService) ListBucketInventoryConfigurationsTest(ctx context.Context, token string) (*ListBucketInventoryConfigResult, *Response, error) {
var res ListBucketInventoryConfigResult
var u string
if token == "" {
u = "/?inventory"
} else {
u = fmt.Sprintf("/?inventory&continuation-token=%s", encodeURIComponent(token))
}
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}

View File

@@ -0,0 +1,92 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// BucketLifecycleFilter is the param of BucketLifecycleRule
type BucketLifecycleFilter struct {
Prefix string `xml:"Prefix,omitempty"`
}
// BucketLifecycleExpiration is the param of BucketLifecycleRule
type BucketLifecycleExpiration struct {
Date string `xml:"Date,omitempty"`
Days int `xml:"Days,omitempty"`
}
// BucketLifecycleTransition is the param of BucketLifecycleRule
type BucketLifecycleTransition struct {
Date string `xml:"Date,omitempty"`
Days int `xml:"Days,omitempty"`
StorageClass string
}
// BucketLifecycleAbortIncompleteMultipartUpload is the param of BucketLifecycleRule
type BucketLifecycleAbortIncompleteMultipartUpload struct {
DaysAfterInitiation string `xml:"DaysAfterInititation,omitempty"`
}
// BucketLifecycleRule is the rule of BucketLifecycle
type BucketLifecycleRule struct {
ID string `xml:"ID,omitempty"`
Status string
Filter *BucketLifecycleFilter `xml:"Filter,omitempty"`
Transition *BucketLifecycleTransition `xml:"Transition,omitempty"`
Expiration *BucketLifecycleExpiration `xml:"Expiration,omitempty"`
AbortIncompleteMultipartUpload *BucketLifecycleAbortIncompleteMultipartUpload `xml:"AbortIncompleteMultipartUpload,omitempty"`
}
// BucketGetLifecycleResult is the result of BucketGetLifecycle
type BucketGetLifecycleResult struct {
XMLName xml.Name `xml:"LifecycleConfiguration"`
Rules []BucketLifecycleRule `xml:"Rule,omitempty"`
}
// GetLifecycle 请求实现读取生命周期管理的配置。当配置不存在时返回404 Not Found。
// https://www.qcloud.com/document/product/436/8278
func (s *BucketService) GetLifecycle(ctx context.Context) (*BucketGetLifecycleResult, *Response, error) {
var res BucketGetLifecycleResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?lifecycle",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// BucketPutLifecycleOptions is the option of PutBucketLifecycle
type BucketPutLifecycleOptions struct {
XMLName xml.Name `xml:"LifecycleConfiguration"`
Rules []BucketLifecycleRule `xml:"Rule,omitempty"`
}
// PutLifecycle 请求实现设置生命周期管理的功能。您可以通过该请求实现数据的生命周期管理配置和定期删除。
// 此请求为覆盖操作,上传新的配置文件将覆盖之前的配置文件。生命周期管理对文件和文件夹同时生效。
// https://www.qcloud.com/document/product/436/8280
func (s *BucketService) PutLifecycle(ctx context.Context, opt *BucketPutLifecycleOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?lifecycle",
method: http.MethodPut,
body: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// DeleteLifecycle 请求实现删除生命周期管理。
// https://www.qcloud.com/document/product/436/8284
func (s *BucketService) DeleteLifecycle(ctx context.Context) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?lifecycle",
method: http.MethodDelete,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}

View File

@@ -0,0 +1,28 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// BucketGetLocationResult is the result of BucketGetLocation
type BucketGetLocationResult struct {
XMLName xml.Name `xml:"LocationConstraint"`
Location string `xml:",chardata"`
}
// GetLocation 接口获取Bucket所在地域信息只有Bucket所有者有权限读取信息。
//
// https://www.qcloud.com/document/product/436/8275
func (s *BucketService) GetLocation(ctx context.Context) (*BucketGetLocationResult, *Response, error) {
var res BucketGetLocationResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?location",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}

View File

@@ -0,0 +1,53 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// Notice bucket logging function is testing, can not use.
// BucketLoggingEnabled main struct of logging
type BucketLoggingEnabled struct {
TargetBucket string `xml:"TargetBucket"`
TargetPrefix string `xml:"TargetPrefix"`
}
// BucketPutLoggingOptions is the options of PutBucketLogging
type BucketPutLoggingOptions struct {
XMLName xml.Name `xml:"BucketLoggingStatus"`
LoggingEnabled *BucketLoggingEnabled `xml:"LoggingEnabled"`
}
// BucketGetLoggingResult is the result of GetBucketLogging
type BucketGetLoggingResult struct {
XMLName xml.Name `xml:"BucketLoggingStatus"`
LoggingEnabled *BucketLoggingEnabled `xml:"LoggingEnabled"`
}
// PutBucketLogging https://cloud.tencent.com/document/product/436/17054
func (s *BucketService) PutBucketLoggingTest(ctx context.Context, opt *BucketPutLoggingOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?logging",
method: http.MethodPut,
body: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// GetBucketLogging https://cloud.tencent.com/document/product/436/17053
func (s *BucketService) GetBucketLoggingTest(ctx context.Context) (*BucketGetLoggingResult, *Response, error) {
var res BucketGetLoggingResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?logging",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}

View File

@@ -0,0 +1,57 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// ListMultipartUploadsResult is the result of ListMultipartUploads
type ListMultipartUploadsResult struct {
XMLName xml.Name `xml:"ListMultipartUploadsResult"`
Bucket string `xml:"Bucket"`
EncodingType string `xml:"Encoding-Type"`
KeyMarker string
UploadIDMarker string `xml:"UploadIdMarker"`
NextKeyMarker string
NextUploadIDMarker string `xml:"NextUploadIdMarker"`
MaxUploads int
IsTruncated bool
Uploads []struct {
Key string
UploadID string `xml:"UploadId"`
StorageClass string
Initiator *Initiator
Owner *Owner
Initiated string
} `xml:"Upload,omitempty"`
Prefix string
Delimiter string `xml:"delimiter,omitempty"`
CommonPrefixes []string `xml:"CommonPrefixs>Prefix,omitempty"`
}
// ListMultipartUploadsOptions is the option of ListMultipartUploads
type ListMultipartUploadsOptions struct {
Delimiter string `url:"delimiter,omitempty"`
EncodingType string `url:"encoding-type,omitempty"`
Prefix string `url:"prefix,omitempty"`
MaxUploads int `url:"max-uploads,omitempty"`
KeyMarker string `url:"key-marker,omitempty"`
UploadIDMarker string `url:"upload-id-marker,omitempty"`
}
// ListMultipartUploads 用来查询正在进行中的分块上传。单次最多列出1000个正在进行中的分块上传。
//
// https://www.qcloud.com/document/product/436/7736
func (s *BucketService) ListMultipartUploads(ctx context.Context, opt *ListMultipartUploadsOptions) (*ListMultipartUploadsResult, *Response, error) {
var res ListMultipartUploadsResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?uploads",
method: http.MethodGet,
result: &res,
optQuery: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}

View File

@@ -0,0 +1,73 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// ReplicationDestination is the sub struct of BucketReplicationRule
type ReplicationDestination struct {
Bucket string `xml:"Bucket"`
StorageClass string `xml:"StorageClass,omitempty"`
}
// BucketReplicationRule is the main param of replication
type BucketReplicationRule struct {
ID string `xml:"ID,omitempty"`
Status string `xml:"Status"`
Prefix string `xml:"Prefix"`
Destination *ReplicationDestination `xml:"Destination"`
}
// PutBucketReplicationOptions is the options of PutBucketReplication
type PutBucketReplicationOptions struct {
XMLName xml.Name `xml:"ReplicationConfiguration"`
Role string `xml:"Role"`
Rule []BucketReplicationRule `xml:"Rule"`
}
// GetBucketReplicationResult is the result of GetBucketReplication
type GetBucketReplicationResult struct {
XMLName xml.Name `xml:"ReplicationConfiguration"`
Role string `xml:"Role"`
Rule []BucketReplicationRule `xml:"Rule"`
}
// PutBucketReplication https://cloud.tencent.com/document/product/436/19223
func (s *BucketService) PutBucketReplication(ctx context.Context, opt *PutBucketReplicationOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?replication",
method: http.MethodPut,
body: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// GetBucketReplication https://cloud.tencent.com/document/product/436/19222
func (s *BucketService) GetBucketReplication(ctx context.Context) (*GetBucketReplicationResult, *Response, error) {
var res GetBucketReplicationResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?replication",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// DeleteBucketReplication https://cloud.tencent.com/document/product/436/19221
func (s *BucketService) DeleteBucketReplication(ctx context.Context) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?replication",
method: http.MethodDelete,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}

View File

@@ -0,0 +1,69 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// BucketTaggingTag is the tag of BucketTagging
type BucketTaggingTag struct {
Key string
Value string
}
// BucketGetTaggingResult is the result of BucketGetTagging
type BucketGetTaggingResult struct {
XMLName xml.Name `xml:"Tagging"`
TagSet []BucketTaggingTag `xml:"TagSet>Tag,omitempty"`
}
// GetTagging 接口实现获取指定Bucket的标签。
//
// https://www.qcloud.com/document/product/436/8277
func (s *BucketService) GetTagging(ctx context.Context) (*BucketGetTaggingResult, *Response, error) {
var res BucketGetTaggingResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?tagging",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// BucketPutTaggingOptions is the option of BucketPutTagging
type BucketPutTaggingOptions struct {
XMLName xml.Name `xml:"Tagging"`
TagSet []BucketTaggingTag `xml:"TagSet>Tag,omitempty"`
}
// PutTagging 接口实现给用指定Bucket打标签。用来组织和管理相关Bucket。
//
// 当该请求设置相同Key名称不同Value时会返回400。请求成功则返回204。
//
// https://www.qcloud.com/document/product/436/8281
func (s *BucketService) PutTagging(ctx context.Context, opt *BucketPutTaggingOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?tagging",
method: http.MethodPut,
body: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// DeleteTagging 接口实现删除指定Bucket的标签。
//
// https://www.qcloud.com/document/product/436/8286
func (s *BucketService) DeleteTagging(ctx context.Context) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?tagging",
method: http.MethodDelete,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}

View File

@@ -0,0 +1,45 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// BucketPutVersionOptions is the options of PutBucketVersioning
type BucketPutVersionOptions struct {
XMLName xml.Name `xml:"VersioningConfiguration"`
Status string `xml:"Status"`
}
// BucketGetVersionResult is the result of GetBucketVersioning
type BucketGetVersionResult struct {
XMLName xml.Name `xml:"VersioningConfiguration"`
Status string `xml:"Status"`
}
// PutVersion https://cloud.tencent.com/document/product/436/19889
// Status has Suspended\Enabled
func (s *BucketService) PutVersioning(ctx context.Context, opt *BucketPutVersionOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?versioning",
method: http.MethodPut,
body: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// GetVersion https://cloud.tencent.com/document/product/436/19888
func (s *BucketService) GetVersioning(ctx context.Context) (*BucketGetVersionResult, *Response, error) {
var res BucketGetVersionResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?versioning",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}

346
vendor/github.com/tencentyun/cos-go-sdk-v5/cos.go generated vendored Normal file
View File

@@ -0,0 +1,346 @@
package cos
import (
"bytes"
"context"
"encoding/base64"
"encoding/xml"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"reflect"
"text/template"
"strconv"
"github.com/google/go-querystring/query"
"github.com/mozillazg/go-httpheader"
)
const (
// Version current go sdk version
Version = "0.7.3"
userAgent = "cos-go-sdk-v5/" + Version
contentTypeXML = "application/xml"
defaultServiceBaseURL = "http://service.cos.myqcloud.com"
)
var bucketURLTemplate = template.Must(
template.New("bucketURLFormat").Parse(
"{{.Schema}}://{{.BucketName}}.cos.{{.Region}}.myqcloud.com",
),
)
// BaseURL 访问各 API 所需的基础 URL
type BaseURL struct {
// 访问 bucket, object 相关 API 的基础 URL不包含 path 部分): http://example.com
BucketURL *url.URL
// 访问 service API 的基础 URL不包含 path 部分): http://example.com
ServiceURL *url.URL
}
// NewBucketURL 生成 BaseURL 所需的 BucketURL
//
// bucketName: bucket名称, bucket的命名规则为{name}-{appid} ,此处填写的存储桶名称必须为此格式
// Region: 区域代码: ap-beijing-1,ap-beijing,ap-shanghai,ap-guangzhou...
// secure: 是否使用 https
func NewBucketURL(bucketName, region string, secure bool) *url.URL {
schema := "https"
if !secure {
schema = "http"
}
w := bytes.NewBuffer(nil)
bucketURLTemplate.Execute(w, struct {
Schema string
BucketName string
Region string
}{
schema, bucketName, region,
})
u, _ := url.Parse(w.String())
return u
}
// Client is a client manages communication with the COS API.
type Client struct {
client *http.Client
UserAgent string
BaseURL *BaseURL
common service
Service *ServiceService
Bucket *BucketService
Object *ObjectService
}
type service struct {
client *Client
}
// NewClient returns a new COS API client.
func NewClient(uri *BaseURL, httpClient *http.Client) *Client {
if httpClient == nil {
httpClient = &http.Client{}
}
baseURL := &BaseURL{}
if uri != nil {
baseURL.BucketURL = uri.BucketURL
baseURL.ServiceURL = uri.ServiceURL
}
if baseURL.ServiceURL == nil {
baseURL.ServiceURL, _ = url.Parse(defaultServiceBaseURL)
}
c := &Client{
client: httpClient,
UserAgent: userAgent,
BaseURL: baseURL,
}
c.common.client = c
c.Service = (*ServiceService)(&c.common)
c.Bucket = (*BucketService)(&c.common)
c.Object = (*ObjectService)(&c.common)
return c
}
func (c *Client) newRequest(ctx context.Context, baseURL *url.URL, uri, method string, body interface{}, optQuery interface{}, optHeader interface{}) (req *http.Request, err error) {
uri, err = addURLOptions(uri, optQuery)
if err != nil {
return
}
u, _ := url.Parse(uri)
urlStr := baseURL.ResolveReference(u).String()
var reader io.Reader
contentType := ""
contentMD5 := ""
if body != nil {
// 上传文件
if r, ok := body.(io.Reader); ok {
reader = r
} else {
b, err := xml.Marshal(body)
if err != nil {
return nil, err
}
contentType = contentTypeXML
reader = bytes.NewReader(b)
contentMD5 = base64.StdEncoding.EncodeToString(calMD5Digest(b))
}
}
req, err = http.NewRequest(method, urlStr, reader)
if err != nil {
return
}
req.Header, err = addHeaderOptions(req.Header, optHeader)
if err != nil {
return
}
if v := req.Header.Get("Content-Length"); req.ContentLength == 0 && v != "" && v != "0" {
req.ContentLength, _ = strconv.ParseInt(v, 10, 64)
}
if contentMD5 != "" {
req.Header["Content-MD5"] = []string{contentMD5}
}
if c.UserAgent != "" {
req.Header.Set("User-Agent", c.UserAgent)
}
if req.Header.Get("Content-Type") == "" && contentType != "" {
req.Header.Set("Content-Type", contentType)
}
return
}
func (c *Client) doAPI(ctx context.Context, req *http.Request, result interface{}, closeBody bool) (*Response, error) {
req = req.WithContext(ctx)
resp, err := c.client.Do(req)
if err != nil {
// If we got an error, and the context has been canceled,
// the context's error is probably more useful.
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
return nil, err
}
defer func() {
if closeBody {
// Close the body to let the Transport reuse the connection
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
}
}()
response := newResponse(resp)
err = checkResponse(resp)
if err != nil {
// even though there was an error, we still return the response
// in case the caller wants to inspect it further
return response, err
}
if result != nil {
if w, ok := result.(io.Writer); ok {
io.Copy(w, resp.Body)
} else {
err = xml.NewDecoder(resp.Body).Decode(result)
if err == io.EOF {
err = nil // ignore EOF errors caused by empty response body
}
}
}
return response, err
}
type sendOptions struct {
// 基础 URL
baseURL *url.URL
// URL 中除基础 URL 外的剩余部分
uri string
// 请求方法
method string
body interface{}
// url 查询参数
optQuery interface{}
// http header 参数
optHeader interface{}
// 用 result 反序列化 resp.Body
result interface{}
// 是否禁用自动调用 resp.Body.Close()
// 自动调用 Close() 是为了能够重用连接
disableCloseBody bool
}
func (c *Client) send(ctx context.Context, opt *sendOptions) (resp *Response, err error) {
req, err := c.newRequest(ctx, opt.baseURL, opt.uri, opt.method, opt.body, opt.optQuery, opt.optHeader)
if err != nil {
return
}
resp, err = c.doAPI(ctx, req, opt.result, !opt.disableCloseBody)
if err != nil {
return
}
return
}
// addURLOptions adds the parameters in opt as URL query parameters to s. opt
// must be a struct whose fields may contain "url" tags.
func addURLOptions(s string, opt interface{}) (string, error) {
v := reflect.ValueOf(opt)
if v.Kind() == reflect.Ptr && v.IsNil() {
return s, nil
}
u, err := url.Parse(s)
if err != nil {
return s, err
}
qs, err := query.Values(opt)
if err != nil {
return s, err
}
// 保留原有的参数,并且放在前面。因为 cos 的 url 路由是以第一个参数作为路由的
// e.g. /?uploads
q := u.RawQuery
rq := qs.Encode()
if q != "" {
if rq != "" {
u.RawQuery = fmt.Sprintf("%s&%s", q, qs.Encode())
}
} else {
u.RawQuery = rq
}
return u.String(), nil
}
// addHeaderOptions adds the parameters in opt as Header fields to req. opt
// must be a struct whose fields may contain "header" tags.
func addHeaderOptions(header http.Header, opt interface{}) (http.Header, error) {
v := reflect.ValueOf(opt)
if v.Kind() == reflect.Ptr && v.IsNil() {
return header, nil
}
h, err := httpheader.Header(opt)
if err != nil {
return nil, err
}
for key, values := range h {
for _, value := range values {
header.Add(key, value)
}
}
return header, nil
}
// Owner defines Bucket/Object's owner
type Owner struct {
UIN string `xml:"uin,omitempty"`
ID string `xml:",omitempty"`
DisplayName string `xml:",omitempty"`
}
// Initiator same to the Owner struct
type Initiator Owner
// Response API 响应
type Response struct {
*http.Response
}
func newResponse(resp *http.Response) *Response {
return &Response{
Response: resp,
}
}
// ACLHeaderOptions is the option of ACLHeader
type ACLHeaderOptions struct {
XCosACL string `header:"x-cos-acl,omitempty" url:"-" xml:"-"`
XCosGrantRead string `header:"x-cos-grant-read,omitempty" url:"-" xml:"-"`
XCosGrantWrite string `header:"x-cos-grant-write,omitempty" url:"-" xml:"-"`
XCosGrantFullControl string `header:"x-cos-grant-full-control,omitempty" url:"-" xml:"-"`
}
// ACLGrantee is the param of ACLGrant
type ACLGrantee struct {
Type string `xml:"type,attr"`
UIN string `xml:"uin,omitempty"`
URI string `xml:"URI,omitempty"`
ID string `xml:",omitempty"`
DisplayName string `xml:",omitempty"`
SubAccount string `xml:"Subaccount,omitempty"`
}
// ACLGrant is the param of ACLXml
type ACLGrant struct {
Grantee *ACLGrantee
Permission string
}
// ACLXml is the ACL body struct
type ACLXml struct {
XMLName xml.Name `xml:"AccessControlPolicy"`
Owner *Owner
AccessControlList []ACLGrant `xml:"AccessControlList>Grant,omitempty"`
}

View File

@@ -0,0 +1,70 @@
package debug
import (
"fmt"
"io"
"net/http"
"net/http/httputil"
"os"
)
// DebugRequestTransport 会打印请求和响应信息, 方便调试.
type DebugRequestTransport struct {
RequestHeader bool
RequestBody bool // RequestHeader 为 true 时,这个选项才会生效
ResponseHeader bool
ResponseBody bool // ResponseHeader 为 true 时,这个选项才会生效
// debug 信息输出到 Writer 中, 默认是 os.Stderr
Writer io.Writer
Transport http.RoundTripper
}
// RoundTrip implements the RoundTripper interface.
func (t *DebugRequestTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req = cloneRequest(req) // per RoundTrip contract
w := t.Writer
if w == nil {
w = os.Stderr
}
if t.RequestHeader {
a, _ := httputil.DumpRequest(req, t.RequestBody)
fmt.Fprintf(w, "%s\n\n", string(a))
}
resp, err := t.transport().RoundTrip(req)
if err != nil {
return resp, err
}
if t.ResponseHeader {
b, _ := httputil.DumpResponse(resp, t.ResponseBody)
fmt.Fprintf(w, "%s\n", string(b))
}
return resp, err
}
func (t *DebugRequestTransport) transport() http.RoundTripper {
if t.Transport != nil {
return t.Transport
}
return http.DefaultTransport
}
// cloneRequest returns a clone of the provided *http.Request. The clone is a
// shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header, len(r.Header))
for k, s := range r.Header {
r2.Header[k] = append([]string(nil), s...)
}
return r2
}

3
vendor/github.com/tencentyun/cos-go-sdk-v5/doc.go generated vendored Normal file
View File

@@ -0,0 +1,3 @@
// Package cos is COS(Cloud Object Storage) Go SDK. The V5 version(XML API).
// There are examples of using each API in the project's 'example' directory.
package cos

49
vendor/github.com/tencentyun/cos-go-sdk-v5/error.go generated vendored Normal file
View File

@@ -0,0 +1,49 @@
package cos
import (
"encoding/xml"
"fmt"
"io/ioutil"
"net/http"
)
// ErrorResponse 包含 API 返回的错误信息
//
// https://www.qcloud.com/document/product/436/7730
type ErrorResponse struct {
XMLName xml.Name `xml:"Error"`
Response *http.Response `xml:"-"`
Code string
Message string
Resource string
RequestID string `header:"x-cos-request-id,omitempty" url:"-" xml:"-"`
TraceID string `xml:"TraceId,omitempty"`
}
// Error returns the error msg
func (r *ErrorResponse) Error() string {
RequestID := r.RequestID
if RequestID == "" {
RequestID = r.Response.Header.Get("X-Cos-Request-Id")
}
TraceID := r.TraceID
if TraceID == "" {
TraceID = r.Response.Header.Get("X-Cos-Trace-Id")
}
return fmt.Sprintf("%v %v: %d %v(Message: %v, RequestId: %v, TraceId: %v)",
r.Response.Request.Method, r.Response.Request.URL,
r.Response.StatusCode, r.Code, r.Message, RequestID, TraceID)
}
// 检查 response 是否是出错时的返回的 response
func checkResponse(r *http.Response) error {
if c := r.StatusCode; 200 <= c && c <= 299 {
return nil
}
errorResponse := &ErrorResponse{Response: r}
data, err := ioutil.ReadAll(r.Body)
if err == nil && data != nil {
xml.Unmarshal(data, errorResponse)
}
return errorResponse
}

85
vendor/github.com/tencentyun/cos-go-sdk-v5/helper.go generated vendored Normal file
View File

@@ -0,0 +1,85 @@
package cos
import (
"bytes"
"crypto/md5"
"crypto/sha1"
"fmt"
"net/http"
)
// 计算 md5 或 sha1 时的分块大小
const calDigestBlockSize = 1024 * 1024 * 10
func calMD5Digest(msg []byte) []byte {
// TODO: 分块计算,减少内存消耗
m := md5.New()
m.Write(msg)
return m.Sum(nil)
}
func calSHA1Digest(msg []byte) []byte {
// TODO: 分块计算,减少内存消耗
m := sha1.New()
m.Write(msg)
return m.Sum(nil)
}
// cloneRequest returns a clone of the provided *http.Request. The clone is a
// shallow copy of the struct and its Header map.
func cloneRequest(r *http.Request) *http.Request {
// shallow copy of the struct
r2 := new(http.Request)
*r2 = *r
// deep copy of the Header
r2.Header = make(http.Header, len(r.Header))
for k, s := range r.Header {
r2.Header[k] = append([]string(nil), s...)
}
return r2
}
// encodeURIComponent like same function in javascript
//
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
//
// http://www.ecma-international.org/ecma-262/6.0/#sec-uri-syntax-and-semantics
func encodeURIComponent(s string) string {
var b bytes.Buffer
written := 0
for i, n := 0, len(s); i < n; i++ {
c := s[i]
switch c {
case '-', '_', '.', '!', '~', '*', '\'', '(', ')':
continue
default:
// Unreserved according to RFC 3986 sec 2.3
if 'a' <= c && c <= 'z' {
continue
}
if 'A' <= c && c <= 'Z' {
continue
}
if '0' <= c && c <= '9' {
continue
}
}
b.WriteString(s[written:i])
fmt.Fprintf(&b, "%%%02X", c)
written = i + 1
}
if written == 0 {
return s
}
b.WriteString(s[written:])
return b.String()
}

483
vendor/github.com/tencentyun/cos-go-sdk-v5/object.go generated vendored Normal file
View File

@@ -0,0 +1,483 @@
package cos
import (
"context"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
)
// ObjectService 相关 API
type ObjectService service
// ObjectGetOptions is the option of GetObject
type ObjectGetOptions struct {
ResponseContentType string `url:"response-content-type,omitempty" header:"-"`
ResponseContentLanguage string `url:"response-content-language,omitempty" header:"-"`
ResponseExpires string `url:"response-expires,omitempty" header:"-"`
ResponseCacheControl string `url:"response-cache-control,omitempty" header:"-"`
ResponseContentDisposition string `url:"response-content-disposition,omitempty" header:"-"`
ResponseContentEncoding string `url:"response-content-encoding,omitempty" header:"-"`
Range string `url:"-" header:"Range,omitempty"`
IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"`
}
// presignedURLTestingOptions is the opt of presigned url
type presignedURLTestingOptions struct {
authTime *AuthTime
}
// Get Object 请求可以将一个文件Object下载至本地。
// 该操作需要对目标 Object 具有读权限或目标 Object 对所有人都开放了读权限(公有读)。
//
// https://www.qcloud.com/document/product/436/7753
func (s *ObjectService) Get(ctx context.Context, name string, opt *ObjectGetOptions, id ...string) (*Response, error) {
var u string
if len(id) == 1 {
u = fmt.Sprintf("/%s?versionId=%s", encodeURIComponent(name), id[0])
} else if len(id) == 0 {
u = "/" + encodeURIComponent(name)
} else {
return nil, errors.New("wrong params")
}
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodGet,
optQuery: opt,
optHeader: opt,
disableCloseBody: true,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// GetToFile download the object to local file
func (s *ObjectService) GetToFile(ctx context.Context, name, localpath string, opt *ObjectGetOptions, id ...string) (*Response, error) {
resp, err := s.Get(ctx, name, opt, id...)
if err != nil {
return resp, err
}
defer resp.Body.Close()
// If file exist, overwrite it
fd, err := os.OpenFile(localpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0660)
if err != nil {
return resp, err
}
_, err = io.Copy(fd, resp.Body)
fd.Close()
if err != nil {
return resp, err
}
return resp, nil
}
// GetPresignedURL get the object presigned to down or upload file by url
func (s *ObjectService) GetPresignedURL(ctx context.Context, httpMethod, name, ak, sk string, expired time.Duration, opt interface{}) (*url.URL, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name),
method: httpMethod,
optQuery: opt,
optHeader: opt,
}
req, err := s.client.newRequest(ctx, sendOpt.baseURL, sendOpt.uri, sendOpt.method, sendOpt.body, sendOpt.optQuery, sendOpt.optHeader)
if err != nil {
return nil, err
}
var authTime *AuthTime
if opt != nil {
if opt, ok := opt.(*presignedURLTestingOptions); ok {
authTime = opt.authTime
}
}
if authTime == nil {
authTime = NewAuthTime(expired)
}
authorization := newAuthorization(ak, sk, req, authTime)
sign := encodeURIComponent(authorization)
if req.URL.RawQuery == "" {
req.URL.RawQuery = fmt.Sprintf("sign=%s", sign)
} else {
req.URL.RawQuery = fmt.Sprintf("%s&sign=%s", req.URL.RawQuery, sign)
}
return req.URL, nil
}
// ObjectPutHeaderOptions the options of header of the put object
type ObjectPutHeaderOptions struct {
CacheControl string `header:"Cache-Control,omitempty" url:"-"`
ContentDisposition string `header:"Content-Disposition,omitempty" url:"-"`
ContentEncoding string `header:"Content-Encoding,omitempty" url:"-"`
ContentType string `header:"Content-Type,omitempty" url:"-"`
ContentMD5 string `header:"Content-MD5,omitempty" url:"-"`
ContentLength int `header:"Content-Length,omitempty" url:"-"`
Expect string `header:"Expect,omitempty" url:"-"`
Expires string `header:"Expires,omitempty" url:"-"`
XCosContentSHA1 string `header:"x-cos-content-sha1,omitempty" url:"-"`
// 自定义的 x-cos-meta-* header
XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"`
XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-"`
// 可选值: Normal, Appendable
//XCosObjectType string `header:"x-cos-object-type,omitempty" url:"-"`
}
// ObjectPutOptions the options of put object
type ObjectPutOptions struct {
*ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"`
*ObjectPutHeaderOptions `header:",omitempty" url:"-" xml:"-"`
}
// Put Object请求可以将一个文件Oject上传至指定Bucket。
//
// 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
//
// https://www.qcloud.com/document/product/436/7749
func (s *ObjectService) Put(ctx context.Context, name string, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name),
method: http.MethodPut,
body: r,
optHeader: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// PutFromFile put object from local file
// Notice that when use this put large file need set non-body of debug req/resp, otherwise will out of memory
func (s *ObjectService) PutFromFile(ctx context.Context, name string, filePath string, opt *ObjectPutOptions) (*Response, error) {
fd, err := os.Open(filePath)
if err != nil {
return nil, err
}
defer fd.Close()
return s.Put(ctx, name, fd, opt)
}
// ObjectCopyHeaderOptions is the head option of the Copy
type ObjectCopyHeaderOptions struct {
// When use replace directive to update meta infos
CacheControl string `header:"Cache-Control,omitempty" url:"-"`
ContentDisposition string `header:"Content-Disposition,omitempty" url:"-"`
ContentEncoding string `header:"Content-Encoding,omitempty" url:"-"`
ContentType string `header:"Content-Type,omitempty" url:"-"`
Expires string `header:"Expires,omitempty" url:"-"`
Expect string `header:"Expect,omitempty" url:"-"`
XCosMetadataDirective string `header:"x-cos-metadata-directive,omitempty" url:"-" xml:"-"`
XCosCopySourceIfModifiedSince string `header:"x-cos-copy-source-If-Modified-Since,omitempty" url:"-" xml:"-"`
XCosCopySourceIfUnmodifiedSince string `header:"x-cos-copy-source-If-Unmodified-Since,omitempty" url:"-" xml:"-"`
XCosCopySourceIfMatch string `header:"x-cos-copy-source-If-Match,omitempty" url:"-" xml:"-"`
XCosCopySourceIfNoneMatch string `header:"x-cos-copy-source-If-None-Match,omitempty" url:"-" xml:"-"`
XCosStorageClass string `header:"x-cos-storage-class,omitempty" url:"-" xml:"-"`
// 自定义的 x-cos-meta-* header
XCosMetaXXX *http.Header `header:"x-cos-meta-*,omitempty" url:"-"`
XCosCopySource string `header:"x-cos-copy-source" url:"-" xml:"-"`
XCosServerSideEncryption string `header:"x-cos-server-side-encryption,omitempty" url:"-" xml:"-"`
}
// ObjectCopyOptions is the option of Copy, choose header or body
type ObjectCopyOptions struct {
*ObjectCopyHeaderOptions `header:",omitempty" url:"-" xml:"-"`
*ACLHeaderOptions `header:",omitempty" url:"-" xml:"-"`
}
// ObjectCopyResult is the result of Copy
type ObjectCopyResult struct {
XMLName xml.Name `xml:"CopyObjectResult"`
ETag string `xml:"ETag,omitempty"`
LastModified string `xml:"LastModified,omitempty"`
}
// Copy 调用 PutObjectCopy 请求实现将一个文件从源路径复制到目标路径。建议文件大小 1M 到 5G
// 超过 5G 的文件请使用分块上传 Upload - Copy。在拷贝的过程中文件元属性和 ACL 可以被修改。
//
// 用户可以通过该接口实现文件移动,文件重命名,修改文件属性和创建副本。
//
// 注意:在跨帐号复制的时候,需要先设置被复制文件的权限为公有读,或者对目标帐号赋权,同帐号则不需要。
//
// https://cloud.tencent.com/document/product/436/10881
func (s *ObjectService) Copy(ctx context.Context, name, sourceURL string, opt *ObjectCopyOptions) (*ObjectCopyResult, *Response, error) {
var res ObjectCopyResult
if opt == nil {
opt = new(ObjectCopyOptions)
}
if opt.ObjectCopyHeaderOptions == nil {
opt.ObjectCopyHeaderOptions = new(ObjectCopyHeaderOptions)
}
opt.XCosCopySource = encodeURIComponent(sourceURL)
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name),
method: http.MethodPut,
body: nil,
optHeader: opt,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
// If the error occurs during the copy operation, the error response is embedded in the 200 OK response. This means that a 200 OK response can contain either a success or an error.
if err == nil && resp.StatusCode == 200 {
if res.ETag == "" {
return &res, resp, errors.New("response 200 OK, but body contains an error")
}
}
return &res, resp, err
}
// Delete Object请求可以将一个文件Object删除。
//
// https://www.qcloud.com/document/product/436/7743
func (s *ObjectService) Delete(ctx context.Context, name string) (*Response, error) {
// When use "" string might call the delete bucket interface
if len(name) == 0 {
return nil, errors.New("empty object name")
}
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name),
method: http.MethodDelete,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// ObjectHeadOptions is the option of HeadObject
type ObjectHeadOptions struct {
IfModifiedSince string `url:"-" header:"If-Modified-Since,omitempty"`
}
// Head Object请求可以取回对应Object的元数据Head的权限与Get的权限一致
//
// https://www.qcloud.com/document/product/436/7745
func (s *ObjectService) Head(ctx context.Context, name string, opt *ObjectHeadOptions, id ...string) (*Response, error) {
var u string
if len(id) == 1 {
u = fmt.Sprintf("/%s?versionId=%s", encodeURIComponent(name), id[0])
} else if len(id) == 0 {
u = "/" + encodeURIComponent(name)
} else {
return nil, errors.New("wrong params")
}
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodHead,
optHeader: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
if resp != nil && resp.Header["X-Cos-Object-Type"] != nil && resp.Header["X-Cos-Object-Type"][0] == "appendable" {
resp.Header.Add("x-cos-next-append-position", resp.Header["Content-Length"][0])
}
return resp, err
}
// ObjectOptionsOptions is the option of object options
type ObjectOptionsOptions struct {
Origin string `url:"-" header:"Origin"`
AccessControlRequestMethod string `url:"-" header:"Access-Control-Request-Method"`
AccessControlRequestHeaders string `url:"-" header:"Access-Control-Request-Headers,omitempty"`
}
// Options Object请求实现跨域访问的预请求。即发出一个 OPTIONS 请求给服务器以确认是否可以进行跨域操作。
//
// 当CORS配置不存在时请求返回403 Forbidden。
//
// https://www.qcloud.com/document/product/436/8288
func (s *ObjectService) Options(ctx context.Context, name string, opt *ObjectOptionsOptions) (*Response, error) {
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name),
method: http.MethodOptions,
optHeader: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// CASJobParameters support three way: Standard(in 35 hours), Expedited(quick way, in 15 mins), Bulk(in 5-12 hours_
type CASJobParameters struct {
Tier string `xml:"Tier"`
}
// ObjectRestoreOptions is the option of object restore
type ObjectRestoreOptions struct {
XMLName xml.Name `xml:"RestoreRequest"`
Days int `xml:"Days"`
Tier *CASJobParameters `xml:"CASJobParameters"`
}
// PutRestore API can recover an object of type archived by COS archive.
//
// https://cloud.tencent.com/document/product/436/12633
func (s *ObjectService) PostRestore(ctx context.Context, name string, opt *ObjectRestoreOptions) (*Response, error) {
u := fmt.Sprintf("/%s?restore", encodeURIComponent(name))
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodPost,
body: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// TODO Append 接口在优化未开放使用
//
// Append请求可以将一个文件Object以分块追加的方式上传至 Bucket 中。使用Append Upload的文件必须事前被设定为Appendable。
// 当Appendable的文件被执行Put Object的操作以后文件被覆盖属性改变为Normal。
//
// 文件属性可以在Head Object操作中被查询到当您发起Head Object请求时会返回自定义Header『x-cos-object-type』该Header只有两个枚举值Normal或者Appendable。
//
// 追加上传建议文件大小1M - 5G。如果position的值和当前Object的长度不致COS会返回409错误。
// 如果Append一个Normal的ObjectCOS会返回409 ObjectNotAppendable。
//
// Appendable的文件不可以被复制不参与版本管理不参与生命周期管理不可跨区域复制。
//
// 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ObjectPutHeaderOptions.ContentLength
//
// https://www.qcloud.com/document/product/436/7741
// func (s *ObjectService) Append(ctx context.Context, name string, position int, r io.Reader, opt *ObjectPutOptions) (*Response, error) {
// u := fmt.Sprintf("/%s?append&position=%d", encodeURIComponent(name), position)
// if position != 0{
// opt = nil
// }
// sendOpt := sendOptions{
// baseURL: s.client.BaseURL.BucketURL,
// uri: u,
// method: http.MethodPost,
// optHeader: opt,
// body: r,
// }
// resp, err := s.client.send(ctx, &sendOpt)
// return resp, err
// }
// ObjectDeleteMultiOptions is the option of DeleteMulti
type ObjectDeleteMultiOptions struct {
XMLName xml.Name `xml:"Delete" header:"-"`
Quiet bool `xml:"Quiet" header:"-"`
Objects []Object `xml:"Object" header:"-"`
//XCosSha1 string `xml:"-" header:"x-cos-sha1"`
}
// ObjectDeleteMultiResult is the result of DeleteMulti
type ObjectDeleteMultiResult struct {
XMLName xml.Name `xml:"DeleteResult"`
DeletedObjects []Object `xml:"Deleted,omitempty"`
Errors []struct {
Key string
Code string
Message string
} `xml:"Error,omitempty"`
}
// DeleteMulti 请求实现批量删除文件最大支持单次删除1000个文件。
// 对于返回结果COS提供Verbose和Quiet两种结果模式。Verbose模式将返回每个Object的删除结果
// Quiet模式只返回报错的Object信息。
// https://www.qcloud.com/document/product/436/8289
func (s *ObjectService) DeleteMulti(ctx context.Context, opt *ObjectDeleteMultiOptions) (*ObjectDeleteMultiResult, *Response, error) {
var res ObjectDeleteMultiResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/?delete",
method: http.MethodPost,
body: opt,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// Object is the meta info of the object
type Object struct {
Key string `xml:",omitempty"`
ETag string `xml:",omitempty"`
Size int `xml:",omitempty"`
PartNumber int `xml:",omitempty"`
LastModified string `xml:",omitempty"`
StorageClass string `xml:",omitempty"`
Owner *Owner `xml:",omitempty"`
}
type MultiUploadOptions struct {
OptIni *InitiateMultipartUploadOptions
PartSize int
}
// MultiUpload 为高级upload接口并发分块上传
//
// 需要指定分块大小 partSize >= 1 ,单位为MB
// 同时请确认分块数量不超过10000
//
func (s *ObjectService) MultiUpload(ctx context.Context, name string, r io.Reader, opt *MultiUploadOptions) (*CompleteMultipartUploadResult, *Response, error) {
optini := opt.OptIni
res, _, err := s.InitiateMultipartUpload(ctx, name, optini)
if err != nil {
return nil, nil, err
}
uploadID := res.UploadID
bufSize := opt.PartSize * 1024 * 1024
buffer := make([]byte, bufSize)
optcom := &CompleteMultipartUploadOptions{}
PartUpload := func(ch chan *Response, ctx context.Context, name string, uploadId string, partNumber int, data io.Reader, opt *ObjectUploadPartOptions) {
defer func() {
if err := recover(); err != nil {
fmt.Println(err)
}
}()
resp, _ := s.UploadPart(context.Background(), name, uploadId, partNumber, data, nil)
ch <- resp
}
chs := make([]chan *Response, 10000)
PartNumber := 0
for i := 1; true; i++ {
bytesread, err := r.Read(buffer)
if err != nil {
if err != io.EOF {
return nil, nil, err
}
PartNumber = i
break
}
chs[i] = make(chan *Response)
go PartUpload(chs[i], context.Background(), name, uploadID, i, strings.NewReader(string(buffer[:bytesread])), nil)
}
for i := 1; i < PartNumber; i++ {
resp := <-chs[i]
// Notice one part fail can not get the etag according.
etag := resp.Header.Get("ETag")
optcom.Parts = append(optcom.Parts, Object{
PartNumber: i, ETag: etag},
)
}
v, resp, err := s.CompleteMultipartUpload(context.Background(), name, uploadID, optcom)
return v, resp, err
}

View File

@@ -0,0 +1,63 @@
package cos
import (
"context"
"net/http"
)
// ObjectGetACLResult is the result of GetObjectACL
type ObjectGetACLResult ACLXml
// GetACL Get Object ACL接口实现使用API读取Object的ACL表只有所有者有权操作。
//
// https://www.qcloud.com/document/product/436/7744
func (s *ObjectService) GetACL(ctx context.Context, name string) (*ObjectGetACLResult, *Response, error) {
var res ObjectGetACLResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name) + "?acl",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// ObjectPutACLOptions the options of put object acl
type ObjectPutACLOptions struct {
Header *ACLHeaderOptions `url:"-" xml:"-"`
Body *ACLXml `url:"-" header:"-"`
}
// PutACL 使用API写入Object的ACL表您可以通过Header"x-cos-acl", "x-cos-grant-read" ,
// "x-cos-grant-write" ,"x-cos-grant-full-control"传入ACL信息
// 也可以通过body以XML格式传入ACL信息但是只能选择Header和Body其中一种否则返回冲突。
//
// Put Object ACL是一个覆盖操作传入新的ACL将覆盖原有ACL。只有所有者有权操作。
//
// "x-cos-acl"枚举值为public-readprivatepublic-read意味这个Object有公有读私有写的权限
// private意味这个Object有私有读写的权限。
//
// "x-cos-grant-read"意味被赋予权限的用户拥有该Object的读权限
//
// "x-cos-grant-write"意味被赋予权限的用户拥有该Object的写权限
//
// "x-cos-grant-full-control"意味被赋予权限的用户拥有该Object的读写权限
//
// https://www.qcloud.com/document/product/436/7748
func (s *ObjectService) PutACL(ctx context.Context, name string, opt *ObjectPutACLOptions) (*Response, error) {
header := opt.Header
body := opt.Body
if body != nil {
header = nil
}
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name) + "?acl",
method: http.MethodPut,
optHeader: header,
body: body,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}

View File

@@ -0,0 +1,191 @@
package cos
import (
"context"
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
)
// InitiateMultipartUploadOptions is the option of InitateMultipartUpload
type InitiateMultipartUploadOptions struct {
*ACLHeaderOptions
*ObjectPutHeaderOptions
}
// InitiateMultipartUploadResult is the result of InitateMultipartUpload
type InitiateMultipartUploadResult struct {
XMLName xml.Name `xml:"InitiateMultipartUploadResult"`
Bucket string
Key string
UploadID string `xml:"UploadId"`
}
// InitiateMultipartUpload 请求实现初始化分片上传成功执行此请求以后会返回Upload ID用于后续的Upload Part请求。
//
// https://www.qcloud.com/document/product/436/7746
func (s *ObjectService) InitiateMultipartUpload(ctx context.Context, name string, opt *InitiateMultipartUploadOptions) (*InitiateMultipartUploadResult, *Response, error) {
var res InitiateMultipartUploadResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: "/" + encodeURIComponent(name) + "?uploads",
method: http.MethodPost,
optHeader: opt,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// ObjectUploadPartOptions is the options of upload-part
type ObjectUploadPartOptions struct {
Expect string `header:"Expect,omitempty" url:"-"`
XCosContentSHA1 string `header:"x-cos-content-sha1" url:"-"`
ContentLength int `header:"Content-Length,omitempty" url:"-"`
}
// UploadPart 请求实现在初始化以后的分块上传支持的块的数量为1到10000块的大小为1 MB 到5 GB。
// 在每次请求Upload Part时候需要携带partNumber和uploadIDpartNumber为块的编号支持乱序上传。
//
// 当传入uploadID和partNumber都相同的时候后传入的块将覆盖之前传入的块。当uploadID不存在时会返回404错误NoSuchUpload.
//
// 当 r 不是 bytes.Buffer/bytes.Reader/strings.Reader 时,必须指定 opt.ContentLength
//
// https://www.qcloud.com/document/product/436/7750
func (s *ObjectService) UploadPart(ctx context.Context, name, uploadID string, partNumber int, r io.Reader, opt *ObjectUploadPartOptions) (*Response, error) {
u := fmt.Sprintf("/%s?partNumber=%d&uploadId=%s", encodeURIComponent(name), partNumber, uploadID)
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodPut,
optHeader: opt,
body: r,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}
// ObjectListPartsOptions is the option of ListParts
type ObjectListPartsOptions struct {
EncodingType string `url:"Encoding-type,omitempty"`
MaxParts string `url:"max-parts,omitempty"`
PartNumberMarker string `url:"part-number-marker,omitempty"`
}
// ObjectListPartsResult is the result of ListParts
type ObjectListPartsResult struct {
XMLName xml.Name `xml:"ListPartsResult"`
Bucket string
EncodingType string `xml:"Encoding-type,omitempty"`
Key string
UploadID string `xml:"UploadId"`
Initiator *Initiator `xml:"Initiator,omitempty"`
Owner *Owner `xml:"Owner,omitempty"`
StorageClass string
PartNumberMarker string
NextPartNumberMarker string `xml:"NextPartNumberMarker,omitempty"`
MaxParts string
IsTruncated bool
Parts []Object `xml:"Part,omitempty"`
}
// ListParts 用来查询特定分块上传中的已上传的块。
//
// https://www.qcloud.com/document/product/436/7747
func (s *ObjectService) ListParts(ctx context.Context, name, uploadID string, opt *ObjectListPartsOptions) (*ObjectListPartsResult, *Response, error) {
u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID)
var res ObjectListPartsResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodGet,
result: &res,
optQuery: opt,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}
// CompleteMultipartUploadOptions is the option of CompleteMultipartUpload
type CompleteMultipartUploadOptions struct {
XMLName xml.Name `xml:"CompleteMultipartUpload"`
Parts []Object `xml:"Part"`
}
// CompleteMultipartUploadResult is the result CompleteMultipartUpload
type CompleteMultipartUploadResult struct {
XMLName xml.Name `xml:"CompleteMultipartUploadResult"`
Location string
Bucket string
Key string
ETag string
}
// ObjectList can used for sort the parts which needs in complete upload part
// sort.Sort(cos.ObjectList(opt.Parts))
type ObjectList []Object
func (o ObjectList) Len() int {
return len(o)
}
func (o ObjectList) Swap(i, j int) {
o[i], o[j] = o[j], o[i]
}
func (o ObjectList) Less(i, j int) bool { // rewrite the Less method from small to big
return o[i].PartNumber < o[j].PartNumber
}
// CompleteMultipartUpload 用来实现完成整个分块上传。当您已经使用Upload Parts上传所有块以后你可以用该API完成上传。
// 在使用该API时您必须在Body中给出每一个块的PartNumber和ETag用来校验块的准确性。
//
// 由于分块上传的合并需要数分钟时间因而当合并分块开始的时候COS就立即返回200的状态码在合并的过程中
// COS会周期性的返回空格信息来保持连接活跃直到合并完成COS会在Body中返回合并后块的内容。
//
// 当上传块小于1 MB的时候在调用该请求时会返回400 EntityTooSmall
// 当上传块编号不连续的时候在调用该请求时会返回400 InvalidPart
// 当请求Body中的块信息没有按序号从小到大排列的时候在调用该请求时会返回400 InvalidPartOrder
// 当UploadId不存在的时候在调用该请求时会返回404 NoSuchUpload。
//
// 建议您及时完成分块上传或者舍弃分块上传,因为已上传但是未终止的块会占用存储空间进而产生存储费用。
//
// https://www.qcloud.com/document/product/436/7742
func (s *ObjectService) CompleteMultipartUpload(ctx context.Context, name, uploadID string, opt *CompleteMultipartUploadOptions) (*CompleteMultipartUploadResult, *Response, error) {
u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID)
var res CompleteMultipartUploadResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodPost,
body: opt,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
// If the error occurs during the copy operation, the error response is embedded in the 200 OK response. This means that a 200 OK response can contain either a success or an error.
if err == nil && resp.StatusCode == 200 {
if res.ETag == "" {
return &res, resp, errors.New("response 200 OK, but body contains an error")
}
}
return &res, resp, err
}
// AbortMultipartUpload 用来实现舍弃一个分块上传并删除已上传的块。当您调用Abort Multipart Upload时
// 如果有正在使用这个Upload Parts上传块的请求则Upload Parts会返回失败。当该UploadID不存在时会返回404 NoSuchUpload。
//
// 建议您及时完成分块上传或者舍弃分块上传,因为已上传但是未终止的块会占用存储空间进而产生存储费用。
//
// https://www.qcloud.com/document/product/436/7740
func (s *ObjectService) AbortMultipartUpload(ctx context.Context, name, uploadID string) (*Response, error) {
u := fmt.Sprintf("/%s?uploadId=%s", encodeURIComponent(name), uploadID)
sendOpt := sendOptions{
baseURL: s.client.BaseURL.BucketURL,
uri: u,
method: http.MethodDelete,
}
resp, err := s.client.send(ctx, &sendOpt)
return resp, err
}

Some files were not shown because too many files have changed in this diff Show More