diff --git a/Gopkg.lock b/Gopkg.lock index c0981cecb..430e9f7e6 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -212,10 +212,10 @@ revision = "2df9a531813370438a4d79bfc33e21f58063ed87" [[projects]] + branch = "master" name = "github.com/spf13/pflag" packages = ["."] - revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" - version = "v1.0.0" + revision = "7aff26db30c1be810f9de5038ec5ef96ac41fd7c" [[projects]] branch = "master" @@ -279,7 +279,7 @@ [[projects]] name = "google.golang.org/appengine" - packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"] + packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","log","urlfetch"] revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a" version = "v1.0.0" @@ -292,6 +292,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "e660aa956f1e580bda1ce240cdb673bcbc00bb8f2ef2e77e85d3d941528836f1" + inputs-digest = "119fcfb60b243cc6c75c9ebd02ddc913a1492a128cb7bdecb7ef180a0fc5be44" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/spf13/pflag/flag.go b/vendor/github.com/spf13/pflag/flag.go index 6f1fc3007..7b84e2cde 100644 --- a/vendor/github.com/spf13/pflag/flag.go +++ b/vendor/github.com/spf13/pflag/flag.go @@ -556,6 +556,10 @@ func UnquoteUsage(flag *Flag) (name string, usage string) { name = "int" case "uint64": name = "uint" + case "stringSlice": + name = "strings" + case "intSlice": + name = "ints" } return diff --git a/vendor/github.com/spf13/pflag/flag_test.go b/vendor/github.com/spf13/pflag/flag_test.go index c3def0fd4..29fec8671 100644 --- a/vendor/github.com/spf13/pflag/flag_test.go +++ b/vendor/github.com/spf13/pflag/flag_test.go @@ -978,12 +978,12 @@ const defaultOutput = ` --A for bootstrapping, allo --IP ip IP address with no default --IPMask ipMask Netmask address with no default --IPNet ipNet IP network with no default - --Ints intSlice int slice with zero default + --Ints ints int slice with zero default --N int a non-zero int (default 27) --ND1 string[="bar"] a string with NoOptDefVal (default "foo") --ND2 num[=4321] a num with NoOptDefVal (default 1234) --StringArray stringArray string array with zero default - --StringSlice stringSlice string slice with zero default + --StringSlice strings string slice with zero default --Z int an int that defaults to zero --custom custom custom Value implementation --customP custom a VarP with default (default 10) diff --git a/vendor/github.com/yunify/qingstor-sdk-go/client/image/image.go b/vendor/github.com/yunify/qingstor-sdk-go/client/image/image.go new file mode 100644 index 000000000..b63725936 --- /dev/null +++ b/vendor/github.com/yunify/qingstor-sdk-go/client/image/image.go @@ -0,0 +1,259 @@ +package image + +import ( + "fmt" + "reflect" + "strings" + + "github.com/yunify/qingstor-sdk-go/service" +) + +const ( + // ActionSep is separator of action. + ActionSep = ":" + + // OPSep is separator of operation. + OPSep = "|" + + // KVSep is separator of Key-Value. + KVSep = "_" + + //KVPairSep is separator of args. + KVPairSep = "," +) + +const ( + // InfoOperation is string of info operation. + InfoOperation string = "info" + + // CropOperation is string of crop operation. + CropOperation string = "crop" + + // FormatOperation is string of format operation. + FormatOperation string = "format" + + // ResizeOperation is string of resize operation. + ResizeOperation string = "resize" + + // RotateOperation is string of rotate operation. + RotateOperation string = "rotate" + + // WaterMarkOperation is string of watermark operation. + WaterMarkOperation string = "watermark" + + // WaterMarkImageOperation is string of watermark image operation. + WaterMarkImageOperation string = "watermark_image" +) + +// ResizeMode is the type of resize mode. +type ResizeMode int + +const ( + // ResizeFixed resizes image to fix width and height. + ResizeFixed ResizeMode = iota + + // ResizeForce resizes image to force witdth and height. + ResizeForce + + // ResizeThumbnail resizes image to thumbnail width and height. + ResizeThumbnail +) + +// CropGravity is the type of crop gravity. +type CropGravity int + +const ( + + // CropCenter crops image to center width and height. + CropCenter CropGravity = iota + + // CropNorth crops image to north width and height. + CropNorth + + // CropEast crops image to east width and height. + CropEast + + // CropSouth crops image to south width and height. + CropSouth + + // CropWest crops image to west width and height. + CropWest + + // CropNorthWest crops image to north west width and height. + CropNorthWest + + // CropNorthEast crops image to north east width and height. + CropNorthEast + + // CropSouthWest crops image to south west width and height. + CropSouthWest + + // CropSouthEast crops image to south east width and height. + CropSouthEast + + // CropAuto crops image to auto width and height. + CropAuto +) + +// Image is struct of Image process. +type Image struct { + key *string + bucket *service.Bucket + input *service.ImageProcessInput +} + +// Init initializes an image to process. +func Init(bucket *service.Bucket, objectKey string) *Image { + return &Image{ + key: &objectKey, + bucket: bucket, + input: &service.ImageProcessInput{}, + } +} + +// Info gets the information of the image. +func (image *Image) Info() *Image { + return image.setActionParam(InfoOperation, nil) +} + +// RotateParam is param of the rotate operation. +type RotateParam struct { + Angle int `schema:"a"` +} + +// Rotate image. +func (image *Image) Rotate(param *RotateParam) *Image { + return image.setActionParam(RotateOperation, param) +} + +// ResizeParam is param of the resize operation. +type ResizeParam struct { + Width int `schema:"w,omitempty"` + Height int `schema:"h,omitempty"` + Mode ResizeMode `schema:"m"` +} + +// Resize image. +func (image *Image) Resize(param *ResizeParam) *Image { + return image.setActionParam(ResizeOperation, param) +} + +// CropParam is param of the crop operation. +type CropParam struct { + Width int `schema:"w,omitempty"` + Height int `schema:"h,omitempty"` + Gravity CropGravity `schema:"g"` +} + +// Crop image. +func (image *Image) Crop(param *CropParam) *Image { + return image.setActionParam(CropOperation, param) +} + +// FormatParam is param of the format operation. +type FormatParam struct { + Type string `schema:"t"` +} + +// Format image. +func (image *Image) Format(param *FormatParam) *Image { + return image.setActionParam(FormatOperation, param) +} + +// WaterMarkParam is param of the wartermark operation. +type WaterMarkParam struct { + Dpi int `schema:"d,omitempty"` + Opacity float64 `schema:"p,omitempty"` + Text string `schema:"t"` + Color string `schema:"c"` +} + +// WaterMark is operation of watermark text content. +func (image *Image) WaterMark(param *WaterMarkParam) *Image { + return image.setActionParam(WaterMarkOperation, param) +} + +// WaterMarkImageParam is param of the waterMark image operation +type WaterMarkImageParam struct { + Left int `schema:"l"` + Top int `schema:"t"` + Opacity float64 `schema:"p,omitempty"` + URL string `schema:"u"` +} + +// WaterMarkImage is operation of watermark image. +func (image *Image) WaterMarkImage(param *WaterMarkImageParam) *Image { + return image.setActionParam(WaterMarkImageOperation, param) +} + +// Process does Image process. +func (image *Image) Process() (*service.ImageProcessOutput, error) { + defer func(input *service.ImageProcessInput) { + input.Action = nil + }(image.input) + return image.bucket.ImageProcess(*image.key, image.input) +} + +func (image *Image) setActionParam(operation string, param interface{}) *Image { + uri := operation + if param != nil { + uri = fmt.Sprintf("%s%s%s", uri, ActionSep, buildOptParamStr(param)) + } + if image.input.Action != nil { + uri = fmt.Sprintf("%s%s%s", *image.input.Action, OPSep, uri) + } + image.input.Action = &uri + return image +} + +func buildOptParamStr(param interface{}) string { + v := reflect.ValueOf(param).Elem() + var kvPairs []string + + for i := 0; i < v.NumField(); i++ { + vf := v.Field(i) + tf := v.Type().Field(i) + key := tf.Tag.Get("schema") + value := vf.Interface() + tagValues := strings.Split(key, ",") + if isEmptyValue(vf) && + len(tagValues) == 2 && + tagValues[1] == "omitempty" { + continue + } + key = tagValues[0] + kvPairs = append(kvPairs, fmt.Sprintf("%v%s%v", key, KVSep, value)) + } + return strings.Join(kvPairs, KVPairSep) +} + +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.Ptr: + return v.IsNil() + } + return false +} diff --git a/vendor/github.com/yunify/qingstor-sdk-go/client/image/image_test.go b/vendor/github.com/yunify/qingstor-sdk-go/client/image/image_test.go new file mode 100644 index 000000000..32254c274 --- /dev/null +++ b/vendor/github.com/yunify/qingstor-sdk-go/client/image/image_test.go @@ -0,0 +1,63 @@ +package image + +import ( + "testing" + + "github.com/stretchr/testify/assert" + qs "github.com/yunify/qingstor-sdk-go/service" +) + +var image *Image + +func init() { + bucket := &qs.Bucket{} + + // test.jpg is only a string + image = Init(bucket, "test.jpg") +} + +func TestQueryString(t *testing.T) { + var param interface{} + param = &RotateParam{ + Angle: 90, + } + image.setActionParam(RotateOperation, param) + assert.Equal(t, *image.input.Action, "rotate:a_90") + + param = &CropParam{ + Width: 300, + Height: 400, + Gravity: 0, + } + image.setActionParam(CropOperation, param) + assert.Equal(t, *image.input.Action, "rotate:a_90|crop:w_300,h_400,g_0") + + param = &ResizeParam{ + Width: 500, + Height: 500, + Mode: ResizeForce, + } + image.setActionParam(ResizeOperation, param) + assert.Equal(t, *image.input.Action, "rotate:a_90|crop:w_300,h_400,g_0|resize:w_500,h_500,m_1") + + param = &FormatParam{ + Type: "png", + } + image.setActionParam(FormatOperation, param) + assert.Equal(t, *image.input.Action, "rotate:a_90|crop:w_300,h_400,g_0|resize:w_500,h_500,m_1|format:t_png") + + param = &WaterMarkParam{ + Text: "5rC05Y2w5paH5a2X", + } + image.setActionParam(WaterMarkOperation, param) + assert.Equal(t, *image.input.Action, "rotate:a_90|crop:w_300,h_400,g_0|resize:w_500,h_500,m_1|format:t_png|watermark:t_5rC05Y2w5paH5a2X,c_") + + param = &WaterMarkImageParam{ + URL: "aHR0cHM6Ly9wZWszYS5xaW5nc3Rvci5jb20vaW1nLWRvYy1lZy9xaW5jbG91ZC5wbmc", + } + image.setActionParam(WaterMarkImageOperation, param) + assert.Equal(t, *image.input.Action, "rotate:a_90|crop:w_300,h_400,g_0|resize:w_500,h_500,m_1|format:t_png|watermark:t_5rC05Y2w5paH5a2X,c_|watermark_image:l_0,t_0,u_aHR0cHM6Ly9wZWszYS5xaW5nc3Rvci5jb20vaW1nLWRvYy1lZy9xaW5jbG91ZC5wbmc") + + image.setActionParam(InfoOperation, nil) + assert.Equal(t, *image.input.Action, "rotate:a_90|crop:w_300,h_400,g_0|resize:w_500,h_500,m_1|format:t_png|watermark:t_5rC05Y2w5paH5a2X,c_|watermark_image:l_0,t_0,u_aHR0cHM6Ly9wZWszYS5xaW5nc3Rvci5jb20vaW1nLWRvYy1lZy9xaW5jbG91ZC5wbmc|info") +} diff --git a/vendor/github.com/yunify/qingstor-sdk-go/docs/image_process_usage.md b/vendor/github.com/yunify/qingstor-sdk-go/docs/image_process_usage.md new file mode 100644 index 000000000..e9b01a042 --- /dev/null +++ b/vendor/github.com/yunify/qingstor-sdk-go/docs/image_process_usage.md @@ -0,0 +1,272 @@ +# QingStor Image Processing Usage Guide + +For processing the image stored in QingStor by a variety of basic operations, such as format, crop, watermark and so on. +Please see [QingStor Image API](https://docs.qingcloud.com/qingstor/data_process/image_process/index.html). + +## Usage +Before using the image service, you need to initialize the [Configuration](https://github.com/yunify/qingstor-sdk-go/blob/master/docs/configuration.md) and [QingStor Service](https://github.com/yunify/qingstor-sdk-go/blob/master/docs/qingstor_service_usage.md). + +``` go +//Import the latest version API +import ( + "github.com/yunify/qingstor-sdk-go/config" + "github.com/yunify/qingstor-sdk-go/client/image" + qs "github.com/yunify/qingstor-sdk-go/service" +) +``` + +## Code Snippet + +Create configuration from Access Key and Initialize the QingStor service with a configuration. +``` go +// Initialize the QingStor service with a configuration +config, _ := config.New("ACCESS_KEY_ID", "SECRET_ACCESS_KEY") +service, _ := qs.Init(config) +``` +Initialize a QingStor bucket. +``` go +bucket, _ := service.Bucket("bucketName", "zoneID") +``` +Initialize a image. +``` go +img := image.Init(bucket, "imageName") +``` + +Now you can use the the high level APIs or basic image process API to do the image operation. + +Get the information of the image +``` go +imageProcessOutput, _ := img.Info().Process() +``` + +Crop the image. +``` go +imageProcessOutput, _ := img.Crop(&image.CropParam{ + ...operation_param... +}).Process() +``` + +Rotate the image. +``` go +imageProcessOutput, _ := img.Rotate(&image.RotateParam{ + ...operation_param... +}).Process() +``` +Resize the image. +``` go +imageProcessOutput, _ := img.Resize(&image.ResizeParam{ + ...operation_param... +}).Process() +``` +Watermark the image. +``` go +imageProcessOutput, _ := img.WaterMark(&image.WaterMarkParam{ + ...operation_param... +}).Process() +``` +WaterMarkImage the image. +``` go +imageProcessOutput, _ : = img.WaterMarkImage(&image.WaterMarkImageParam{ + ...operation_param... +}).Process() +``` +Format the image. +``` go +imageProcessOutput, _ := img.Format(&image.Format{ + ...operation_param... +}).Process() +``` +Operation pipline, the image will be processed by order. The maximum number of operations in the pipeline is 10. +``` go +// Rotate and then resize the image +imageProcessOutput, _ := img.Rotate(&image.RotateParam{ + ... operation_param... +}).Resize(&image.ResizeParam{ + ... operation_param... +}).Process() +``` +Use the original basic API to rotate the image 90 angles. +``` go +operation := "rotate:a_90" +imageProcessOutput, err := bucket.ImageProcess("imageName", &qs.ImageProcessInput{ + Action: &operation}) +``` + +`operation_param` is the image operation param, which definined in `qingstor-sdk-go/client/image/image.go`. +``` go +import "github.com/yunify/qingstor-sdk-go/service" +// client/image/image.go +type Image struct { + key *string + bucket *service.Bucket + input *service.ImageProcessInput +} + +// About cropping image definition +type CropGravity int +const ( + CropCenter CropGravity = iota + CropNorth + CropEast + CropSouth + CropWest + CropNorthWest + CropNorthEast + CropSouthWest + CropSouthEast + CropAuto +) +type CropParam struct { + Width int `schema:"w,omitempty"` + Height int `schema:"h,omitempty"` + Gravity CropGravity `schema:"g"` +} + +// About rotating image definitions +type RotateParam struct { + Angle int `schema:"a"` +} + +// About resizing image definitions +type ResizeMode int +type ResizeParam struct { + Width int `schema:"w,omitempty"` + Height int `schema:"h,omitempty"` + Mode ResizeMode `schema:"m"` +} + +// On the definition of text watermarking +type WaterMarkParam struct { + Dpi int `schema:"d,omitempty"` + Opacity float64 `schema:"p,omitempty"` + Text string `schema:"t"` + Color string `schema:"c"` +} + +// On the definition of image watermarking + type WaterMarkImageParam struct { + Left int `schema:"l"` + Top int `schema:"t"` + Opacity float64 `schema:"p,omitempty"` + URL string `schema:"u"` +} + +// About image format conversion definitions +type FormatParam struct { + Type string `schema:"t"` +} + +``` + +__Quick Start Code Example:__ + +Include a complete example, but the code needs to fill in your own information + +``` go +package main + +import ( + "log" + + "github.com/yunify/qingstor-sdk-go/client/image" + "github.com/yunify/qingstor-sdk-go/config" + qs "github.com/yunify/qingstor-sdk-go/service" +) + +func main() { + // Load your configuration + // Replace here with your key pair + config, err := config.New("ACCESS_KEY_ID", "SECRET_ACCESS_KEY") + checkErr(err) + + // Initialize QingStror Service + service, err := qs.Init(config) + checkErr(err) + + // Initialize Bucket + // Replace here with your bucketName and zoneID + bucket, err := service.Bucket("bucketName", "zoneID") + checkErr(err) + + // Initialize Image + // Replace here with your your ImageName + img := image.Init(bucket, "imageName") + checkErr(err) + + // Because 0 is an invalid parameter, default not modify + imageProcessOutput, err := img.Crop(&image.CropParam{Width: 0}).Process() + checkErr(err) + testOutput(imageProcessOutput) + + // Rotate the image 90 angles + imageProcessOutput, err = img.Rotate(&image.RotateParam{Angle: 90}).Process() + checkErr(err) + testOutput(imageProcessOutput) + + // Text watermark, Watermark text content, encoded by base64. + imageProcessOutput, err = img.WaterMark(&image.WaterMarkParam{ + Text: "5rC05Y2w5paH5a2X", + }).Process() + checkErr(err) + testOutput(imageProcessOutput) + + // Image watermark, Watermark image url encoded by base64. + imageProcessOutput, err = img.WaterMarkImage(&image.WaterMarkImageParam{ + URL: "aHR0cHM6Ly9wZWszYS5xaW5nc3Rvci5jb20vaW1nLWRvYy1lZy9xaW5jbG91ZC5wbmc", + }).Process() + checkErr(err) + testOutput(imageProcessOutput) + + // Reszie the image with width 300px and height 400 px + imageProcessOutput, err = img.Resize(&image.ResizeParam{ + Width: 300, + Height: 400, + }).Process() + checkErr(err) + testOutput(imageProcessOutput) + + // Swap format to jpeg + imageProcessOutput, err = img.Format(&image.FormatParam{ + Type: "jpeg", + }).Process() + checkErr(err) + testOutput(imageProcessOutput) + + // Pipline model + // The maximum number of operations in the pipeline is 10 + imageProcessOutput, err = img.Rotate(&image.RotateParam{ + Angle: 270, + }).Resize(&image.ResizeParam{ + Width: 300, + Height: 300, + }).Process() + checkErr(err) + testOutput(imageProcessOutput) + + // Get the information of the image + imageProcessOutput, err = img.Info().Process() + checkErr(err) + testOutput(imageProcessOutput) + + // Use the original api to rotate the image 90 angles + operation := "rotate:a_90" + imageProcessOutput, err = bucket.ImageProcess("imageName", &qs.ImageProcessInput{ + Action: &operation}) + checkErr(err) + testOutput(imageProcessOutput) +} + +// *qs.ImageProcessOutput: github.com/yunify/qingstor-sdk-go/service/object.go +func testOutput(out *qs.ImageProcessOutput) { + log.Println(*out.StatusCode) + log.Println(*out.RequestID) + log.Println(out.Body) + log.Println(*out.ContentLength) +} + +func checkErr(err error) { + if err != nil { + log.Println(err) + } +} +``` diff --git a/vendor/github.com/yunify/qingstor-sdk-go/request/request.go b/vendor/github.com/yunify/qingstor-sdk-go/request/request.go index f731b0f6d..6feef641a 100644 --- a/vendor/github.com/yunify/qingstor-sdk-go/request/request.go +++ b/vendor/github.com/yunify/qingstor-sdk-go/request/request.go @@ -21,6 +21,7 @@ import ( "fmt" "net/http" "reflect" + "strconv" "time" "github.com/pengsrc/go-shared/convert" @@ -64,6 +65,27 @@ func New(o *data.Operation, i data.Input, x interface{}) (*Request, error) { // Send sends API request. // It returns error if error occurred. func (r *Request) Send() error { + err := r.Build() + if err != nil { + return err + } + + err = r.Sign() + if err != nil { + return err + } + + err = r.Do() + if err != nil { + return err + } + + return nil +} + +// Build checks and builds the API request. +// It returns error if error occurred. +func (r *Request) Build() error { err := r.check() if err != nil { return err @@ -74,12 +96,13 @@ func (r *Request) Send() error { return err } - err = r.sign() - if err != nil { - return err - } + return nil +} - err = r.send() +// Do sends and unpacks the API request. +// It returns error if error occurred. +func (r *Request) Do() error { + err := r.send() if err != nil { return err } @@ -95,17 +118,7 @@ func (r *Request) Send() error { // Sign sign the API request by setting the authorization header. // It returns error if error occurred. func (r *Request) Sign() error { - err := r.check() - if err != nil { - return err - } - - err = r.build() - if err != nil { - return err - } - - err = r.sign() + err := r.sign() if err != nil { return err } @@ -116,21 +129,30 @@ func (r *Request) Sign() error { // SignQuery sign the API request by appending query string. // It returns error if error occurred. func (r *Request) SignQuery(timeoutSeconds int) error { - err := r.check() + err := r.signQuery(int(time.Now().Unix()) + timeoutSeconds) if err != nil { return err } - err = r.build() - if err != nil { - return err - } + return nil +} - err = r.signQuery(int(time.Now().Unix()) + timeoutSeconds) - if err != nil { - return err - } +// ApplySignature applies the Authorization header. +// It returns error if error occurred. +func (r *Request) ApplySignature(authorization string) error { + r.HTTPRequest.Header.Set("Authorization", authorization) + return nil +} +// ApplyQuerySignature applies the query signature. +// It returns error if error occurred. +func (r *Request) ApplyQuerySignature(accessKeyID string, expires int, signature string) error { + queryValue := r.HTTPRequest.URL.Query() + queryValue.Set("access_key_id", accessKeyID) + queryValue.Set("expires", strconv.Itoa(expires)) + queryValue.Set("signature", signature) + + r.HTTPRequest.URL.RawQuery = queryValue.Encode() return nil } diff --git a/vendor/github.com/yunify/qingstor-sdk-go/request/request_test.go b/vendor/github.com/yunify/qingstor-sdk-go/request/request_test.go index 203cabddf..5fb768f36 100644 --- a/vendor/github.com/yunify/qingstor-sdk-go/request/request_test.go +++ b/vendor/github.com/yunify/qingstor-sdk-go/request/request_test.go @@ -67,7 +67,7 @@ func Time(v time.Time) *time.Time { return &v } -func TestRequest_Send(t *testing.T) { +func TestRequestSend(t *testing.T) { conf, err := config.New("ACCESS_KEY_ID", "SECRET_ACCESS_KEY") assert.Nil(t, err) logger.SetLevel("warn") diff --git a/vendor/github.com/yunify/qingstor-sdk-go/request/signer/qingstor.go b/vendor/github.com/yunify/qingstor-sdk-go/request/signer/qingstor.go index 361491276..088cec1c4 100644 --- a/vendor/github.com/yunify/qingstor-sdk-go/request/signer/qingstor.go +++ b/vendor/github.com/yunify/qingstor-sdk-go/request/signer/qingstor.go @@ -122,12 +122,16 @@ func (qss *QingStorSigner) BuildQuerySignature(request *http.Request, expires in // BuildStringToSign build the string to sign. func (qss *QingStorSigner) BuildStringToSign(request *http.Request) (string, error) { + date := request.Header.Get("Date") + if request.Header.Get("X-QS-Date") != "" { + date = "" + } stringToSign := fmt.Sprintf( "%s\n%s\n%s\n%s\n", request.Method, request.Header.Get("Content-MD5"), request.Header.Get("Content-Type"), - request.Header.Get("Date"), + date, ) stringToSign += qss.buildCanonicalizedHeaders(request) @@ -247,6 +251,7 @@ func (qss *QingStorSigner) paramsToSign(key string) bool { "stats": true, "upload_id": true, "uploads": true, + "image": true, "response-expires": true, "response-cache-control": true, "response-content-type": true, diff --git a/vendor/github.com/yunify/qingstor-sdk-go/request/signer/qingstor_test.go b/vendor/github.com/yunify/qingstor-sdk-go/request/signer/qingstor_test.go index 2d159afb7..70f492345 100644 --- a/vendor/github.com/yunify/qingstor-sdk-go/request/signer/qingstor_test.go +++ b/vendor/github.com/yunify/qingstor-sdk-go/request/signer/qingstor_test.go @@ -46,6 +46,27 @@ func TestQingStorSignerWriteSignature(t *testing.T) { assert.Equal(t, signature, httpRequest.Header.Get("Authorization")) } +func TestQingStorSignerWriteSignatureWithXQSDate(t *testing.T) { + url := "https://qingstor.com/?acl&upload_id=fde133b5f6d932cd9c79bac3c7318da1&part_number=0&other=abc" + httpRequest, err := http.NewRequest("GET", url, nil) + httpRequest.Header.Set("Date", convert.TimeToString(time.Time{}, convert.RFC822)) + httpRequest.Header.Set("X-QS-Date", convert.TimeToString(time.Time{}, convert.RFC822)) + httpRequest.Header.Set("X-QS-Test-2", "Test 2") + httpRequest.Header.Set("X-QS-Test-1", "Test 1") + assert.Nil(t, err) + + s := QingStorSigner{ + AccessKeyID: "ENV_ACCESS_KEY_ID", + SecretAccessKey: "ENV_SECRET_ACCESS_KEY", + } + + err = s.WriteSignature(httpRequest) + assert.Nil(t, err) + + signature := "QS ENV_ACCESS_KEY_ID:qkY+tOMdqfDAVv+ZBtlWeEBxlbyIKaQmj5lQlylENzo=" + assert.Equal(t, signature, httpRequest.Header.Get("Authorization")) +} + func TestQingStorSignerWriteSignatureChinese(t *testing.T) { url := "https://zone.qingstor.com/bucket-name/中文" httpRequest, err := http.NewRequest("GET", url, nil) @@ -83,3 +104,24 @@ func TestQingStorSignerWriteQuerySignature(t *testing.T) { targetURL := "https://qingstor.com/?acl&upload_id=fde133b5f6d932cd9c79bac3c7318da1&part_number=0&access_key_id=ENV_ACCESS_KEY_ID&expires=3600&signature=GRL3p3NOgHR9CQygASvyo344vdnO1hFke6ZvQ5mDVHM=" assert.Equal(t, httpRequest.URL.String(), targetURL) } + +func TestQingStorSignerWriteQuerySignatureWithXQSDate(t *testing.T) { + url := "https://qingstor.com/?acl&upload_id=fde133b5f6d932cd9c79bac3c7318da1&part_number=0" + httpRequest, err := http.NewRequest("GET", url, nil) + httpRequest.Header.Set("Date", convert.TimeToString(time.Time{}, convert.RFC822)) + httpRequest.Header.Set("X-QS-Date", convert.TimeToString(time.Time{}, convert.RFC822)) + httpRequest.Header.Set("X-QS-Test-2", "Test 2") + httpRequest.Header.Set("X-QS-Test-1", "Test 1") + assert.Nil(t, err) + + s := QingStorSigner{ + AccessKeyID: "ENV_ACCESS_KEY_ID", + SecretAccessKey: "ENV_SECRET_ACCESS_KEY", + } + + err = s.WriteQuerySignature(httpRequest, 3600) + assert.Nil(t, err) + + targetURL := "https://qingstor.com/?acl&upload_id=fde133b5f6d932cd9c79bac3c7318da1&part_number=0&access_key_id=ENV_ACCESS_KEY_ID&expires=3600&signature=plFxMFP1EzKVtdF%2BbApT8rhW9AUAIWfmZcOGH3m27t0=" + assert.Equal(t, httpRequest.URL.String(), targetURL) +} diff --git a/vendor/github.com/yunify/qingstor-sdk-go/service/bucket.go b/vendor/github.com/yunify/qingstor-sdk-go/service/bucket.go index cd71ac3ef..935ad33bd 100644 --- a/vendor/github.com/yunify/qingstor-sdk-go/service/bucket.go +++ b/vendor/github.com/yunify/qingstor-sdk-go/service/bucket.go @@ -744,12 +744,14 @@ func (s *Bucket) ListMultipartUploadsRequest(input *ListMultipartUploadsInput) ( type ListMultipartUploadsInput struct { // Put all keys that share a common prefix into a list Delimiter *string `json:"delimiter,omitempty" name:"delimiter" location:"params"` + // Limit results returned from the first key after key_marker sorted by alphabetical order + KeyMarker *string `json:"key_marker,omitempty" name:"key_marker" location:"params"` // Results count limit Limit *int `json:"limit,omitempty" name:"limit" location:"params"` - // Limit results to keys that start at this marker - Marker *string `json:"marker,omitempty" name:"marker" location:"params"` // Limits results to keys that begin with the prefix Prefix *string `json:"prefix,omitempty" name:"prefix" location:"params"` + // Limit results returned from the first uploading segment after upload_id_marker sorted by the time of upload_id + UploadIDMarker *string `json:"upload_id_marker,omitempty" name:"upload_id_marker" location:"params"` } // Validate validates the input for ListMultipartUploads. @@ -774,8 +776,10 @@ type ListMultipartUploadsOutput struct { Marker *string `json:"marker,omitempty" name:"marker" location:"elements"` // Bucket name Name *string `json:"name,omitempty" name:"name" location:"elements"` - // The last key in keys list - NextMarker *string `json:"next_marker,omitempty" name:"next_marker" location:"elements"` + // The last key in uploads list + NextKeyMarker *string `json:"next_key_marker,omitempty" name:"next_key_marker" location:"elements"` + // The last upload_id in uploads list + NextUploadIDMarker *string `json:"next_upload_id_marker,omitempty" name:"next_upload_id_marker" location:"elements"` // Prefix that specified in request parameters Prefix *string `json:"prefix,omitempty" name:"prefix" location:"elements"` // Multipart uploads diff --git a/vendor/github.com/yunify/qingstor-sdk-go/service/object.go b/vendor/github.com/yunify/qingstor-sdk-go/service/object.go index 2118548fc..4a081ece9 100644 --- a/vendor/github.com/yunify/qingstor-sdk-go/service/object.go +++ b/vendor/github.com/yunify/qingstor-sdk-go/service/object.go @@ -467,6 +467,105 @@ type HeadObjectOutput struct { XQSEncryptionCustomerAlgorithm *string `json:"X-QS-Encryption-Customer-Algorithm,omitempty" name:"X-QS-Encryption-Customer-Algorithm" location:"headers"` } +// ImageProcess does Image process with the action on the object +// Documentation URL: https://docs.qingcloud.com/qingstor/data_process/image_process/index.html +func (s *Bucket) ImageProcess(objectKey string, input *ImageProcessInput) (*ImageProcessOutput, error) { + r, x, err := s.ImageProcessRequest(objectKey, input) + + if err != nil { + return x, err + } + + err = r.Send() + if err != nil { + return nil, err + } + + requestID := r.HTTPResponse.Header.Get(http.CanonicalHeaderKey("X-QS-Request-ID")) + x.RequestID = &requestID + + return x, err +} + +// ImageProcessRequest creates request and output object of ImageProcess. +func (s *Bucket) ImageProcessRequest(objectKey string, input *ImageProcessInput) (*request.Request, *ImageProcessOutput, error) { + + if input == nil { + input = &ImageProcessInput{} + } + + properties := *s.Properties + + properties.ObjectKey = &objectKey + + o := &data.Operation{ + Config: s.Config, + Properties: &properties, + APIName: "Image Process", + RequestMethod: "GET", + RequestURI: "//?image", + StatusCodes: []int{ + 200, // OK + 304, // Not modified + }, + } + + x := &ImageProcessOutput{} + r, err := request.New(o, input, x) + if err != nil { + return nil, nil, err + } + + return r, x, nil +} + +// ImageProcessInput presents input for ImageProcess. +type ImageProcessInput struct { + // Image process action + Action *string `json:"action" name:"action" location:"params"` // Required + // Specified the Cache-Control response header + ResponseCacheControl *string `json:"response-cache-control,omitempty" name:"response-cache-control" location:"params"` + // Specified the Content-Disposition response header + ResponseContentDisposition *string `json:"response-content-disposition,omitempty" name:"response-content-disposition" location:"params"` + // Specified the Content-Encoding response header + ResponseContentEncoding *string `json:"response-content-encoding,omitempty" name:"response-content-encoding" location:"params"` + // Specified the Content-Language response header + ResponseContentLanguage *string `json:"response-content-language,omitempty" name:"response-content-language" location:"params"` + // Specified the Content-Type response header + ResponseContentType *string `json:"response-content-type,omitempty" name:"response-content-type" location:"params"` + // Specified the Expires response header + ResponseExpires *string `json:"response-expires,omitempty" name:"response-expires" location:"params"` + + // Check whether the object has been modified + IfModifiedSince *time.Time `json:"If-Modified-Since,omitempty" name:"If-Modified-Since" format:"RFC 822" location:"headers"` +} + +// Validate validates the input for ImageProcess. +func (v *ImageProcessInput) Validate() error { + + if v.Action == nil { + return errors.ParameterRequiredError{ + ParameterName: "Action", + ParentName: "ImageProcessInput", + } + } + + return nil +} + +// ImageProcessOutput presents output for ImageProcess. +type ImageProcessOutput struct { + StatusCode *int `location:"statusCode"` + + RequestID *string `location:"requestID"` + + // The response body + Body io.ReadCloser `location:"body"` + + // Object content length + ContentLength *int64 `json:"Content-Length,omitempty" name:"Content-Length" location:"headers"` +} + // InitiateMultipartUpload does Initial multipart upload on the object. // Documentation URL: https://docs.qingcloud.com/qingstor/api/object/initiate_multipart_upload.html func (s *Bucket) InitiateMultipartUpload(objectKey string, input *InitiateMultipartUploadInput) (*InitiateMultipartUploadOutput, error) { @@ -909,6 +1008,24 @@ type UploadMultipartInput struct { ContentLength *int64 `json:"Content-Length,omitempty" name:"Content-Length" location:"headers"` // Object multipart content MD5sum ContentMD5 *string `json:"Content-MD5,omitempty" name:"Content-MD5" location:"headers"` + // Specify range of the source object + XQSCopyRange *string `json:"X-QS-Copy-Range,omitempty" name:"X-QS-Copy-Range" location:"headers"` + // Copy source, format (//) + XQSCopySource *string `json:"X-QS-Copy-Source,omitempty" name:"X-QS-Copy-Source" location:"headers"` + // Encryption algorithm of the object + XQSCopySourceEncryptionCustomerAlgorithm *string `json:"X-QS-Copy-Source-Encryption-Customer-Algorithm,omitempty" name:"X-QS-Copy-Source-Encryption-Customer-Algorithm" location:"headers"` + // Encryption key of the object + XQSCopySourceEncryptionCustomerKey *string `json:"X-QS-Copy-Source-Encryption-Customer-Key,omitempty" name:"X-QS-Copy-Source-Encryption-Customer-Key" location:"headers"` + // MD5sum of encryption key + XQSCopySourceEncryptionCustomerKeyMD5 *string `json:"X-QS-Copy-Source-Encryption-Customer-Key-MD5,omitempty" name:"X-QS-Copy-Source-Encryption-Customer-Key-MD5" location:"headers"` + // Check whether the Etag of copy source matches the specified value + XQSCopySourceIfMatch *string `json:"X-QS-Copy-Source-If-Match,omitempty" name:"X-QS-Copy-Source-If-Match" location:"headers"` + // Check whether the copy source has been modified since the specified date + XQSCopySourceIfModifiedSince *time.Time `json:"X-QS-Copy-Source-If-Modified-Since,omitempty" name:"X-QS-Copy-Source-If-Modified-Since" format:"RFC 822" location:"headers"` + // Check whether the Etag of copy source does not matches the specified value + XQSCopySourceIfNoneMatch *string `json:"X-QS-Copy-Source-If-None-Match,omitempty" name:"X-QS-Copy-Source-If-None-Match" location:"headers"` + // Check whether the copy source has not been unmodified since the specified date + XQSCopySourceIfUnmodifiedSince *time.Time `json:"X-QS-Copy-Source-If-Unmodified-Since,omitempty" name:"X-QS-Copy-Source-If-Unmodified-Since" format:"RFC 822" location:"headers"` // Encryption algorithm of the object XQSEncryptionCustomerAlgorithm *string `json:"X-QS-Encryption-Customer-Algorithm,omitempty" name:"X-QS-Encryption-Customer-Algorithm" location:"headers"` // Encryption key of the object diff --git a/vendor/github.com/yunify/qingstor-sdk-go/test/image.go b/vendor/github.com/yunify/qingstor-sdk-go/test/image.go new file mode 100644 index 000000000..cc6866777 --- /dev/null +++ b/vendor/github.com/yunify/qingstor-sdk-go/test/image.go @@ -0,0 +1,68 @@ +package main + +import ( + "errors" + "os" + "path" + + "github.com/DATA-DOG/godog" + + qs "github.com/yunify/qingstor-sdk-go/service" +) + +// ImageFeatureContext provides feature context for image. +func ImageFeatureContext(s *godog.Suite) { + s.Step(`^image process with key "([^"]*)" and query "([^"]*)"$`, imageProcessWithKeyAndQuery) + s.Step(`^image process status code is (\d+)$`, imageProcessStatusCodeIs) + +} + +var imageName string + +func imageProcessWithKeyAndQuery(objectKey, query string) error { + if bucket == nil { + return errors.New("The bucket is not exist") + } + file, err := os.Open(path.Join("features", "fixtures", objectKey)) + if err != nil { + return err + } + defer file.Close() + + imageName = objectKey + + _, err = bucket.PutObject(imageName, &qs.PutObjectInput{Body: file}) + if err != nil { + return err + } + + output, err := bucket.ImageProcess(objectKey, &qs.ImageProcessInput{ + Action: &query}) + if err != nil { + return err + } + imageProcessOutput = output + return nil +} + +var imageProcessOutput *qs.ImageProcessOutput + +func imageProcessStatusCodeIs(statusCode int) error { + defer deleteImage(imageName) + return checkEqual(qs.IntValue(imageProcessOutput.StatusCode), statusCode) +} + +var oOutput *qs.DeleteObjectOutput + +func deleteImage(imageName string) error { + + if bucket == nil { + return errors.New("The bucket is not exist") + } + + oOutput, err = bucket.DeleteObject(imageName) + if err != nil { + return err + } + return checkEqual(qs.IntValue(oOutput.StatusCode), 204) +} diff --git a/vendor/github.com/yunify/qingstor-sdk-go/test/main.go b/vendor/github.com/yunify/qingstor-sdk-go/test/main.go index 62e4e42ac..af6fcf08a 100644 --- a/vendor/github.com/yunify/qingstor-sdk-go/test/main.go +++ b/vendor/github.com/yunify/qingstor-sdk-go/test/main.go @@ -41,6 +41,7 @@ func main() { BucketExternalMirrorFeatureContext(s) ObjectFeatureContext(s) ObjectMultipartFeatureContext(s) + ImageFeatureContext(s) } options := godog.Options{ Format: "pretty", diff --git a/vendor/github.com/yunify/qingstor-sdk-go/test/object.go b/vendor/github.com/yunify/qingstor-sdk-go/test/object.go index 7c308a4b9..3dd6b2246 100644 --- a/vendor/github.com/yunify/qingstor-sdk-go/test/object.go +++ b/vendor/github.com/yunify/qingstor-sdk-go/test/object.go @@ -396,6 +396,11 @@ func getObjectWithQuerySignature(objectKey string) error { errChan <- err return } + err = r.Build() + if err != nil { + errChan <- err + return + } err = r.SignQuery(10) if err != nil { errChan <- err