From d81589416bc600a8eafa8cbbfeb830ded3dc06c2 Mon Sep 17 00:00:00 2001 From: buhe Date: Mon, 11 Mar 2019 18:02:04 +0800 Subject: [PATCH 1/2] add Aliyun oss storage provider --- Gopkg.lock | 241 +- Gopkg.toml | 4 + pkg/apis/mysql/v1alpha1/types.go | 1 + pkg/backup/storage/oss/provider.go | 106 + pkg/backup/storage/storage.go | 11 +- .../aliyun/aliyun-oss-go-sdk/.travis.yml | 30 + .../aliyun/aliyun-oss-go-sdk/CHANGELOG.md | 119 + .../aliyun/aliyun-oss-go-sdk/README-CN.md | 168 + .../aliyun/aliyun-oss-go-sdk/README.md | 167 + .../aliyun/aliyun-oss-go-sdk/oss/auth.go | 130 + .../aliyun/aliyun-oss-go-sdk/oss/bucket.go | 973 +++++ .../aliyun-oss-go-sdk/oss/bucket_test.go | 2741 ++++++++++++ .../aliyun/aliyun-oss-go-sdk/oss/client.go | 805 ++++ .../aliyun-oss-go-sdk/oss/client_test.go | 1619 ++++++++ .../aliyun/aliyun-oss-go-sdk/oss/conf.go | 128 + .../aliyun/aliyun-oss-go-sdk/oss/conn.go | 730 ++++ .../aliyun/aliyun-oss-go-sdk/oss/conn_test.go | 184 + .../aliyun/aliyun-oss-go-sdk/oss/const.go | 146 + .../aliyun/aliyun-oss-go-sdk/oss/crc.go | 123 + .../aliyun/aliyun-oss-go-sdk/oss/crc_test.go | 476 +++ .../aliyun/aliyun-oss-go-sdk/oss/download.go | 568 +++ .../aliyun-oss-go-sdk/oss/download_test.go | 658 +++ .../aliyun/aliyun-oss-go-sdk/oss/error.go | 94 + .../aliyun-oss-go-sdk/oss/error_test.go | 111 + .../aliyun-oss-go-sdk/oss/limit_reader_1_6.go | 28 + .../aliyun-oss-go-sdk/oss/limit_reader_1_7.go | 91 + .../aliyun-oss-go-sdk/oss/livechannel.go | 257 ++ .../aliyun-oss-go-sdk/oss/livechannel_test.go | 428 ++ .../aliyun/aliyun-oss-go-sdk/oss/mime.go | 245 ++ .../aliyun/aliyun-oss-go-sdk/oss/model.go | 68 + .../aliyun/aliyun-oss-go-sdk/oss/multicopy.go | 468 +++ .../aliyun-oss-go-sdk/oss/multicopy_test.go | 481 +++ .../aliyun/aliyun-oss-go-sdk/oss/multipart.go | 290 ++ .../aliyun-oss-go-sdk/oss/multipart_test.go | 949 +++++ .../aliyun/aliyun-oss-go-sdk/oss/option.go | 433 ++ .../aliyun-oss-go-sdk/oss/option_test.go | 299 ++ .../aliyun/aliyun-oss-go-sdk/oss/progress.go | 112 + .../aliyun-oss-go-sdk/oss/progress_test.go | 460 +++ .../aliyun-oss-go-sdk/oss/transport_1_6.go | 26 + .../aliyun-oss-go-sdk/oss/transport_1_7.go | 28 + .../aliyun/aliyun-oss-go-sdk/oss/type.go | 566 +++ .../aliyun/aliyun-oss-go-sdk/oss/type_test.go | 127 + .../aliyun/aliyun-oss-go-sdk/oss/upload.go | 526 +++ .../aliyun-oss-go-sdk/oss/upload_test.go | 459 +++ .../aliyun/aliyun-oss-go-sdk/oss/utils.go | 265 ++ .../aliyun-oss-go-sdk/oss/utils_test.go | 220 + .../aliyun/aliyun-oss-go-sdk/sample.go | 36 + .../sample/BingWallpaper-2015-11-07.jpg | Bin 0 -> 482048 bytes .../sample/The Go Programming Language.html | 3663 +++++++++++++++++ .../aliyun-oss-go-sdk/sample/append_object.go | 156 + .../aliyun-oss-go-sdk/sample/archive.go | 74 + .../aliyun-oss-go-sdk/sample/bucket_acl.go | 43 + .../aliyun-oss-go-sdk/sample/bucket_cors.go | 71 + .../sample/bucket_lifecycle.go | 68 + .../sample/bucket_logging.go | 90 + .../sample/bucket_referer.go | 57 + .../aliyun-oss-go-sdk/sample/cname_sample.go | 96 + .../aliyun/aliyun-oss-go-sdk/sample/comm.go | 167 + .../aliyun/aliyun-oss-go-sdk/sample/config.go | 25 + .../aliyun-oss-go-sdk/sample/copy_object.go | 114 + .../aliyun-oss-go-sdk/sample/create_bucket.go | 50 + .../aliyun-oss-go-sdk/sample/delete_object.go | 108 + .../aliyun-oss-go-sdk/sample/get_object.go | 143 + .../aliyun-oss-go-sdk/sample/list_buckets.go | 129 + .../aliyun-oss-go-sdk/sample/list_objects.go | 148 + .../aliyun-oss-go-sdk/sample/livechannel.go | 445 ++ .../aliyun-oss-go-sdk/sample/new_bucket.go | 50 + .../aliyun-oss-go-sdk/sample/object_acl.go | 44 + .../aliyun-oss-go-sdk/sample/object_meta.go | 73 + .../aliyun-oss-go-sdk/sample/put_object.go | 132 + .../aliyun-oss-go-sdk/sample/sign_url.go | 74 + 71 files changed, 23188 insertions(+), 27 deletions(-) create mode 100644 pkg/backup/storage/oss/provider.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/.travis.yml create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/CHANGELOG.md create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/README-CN.md create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/README.md create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/auth.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conf.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/const.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_6.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_7.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/mime.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/model.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_6.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_7.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils_test.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/BingWallpaper-2015-11-07.jpg create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/The Go Programming Language.html create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/append_object.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/archive.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_acl.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_cors.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_lifecycle.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_logging.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_referer.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/cname_sample.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/comm.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/config.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/copy_object.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/create_bucket.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/delete_object.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/get_object.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_buckets.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_objects.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/livechannel.go create mode 100644 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/new_bucket.go create mode 100755 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_acl.go create mode 100755 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_meta.go create mode 100755 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/put_object.go create mode 100755 vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sign_url.go diff --git a/Gopkg.lock b/Gopkg.lock index 678b5f6a1..f93e9bc37 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -2,17 +2,34 @@ [[projects]] + digest = "1:7020b5857474f4cd4ac3326eb0fc8bcf2a639ed0a732b42dce9083afea1e2e54" name = "github.com/PuerkitoBio/purell" packages = ["."] + pruneopts = "" revision = "8a290539e2e8629dbc4e6bad948158f790ec31f4" version = "v1.0.0" [[projects]] + digest = "1:eefdb56f035de98e76cc8b7d154faf3ac0aa1db212f789cf17454775249c8254" name = "github.com/PuerkitoBio/urlesc" packages = ["."] + pruneopts = "" revision = "5bd2802263f21d8788851d5305584c82a5c75d7e" [[projects]] + digest = "1:b72e7a1b24bc87f1b22e9f208d43b2160afea6c2902b813b2817730d58cf29bd" + name = "github.com/aliyun/aliyun-oss-go-sdk" + packages = [ + ".", + "oss", + "sample", + ] + pruneopts = "" + revision = "86c17b95fcd5db33628a61e492fb4a1a937d5906" + version = "1.9.5" + +[[projects]] + digest = "1:2de4710756a7ba67fd78a654c8b615cc9f6a655be0d2ce75975f4d896d5ba8bb" name = "github.com/aws/aws-sdk-go" packages = [ "aws", @@ -41,162 +58,212 @@ "service/s3", "service/s3/s3iface", "service/s3/s3manager", - "service/sts" + "service/sts", ] + pruneopts = "" revision = "a9c63ac6fd79402f91191c2e0a1348d0b8968e6c" version = "v1.12.41" [[projects]] branch = "master" + digest = "1:0c5485088ce274fac2e931c1b979f2619345097b39d91af3239977114adf0320" name = "github.com/beorn7/perks" packages = ["quantile"] + pruneopts = "" revision = "4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9" [[projects]] + digest = "1:8495649476b2da658de5f993fa2758f342114ce057b6c559d26cf380447eaed8" name = "github.com/coreos/go-semver" packages = ["semver"] + pruneopts = "" revision = "e214231b295a8ea9479f11b70b35d5acf3556d9b" [[projects]] + digest = "1:9a62b6bb832d61f198b4301a6ecb4fbef21a731596bb21c27e27f79b1e8260d7" name = "github.com/davecgh/go-spew" packages = ["spew"] + pruneopts = "" revision = "782f4967f2dc4564575ca782fe2d04090b5faca8" [[projects]] + digest = "1:29d00f4e339b0ae6e05bf1e3a43c0064fc7374e77417d1866cc362252bd2f6d2" name = "github.com/emicklei/go-restful" packages = [ ".", - "log" + "log", ] + pruneopts = "" revision = "ff4f55a206334ef123e4f79bbf348980da81ca46" [[projects]] + digest = "1:a31fbb19d2b38d50bc125d97b7c3e7a286d3f6f37d18756011eb6e7d1a9fa7d0" name = "github.com/ghodss/yaml" packages = ["."] + pruneopts = "" revision = "73d445a93680fa1a78ae23a5839bad48f32ba1ee" [[projects]] + digest = "1:a00483fe4106b86fb1187a92b5cf6915c85f294ed4c129ccbe7cb1f1a06abd46" name = "github.com/go-ini/ini" packages = ["."] + pruneopts = "" revision = "32e4c1e6bc4e7d0d8451aa6b75200d19e37a536a" version = "v1.32.0" [[projects]] + digest = "1:d5684de69d41b91bbc4582447389b6e3d422a856c7863e2cb69e83fa22585686" name = "github.com/go-openapi/jsonpointer" packages = ["."] + pruneopts = "" revision = "46af16f9f7b149af66e5d1bd010e3574dc06de98" [[projects]] + digest = "1:880132ce271710075e3dd41e5b267fb444feb4d3f6a2a6031227af8cadfa4ad0" name = "github.com/go-openapi/jsonreference" packages = ["."] + pruneopts = "" revision = "13c6e3589ad90f49bd3e3bbe2c2cb3d7a4142272" [[projects]] + digest = "1:2d7624c60176d2eefb4d5029163002282de792172273cd965d8d3ce2dfcd1ccf" name = "github.com/go-openapi/spec" packages = ["."] + pruneopts = "" revision = "6aced65f8501fe1217321abf0749d354824ba2ff" [[projects]] + digest = "1:60a4d50770b16e62199e9acad6bc92b6835d93538545933d48fc7adf0f4d8edb" name = "github.com/go-openapi/swag" packages = ["."] + pruneopts = "" revision = "1d0bd113de87027671077d3c71eb3ac5d7dbba72" [[projects]] + digest = "1:91ea659283519bfbcc8d428ecbae5475394a48f8209f5c5cd20fee169b37a63a" name = "github.com/gogo/protobuf" packages = [ "proto", - "sortkeys" + "sortkeys", ] + pruneopts = "" revision = "c0656edd0d9eab7c66d1eb0c568f9039345796f7" [[projects]] + digest = "1:107b233e45174dbab5b1324201d092ea9448e58243ab9f039e4c0f332e121e3a" name = "github.com/golang/glog" packages = ["."] + pruneopts = "" revision = "23def4e6c14b4da8ac2ed8007337bc5eb5007998" [[projects]] branch = "master" + digest = "1:736952736991249599ad037f945ba3d45f6bc5d72e73d84203560745624552d1" name = "github.com/golang/groupcache" packages = ["lru"] + pruneopts = "" revision = "84a468cf14b4376def5d68c722b139b881c450a4" [[projects]] + digest = "1:5126907b5a6192e00ab27ceae807c53d7ff61343f15308dbe09621d12231da02" name = "github.com/golang/protobuf" packages = [ "proto", "ptypes", "ptypes/any", "ptypes/duration", - "ptypes/timestamp" + "ptypes/timestamp", ] + pruneopts = "" revision = "4bd1920723d7b7c925de087aa32e2187708897f7" [[projects]] + digest = "1:a2823c34933d4a2b36284f617f483d51fe156a443923284b3660f183dcfa3338" name = "github.com/google/gofuzz" packages = ["."] + pruneopts = "" revision = "44d81051d367757e1c7c6a5a86423ece9afcf63c" [[projects]] + digest = "1:02350ac468c148841775f48dbfc8155bf02bfecefac1dc6fe676ac2a314f5451" name = "github.com/googleapis/gnostic" packages = [ "OpenAPIv2", "compiler", - "extensions" + "extensions", ] + pruneopts = "" revision = "68f4ded48ba9414dab2ae69b3f0d69971da73aa5" [[projects]] + digest = "1:9830c3ef8075a224fca4ed2d3761b58b3759310343d028223779dea21e139893" name = "github.com/hashicorp/golang-lru" packages = [ ".", - "simplelru" + "simplelru", ] + pruneopts = "" revision = "a0d98a5f288019575c6d1f4bb1573fef2d1fcdc4" [[projects]] branch = "master" + digest = "1:40944f9b88767f2abbe8625b5b3407cbfef9110ed29d01e8d2150e895d30f50e" name = "github.com/heptiolabs/healthcheck" packages = ["."] + pruneopts = "" revision = "9926c14869d196ae77eec009e7f016f532a5f4b9" [[projects]] branch = "master" + digest = "1:f81c8d7354cc0c6340f2f7a48724ee6c2b3db3e918ecd441c985b4d2d97dd3e7" name = "github.com/howeyc/gopass" packages = ["."] + pruneopts = "" revision = "bf9dde6d0d2c004a008c27aaee91170c786f6db8" [[projects]] + digest = "1:af7e132906cb360f4d7c34a9e1434825467f21c4ff5c521ad4cc5b55352876a8" name = "github.com/imdario/mergo" packages = ["."] + pruneopts = "" revision = "6633656539c1639d9d78127b7d47c622b5d7b6dc" [[projects]] + digest = "1:6f49eae0c1e5dab1dafafee34b207aeb7a42303105960944828c2079b92fc88e" name = "github.com/jmespath/go-jmespath" packages = ["."] + pruneopts = "" revision = "0b12d6b5" [[projects]] + digest = "1:674f4d8ef8622e4f61991a6ebddec7722e5c026c286606a7f05f21d86b4d91fa" name = "github.com/json-iterator/go" packages = ["."] + pruneopts = "" revision = "6ed27152e0428abfde127acb33b08b03a1e67cac" version = "1.0.2" [[projects]] + digest = "1:5f3c38b5f2dd7bec9286e00c3ac7babbe68859cc0edc4d3e3ba3fda93c24d7e7" name = "github.com/mailru/easyjson" packages = [ "buffer", "jlexer", - "jwriter" + "jwriter", ] + pruneopts = "" revision = "d5b7844b561a7bc640052f1b935f7b800330d7e0" [[projects]] + digest = "1:4c23ced97a470b17d9ffd788310502a077b9c1f60221a85563e49696276b4147" name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] + pruneopts = "" revision = "3247c84500bff8d9fb6d579d800f20b3e091582c" version = "v1.0.0" [[projects]] + digest = "1:32b27072cd55bd2fb7244de0425943d125da6a552ae2b6517cdd965a662baf18" name = "github.com/onsi/ginkgo" packages = [ ".", @@ -216,12 +283,14 @@ "reporters/stenographer", "reporters/stenographer/support/go-colorable", "reporters/stenographer/support/go-isatty", - "types" + "types", ] + pruneopts = "" revision = "9eda700730cba42af70d53180f9dcce9266bc2bc" version = "v1.4.0" [[projects]] + digest = "1:44febbaa3af725dccbe3d83b21ef155f7136670a419bb24a91383bb27d6dcbd4" name = "github.com/onsi/gomega" packages = [ ".", @@ -235,86 +304,110 @@ "matchers/support/goraph/edge", "matchers/support/goraph/node", "matchers/support/goraph/util", - "types" + "types", ] + pruneopts = "" revision = "003f63b7f4cff3fc95357005358af2de0f5fe152" version = "v1.3.0" [[projects]] + digest = "1:63e142fc50307bcb3c57494913cfc9c12f6061160bdf97a678f78c71615f939b" name = "github.com/pborman/uuid" packages = ["."] + pruneopts = "" revision = "e790cca94e6cc75c7064b1332e63811d4aae1a53" version = "v1.1" [[projects]] + digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] + digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" name = "github.com/pmezard/go-difflib" packages = ["difflib"] + pruneopts = "" revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] + digest = "1:4142d94383572e74b42352273652c62afec5b23f325222ed09198f46009022d1" name = "github.com/prometheus/client_golang" packages = ["prometheus"] + pruneopts = "" revision = "c5b7fccd204277076155f10851dad72b76a49317" version = "v0.8.0" [[projects]] branch = "master" + digest = "1:60aca47f4eeeb972f1b9da7e7db51dee15ff6c59f7b401c1588b8e6771ba15ef" name = "github.com/prometheus/client_model" packages = ["go"] + pruneopts = "" revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" [[projects]] branch = "master" + digest = "1:e3aa5178be4fc4ae8cdb37d11c02f7490c00450a9f419e6aa84d02d3b47e90d2" name = "github.com/prometheus/common" packages = [ "expfmt", "internal/bitbucket.org/ww/goautoneg", - "model" + "model", ] + pruneopts = "" revision = "2e54d0b93cba2fd133edc32211dcc32c06ef72ca" [[projects]] branch = "master" + digest = "1:a6a85fc81f2a06ccac3d45005523afbeee45138d781d4f3cb7ad9889d5c65aab" name = "github.com/prometheus/procfs" packages = [ ".", - "xfs" + "xfs", ] + pruneopts = "" revision = "a6e9df898b1336106c743392c48ee0b71f5c4efa" [[projects]] + digest = "1:4fc12a4611ba6e3db7dd567580579cb23fbe59ac77e7233dc8defa7cb0c62001" name = "github.com/robfig/cron" packages = ["."] + pruneopts = "" revision = "df38d32658d8788cd446ba74db4bb5375c4b0cb3" [[projects]] + digest = "1:261bc565833ef4f02121450d74eb88d5ae4bd74bfe5d0e862cddb8550ec35000" name = "github.com/spf13/pflag" packages = ["."] + pruneopts = "" revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" version = "v1.0.0" [[projects]] + digest = "1:3926a4ec9a4ff1a072458451aa2d9b98acd059a45b38f7335d31e06c3d6a0159" name = "github.com/stretchr/testify" packages = [ "assert", - "require" + "require", ] + pruneopts = "" revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0" version = "v1.1.4" [[projects]] + digest = "1:0f67b4bbcdf1caaee0450f225a53fd2c2f8793578cc7810eb09c290e008e33ac" name = "golang.org/x/crypto" packages = ["ssh/terminal"] + pruneopts = "" revision = "d172538b2cfce0c13cee31e647d0367aa8cd2486" [[projects]] + digest = "1:605a34a4f37423103eecd6932f2620723d2988e5d0f2b0a568ff6a5d5524610d" name = "golang.org/x/net" packages = [ "context", @@ -324,16 +417,20 @@ "http2", "http2/hpack", "idna", - "lex/httplex" + "lex/httplex", ] + pruneopts = "" revision = "f2499483f923065a842d38eb4c7f1927e6fc6e6d" [[projects]] + digest = "1:b6e4bf8197b5de25f2ed05c603d39d619f4c41bb4a573ef5274c3446df44d6e4" name = "golang.org/x/sys" packages = ["unix"] + pruneopts = "" revision = "8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9" [[projects]] + digest = "1:9fa38bfa647dffc304504ea076b54f7eeb28f7ff7476536695abffcc1addd6d1" name = "golang.org/x/text" packages = [ "cases", @@ -361,37 +458,47 @@ "unicode/cldr", "unicode/norm", "unicode/rangetable", - "width" + "width", ] + pruneopts = "" revision = "2910a502d2bf9e43193af9d68ca516529614eed3" [[projects]] branch = "master" + digest = "1:55a681cb66f28755765fa5fa5104cbd8dc85c55c02d206f9f89566451e3fe1aa" name = "golang.org/x/time" packages = ["rate"] + pruneopts = "" revision = "fbb02b2291d28baffd63558aa44b4b56f178d650" [[projects]] branch = "master" + digest = "1:805a5b21bf9a3d1053330d504476ad194d09ba31d508a05736d26d33ee98ae21" name = "golang.org/x/tools" packages = [ "go/ast/astutil", - "imports" + "imports", ] + pruneopts = "" revision = "b790d0ba0332a621d0b58cfd69fa13bd3dc358d2" [[projects]] + digest = "1:e5d1fb981765b6f7513f793a3fcaac7158408cca77f75f7311ac82cc88e9c445" name = "gopkg.in/inf.v0" packages = ["."] + pruneopts = "" revision = "3887ee99ecf07df5b447e9b00d9c0b2adaa9f3e4" version = "v0.9.0" [[projects]] + digest = "1:5fe876313b07628905b2181e537faabe45032cb9c79c01b49b51c25a0a40040d" name = "gopkg.in/yaml.v2" packages = ["."] + pruneopts = "" revision = "7f97868eec74b32b0982dd158a51a446d1da7eb5" [[projects]] + digest = "1:4d235221f43d5243b4929e098993fba365cd9a6448c613f4f8e59ea869ac094f" name = "k8s.io/api" packages = [ "admissionregistration/v1alpha1", @@ -421,13 +528,15 @@ "settings/v1alpha1", "storage/v1", "storage/v1alpha1", - "storage/v1beta1" + "storage/v1beta1", ] + pruneopts = "" revision = "73d903622b7391f3312dcbac6483fed484e185f8" version = "kubernetes-1.10.0" [[projects]] branch = "release-1.9" + digest = "1:fceda32839134f5fce1ea6c867ea1e6cf4980234c730007a76720d622ab7c0bb" name = "k8s.io/apimachinery" packages = [ "pkg/api/errors", @@ -471,20 +580,24 @@ "pkg/version", "pkg/watch", "third_party/forked/golang/json", - "third_party/forked/golang/reflect" + "third_party/forked/golang/reflect", ] + pruneopts = "" revision = "68f9c3a1feb3140df59c67ced62d3a5df8e6c9c2" [[projects]] + digest = "1:b5209cb45d6de1c5bb26a8ae9ea1bc029393d5cff47ed3cff510f498135fe8f8" name = "k8s.io/apiserver" packages = [ "pkg/util/flag", - "pkg/util/logs" + "pkg/util/logs", ] + pruneopts = "" revision = "5ae41ac86efd753e2ba012f097b83a914b268236" version = "kubernetes-1.10.0" [[projects]] + digest = "1:071cc2f032b701b9dba26568e040940f26931a49e3a3985f3375f17f7f6d9c5f" name = "k8s.io/client-go" packages = [ "discovery", @@ -636,12 +749,14 @@ "util/homedir", "util/integer", "util/retry", - "util/workqueue" + "util/workqueue", ] + pruneopts = "" revision = "23781f4d6632d88e869066eaebb743857aa1ef9b" - version = "v7.0.0" + version = "kubernetes-1.10.0" [[projects]] + digest = "1:a85379f6270659224a302cadbbcc769f140eb1949bdc4ed4b978e7406dda3828" name = "k8s.io/code-generator" packages = [ "cmd/client-gen", @@ -665,12 +780,14 @@ "cmd/lister-gen", "cmd/lister-gen/args", "cmd/lister-gen/generators", - "pkg/util" + "pkg/util", ] + pruneopts = "" revision = "7ead8f38b01cf8653249f5af80ce7b2c8aba12e2" version = "kubernetes-1.10.0" [[projects]] + digest = "1:18db72d2c7f149e514d4a6b4334002651635c2bbf8ef053b592d272da89e8abf" name = "k8s.io/gengo" packages = [ "args", @@ -680,31 +797,105 @@ "generator", "namer", "parser", - "types" + "types", ] + pruneopts = "" revision = "b6c426f7730e6d66e6e476a85d1c3eb7633880e0" [[projects]] branch = "master" + digest = "1:3a5678d51679e111a07a7a815bab811b6388fb9977dcdcfdb0d29bff008882a9" name = "k8s.io/kube-openapi" packages = [ "pkg/common", - "pkg/util/proto" + "pkg/util/proto", ] + pruneopts = "" revision = "abfc5fbe1cf87ee697db107fdfd24c32fe4397a8" [[projects]] branch = "master" + digest = "1:862c4a97757db0149721f648cd485749b1d104f98a0bb4cde8a953cf1e69258f" name = "k8s.io/utils" packages = [ "exec", - "exec/testing" + "exec/testing", ] + pruneopts = "" revision = "0ab3217ce272697eb60feb79a613a86a2fab7918" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "1146abc7b406d9f9fbecd9615c90853ddb12fcbd5324facff84a6b92229d4f58" + input-imports = [ + "github.com/aliyun/aliyun-oss-go-sdk", + "github.com/aliyun/aliyun-oss-go-sdk/oss", + "github.com/aws/aws-sdk-go/aws", + "github.com/aws/aws-sdk-go/aws/credentials", + "github.com/aws/aws-sdk-go/aws/session", + "github.com/aws/aws-sdk-go/service/s3", + "github.com/aws/aws-sdk-go/service/s3/s3manager", + "github.com/coreos/go-semver/semver", + "github.com/ghodss/yaml", + "github.com/golang/glog", + "github.com/heptiolabs/healthcheck", + "github.com/onsi/ginkgo", + "github.com/onsi/gomega", + "github.com/pkg/errors", + "github.com/prometheus/client_golang/prometheus", + "github.com/robfig/cron", + "github.com/spf13/pflag", + "github.com/stretchr/testify/assert", + "github.com/stretchr/testify/require", + "gopkg.in/yaml.v2", + "k8s.io/api/apps/v1beta1", + "k8s.io/api/core/v1", + "k8s.io/apimachinery/pkg/api/errors", + "k8s.io/apimachinery/pkg/api/resource", + "k8s.io/apimachinery/pkg/apis/meta/v1", + "k8s.io/apimachinery/pkg/labels", + "k8s.io/apimachinery/pkg/runtime", + "k8s.io/apimachinery/pkg/runtime/schema", + "k8s.io/apimachinery/pkg/runtime/serializer", + "k8s.io/apimachinery/pkg/selection", + "k8s.io/apimachinery/pkg/types", + "k8s.io/apimachinery/pkg/util/clock", + "k8s.io/apimachinery/pkg/util/intstr", + "k8s.io/apimachinery/pkg/util/runtime", + "k8s.io/apimachinery/pkg/util/strategicpatch", + "k8s.io/apimachinery/pkg/util/uuid", + "k8s.io/apimachinery/pkg/util/validation/field", + "k8s.io/apimachinery/pkg/util/wait", + "k8s.io/apimachinery/pkg/watch", + "k8s.io/apiserver/pkg/util/flag", + "k8s.io/apiserver/pkg/util/logs", + "k8s.io/client-go/discovery", + "k8s.io/client-go/discovery/fake", + "k8s.io/client-go/informers", + "k8s.io/client-go/informers/apps/v1beta1", + "k8s.io/client-go/informers/core/v1", + "k8s.io/client-go/kubernetes", + "k8s.io/client-go/kubernetes/fake", + "k8s.io/client-go/kubernetes/scheme", + "k8s.io/client-go/kubernetes/typed/core/v1", + "k8s.io/client-go/listers/apps/v1beta1", + "k8s.io/client-go/listers/core/v1", + "k8s.io/client-go/rest", + "k8s.io/client-go/testing", + "k8s.io/client-go/tools/cache", + "k8s.io/client-go/tools/clientcmd", + "k8s.io/client-go/tools/record", + "k8s.io/client-go/util/flowcontrol", + "k8s.io/client-go/util/retry", + "k8s.io/client-go/util/workqueue", + "k8s.io/code-generator/cmd/client-gen", + "k8s.io/code-generator/cmd/conversion-gen", + "k8s.io/code-generator/cmd/deepcopy-gen", + "k8s.io/code-generator/cmd/defaulter-gen", + "k8s.io/code-generator/cmd/informer-gen", + "k8s.io/code-generator/cmd/lister-gen", + "k8s.io/utils/exec", + "k8s.io/utils/exec/testing", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 57617fd22..b9b494fa7 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -91,3 +91,7 @@ required = [ [[constraint]] name = "github.com/aws/aws-sdk-go" version = "1.12.41" + +[[constraint]] + name = "github.com/aliyun/aliyun-oss-go-sdk" + version = "1.9.5" diff --git a/pkg/apis/mysql/v1alpha1/types.go b/pkg/apis/mysql/v1alpha1/types.go index dcc8735cf..4d10cac7f 100644 --- a/pkg/apis/mysql/v1alpha1/types.go +++ b/pkg/apis/mysql/v1alpha1/types.go @@ -178,6 +178,7 @@ type S3StorageProvider struct { // service. type StorageProvider struct { S3 *S3StorageProvider `json:"s3"` + ProviderType string `json:"providerType"` } // BackupSpec defines the specification for a MySQL backup. This includes what should be backed up, diff --git a/pkg/backup/storage/oss/provider.go b/pkg/backup/storage/oss/provider.go new file mode 100644 index 000000000..a08c1071f --- /dev/null +++ b/pkg/backup/storage/oss/provider.go @@ -0,0 +1,106 @@ +// Copyright 2018 Oracle and/or its affiliates. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package oss + +import ( + "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/golang/glog" + "github.com/pkg/errors" + "io" + + "k8s.io/apimachinery/pkg/util/validation/field" + + "github.com/oracle/mysql-operator/pkg/apis/mysql/v1alpha1" +) + +// Provider is storage implementation of provider.Interface. +type Provider struct { + v1alpha1.S3StorageProvider + + oss *oss.Client +} + +// NewProvider creates a new S3 (compatible) storage provider. +func NewProvider(provider *v1alpha1.S3StorageProvider, credentials map[string]string) (*Provider, error) { + accessKey, secretKey, err := getCredentials(credentials) + if err != nil { + return nil, errors.WithStack(err) + } + + client, err := oss.New(provider.Endpoint, accessKey, secretKey) + + if err != nil { + return nil, errors.WithStack(err) + } + + return &Provider{ + S3StorageProvider: *provider, + oss: client, + }, nil +} + +// Store the given data at the given key. +func (p *Provider) Store(key string, body io.ReadCloser) error { + glog.V(2).Infof("Storing backup (provider=\"S3\", endpoint=%q, bucket=%q, key=%q)", p.Endpoint, p.Bucket, key) + + defer body.Close() + + bucket, err := p.oss.Bucket(p.Bucket) + if err != nil { + return errors.Wrapf(err, "error storing backup (provider=\"S3\", endpoint=%q, bucket=%q, key=%q)", p.Endpoint, p.Bucket, key) + } + err = bucket.PutObject(key, body); + return errors.Wrapf(err, "error storing backup (provider=\"S3\", endpoint=%q, bucket=%q, key=%q)", p.Endpoint, p.Bucket, key) +} + +// Retrieve the given key from S3 storage service. +func (p *Provider) Retrieve(key string) (io.ReadCloser, error) { + glog.V(2).Infof("Retrieving backup (provider=\"s3\", endpoint=%q, bucket=%q, key=%q)", p.Endpoint, p.Bucket, key) + bucket, err := p.oss.Bucket(p.Bucket) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving backup (provider='S3', endpoint='%s', bucket='%s', key='%s')", p.Endpoint, p.Bucket, key) + } + obj, err := bucket.GetObject(key) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving backup (provider='S3', endpoint='%s', bucket='%s', key='%s')", p.Endpoint, p.Bucket, key) + } + + return obj, nil +} + +// getCredentials gets an accesskey and secretKey from the provided map. +func getCredentials(credentials map[string]string) (string, string, error) { + allErrs := field.ErrorList{} + fldPath := field.NewPath("data") + + if credentials == nil { + return "", "", errors.New("no credentials provided") + } + + accessKey, ok := credentials["accessKey"] + if !ok { + allErrs = append(allErrs, field.Required(fldPath.Child("accessKey"), "")) + } + secretKey, ok := credentials["secretKey"] + if !ok { + allErrs = append(allErrs, field.Required(fldPath.Child("secretKey"), "")) + } + + if len(allErrs) > 0 { + return "", "", allErrs.ToAggregate() + } + + return accessKey, secretKey, nil +} diff --git a/pkg/backup/storage/storage.go b/pkg/backup/storage/storage.go index 951f8fcc3..8549e7fd3 100644 --- a/pkg/backup/storage/storage.go +++ b/pkg/backup/storage/storage.go @@ -15,10 +15,11 @@ package storage import ( + "github.com/oracle/mysql-operator/pkg/backup/storage/oss" + "github.com/oracle/mysql-operator/pkg/backup/storage/s3" "io" "github.com/oracle/mysql-operator/pkg/apis/mysql/v1alpha1" - "github.com/oracle/mysql-operator/pkg/backup/storage/s3" ) // Interface abstracts the underlying storage provider. @@ -33,5 +34,11 @@ type Interface interface { // NewStorageProvider accepts a secret map and uses its contents to determine the // desired object storage provider implementation. func NewStorageProvider(config v1alpha1.StorageProvider, credentials map[string]string) (Interface, error) { - return s3.NewProvider(config.S3, credentials) + if config.ProviderType == "s3" { + return s3.NewProvider(config.S3, credentials) + } else if config.ProviderType == "oss" { + return oss.NewProvider(config.S3, credentials) + } else { + return nil, nil + } } diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/.travis.yml b/vendor/github.com/aliyun/aliyun-oss-go-sdk/.travis.yml new file mode 100644 index 000000000..7cf6c4309 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/.travis.yml @@ -0,0 +1,30 @@ +language: go +go: +- 1.5 +- 1.6 +- 1.7 +- 1.8 +install: +- go get golang.org/x/tools/cmd/cover +- go get github.com/mattn/goveralls +- go get gopkg.in/check.v1 +- go get github.com/satori/go.uuid +- go get github.com/baiyubin/aliyun-sts-go-sdk/sts + +- if [[ $TRAVIS_GO_VERSION = '1.7' || $TRAVIS_GO_VERSION > '1.7' ]]; then go get golang.org/x/time/rate ; fi + +script: +- cd oss +- travis_wait 30 go test -v -covermode=count -coverprofile=coverage.out -timeout=30m +- "$HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci" +env: + global: + - secure: ZCL5egxJmZA+o1ujsaJe//AIh1ag/exSUJ2FzoKPF3O9c84hMc2k5EYO2rGzTNn1ML6M89Mo5hAvHQhyJEHjRuMtjc1QrfxAaA3mqm4scGXIXesPMqYGuvuPSh++6/fkAwaVBAhrk5GaDG1/FuxHE5zusGx3SvGegnCwO7n/2YCfXco6DWgVCdrz4p1EpPkAM3JIdHFUzsDWiimVuiNAvJmAT8+IeOPTT+WgusCJj4ORS3X3LddTjttBP+hRrp/pGSoNqPMzfysWybtaL2SJ8URtvsxW0Mo5BwocHAxAhPP+M2OscQbDzthSAezCLngYvrfBplfIyWlahlgzNz/FjXz5pQwWdYVNoibyxLLMOH685n75LNONN/xVO/GFmVPx7DMGapkN5NzIWS62D4v8QrRkwtms42OUkyEUHjDh8Evui3K2MNJVXA3TI9zOAR+C0krD7OEyS37qrppodhRxJSqFUlgXnk//wLldMC7vleDd7L2UQSWjqyBHqFOgsVaiLU2KRTY3zvv7ke+dqb5VF31mH6qAr8lJTR9un8M1att0VwCEKxoIRT4cKJCpEtZd8ovXOVt1uE695ThVXE9I5e00GXdTzqXOuv6zT4hv/dgmbz9JN9MYeCwmokEoIUmJKNYERa/bNVVefdnJt7h+dm+KpyPAS+XvPLzjbnWdYNA= + - secure: GdrPX7nUoZhB1DYTVD6x/vgA7H9dOlQc4n7nCsqEDyif+Y1XdPT83Ic3gSOt+cfy0/Kjh0/TT5xmLqpSh7wr7eyTpBPZGjz4ZbxBOcSLTfrf/spacgzla9I1335CvaTmpvrnvGUlOuVS8rb3J/+19dHlN6dfxX+ucjdfShR504d2JEcCLpTc1CEXAl+HEt3hM9gztOX5ykxyrtibDr0OPkNF7QjZ485V6UJkfyVlBM6JL59ywgh2dhdZn6JwmexHjVPsw6V8Ka07GzbpOs1e5eis42RUJe8eSqRRToCcTUbA9HOgWXswuu5k7nAwErygX2ub3hZ+yIjc+9JLsiy6F69RaUPVFlxfw8s5NLeInTIt28+A6iaf3X2k4lOaVFytgTl7lkYGNWz4eV/vXf2H4wZmaZn5OI0WKd3WuEJ04rsm7xzx9rC8znnsI3R1BHfapU/y6z2QGjgJsHqZmgfvXNKgSOurM6O/nlDnEsYOwYLQLhpeXVFNmbo+M77HAVicKD4yL08+5uBZaeYYipzyC1O4DEHX5BNdl34NpNxUdMqUb4MfNEnYeqvmemvZkOO6BO+xucP1S2LSKNXfFxH5iVfKbz4+VJ/2kt5R77672lkG3bXPUJFk4t1CTHBOLizEJNTTD8uzRIsW7Io+XFk6oyoEqXF0sT6Lbp/4pUHJQQM= + - secure: DGIgOKinCvYcLdcaIOKcecidQe5q/K4aGAjTyl8/fCp3mRWwFTrlv5gPMy9sHQEsiRjzQehpubMO1d0XFVO+0LvqdGLnXyM6lSnMhN6voQMnF1GaIXmxxBfvP3BHwyN8kMyY+4oqgMROvpxDKvq0IH/GE8opWRJhQdNkRscbtfHUvnqSk62oNziqIBxXrYBIuezuNcgZkCwEoZQwPu674efIAwVr01BmRfd/8Q5W27dJbCJFF9ceyOQrsjG2QGmW6GWraaOpqYfQ82A5ROB2saVU8mEs/f+mGoJgOP2aH3ErFBkJWBCUNzajluAyGyU9VgHGRQ+GMbSr/HqRILd5aYpDCGA0oFer4/jP32CqeEt1Jdqs2lWar5mFX1sae6PIxFyl5lnENgjTfbOt4oiGGye6t0mgI4+psRdCCQV5FQ7WfobEZ2ryfQxbiVU1X219jMoHhHmFzC83e/T6V/mkHrh8OR67k9pieH+DqNGvWFtv7BBs/ihfoo2ONNgsHcofsPj85I6odWAhsBnsYm6FsR31N19nObnggeDyqiCyh5qUFvSGPkH4fXTKthKETIRdsdOEDOcbCD6kUpZqIWyuk6TKeOD8PxjgKzm3hZjlugU1x3amVv71EKjA5/FOVyIVuekLKoLn7pt+n3PVlT30IZfWorEfqjeVAKp2SglE8nA= + - secure: LnL0Hl9yZEie1aYngEO5QK332cn3W2i4f6R5+kxX3tncdqBDFhsp5tQfMvlKHIzFlK94DI/G0diAl8zJmYQfAYARe4uvW5FAINRCkOUz2jwIA/gtQDr+oONqHK0OLPWYuZ6KJM4t7dmuPUR2/frKe0/6r5XeFkeAz9l8r1Gw9o42jFDQPDkhBj7k5EkmB1DpuAY74vXy0tVBCJsd57/kuaRqbX4euhx3zFrDcr+xQEiDWHKzNHlJd6DZnruy0KDuWmIbUWhR2rd8YKAnzP7OHzpbTHsnbXvrVpaN1Mv8lXz2dpPTr7I2LMCrMEtfECu9U+LDIqhbHVMsp9rZ+fNQ048fREoj8HZrorIxmsRJzV0ZQzjdW9Q6EVaiYcLZPFOASsyuTNBbSJ2AIrE/izo4EUKme8BY+0mFzTJwMk7XwAtatItVhEUXb2wXWZm8GR8wIrNmbeSzle5NkwXpdpW3QzZ2EADL63/pP80aV1aMBmoAuLMIHxeHEnXOTAgQj6SOiloY+II/iJE4cE5vo9UNtZsqnJZqdd22s3kLdQV0kbFMWq8S3qmxtDFPeoZAy03xhTVnJUBkdjSL2UER5WAacZIr85M6Z2APc6dzMUlWEE+4QkkM1UAbwDBTXFrrmfDVYc0LrePRuoHQiOmSvTus5+WV9iIQF7rM4BcSLnOEW5U= + - secure: SgvbTYTbMEkmqDXP8MW6lbERkHUjBRwg477hUL11Ok1TiRdHCbEDrq3mfUP+Tl2sS1x5qQ2JFg2NyWS2ikCAd0zjO3QEfmhfQFRpmfgb5O67wY2oEAsbRDanjIXTwpDAZn87KFIPB6ohVsX4LEztfR8zqXKIfXrVFs6lyDHS1LIgbgQhJl+XfJRsfPlWRq7QydANTCY34raUXgXgBxtbv43b232LT8UusizUvZS4HJbrbo4oXhVfhkUH46B2o0ct2Xt6EIlxyOtxtZOmnai8O7kIFHoG+GcHxeZ7++X6FaHR3Cdv3rr7EEg3MwsOIZ6SmgeQ0gcs32RZf0giFMwo1LkgiB9KrJTBXkU0CSYysxfeLUCEd11q4h4lZyhxU8CMvgs+1m3s6A2/5uYDYSSqJuAHTHQgntMn7/baXKVXuWXrSSERxkUiqwlKjFFHz0kshj3ZXiTZ3EhjBZgXeeGzGEEbZBQCEJgXKUpl+C0D07PLKt6f2ya4TVTZ3WOjh7dKq14+0nC3w+5z0ZtGtv+IS1LFfajNs/LsT4fDmKsIoEQg2Kf5S//ckeuzaR4bkMGCm3qquJrNE6Uqq0MZzSUdUpnsILjfLiVlrtHk/9So7ulRc8XyhBIWDy9lTS7NxOfpw5cnVJRcjEwrZ4Q6iNwdsh4vPZLVgavwfFRW87DjF6A= + - secure: Jef94P0QfHuMT5GQNrzfMdtVUVvV9dEGsvOLFqPvIkPLJZWGqwaIFUG5t0mCPEgn8uct3XPOiIYivgxnOURa1JNegnTbjRLXBOAjhE5hw2x2IRWj2xT+ylYCNqh9jtIEJdAjUzJnXXj1iasZGCKa4DNwNLgsuQ8d5Yl3bY5l5YI4MtTsdzbBUT9WewDWgnO/MhZM52w18XLBx+Fsq80F0VwpzoStKRula8anOhL+Bvj1uAielMOUo3QcpYcV2XfTnM5n0ApwqUhmv/8YoJpHXjGTeKRM1Hem1jFtfWCjSRrlEKEFJALEJImf0iWbZN5Z0TOcfJqzPY09/8h60OOfi0TXcnwVnSX33Zp1oDLDlRnsN7HQg+yIub0N03OwHqmC2AO0ShkO/lBmEMsfqlEoc4o2GJ3YL+JpC5vPsy7fFMad+jNGXlg6jPAshvCJ2DfnmK1jYSSVdVNUUeP1Bk5rhQkFzFH3vgNgX3nFk0gEYrfDn3/Ea6tORybSJzaAkB9bU1n4U2e3OplvWr1Ll8O/t87ws8ctyY/Ah2hRmhSKEG9cdySnm7Uq8H7696MZEEw9aatj+bRJk5CbCVtSX8v49I3C0tERcUBO5M3U+/g0qeBW9hEhxnBeG3y253Bo1FhSxbaZhGwSGJ91htRXLlJlUs2QrOcSYMsCT6p35KdWaqA= + - secure: NMVS9EU+ahQXGiyTCHyZ44rf+8b3me3UXD1DozMm04lCvnWoBqJE4aXBGQsDAWuOL4NTTm0SaVu6sBY6ZTXOYYF59mwEbxt4qpmVjZ+vBrtMbMiqoxv145blquR9JKedkdP6IGSd7VSQwSba71f/RVv5VeGvxUSEhCwA04kKxToOPwmnORmT6qwb7PkPCMNHxz4VpsUIsKx8jRrY6Gmp6FvQJBHfKEHnDQohB1ReIYEYi39ijLvpbCZqrB5u1N9oF6WlpBiNIX3kQizn7ftUyewJgoZMnfpW/Lta6e91yzFInWg75bZdW3faa30Qy0yw0zlQIPLs89c8A/XH1fGVECH9At9VNmdYrb0fD9aWnH7zdX6Im+Bw7Ptph4x6tB7zPeFoZR5cVZT7L06/HbnW7NeQk4tg/N4I1tOaO7AQl+ofhCzesZ56bSxETiNFn9QiNwWFTzjlkG7jxN1iAAkdYsZEQHwtEK63R//NJtXpbbtNA831QqgDqBK+IxyKeLhmxmu17dWcUw9tm4jlZ7d6nPB9bzJcVM6K2uRJyW07SlBqd65WJTXPV1PFww8zh+chAC4ZkLDhupn+7ZSG2ylLYGgepmABoC/CXHkXEsNzdQ8wPX/pDIz2WNmwEXyC/Nv+WNpFS/tWIAryIPOLMuETIgbaOLbD5vZDSKxDZVGDvPE= + - secure: cNr4PiK6ZZADoRMacL4lvdMYWgM9H4lKN3u+nfMA/MrVrnSjeRonkO7RjMJWs9HicPanEks10R1w/T/6nWyFQV2CPkEBSNSLf3DAD+dlRekKfWogGXzYnvqeiki1HzsIPYTImiD5BtPn6SbJmO/ErJt3zuogspyBws/X7XfZ+u8FIpPsYEmvHslT2jARuput0yNfldUgxxyI0IvgkuCEcCTFwePspjbn6zR6Df67e+r5ibFqkdPYdXkQVpvfA90RPpfBUuuaEO7kkFlKbPK+Nl/jbUnjcfbe8zJRpSb8j/S2USAiBUjFsqsdvFqZ9WumjXJLrrNFt/UgIXaMyG3Y8xJl9kzCcx36wcNoaP2mx2qucYTdC0ey49g0uywvOVDdykmctQRF7uYQS+UkMqs5jRLgAjQ1/wJISrvtcpaK/4DyhLBUFrOf9chep2hzWBFaWPto1IUpWu9Sjtz5rtjsAm5LR7zvIxcorvRall5kRokAspHb9+TaQKSDOpYNF+PB77cb9H0OLZBLVPGo0WJHq5dv0NVZSH9JVq4AiJDmvMWI6weBis+tLbECMwbeTezo6hDOuII7VZt/KcHgzlt3KCDwv8krQq71q7ySDt7SxrvLeDjeiEFkjwA0lN7Cin1yqjON83LsIsWkKxbf+xNJRngSE4bPn95j3pHELdo3uqY= + - secure: iDkNjibPknbawHN+jobw1AEqhQDhqFvGPBUX7FkxlrIanNR71Tj8CPAFtDpJbYaBMdPt4IzOmD2JVo9w7E1TfNX4DsOpkb2MbOr55TxfXQ/+y7TBdJ9B/62BvhFWk8Hvq8TWVPTCgNIaVXNfqBAj6WQSX3AbUVHWSOM9Aey5joBZuhdWXSmKo46lLOradacDDPZYjrLEsMTS2CotzduKQ4C8dGCVcMEbBnS3O2WRj3oR0wiiP3X0jbnxJkOV2MCoadZxSu5B+gaaJ+Yv7EKT0vy6ASp6LYrWkjY0eKbTqy8NtCvCFlliND/iaq4LEv838hQfO/o0WeB2b7/2MH2EW1v8XLacV12ak5wJgb7b+L6fG+lMKMta5Re+vjdRYgoU5EVeWQNxrnX1chEdzFXb/q2+5DVp43qH5i1Tu4FR/kSBobQeSAbT7HTkWAVz3kg8HmubYZ3P0eXToZA/DlX0dphWxO9ShR3H+XTJhh3tSqzxMZxxhGqPcN4DPSfOTnJQ0v0NPz016lulCr9SuIOSM3f7HpeGXo5SeQbrY3yCnBG8Qxpx2kYaZZlT4J6fx3iFl77SY/lQu6H/Y8ZtufWEogPSkGEh+NLLWuwwBQFC3vH8l3J26vcqHZR3N9+GyqX13CSqWEUysMF4nBOi52ckhwJRF8hAeX+DIqxoLfjUkDc= diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/CHANGELOG.md b/vendor/github.com/aliyun/aliyun-oss-go-sdk/CHANGELOG.md new file mode 100644 index 000000000..350c9e607 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/CHANGELOG.md @@ -0,0 +1,119 @@ +# ChangeLog - Aliyun OSS SDK for Go +## 版本号:1.9.5 日期:2019-03-08 +### 变更内容 +- 变更:增加了限速上传功能 + +## 版本号:1.9.4 日期:2019-01-25 +### 变更内容 +- 修复:在开启日志后,如果接口返回错误readResponseBody函数被调用两次 +- 变更:增加livechannel功能各个api接口 + +## 版本号:1.9.3 日期:2019-01-10 +### 变更内容 +- 修复:分片上传时传入partSize值不对是的提示信息不准确的问题 +- 修复:仅仅在使用userAgent的时候初始化它的值 +- 变更:添加ContentLanguage选项 +- 变更:支持设置最大的空闲连接个数 +- 变更:当配置的endpoint不对时,输出的错误信息将会打印出正确的endpoint +- 变更:支持ServerSideEncryptionKeyID选项,允许用户传入kms-id +- 变更:添加日志模块,支持设置日志级别 + +## 版本号:1.9.2 日期:2018-11-16 +### 变更内容 +- 变更:添加支持设置request Payer的option +- 变更:添加支持设置checkpoint目录的option +- 变更:getobjectmeta接口增加options参数,可以支持传入option选项 +- 变更:listobjecs接口增加options参数,可以支持传入option选项 +- 变更:listmultipartuploads接口增加options参数, 可以支持传入option选项 +- 修复:解决调用接口返回出错时,且返回的http body为空时,打印错误消息不包含"request_id"的问题 +- 变更:abortmultipartupload接口增加options参数, 可以支持传入option选项 +- 变更:completemultipartupload接口增加options参数, 可以支持传入option选项 + +## 版本号:1.9.1 日期:2018-09-17 +### 变更内容 + - 变更:支持ipv6 + - 变更:支持修改对象的存储类型 + - 修复:修改sample中GetBucketReferer方法名拼写错误 + - 修复:修复NopCloser在close的时候并不释放内存的内存泄漏问题 + - 变更:增加ProcessObject接口 + - 修复:修改图片处理接口参数拼写错误导致无法处理的bug + - 修复:增加ListUploadedParts接口的options选项 + - 修复:增加Callback&CallbackVal选项,支持回调使用 + - 修复:GetObject接口返回Response,支持用户读取crc等返回值 + - 修复:当以压缩格式返回数据时,GetObject接口不校验crc + +## 版本号:1.9.0 日期:2018-06-15 +### 变更内容 + - 变更:国际化 + +## 版本号:1.8.0 日期:2017-12-12 +### 变更内容 + - 变更:空闲链接关闭时间调整为50秒 + - 修复:修复临时账号使用SignURL的问题 + +## 版本号:1.7.0 日期:2017-09-25 +### 变更内容 + - 增加:DownloadFile支持CRC校验 + - 增加:STS测试用例 + +## 版本号:1.6.0 日期:2017-09-01 +### 变更内容 + - 修复:URL中特殊字符的编码问题 + - 变更:不再支持Golang 1.4 + +## 版本号:1.5.1 日期:2017-08-04 +### 变更内容 + - 修复:SignURL中Key编码的问题 + - 修复:DownloadFile下载完成后rename失败的问题 + +## 版本号:1.5.0 日期:2017-07-25 +### 变更内容 + - 增加:支持生成URL签名 + - 增加:GetObject支持ResponseContentType等选项 + - 修复:DownloadFile去除分片小于5GB的限制 + - 修复:AppendObject在appendPosition不正确时发生panic + +## 版本号:1.4.0 日期:2017-05-23 +### 变更内容 + - 增加:支持符号链接symlink + - 增加:支持RestoreObject + - 增加:CreateBucket支持StorageClass + - 增加:支持范围读NormalizedRange + - 修复:IsObjectExist使用GetObjectMeta实现 + +## 版本号:1.3.0 日期:2017-01-13 +### 变更内容 + - 增加:上传下载支持进度条功能 + +## 版本号:1.2.3 日期:2016-12-28 +### 变更内容 + - 修复:每次请求使用一个http.Client修改为共用http.Client + +## 版本号:1.2.2 日期:2016-12-10 +### 变更内容 + - 修复:GetObjectToFile/DownloadFile使用临时文件下载,成功后重命名成下载文件 + - 修复:新建的下载文件权限修改为0664 + +## 版本号:1.2.1 日期:2016-11-11 +### 变更内容 + - 修复:只有当OSS返回x-oss-hash-crc64ecma头部时,才对上传的文件进行CRC64完整性校验 + +## 版本号:1.2.0 日期:2016-10-18 +### 变更内容 + - 增加:支持CRC64校验 + - 增加:支持指定Useragent + - 修复:计算MD5占用内存大的问题 + - 修复:CopyObject时Object名称没有URL编码的问题 + +## 版本号:1.1.0 日期:2016-08-09 +### 变更内容 + - 增加:支持代理服务器 + +## 版本号:1.0.0 日期:2016-06-24 +### 变更内容 + - 增加:断点分片复制接口Bucket.CopyFile + - 增加:Bucket间复制接口Bucket.CopyObjectTo、Bucket.CopyObjectFrom + - 增加:Client.GetBucketInfo接口 + - 增加:Bucket.UploadPartCopy支持Bucket间复制 + - 修复:断点上传、断点下载出错后,协程不退出的Bug + - 删除:接口Bucket.CopyObjectToBucket diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/README-CN.md b/vendor/github.com/aliyun/aliyun-oss-go-sdk/README-CN.md new file mode 100644 index 000000000..9eda131d0 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/README-CN.md @@ -0,0 +1,168 @@ +# Aliyun OSS SDK for Go + +[![GitHub version](https://badge.fury.io/gh/aliyun%2Faliyun-oss-go-sdk.svg)](https://badge.fury.io/gh/aliyun%2Faliyun-oss-go-sdk) +[![Build Status](https://travis-ci.org/aliyun/aliyun-oss-go-sdk.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-oss-go-sdk) +[![Coverage Status](https://coveralls.io/repos/github/aliyun/aliyun-oss-go-sdk/badge.svg?branch=master)](https://coveralls.io/github/aliyun/aliyun-oss-go-sdk?branch=master) + +## [README of English](https://github.com/aliyun/aliyun-oss-go-sdk/blob/master/README.md) + +## 关于 +> - 此Go SDK基于[阿里云对象存储服务](http://www.aliyun.com/product/oss/)官方API构建。 +> - 阿里云对象存储(Object Storage Service,简称OSS),是阿里云对外提供的海量,安全,低成本,高可靠的云存储服务。 +> - OSS适合存放任意文件类型,适合各种网站、开发企业及开发者使用。 +> - 使用此SDK,用户可以方便地在任何应用、任何时间、任何地点上传,下载和管理数据。 + +## 版本 +> - 当前版本:1.9.5 + +## 运行环境 +> - Go 1.5及以上。 + +## 安装方法 +### GitHub安装 +> - 执行命令`go get github.com/aliyun/aliyun-oss-go-sdk/oss`获取远程代码包。 +> - 在您的代码中使用`import "github.com/aliyun/aliyun-oss-go-sdk/oss"`引入OSS Go SDK的包。 + +## 快速使用 +#### 获取存储空间列表(List Bucket) +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + lsRes, err := client.ListBuckets() + if err != nil { + // HandleError(err) + } + + for _, bucket := range lsRes.Buckets { + fmt.Println("Buckets:", bucket.Name) + } +``` + +#### 创建存储空间(Create Bucket) +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + err = client.CreateBucket("my-bucket") + if err != nil { + // HandleError(err) + } +``` + +#### 删除存储空间(Delete Bucket) +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + err = client.DeleteBucket("my-bucket") + if err != nil { + // HandleError(err) + } +``` + +#### 上传文件(Put Object) +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + bucket, err := client.Bucket("my-bucket") + if err != nil { + // HandleError(err) + } + + err = bucket.PutObjectFromFile("my-object", "LocalFile") + if err != nil { + // HandleError(err) + } +``` + +#### 下载文件 (Get Object) +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + bucket, err := client.Bucket("my-bucket") + if err != nil { + // HandleError(err) + } + + err = bucket.GetObjectToFile("my-object", "LocalFile") + if err != nil { + // HandleError(err) + } +``` + +#### 获取文件列表(List Objects) +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + bucket, err := client.Bucket("my-bucket") + if err != nil { + // HandleError(err) + } + + lsRes, err := bucket.ListObjects() + if err != nil { + // HandleError(err) + } + + for _, object := range lsRes.Objects { + fmt.Println("Objects:", object.Key) + } +``` + +#### 删除文件(Delete Object) +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + bucket, err := client.Bucket("my-bucket") + if err != nil { + // HandleError(err) + } + + err = bucket.DeleteObject("my-object") + if err != nil { + // HandleError(err) + } +``` + +#### 其它 +更多的示例程序,请参看OSS Go SDK安装路径(即GOPATH变量中的第一个路径)下的`src\github.com\aliyun\aliyun-oss-go-sdk\sample`,该目录下为示例程序, +或者参看`https://github.com/aliyun/aliyun-oss-go-sdk`下sample目录中的示例文件。 + +## 注意事项 +### 运行sample +> - 拷贝示例文件。到OSS Go SDK的安装路径(即GOPATH变量中的第一个路径),进入OSS Go SDK的代码目录`src\github.com\aliyun\aliyun-oss-go-sdk`, +把其下的sample目录和sample.go复制到您的测试工程src目录下。 +> - 修改sample/config.go里的endpoint、AccessKeyId、AccessKeySecret、BucketName等配置。 +> - 请在您的工程目录下执行`go run src/sample.go`。 + +## 联系我们 +> - [阿里云OSS官方网站](http://oss.aliyun.com) +> - [阿里云OSS官方论坛](http://bbs.aliyun.com) +> - [阿里云OSS官方文档中心](http://www.aliyun.com/product/oss#Docs) +> - 阿里云官方技术支持:[提交工单](https://workorder.console.aliyun.com/#/ticket/createIndex) + +## 作者 +> - [Yubin Bai](https://github.com/baiyubin) +> - [Guozhong Han](https://github.com/hangzws) + +## License +> - Apache License 2.0 diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/README.md b/vendor/github.com/aliyun/aliyun-oss-go-sdk/README.md new file mode 100644 index 000000000..a2218c4af --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/README.md @@ -0,0 +1,167 @@ +# Alibaba Cloud OSS SDK for Go + +[![GitHub Version](https://badge.fury.io/gh/aliyun%2Faliyun-oss-go-sdk.svg)](https://badge.fury.io/gh/aliyun%2Faliyun-oss-go-sdk) +[![Build Status](https://travis-ci.org/aliyun/aliyun-oss-go-sdk.svg?branch=master)](https://travis-ci.org/aliyun/aliyun-oss-go-sdk) +[![Coverage Status](https://coveralls.io/repos/github/aliyun/aliyun-oss-go-sdk/badge.svg?branch=master)](https://coveralls.io/github/aliyun/aliyun-oss-go-sdk?branch=master) + +## [README of Chinese](https://github.com/aliyun/aliyun-oss-go-sdk/blob/master/README-CN.md) + +## About +> - This Go SDK is based on the official APIs of [Alibaba Cloud OSS](http://www.aliyun.com/product/oss/). +> - Alibaba Cloud Object Storage Service (OSS) is a cloud storage service provided by Alibaba Cloud, featuring massive capacity, security, a low cost, and high reliability. +> - The OSS can store any type of files and therefore applies to various websites, development enterprises and developers. +> - With this SDK, you can upload, download and manage data on any app anytime and anywhere conveniently. + +## Version +> - Current version: 1.9.5 + +## Running Environment +> - Go 1.5 or above. + +## Installing +### Install the SDK through GitHub +> - Run the 'go get github.com/aliyun/aliyun-oss-go-sdk/oss' command to get the remote code package. +> - Use 'import "github.com/aliyun/aliyun-oss-go-sdk/oss"' in your code to introduce OSS Go SDK package. + +## Getting Started +### List Bucket +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + lsRes, err := client.ListBuckets() + if err != nil { + // HandleError(err) + } + + for _, bucket := range lsRes.Buckets { + fmt.Println("Buckets:", bucket.Name) + } +``` + +### Create Bucket +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + err = client.CreateBucket("my-bucket") + if err != nil { + // HandleError(err) + } +``` + +### Delete Bucket +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + err = client.DeleteBucket("my-bucket") + if err != nil { + // HandleError(err) + } +``` + +### Put Object +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + bucket, err := client.Bucket("my-bucket") + if err != nil { + // HandleError(err) + } + + err = bucket.PutObjectFromFile("my-object", "LocalFile") + if err != nil { + // HandleError(err) + } +``` + +### Get Object +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + bucket, err := client.Bucket("my-bucket") + if err != nil { + // HandleError(err) + } + + err = bucket.GetObjectToFile("my-object", "LocalFile") + if err != nil { + // HandleError(err) + } +``` + +### List Objects +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + bucket, err := client.Bucket("my-bucket") + if err != nil { + // HandleError(err) + } + + lsRes, err := bucket.ListObjects() + if err != nil { + // HandleError(err) + } + + for _, object := range lsRes.Objects { + fmt.Println("Objects:", object.Key) + } +``` + +### Delete Object +```go + client, err := oss.New("Endpoint", "AccessKeyId", "AccessKeySecret") + if err != nil { + // HandleError(err) + } + + bucket, err := client.Bucket("my-bucket") + if err != nil { + // HandleError(err) + } + + err = bucket.DeleteObject("my-object") + if err != nil { + // HandleError(err) + } +``` + +## Complete Example +More example projects can be found at 'src\github.com\aliyun\aliyun-oss-go-sdk\sample' under the installation path of the OSS Go SDK (the first path of the GOPATH variable). The directory contains example projects. +Or you can refer to the example objects in the sample directory under 'https://github.com/aliyun/aliyun-oss-go-sdk'. + +### Running Example +> - Copy the example file. Go to the installation path of OSS Go SDK (the first path of the GOPATH variable), enter the code directory of the OSS Go SDK, namely 'src\github.com\aliyun\aliyun-oss-go-sdk', +and copy the sample directory and sample.go to the src directory of your test project. +> - Modify the endpoint, AccessKeyId, AccessKeySecret and BucketName configuration settings in sample/config.go. +> - Run 'go run src/sample.go' under your project directory. + +## Contacting us +> - [Alibaba Cloud OSS official website](http://oss.aliyun.com). +> - [Alibaba Cloud OSS official forum](http://bbs.aliyun.com). +> - [Alibaba Cloud OSS official documentation center](http://www.aliyun.com/product/oss#Docs). +> - Alibaba Cloud official technical support: [Submit a ticket](https://workorder.console.aliyun.com/#/ticket/createIndex). + +## Author +> - [Yubin Bai](https://github.com/baiyubin) +> - [Guozhong Han](https://github.com/hangzws) + +## License +> - Apache License 2.0. diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/auth.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/auth.go new file mode 100644 index 000000000..fad9f0c6c --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/auth.go @@ -0,0 +1,130 @@ +package oss + +import ( + "bytes" + "crypto/hmac" + "crypto/sha1" + "encoding/base64" + "fmt" + "hash" + "io" + "net/http" + "sort" + "strconv" + "strings" +) + +// headerSorter defines the key-value structure for storing the sorted data in signHeader. +type headerSorter struct { + Keys []string + Vals []string +} + +// signHeader signs the header and sets it as the authorization header. +func (conn Conn) signHeader(req *http.Request, canonicalizedResource string) { + // Get the final authorization string + authorizationStr := "OSS " + conn.config.AccessKeyID + ":" + conn.getSignedStr(req, canonicalizedResource) + + // Give the parameter "Authorization" value + req.Header.Set(HTTPHeaderAuthorization, authorizationStr) +} + +func (conn Conn) getSignedStr(req *http.Request, canonicalizedResource string) string { + // Find out the "x-oss-"'s address in header of the request + temp := make(map[string]string) + + for k, v := range req.Header { + if strings.HasPrefix(strings.ToLower(k), "x-oss-") { + temp[strings.ToLower(k)] = v[0] + } + } + hs := newHeaderSorter(temp) + + // Sort the temp by the ascending order + hs.Sort() + + // Get the canonicalizedOSSHeaders + canonicalizedOSSHeaders := "" + for i := range hs.Keys { + canonicalizedOSSHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n" + } + + // Give other parameters values + // when sign URL, date is expires + date := req.Header.Get(HTTPHeaderDate) + contentType := req.Header.Get(HTTPHeaderContentType) + contentMd5 := req.Header.Get(HTTPHeaderContentMD5) + + signStr := req.Method + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedOSSHeaders + canonicalizedResource + + conn.config.WriteLog(Debug, "[Req:%p]signStr:%s.\n", req, signStr) + + h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(conn.config.AccessKeySecret)) + io.WriteString(h, signStr) + signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil)) + + return signedStr +} + +func (conn Conn) getRtmpSignedStr(bucketName, channelName, playlistName string, expiration int64, params map[string]interface{}) string { + if params[HTTPParamAccessKeyID] == nil { + return "" + } + + canonResource := fmt.Sprintf("/%s/%s", bucketName, channelName) + canonParamsKeys := []string{} + for key := range params { + if key != HTTPParamAccessKeyID && key != HTTPParamSignature && key != HTTPParamExpires && key != HTTPParamSecurityToken { + canonParamsKeys = append(canonParamsKeys, key) + } + } + + sort.Strings(canonParamsKeys) + canonParamsStr := "" + for _, key := range canonParamsKeys { + canonParamsStr = fmt.Sprintf("%s%s:%s\n", canonParamsStr, key, params[key].(string)) + } + + expireStr := strconv.FormatInt(expiration, 10) + signStr := expireStr + "\n" + canonParamsStr + canonResource + + h := hmac.New(func() hash.Hash { return sha1.New() }, []byte(conn.config.AccessKeySecret)) + io.WriteString(h, signStr) + signedStr := base64.StdEncoding.EncodeToString(h.Sum(nil)) + return signedStr +} + +// newHeaderSorter is an additional function for function SignHeader. +func newHeaderSorter(m map[string]string) *headerSorter { + hs := &headerSorter{ + Keys: make([]string, 0, len(m)), + Vals: make([]string, 0, len(m)), + } + + for k, v := range m { + hs.Keys = append(hs.Keys, k) + hs.Vals = append(hs.Vals, v) + } + return hs +} + +// Sort is an additional function for function SignHeader. +func (hs *headerSorter) Sort() { + sort.Sort(hs) +} + +// Len is an additional function for function SignHeader. +func (hs *headerSorter) Len() int { + return len(hs.Vals) +} + +// Less is an additional function for function SignHeader. +func (hs *headerSorter) Less(i, j int) bool { + return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0 +} + +// Swap is an additional function for function SignHeader. +func (hs *headerSorter) Swap(i, j int) { + hs.Vals[i], hs.Vals[j] = hs.Vals[j], hs.Vals[i] + hs.Keys[i], hs.Keys[j] = hs.Keys[j], hs.Keys[i] +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket.go new file mode 100644 index 000000000..067855e09 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket.go @@ -0,0 +1,973 @@ +package oss + +import ( + "bytes" + "crypto/md5" + "encoding/base64" + "encoding/xml" + "fmt" + "hash" + "hash/crc64" + "io" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" +) + +// Bucket implements the operations of object. +type Bucket struct { + Client Client + BucketName string +} + +// PutObject creates a new object and it will overwrite the original one if it exists already. +// +// objectKey the object key in UTF-8 encoding. The length must be between 1 and 1023, and cannot start with "/" or "\". +// reader io.Reader instance for reading the data for uploading +// options the options for uploading the object. The valid options here are CacheControl, ContentDisposition, ContentEncoding +// Expires, ServerSideEncryption, ObjectACL and Meta. Refer to the link below for more details. +// https://help.aliyun.com/document_detail/oss/api-reference/object/PutObject.html +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) PutObject(objectKey string, reader io.Reader, options ...Option) error { + opts := addContentType(options, objectKey) + + request := &PutObjectRequest{ + ObjectKey: objectKey, + Reader: reader, + } + resp, err := bucket.DoPutObject(request, opts) + if err != nil { + return err + } + defer resp.Body.Close() + + return err +} + +// PutObjectFromFile creates a new object from the local file. +// +// objectKey object key. +// filePath the local file path to upload. +// options the options for uploading the object. Refer to the parameter options in PutObject for more details. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) PutObjectFromFile(objectKey, filePath string, options ...Option) error { + fd, err := os.Open(filePath) + if err != nil { + return err + } + defer fd.Close() + + opts := addContentType(options, filePath, objectKey) + + request := &PutObjectRequest{ + ObjectKey: objectKey, + Reader: fd, + } + resp, err := bucket.DoPutObject(request, opts) + if err != nil { + return err + } + defer resp.Body.Close() + + return err +} + +// DoPutObject does the actual upload work. +// +// request the request instance for uploading an object. +// options the options for uploading an object. +// +// Response the response from OSS. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) DoPutObject(request *PutObjectRequest, options []Option) (*Response, error) { + isOptSet, _, _ := isOptionSet(options, HTTPHeaderContentType) + if !isOptSet { + options = addContentType(options, request.ObjectKey) + } + + listener := getProgressListener(options) + + params := map[string]interface{}{} + resp, err := bucket.do("PUT", request.ObjectKey, params, options, request.Reader, listener) + if err != nil { + return nil, err + } + + if bucket.getConfig().IsEnableCRC { + err = checkCRC(resp, "DoPutObject") + if err != nil { + return resp, err + } + } + + err = checkRespCode(resp.StatusCode, []int{http.StatusOK}) + + return resp, err +} + +// GetObject downloads the object. +// +// objectKey the object key. +// options the options for downloading the object. The valid values are: Range, IfModifiedSince, IfUnmodifiedSince, IfMatch, +// IfNoneMatch, AcceptEncoding. For more details, please check out: +// https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html +// +// io.ReadCloser reader instance for reading data from response. It must be called close() after the usage and only valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) GetObject(objectKey string, options ...Option) (io.ReadCloser, error) { + result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options) + if err != nil { + return nil, err + } + + return result.Response, nil +} + +// GetObjectToFile downloads the data to a local file. +// +// objectKey the object key to download. +// filePath the local file to store the object data. +// options the options for downloading the object. Refer to the parameter options in method GetObject for more details. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) GetObjectToFile(objectKey, filePath string, options ...Option) error { + tempFilePath := filePath + TempFileSuffix + + // Calls the API to actually download the object. Returns the result instance. + result, err := bucket.DoGetObject(&GetObjectRequest{objectKey}, options) + if err != nil { + return err + } + defer result.Response.Close() + + // If the local file does not exist, create a new one. If it exists, overwrite it. + fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode) + if err != nil { + return err + } + + // Copy the data to the local file path. + _, err = io.Copy(fd, result.Response.Body) + fd.Close() + if err != nil { + return err + } + + // Compares the CRC value + hasRange, _, _ := isOptionSet(options, HTTPHeaderRange) + encodeOpt, _ := findOption(options, HTTPHeaderAcceptEncoding, nil) + acceptEncoding := "" + if encodeOpt != nil { + acceptEncoding = encodeOpt.(string) + } + if bucket.getConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" { + result.Response.ClientCRC = result.ClientCRC.Sum64() + err = checkCRC(result.Response, "GetObjectToFile") + if err != nil { + os.Remove(tempFilePath) + return err + } + } + + return os.Rename(tempFilePath, filePath) +} + +// DoGetObject is the actual API that gets the object. It's the internal function called by other public APIs. +// +// request the request to download the object. +// options the options for downloading the file. Checks out the parameter options in method GetObject. +// +// GetObjectResult the result instance of getting the object. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) DoGetObject(request *GetObjectRequest, options []Option) (*GetObjectResult, error) { + params, _ := getRawParams(options) + resp, err := bucket.do("GET", request.ObjectKey, params, options, nil, nil) + if err != nil { + return nil, err + } + + result := &GetObjectResult{ + Response: resp, + } + + // CRC + var crcCalc hash.Hash64 + hasRange, _, _ := isOptionSet(options, HTTPHeaderRange) + if bucket.getConfig().IsEnableCRC && !hasRange { + crcCalc = crc64.New(crcTable()) + result.ServerCRC = resp.ServerCRC + result.ClientCRC = crcCalc + } + + // Progress + listener := getProgressListener(options) + + contentLen, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderContentLength), 10, 64) + resp.Body = TeeReader(resp.Body, crcCalc, contentLen, listener, nil) + + return result, nil +} + +// CopyObject copies the object inside the bucket. +// +// srcObjectKey the source object to copy. +// destObjectKey the target object to copy. +// options options for copying an object. You can specify the conditions of copy. The valid conditions are CopySourceIfMatch, +// CopySourceIfNoneMatch, CopySourceIfModifiedSince, CopySourceIfUnmodifiedSince, MetadataDirective. +// Also you can specify the target object's attributes, such as CacheControl, ContentDisposition, ContentEncoding, Expires, +// ServerSideEncryption, ObjectACL, Meta. Refer to the link below for more details : +// https://help.aliyun.com/document_detail/oss/api-reference/object/CopyObject.html +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) CopyObject(srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) { + var out CopyObjectResult + options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey))) + params := map[string]interface{}{} + resp, err := bucket.do("PUT", destObjectKey, params, options, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// CopyObjectTo copies the object to another bucket. +// +// srcObjectKey source object key. The source bucket is Bucket.BucketName . +// destBucketName target bucket name. +// destObjectKey target object name. +// options copy options, check out parameter options in function CopyObject for more details. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) CopyObjectTo(destBucketName, destObjectKey, srcObjectKey string, options ...Option) (CopyObjectResult, error) { + return bucket.copy(srcObjectKey, destBucketName, destObjectKey, options...) +} + +// +// CopyObjectFrom copies the object to another bucket. +// +// srcBucketName source bucket name. +// srcObjectKey source object name. +// destObjectKey target object name. The target bucket name is Bucket.BucketName. +// options copy options. Check out parameter options in function CopyObject. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) CopyObjectFrom(srcBucketName, srcObjectKey, destObjectKey string, options ...Option) (CopyObjectResult, error) { + destBucketName := bucket.BucketName + var out CopyObjectResult + srcBucket, err := bucket.Client.Bucket(srcBucketName) + if err != nil { + return out, err + } + + return srcBucket.copy(srcObjectKey, destBucketName, destObjectKey, options...) +} + +func (bucket Bucket) copy(srcObjectKey, destBucketName, destObjectKey string, options ...Option) (CopyObjectResult, error) { + var out CopyObjectResult + options = append(options, CopySource(bucket.BucketName, url.QueryEscape(srcObjectKey))) + headers := make(map[string]string) + err := handleOptions(headers, options) + if err != nil { + return out, err + } + params := map[string]interface{}{} + resp, err := bucket.Client.Conn.Do("PUT", destBucketName, destObjectKey, params, headers, nil, 0, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// AppendObject uploads the data in the way of appending an existing or new object. +// +// AppendObject the parameter appendPosition specifies which postion (in the target object) to append. For the first append (to a non-existing file), +// the appendPosition should be 0. The appendPosition in the subsequent calls will be the current object length. +// For example, the first appendObject's appendPosition is 0 and it uploaded 65536 bytes data, then the second call's position is 65536. +// The response header x-oss-next-append-position after each successful request also specifies the next call's append position (so the caller need not to maintain this information). +// +// objectKey the target object to append to. +// reader io.Reader. The read instance for reading the data to append. +// appendPosition the start position to append. +// destObjectProperties the options for the first appending, such as CacheControl, ContentDisposition, ContentEncoding, +// Expires, ServerSideEncryption, ObjectACL. +// +// int64 the next append position, it's valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) AppendObject(objectKey string, reader io.Reader, appendPosition int64, options ...Option) (int64, error) { + request := &AppendObjectRequest{ + ObjectKey: objectKey, + Reader: reader, + Position: appendPosition, + } + + result, err := bucket.DoAppendObject(request, options) + if err != nil { + return appendPosition, err + } + + return result.NextPosition, err +} + +// DoAppendObject is the actual API that does the object append. +// +// request the request object for appending object. +// options the options for appending object. +// +// AppendObjectResult the result object for appending object. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) DoAppendObject(request *AppendObjectRequest, options []Option) (*AppendObjectResult, error) { + params := map[string]interface{}{} + params["append"] = nil + params["position"] = strconv.FormatInt(request.Position, 10) + headers := make(map[string]string) + + opts := addContentType(options, request.ObjectKey) + handleOptions(headers, opts) + + var initCRC uint64 + isCRCSet, initCRCOpt, _ := isOptionSet(options, initCRC64) + if isCRCSet { + initCRC = initCRCOpt.(uint64) + } + + listener := getProgressListener(options) + + handleOptions(headers, opts) + resp, err := bucket.Client.Conn.Do("POST", bucket.BucketName, request.ObjectKey, params, headers, + request.Reader, initCRC, listener) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + nextPosition, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderOssNextAppendPosition), 10, 64) + result := &AppendObjectResult{ + NextPosition: nextPosition, + CRC: resp.ServerCRC, + } + + if bucket.getConfig().IsEnableCRC && isCRCSet { + err = checkCRC(resp, "AppendObject") + if err != nil { + return result, err + } + } + + return result, nil +} + +// DeleteObject deletes the object. +// +// objectKey the object key to delete. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) DeleteObject(objectKey string) error { + params := map[string]interface{}{} + resp, err := bucket.do("DELETE", objectKey, params, nil, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + +// DeleteObjects deletes multiple objects. +// +// objectKeys the object keys to delete. +// options the options for deleting objects. +// Supported option is DeleteObjectsQuiet which means it will not return error even deletion failed (not recommended). By default it's not used. +// +// DeleteObjectsResult the result object. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) DeleteObjects(objectKeys []string, options ...Option) (DeleteObjectsResult, error) { + out := DeleteObjectsResult{} + dxml := deleteXML{} + for _, key := range objectKeys { + dxml.Objects = append(dxml.Objects, DeleteObject{Key: key}) + } + isQuiet, _ := findOption(options, deleteObjectsQuiet, false) + dxml.Quiet = isQuiet.(bool) + + bs, err := xml.Marshal(dxml) + if err != nil { + return out, err + } + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + options = append(options, ContentType(contentType)) + sum := md5.Sum(bs) + b64 := base64.StdEncoding.EncodeToString(sum[:]) + options = append(options, ContentMD5(b64)) + + params := map[string]interface{}{} + params["delete"] = nil + params["encoding-type"] = "url" + + resp, err := bucket.do("POST", "", params, options, buffer, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + if !dxml.Quiet { + if err = xmlUnmarshal(resp.Body, &out); err == nil { + err = decodeDeleteObjectsResult(&out) + } + } + return out, err +} + +// IsObjectExist checks if the object exists. +// +// bool flag of object's existence (true:exists; false:non-exist) when error is nil. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) IsObjectExist(objectKey string) (bool, error) { + _, err := bucket.GetObjectMeta(objectKey) + if err == nil { + return true, nil + } + + switch err.(type) { + case ServiceError: + if err.(ServiceError).StatusCode == 404 { + return false, nil + } + } + + return false, err +} + +// ListObjects lists the objects under the current bucket. +// +// options it contains all the filters for listing objects. +// It could specify a prefix filter on object keys, the max keys count to return and the object key marker and the delimiter for grouping object names. +// The key marker means the returned objects' key must be greater than it in lexicographic order. +// +// For example, if the bucket has 8 objects, my-object-1, my-object-11, my-object-2, my-object-21, +// my-object-22, my-object-3, my-object-31, my-object-32. If the prefix is my-object-2 (no other filters), then it returns +// my-object-2, my-object-21, my-object-22 three objects. If the marker is my-object-22 (no other filters), then it returns +// my-object-3, my-object-31, my-object-32 three objects. If the max keys is 5, then it returns 5 objects. +// The three filters could be used together to achieve filter and paging functionality. +// If the prefix is the folder name, then it could list all files under this folder (including the files under its subfolders). +// But if the delimiter is specified with '/', then it only returns that folder's files (no subfolder's files). The direct subfolders are in the commonPrefixes properties. +// For example, if the bucket has three objects fun/test.jpg, fun/movie/001.avi, fun/movie/007.avi. And if the prefix is "fun/", then it returns all three objects. +// But if the delimiter is '/', then only "fun/test.jpg" is returned as files and fun/movie/ is returned as common prefix. +// +// For common usage scenario, check out sample/list_object.go. +// +// ListObjectsResponse the return value after operation succeeds (only valid when error is nil). +// +func (bucket Bucket) ListObjects(options ...Option) (ListObjectsResult, error) { + var out ListObjectsResult + + options = append(options, EncodingType("url")) + params, err := getRawParams(options) + if err != nil { + return out, err + } + + resp, err := bucket.do("GET", "", params, options, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + if err != nil { + return out, err + } + + err = decodeListObjectsResult(&out) + return out, err +} + +// SetObjectMeta sets the metadata of the Object. +// +// objectKey object +// options options for setting the metadata. The valid options are CacheControl, ContentDisposition, ContentEncoding, Expires, +// ServerSideEncryption, and custom metadata. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) SetObjectMeta(objectKey string, options ...Option) error { + options = append(options, MetadataDirective(MetaReplace)) + _, err := bucket.CopyObject(objectKey, objectKey, options...) + return err +} + +// GetObjectDetailedMeta gets the object's detailed metadata +// +// objectKey object key. +// options the constraints of the object. Only when the object meets the requirements this method will return the metadata. Otherwise returns error. Valid options are IfModifiedSince, IfUnmodifiedSince, +// IfMatch, IfNoneMatch. For more details check out https://help.aliyun.com/document_detail/oss/api-reference/object/HeadObject.html +// +// http.Header object meta when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) GetObjectDetailedMeta(objectKey string, options ...Option) (http.Header, error) { + params := map[string]interface{}{} + resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return resp.Headers, nil +} + +// GetObjectMeta gets object metadata. +// +// GetObjectMeta is more lightweight than GetObjectDetailedMeta as it only returns basic metadata including ETag +// size, LastModified. The size information is in the HTTP header Content-Length. +// +// objectKey object key +// +// http.Header the object's metadata, valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) GetObjectMeta(objectKey string, options ...Option) (http.Header, error) { + params := map[string]interface{}{} + params["objectMeta"] = nil + //resp, err := bucket.do("GET", objectKey, "?objectMeta", "", nil, nil, nil) + resp, err := bucket.do("HEAD", objectKey, params, options, nil, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + return resp.Headers, nil +} + +// SetObjectACL updates the object's ACL. +// +// Only the bucket's owner could update object's ACL which priority is higher than bucket's ACL. +// For example, if the bucket ACL is private and object's ACL is public-read-write. +// Then object's ACL is used and it means all users could read or write that object. +// When the object's ACL is not set, then bucket's ACL is used as the object's ACL. +// +// Object read operations include GetObject, HeadObject, CopyObject and UploadPartCopy on the source object; +// Object write operations include PutObject, PostObject, AppendObject, DeleteObject, DeleteMultipleObjects, +// CompleteMultipartUpload and CopyObject on target object. +// +// objectKey the target object key (to set the ACL on) +// objectAcl object ACL. Valid options are PrivateACL, PublicReadACL, PublicReadWriteACL. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) SetObjectACL(objectKey string, objectACL ACLType) error { + options := []Option{ObjectACL(objectACL)} + params := map[string]interface{}{} + params["acl"] = nil + resp, err := bucket.do("PUT", objectKey, params, options, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// GetObjectACL gets object's ACL +// +// objectKey the object to get ACL from. +// +// GetObjectACLResult the result object when error is nil. GetObjectACLResult.Acl is the object ACL. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) GetObjectACL(objectKey string) (GetObjectACLResult, error) { + var out GetObjectACLResult + params := map[string]interface{}{} + params["acl"] = nil + resp, err := bucket.do("GET", objectKey, params, nil, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// PutSymlink creates a symlink (to point to an existing object) +// +// Symlink cannot point to another symlink. +// When creating a symlink, it does not check the existence of the target file, and does not check if the target file is symlink. +// Neither it checks the caller's permission on the target file. All these checks are deferred to the actual GetObject call via this symlink. +// If trying to add an existing file, as long as the caller has the write permission, the existing one will be overwritten. +// If the x-oss-meta- is specified, it will be added as the metadata of the symlink file. +// +// symObjectKey the symlink object's key. +// targetObjectKey the target object key to point to. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) PutSymlink(symObjectKey string, targetObjectKey string, options ...Option) error { + options = append(options, symlinkTarget(url.QueryEscape(targetObjectKey))) + params := map[string]interface{}{} + params["symlink"] = nil + resp, err := bucket.do("PUT", symObjectKey, params, options, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// GetSymlink gets the symlink object with the specified key. +// If the symlink object does not exist, returns 404. +// +// objectKey the symlink object's key. +// +// error it's nil if no error, otherwise it's an error object. +// When error is nil, the target file key is in the X-Oss-Symlink-Target header of the returned object. +// +func (bucket Bucket) GetSymlink(objectKey string) (http.Header, error) { + params := map[string]interface{}{} + params["symlink"] = nil + resp, err := bucket.do("GET", objectKey, params, nil, nil, nil) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + targetObjectKey := resp.Headers.Get(HTTPHeaderOssSymlinkTarget) + targetObjectKey, err = url.QueryUnescape(targetObjectKey) + if err != nil { + return resp.Headers, err + } + resp.Headers.Set(HTTPHeaderOssSymlinkTarget, targetObjectKey) + return resp.Headers, err +} + +// RestoreObject restores the object from the archive storage. +// +// An archive object is in cold status by default and it cannot be accessed. +// When restore is called on the cold object, it will become available for access after some time. +// If multiple restores are called on the same file when the object is being restored, server side does nothing for additional calls but returns success. +// By default, the restored object is available for access for one day. After that it will be unavailable again. +// But if another RestoreObject are called after the file is restored, then it will extend one day's access time of that object, up to 7 days. +// +// objectKey object key to restore. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) RestoreObject(objectKey string) error { + params := map[string]interface{}{} + params["restore"] = nil + resp, err := bucket.do("POST", objectKey, params, nil, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK, http.StatusAccepted}) +} + +// SignURL signs the URL. Users could access the object directly with this URL without getting the AK. +// +// objectKey the target object to sign. +// signURLConfig the configuration for the signed URL +// +// string returns the signed URL, when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) SignURL(objectKey string, method HTTPMethod, expiredInSec int64, options ...Option) (string, error) { + if expiredInSec < 0 { + return "", fmt.Errorf("invalid expires: %d, expires must bigger than 0", expiredInSec) + } + expiration := time.Now().Unix() + expiredInSec + + params, err := getRawParams(options) + if err != nil { + return "", err + } + + headers := make(map[string]string) + err = handleOptions(headers, options) + if err != nil { + return "", err + } + + return bucket.Client.Conn.signURL(method, bucket.BucketName, objectKey, expiration, params, headers), nil +} + +// PutObjectWithURL uploads an object with the URL. If the object exists, it will be overwritten. +// PutObjectWithURL It will not generate minetype according to the key name. +// +// signedURL signed URL. +// reader io.Reader the read instance for reading the data for the upload. +// options the options for uploading the data. The valid options are CacheControl, ContentDisposition, ContentEncoding, +// Expires, ServerSideEncryption, ObjectACL and custom metadata. Check out the following link for details: +// https://help.aliyun.com/document_detail/oss/api-reference/object/PutObject.html +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) PutObjectWithURL(signedURL string, reader io.Reader, options ...Option) error { + resp, err := bucket.DoPutObjectWithURL(signedURL, reader, options) + if err != nil { + return err + } + defer resp.Body.Close() + + return err +} + +// PutObjectFromFileWithURL uploads an object from a local file with the signed URL. +// PutObjectFromFileWithURL It does not generate mimetype according to object key's name or the local file name. +// +// signedURL the signed URL. +// filePath local file path, such as dirfile.txt, for uploading. +// options options for uploading, same as the options in PutObject function. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) PutObjectFromFileWithURL(signedURL, filePath string, options ...Option) error { + fd, err := os.Open(filePath) + if err != nil { + return err + } + defer fd.Close() + + resp, err := bucket.DoPutObjectWithURL(signedURL, fd, options) + if err != nil { + return err + } + defer resp.Body.Close() + + return err +} + +// DoPutObjectWithURL is the actual API that does the upload with URL work(internal for SDK) +// +// signedURL the signed URL. +// reader io.Reader the read instance for getting the data to upload. +// options options for uploading. +// +// Response the response object which contains the HTTP response. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) DoPutObjectWithURL(signedURL string, reader io.Reader, options []Option) (*Response, error) { + listener := getProgressListener(options) + + params := map[string]interface{}{} + resp, err := bucket.doURL("PUT", signedURL, params, options, reader, listener) + if err != nil { + return nil, err + } + + if bucket.getConfig().IsEnableCRC { + err = checkCRC(resp, "DoPutObjectWithURL") + if err != nil { + return resp, err + } + } + + err = checkRespCode(resp.StatusCode, []int{http.StatusOK}) + + return resp, err +} + +// GetObjectWithURL downloads the object and returns the reader instance, with the signed URL. +// +// signedURL the signed URL. +// options options for downloading the object. Valid options are IfModifiedSince, IfUnmodifiedSince, IfMatch, +// IfNoneMatch, AcceptEncoding. For more information, check out the following link: +// https://help.aliyun.com/document_detail/oss/api-reference/object/GetObject.html +// +// io.ReadCloser the reader object for getting the data from response. It needs be closed after the usage. It's only valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) GetObjectWithURL(signedURL string, options ...Option) (io.ReadCloser, error) { + result, err := bucket.DoGetObjectWithURL(signedURL, options) + if err != nil { + return nil, err + } + return result.Response, nil +} + +// GetObjectToFileWithURL downloads the object into a local file with the signed URL. +// +// signedURL the signed URL +// filePath the local file path to download to. +// options the options for downloading object. Check out the parameter options in function GetObject for the reference. +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) GetObjectToFileWithURL(signedURL, filePath string, options ...Option) error { + tempFilePath := filePath + TempFileSuffix + + // Get the object's content + result, err := bucket.DoGetObjectWithURL(signedURL, options) + if err != nil { + return err + } + defer result.Response.Close() + + // If the file does not exist, create one. If exists, then overwrite it. + fd, err := os.OpenFile(tempFilePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, FilePermMode) + if err != nil { + return err + } + + // Save the data to the file. + _, err = io.Copy(fd, result.Response.Body) + fd.Close() + if err != nil { + return err + } + + // Compare the CRC value. If CRC values do not match, return error. + hasRange, _, _ := isOptionSet(options, HTTPHeaderRange) + encodeOpt, _ := findOption(options, HTTPHeaderAcceptEncoding, nil) + acceptEncoding := "" + if encodeOpt != nil { + acceptEncoding = encodeOpt.(string) + } + + if bucket.getConfig().IsEnableCRC && !hasRange && acceptEncoding != "gzip" { + result.Response.ClientCRC = result.ClientCRC.Sum64() + err = checkCRC(result.Response, "GetObjectToFileWithURL") + if err != nil { + os.Remove(tempFilePath) + return err + } + } + + return os.Rename(tempFilePath, filePath) +} + +// DoGetObjectWithURL is the actual API that downloads the file with the signed URL. +// +// signedURL the signed URL. +// options the options for getting object. Check out parameter options in GetObject for the reference. +// +// GetObjectResult the result object when the error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) DoGetObjectWithURL(signedURL string, options []Option) (*GetObjectResult, error) { + params, _ := getRawParams(options) + resp, err := bucket.doURL("GET", signedURL, params, options, nil, nil) + if err != nil { + return nil, err + } + + result := &GetObjectResult{ + Response: resp, + } + + // CRC + var crcCalc hash.Hash64 + hasRange, _, _ := isOptionSet(options, HTTPHeaderRange) + if bucket.getConfig().IsEnableCRC && !hasRange { + crcCalc = crc64.New(crcTable()) + result.ServerCRC = resp.ServerCRC + result.ClientCRC = crcCalc + } + + // Progress + listener := getProgressListener(options) + + contentLen, _ := strconv.ParseInt(resp.Headers.Get(HTTPHeaderContentLength), 10, 64) + resp.Body = TeeReader(resp.Body, crcCalc, contentLen, listener, nil) + + return result, nil +} + +// +// ProcessObject apply process on the specified image file. +// +// The supported process includes resize, rotate, crop, watermark, format, +// udf, customized style, etc. +// +// +// objectKey object key to process. +// process process string, such as "image/resize,w_100|sys/saveas,o_dGVzdC5qcGc,b_dGVzdA" +// +// error it's nil if no error, otherwise it's an error object. +// +func (bucket Bucket) ProcessObject(objectKey string, process string) (ProcessObjectResult, error) { + var out ProcessObjectResult + params := map[string]interface{}{} + params["x-oss-process"] = nil + processData := fmt.Sprintf("%v=%v", "x-oss-process", process) + data := strings.NewReader(processData) + resp, err := bucket.do("POST", objectKey, params, nil, data, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = jsonUnmarshal(resp.Body, &out) + return out, err +} + +// Private +func (bucket Bucket) do(method, objectName string, params map[string]interface{}, options []Option, + data io.Reader, listener ProgressListener) (*Response, error) { + headers := make(map[string]string) + err := handleOptions(headers, options) + if err != nil { + return nil, err + } + return bucket.Client.Conn.Do(method, bucket.BucketName, objectName, + params, headers, data, 0, listener) +} + +func (bucket Bucket) doURL(method HTTPMethod, signedURL string, params map[string]interface{}, options []Option, + data io.Reader, listener ProgressListener) (*Response, error) { + headers := make(map[string]string) + err := handleOptions(headers, options) + if err != nil { + return nil, err + } + return bucket.Client.Conn.DoURL(method, signedURL, headers, data, 0, listener) +} + +func (bucket Bucket) getConfig() *Config { + return bucket.Client.Config +} + +func addContentType(options []Option, keys ...string) []Option { + typ := TypeByExtension("") + for _, key := range keys { + typ = TypeByExtension(key) + if typ != "" { + break + } + } + + if typ == "" { + typ = "application/octet-stream" + } + + opts := []Option{ContentType(typ)} + opts = append(opts, options...) + + return opts +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_test.go new file mode 100644 index 000000000..90e5fdd3a --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/bucket_test.go @@ -0,0 +1,2741 @@ +// Bucket test + +package oss + +import ( + "bytes" + "encoding/base64" + "errors" + "fmt" + "io" + "io/ioutil" + "math/rand" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/baiyubin/aliyun-sts-go-sdk/sts" + + . "gopkg.in/check.v1" +) + +type OssBucketSuite struct { + client *Client + bucket *Bucket + archiveBucket *Bucket +} + +var _ = Suite(&OssBucketSuite{}) + +var ( + pastDate = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + futureDate = time.Date(2049, time.January, 10, 23, 0, 0, 0, time.UTC) +) + +// SetUpSuite runs once when the suite starts running. +func (s *OssBucketSuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + s.client = client + + s.client.CreateBucket(bucketName) + + err = s.client.CreateBucket(archiveBucketName, StorageClass(StorageArchive)) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + + bucket, err := s.client.Bucket(bucketName) + c.Assert(err, IsNil) + s.bucket = bucket + + archiveBucket, err := s.client.Bucket(archiveBucketName) + c.Assert(err, IsNil) + s.archiveBucket = archiveBucket + + testLogger.Println("test bucket started") +} + +// TearDownSuite runs before each test or benchmark starts running. +func (s *OssBucketSuite) TearDownSuite(c *C) { + for _, bucket := range []*Bucket{s.bucket, s.archiveBucket} { + // Delete multipart + lmu, err := bucket.ListMultipartUploads() + c.Assert(err, IsNil) + + for _, upload := range lmu.Uploads { + imur := InitiateMultipartUploadResult{Bucket: bucketName, Key: upload.Key, UploadID: upload.UploadID} + err = bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } + + // Delete objects + lor, err := bucket.ListObjects() + c.Assert(err, IsNil) + + for _, object := range lor.Objects { + err = bucket.DeleteObject(object.Key) + c.Assert(err, IsNil) + } + } + + testLogger.Println("test bucket completed") +} + +// SetUpTest runs after each test or benchmark runs. +func (s *OssBucketSuite) SetUpTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) +} + +// TearDownTest runs once after all tests or benchmarks have finished running. +func (s *OssBucketSuite) TearDownTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".txt") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".temp") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".txt1") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".txt2") + c.Assert(err, IsNil) +} + +// TestPutObject +func (s *OssBucketSuite) TestPutObject(c *C) { + objectName := objectNamePrefix + "tpo" + objectValue := "大江东去,浪淘尽,千古风流人物。 故垒西边,人道是、三国周郎赤壁。 乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。" + + "遥想公谨当年,小乔初嫁了,雄姿英发。 羽扇纶巾,谈笑间、樯橹灰飞烟灭。故国神游,多情应笑我,早生华发,人生如梦,一尊还酹江月。" + + // Put string + err := s.bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Check + body, err := s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + acl, err := s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + testLogger.Println("aclRes:", acl) + c.Assert(acl.ACL, Equals, "default") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Put bytes + err = s.bucket.PutObject(objectName, bytes.NewReader([]byte(objectValue))) + c.Assert(err, IsNil) + + // Check + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Put file + err = createFileAndWrite(objectName+".txt", []byte(objectValue)) + c.Assert(err, IsNil) + fd, err := os.Open(objectName + ".txt") + c.Assert(err, IsNil) + + err = s.bucket.PutObject(objectName, fd) + c.Assert(err, IsNil) + os.Remove(objectName + ".txt") + + // Check + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Put with properties + objectName = objectNamePrefix + "tpox" + options := []Option{ + Expires(futureDate), + ObjectACL(ACLPublicRead), + Meta("myprop", "mypropval"), + } + err = s.bucket.PutObject(objectName, strings.NewReader(objectValue), options...) + c.Assert(err, IsNil) + + // Check + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + acl, err = s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectACL:", acl) + c.Assert(acl.ACL, Equals, string(ACLPublicRead)) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +func (s *OssBucketSuite) TestSignURL(c *C) { + objectName := objectNamePrefix + randStr(5) + objectValue := randStr(20) + + filePath := randLowStr(10) + content := "复写object" + createFile(filePath, content, c) + + notExistfilePath := randLowStr(10) + os.Remove(notExistfilePath) + + // Sign URL for put + str, err := s.bucket.SignURL(objectName, HTTPPut, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Error put object with URL + err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue), ContentType("image/tiff")) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch") + + err = s.bucket.PutObjectFromFileWithURL(str, filePath, ContentType("image/tiff")) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch") + + // Put object with URL + err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + acl, err := s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, "default") + + // Get object meta + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get(HTTPHeaderContentType), Equals, "application/octet-stream") + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "") + + // Sign URL for function GetObjectWithURL + str, err = s.bucket.SignURL(objectName, HTTPGet, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Get object with URL + body, err := s.bucket.GetObjectWithURL(str) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Sign URL for function PutObjectWithURL + options := []Option{ + ObjectACL(ACLPublicRead), + Meta("myprop", "mypropval"), + ContentType("image/tiff"), + ResponseContentEncoding("deflate"), + } + str, err = s.bucket.SignURL(objectName, HTTPPut, 60, options...) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Put object with URL from file + // Without option, error + err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch") + + err = s.bucket.PutObjectFromFileWithURL(str, filePath) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch") + + // With option, error file + err = s.bucket.PutObjectFromFileWithURL(str, notExistfilePath, options...) + c.Assert(err, NotNil) + + // With option + err = s.bucket.PutObjectFromFileWithURL(str, filePath, options...) + c.Assert(err, IsNil) + + // Get object meta + meta, err = s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + c.Assert(meta.Get(HTTPHeaderContentType), Equals, "image/tiff") + + acl, err = s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, string(ACLPublicRead)) + + // Sign URL for function GetObjectToFileWithURL + str, err = s.bucket.SignURL(objectName, HTTPGet, 60) + c.Assert(err, IsNil) + + // Get object to file with URL + newFile := randStr(10) + err = s.bucket.GetObjectToFileWithURL(str, newFile) + c.Assert(err, IsNil) + eq, err := compareFiles(filePath, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + os.Remove(newFile) + + // Get object to file error + err = s.bucket.GetObjectToFileWithURL(str, newFile, options...) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch") + _, err = os.Stat(newFile) + c.Assert(err, NotNil) + + // Get object error + body, err = s.bucket.GetObjectWithURL(str, options...) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch") + c.Assert(body, IsNil) + + // Sign URL for function GetObjectToFileWithURL + options = []Option{ + Expires(futureDate), + ObjectACL(ACLPublicRead), + Meta("myprop", "mypropval"), + ContentType("image/tiff"), + ResponseContentEncoding("deflate"), + } + str, err = s.bucket.SignURL(objectName, HTTPGet, 60, options...) + c.Assert(err, IsNil) + + // Get object to file with URL and options + err = s.bucket.GetObjectToFileWithURL(str, newFile, options...) + c.Assert(err, IsNil) + eq, err = compareFiles(filePath, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + os.Remove(newFile) + + // Get object to file error + err = s.bucket.GetObjectToFileWithURL(str, newFile) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch") + _, err = os.Stat(newFile) + c.Assert(err, NotNil) + + // Get object error + body, err = s.bucket.GetObjectWithURL(str) + c.Assert(err, NotNil) + c.Assert(err.(ServiceError).Code, Equals, "SignatureDoesNotMatch") + c.Assert(body, IsNil) + + err = s.bucket.PutObjectFromFile(objectName, "../sample/The Go Programming Language.html") + c.Assert(err, IsNil) + str, err = s.bucket.SignURL(objectName, HTTPGet, 3600, AcceptEncoding("gzip")) + c.Assert(err, IsNil) + s.bucket.GetObjectToFileWithURL(str, newFile) + c.Assert(err, IsNil) + + os.Remove(filePath) + os.Remove(newFile) + + // Sign URL error + str, err = s.bucket.SignURL(objectName, HTTPGet, -1) + c.Assert(err, NotNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Invalid URL parse + str = randStr(20) + + err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, NotNil) + + err = s.bucket.GetObjectToFileWithURL(str, newFile) + c.Assert(err, NotNil) +} + +func (s *OssBucketSuite) TestSignURLWithEscapedKey(c *C) { + // Key with '/' + objectName := "zyimg/86/e8/653b5dc97bb0022051a84c632bc4" + objectValue := "弃我去者,昨日之日不可留;乱我心者,今日之日多烦忧。长风万里送秋雁,对此可以酣高楼。蓬莱文章建安骨,中间小谢又清发。" + + "俱怀逸兴壮思飞,欲上青天揽明月。抽刀断水水更流,举杯销愁愁更愁。人生在世不称意,明朝散发弄扁舟。" + + // Sign URL for function PutObjectWithURL + str, err := s.bucket.SignURL(objectName, HTTPPut, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Put object with URL + err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Sign URL for function GetObjectWithURL + str, err = s.bucket.SignURL(objectName, HTTPGet, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Get object with URL + body, err := s.bucket.GetObjectWithURL(str) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Key with escaped chars + objectName = "<>[]()`?.,!@#$%^&'/*-_=+~:;" + + // Sign URL for funciton PutObjectWithURL + str, err = s.bucket.SignURL(objectName, HTTPPut, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Put object with URL + err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Sign URL for function GetObjectWithURL + str, err = s.bucket.SignURL(objectName, HTTPGet, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Get object with URL + body, err = s.bucket.GetObjectWithURL(str) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Key with Chinese chars + objectName = "风吹柳花满店香,吴姬压酒劝客尝。金陵子弟来相送,欲行不行各尽觞。请君试问东流水,别意与之谁短长。" + + // Sign URL for function PutObjectWithURL + str, err = s.bucket.SignURL(objectName, HTTPPut, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Put object with URL + err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Sign URL for get function GetObjectWithURL + str, err = s.bucket.SignURL(objectName, HTTPGet, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Get object with URL + body, err = s.bucket.GetObjectWithURL(str) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Key + objectName = "test/此情无计可消除/才下眉头/却上 心头/。,;:‘’“”?()『』【】《》!@#¥%……&×/test+ =-_*&^%$#@!`~[]{}()<>|\\/?.,;.txt" + + // Sign URL for function PutObjectWithURL + str, err = s.bucket.SignURL(objectName, HTTPPut, 60) + c.Assert(err, IsNil) + + // Put object with URL + err = s.bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Sign URL for function GetObjectWithURL + str, err = s.bucket.SignURL(objectName, HTTPGet, 60) + c.Assert(err, IsNil) + + // Get object with URL + body, err = s.bucket.GetObjectWithURL(str) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Put object + err = s.bucket.PutObject(objectName, bytes.NewReader([]byte(objectValue))) + c.Assert(err, IsNil) + + // Get object + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Delete object + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +func (s *OssBucketSuite) TestSignURLWithEscapedKeyAndPorxy(c *C) { + // Key with '/' + objectName := "zyimg/86/e8/653b5dc97bb0022051a84c632bc4" + objectValue := "弃我去者,昨日之日不可留;乱我心者,今日之日多烦忧。长风万里送秋雁,对此可以酣高楼。蓬莱文章建安骨,中间小谢又清发。" + + "俱怀逸兴壮思飞,欲上青天揽明月。抽刀断水水更流,举杯销愁愁更愁。人生在世不称意,明朝散发弄扁舟。" + + client, err := New(endpoint, accessID, accessKey, AuthProxy(proxyHost, proxyUser, proxyPasswd)) + bucket, err := client.Bucket(bucketName) + + // Sign URL for put + str, err := bucket.SignURL(objectName, HTTPPut, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Put object with URL + err = bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Sign URL for function GetObjectWithURL + str, err = bucket.SignURL(objectName, HTTPGet, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Get object with URL + body, err := bucket.GetObjectWithURL(str) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Key with Chinese chars + objectName = "test/此情无计可消除/才下眉头/却上 心头/。,;:‘’“”?()『』【】《》!@#¥%……&×/test+ =-_*&^%$#@!`~[]{}()<>|\\/?.,;.txt" + + // Sign URL for function PutObjectWithURL + str, err = bucket.SignURL(objectName, HTTPPut, 60) + c.Assert(err, IsNil) + + // Put object with URL + err = bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Sign URL for function GetObjectWithURL + str, err = bucket.SignURL(objectName, HTTPGet, 60) + c.Assert(err, IsNil) + + // Get object with URL + body, err = bucket.GetObjectWithURL(str) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Put object + err = bucket.PutObject(objectName, bytes.NewReader([]byte(objectValue))) + c.Assert(err, IsNil) + + // Get object + body, err = bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Delete object + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestPutObjectType +func (s *OssBucketSuite) TestPutObjectType(c *C) { + objectName := objectNamePrefix + "tptt" + objectValue := "乱石穿空,惊涛拍岸,卷起千堆雪。 江山如画,一时多少豪杰。" + + // Put + err := s.bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Check + time.Sleep(time.Second) + body, err := s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("Content-Type"), Equals, "application/octet-stream") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Put + err = s.bucket.PutObject(objectName+".txt", strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + meta, err = s.bucket.GetObjectDetailedMeta(objectName + ".txt") + c.Assert(err, IsNil) + c.Assert(meta.Get("Content-Type"), Equals, "text/plain; charset=utf-8") + + err = s.bucket.DeleteObject(objectName + ".txt") + c.Assert(err, IsNil) + + // Put + err = s.bucket.PutObject(objectName+".apk", strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + meta, err = s.bucket.GetObjectDetailedMeta(objectName + ".apk") + c.Assert(err, IsNil) + c.Assert(meta.Get("Content-Type"), Equals, "application/vnd.android.package-archive") + + err = s.bucket.DeleteObject(objectName + ".txt") + c.Assert(err, IsNil) +} + +// TestPutObject +func (s *OssBucketSuite) TestPutObjectKeyChars(c *C) { + objectName := objectNamePrefix + "tpokc" + objectValue := "白日依山尽,黄河入海流。欲穷千里目,更上一层楼。" + + // Put + objectKey := objectName + "十步杀一人,千里不留行。事了拂衣去,深藏身与名" + err := s.bucket.PutObject(objectKey, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Check + body, err := s.bucket.GetObject(objectKey) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectKey) + c.Assert(err, IsNil) + + // Put + objectKey = objectName + "ごきげん如何ですかおれの顔をよく拝んでおけ" + err = s.bucket.PutObject(objectKey, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Check + body, err = s.bucket.GetObject(objectKey) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectKey) + c.Assert(err, IsNil) + + // Put + objectKey = objectName + "~!@#$%^&*()_-+=|\\[]{}<>,./?" + err = s.bucket.PutObject(objectKey, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Check + body, err = s.bucket.GetObject(objectKey) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectKey) + c.Assert(err, IsNil) + + // Put + objectKey = "go/中国 日本 +-#&=*" + err = s.bucket.PutObject(objectKey, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Check + body, err = s.bucket.GetObject(objectKey) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectKey) + c.Assert(err, IsNil) +} + +// TestPutObjectNegative +func (s *OssBucketSuite) TestPutObjectNegative(c *C) { + objectName := objectNamePrefix + "tpon" + objectValue := "大江东去,浪淘尽,千古风流人物。 " + + // Put + objectName = objectNamePrefix + "tpox" + err := s.bucket.PutObject(objectName, strings.NewReader(objectValue), + Meta("meta-my", "myprop")) + c.Assert(err, IsNil) + + // Check meta + body, err := s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("X-Oss-Meta-My"), Not(Equals), "myprop") + c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Invalid option + err = s.bucket.PutObject(objectName, strings.NewReader(objectValue), + IfModifiedSince(pastDate)) + c.Assert(err, NotNil) + + err = s.bucket.PutObjectFromFile(objectName, "bucket.go", IfModifiedSince(pastDate)) + c.Assert(err, NotNil) + + err = s.bucket.PutObjectFromFile(objectName, "/tmp/xxx") + c.Assert(err, NotNil) +} + +// TestPutObjectFromFile +func (s *OssBucketSuite) TestPutObjectFromFile(c *C) { + objectName := objectNamePrefix + "tpoff" + localFile := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "newpic11.jpg" + + // Put + err := s.bucket.PutObjectFromFile(objectName, localFile) + c.Assert(err, IsNil) + + // Check + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + eq, err := compareFiles(localFile, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + acl, err := s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + testLogger.Println("aclRes:", acl) + c.Assert(acl.ACL, Equals, "default") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Put with properties + options := []Option{ + Expires(futureDate), + ObjectACL(ACLPublicRead), + Meta("myprop", "mypropval"), + } + os.Remove(newFile) + err = s.bucket.PutObjectFromFile(objectName, localFile, options...) + c.Assert(err, IsNil) + + // Check + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + eq, err = compareFiles(localFile, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + acl, err = s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectACL:", acl) + c.Assert(acl.ACL, Equals, string(ACLPublicRead)) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + os.Remove(newFile) +} + +// TestPutObjectFromFile +func (s *OssBucketSuite) TestPutObjectFromFileType(c *C) { + objectName := objectNamePrefix + "tpoffwm" + localFile := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "newpic11.jpg" + + // Put + err := s.bucket.PutObjectFromFile(objectName, localFile) + c.Assert(err, IsNil) + + // Check + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + eq, err := compareFiles(localFile, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("Content-Type"), Equals, "image/jpeg") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + os.Remove(newFile) +} + +// TestGetObject +func (s *OssBucketSuite) TestGetObject(c *C) { + objectName := objectNamePrefix + "tgo" + objectValue := "长忆观潮,满郭人争江上望。来疑沧海尽成空,万面鼓声中。弄潮儿向涛头立,手把红旗旗不湿。别来几向梦中看,梦觉尚心寒。" + + // Put + err := s.bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Check + body, err := s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + data, err := ioutil.ReadAll(body) + body.Close() + str := string(data) + c.Assert(str, Equals, objectValue) + testLogger.Println("GetObjec:", str) + + // Range + var subObjectValue = string(([]byte(objectValue))[15:36]) + body, err = s.bucket.GetObject(objectName, Range(15, 35)) + c.Assert(err, IsNil) + data, err = ioutil.ReadAll(body) + body.Close() + str = string(data) + c.Assert(str, Equals, subObjectValue) + testLogger.Println("GetObject:", str, ",", subObjectValue) + + // If-Modified-Since + _, err = s.bucket.GetObject(objectName, IfModifiedSince(futureDate)) + c.Assert(err, NotNil) + + // If-Unmodified-Since + body, err = s.bucket.GetObject(objectName, IfUnmodifiedSince(futureDate)) + c.Assert(err, IsNil) + data, err = ioutil.ReadAll(body) + body.Close() + c.Assert(string(data), Equals, objectValue) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + + // If-Match + body, err = s.bucket.GetObject(objectName, IfMatch(meta.Get("Etag"))) + c.Assert(err, IsNil) + data, err = ioutil.ReadAll(body) + body.Close() + c.Assert(string(data), Equals, objectValue) + + // If-None-Match + _, err = s.bucket.GetObject(objectName, IfNoneMatch(meta.Get("Etag"))) + c.Assert(err, NotNil) + + // process + err = s.bucket.PutObjectFromFile(objectName, "../sample/BingWallpaper-2015-11-07.jpg") + c.Assert(err, IsNil) + _, err = s.bucket.GetObject(objectName, Process("image/format,png")) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestGetObjectNegative +func (s *OssBucketSuite) TestGetObjectToWriterNegative(c *C) { + objectName := objectNamePrefix + "tgotwn" + objectValue := "长忆观潮,满郭人争江上望。" + + // Object not exist + _, err := s.bucket.GetObject("NotExist") + c.Assert(err, NotNil) + + // Constraint invalid + err = s.bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Out of range + _, err = s.bucket.GetObject(objectName, Range(15, 1000)) + c.Assert(err, IsNil) + + // Not exist + err = s.bucket.GetObjectToFile(objectName, "/root/123abc9874") + c.Assert(err, NotNil) + + // Invalid option + _, err = s.bucket.GetObject(objectName, ACL(ACLPublicRead)) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(objectName, "newpic15.jpg", ACL(ACLPublicRead)) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestGetObjectToFile +func (s *OssBucketSuite) TestGetObjectToFile(c *C) { + objectName := objectNamePrefix + "tgotf" + objectValue := "江南好,风景旧曾谙;日出江花红胜火,春来江水绿如蓝。能不忆江南?江南忆,最忆是杭州;山寺月中寻桂子,郡亭枕上看潮头。何日更重游!" + newFile := "newpic15.jpg" + + // Put + var val = []byte(objectValue) + err := s.bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Check + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + eq, err := compareFileData(newFile, val) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + os.Remove(newFile) + + // Range + err = s.bucket.GetObjectToFile(objectName, newFile, Range(15, 35)) + c.Assert(err, IsNil) + eq, err = compareFileData(newFile, val[15:36]) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + os.Remove(newFile) + + err = s.bucket.GetObjectToFile(objectName, newFile, NormalizedRange("15-35")) + c.Assert(err, IsNil) + eq, err = compareFileData(newFile, val[15:36]) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + os.Remove(newFile) + + err = s.bucket.GetObjectToFile(objectName, newFile, NormalizedRange("15-")) + c.Assert(err, IsNil) + eq, err = compareFileData(newFile, val[15:]) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + os.Remove(newFile) + + err = s.bucket.GetObjectToFile(objectName, newFile, NormalizedRange("-10")) + c.Assert(err, IsNil) + eq, err = compareFileData(newFile, val[(len(val)-10):len(val)]) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + os.Remove(newFile) + + // If-Modified-Since + err = s.bucket.GetObjectToFile(objectName, newFile, IfModifiedSince(futureDate)) + c.Assert(err, NotNil) + + // If-Unmodified-Since + err = s.bucket.GetObjectToFile(objectName, newFile, IfUnmodifiedSince(futureDate)) + c.Assert(err, IsNil) + eq, err = compareFileData(newFile, val) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + os.Remove(newFile) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + + // If-Match + err = s.bucket.GetObjectToFile(objectName, newFile, IfMatch(meta.Get("Etag"))) + c.Assert(err, IsNil) + eq, err = compareFileData(newFile, val) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // If-None-Match + err = s.bucket.GetObjectToFile(objectName, newFile, IfNoneMatch(meta.Get("Etag"))) + c.Assert(err, NotNil) + + // Accept-Encoding:gzip + err = s.bucket.PutObjectFromFile(objectName, "../sample/The Go Programming Language.html") + c.Assert(err, IsNil) + err = s.bucket.GetObjectToFile(objectName, newFile, AcceptEncoding("gzip")) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestListObjects +func (s *OssBucketSuite) TestListObjects(c *C) { + objectName := objectNamePrefix + "tlo" + + // List empty bucket + lor, err := s.bucket.ListObjects() + c.Assert(err, IsNil) + left := len(lor.Objects) + + // Put three objects + err = s.bucket.PutObject(objectName+"1", strings.NewReader("")) + c.Assert(err, IsNil) + err = s.bucket.PutObject(objectName+"2", strings.NewReader("")) + c.Assert(err, IsNil) + err = s.bucket.PutObject(objectName+"3", strings.NewReader("")) + c.Assert(err, IsNil) + + // List + lor, err = s.bucket.ListObjects() + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, left+3) + + // List with prefix + lor, err = s.bucket.ListObjects(Prefix(objectName + "2")) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 1) + + lor, err = s.bucket.ListObjects(Prefix(objectName + "22")) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 0) + + // List with max keys + lor, err = s.bucket.ListObjects(Prefix(objectName), MaxKeys(2)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 2) + + // List with marker + lor, err = s.bucket.ListObjects(Marker(objectName+"1"), MaxKeys(1)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 1) + + err = s.bucket.DeleteObject(objectName + "1") + c.Assert(err, IsNil) + err = s.bucket.DeleteObject(objectName + "2") + c.Assert(err, IsNil) + err = s.bucket.DeleteObject(objectName + "3") + c.Assert(err, IsNil) +} + +// TestListObjects +func (s *OssBucketSuite) TestListObjectsEncodingType(c *C) { + objectName := objectNamePrefix + "床前明月光,疑是地上霜。举头望明月,低头思故乡。" + "tloet" + + for i := 0; i < 10; i++ { + err := s.bucket.PutObject(objectName+strconv.Itoa(i), strings.NewReader("")) + c.Assert(err, IsNil) + } + + lor, err := s.bucket.ListObjects(Prefix(objectNamePrefix + "床前明月光,")) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 10) + + lor, err = s.bucket.ListObjects(Prefix(objectNamePrefix + "床前明月光,")) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 10) + + lor, err = s.bucket.ListObjects(Marker(objectNamePrefix + "床前明月光,疑是地上霜。举头望明月,低头思故乡。")) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 10) + + lor, err = s.bucket.ListObjects(Prefix(objectNamePrefix + "床前明月光")) + c.Assert(err, IsNil) + for i, obj := range lor.Objects { + c.Assert(obj.Key, Equals, objectNamePrefix+"床前明月光,疑是地上霜。举头望明月,低头思故乡。tloet"+strconv.Itoa(i)) + } + + for i := 0; i < 10; i++ { + err = s.bucket.DeleteObject(objectName + strconv.Itoa(i)) + c.Assert(err, IsNil) + } + + // Special characters + objectName = "go go ` ~ ! @ # $ % ^ & * () - _ + =[] {} \\ | < > , . ? / 0" + err = s.bucket.PutObject(objectName, strings.NewReader("明月几时有,把酒问青天")) + c.Assert(err, IsNil) + + lor, err = s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 1) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + objectName = "go/中国 日本 +-#&=*" + err = s.bucket.PutObject(objectName, strings.NewReader("明月几时有,把酒问青天")) + c.Assert(err, IsNil) + + lor, err = s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 1) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestIsBucketExist +func (s *OssBucketSuite) TestIsObjectExist(c *C) { + objectName := objectNamePrefix + "tibe" + + // Put three objects + err := s.bucket.PutObject(objectName+"1", strings.NewReader("")) + c.Assert(err, IsNil) + err = s.bucket.PutObject(objectName+"11", strings.NewReader("")) + c.Assert(err, IsNil) + err = s.bucket.PutObject(objectName+"111", strings.NewReader("")) + c.Assert(err, IsNil) + + // Exist + exist, err := s.bucket.IsObjectExist(objectName + "11") + c.Assert(err, IsNil) + c.Assert(exist, Equals, true) + + exist, err = s.bucket.IsObjectExist(objectName + "1") + c.Assert(err, IsNil) + c.Assert(exist, Equals, true) + + exist, err = s.bucket.IsObjectExist(objectName + "111") + c.Assert(err, IsNil) + c.Assert(exist, Equals, true) + + // Not exist + exist, err = s.bucket.IsObjectExist(objectName + "1111") + c.Assert(err, IsNil) + c.Assert(exist, Equals, false) + + exist, err = s.bucket.IsObjectExist(objectName) + c.Assert(err, IsNil) + c.Assert(exist, Equals, false) + + err = s.bucket.DeleteObject(objectName + "1") + c.Assert(err, IsNil) + err = s.bucket.DeleteObject(objectName + "11") + c.Assert(err, IsNil) + err = s.bucket.DeleteObject(objectName + "111") + c.Assert(err, IsNil) +} + +// TestDeleteObject +func (s *OssBucketSuite) TestDeleteObject(c *C) { + objectName := objectNamePrefix + "tdo" + + err := s.bucket.PutObject(objectName, strings.NewReader("")) + c.Assert(err, IsNil) + + lor, err := s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 1) + + // Delete + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Duplicate delete + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + lor, err = s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 0) +} + +// TestDeleteObjects +func (s *OssBucketSuite) TestDeleteObjects(c *C) { + objectName := objectNamePrefix + "tdos" + + // Delete objects + err := s.bucket.PutObject(objectName, strings.NewReader("")) + c.Assert(err, IsNil) + + res, err := s.bucket.DeleteObjects([]string{objectName}) + c.Assert(err, IsNil) + c.Assert(len(res.DeletedObjects), Equals, 1) + + lor, err := s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 0) + + // Delete objects + err = s.bucket.PutObject(objectName+"1", strings.NewReader("")) + c.Assert(err, IsNil) + + err = s.bucket.PutObject(objectName+"2", strings.NewReader("")) + c.Assert(err, IsNil) + + res, err = s.bucket.DeleteObjects([]string{objectName + "1", objectName + "2"}) + c.Assert(err, IsNil) + c.Assert(len(res.DeletedObjects), Equals, 2) + + lor, err = s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 0) + + // Delete 0 + _, err = s.bucket.DeleteObjects([]string{}) + c.Assert(err, NotNil) + + // DeleteObjectsQuiet + err = s.bucket.PutObject(objectName+"1", strings.NewReader("")) + c.Assert(err, IsNil) + + err = s.bucket.PutObject(objectName+"2", strings.NewReader("")) + c.Assert(err, IsNil) + + res, err = s.bucket.DeleteObjects([]string{objectName + "1", objectName + "2"}, + DeleteObjectsQuiet(false)) + c.Assert(err, IsNil) + c.Assert(len(res.DeletedObjects), Equals, 2) + + lor, err = s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 0) + + // DeleteObjectsQuiet + err = s.bucket.PutObject(objectName+"1", strings.NewReader("")) + c.Assert(err, IsNil) + + err = s.bucket.PutObject(objectName+"2", strings.NewReader("")) + c.Assert(err, IsNil) + + res, err = s.bucket.DeleteObjects([]string{objectName + "1", objectName + "2"}, + DeleteObjectsQuiet(true)) + c.Assert(err, IsNil) + c.Assert(len(res.DeletedObjects), Equals, 0) + + lor, err = s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, 0) + + // EncodingType + err = s.bucket.PutObject("中国人", strings.NewReader("")) + c.Assert(err, IsNil) + + res, err = s.bucket.DeleteObjects([]string{"中国人"}) + c.Assert(err, IsNil) + c.Assert(len(res.DeletedObjects), Equals, 1) + c.Assert(res.DeletedObjects[0], Equals, "中国人") + + // EncodingType + err = s.bucket.PutObject("中国人", strings.NewReader("")) + c.Assert(err, IsNil) + + res, err = s.bucket.DeleteObjects([]string{"中国人"}, DeleteObjectsQuiet(false)) + c.Assert(err, IsNil) + c.Assert(len(res.DeletedObjects), Equals, 1) + c.Assert(res.DeletedObjects[0], Equals, "中国人") + + // EncodingType + err = s.bucket.PutObject("中国人", strings.NewReader("")) + c.Assert(err, IsNil) + + res, err = s.bucket.DeleteObjects([]string{"中国人"}, DeleteObjectsQuiet(true)) + c.Assert(err, IsNil) + c.Assert(len(res.DeletedObjects), Equals, 0) + + // Special characters + key := "A ' < > \" & ~ ` ! @ # $ % ^ & * ( ) [] {} - _ + = / | \\ ? . , : ; A" + err = s.bucket.PutObject(key, strings.NewReader("value")) + c.Assert(err, IsNil) + + _, err = s.bucket.DeleteObjects([]string{key}) + c.Assert(err, IsNil) + + ress, err := s.bucket.ListObjects(Prefix(key)) + c.Assert(err, IsNil) + c.Assert(len(ress.Objects), Equals, 0) + + // Not exist + _, err = s.bucket.DeleteObjects([]string{"NotExistObject"}) + c.Assert(err, IsNil) +} + +// TestSetObjectMeta +func (s *OssBucketSuite) TestSetObjectMeta(c *C) { + objectName := objectNamePrefix + "tsom" + + err := s.bucket.PutObject(objectName, strings.NewReader("")) + c.Assert(err, IsNil) + + err = s.bucket.SetObjectMeta(objectName, + Expires(futureDate), + Meta("myprop", "mypropval")) + c.Assert(err, IsNil) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("Meta:", meta) + c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat)) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + + acl, err := s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, "default") + + // Invalid option + err = s.bucket.SetObjectMeta(objectName, AcceptEncoding("url")) + c.Assert(err, IsNil) + + // Invalid option value + err = s.bucket.SetObjectMeta(objectName, ServerSideEncryption("invalid")) + c.Assert(err, NotNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Not exist + err = s.bucket.SetObjectMeta(objectName, Expires(futureDate)) + c.Assert(err, NotNil) +} + +// TestGetObjectMeta +func (s *OssBucketSuite) TestGetObjectMeta(c *C) { + objectName := objectNamePrefix + "tgom" + + // Put + err := s.bucket.PutObject(objectName, strings.NewReader("")) + c.Assert(err, IsNil) + + meta, err := s.bucket.GetObjectMeta(objectName) + c.Assert(err, IsNil) + c.Assert(len(meta) > 0, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + _, err = s.bucket.GetObjectMeta("NotExistObject") + c.Assert(err, NotNil) +} + +// TestGetObjectDetailedMeta +func (s *OssBucketSuite) TestGetObjectDetailedMeta(c *C) { + objectName := objectNamePrefix + "tgodm" + + // Put + err := s.bucket.PutObject(objectName, strings.NewReader(""), + Expires(futureDate), Meta("myprop", "mypropval")) + c.Assert(err, IsNil) + + // Check + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat)) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + c.Assert(meta.Get("Content-Length"), Equals, "0") + c.Assert(len(meta.Get("Date")) > 0, Equals, true) + c.Assert(len(meta.Get("X-Oss-Request-Id")) > 0, Equals, true) + c.Assert(len(meta.Get("Last-Modified")) > 0, Equals, true) + + // IfModifiedSince/IfModifiedSince + _, err = s.bucket.GetObjectDetailedMeta(objectName, IfModifiedSince(futureDate)) + c.Assert(err, NotNil) + + meta, err = s.bucket.GetObjectDetailedMeta(objectName, IfUnmodifiedSince(futureDate)) + c.Assert(err, IsNil) + c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat)) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + + // IfMatch/IfNoneMatch + _, err = s.bucket.GetObjectDetailedMeta(objectName, IfNoneMatch(meta.Get("Etag"))) + c.Assert(err, NotNil) + + meta, err = s.bucket.GetObjectDetailedMeta(objectName, IfMatch(meta.Get("Etag"))) + c.Assert(err, IsNil) + c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat)) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + _, err = s.bucket.GetObjectDetailedMeta("NotExistObject") + c.Assert(err, NotNil) +} + +// TestSetAndGetObjectAcl +func (s *OssBucketSuite) TestSetAndGetObjectAcl(c *C) { + objectName := objectNamePrefix + "tsgba" + + err := s.bucket.PutObject(objectName, strings.NewReader("")) + c.Assert(err, IsNil) + + // Default + acl, err := s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, "default") + + // Set ACL_PUBLIC_RW + err = s.bucket.SetObjectACL(objectName, ACLPublicReadWrite) + c.Assert(err, IsNil) + + acl, err = s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite)) + + // Set ACL_PRIVATE + err = s.bucket.SetObjectACL(objectName, ACLPrivate) + c.Assert(err, IsNil) + + acl, err = s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, string(ACLPrivate)) + + // Set ACL_PUBLIC_R + err = s.bucket.SetObjectACL(objectName, ACLPublicRead) + c.Assert(err, IsNil) + + acl, err = s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, string(ACLPublicRead)) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestSetAndGetObjectAclNegative +func (s *OssBucketSuite) TestSetAndGetObjectAclNegative(c *C) { + objectName := objectNamePrefix + "tsgban" + + // Object not exist + err := s.bucket.SetObjectACL(objectName, ACLPublicRead) + c.Assert(err, NotNil) +} + +// TestCopyObject +func (s *OssBucketSuite) TestCopyObject(c *C) { + objectName := objectNamePrefix + "tco" + objectValue := "男儿何不带吴钩,收取关山五十州。请君暂上凌烟阁,若个书生万户侯?" + + err := s.bucket.PutObject(objectName, strings.NewReader(objectValue), + ACL(ACLPublicRead), Meta("my", "myprop")) + c.Assert(err, IsNil) + + // Copy + var objectNameDest = objectName + "dest" + _, err = s.bucket.CopyObject(objectName, objectNameDest) + c.Assert(err, IsNil) + + // Check + lor, err := s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + testLogger.Println("objects:", lor.Objects) + c.Assert(len(lor.Objects), Equals, 2) + + body, err := s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectNameDest) + c.Assert(err, IsNil) + + // Copy with constraints x-oss-copy-source-if-modified-since + _, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfModifiedSince(futureDate)) + c.Assert(err, NotNil) + testLogger.Println("CopyObject:", err) + + // Copy with constraints x-oss-copy-source-if-unmodified-since + _, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfUnmodifiedSince(futureDate)) + c.Assert(err, IsNil) + + // Check + lor, err = s.bucket.ListObjects(Prefix(objectName)) + c.Assert(err, IsNil) + testLogger.Println("objects:", lor.Objects) + c.Assert(len(lor.Objects), Equals, 2) + + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectNameDest) + c.Assert(err, IsNil) + + // Copy with constraints x-oss-copy-source-if-match + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + + _, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfMatch(meta.Get("Etag"))) + c.Assert(err, IsNil) + + // Check + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectNameDest) + c.Assert(err, IsNil) + + // Copy with constraints x-oss-copy-source-if-none-match + _, err = s.bucket.CopyObject(objectName, objectNameDest, CopySourceIfNoneMatch(meta.Get("Etag"))) + c.Assert(err, NotNil) + + // Copy with constraints x-oss-metadata-directive + _, err = s.bucket.CopyObject(objectName, objectNameDest, Meta("my", "mydestprop"), + MetadataDirective(MetaCopy)) + c.Assert(err, IsNil) + + // Check + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + destMeta, err := s.bucket.GetObjectDetailedMeta(objectNameDest) + c.Assert(err, IsNil) + c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop") + + acl, err := s.bucket.GetObjectACL(objectNameDest) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, "default") + + err = s.bucket.DeleteObject(objectNameDest) + c.Assert(err, IsNil) + + // Copy with constraints x-oss-metadata-directive and self defined dest object meta + options := []Option{ + ObjectACL(ACLPublicReadWrite), + Meta("my", "mydestprop"), + MetadataDirective(MetaReplace), + } + _, err = s.bucket.CopyObject(objectName, objectNameDest, options...) + c.Assert(err, IsNil) + + // Check + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + destMeta, err = s.bucket.GetObjectDetailedMeta(objectNameDest) + c.Assert(err, IsNil) + c.Assert(destMeta.Get("X-Oss-Meta-My"), Equals, "mydestprop") + + acl, err = s.bucket.GetObjectACL(objectNameDest) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite)) + + err = s.bucket.DeleteObject(objectNameDest) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestCopyObjectToOrFrom +func (s *OssBucketSuite) TestCopyObjectToOrFrom(c *C) { + objectName := objectNamePrefix + "tcotof" + randLowStr(5) + objectValue := "男儿何不带吴钩,收取关山五十州。请君暂上凌烟阁,若个书生万户侯?" + destBucketName := bucketName + "-dest" + randLowStr(5) + objectNameDest := objectName + "dest" + + err := s.client.CreateBucket(destBucketName) + c.Assert(err, IsNil) + + destBucket, err := s.client.Bucket(destBucketName) + c.Assert(err, IsNil) + + err = s.bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Copy from + _, err = destBucket.CopyObjectFrom(bucketName, objectName, objectNameDest) + c.Assert(err, IsNil) + + // Check + body, err := destBucket.GetObject(objectNameDest) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Copy to + _, err = destBucket.CopyObjectTo(bucketName, objectName, objectNameDest) + c.Assert(err, IsNil) + + // Check + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Clean + err = destBucket.DeleteObject(objectNameDest) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + err = s.client.DeleteBucket(destBucketName) + c.Assert(err, IsNil) +} + +// TestCopyObjectToOrFromNegative +func (s *OssBucketSuite) TestCopyObjectToOrFromNegative(c *C) { + objectName := objectNamePrefix + "tcotofn" + destBucket := bucketName + "-destn" + objectNameDest := objectName + "destn" + + // Object not exist + _, err := s.bucket.CopyObjectTo(bucketName, objectName, objectNameDest) + c.Assert(err, NotNil) + + // Bucket not exist + _, err = s.bucket.CopyObjectFrom(destBucket, objectNameDest, objectName) + c.Assert(err, NotNil) +} + +// TestAppendObject +func (s *OssBucketSuite) TestAppendObject(c *C) { + objectName := objectNamePrefix + "tao" + objectValue := "昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,却道海棠依旧。知否?知否?应是绿肥红瘦。" + var val = []byte(objectValue) + var localFile = "testx.txt" + var nextPos int64 + var midPos = 1 + rand.Intn(len(val)-1) + + var err = createFileAndWrite(localFile+"1", val[0:midPos]) + c.Assert(err, IsNil) + err = createFileAndWrite(localFile+"2", val[midPos:]) + c.Assert(err, IsNil) + + // String append + nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader("昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,"), nextPos) + c.Assert(err, IsNil) + nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader("却道海棠依旧。知否?知否?应是绿肥红瘦。"), nextPos) + c.Assert(err, IsNil) + + body, err := s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Byte append + nextPos = 0 + nextPos, err = s.bucket.AppendObject(objectName, bytes.NewReader(val[0:midPos]), nextPos) + c.Assert(err, IsNil) + nextPos, err = s.bucket.AppendObject(objectName, bytes.NewReader(val[midPos:]), nextPos) + c.Assert(err, IsNil) + + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // File append + options := []Option{ + ObjectACL(ACLPublicReadWrite), + Meta("my", "myprop"), + } + + fd, err := os.Open(localFile + "1") + c.Assert(err, IsNil) + defer fd.Close() + nextPos = 0 + nextPos, err = s.bucket.AppendObject(objectName, fd, nextPos, options...) + c.Assert(err, IsNil) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta, ",", nextPos) + c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Appendable") + c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop") + c.Assert(meta.Get("x-oss-Meta-Mine"), Equals, "") + c.Assert(meta.Get("X-Oss-Next-Append-Position"), Equals, strconv.FormatInt(nextPos, 10)) + + acl, err := s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectACL:", acl) + c.Assert(acl.ACL, Equals, string(ACLPublicReadWrite)) + + // Second append + options = []Option{ + ObjectACL(ACLPublicRead), + Meta("my", "myproptwo"), + Meta("mine", "mypropmine"), + } + fd, err = os.Open(localFile + "2") + c.Assert(err, IsNil) + defer fd.Close() + nextPos, err = s.bucket.AppendObject(objectName, fd, nextPos, options...) + c.Assert(err, IsNil) + + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + meta, err = s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta xxx:", meta) + c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Appendable") + c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop") + c.Assert(meta.Get("x-Oss-Meta-Mine"), Equals, "") + c.Assert(meta.Get("X-Oss-Next-Append-Position"), Equals, strconv.FormatInt(nextPos, 10)) + + acl, err = s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + c.Assert(acl.ACL, Equals, string(ACLPublicRead)) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestAppendObjectNegative +func (s *OssBucketSuite) TestAppendObjectNegative(c *C) { + objectName := objectNamePrefix + "taon" + nextPos := int64(0) + + nextPos, err := s.bucket.AppendObject(objectName, strings.NewReader("ObjectValue"), nextPos) + c.Assert(err, IsNil) + + nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader("ObjectValue"), 0) + c.Assert(err, NotNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestContentType +func (s *OssBucketSuite) TestAddContentType(c *C) { + opts := addContentType(nil, "abc.txt") + typ, err := findOption(opts, HTTPHeaderContentType, "") + c.Assert(err, IsNil) + c.Assert(typ, Equals, "text/plain; charset=utf-8") + + opts = addContentType(nil) + typ, err = findOption(opts, HTTPHeaderContentType, "") + c.Assert(err, IsNil) + c.Assert(len(opts), Equals, 1) + c.Assert(typ, Equals, "application/octet-stream") + + opts = addContentType(nil, "abc.txt", "abc.pdf") + typ, err = findOption(opts, HTTPHeaderContentType, "") + c.Assert(err, IsNil) + c.Assert(typ, Equals, "text/plain; charset=utf-8") + + opts = addContentType(nil, "abc", "abc.txt", "abc.pdf") + typ, err = findOption(opts, HTTPHeaderContentType, "") + c.Assert(err, IsNil) + c.Assert(typ, Equals, "text/plain; charset=utf-8") + + opts = addContentType(nil, "abc", "abc", "edf") + typ, err = findOption(opts, HTTPHeaderContentType, "") + c.Assert(err, IsNil) + c.Assert(typ, Equals, "application/octet-stream") + + opts = addContentType([]Option{Meta("meta", "my")}, "abc", "abc.txt", "abc.pdf") + typ, err = findOption(opts, HTTPHeaderContentType, "") + c.Assert(err, IsNil) + c.Assert(len(opts), Equals, 2) + c.Assert(typ, Equals, "text/plain; charset=utf-8") +} + +func (s *OssBucketSuite) TestGetConfig(c *C) { + client, err := New(endpoint, accessID, accessKey, UseCname(true), + Timeout(11, 12), SecurityToken("token"), EnableMD5(false)) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + c.Assert(bucket.getConfig().HTTPTimeout.ConnectTimeout, Equals, time.Second*11) + c.Assert(bucket.getConfig().HTTPTimeout.ReadWriteTimeout, Equals, time.Second*12) + c.Assert(bucket.getConfig().HTTPTimeout.HeaderTimeout, Equals, time.Second*12) + c.Assert(bucket.getConfig().HTTPTimeout.IdleConnTimeout, Equals, time.Second*12) + c.Assert(bucket.getConfig().HTTPTimeout.LongTimeout, Equals, time.Second*12*10) + + c.Assert(bucket.getConfig().SecurityToken, Equals, "token") + c.Assert(bucket.getConfig().IsCname, Equals, true) + c.Assert(bucket.getConfig().IsEnableMD5, Equals, false) +} + +func (s *OssBucketSuite) TestSTSToken(c *C) { + objectName := objectNamePrefix + "tst" + objectValue := "红藕香残玉簟秋。轻解罗裳,独上兰舟。云中谁寄锦书来?雁字回时,月满西楼。" + + stsClient := sts.NewClient(stsaccessID, stsaccessKey, stsARN, "oss_test_sess") + + resp, err := stsClient.AssumeRole(1800) + c.Assert(err, IsNil) + + client, err := New(endpoint, resp.Credentials.AccessKeyId, resp.Credentials.AccessKeySecret, + SecurityToken(resp.Credentials.SecurityToken)) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + // Put + err = bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Get + body, err := bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // List + lor, err := bucket.ListObjects() + c.Assert(err, IsNil) + testLogger.Println("Objects:", lor.Objects) + + // Put with URL + signedURL, err := bucket.SignURL(objectName, HTTPPut, 3600) + c.Assert(err, IsNil) + + err = bucket.PutObjectWithURL(signedURL, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Get with URL + signedURL, err = bucket.SignURL(objectName, HTTPGet, 3600) + c.Assert(err, IsNil) + + body, err = bucket.GetObjectWithURL(signedURL) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Delete + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +func (s *OssBucketSuite) TestSTSTonekNegative(c *C) { + objectName := objectNamePrefix + "tstg" + localFile := objectName + ".jpg" + + client, err := New(endpoint, accessID, accessKey, SecurityToken("Invalid")) + c.Assert(err, IsNil) + + _, err = client.ListBuckets() + c.Assert(err, NotNil) + + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + err = bucket.PutObject(objectName, strings.NewReader("")) + c.Assert(err, NotNil) + + err = bucket.PutObjectFromFile(objectName, "") + c.Assert(err, NotNil) + + _, err = bucket.GetObject(objectName) + c.Assert(err, NotNil) + + err = bucket.GetObjectToFile(objectName, "") + c.Assert(err, NotNil) + + _, err = bucket.ListObjects() + c.Assert(err, NotNil) + + err = bucket.SetObjectACL(objectName, ACLPublicRead) + c.Assert(err, NotNil) + + _, err = bucket.GetObjectACL(objectName) + c.Assert(err, NotNil) + + err = bucket.UploadFile(objectName, localFile, MinPartSize) + c.Assert(err, NotNil) + + err = bucket.DownloadFile(objectName, localFile, MinPartSize) + c.Assert(err, NotNil) + + _, err = bucket.IsObjectExist(objectName) + c.Assert(err, NotNil) + + _, err = bucket.ListMultipartUploads() + c.Assert(err, NotNil) + + err = bucket.DeleteObject(objectName) + c.Assert(err, NotNil) + + _, err = bucket.DeleteObjects([]string{objectName}) + c.Assert(err, NotNil) + + err = client.DeleteBucket(bucketName) + c.Assert(err, NotNil) +} + +func (s *OssBucketSuite) TestUploadBigFile(c *C) { + objectName := objectNamePrefix + "tubf" + bigFile := "D:\\tmp\\bigfile.zip" + newFile := "D:\\tmp\\newbigfile.zip" + + exist, err := isFileExist(bigFile) + c.Assert(err, IsNil) + if !exist { + return + } + + // Put + start := GetNowSec() + err = s.bucket.PutObjectFromFile(objectName, bigFile) + c.Assert(err, IsNil) + end := GetNowSec() + testLogger.Println("Put big file:", bigFile, "use sec:", end-start) + + // Check + start = GetNowSec() + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + end = GetNowSec() + testLogger.Println("Get big file:", bigFile, "use sec:", end-start) + + start = GetNowSec() + eq, err := compareFiles(bigFile, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + end = GetNowSec() + testLogger.Println("Compare big file:", bigFile, "use sec:", end-start) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +func (s *OssBucketSuite) TestSymlink(c *C) { + objectName := objectNamePrefix + "符号链接" + targetObjectName := objectNamePrefix + "符号链接目标文件" + + err := s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(targetObjectName) + c.Assert(err, IsNil) + + meta, err := s.bucket.GetSymlink(objectName) + c.Assert(err, NotNil) + + // Put symlink + err = s.bucket.PutSymlink(objectName, targetObjectName) + c.Assert(err, IsNil) + + err = s.bucket.PutObject(targetObjectName, strings.NewReader("target")) + c.Assert(err, IsNil) + + err = s.bucket.PutSymlink(objectName, targetObjectName) + c.Assert(err, IsNil) + + meta, err = s.bucket.GetSymlink(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get(HTTPHeaderOssSymlinkTarget), Equals, targetObjectName) + + // List object + lor, err := s.bucket.ListObjects() + c.Assert(err, IsNil) + exist, v := s.getObject(lor.Objects, objectName) + c.Assert(exist, Equals, true) + c.Assert(v.Type, Equals, "Symlink") + + body, err := s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, "target") + + meta, err = s.bucket.GetSymlink(targetObjectName) + c.Assert(err, NotNil) + + err = s.bucket.PutObject(objectName, strings.NewReader("src")) + c.Assert(err, IsNil) + + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, "src") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(targetObjectName) + c.Assert(err, IsNil) + + // Put symlink again + objectName = objectNamePrefix + "symlink" + targetObjectName = objectNamePrefix + "symlink-target" + + err = s.bucket.PutSymlink(objectName, targetObjectName) + c.Assert(err, IsNil) + + err = s.bucket.PutObject(targetObjectName, strings.NewReader("target1")) + c.Assert(err, IsNil) + + meta, err = s.bucket.GetSymlink(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get(HTTPHeaderOssSymlinkTarget), Equals, targetObjectName) + + body, err = s.bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, "target1") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(targetObjectName) + c.Assert(err, IsNil) +} + +// TestRestoreObject +func (s *OssBucketSuite) TestRestoreObject(c *C) { + objectName := objectNamePrefix + "restore" + + // List objects + lor, err := s.archiveBucket.ListObjects() + c.Assert(err, IsNil) + left := len(lor.Objects) + + // Put object + err = s.archiveBucket.PutObject(objectName, strings.NewReader("")) + c.Assert(err, IsNil) + + // List + lor, err = s.archiveBucket.ListObjects() + c.Assert(err, IsNil) + c.Assert(len(lor.Objects), Equals, left+1) + for _, object := range lor.Objects { + c.Assert(object.StorageClass, Equals, string(StorageArchive)) + c.Assert(object.Type, Equals, "Normal") + } + + // Head object + meta, err := s.archiveBucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + _, ok := meta["X-Oss-Restore"] + c.Assert(ok, Equals, false) + c.Assert(meta.Get("X-Oss-Storage-Class"), Equals, "Archive") + + // Error restore object + err = s.archiveBucket.RestoreObject("notexistobject") + c.Assert(err, NotNil) + + // Restore object + err = s.archiveBucket.RestoreObject(objectName) + c.Assert(err, IsNil) + + // Head object + meta, err = s.archiveBucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("X-Oss-Restore"), Equals, "ongoing-request=\"true\"") + c.Assert(meta.Get("X-Oss-Storage-Class"), Equals, "Archive") +} + +// TestProcessObject +func (s *OssBucketSuite) TestProcessObject(c *C) { + objectName := objectNamePrefix + "_process_src.jpg" + err := s.bucket.PutObjectFromFile(objectName, "../sample/BingWallpaper-2015-11-07.jpg") + c.Assert(err, IsNil) + + // If bucket-name not specified, it is saved to the current bucket by default. + destObjName := objectNamePrefix + "_process_dest_1.jpg" + process := fmt.Sprintf("image/resize,w_100|sys/saveas,o_%v", base64.URLEncoding.EncodeToString([]byte(destObjName))) + result, err := s.bucket.ProcessObject(objectName, process) + c.Assert(err, IsNil) + exist, _ := s.bucket.IsObjectExist(destObjName) + c.Assert(exist, Equals, true) + c.Assert(result.Bucket, Equals, "") + c.Assert(result.Object, Equals, destObjName) + + destObjName = objectNamePrefix + "_process_dest_1.jpg" + process = fmt.Sprintf("image/resize,w_100|sys/saveas,o_%v,b_%v", base64.URLEncoding.EncodeToString([]byte(destObjName)), base64.URLEncoding.EncodeToString([]byte(s.bucket.BucketName))) + result, err = s.bucket.ProcessObject(objectName, process) + c.Assert(err, IsNil) + exist, _ = s.bucket.IsObjectExist(destObjName) + c.Assert(exist, Equals, true) + c.Assert(result.Bucket, Equals, s.bucket.BucketName) + c.Assert(result.Object, Equals, destObjName) + + //no support process + process = fmt.Sprintf("image/resize,w_100|saveas,o_%v,b_%v", base64.URLEncoding.EncodeToString([]byte(destObjName)), base64.URLEncoding.EncodeToString([]byte(s.bucket.BucketName))) + result, err = s.bucket.ProcessObject(objectName, process) + c.Assert(err, NotNil) +} + +// Private +func createFileAndWrite(fileName string, data []byte) error { + os.Remove(fileName) + + fo, err := os.Create(fileName) + if err != nil { + return err + } + defer fo.Close() + + bytes, err := fo.Write(data) + if err != nil { + return err + } + + if bytes != len(data) { + return fmt.Errorf(fmt.Sprintf("write %d bytes not equal data length %d", bytes, len(data))) + } + + return nil +} + +// Compare the content between fileL and fileR +func compareFiles(fileL string, fileR string) (bool, error) { + finL, err := os.Open(fileL) + if err != nil { + return false, err + } + defer finL.Close() + + finR, err := os.Open(fileR) + if err != nil { + return false, err + } + defer finR.Close() + + statL, err := finL.Stat() + if err != nil { + return false, err + } + + statR, err := finR.Stat() + if err != nil { + return false, err + } + + if statL.Size() != statR.Size() { + return false, nil + } + + size := statL.Size() + if size > 102400 { + size = 102400 + } + + bufL := make([]byte, size) + bufR := make([]byte, size) + for { + n, _ := finL.Read(bufL) + if 0 == n { + break + } + + n, _ = finR.Read(bufR) + if 0 == n { + break + } + + if !bytes.Equal(bufL, bufR) { + return false, nil + } + } + + return true, nil +} + +// Compare the content of file and data +func compareFileData(file string, data []byte) (bool, error) { + fin, err := os.Open(file) + if err != nil { + return false, err + } + defer fin.Close() + + stat, err := fin.Stat() + if err != nil { + return false, err + } + + if stat.Size() != (int64)(len(data)) { + return false, nil + } + + buf := make([]byte, stat.Size()) + n, err := fin.Read(buf) + if err != nil { + return false, err + } + if stat.Size() != (int64)(n) { + return false, errors.New("read error") + } + + if !bytes.Equal(buf, data) { + return false, nil + } + + return true, nil +} + +func walkDir(dirPth, suffix string) ([]string, error) { + var files = []string{} + suffix = strings.ToUpper(suffix) + err := filepath.Walk(dirPth, + func(filename string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + if fi.IsDir() { + return nil + } + if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) { + files = append(files, filename) + } + return nil + }) + return files, err +} + +func removeTempFiles(path string, prefix string) error { + files, err := walkDir(path, prefix) + if err != nil { + return nil + } + + for _, file := range files { + os.Remove(file) + } + + return nil +} + +func isFileExist(filename string) (bool, error) { + _, err := os.Stat(filename) + if err != nil && os.IsNotExist(err) { + return false, nil + } else if err != nil { + return false, err + } else { + return true, nil + } +} + +func readBody(body io.ReadCloser) (string, error) { + data, err := ioutil.ReadAll(body) + body.Close() + if err != nil { + return "", err + } + return string(data), nil +} + +func (s *OssBucketSuite) getObject(objects []ObjectProperties, object string) (bool, ObjectProperties) { + for _, v := range objects { + if v.Key == object { + return true, v + } + } + return false, ObjectProperties{} +} + +func (s *OssBucketSuite) detectUploadSpeed(bucket *Bucket, c *C) (upSpeed int) { + objectName := objectNamePrefix + getUuid() + + // 1M byte + textBuffer := randStr(1024 * 1024) + + // Put string + startT := time.Now() + err := bucket.PutObject(objectName, strings.NewReader(textBuffer)) + endT := time.Now() + + c.Assert(err, IsNil) + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // byte/s + upSpeed = len(textBuffer) * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000) + return upSpeed +} + +func (s *OssBucketSuite) TestPutSingleObjectLimitSpeed(c *C) { + + // create client and bucket + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.LimitUploadSpeed(1) + if err != nil { + // go version is less than go1.7,not support limit upload speed + // doesn't run this test + return + } else { + // set unlimited again + client.LimitUploadSpeed(0) + } + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + //detect speed:byte/s + detectSpeed := s.detectUploadSpeed(bucket, c) + + var limitSpeed = 0 + if detectSpeed <= perTokenBandwidthSize*2 { + limitSpeed = perTokenBandwidthSize + } else { + //this situation, the test works better + limitSpeed = detectSpeed / 2 + } + + // KB/s + err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize) + c.Assert(err, IsNil) + + objectName := objectNamePrefix + getUuid() + + // 1M byte + textBuffer := randStr(1024 * 1024) + + // Put body + startT := time.Now() + err = bucket.PutObject(objectName, strings.NewReader(textBuffer)) + endT := time.Now() + + realSpeed := int64(len(textBuffer)) * 1000 / (endT.UnixNano()/1000/1000 - startT.UnixNano()/1000/1000) + + fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed) + + c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true) + + if detectSpeed > perTokenBandwidthSize { + // the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s) + c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true) + } + + // Get object and compare content + body, err := bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, textBuffer) + + bucket.DeleteObject(objectName) + client.DeleteBucket(bucketName) + c.Assert(err, IsNil) + + return +} + +func putObjectRoutin(bucket *Bucket, object string, textBuffer *string, notifyChan chan int) error { + err := bucket.PutObject(object, strings.NewReader(*textBuffer)) + if err == nil { + notifyChan <- 1 + } else { + notifyChan <- 0 + } + return err +} + +func (s *OssBucketSuite) TestPutManyObjectLimitSpeed(c *C) { + + // create client and bucket + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.LimitUploadSpeed(1) + if err != nil { + // go version is less than go1.7,not support limit upload speed + // doesn't run this test + return + } else { + // set unlimited + client.LimitUploadSpeed(0) + } + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + //detect speed:byte/s + detectSpeed := s.detectUploadSpeed(bucket, c) + var limitSpeed = 0 + if detectSpeed <= perTokenBandwidthSize*2 { + limitSpeed = perTokenBandwidthSize + } else { + limitSpeed = detectSpeed / 2 + } + + // KB/s + err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize) + c.Assert(err, IsNil) + + // object1 + objectNameFirst := objectNamePrefix + getUuid() + objectNameSecond := objectNamePrefix + getUuid() + + // 1M byte + textBuffer := randStr(1024 * 1024) + + objectCount := 2 + notifyChan := make(chan int, objectCount) + + //start routin + startT := time.Now() + go putObjectRoutin(bucket, objectNameFirst, &textBuffer, notifyChan) + go putObjectRoutin(bucket, objectNameSecond, &textBuffer, notifyChan) + + // wait routin end + sum := int(0) + for j := 0; j < objectCount; j++ { + result := <-notifyChan + sum += result + } + endT := time.Now() + + realSpeed := len(textBuffer) * 2 * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000) + c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true) + + if detectSpeed > perTokenBandwidthSize { + // the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s) + c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true) + } + c.Assert(sum, Equals, 2) + + // Get object and compare content + body, err := bucket.GetObject(objectNameFirst) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, textBuffer) + + body, err = bucket.GetObject(objectNameSecond) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, textBuffer) + + // clear bucket and object + bucket.DeleteObject(objectNameFirst) + bucket.DeleteObject(objectNameSecond) + client.DeleteBucket(bucketName) + + fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed) + + return +} + +func (s *OssBucketSuite) TestPutMultipartObjectLimitSpeed(c *C) { + + // create client and bucket + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.LimitUploadSpeed(1) + if err != nil { + // go version is less than go1.7,not support limit upload speed + // doesn't run this test + return + } else { + // set unlimited + client.LimitUploadSpeed(0) + } + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + //detect speed:byte/s + detectSpeed := s.detectUploadSpeed(bucket, c) + + var limitSpeed = 0 + if detectSpeed <= perTokenBandwidthSize*2 { + limitSpeed = perTokenBandwidthSize + } else { + //this situation, the test works better + limitSpeed = detectSpeed / 2 + } + + // KB/s + err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize) + c.Assert(err, IsNil) + + objectName := objectNamePrefix + getUuid() + fileName := "." + string(os.PathSeparator) + objectName + + // 1M byte + fileSize := 0 + textBuffer := randStr(1024 * 1024) + if detectSpeed < perTokenBandwidthSize { + ioutil.WriteFile(fileName, []byte(textBuffer), 0644) + f, err := os.Stat(fileName) + c.Assert(err, IsNil) + + fileSize = int(f.Size()) + c.Assert(fileSize, Equals, len(textBuffer)) + + } else { + loopCount := 5 + f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660) + c.Assert(err, IsNil) + + for i := 0; i < loopCount; i++ { + f.Write([]byte(textBuffer)) + } + + fileInfo, err := f.Stat() + c.Assert(err, IsNil) + + fileSize = int(fileInfo.Size()) + c.Assert(fileSize, Equals, len(textBuffer)*loopCount) + + f.Close() + } + + // Put body + startT := time.Now() + err = bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, "")) + endT := time.Now() + + c.Assert(err, IsNil) + realSpeed := fileSize * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000) + c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true) + + if detectSpeed > perTokenBandwidthSize { + // the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s) + c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true) + } + + // Get object and compare content + body, err := bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + + fileBody, err := ioutil.ReadFile(fileName) + c.Assert(err, IsNil) + c.Assert(str, Equals, string(fileBody)) + + // delete bucket、object、file + bucket.DeleteObject(objectName) + client.DeleteBucket(bucketName) + os.Remove(fileName) + + fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed) + + return +} + +func (s *OssBucketSuite) TestPutObjectFromFileLimitSpeed(c *C) { + // create client and bucket + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.LimitUploadSpeed(1) + if err != nil { + // go version is less than go1.7,not support limit upload speed + // doesn't run this test + return + } else { + // set unlimited + client.LimitUploadSpeed(0) + } + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + //detect speed:byte/s + detectSpeed := s.detectUploadSpeed(bucket, c) + + var limitSpeed = 0 + if detectSpeed <= perTokenBandwidthSize*2 { + limitSpeed = perTokenBandwidthSize + } else { + //this situation, the test works better + limitSpeed = detectSpeed / 2 + } + + // KB/s + err = client.LimitUploadSpeed(limitSpeed / perTokenBandwidthSize) + c.Assert(err, IsNil) + + objectName := objectNamePrefix + getUuid() + fileName := "." + string(os.PathSeparator) + objectName + + // 1M byte + fileSize := 0 + textBuffer := randStr(1024 * 1024) + if detectSpeed < perTokenBandwidthSize { + ioutil.WriteFile(fileName, []byte(textBuffer), 0644) + f, err := os.Stat(fileName) + c.Assert(err, IsNil) + + fileSize = int(f.Size()) + c.Assert(fileSize, Equals, len(textBuffer)) + + } else { + loopCount := 2 + f, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0660) + c.Assert(err, IsNil) + + for i := 0; i < loopCount; i++ { + f.Write([]byte(textBuffer)) + } + + fileInfo, err := f.Stat() + c.Assert(err, IsNil) + + fileSize = int(fileInfo.Size()) + c.Assert(fileSize, Equals, len(textBuffer)*loopCount) + + f.Close() + } + + // Put body + startT := time.Now() + err = bucket.PutObjectFromFile(objectName, fileName) + endT := time.Now() + + c.Assert(err, IsNil) + realSpeed := fileSize * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000) + c.Assert(float64(realSpeed) < float64(limitSpeed)*1.1, Equals, true) + + if detectSpeed > perTokenBandwidthSize { + // the minimum uploas limit speed is perTokenBandwidthSize(1024 byte/s) + c.Assert(float64(realSpeed) > float64(limitSpeed)*0.9, Equals, true) + } + + // Get object and compare content + body, err := bucket.GetObject(objectName) + c.Assert(err, IsNil) + str, err := readBody(body) + c.Assert(err, IsNil) + + fileBody, err := ioutil.ReadFile(fileName) + c.Assert(err, IsNil) + c.Assert(str, Equals, string(fileBody)) + + // delete bucket、file、object + bucket.DeleteObject(objectName) + client.DeleteBucket(bucketName) + os.Remove(fileName) + + fmt.Printf("detect speed:%d,limit speed:%d,real speed:%d.\n", detectSpeed, limitSpeed, realSpeed) + + return +} + +// upload speed limit parameters will not affect download speed +func (s *OssBucketSuite) TestUploadObjectLimitSpeed(c *C) { + // create limit client and bucket + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + tokenCount := 1 + err = client.LimitUploadSpeed(tokenCount) + if err != nil { + // go version is less than go1.7,not support limit upload speed + // doesn't run this test + return + } else { + // set unlimited + client.LimitUploadSpeed(0) + } + + bucketName := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketName) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + //first:upload a object + textBuffer := randStr(1024 * 100) + objectName := objectNamePrefix + getUuid() + err = bucket.PutObject(objectName, strings.NewReader(textBuffer)) + c.Assert(err, IsNil) + + // limit upload speed + err = client.LimitUploadSpeed(tokenCount) + c.Assert(err, IsNil) + + // then download the object + startT := time.Now() + body, err := bucket.GetObject(objectName) + c.Assert(err, IsNil) + + str, err := readBody(body) + c.Assert(err, IsNil) + endT := time.Now() + + c.Assert(str, Equals, textBuffer) + + // byte/s + downloadSpeed := len(textBuffer) * 1000 / int(endT.UnixNano()/1000/1000-startT.UnixNano()/1000/1000) + + // upload speed limit parameters will not affect download speed + c.Assert(downloadSpeed > 2*tokenCount*perTokenBandwidthSize, Equals, true) + + bucket.DeleteObject(objectName) + client.DeleteBucket(bucketName) +} + +// test LimitUploadSpeed failure +func (s *OssBucketSuite) TestLimitUploadSpeedFail(c *C) { + // create limit client and bucket + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.LimitUploadSpeed(-1) + c.Assert(err, NotNil) + + client.Config = nil + err = client.LimitUploadSpeed(100) + c.Assert(err, NotNil) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client.go new file mode 100644 index 000000000..ff370f6dd --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client.go @@ -0,0 +1,805 @@ +// Package oss implements functions for access oss service. +// It has two main struct Client and Bucket. +package oss + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "log" + "net/http" + "strings" + "time" +) + +// Client SDK's entry point. It's for bucket related options such as create/delete/set bucket (such as set/get ACL/lifecycle/referer/logging/website). +// Object related operations are done by Bucket class. +// Users use oss.New to create Client instance. +// +type ( + // Client OSS client + Client struct { + Config *Config // OSS client configuration + Conn *Conn // Send HTTP request + HTTPClient *http.Client //http.Client to use - if nil will make its own + } + + // ClientOption client option such as UseCname, Timeout, SecurityToken. + ClientOption func(*Client) +) + +// New creates a new client. +// +// endpoint the OSS datacenter endpoint such as http://oss-cn-hangzhou.aliyuncs.com . +// accessKeyId access key Id. +// accessKeySecret access key secret. +// +// Client creates the new client instance, the returned value is valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func New(endpoint, accessKeyID, accessKeySecret string, options ...ClientOption) (*Client, error) { + // Configuration + config := getDefaultOssConfig() + config.Endpoint = endpoint + config.AccessKeyID = accessKeyID + config.AccessKeySecret = accessKeySecret + + // URL parse + url := &urlMaker{} + url.Init(config.Endpoint, config.IsCname, config.IsUseProxy) + + // HTTP connect + conn := &Conn{config: config, url: url} + + // OSS client + client := &Client{ + Config: config, + Conn: conn, + } + + // Client options parse + for _, option := range options { + option(client) + } + + // Create HTTP connection + err := conn.init(config, url, client.HTTPClient) + + return client, err +} + +// Bucket gets the bucket instance. +// +// bucketName the bucket name. +// Bucket the bucket object, when error is nil. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) Bucket(bucketName string) (*Bucket, error) { + return &Bucket{ + client, + bucketName, + }, nil +} + +// CreateBucket creates a bucket. +// +// bucketName the bucket name, it's globably unique and immutable. The bucket name can only consist of lowercase letters, numbers and dash ('-'). +// It must start with lowercase letter or number and the length can only be between 3 and 255. +// options options for creating the bucket, with optional ACL. The ACL could be ACLPrivate, ACLPublicRead, and ACLPublicReadWrite. By default it's ACLPrivate. +// It could also be specified with StorageClass option, which supports StorageStandard, StorageIA(infrequent access), StorageArchive. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) CreateBucket(bucketName string, options ...Option) error { + headers := make(map[string]string) + handleOptions(headers, options) + + buffer := new(bytes.Buffer) + + isOptSet, val, _ := isOptionSet(options, storageClass) + if isOptSet { + cbConfig := createBucketConfiguration{StorageClass: val.(StorageClassType)} + bs, err := xml.Marshal(cbConfig) + if err != nil { + return err + } + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + headers[HTTPHeaderContentType] = contentType + } + + params := map[string]interface{}{} + resp, err := client.do("PUT", bucketName, params, headers, buffer) + if err != nil { + return err + } + + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// ListBuckets lists buckets of the current account under the given endpoint, with optional filters. +// +// options specifies the filters such as Prefix, Marker and MaxKeys. Prefix is the bucket name's prefix filter. +// And marker makes sure the returned buckets' name are greater than it in lexicographic order. +// Maxkeys limits the max keys to return, and by default it's 100 and up to 1000. +// For the common usage scenario, please check out list_bucket.go in the sample. +// ListBucketsResponse the response object if error is nil. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) ListBuckets(options ...Option) (ListBucketsResult, error) { + var out ListBucketsResult + + params, err := getRawParams(options) + if err != nil { + return out, err + } + + resp, err := client.do("GET", "", params, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// IsBucketExist checks if the bucket exists +// +// bucketName the bucket name. +// +// bool true if it exists, and it's only valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) IsBucketExist(bucketName string) (bool, error) { + listRes, err := client.ListBuckets(Prefix(bucketName), MaxKeys(1)) + if err != nil { + return false, err + } + + if len(listRes.Buckets) == 1 && listRes.Buckets[0].Name == bucketName { + return true, nil + } + return false, nil +} + +// DeleteBucket deletes the bucket. Only empty bucket can be deleted (no object and parts). +// +// bucketName the bucket name. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) DeleteBucket(bucketName string) error { + params := map[string]interface{}{} + resp, err := client.do("DELETE", bucketName, params, nil, nil) + if err != nil { + return err + } + + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + +// GetBucketLocation gets the bucket location. +// +// Checks out the following link for more information : +// https://help.aliyun.com/document_detail/oss/user_guide/oss_concept/endpoint.html +// +// bucketName the bucket name +// +// string bucket's datacenter location +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) GetBucketLocation(bucketName string) (string, error) { + params := map[string]interface{}{} + params["location"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + if err != nil { + return "", err + } + defer resp.Body.Close() + + var LocationConstraint string + err = xmlUnmarshal(resp.Body, &LocationConstraint) + return LocationConstraint, err +} + +// SetBucketACL sets bucket's ACL. +// +// bucketName the bucket name +// bucketAcl the bucket ACL: ACLPrivate, ACLPublicRead and ACLPublicReadWrite. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) SetBucketACL(bucketName string, bucketACL ACLType) error { + headers := map[string]string{HTTPHeaderOssACL: string(bucketACL)} + params := map[string]interface{}{} + resp, err := client.do("PUT", bucketName, params, headers, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// GetBucketACL gets the bucket ACL. +// +// bucketName the bucket name. +// +// GetBucketAclResponse the result object, and it's only valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) GetBucketACL(bucketName string) (GetBucketACLResult, error) { + var out GetBucketACLResult + params := map[string]interface{}{} + params["acl"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// SetBucketLifecycle sets the bucket's lifecycle. +// +// For more information, checks out following link: +// https://help.aliyun.com/document_detail/oss/user_guide/manage_object/object_lifecycle.html +// +// bucketName the bucket name. +// rules the lifecycle rules. There're two kind of rules: absolute time expiration and relative time expiration in days and day/month/year respectively. +// Check out sample/bucket_lifecycle.go for more details. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) SetBucketLifecycle(bucketName string, rules []LifecycleRule) error { + lxml := lifecycleXML{Rules: convLifecycleRule(rules)} + bs, err := xml.Marshal(lxml) + if err != nil { + return err + } + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + headers := map[string]string{} + headers[HTTPHeaderContentType] = contentType + + params := map[string]interface{}{} + params["lifecycle"] = nil + resp, err := client.do("PUT", bucketName, params, headers, buffer) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// DeleteBucketLifecycle deletes the bucket's lifecycle. +// +// +// bucketName the bucket name. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) DeleteBucketLifecycle(bucketName string) error { + params := map[string]interface{}{} + params["lifecycle"] = nil + resp, err := client.do("DELETE", bucketName, params, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + +// GetBucketLifecycle gets the bucket's lifecycle settings. +// +// bucketName the bucket name. +// +// GetBucketLifecycleResponse the result object upon successful request. It's only valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) GetBucketLifecycle(bucketName string) (GetBucketLifecycleResult, error) { + var out GetBucketLifecycleResult + params := map[string]interface{}{} + params["lifecycle"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// SetBucketReferer sets the bucket's referer whitelist and the flag if allowing empty referrer. +// +// To avoid stealing link on OSS data, OSS supports the HTTP referrer header. A whitelist referrer could be set either by API or web console, as well as +// the allowing empty referrer flag. Note that this applies to requests from webbrowser only. +// For example, for a bucket os-example and its referrer http://www.aliyun.com, all requests from this URL could access the bucket. +// For more information, please check out this link : +// https://help.aliyun.com/document_detail/oss/user_guide/security_management/referer.html +// +// bucketName the bucket name. +// referers the referrer white list. A bucket could have a referrer list and each referrer supports one '*' and multiple '?' as wildcards. +// The sample could be found in sample/bucket_referer.go +// allowEmptyReferer the flag of allowing empty referrer. By default it's true. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) SetBucketReferer(bucketName string, referers []string, allowEmptyReferer bool) error { + rxml := RefererXML{} + rxml.AllowEmptyReferer = allowEmptyReferer + if referers == nil { + rxml.RefererList = append(rxml.RefererList, "") + } else { + for _, referer := range referers { + rxml.RefererList = append(rxml.RefererList, referer) + } + } + + bs, err := xml.Marshal(rxml) + if err != nil { + return err + } + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + headers := map[string]string{} + headers[HTTPHeaderContentType] = contentType + + params := map[string]interface{}{} + params["referer"] = nil + resp, err := client.do("PUT", bucketName, params, headers, buffer) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// GetBucketReferer gets the bucket's referrer white list. +// +// bucketName the bucket name. +// +// GetBucketRefererResponse the result object upon successful request. It's only valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) GetBucketReferer(bucketName string) (GetBucketRefererResult, error) { + var out GetBucketRefererResult + params := map[string]interface{}{} + params["referer"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// SetBucketLogging sets the bucket logging settings. +// +// OSS could automatically store the access log. Only the bucket owner could enable the logging. +// Once enabled, OSS would save all the access log into hourly log files in a specified bucket. +// For more information, please check out https://help.aliyun.com/document_detail/oss/user_guide/security_management/logging.html +// +// bucketName bucket name to enable the log. +// targetBucket the target bucket name to store the log files. +// targetPrefix the log files' prefix. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) SetBucketLogging(bucketName, targetBucket, targetPrefix string, + isEnable bool) error { + var err error + var bs []byte + if isEnable { + lxml := LoggingXML{} + lxml.LoggingEnabled.TargetBucket = targetBucket + lxml.LoggingEnabled.TargetPrefix = targetPrefix + bs, err = xml.Marshal(lxml) + } else { + lxml := loggingXMLEmpty{} + bs, err = xml.Marshal(lxml) + } + + if err != nil { + return err + } + + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + headers := map[string]string{} + headers[HTTPHeaderContentType] = contentType + + params := map[string]interface{}{} + params["logging"] = nil + resp, err := client.do("PUT", bucketName, params, headers, buffer) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// DeleteBucketLogging deletes the logging configuration to disable the logging on the bucket. +// +// bucketName the bucket name to disable the logging. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) DeleteBucketLogging(bucketName string) error { + params := map[string]interface{}{} + params["logging"] = nil + resp, err := client.do("DELETE", bucketName, params, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + +// GetBucketLogging gets the bucket's logging settings +// +// bucketName the bucket name +// GetBucketLoggingResponse the result object upon successful request. It's only valid when error is nil. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) GetBucketLogging(bucketName string) (GetBucketLoggingResult, error) { + var out GetBucketLoggingResult + params := map[string]interface{}{} + params["logging"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// SetBucketWebsite sets the bucket's static website's index and error page. +// +// OSS supports static web site hosting for the bucket data. When the bucket is enabled with that, you can access the file in the bucket like the way to access a static website. +// For more information, please check out: https://help.aliyun.com/document_detail/oss/user_guide/static_host_website.html +// +// bucketName the bucket name to enable static web site. +// indexDocument index page. +// errorDocument error page. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) SetBucketWebsite(bucketName, indexDocument, errorDocument string) error { + wxml := WebsiteXML{} + wxml.IndexDocument.Suffix = indexDocument + wxml.ErrorDocument.Key = errorDocument + + bs, err := xml.Marshal(wxml) + if err != nil { + return err + } + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + headers := make(map[string]string) + headers[HTTPHeaderContentType] = contentType + + params := map[string]interface{}{} + params["website"] = nil + resp, err := client.do("PUT", bucketName, params, headers, buffer) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// DeleteBucketWebsite deletes the bucket's static web site settings. +// +// bucketName the bucket name. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) DeleteBucketWebsite(bucketName string) error { + params := map[string]interface{}{} + params["website"] = nil + resp, err := client.do("DELETE", bucketName, params, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + +// GetBucketWebsite gets the bucket's default page (index page) and the error page. +// +// bucketName the bucket name +// +// GetBucketWebsiteResponse the result object upon successful request. It's only valid when error is nil. +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) GetBucketWebsite(bucketName string) (GetBucketWebsiteResult, error) { + var out GetBucketWebsiteResult + params := map[string]interface{}{} + params["website"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// SetBucketCORS sets the bucket's CORS rules +// +// For more information, please check out https://help.aliyun.com/document_detail/oss/user_guide/security_management/cors.html +// +// bucketName the bucket name +// corsRules the CORS rules to set. The related sample code is in sample/bucket_cors.go. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) SetBucketCORS(bucketName string, corsRules []CORSRule) error { + corsxml := CORSXML{} + for _, v := range corsRules { + cr := CORSRule{} + cr.AllowedMethod = v.AllowedMethod + cr.AllowedOrigin = v.AllowedOrigin + cr.AllowedHeader = v.AllowedHeader + cr.ExposeHeader = v.ExposeHeader + cr.MaxAgeSeconds = v.MaxAgeSeconds + corsxml.CORSRules = append(corsxml.CORSRules, cr) + } + + bs, err := xml.Marshal(corsxml) + if err != nil { + return err + } + buffer := new(bytes.Buffer) + buffer.Write(bs) + + contentType := http.DetectContentType(buffer.Bytes()) + headers := map[string]string{} + headers[HTTPHeaderContentType] = contentType + + params := map[string]interface{}{} + params["cors"] = nil + resp, err := client.do("PUT", bucketName, params, headers, buffer) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// DeleteBucketCORS deletes the bucket's static website settings. +// +// bucketName the bucket name. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) DeleteBucketCORS(bucketName string) error { + params := map[string]interface{}{} + params["cors"] = nil + resp, err := client.do("DELETE", bucketName, params, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + +// GetBucketCORS gets the bucket's CORS settings. +// +// bucketName the bucket name. +// GetBucketCORSResult the result object upon successful request. It's only valid when error is nil. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) GetBucketCORS(bucketName string) (GetBucketCORSResult, error) { + var out GetBucketCORSResult + params := map[string]interface{}{} + params["cors"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// GetBucketInfo gets the bucket information. +// +// bucketName the bucket name. +// GetBucketInfoResult the result object upon successful request. It's only valid when error is nil. +// +// error it's nil if no error, otherwise it's an error object. +// +func (client Client) GetBucketInfo(bucketName string) (GetBucketInfoResult, error) { + var out GetBucketInfoResult + params := map[string]interface{}{} + params["bucketInfo"] = nil + resp, err := client.do("GET", bucketName, params, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// LimitUploadSpeed: set upload bandwidth limit speed,default is 0,unlimited +// upSpeed: KB/s, 0 is unlimited,default is 0 +// error:it's nil if success, otherwise failure +func (client Client) LimitUploadSpeed(upSpeed int) error { + if client.Config == nil { + return fmt.Errorf("client config is nil") + } + return client.Config.LimitUploadSpeed(upSpeed) +} + +// UseCname sets the flag of using CName. By default it's false. +// +// isUseCname true: the endpoint has the CName, false: the endpoint does not have cname. Default is false. +// +func UseCname(isUseCname bool) ClientOption { + return func(client *Client) { + client.Config.IsCname = isUseCname + client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname, client.Config.IsUseProxy) + } +} + +// Timeout sets the HTTP timeout in seconds. +// +// connectTimeoutSec HTTP timeout in seconds. Default is 10 seconds. 0 means infinite (not recommended) +// readWriteTimeout HTTP read or write's timeout in seconds. Default is 20 seconds. 0 means infinite. +// +func Timeout(connectTimeoutSec, readWriteTimeout int64) ClientOption { + return func(client *Client) { + client.Config.HTTPTimeout.ConnectTimeout = + time.Second * time.Duration(connectTimeoutSec) + client.Config.HTTPTimeout.ReadWriteTimeout = + time.Second * time.Duration(readWriteTimeout) + client.Config.HTTPTimeout.HeaderTimeout = + time.Second * time.Duration(readWriteTimeout) + client.Config.HTTPTimeout.IdleConnTimeout = + time.Second * time.Duration(readWriteTimeout) + client.Config.HTTPTimeout.LongTimeout = + time.Second * time.Duration(readWriteTimeout*10) + } +} + +// SecurityToken sets the temporary user's SecurityToken. +// +// token STS token +// +func SecurityToken(token string) ClientOption { + return func(client *Client) { + client.Config.SecurityToken = strings.TrimSpace(token) + } +} + +// EnableMD5 enables MD5 validation. +// +// isEnableMD5 true: enable MD5 validation; false: disable MD5 validation. +// +func EnableMD5(isEnableMD5 bool) ClientOption { + return func(client *Client) { + client.Config.IsEnableMD5 = isEnableMD5 + } +} + +// MD5ThresholdCalcInMemory sets the memory usage threshold for computing the MD5, default is 16MB. +// +// threshold the memory threshold in bytes. When the uploaded content is more than 16MB, the temp file is used for computing the MD5. +// +func MD5ThresholdCalcInMemory(threshold int64) ClientOption { + return func(client *Client) { + client.Config.MD5Threshold = threshold + } +} + +// EnableCRC enables the CRC checksum. Default is true. +// +// isEnableCRC true: enable CRC checksum; false: disable the CRC checksum. +// +func EnableCRC(isEnableCRC bool) ClientOption { + return func(client *Client) { + client.Config.IsEnableCRC = isEnableCRC + } +} + +// UserAgent specifies UserAgent. The default is aliyun-sdk-go/1.2.0 (windows/-/amd64;go1.5.2). +// +// userAgent the user agent string. +// +func UserAgent(userAgent string) ClientOption { + return func(client *Client) { + client.Config.UserAgent = userAgent + } +} + +// Proxy sets the proxy (optional). The default is not using proxy. +// +// proxyHost the proxy host in the format "host:port". For example, proxy.com:80 . +// +func Proxy(proxyHost string) ClientOption { + return func(client *Client) { + client.Config.IsUseProxy = true + client.Config.ProxyHost = proxyHost + client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname, client.Config.IsUseProxy) + } +} + +// AuthProxy sets the proxy information with user name and password. +// +// proxyHost the proxy host in the format "host:port". For example, proxy.com:80 . +// proxyUser the proxy user name. +// proxyPassword the proxy password. +// +func AuthProxy(proxyHost, proxyUser, proxyPassword string) ClientOption { + return func(client *Client) { + client.Config.IsUseProxy = true + client.Config.ProxyHost = proxyHost + client.Config.IsAuthProxy = true + client.Config.ProxyUser = proxyUser + client.Config.ProxyPassword = proxyPassword + client.Conn.url.Init(client.Config.Endpoint, client.Config.IsCname, client.Config.IsUseProxy) + } +} + +// +// HTTPClient sets the http.Client in use to the one passed in +// +func HTTPClient(HTTPClient *http.Client) ClientOption { + return func(client *Client) { + client.HTTPClient = HTTPClient + } +} + +// +// SetLogLevel sets the oss sdk log level +// +func SetLogLevel(LogLevel int) ClientOption { + return func(client *Client) { + client.Config.LogLevel = LogLevel + } +} + +// +// SetLogLevel sets the oss sdk log level +// +func SetLogger(Logger *log.Logger) ClientOption { + return func(client *Client) { + client.Config.Logger = Logger + } +} + +// Private +func (client Client) do(method, bucketName string, params map[string]interface{}, + headers map[string]string, data io.Reader) (*Response, error) { + return client.Conn.Do(method, bucketName, "", params, + headers, data, 0, nil) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client_test.go new file mode 100644 index 000000000..94ff889d3 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/client_test.go @@ -0,0 +1,1619 @@ +// client test +// use gocheck, install gocheck to execute "go get gopkg.in/check.v1", +// see https://labix.org/gocheck + +package oss + +import ( + "io/ioutil" + "log" + "math/rand" + "net/http" + "os" + "runtime" + "strings" + "testing" + "time" + + uuid "github.com/satori/go.uuid" + . "gopkg.in/check.v1" +) + +// Test hooks up gocheck into the "go test" runner. +func Test(t *testing.T) { + TestingT(t) +} + +type OssClientSuite struct{} + +var _ = Suite(&OssClientSuite{}) + +var ( + // Endpoint/ID/Key + endpoint = os.Getenv("OSS_TEST_ENDPOINT") + accessID = os.Getenv("OSS_TEST_ACCESS_KEY_ID") + accessKey = os.Getenv("OSS_TEST_ACCESS_KEY_SECRET") + + // Proxy + proxyHost = os.Getenv("OSS_TEST_PROXY_HOST") + proxyUser = os.Getenv("OSS_TEST_PROXY_USER") + proxyPasswd = os.Getenv("OSS_TEST_PROXY_PASSWORD") + + // STS + stsaccessID = os.Getenv("OSS_TEST_STS_ID") + stsaccessKey = os.Getenv("OSS_TEST_STS_KEY") + stsARN = os.Getenv("OSS_TEST_STS_ARN") +) + +var ( + // prefix of bucket name for bucket ops test + bucketNamePrefix = "go-sdk-test-bucket-abcx-" + // bucket name for object ops test + bucketName = "go-sdk-test-bucket-abcx-for-object" + randLowStr(5) + archiveBucketName = "go-sdk-test-bucket-abcx-for-archive" + randLowStr(5) + // object name for object ops test + objectNamePrefix = "go-sdk-test-object-abcx-" + // sts region is one and only hangzhou + stsRegion = "cn-hangzhou" +) + +var ( + logPath = "go_sdk_test_" + time.Now().Format("20060102_150405") + ".log" + testLogFile, _ = os.OpenFile(logPath, os.O_RDWR|os.O_CREATE, 0664) + testLogger = log.New(testLogFile, "", log.Ldate|log.Ltime|log.Lshortfile) + letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") +) + +func randStr(n int) string { + b := make([]rune, n) + r := rand.New(rand.NewSource(time.Now().UnixNano())) + for i := range b { + b[i] = letters[r.Intn(len(letters))] + } + return string(b) +} + +func createFile(fileName, content string, c *C) { + fout, err := os.Create(fileName) + defer fout.Close() + c.Assert(err, IsNil) + _, err = fout.WriteString(content) + c.Assert(err, IsNil) +} + +func randLowStr(n int) string { + return strings.ToLower(randStr(n)) +} + +func getUuid() string { + uniqId, _ := uuid.NewV4() + uniqKey := uniqId.String() + return uniqKey +} + +// SetUpSuite runs once when the suite starts running +func (s *OssClientSuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000)) + c.Assert(err, IsNil) + + for _, bucket := range lbr.Buckets { + s.deleteBucket(client, bucket.Name, c) + } + + testLogger.Println("test client started") +} + +// TearDownSuite runs before each test or benchmark starts running +func (s *OssClientSuite) TearDownSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(1000)) + c.Assert(err, IsNil) + + for _, bucket := range lbr.Buckets { + s.deleteBucket(client, bucket.Name, c) + } + + testLogger.Println("test client completed") +} + +func (s *OssClientSuite) deleteBucket(client *Client, bucketName string, c *C) { + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + // Delete Object + lor, err := bucket.ListObjects() + c.Assert(err, IsNil) + + for _, object := range lor.Objects { + err = bucket.DeleteObject(object.Key) + c.Assert(err, IsNil) + } + + // Delete Part + lmur, err := bucket.ListMultipartUploads() + c.Assert(err, IsNil) + + for _, upload := range lmur.Uploads { + var imur = InitiateMultipartUploadResult{Bucket: bucketName, + Key: upload.Key, UploadID: upload.UploadID} + err = bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } + + // Delete Bucket + err = client.DeleteBucket(bucketName) + c.Assert(err, IsNil) +} + +// SetUpTest runs after each test or benchmark runs +func (s *OssClientSuite) SetUpTest(c *C) { +} + +// TearDownTest runs once after all tests or benchmarks have finished running +func (s *OssClientSuite) TearDownTest(c *C) { +} + +// TestCreateBucket +func (s *OssClientSuite) TestCreateBucket(c *C) { + var bucketNameTest = bucketNamePrefix + "tcb" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Create + client.DeleteBucket(bucketNameTest) + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + //sleep 5 seconds after create bucket + time.Sleep(5 * time.Second) + + // verify bucket is exist + found, err := client.IsBucketExist(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(found, Equals, true) + + res, err := client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPrivate)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // CreateBucket creates with ACLPublicRead + err = client.CreateBucket(bucketNameTest, ACL(ACLPublicRead)) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + res, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPublicRead)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // ACLPublicReadWrite + err = client.CreateBucket(bucketNameTest, ACL(ACLPublicReadWrite)) + c.Assert(err, IsNil) + + res, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPublicReadWrite)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // ACLPrivate + err = client.CreateBucket(bucketNameTest, ACL(ACLPrivate)) + c.Assert(err, IsNil) + + res, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPrivate)) + + // Delete + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + + // Create bucket with configuration and test GetBucketInfo + for _, storage := range []StorageClassType{StorageStandard, StorageIA, StorageArchive} { + bucketNameTest := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketNameTest, StorageClass(storage), ACL(ACLPublicRead)) + c.Assert(err, IsNil) + + res, err := client.GetBucketInfo(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.BucketInfo.Name, Equals, bucketNameTest) + c.Assert(res.BucketInfo.StorageClass, Equals, string(storage)) + c.Assert(res.BucketInfo.ACL, Equals, string(ACLPublicRead)) + + // Delete + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + } + + // Error put bucket with configuration + err = client.CreateBucket("ERRORBUCKETNAME", StorageClass(StorageArchive)) + c.Assert(err, NotNil) + + // Create bucket with configuration and test ListBuckets + for _, storage := range []StorageClassType{StorageStandard, StorageIA, StorageArchive} { + bucketNameTest := bucketNamePrefix + randLowStr(5) + err = client.CreateBucket(bucketNameTest, StorageClass(storage)) + c.Assert(err, IsNil) + + res, err := client.GetBucketInfo(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.BucketInfo.Name, Equals, bucketNameTest) + c.Assert(res.BucketInfo.StorageClass, Equals, string(storage)) + + // Delete + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + } +} + +// TestCreateBucketNegative +func (s *OssClientSuite) TestCreateBucketNegative(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Bucket name invalid + err = client.CreateBucket("xx") + c.Assert(err, NotNil) + + err = client.CreateBucket("XXXX") + c.Assert(err, NotNil) + testLogger.Println(err) + + err = client.CreateBucket("_bucket") + c.Assert(err, NotNil) + testLogger.Println(err) + + // ACL invalid + err = client.CreateBucket(bucketNamePrefix+"tcbn", ACL("InvaldAcl")) + c.Assert(err, NotNil) + testLogger.Println(err) +} + +// TestDeleteBucket +func (s *OssClientSuite) TestDeleteBucket(c *C) { + var bucketNameTest = bucketNamePrefix + "tdb" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Create + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // Check + found, err := client.IsBucketExist(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(found, Equals, true) + + // Delete + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(3 * time.Second) + + // Check + found, err = client.IsBucketExist(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(found, Equals, false) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, NotNil) +} + +// TestDeleteBucketNegative +func (s *OssClientSuite) TestDeleteBucketNegative(c *C) { + var bucketNameTest = bucketNamePrefix + "tdbn" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Bucket name invalid + err = client.DeleteBucket("xx") + c.Assert(err, NotNil) + + err = client.DeleteBucket("XXXX") + c.Assert(err, NotNil) + + err = client.DeleteBucket("_bucket") + c.Assert(err, NotNil) + + // Delete no exist bucket + err = client.DeleteBucket("notexist") + c.Assert(err, NotNil) + + // No permission to delete, this ak/sk for js sdk + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + accessID := "" + accessKey := "" + clientOtherUser, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = clientOtherUser.DeleteBucket(bucketNameTest) + c.Assert(err, NotNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestListBucket +func (s *OssClientSuite) TestListBucket(c *C) { + var bucketNameLbOne = bucketNamePrefix + "tlb1" + var bucketNameLbTwo = bucketNamePrefix + "tlb2" + var bucketNameLbThree = bucketNamePrefix + "tlb3" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // CreateBucket + err = client.CreateBucket(bucketNameLbOne) + c.Assert(err, IsNil) + err = client.CreateBucket(bucketNameLbTwo) + c.Assert(err, IsNil) + err = client.CreateBucket(bucketNameLbThree) + c.Assert(err, IsNil) + + // ListBuckets, specified prefix + lbr, err := client.ListBuckets(Prefix(bucketNamePrefix), MaxKeys(2)) + c.Assert(err, IsNil) + c.Assert(len(lbr.Buckets), Equals, 2) + + // ListBuckets, specified max keys + lbr, err = client.ListBuckets(MaxKeys(2)) + c.Assert(err, IsNil) + c.Assert(len(lbr.Buckets), Equals, 2) + + // ListBuckets, specified max keys + lbr, err = client.ListBuckets(Marker(bucketNameLbOne), MaxKeys(1)) + c.Assert(err, IsNil) + c.Assert(len(lbr.Buckets), Equals, 1) + + // ListBuckets, specified max keys + lbr, err = client.ListBuckets(Marker(bucketNameLbOne)) + c.Assert(err, IsNil) + c.Assert(len(lbr.Buckets) >= 2, Equals, true) + + // DeleteBucket + err = client.DeleteBucket(bucketNameLbOne) + c.Assert(err, IsNil) + err = client.DeleteBucket(bucketNameLbTwo) + c.Assert(err, IsNil) + err = client.DeleteBucket(bucketNameLbThree) + c.Assert(err, IsNil) +} + +// TestListBucket +func (s *OssClientSuite) TestIsBucketExist(c *C) { + var bucketNameLbOne = bucketNamePrefix + "tibe1" + var bucketNameLbTwo = bucketNamePrefix + "tibe11" + var bucketNameLbThree = bucketNamePrefix + "tibe111" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // CreateBucket + err = client.CreateBucket(bucketNameLbOne) + c.Assert(err, IsNil) + err = client.CreateBucket(bucketNameLbTwo) + c.Assert(err, IsNil) + err = client.CreateBucket(bucketNameLbThree) + c.Assert(err, IsNil) + + // Exist + exist, err := client.IsBucketExist(bucketNameLbTwo) + c.Assert(err, IsNil) + c.Assert(exist, Equals, true) + + exist, err = client.IsBucketExist(bucketNameLbThree) + c.Assert(err, IsNil) + c.Assert(exist, Equals, true) + + exist, err = client.IsBucketExist(bucketNameLbOne) + c.Assert(err, IsNil) + c.Assert(exist, Equals, true) + + // Not exist + exist, err = client.IsBucketExist(bucketNamePrefix + "tibe") + c.Assert(err, IsNil) + c.Assert(exist, Equals, false) + + exist, err = client.IsBucketExist(bucketNamePrefix + "tibe1111") + c.Assert(err, IsNil) + c.Assert(exist, Equals, false) + + // Negative + exist, err = client.IsBucketExist("BucketNameInvalid") + c.Assert(err, NotNil) + + // DeleteBucket + err = client.DeleteBucket(bucketNameLbOne) + c.Assert(err, IsNil) + err = client.DeleteBucket(bucketNameLbTwo) + c.Assert(err, IsNil) + err = client.DeleteBucket(bucketNameLbThree) + c.Assert(err, IsNil) +} + +// TestSetBucketAcl +func (s *OssClientSuite) TestSetBucketAcl(c *C) { + var bucketNameTest = bucketNamePrefix + "tsba" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Private + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + res, err := client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPrivate)) + + // Set ACL_PUBLIC_R + err = client.SetBucketACL(bucketNameTest, ACLPublicRead) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + res, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPublicRead)) + + // Set ACL_PUBLIC_RW + err = client.SetBucketACL(bucketNameTest, ACLPublicReadWrite) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + res, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPublicReadWrite)) + + // Set ACL_PUBLIC_RW + err = client.SetBucketACL(bucketNameTest, ACLPrivate) + c.Assert(err, IsNil) + err = client.SetBucketACL(bucketNameTest, ACLPrivate) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + res, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPrivate)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestSetBucketAclNegative +func (s *OssClientSuite) TestBucketAclNegative(c *C) { + var bucketNameTest = bucketNamePrefix + "tsban" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + err = client.SetBucketACL(bucketNameTest, "InvalidACL") + c.Assert(err, NotNil) + testLogger.Println(err) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestGetBucketAcl +func (s *OssClientSuite) TestGetBucketAcl(c *C) { + var bucketNameTest = bucketNamePrefix + "tgba" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Private + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + res, err := client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPrivate)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // PublicRead + err = client.CreateBucket(bucketNameTest, ACL(ACLPublicRead)) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + res, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPublicRead)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // PublicReadWrite + err = client.CreateBucket(bucketNameTest, ACL(ACLPublicReadWrite)) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + res, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPublicReadWrite)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestGetBucketAcl +func (s *OssClientSuite) TestGetBucketLocation(c *C) { + var bucketNameTest = bucketNamePrefix + "tgbl" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Private + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + loc, err := client.GetBucketLocation(bucketNameTest) + c.Assert(strings.HasPrefix(loc, "oss-"), Equals, true) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestGetBucketLocationNegative +func (s *OssClientSuite) TestGetBucketLocationNegative(c *C) { + var bucketNameTest = bucketNamePrefix + "tgblg" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Not exist + _, err = client.GetBucketLocation(bucketNameTest) + c.Assert(err, NotNil) + + // Not exist + _, err = client.GetBucketLocation("InvalidBucketName_") + c.Assert(err, NotNil) +} + +// TestSetBucketLifecycle +func (s *OssClientSuite) TestSetBucketLifecycle(c *C) { + var bucketNameTest = bucketNamePrefix + "tsbl" + var rule1 = BuildLifecycleRuleByDate("idone", "one", true, 2015, 11, 11) + var rule2 = BuildLifecycleRuleByDays("idtwo", "two", true, 3) + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + // Set single rule + var rules = []LifecycleRule{rule1} + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, IsNil) + // Double set rule + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, IsNil) + + res, err := client.GetBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(res.Rules), Equals, 1) + c.Assert(res.Rules[0].ID, Equals, "idone") + + err = client.DeleteBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + + // Set two rules + rules = []LifecycleRule{rule1, rule2} + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, IsNil) + + // Eliminate effect of cache + time.Sleep(5 * time.Second) + + res, err = client.GetBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(res.Rules), Equals, 2) + c.Assert(res.Rules[0].ID, Equals, "idone") + c.Assert(res.Rules[1].ID, Equals, "idtwo") + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestDeleteBucketLifecycle +func (s *OssClientSuite) TestDeleteBucketLifecycle(c *C) { + var bucketNameTest = bucketNamePrefix + "tdbl" + + var rule1 = BuildLifecycleRuleByDate("idone", "one", true, 2015, 11, 11) + var rule2 = BuildLifecycleRuleByDays("idtwo", "two", true, 3) + var rules = []LifecycleRule{rule1, rule2} + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + err = client.DeleteBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + res, err := client.GetBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(res.Rules), Equals, 2) + + // Delete + err = client.DeleteBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + res, err = client.GetBucketLifecycle(bucketNameTest) + c.Assert(err, NotNil) + + // Eliminate effect of cache + time.Sleep(time.Second * 3) + + // Delete when not set + err = client.DeleteBucketLifecycle(bucketNameTest) + c.Assert(err, IsNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestSetBucketLifecycleNegative +func (s *OssClientSuite) TestBucketLifecycleNegative(c *C) { + var bucketNameTest = bucketNamePrefix + "tsbln" + var rules = []LifecycleRule{} + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + // Set with no rule + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, NotNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + + // Not exist + err = client.SetBucketLifecycle(bucketNameTest, rules) + c.Assert(err, NotNil) + + // Not exist + _, err = client.GetBucketLifecycle(bucketNameTest) + c.Assert(err, NotNil) + + // Not exist + err = client.DeleteBucketLifecycle(bucketNameTest) + c.Assert(err, NotNil) +} + +// TestSetBucketReferer +func (s *OssClientSuite) TestSetBucketReferer(c *C) { + var bucketNameTest = bucketNamePrefix + "tsbr" + var referers = []string{"http://www.aliyun.com", "https://www.aliyun.com"} + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + res, err := client.GetBucketReferer(bucketNameTest) + c.Assert(res.AllowEmptyReferer, Equals, true) + c.Assert(len(res.RefererList), Equals, 0) + + // Set referers + err = client.SetBucketReferer(bucketNameTest, referers, false) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + res, err = client.GetBucketReferer(bucketNameTest) + c.Assert(res.AllowEmptyReferer, Equals, false) + c.Assert(len(res.RefererList), Equals, 2) + c.Assert(res.RefererList[0], Equals, "http://www.aliyun.com") + c.Assert(res.RefererList[1], Equals, "https://www.aliyun.com") + + // Reset referer, referers empty + referers = []string{""} + err = client.SetBucketReferer(bucketNameTest, referers, true) + c.Assert(err, IsNil) + + referers = []string{} + err = client.SetBucketReferer(bucketNameTest, referers, true) + c.Assert(err, IsNil) + + res, err = client.GetBucketReferer(bucketNameTest) + c.Assert(res.AllowEmptyReferer, Equals, true) + c.Assert(len(res.RefererList), Equals, 0) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestSetBucketRefererNegative +func (s *OssClientSuite) TestBucketRefererNegative(c *C) { + var bucketNameTest = bucketNamePrefix + "tsbrn" + var referers = []string{""} + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Not exist + _, err = client.GetBucketReferer(bucketNameTest) + c.Assert(err, NotNil) + testLogger.Println(err) + + // Not exist + err = client.SetBucketReferer(bucketNameTest, referers, true) + c.Assert(err, NotNil) + testLogger.Println(err) +} + +// TestSetBucketLogging +func (s *OssClientSuite) TestSetBucketLogging(c *C) { + var bucketNameTest = bucketNamePrefix + "tsbll" + var bucketNameTarget = bucketNamePrefix + "tsbllt" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + err = client.CreateBucket(bucketNameTarget) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // Set logging + err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", true) + c.Assert(err, IsNil) + // Reset + err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", false) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + res, err := client.GetBucketLogging(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.LoggingEnabled.TargetBucket, Equals, "") + c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "") + + err = client.DeleteBucketLogging(bucketNameTest) + c.Assert(err, IsNil) + + // Set to self + err = client.SetBucketLogging(bucketNameTest, bucketNameTest, "prefix", true) + c.Assert(err, IsNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + err = client.DeleteBucket(bucketNameTarget) + c.Assert(err, IsNil) +} + +// TestDeleteBucketLogging +func (s *OssClientSuite) TestDeleteBucketLogging(c *C) { + var bucketNameTest = bucketNamePrefix + "tdbl" + var bucketNameTarget = bucketNamePrefix + "tdblt" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + err = client.CreateBucket(bucketNameTarget) + c.Assert(err, IsNil) + + // Get when not set + res, err := client.GetBucketLogging(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.LoggingEnabled.TargetBucket, Equals, "") + c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "") + + // Set + err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", true) + c.Assert(err, IsNil) + + // Get + time.Sleep(5 * time.Second) + res, err = client.GetBucketLogging(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.LoggingEnabled.TargetBucket, Equals, bucketNameTarget) + c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "prefix") + + // Set + err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", false) + c.Assert(err, IsNil) + + // Get + time.Sleep(5 * time.Second) + res, err = client.GetBucketLogging(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.LoggingEnabled.TargetBucket, Equals, "") + c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "") + + // Delete + err = client.DeleteBucketLogging(bucketNameTest) + c.Assert(err, IsNil) + + // Get after delete + time.Sleep(5 * time.Second) + res, err = client.GetBucketLogging(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.LoggingEnabled.TargetBucket, Equals, "") + c.Assert(res.LoggingEnabled.TargetPrefix, Equals, "") + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + err = client.DeleteBucket(bucketNameTarget) + c.Assert(err, IsNil) +} + +// TestSetBucketLoggingNegative +func (s *OssClientSuite) TestSetBucketLoggingNegative(c *C) { + var bucketNameTest = bucketNamePrefix + "tsblnn" + var bucketNameTarget = bucketNamePrefix + "tsblnnt" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Not exist + _, err = client.GetBucketLogging(bucketNameTest) + c.Assert(err, NotNil) + + // Not exist + err = client.SetBucketLogging(bucketNameTest, "targetbucket", "prefix", true) + c.Assert(err, NotNil) + + // Not exist + err = client.DeleteBucketLogging(bucketNameTest) + c.Assert(err, NotNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // Target bucket not exist + err = client.SetBucketLogging(bucketNameTest, bucketNameTarget, "prefix", true) + c.Assert(err, NotNil) + + // Parameter invalid + err = client.SetBucketLogging(bucketNameTest, "XXXX", "prefix", true) + c.Assert(err, NotNil) + + err = client.SetBucketLogging(bucketNameTest, "xx", "prefix", true) + c.Assert(err, NotNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestSetBucketWebsite +func (s *OssClientSuite) TestSetBucketWebsite(c *C) { + var bucketNameTest = bucketNamePrefix + "tsbw" + var indexWebsite = "myindex.html" + var errorWebsite = "myerror.html" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + // Set + err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite) + c.Assert(err, IsNil) + + // Double set + err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite) + c.Assert(err, IsNil) + + res, err := client.GetBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite) + c.Assert(res.ErrorDocument.Key, Equals, errorWebsite) + + // Reset + err = client.SetBucketWebsite(bucketNameTest, "your"+indexWebsite, "your"+errorWebsite) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + res, err = client.GetBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.IndexDocument.Suffix, Equals, "your"+indexWebsite) + c.Assert(res.ErrorDocument.Key, Equals, "your"+errorWebsite) + + err = client.DeleteBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + + // Set after delete + err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite) + c.Assert(err, IsNil) + + // Eliminate effect of cache + time.Sleep(5 * time.Second) + + res, err = client.GetBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite) + c.Assert(res.ErrorDocument.Key, Equals, errorWebsite) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestDeleteBucketWebsite +func (s *OssClientSuite) TestDeleteBucketWebsite(c *C) { + var bucketNameTest = bucketNamePrefix + "tdbw" + var indexWebsite = "myindex.html" + var errorWebsite = "myerror.html" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + // Get + res, err := client.GetBucketWebsite(bucketNameTest) + c.Assert(err, NotNil) + + // Detele without set + err = client.DeleteBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + + // Set + err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + res, err = client.GetBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.IndexDocument.Suffix, Equals, indexWebsite) + c.Assert(res.ErrorDocument.Key, Equals, errorWebsite) + + // Detele + time.Sleep(5 * time.Second) + err = client.DeleteBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + res, err = client.GetBucketWebsite(bucketNameTest) + c.Assert(err, NotNil) + + // Detele after delete + err = client.DeleteBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestSetBucketWebsiteNegative +func (s *OssClientSuite) TestSetBucketWebsiteNegative(c *C) { + var bucketNameTest = bucketNamePrefix + "tdbw" + var indexWebsite = "myindex.html" + var errorWebsite = "myerror.html" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.DeleteBucket(bucketNameTest) + + // Not exist + _, err = client.GetBucketWebsite(bucketNameTest) + c.Assert(err, NotNil) + + err = client.DeleteBucketWebsite(bucketNameTest) + c.Assert(err, NotNil) + + err = client.SetBucketWebsite(bucketNameTest, indexWebsite, errorWebsite) + c.Assert(err, NotNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + // Set + time.Sleep(5 * time.Second) + err = client.SetBucketWebsite(bucketNameTest, "myindex", "myerror") + c.Assert(err, IsNil) + + res, err := client.GetBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.IndexDocument.Suffix, Equals, "myindex") + c.Assert(res.ErrorDocument.Key, Equals, "myerror") + + // Detele + err = client.DeleteBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + _, err = client.GetBucketWebsite(bucketNameTest) + c.Assert(err, NotNil) + + // Detele after delete + err = client.DeleteBucketWebsite(bucketNameTest) + c.Assert(err, IsNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestSetBucketWebsite +func (s *OssClientSuite) TestSetBucketCORS(c *C) { + var bucketNameTest = bucketNamePrefix + "tsbc" + var rule1 = CORSRule{ + AllowedOrigin: []string{"*"}, + AllowedMethod: []string{"PUT", "GET", "POST"}, + AllowedHeader: []string{}, + ExposeHeader: []string{}, + MaxAgeSeconds: 100, + } + + var rule2 = CORSRule{ + AllowedOrigin: []string{"http://www.a.com", "http://www.b.com"}, + AllowedMethod: []string{"GET"}, + AllowedHeader: []string{"Authorization"}, + ExposeHeader: []string{"x-oss-test", "x-oss-test1"}, + MaxAgeSeconds: 200, + } + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // Set + err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1}) + c.Assert(err, IsNil) + + gbcr, err := client.GetBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(gbcr.CORSRules), Equals, 1) + c.Assert(len(gbcr.CORSRules[0].AllowedOrigin), Equals, 1) + c.Assert(len(gbcr.CORSRules[0].AllowedMethod), Equals, 3) + c.Assert(len(gbcr.CORSRules[0].AllowedHeader), Equals, 0) + c.Assert(len(gbcr.CORSRules[0].ExposeHeader), Equals, 0) + c.Assert(gbcr.CORSRules[0].MaxAgeSeconds, Equals, 100) + + // Double set + err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1}) + c.Assert(err, IsNil) + + gbcr, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(gbcr.CORSRules), Equals, 1) + c.Assert(len(gbcr.CORSRules[0].AllowedOrigin), Equals, 1) + c.Assert(len(gbcr.CORSRules[0].AllowedMethod), Equals, 3) + c.Assert(len(gbcr.CORSRules[0].AllowedHeader), Equals, 0) + c.Assert(len(gbcr.CORSRules[0].ExposeHeader), Equals, 0) + c.Assert(gbcr.CORSRules[0].MaxAgeSeconds, Equals, 100) + + // Set rule2 + err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule2}) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + gbcr, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(gbcr.CORSRules), Equals, 1) + c.Assert(len(gbcr.CORSRules[0].AllowedOrigin), Equals, 2) + c.Assert(len(gbcr.CORSRules[0].AllowedMethod), Equals, 1) + c.Assert(len(gbcr.CORSRules[0].AllowedHeader), Equals, 1) + c.Assert(len(gbcr.CORSRules[0].ExposeHeader), Equals, 2) + c.Assert(gbcr.CORSRules[0].MaxAgeSeconds, Equals, 200) + + // Reset + err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1, rule2}) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + gbcr, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(gbcr.CORSRules), Equals, 2) + + // Set after delete + err = client.DeleteBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + + err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule1, rule2}) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + gbcr, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(len(gbcr.CORSRules), Equals, 2) + + err = client.DeleteBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestDeleteBucketCORS +func (s *OssClientSuite) TestDeleteBucketCORS(c *C) { + var bucketNameTest = bucketNamePrefix + "tdbc" + var rule = CORSRule{ + AllowedOrigin: []string{"*"}, + AllowedMethod: []string{"PUT", "GET", "POST"}, + AllowedHeader: []string{}, + ExposeHeader: []string{}, + MaxAgeSeconds: 100, + } + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + // Delete not set + err = client.DeleteBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + + // Set + err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule}) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + _, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + + // Detele + err = client.DeleteBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + _, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, NotNil) + + // Detele after deleting + err = client.DeleteBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestSetBucketCORSNegative +func (s *OssClientSuite) TestSetBucketCORSNegative(c *C) { + var bucketNameTest = bucketNamePrefix + "tsbcn" + var rule = CORSRule{ + AllowedOrigin: []string{"*"}, + AllowedMethod: []string{"PUT", "GET", "POST"}, + AllowedHeader: []string{}, + ExposeHeader: []string{}, + MaxAgeSeconds: 100, + } + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.DeleteBucket(bucketNameTest) + + // Not exist + _, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, NotNil) + + err = client.DeleteBucketCORS(bucketNameTest) + c.Assert(err, NotNil) + + err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule}) + c.Assert(err, NotNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + _, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, NotNil) + + // Set + err = client.SetBucketCORS(bucketNameTest, []CORSRule{rule}) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + _, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + + // Delete + err = client.DeleteBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + _, err = client.GetBucketCORS(bucketNameTest) + c.Assert(err, NotNil) + + // Delete after deleting + err = client.DeleteBucketCORS(bucketNameTest) + c.Assert(err, IsNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestGetBucketInfo +func (s *OssClientSuite) TestGetBucketInfo(c *C) { + var bucketNameTest = bucketNamePrefix + "tgbi" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + res, err := client.GetBucketInfo(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.BucketInfo.Name, Equals, bucketNameTest) + c.Assert(strings.HasPrefix(res.BucketInfo.Location, "oss-"), Equals, true) + c.Assert(res.BucketInfo.ACL, Equals, "private") + c.Assert(strings.HasSuffix(res.BucketInfo.ExtranetEndpoint, ".com"), Equals, true) + c.Assert(strings.HasSuffix(res.BucketInfo.IntranetEndpoint, ".com"), Equals, true) + c.Assert(res.BucketInfo.CreationDate, NotNil) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestGetBucketInfoNegative +func (s *OssClientSuite) TestGetBucketInfoNegative(c *C) { + var bucketNameTest = bucketNamePrefix + "tgbig" + + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + // Not exist + _, err = client.GetBucketInfo(bucketNameTest) + c.Assert(err, NotNil) + + // Bucket name invalid + _, err = client.GetBucketInfo("InvalidBucketName_") + c.Assert(err, NotNil) +} + +// TestEndpointFormat +func (s *OssClientSuite) TestEndpointFormat(c *C) { + var bucketNameTest = bucketNamePrefix + "tef" + + // http://host + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + res, err := client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPrivate)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + // http://host:port + client, err = New(endpoint+":80", accessID, accessKey) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + time.Sleep(5 * time.Second) + res, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPrivate)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestCname +func (s *OssClientSuite) _TestCname(c *C) { + var bucketNameTest = "" + + client, err := New("", "", "", UseCname(true)) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + _, err = client.ListBuckets() + c.Assert(err, NotNil) + + res, err := client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPrivate)) +} + +// TestCnameNegative +func (s *OssClientSuite) _TestCnameNegative(c *C) { + var bucketNameTest = "" + + client, err := New("", "", "", UseCname(true)) + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, NotNil) + + _, err = client.ListBuckets() + c.Assert(err, NotNil) + + _, err = client.GetBucketACL(bucketNameTest) + c.Assert(err, NotNil) +} + +// _TestHTTPS +func (s *OssClientSuite) _TestHTTPS(c *C) { + var bucketNameTest = "" + + client, err := New("", "", "") + c.Assert(err, IsNil) + + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + res, err := client.GetBucketACL(bucketNameTest) + c.Assert(err, IsNil) + c.Assert(res.ACL, Equals, string(ACLPrivate)) + + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// TestClientOption +func (s *OssClientSuite) TestClientOption(c *C) { + var bucketNameTest = bucketNamePrefix + "tco" + + client, err := New(endpoint, accessID, accessKey, UseCname(true), + Timeout(11, 12), SecurityToken("token"), Proxy(proxyHost)) + c.Assert(err, IsNil) + + // CreateBucket timeout + err = client.CreateBucket(bucketNameTest) + c.Assert(err, NotNil) + + c.Assert(client.Conn.config.HTTPTimeout.ConnectTimeout, Equals, time.Second*11) + c.Assert(client.Conn.config.HTTPTimeout.ReadWriteTimeout, Equals, time.Second*12) + c.Assert(client.Conn.config.HTTPTimeout.HeaderTimeout, Equals, time.Second*12) + c.Assert(client.Conn.config.HTTPTimeout.IdleConnTimeout, Equals, time.Second*12) + c.Assert(client.Conn.config.HTTPTimeout.LongTimeout, Equals, time.Second*12*10) + + c.Assert(client.Conn.config.SecurityToken, Equals, "token") + c.Assert(client.Conn.config.IsCname, Equals, true) + + c.Assert(client.Conn.config.IsUseProxy, Equals, true) + c.Assert(client.Config.ProxyHost, Equals, proxyHost) + + client, err = New(endpoint, accessID, accessKey, AuthProxy(proxyHost, proxyUser, proxyPasswd)) + + c.Assert(client.Conn.config.IsUseProxy, Equals, true) + c.Assert(client.Config.ProxyHost, Equals, proxyHost) + c.Assert(client.Conn.config.IsAuthProxy, Equals, true) + c.Assert(client.Conn.config.ProxyUser, Equals, proxyUser) + c.Assert(client.Conn.config.ProxyPassword, Equals, proxyPasswd) + + client, err = New(endpoint, accessID, accessKey, UserAgent("go sdk user agent")) + c.Assert(client.Conn.config.UserAgent, Equals, "go sdk user agent") + + // Check we can overide the http.Client + httpClient := new(http.Client) + client, err = New(endpoint, accessID, accessKey, HTTPClient(httpClient)) + c.Assert(client.HTTPClient, Equals, httpClient) + c.Assert(client.Conn.client, Equals, httpClient) + client, err = New(endpoint, accessID, accessKey) + c.Assert(client.HTTPClient, IsNil) +} + +// TestProxy +func (s *OssClientSuite) TestProxy(c *C) { + bucketNameTest := bucketNamePrefix + "tp" + objectName := "体育/奥运/首金" + objectValue := "大江东去,浪淘尽,千古风流人物。 故垒西边,人道是、三国周郎赤壁。" + + client, err := New(endpoint, accessID, accessKey, AuthProxy(proxyHost, proxyUser, proxyPasswd)) + + // Create bucket + err = client.CreateBucket(bucketNameTest) + c.Assert(err, IsNil) + + // Get bucket info + _, err = client.GetBucketInfo(bucketNameTest) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketNameTest) + + // Sign URL + str, err := bucket.SignURL(objectName, HTTPPut, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Put object with URL + err = bucket.PutObjectWithURL(str, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Sign URL for get object + str, err = bucket.SignURL(objectName, HTTPGet, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Get object with URL + body, err := bucket.GetObjectWithURL(str) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // Put object + err = bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // Get object + _, err = bucket.GetObject(objectName) + c.Assert(err, IsNil) + + // List objects + _, err = bucket.ListObjects() + c.Assert(err, IsNil) + + // Delete object + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Delete bucket + err = client.DeleteBucket(bucketNameTest) + c.Assert(err, IsNil) +} + +// Private +func (s *OssClientSuite) checkBucket(buckets []BucketProperties, bucket string) bool { + for _, v := range buckets { + if v.Name == bucket { + return true + } + } + return false +} + +func (s *OssClientSuite) getBucket(buckets []BucketProperties, bucket string) (bool, BucketProperties) { + for _, v := range buckets { + if v.Name == bucket { + return true, v + } + } + return false, BucketProperties{} +} + +func (s *OssClientSuite) TestHttpLogNotSignUrl(c *C) { + logName := "." + string(os.PathSeparator) + "test-go-sdk-httpdebug.log" + randStr(5) + f, err := os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660) + c.Assert(err, IsNil) + + client, err := New(endpoint, accessID, accessKey) + client.Config.LogLevel = Debug + + client.Config.Logger = log.New(f, "", log.LstdFlags) + + var testBucketName = bucketNamePrefix + strings.ToLower(randStr(5)) + + // CreateBucket + err = client.CreateBucket(testBucketName) + f.Close() + + // read log file,get http info + contents, err := ioutil.ReadFile(logName) + c.Assert(err, IsNil) + + httpContent := string(contents) + //fmt.Println(httpContent) + + c.Assert(strings.Contains(httpContent, "signStr"), Equals, true) + c.Assert(strings.Contains(httpContent, "Method:"), Equals, true) + + // delete test bucket and log + os.Remove(logName) + client.DeleteBucket(testBucketName) +} + +func (s *OssClientSuite) TestHttpLogSignUrl(c *C) { + logName := "." + string(os.PathSeparator) + "test-go-sdk-httpdebug-signurl.log" + randStr(5) + f, err := os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660) + c.Assert(err, IsNil) + + client, err := New(endpoint, accessID, accessKey) + client.Config.LogLevel = Debug + client.Config.Logger = log.New(f, "", log.LstdFlags) + + var testBucketName = bucketNamePrefix + strings.ToLower(randStr(5)) + + // CreateBucket + err = client.CreateBucket(testBucketName) + f.Close() + + // clear log + f, err = os.OpenFile(logName, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0660) + client.Config.Logger = log.New(f, "", log.LstdFlags) + + bucket, _ := client.Bucket(testBucketName) + objectName := objectNamePrefix + randStr(5) + objectValue := randStr(20) + + // Sign URL for put + str, err := bucket.SignURL(objectName, HTTPPut, 60) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Error put object with URL + err = bucket.PutObjectWithURL(str, strings.NewReader(objectValue), ContentType("image/tiff")) + f.Close() + + // read log file,get http info + contents, err := ioutil.ReadFile(logName) + c.Assert(err, IsNil) + + httpContent := string(contents) + //fmt.Println(httpContent) + + c.Assert(strings.Contains(httpContent, "signStr"), Equals, true) + c.Assert(strings.Contains(httpContent, "Method:"), Equals, true) + + // delete test bucket and log + os.Remove(logName) + client.DeleteBucket(testBucketName) +} + +func (s *OssClientSuite) TestSetLimitUploadSpeed(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + err = client.LimitUploadSpeed(100) + + goVersion := runtime.Version() + pSlice := strings.Split(strings.ToLower(goVersion), ".") + + // compare with go1.7 + if len(pSlice) >= 2 { + if pSlice[0] > "go1" { + c.Assert(err, IsNil) + } else if pSlice[0] == "go1" && pSlice[1] >= "7" { + c.Assert(err, IsNil) + } else { + c.Assert(err, NotNil) + } + } else { + c.Assert(err, NotNil) + } +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conf.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conf.go new file mode 100644 index 000000000..8886102d0 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conf.go @@ -0,0 +1,128 @@ +package oss + +import ( + "bytes" + "fmt" + "log" + "os" + "time" +) + +const ( + LogOff = iota + Error + Warn + Info + Debug +) + +var LogTag = []string{"[error]", "[warn]", "[info]", "[debug]"} + +// HTTPTimeout defines HTTP timeout. +type HTTPTimeout struct { + ConnectTimeout time.Duration + ReadWriteTimeout time.Duration + HeaderTimeout time.Duration + LongTimeout time.Duration + IdleConnTimeout time.Duration +} + +type HTTPMaxConns struct { + MaxIdleConns int + MaxIdleConnsPerHost int +} + +// Config defines oss configuration +type Config struct { + Endpoint string // OSS endpoint + AccessKeyID string // AccessId + AccessKeySecret string // AccessKey + RetryTimes uint // Retry count by default it's 5. + UserAgent string // SDK name/version/system information + IsDebug bool // Enable debug mode. Default is false. + Timeout uint // Timeout in seconds. By default it's 60. + SecurityToken string // STS Token + IsCname bool // If cname is in the endpoint. + HTTPTimeout HTTPTimeout // HTTP timeout + HTTPMaxConns HTTPMaxConns // Http max connections + IsUseProxy bool // Flag of using proxy. + ProxyHost string // Flag of using proxy host. + IsAuthProxy bool // Flag of needing authentication. + ProxyUser string // Proxy user + ProxyPassword string // Proxy password + IsEnableMD5 bool // Flag of enabling MD5 for upload. + MD5Threshold int64 // Memory footprint threshold for each MD5 computation (16MB is the default), in byte. When the data is more than that, temp file is used. + IsEnableCRC bool // Flag of enabling CRC for upload. + LogLevel int // Log level + Logger *log.Logger // For write log + UploadLimitSpeed int // Upload limit speed:KB/s, 0 is unlimited + UploadLimiter *OssLimiter // Bandwidth limit reader for upload +} + +// LimitUploadSpeed, uploadSpeed:KB/s, 0 is unlimited,default is 0 +func (config *Config) LimitUploadSpeed(uploadSpeed int) error { + if uploadSpeed < 0 { + return fmt.Errorf("erro,speed is less than 0") + } else if uploadSpeed == 0 { + config.UploadLimitSpeed = 0 + config.UploadLimiter = nil + return nil + } + + var err error + config.UploadLimiter, err = GetOssLimiter(uploadSpeed) + if err == nil { + config.UploadLimitSpeed = uploadSpeed + } + return err +} + +// WriteLog +func (config *Config) WriteLog(LogLevel int, format string, a ...interface{}) { + if config.LogLevel < LogLevel || config.Logger == nil { + return + } + + var logBuffer bytes.Buffer + logBuffer.WriteString(LogTag[LogLevel-1]) + logBuffer.WriteString(fmt.Sprintf(format, a...)) + config.Logger.Printf("%s", logBuffer.String()) +} + +// getDefaultOssConfig gets the default configuration. +func getDefaultOssConfig() *Config { + config := Config{} + + config.Endpoint = "" + config.AccessKeyID = "" + config.AccessKeySecret = "" + config.RetryTimes = 5 + config.IsDebug = false + config.UserAgent = userAgent() + config.Timeout = 60 // Seconds + config.SecurityToken = "" + config.IsCname = false + + config.HTTPTimeout.ConnectTimeout = time.Second * 30 // 30s + config.HTTPTimeout.ReadWriteTimeout = time.Second * 60 // 60s + config.HTTPTimeout.HeaderTimeout = time.Second * 60 // 60s + config.HTTPTimeout.LongTimeout = time.Second * 300 // 300s + config.HTTPTimeout.IdleConnTimeout = time.Second * 50 // 50s + config.HTTPMaxConns.MaxIdleConns = 100 + config.HTTPMaxConns.MaxIdleConnsPerHost = 100 + + config.IsUseProxy = false + config.ProxyHost = "" + config.IsAuthProxy = false + config.ProxyUser = "" + config.ProxyPassword = "" + + config.MD5Threshold = 16 * 1024 * 1024 // 16MB + config.IsEnableMD5 = false + config.IsEnableCRC = true + + config.LogLevel = LogOff + config.Logger = log.New(os.Stdout, "", log.LstdFlags) + + return &config +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn.go new file mode 100644 index 000000000..896295c33 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn.go @@ -0,0 +1,730 @@ +package oss + +import ( + "bytes" + "crypto/md5" + "encoding/base64" + "encoding/json" + "encoding/xml" + "fmt" + "hash" + "io" + "io/ioutil" + "net" + "net/http" + "net/url" + "os" + "sort" + "strconv" + "strings" + "time" +) + +// Conn defines OSS Conn +type Conn struct { + config *Config + url *urlMaker + client *http.Client +} + +var signKeyList = []string{"acl", "uploads", "location", "cors", "logging", "website", "referer", "lifecycle", "delete", "append", "tagging", "objectMeta", "uploadId", "partNumber", "security-token", "position", "img", "style", "styleName", "replication", "replicationProgress", "replicationLocation", "cname", "bucketInfo", "comp", "qos", "live", "status", "vod", "startTime", "endTime", "symlink", "x-oss-process", "response-content-type", "response-content-language", "response-expires", "response-cache-control", "response-content-disposition", "response-content-encoding", "udf", "udfName", "udfImage", "udfId", "udfImageDesc", "udfApplication", "comp", "udfApplicationLog", "restore", "callback", "callback-var"} + +// init initializes Conn +func (conn *Conn) init(config *Config, urlMaker *urlMaker, client *http.Client) error { + if client == nil { + // New transport + transport := newTransport(conn, config) + + // Proxy + if conn.config.IsUseProxy { + proxyURL, err := url.Parse(config.ProxyHost) + if err != nil { + return err + } + transport.Proxy = http.ProxyURL(proxyURL) + } + client = &http.Client{Transport: transport} + } + + conn.config = config + conn.url = urlMaker + conn.client = client + + return nil +} + +// Do sends request and returns the response +func (conn Conn) Do(method, bucketName, objectName string, params map[string]interface{}, headers map[string]string, + data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) { + urlParams := conn.getURLParams(params) + subResource := conn.getSubResource(params) + uri := conn.url.getURL(bucketName, objectName, urlParams) + resource := conn.url.getResource(bucketName, objectName, subResource) + return conn.doRequest(method, uri, resource, headers, data, initCRC, listener) +} + +// DoURL sends the request with signed URL and returns the response result. +func (conn Conn) DoURL(method HTTPMethod, signedURL string, headers map[string]string, + data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) { + // Get URI from signedURL + uri, err := url.ParseRequestURI(signedURL) + if err != nil { + return nil, err + } + + m := strings.ToUpper(string(method)) + req := &http.Request{ + Method: m, + URL: uri, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + Host: uri.Host, + } + + tracker := &readerTracker{completedBytes: 0} + fd, crc := conn.handleBody(req, data, initCRC, listener, tracker) + if fd != nil { + defer func() { + fd.Close() + os.Remove(fd.Name()) + }() + } + + if conn.config.IsAuthProxy { + auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword + basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + req.Header.Set("Proxy-Authorization", basic) + } + + req.Header.Set(HTTPHeaderHost, conn.config.Endpoint) + req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent) + + if headers != nil { + for k, v := range headers { + req.Header.Set(k, v) + } + } + + // Transfer started + event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength) + publishProgress(listener, event) + + if conn.config.LogLevel >= Debug { + conn.LoggerHttpReq(req) + } + + resp, err := conn.client.Do(req) + if err != nil { + // Transfer failed + event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength) + publishProgress(listener, event) + return nil, err + } + + if conn.config.LogLevel >= Debug { + //print out http resp + conn.LoggerHttpResp(req, resp) + } + + // Transfer completed + event = newProgressEvent(TransferCompletedEvent, tracker.completedBytes, req.ContentLength) + publishProgress(listener, event) + + return conn.handleResponse(resp, crc) +} + +func (conn Conn) getURLParams(params map[string]interface{}) string { + // Sort + keys := make([]string, 0, len(params)) + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + + // Serialize + var buf bytes.Buffer + for _, k := range keys { + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(url.QueryEscape(k)) + if params[k] != nil { + buf.WriteString("=" + url.QueryEscape(params[k].(string))) + } + } + + return buf.String() +} + +func (conn Conn) getSubResource(params map[string]interface{}) string { + // Sort + keys := make([]string, 0, len(params)) + for k := range params { + if conn.isParamSign(k) { + keys = append(keys, k) + } + } + sort.Strings(keys) + + // Serialize + var buf bytes.Buffer + for _, k := range keys { + if buf.Len() > 0 { + buf.WriteByte('&') + } + buf.WriteString(k) + if params[k] != nil { + buf.WriteString("=" + params[k].(string)) + } + } + + return buf.String() +} + +func (conn Conn) isParamSign(paramKey string) bool { + for _, k := range signKeyList { + if paramKey == k { + return true + } + } + return false +} + +func (conn Conn) doRequest(method string, uri *url.URL, canonicalizedResource string, headers map[string]string, + data io.Reader, initCRC uint64, listener ProgressListener) (*Response, error) { + method = strings.ToUpper(method) + req := &http.Request{ + Method: method, + URL: uri, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + Host: uri.Host, + } + + tracker := &readerTracker{completedBytes: 0} + fd, crc := conn.handleBody(req, data, initCRC, listener, tracker) + if fd != nil { + defer func() { + fd.Close() + os.Remove(fd.Name()) + }() + } + + if conn.config.IsAuthProxy { + auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword + basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + req.Header.Set("Proxy-Authorization", basic) + } + + date := time.Now().UTC().Format(http.TimeFormat) + req.Header.Set(HTTPHeaderDate, date) + req.Header.Set(HTTPHeaderHost, conn.config.Endpoint) + req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent) + if conn.config.SecurityToken != "" { + req.Header.Set(HTTPHeaderOssSecurityToken, conn.config.SecurityToken) + } + + if headers != nil { + for k, v := range headers { + req.Header.Set(k, v) + } + } + + conn.signHeader(req, canonicalizedResource) + + // Transfer started + event := newProgressEvent(TransferStartedEvent, 0, req.ContentLength) + publishProgress(listener, event) + + if conn.config.LogLevel >= Debug { + conn.LoggerHttpReq(req) + } + + resp, err := conn.client.Do(req) + + if err != nil { + // Transfer failed + event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength) + publishProgress(listener, event) + return nil, err + } + + if conn.config.LogLevel >= Debug { + //print out http resp + conn.LoggerHttpResp(req, resp) + } + + // Transfer completed + event = newProgressEvent(TransferCompletedEvent, tracker.completedBytes, req.ContentLength) + publishProgress(listener, event) + + return conn.handleResponse(resp, crc) +} + +func (conn Conn) signURL(method HTTPMethod, bucketName, objectName string, expiration int64, params map[string]interface{}, headers map[string]string) string { + if conn.config.SecurityToken != "" { + params[HTTPParamSecurityToken] = conn.config.SecurityToken + } + subResource := conn.getSubResource(params) + canonicalizedResource := conn.url.getResource(bucketName, objectName, subResource) + + m := strings.ToUpper(string(method)) + req := &http.Request{ + Method: m, + Header: make(http.Header), + } + + if conn.config.IsAuthProxy { + auth := conn.config.ProxyUser + ":" + conn.config.ProxyPassword + basic := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + req.Header.Set("Proxy-Authorization", basic) + } + + req.Header.Set(HTTPHeaderDate, strconv.FormatInt(expiration, 10)) + req.Header.Set(HTTPHeaderHost, conn.config.Endpoint) + req.Header.Set(HTTPHeaderUserAgent, conn.config.UserAgent) + + if headers != nil { + for k, v := range headers { + req.Header.Set(k, v) + } + } + + signedStr := conn.getSignedStr(req, canonicalizedResource) + + params[HTTPParamExpires] = strconv.FormatInt(expiration, 10) + params[HTTPParamAccessKeyID] = conn.config.AccessKeyID + params[HTTPParamSignature] = signedStr + + urlParams := conn.getURLParams(params) + return conn.url.getSignURL(bucketName, objectName, urlParams) +} + +func (conn Conn) signRtmpURL(bucketName, channelName, playlistName string, expiration int64) string { + params := map[string]interface{}{} + if playlistName != "" { + params[HTTPParamPlaylistName] = playlistName + } + expireStr := strconv.FormatInt(expiration, 10) + params[HTTPParamExpires] = expireStr + + if conn.config.AccessKeyID != "" { + params[HTTPParamAccessKeyID] = conn.config.AccessKeyID + if conn.config.SecurityToken != "" { + params[HTTPParamSecurityToken] = conn.config.SecurityToken + } + signedStr := conn.getRtmpSignedStr(bucketName, channelName, playlistName, expiration, params) + params[HTTPParamSignature] = signedStr + } + + urlParams := conn.getURLParams(params) + return conn.url.getSignRtmpURL(bucketName, channelName, urlParams) +} + +// handleBody handles request body +func (conn Conn) handleBody(req *http.Request, body io.Reader, initCRC uint64, + listener ProgressListener, tracker *readerTracker) (*os.File, hash.Hash64) { + var file *os.File + var crc hash.Hash64 + reader := body + + // Length + switch v := body.(type) { + case *bytes.Buffer: + req.ContentLength = int64(v.Len()) + case *bytes.Reader: + req.ContentLength = int64(v.Len()) + case *strings.Reader: + req.ContentLength = int64(v.Len()) + case *os.File: + req.ContentLength = tryGetFileSize(v) + case *io.LimitedReader: + req.ContentLength = int64(v.N) + } + req.Header.Set(HTTPHeaderContentLength, strconv.FormatInt(req.ContentLength, 10)) + + // MD5 + if body != nil && conn.config.IsEnableMD5 && req.Header.Get(HTTPHeaderContentMD5) == "" { + md5 := "" + reader, md5, file, _ = calcMD5(body, req.ContentLength, conn.config.MD5Threshold) + req.Header.Set(HTTPHeaderContentMD5, md5) + } + + // CRC + if reader != nil && conn.config.IsEnableCRC { + crc = NewCRC(crcTable(), initCRC) + reader = TeeReader(reader, crc, req.ContentLength, listener, tracker) + } + + // HTTP body + rc, ok := reader.(io.ReadCloser) + if !ok && reader != nil { + rc = ioutil.NopCloser(reader) + } + + if conn.isUploadLimitReq(req) { + limitReader := &LimitSpeedReader{ + reader: rc, + ossLimiter: conn.config.UploadLimiter, + } + req.Body = limitReader + } else { + req.Body = rc + } + return file, crc +} + +// isUploadLimitReq: judge limit upload speed or not +func (conn Conn) isUploadLimitReq(req *http.Request) bool { + if conn.config.UploadLimitSpeed == 0 || conn.config.UploadLimiter == nil { + return false + } + + if req.Method != "GET" && req.Method != "DELETE" && req.Method != "HEAD" { + if req.ContentLength > 0 { + return true + } + } + return false +} + +func tryGetFileSize(f *os.File) int64 { + fInfo, _ := f.Stat() + return fInfo.Size() +} + +// handleResponse handles response +func (conn Conn) handleResponse(resp *http.Response, crc hash.Hash64) (*Response, error) { + var cliCRC uint64 + var srvCRC uint64 + + statusCode := resp.StatusCode + if statusCode >= 400 && statusCode <= 505 { + // 4xx and 5xx indicate that the operation has error occurred + var respBody []byte + respBody, err := readResponseBody(resp) + if err != nil { + return nil, err + } + + if len(respBody) == 0 { + err = ServiceError{ + StatusCode: statusCode, + RequestID: resp.Header.Get(HTTPHeaderOssRequestID), + } + } else { + // Response contains storage service error object, unmarshal + srvErr, errIn := serviceErrFromXML(respBody, resp.StatusCode, + resp.Header.Get(HTTPHeaderOssRequestID)) + if errIn != nil { // error unmarshaling the error response + err = fmt.Errorf("oss: service returned invalid response body, status = %s, RequestId = %s", resp.Status, resp.Header.Get(HTTPHeaderOssRequestID)) + } else { + err = srvErr + } + } + + return &Response{ + StatusCode: resp.StatusCode, + Headers: resp.Header, + Body: ioutil.NopCloser(bytes.NewReader(respBody)), // restore the body + }, err + } else if statusCode >= 300 && statusCode <= 307 { + // OSS use 3xx, but response has no body + err := fmt.Errorf("oss: service returned %d,%s", resp.StatusCode, resp.Status) + return &Response{ + StatusCode: resp.StatusCode, + Headers: resp.Header, + Body: resp.Body, + }, err + } + + if conn.config.IsEnableCRC && crc != nil { + cliCRC = crc.Sum64() + } + srvCRC, _ = strconv.ParseUint(resp.Header.Get(HTTPHeaderOssCRC64), 10, 64) + + // 2xx, successful + return &Response{ + StatusCode: resp.StatusCode, + Headers: resp.Header, + Body: resp.Body, + ClientCRC: cliCRC, + ServerCRC: srvCRC, + }, nil +} + +func (conn Conn) LoggerHttpReq(req *http.Request) { + var logBuffer bytes.Buffer + logBuffer.WriteString(fmt.Sprintf("[Req:%p]Method:%s\t", req, req.Method)) + logBuffer.WriteString(fmt.Sprintf("Host:%s\t", req.URL.Host)) + logBuffer.WriteString(fmt.Sprintf("Path:%s\t", req.URL.Path)) + logBuffer.WriteString(fmt.Sprintf("Query:%s\t", req.URL.RawQuery)) + logBuffer.WriteString(fmt.Sprintf("Header info:")) + + for k, v := range req.Header { + var valueBuffer bytes.Buffer + for j := 0; j < len(v); j++ { + if j > 0 { + valueBuffer.WriteString(" ") + } + valueBuffer.WriteString(v[j]) + } + logBuffer.WriteString(fmt.Sprintf("\t%s:%s", k, valueBuffer.String())) + } + conn.config.WriteLog(Debug, "%s\n", logBuffer.String()) +} + +func (conn Conn) LoggerHttpResp(req *http.Request, resp *http.Response) { + var logBuffer bytes.Buffer + logBuffer.WriteString(fmt.Sprintf("[Resp:%p]StatusCode:%d\t", req, resp.StatusCode)) + logBuffer.WriteString(fmt.Sprintf("Header info:")) + for k, v := range resp.Header { + var valueBuffer bytes.Buffer + for j := 0; j < len(v); j++ { + if j > 0 { + valueBuffer.WriteString(" ") + } + valueBuffer.WriteString(v[j]) + } + logBuffer.WriteString(fmt.Sprintf("\t%s:%s", k, valueBuffer.String())) + } + conn.config.WriteLog(Debug, "%s\n", logBuffer.String()) +} + +func calcMD5(body io.Reader, contentLen, md5Threshold int64) (reader io.Reader, b64 string, tempFile *os.File, err error) { + if contentLen == 0 || contentLen > md5Threshold { + // Huge body, use temporary file + tempFile, err = ioutil.TempFile(os.TempDir(), TempFilePrefix) + if tempFile != nil { + io.Copy(tempFile, body) + tempFile.Seek(0, os.SEEK_SET) + md5 := md5.New() + io.Copy(md5, tempFile) + sum := md5.Sum(nil) + b64 = base64.StdEncoding.EncodeToString(sum[:]) + tempFile.Seek(0, os.SEEK_SET) + reader = tempFile + } + } else { + // Small body, use memory + buf, _ := ioutil.ReadAll(body) + sum := md5.Sum(buf) + b64 = base64.StdEncoding.EncodeToString(sum[:]) + reader = bytes.NewReader(buf) + } + return +} + +func readResponseBody(resp *http.Response) ([]byte, error) { + defer resp.Body.Close() + out, err := ioutil.ReadAll(resp.Body) + if err == io.EOF { + err = nil + } + return out, err +} + +func serviceErrFromXML(body []byte, statusCode int, requestID string) (ServiceError, error) { + var storageErr ServiceError + + if err := xml.Unmarshal(body, &storageErr); err != nil { + return storageErr, err + } + + storageErr.StatusCode = statusCode + storageErr.RequestID = requestID + storageErr.RawMessage = string(body) + return storageErr, nil +} + +func xmlUnmarshal(body io.Reader, v interface{}) error { + data, err := ioutil.ReadAll(body) + if err != nil { + return err + } + return xml.Unmarshal(data, v) +} + +func jsonUnmarshal(body io.Reader, v interface{}) error { + data, err := ioutil.ReadAll(body) + if err != nil { + return err + } + return json.Unmarshal(data, v) +} + +// timeoutConn handles HTTP timeout +type timeoutConn struct { + conn net.Conn + timeout time.Duration + longTimeout time.Duration +} + +func newTimeoutConn(conn net.Conn, timeout time.Duration, longTimeout time.Duration) *timeoutConn { + conn.SetReadDeadline(time.Now().Add(longTimeout)) + return &timeoutConn{ + conn: conn, + timeout: timeout, + longTimeout: longTimeout, + } +} + +func (c *timeoutConn) Read(b []byte) (n int, err error) { + c.SetReadDeadline(time.Now().Add(c.timeout)) + n, err = c.conn.Read(b) + c.SetReadDeadline(time.Now().Add(c.longTimeout)) + return n, err +} + +func (c *timeoutConn) Write(b []byte) (n int, err error) { + c.SetWriteDeadline(time.Now().Add(c.timeout)) + n, err = c.conn.Write(b) + c.SetReadDeadline(time.Now().Add(c.longTimeout)) + return n, err +} + +func (c *timeoutConn) Close() error { + return c.conn.Close() +} + +func (c *timeoutConn) LocalAddr() net.Addr { + return c.conn.LocalAddr() +} + +func (c *timeoutConn) RemoteAddr() net.Addr { + return c.conn.RemoteAddr() +} + +func (c *timeoutConn) SetDeadline(t time.Time) error { + return c.conn.SetDeadline(t) +} + +func (c *timeoutConn) SetReadDeadline(t time.Time) error { + return c.conn.SetReadDeadline(t) +} + +func (c *timeoutConn) SetWriteDeadline(t time.Time) error { + return c.conn.SetWriteDeadline(t) +} + +// UrlMaker builds URL and resource +const ( + urlTypeCname = 1 + urlTypeIP = 2 + urlTypeAliyun = 3 +) + +type urlMaker struct { + Scheme string // HTTP or HTTPS + NetLoc string // Host or IP + Type int // 1 CNAME, 2 IP, 3 ALIYUN + IsProxy bool // Proxy +} + +// Init parses endpoint +func (um *urlMaker) Init(endpoint string, isCname bool, isProxy bool) { + if strings.HasPrefix(endpoint, "http://") { + um.Scheme = "http" + um.NetLoc = endpoint[len("http://"):] + } else if strings.HasPrefix(endpoint, "https://") { + um.Scheme = "https" + um.NetLoc = endpoint[len("https://"):] + } else { + um.Scheme = "http" + um.NetLoc = endpoint + } + + host, _, err := net.SplitHostPort(um.NetLoc) + if err != nil { + host = um.NetLoc + if host[0] == '[' && host[len(host)-1] == ']' { + host = host[1 : len(host)-1] + } + } + + ip := net.ParseIP(host) + if ip != nil { + um.Type = urlTypeIP + } else if isCname { + um.Type = urlTypeCname + } else { + um.Type = urlTypeAliyun + } + um.IsProxy = isProxy +} + +// getURL gets URL +func (um urlMaker) getURL(bucket, object, params string) *url.URL { + host, path := um.buildURL(bucket, object) + addr := "" + if params == "" { + addr = fmt.Sprintf("%s://%s%s", um.Scheme, host, path) + } else { + addr = fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params) + } + uri, _ := url.ParseRequestURI(addr) + return uri +} + +// getSignURL gets sign URL +func (um urlMaker) getSignURL(bucket, object, params string) string { + host, path := um.buildURL(bucket, object) + return fmt.Sprintf("%s://%s%s?%s", um.Scheme, host, path, params) +} + +// getSignRtmpURL Build Sign Rtmp URL +func (um urlMaker) getSignRtmpURL(bucket, channelName, params string) string { + host, path := um.buildURL(bucket, "live") + + channelName = url.QueryEscape(channelName) + channelName = strings.Replace(channelName, "+", "%20", -1) + + return fmt.Sprintf("rtmp://%s%s/%s?%s", host, path, channelName, params) +} + +// buildURL builds URL +func (um urlMaker) buildURL(bucket, object string) (string, string) { + var host = "" + var path = "" + + object = url.QueryEscape(object) + object = strings.Replace(object, "+", "%20", -1) + + if um.Type == urlTypeCname { + host = um.NetLoc + path = "/" + object + } else if um.Type == urlTypeIP { + if bucket == "" { + host = um.NetLoc + path = "/" + } else { + host = um.NetLoc + path = fmt.Sprintf("/%s/%s", bucket, object) + } + } else { + if bucket == "" { + host = um.NetLoc + path = "/" + } else { + host = bucket + "." + um.NetLoc + path = "/" + object + } + } + + return host, path +} + +// getResource gets canonicalized resource +func (um urlMaker) getResource(bucketName, objectName, subResource string) string { + if subResource != "" { + subResource = "?" + subResource + } + if bucketName == "" { + return fmt.Sprintf("/%s%s", bucketName, subResource) + } + return fmt.Sprintf("/%s/%s%s", bucketName, objectName, subResource) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn_test.go new file mode 100644 index 000000000..c1dc97417 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn_test.go @@ -0,0 +1,184 @@ +package oss + +import ( + "net/http" + "os" + "strings" + "time" + + . "gopkg.in/check.v1" +) + +type OssConnSuite struct{} + +var _ = Suite(&OssConnSuite{}) + +func (s *OssConnSuite) TestURLMarker(c *C) { + um := urlMaker{} + um.Init("docs.github.com", true, false) + c.Assert(um.Type, Equals, urlTypeCname) + c.Assert(um.Scheme, Equals, "http") + c.Assert(um.NetLoc, Equals, "docs.github.com") + + c.Assert(um.getURL("bucket", "object", "params").String(), Equals, "http://docs.github.com/object?params") + c.Assert(um.getURL("bucket", "object", "").String(), Equals, "http://docs.github.com/object") + c.Assert(um.getURL("", "object", "").String(), Equals, "http://docs.github.com/object") + c.Assert(um.getResource("bucket", "object", "subres"), Equals, "/bucket/object?subres") + c.Assert(um.getResource("bucket", "object", ""), Equals, "/bucket/object") + c.Assert(um.getResource("", "object", ""), Equals, "/") + + um.Init("https://docs.github.com", true, false) + c.Assert(um.Type, Equals, urlTypeCname) + c.Assert(um.Scheme, Equals, "https") + c.Assert(um.NetLoc, Equals, "docs.github.com") + + um.Init("http://docs.github.com", true, false) + c.Assert(um.Type, Equals, urlTypeCname) + c.Assert(um.Scheme, Equals, "http") + c.Assert(um.NetLoc, Equals, "docs.github.com") + + um.Init("docs.github.com:8080", false, true) + c.Assert(um.Type, Equals, urlTypeAliyun) + c.Assert(um.Scheme, Equals, "http") + c.Assert(um.NetLoc, Equals, "docs.github.com:8080") + + c.Assert(um.getURL("bucket", "object", "params").String(), Equals, "http://bucket.docs.github.com:8080/object?params") + c.Assert(um.getURL("bucket", "object", "").String(), Equals, "http://bucket.docs.github.com:8080/object") + c.Assert(um.getURL("", "object", "").String(), Equals, "http://docs.github.com:8080/") + c.Assert(um.getResource("bucket", "object", "subres"), Equals, "/bucket/object?subres") + c.Assert(um.getResource("bucket", "object", ""), Equals, "/bucket/object") + c.Assert(um.getResource("", "object", ""), Equals, "/") + + um.Init("https://docs.github.com:8080", false, true) + c.Assert(um.Type, Equals, urlTypeAliyun) + c.Assert(um.Scheme, Equals, "https") + c.Assert(um.NetLoc, Equals, "docs.github.com:8080") + + um.Init("127.0.0.1", false, true) + c.Assert(um.Type, Equals, urlTypeIP) + c.Assert(um.Scheme, Equals, "http") + c.Assert(um.NetLoc, Equals, "127.0.0.1") + + um.Init("http://127.0.0.1", false, false) + c.Assert(um.Type, Equals, urlTypeIP) + c.Assert(um.Scheme, Equals, "http") + c.Assert(um.NetLoc, Equals, "127.0.0.1") + c.Assert(um.getURL("bucket", "object", "params").String(), Equals, "http://127.0.0.1/bucket/object?params") + c.Assert(um.getURL("", "object", "params").String(), Equals, "http://127.0.0.1/?params") + + um.Init("https://127.0.0.1:8080", false, false) + c.Assert(um.Type, Equals, urlTypeIP) + c.Assert(um.Scheme, Equals, "https") + c.Assert(um.NetLoc, Equals, "127.0.0.1:8080") + + um.Init("http://[2401:b180::dc]", false, false) + c.Assert(um.Type, Equals, urlTypeIP) + c.Assert(um.Scheme, Equals, "http") + c.Assert(um.NetLoc, Equals, "[2401:b180::dc]") + + um.Init("https://[2401:b180::dc]:8080", false, false) + c.Assert(um.Type, Equals, urlTypeIP) + c.Assert(um.Scheme, Equals, "https") + c.Assert(um.NetLoc, Equals, "[2401:b180::dc]:8080") +} + +func (s *OssConnSuite) TestAuth(c *C) { + endpoint := "https://github.com/" + cfg := getDefaultOssConfig() + um := urlMaker{} + um.Init(endpoint, false, false) + conn := Conn{cfg, &um, nil} + uri := um.getURL("bucket", "object", "") + req := &http.Request{ + Method: "PUT", + URL: uri, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + Host: uri.Host, + } + + req.Header.Set("Content-Type", "text/html") + req.Header.Set("Date", "Thu, 17 Nov 2005 18:49:58 GMT") + req.Header.Set("Host", endpoint) + req.Header.Set("X-OSS-Meta-Your", "your") + req.Header.Set("X-OSS-Meta-Author", "foo@bar.com") + req.Header.Set("X-OSS-Magic", "abracadabra") + req.Header.Set("Content-Md5", "ODBGOERFMDMzQTczRUY3NUE3NzA5QzdFNUYzMDQxNEM=") + + conn.signHeader(req, um.getResource("bucket", "object", "")) + testLogger.Println("AUTHORIZATION:", req.Header.Get(HTTPHeaderAuthorization)) +} + +func (s *OssConnSuite) TestConnToolFunc(c *C) { + err := checkRespCode(202, []int{}) + c.Assert(err, NotNil) + + err = checkRespCode(202, []int{404}) + c.Assert(err, NotNil) + + err = checkRespCode(202, []int{202, 404}) + c.Assert(err, IsNil) + + srvErr, err := serviceErrFromXML([]byte(""), 312, "") + c.Assert(err, NotNil) + c.Assert(srvErr.StatusCode, Equals, 0) + + srvErr, err = serviceErrFromXML([]byte("ABC"), 312, "") + c.Assert(err, NotNil) + c.Assert(srvErr.StatusCode, Equals, 0) + + srvErr, err = serviceErrFromXML([]byte(""), 312, "") + c.Assert(err, IsNil) + c.Assert(srvErr.StatusCode, Equals, 312) + + unexpect := UnexpectedStatusCodeError{[]int{200}, 202} + c.Assert(len(unexpect.Error()) > 0, Equals, true) + c.Assert(unexpect.Got(), Equals, 202) + + fd, err := os.Open("../sample/BingWallpaper-2015-11-07.jpg") + c.Assert(err, IsNil) + fd.Close() + var out ProcessObjectResult + err = jsonUnmarshal(fd, &out) + c.Assert(err, NotNil) +} + +func (s *OssConnSuite) TestSignRtmpURL(c *C) { + cfg := getDefaultOssConfig() + um := urlMaker{} + um.Init(endpoint, false, false) + conn := Conn{cfg, &um, nil} + + //Anonymous + channelName := "test-sign-rtmp-url" + playlistName := "playlist.m3u8" + expiration := time.Now().Unix() + 3600 + signedRtmpURL := conn.signRtmpURL(bucketName, channelName, playlistName, expiration) + playURL := getPublishURL(bucketName, channelName) + hasPrefix := strings.HasPrefix(signedRtmpURL, playURL) + c.Assert(hasPrefix, Equals, true) + + //empty playlist name + playlistName = "" + signedRtmpURL = conn.signRtmpURL(bucketName, channelName, playlistName, expiration) + playURL = getPublishURL(bucketName, channelName) + hasPrefix = strings.HasPrefix(signedRtmpURL, playURL) + c.Assert(hasPrefix, Equals, true) +} + +func (s *OssConnSuite) TestGetRtmpSignedStr(c *C) { + cfg := getDefaultOssConfig() + um := urlMaker{} + um.Init(endpoint, false, false) + conn := Conn{cfg, &um, nil} + + //Anonymous + channelName := "test-get-rtmp-signed-str" + playlistName := "playlist.m3u8" + expiration := time.Now().Unix() + 3600 + params := map[string]interface{}{} + signedStr := conn.getRtmpSignedStr(bucketName, channelName, playlistName, expiration, params) + c.Assert(signedStr, Equals, "") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/const.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/const.go new file mode 100644 index 000000000..d1eb4b5f6 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/const.go @@ -0,0 +1,146 @@ +package oss + +import "os" + +// ACLType bucket/object ACL +type ACLType string + +const ( + // ACLPrivate definition : private read and write + ACLPrivate ACLType = "private" + + // ACLPublicRead definition : public read and private write + ACLPublicRead ACLType = "public-read" + + // ACLPublicReadWrite definition : public read and public write + ACLPublicReadWrite ACLType = "public-read-write" + + // ACLDefault Object. It's only applicable for object. + ACLDefault ACLType = "default" +) + +// MetadataDirectiveType specifying whether use the metadata of source object when copying object. +type MetadataDirectiveType string + +const ( + // MetaCopy the target object's metadata is copied from the source one + MetaCopy MetadataDirectiveType = "COPY" + + // MetaReplace the target object's metadata is created as part of the copy request (not same as the source one) + MetaReplace MetadataDirectiveType = "REPLACE" +) + +// StorageClassType bucket storage type +type StorageClassType string + +const ( + // StorageStandard standard + StorageStandard StorageClassType = "Standard" + + // StorageIA infrequent access + StorageIA StorageClassType = "IA" + + // StorageArchive archive + StorageArchive StorageClassType = "Archive" +) + +// PayerType the type of request payer +type PayerType string + +const ( + // Requester the requester who send the request + Requester PayerType = "requester" +) + +// HTTPMethod HTTP request method +type HTTPMethod string + +const ( + // HTTPGet HTTP GET + HTTPGet HTTPMethod = "GET" + + // HTTPPut HTTP PUT + HTTPPut HTTPMethod = "PUT" + + // HTTPHead HTTP HEAD + HTTPHead HTTPMethod = "HEAD" + + // HTTPPost HTTP POST + HTTPPost HTTPMethod = "POST" + + // HTTPDelete HTTP DELETE + HTTPDelete HTTPMethod = "DELETE" +) + +// HTTP headers +const ( + HTTPHeaderAcceptEncoding string = "Accept-Encoding" + HTTPHeaderAuthorization = "Authorization" + HTTPHeaderCacheControl = "Cache-Control" + HTTPHeaderContentDisposition = "Content-Disposition" + HTTPHeaderContentEncoding = "Content-Encoding" + HTTPHeaderContentLength = "Content-Length" + HTTPHeaderContentMD5 = "Content-MD5" + HTTPHeaderContentType = "Content-Type" + HTTPHeaderContentLanguage = "Content-Language" + HTTPHeaderDate = "Date" + HTTPHeaderEtag = "ETag" + HTTPHeaderExpires = "Expires" + HTTPHeaderHost = "Host" + HTTPHeaderLastModified = "Last-Modified" + HTTPHeaderRange = "Range" + HTTPHeaderLocation = "Location" + HTTPHeaderOrigin = "Origin" + HTTPHeaderServer = "Server" + HTTPHeaderUserAgent = "User-Agent" + HTTPHeaderIfModifiedSince = "If-Modified-Since" + HTTPHeaderIfUnmodifiedSince = "If-Unmodified-Since" + HTTPHeaderIfMatch = "If-Match" + HTTPHeaderIfNoneMatch = "If-None-Match" + + HTTPHeaderOssACL = "X-Oss-Acl" + HTTPHeaderOssMetaPrefix = "X-Oss-Meta-" + HTTPHeaderOssObjectACL = "X-Oss-Object-Acl" + HTTPHeaderOssSecurityToken = "X-Oss-Security-Token" + HTTPHeaderOssServerSideEncryption = "X-Oss-Server-Side-Encryption" + HTTPHeaderOssServerSideEncryptionKeyID = "X-Oss-Server-Side-Encryption-Key-Id" + HTTPHeaderOssCopySource = "X-Oss-Copy-Source" + HTTPHeaderOssCopySourceRange = "X-Oss-Copy-Source-Range" + HTTPHeaderOssCopySourceIfMatch = "X-Oss-Copy-Source-If-Match" + HTTPHeaderOssCopySourceIfNoneMatch = "X-Oss-Copy-Source-If-None-Match" + HTTPHeaderOssCopySourceIfModifiedSince = "X-Oss-Copy-Source-If-Modified-Since" + HTTPHeaderOssCopySourceIfUnmodifiedSince = "X-Oss-Copy-Source-If-Unmodified-Since" + HTTPHeaderOssMetadataDirective = "X-Oss-Metadata-Directive" + HTTPHeaderOssNextAppendPosition = "X-Oss-Next-Append-Position" + HTTPHeaderOssRequestID = "X-Oss-Request-Id" + HTTPHeaderOssCRC64 = "X-Oss-Hash-Crc64ecma" + HTTPHeaderOssSymlinkTarget = "X-Oss-Symlink-Target" + HTTPHeaderOssStorageClass = "X-Oss-Storage-Class" + HTTPHeaderOssCallback = "X-Oss-Callback" + HTTPHeaderOssCallbackVar = "X-Oss-Callback-Var" + HTTPHeaderOSSRequester = "X-Oss-Request-Payer" +) + +// HTTP Param +const ( + HTTPParamExpires = "Expires" + HTTPParamAccessKeyID = "OSSAccessKeyId" + HTTPParamSignature = "Signature" + HTTPParamSecurityToken = "security-token" + HTTPParamPlaylistName = "playlistName" +) + +// Other constants +const ( + MaxPartSize = 5 * 1024 * 1024 * 1024 // Max part size, 5GB + MinPartSize = 100 * 1024 // Min part size, 100KB + + FilePermMode = os.FileMode(0664) // Default file permission + + TempFilePrefix = "oss-go-temp-" // Temp file prefix + TempFileSuffix = ".temp" // Temp file suffix + + CheckpointFileSuffix = ".cp" // Checkpoint file suffix + + Version = "1.9.5" // Go SDK version +) diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc.go new file mode 100644 index 000000000..c96694f28 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc.go @@ -0,0 +1,123 @@ +package oss + +import ( + "hash" + "hash/crc64" +) + +// digest represents the partial evaluation of a checksum. +type digest struct { + crc uint64 + tab *crc64.Table +} + +// NewCRC creates a new hash.Hash64 computing the CRC64 checksum +// using the polynomial represented by the Table. +func NewCRC(tab *crc64.Table, init uint64) hash.Hash64 { return &digest{init, tab} } + +// Size returns the number of bytes sum will return. +func (d *digest) Size() int { return crc64.Size } + +// BlockSize returns the hash's underlying block size. +// The Write method must be able to accept any amount +// of data, but it may operate more efficiently if all writes +// are a multiple of the block size. +func (d *digest) BlockSize() int { return 1 } + +// Reset resets the hash to its initial state. +func (d *digest) Reset() { d.crc = 0 } + +// Write (via the embedded io.Writer interface) adds more data to the running hash. +// It never returns an error. +func (d *digest) Write(p []byte) (n int, err error) { + d.crc = crc64.Update(d.crc, d.tab, p) + return len(p), nil +} + +// Sum64 returns CRC64 value. +func (d *digest) Sum64() uint64 { return d.crc } + +// Sum returns hash value. +func (d *digest) Sum(in []byte) []byte { + s := d.Sum64() + return append(in, byte(s>>56), byte(s>>48), byte(s>>40), byte(s>>32), byte(s>>24), byte(s>>16), byte(s>>8), byte(s)) +} + +// gf2Dim dimension of GF(2) vectors (length of CRC) +const gf2Dim int = 64 + +func gf2MatrixTimes(mat []uint64, vec uint64) uint64 { + var sum uint64 + for i := 0; vec != 0; i++ { + if vec&1 != 0 { + sum ^= mat[i] + } + + vec >>= 1 + } + return sum +} + +func gf2MatrixSquare(square []uint64, mat []uint64) { + for n := 0; n < gf2Dim; n++ { + square[n] = gf2MatrixTimes(mat, mat[n]) + } +} + +// CRC64Combine combines CRC64 +func CRC64Combine(crc1 uint64, crc2 uint64, len2 uint64) uint64 { + var even [gf2Dim]uint64 // Even-power-of-two zeros operator + var odd [gf2Dim]uint64 // Odd-power-of-two zeros operator + + // Degenerate case + if len2 == 0 { + return crc1 + } + + // Put operator for one zero bit in odd + odd[0] = crc64.ECMA // CRC64 polynomial + var row uint64 = 1 + for n := 1; n < gf2Dim; n++ { + odd[n] = row + row <<= 1 + } + + // Put operator for two zero bits in even + gf2MatrixSquare(even[:], odd[:]) + + // Put operator for four zero bits in odd + gf2MatrixSquare(odd[:], even[:]) + + // Apply len2 zeros to crc1, first square will put the operator for one zero byte, eight zero bits, in even + for { + // Apply zeros operator for this bit of len2 + gf2MatrixSquare(even[:], odd[:]) + + if len2&1 != 0 { + crc1 = gf2MatrixTimes(even[:], crc1) + } + + len2 >>= 1 + + // If no more bits set, then done + if len2 == 0 { + break + } + + // Another iteration of the loop with odd and even swapped + gf2MatrixSquare(odd[:], even[:]) + if len2&1 != 0 { + crc1 = gf2MatrixTimes(odd[:], crc1) + } + len2 >>= 1 + + // If no more bits set, then done + if len2 == 0 { + break + } + } + + // Return combined CRC + crc1 ^= crc2 + return crc1 +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc_test.go new file mode 100644 index 000000000..b9be07c96 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc_test.go @@ -0,0 +1,476 @@ +package oss + +import ( + "crypto/md5" + "encoding/base64" + "hash/crc64" + "io" + "io/ioutil" + "math/rand" + "os" + "strings" + "time" + + . "gopkg.in/check.v1" +) + +type OssCrcSuite struct { + client *Client + bucket *Bucket +} + +var _ = Suite(&OssCrcSuite{}) + +// SetUpSuite runs once when the suite starts running +func (s *OssCrcSuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + s.client = client + + s.client.CreateBucket(bucketName) + time.Sleep(5 * time.Second) + + bucket, err := s.client.Bucket(bucketName) + c.Assert(err, IsNil) + s.bucket = bucket + + testLogger.Println("test crc started") +} + +// TearDownSuite runs before each test or benchmark starts running +func (s *OssCrcSuite) TearDownSuite(c *C) { + // Delete part + lmur, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + + for _, upload := range lmur.Uploads { + var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName, + Key: upload.Key, UploadID: upload.UploadID} + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } + + // Delete objects + lor, err := s.bucket.ListObjects() + c.Assert(err, IsNil) + + for _, object := range lor.Objects { + err = s.bucket.DeleteObject(object.Key) + c.Assert(err, IsNil) + } + + testLogger.Println("test crc completed") +} + +// SetUpTest runs after each test or benchmark runs +func (s *OssCrcSuite) SetUpTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) +} + +// TearDownTest runs once after all tests or benchmarks have finished running +func (s *OssCrcSuite) TearDownTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) +} + +// TestCRCGolden tests OSS's CRC64 +func (s *OssCrcSuite) TestCRCGolden(c *C) { + type crcTest struct { + out uint64 + in string + } + + var crcGolden = []crcTest{ + {0x0, ""}, + {0x3420000000000000, "a"}, + {0x36c4200000000000, "ab"}, + {0x3776c42000000000, "abc"}, + {0x336776c420000000, "abcd"}, + {0x32d36776c4200000, "abcde"}, + {0x3002d36776c42000, "abcdef"}, + {0x31b002d36776c420, "abcdefg"}, + {0xe21b002d36776c4, "abcdefgh"}, + {0x8b6e21b002d36776, "abcdefghi"}, + {0x7f5b6e21b002d367, "abcdefghij"}, + {0x8ec0e7c835bf9cdf, "Discard medicine more than two years old."}, + {0xc7db1759e2be5ab4, "He who has a shady past knows that nice guys finish last."}, + {0xfbf9d9603a6fa020, "I wouldn't marry him with a ten foot pole."}, + {0xeafc4211a6daa0ef, "Free! Free!/A trip/to Mars/for 900/empty jars/Burma Shave"}, + {0x3e05b21c7a4dc4da, "The days of the digital watch are numbered. -Tom Stoppard"}, + {0x5255866ad6ef28a6, "Nepal premier won't resign."}, + {0x8a79895be1e9c361, "For every action there is an equal and opposite government program."}, + {0x8878963a649d4916, "His money is twice tainted: 'taint yours and 'taint mine."}, + {0xa7b9d53ea87eb82f, "There is no reason for any individual to have a computer in their home. -Ken Olsen, 1977"}, + {0xdb6805c0966a2f9c, "It's a tiny change to the code and not completely disgusting. - Bob Manchek"}, + {0xf3553c65dacdadd2, "size: a.out: bad magic"}, + {0x9d5e034087a676b9, "The major problem is with sendmail. -Mark Horton"}, + {0xa6db2d7f8da96417, "Give me a rock, paper and scissors and I will move the world. CCFestoon"}, + {0x325e00cd2fe819f9, "If the enemy is within range, then so are you."}, + {0x88c6600ce58ae4c6, "It's well we cannot hear the screams/That we create in others' dreams."}, + {0x28c4a3f3b769e078, "You remind me of a TV show, but that's all right: I watch it anyway."}, + {0xa698a34c9d9f1dca, "C is as portable as Stonehedge!!"}, + {0xf6c1e2a8c26c5cfc, "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley"}, + {0xd402559dfe9b70c, "The fugacity of a constituent in a mixture of gases at a given temperature is proportional to its mole fraction. Lewis-Randall Rule"}, + {0xdb6efff26aa94946, "How can you write a big system without C++? -Paul Glick"}, + } + + var tab = crc64.MakeTable(crc64.ISO) + + for i := 0; i < len(crcGolden); i++ { + golden := crcGolden[i] + crc := NewCRC(tab, 0) + io.WriteString(crc, golden.in) + sum := crc.Sum64() + + c.Assert(sum, Equals, golden.out) + } +} + +// testCRC64Combine tests CRC64 on vector[0..pos] which should have CRC64 crc. +// Also test CRC64Combine on vector[] split in two. +func testCRC64Combine(c *C, str string, pos int, crc uint64) { + tabECMA := crc64.MakeTable(crc64.ECMA) + + // Test CRC64 + hash := crc64.New(tabECMA) + io.WriteString(hash, str) + crc1 := hash.Sum64() + c.Assert(crc1, Equals, crc) + + // Test CRC64 combine + hash = crc64.New(tabECMA) + io.WriteString(hash, str[0:pos]) + crc1 = hash.Sum64() + + hash = crc64.New(tabECMA) + io.WriteString(hash, str[pos:len(str)]) + crc2 := hash.Sum64() + + crc1 = CRC64Combine(crc1, crc2, uint64(len(str)-pos)) + c.Assert(crc1, Equals, crc) +} + +// TestCRCCombine tests CRC64Combine +func (s *OssCrcSuite) TestCRCCombine(c *C) { + str := "123456789" + testCRC64Combine(c, str, (len(str)+1)>>1, 0x995DC9BBDF1939FA) + + str = "This is a test of the emergency broadcast system." + testCRC64Combine(c, str, (len(str)+1)>>1, 0x27DB187FC15BBC72) +} + +// TestCRCRepeatedCombine tests CRC64Combine +func (s *OssCrcSuite) TestCRCRepeatedCombine(c *C) { + tab := crc64.MakeTable(crc64.ECMA) + str := "Even if I could be Shakespeare, I think I should still choose to be Faraday. - A. Huxley" + + for i := 0; i <= len(str); i++ { + hash := crc64.New(tab) + io.WriteString(hash, string(str[0:i])) + prev := hash.Sum64() + + hash = crc64.New(tab) + io.WriteString(hash, string(str[i:len(str)])) + post := hash.Sum64() + + crc := CRC64Combine(prev, post, uint64(len(str)-i)) + testLogger.Println("TestCRCRepeatedCombine:", prev, post, crc, i, len(str)) + c.Assert(crc == 0x7AD25FAFA1710407, Equals, true) + } +} + +// TestCRCRandomCombine tests CRC64Combine +func (s *OssCrcSuite) TestCRCRandomCombine(c *C) { + tab := crc64.MakeTable(crc64.ECMA) + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + + body, err := ioutil.ReadFile(fileName) + c.Assert(err, IsNil) + + for i := 0; i < 10; i++ { + fileParts, err := SplitFileByPartNum(fileName, 1+rand.Intn(9999)) + c.Assert(err, IsNil) + + var crc uint64 + for _, part := range fileParts { + calc := NewCRC(tab, 0) + calc.Write(body[part.Offset : part.Offset+part.Size]) + crc = CRC64Combine(crc, calc.Sum64(), (uint64)(part.Size)) + } + + testLogger.Println("TestCRCRandomCombine:", crc, i, fileParts) + c.Assert(crc == 0x2B612D24FFF64222, Equals, true) + } +} + +// TestEnableCRCAndMD5 tests MD5 and CRC check +func (s *OssCrcSuite) TestEnableCRCAndMD5(c *C) { + objectName := objectNamePrefix + "tecam" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFileName := "BingWallpaper-2015-11-07-2.jpg" + objectValue := "空山新雨后,天气晚来秋。明月松间照,清泉石上流。竹喧归浣女,莲动下渔舟。随意春芳歇,王孙自可留。" + + client, err := New(endpoint, accessID, accessKey, EnableCRC(true), EnableMD5(true), MD5ThresholdCalcInMemory(200*1024)) + c.Assert(err, IsNil) + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + // PutObject + err = bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // GetObject + body, err := bucket.GetObject(objectName) + c.Assert(err, IsNil) + _, err = ioutil.ReadAll(body) + c.Assert(err, IsNil) + body.Close() + + // GetObjectWithCRC + getResult, err := bucket.DoGetObject(&GetObjectRequest{objectName}, nil) + c.Assert(err, IsNil) + str, err := readBody(getResult.Response.Body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + c.Assert(getResult.ClientCRC.Sum64(), Equals, getResult.ServerCRC) + + // PutObjectFromFile + err = bucket.PutObjectFromFile(objectName, fileName) + c.Assert(err, IsNil) + + // GetObjectToFile + err = bucket.GetObjectToFile(objectName, newFileName) + c.Assert(err, IsNil) + eq, err := compareFiles(fileName, newFileName) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // DeleteObject + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // AppendObject + var nextPos int64 + nextPos, err = bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos) + c.Assert(err, IsNil) + nextPos, err = bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos) + c.Assert(err, IsNil) + + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + request := &AppendObjectRequest{ + ObjectKey: objectName, + Reader: strings.NewReader(objectValue), + Position: 0, + } + appendResult, err := bucket.DoAppendObject(request, []Option{InitCRC(0)}) + c.Assert(err, IsNil) + request.Position = appendResult.NextPosition + appendResult, err = bucket.DoAppendObject(request, []Option{InitCRC(appendResult.CRC)}) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // MultipartUpload + chunks, err := SplitFileByPartSize(fileName, 100*1024) + imurUpload, err := bucket.InitiateMultipartUpload(objectName) + c.Assert(err, IsNil) + var partsUpload []UploadPart + + for _, chunk := range chunks { + part, err := bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + partsUpload = append(partsUpload, part) + } + + _, err = bucket.CompleteMultipartUpload(imurUpload, partsUpload) + c.Assert(err, IsNil) + + // Check MultipartUpload + err = bucket.GetObjectToFile(objectName, newFileName) + c.Assert(err, IsNil) + eq, err = compareFiles(fileName, newFileName) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // DeleteObjects + _, err = bucket.DeleteObjects([]string{objectName}) + c.Assert(err, IsNil) +} + +// TestDisableCRCAndMD5 disables MD5 and CRC +func (s *OssCrcSuite) TestDisableCRCAndMD5(c *C) { + objectName := objectNamePrefix + "tdcam" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFileName := "BingWallpaper-2015-11-07-3.jpg" + objectValue := "中岁颇好道,晚家南山陲。兴来每独往,胜事空自知。行到水穷处,坐看云起时。偶然值林叟,谈笑无还期。" + + client, err := New(endpoint, accessID, accessKey, EnableCRC(false), EnableMD5(false)) + c.Assert(err, IsNil) + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + // PutObject + err = bucket.PutObject(objectName, strings.NewReader(objectValue)) + c.Assert(err, IsNil) + + // GetObject + body, err := bucket.GetObject(objectName) + c.Assert(err, IsNil) + _, err = ioutil.ReadAll(body) + c.Assert(err, IsNil) + body.Close() + + // GetObjectWithCRC + getResult, err := bucket.DoGetObject(&GetObjectRequest{objectName}, nil) + c.Assert(err, IsNil) + str, err := readBody(getResult.Response.Body) + c.Assert(err, IsNil) + c.Assert(str, Equals, objectValue) + + // PutObjectFromFile + err = bucket.PutObjectFromFile(objectName, fileName) + c.Assert(err, IsNil) + + // GetObjectToFile + err = bucket.GetObjectToFile(objectName, newFileName) + c.Assert(err, IsNil) + eq, err := compareFiles(fileName, newFileName) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // DeleteObject + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // AppendObject + var nextPos int64 + nextPos, err = bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos) + c.Assert(err, IsNil) + nextPos, err = bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos) + c.Assert(err, IsNil) + + err = bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + request := &AppendObjectRequest{ + ObjectKey: objectName, + Reader: strings.NewReader(objectValue), + Position: 0, + } + appendResult, err := bucket.DoAppendObject(request, []Option{InitCRC(0)}) + c.Assert(err, IsNil) + request.Position = appendResult.NextPosition + appendResult, err = bucket.DoAppendObject(request, []Option{InitCRC(appendResult.CRC)}) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // MultipartUpload + chunks, err := SplitFileByPartSize(fileName, 100*1024) + imurUpload, err := bucket.InitiateMultipartUpload(objectName) + c.Assert(err, IsNil) + var partsUpload []UploadPart + + for _, chunk := range chunks { + part, err := bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + partsUpload = append(partsUpload, part) + } + + _, err = bucket.CompleteMultipartUpload(imurUpload, partsUpload) + c.Assert(err, IsNil) + + // Check MultipartUpload + err = bucket.GetObjectToFile(objectName, newFileName) + c.Assert(err, IsNil) + eq, err = compareFiles(fileName, newFileName) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // DeleteObjects + _, err = bucket.DeleteObjects([]string{objectName}) + c.Assert(err, IsNil) +} + +// TestSpecifyContentMD5 specifies MD5 +func (s *OssCrcSuite) TestSpecifyContentMD5(c *C) { + objectName := objectNamePrefix + "tdcam" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + objectValue := "积雨空林烟火迟,蒸藜炊黍饷东菑。漠漠水田飞白鹭,阴阴夏木啭黄鹂。山中习静观朝槿,松下清斋折露葵。野老与人争席罢,海鸥何事更相疑。" + + mh := md5.Sum([]byte(objectValue)) + md5B64 := base64.StdEncoding.EncodeToString(mh[:]) + + // PutObject + err := s.bucket.PutObject(objectName, strings.NewReader(objectValue), ContentMD5(md5B64)) + c.Assert(err, IsNil) + + // PutObjectFromFile + file, err := os.Open(fileName) + md5 := md5.New() + io.Copy(md5, file) + mdHex := base64.StdEncoding.EncodeToString(md5.Sum(nil)[:]) + err = s.bucket.PutObjectFromFile(objectName, fileName, ContentMD5(mdHex)) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // AppendObject + var nextPos int64 + nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos) + c.Assert(err, IsNil) + nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + request := &AppendObjectRequest{ + ObjectKey: objectName, + Reader: strings.NewReader(objectValue), + Position: 0, + } + appendResult, err := s.bucket.DoAppendObject(request, []Option{InitCRC(0)}) + c.Assert(err, IsNil) + request.Position = appendResult.NextPosition + appendResult, err = s.bucket.DoAppendObject(request, []Option{InitCRC(appendResult.CRC)}) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // MultipartUpload + imurUpload, err := s.bucket.InitiateMultipartUpload(objectName) + c.Assert(err, IsNil) + + var partsUpload []UploadPart + part, err := s.bucket.UploadPart(imurUpload, strings.NewReader(objectValue), (int64)(len([]byte(objectValue))), 1) + c.Assert(err, IsNil) + partsUpload = append(partsUpload, part) + + _, err = s.bucket.CompleteMultipartUpload(imurUpload, partsUpload) + c.Assert(err, IsNil) + + // DeleteObject + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestAppendObjectNegative +func (s *OssCrcSuite) TestAppendObjectNegative(c *C) { + objectName := objectNamePrefix + "taoncrc" + objectValue := "空山不见人,但闻人语响。返影入深林,复照青苔上。" + + nextPos, err := s.bucket.AppendObject(objectName, strings.NewReader(objectValue), 0, InitCRC(0)) + c.Assert(err, IsNil) + + nextPos, err = s.bucket.AppendObject(objectName, strings.NewReader(objectValue), nextPos, InitCRC(0)) + c.Assert(err, NotNil) + c.Assert(strings.HasPrefix(err.Error(), "oss: the crc"), Equals, true) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download.go new file mode 100644 index 000000000..f0f0857bd --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download.go @@ -0,0 +1,568 @@ +package oss + +import ( + "crypto/md5" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "hash" + "hash/crc64" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strconv" +) + +// DownloadFile downloads files with multipart download. +// +// objectKey the object key. +// filePath the local file to download from objectKey in OSS. +// partSize the part size in bytes. +// options object's constraints, check out GetObject for the reference. +// +// error it's nil when the call succeeds, otherwise it's an error object. +// +func (bucket Bucket) DownloadFile(objectKey, filePath string, partSize int64, options ...Option) error { + if partSize < 1 { + return errors.New("oss: part size smaller than 1") + } + + uRange, err := getRangeConfig(options) + if err != nil { + return err + } + + cpConf := getCpConfig(options) + routines := getRoutines(options) + + if cpConf != nil && cpConf.IsEnable { + cpFilePath := getDownloadCpFilePath(cpConf, bucket.BucketName, objectKey, filePath) + if cpFilePath != "" { + return bucket.downloadFileWithCp(objectKey, filePath, partSize, options, cpFilePath, routines, uRange) + } + } + + return bucket.downloadFile(objectKey, filePath, partSize, options, routines, uRange) +} + +func getDownloadCpFilePath(cpConf *cpConfig, srcBucket, srcObject, destFile string) string { + if cpConf.FilePath == "" && cpConf.DirPath != "" { + src := fmt.Sprintf("oss://%v/%v", srcBucket, srcObject) + absPath, _ := filepath.Abs(destFile) + cpFileName := getCpFileName(src, absPath) + cpConf.FilePath = cpConf.DirPath + string(os.PathSeparator) + cpFileName + } + return cpConf.FilePath +} + +// getRangeConfig gets the download range from the options. +func getRangeConfig(options []Option) (*unpackedRange, error) { + rangeOpt, err := findOption(options, HTTPHeaderRange, nil) + if err != nil || rangeOpt == nil { + return nil, err + } + return parseRange(rangeOpt.(string)) +} + +// ----- concurrent download without checkpoint ----- + +// downloadWorkerArg is download worker's parameters +type downloadWorkerArg struct { + bucket *Bucket + key string + filePath string + options []Option + hook downloadPartHook + enableCRC bool +} + +// downloadPartHook is hook for test +type downloadPartHook func(part downloadPart) error + +var downloadPartHooker downloadPartHook = defaultDownloadPartHook + +func defaultDownloadPartHook(part downloadPart) error { + return nil +} + +// defaultDownloadProgressListener defines default ProgressListener, shields the ProgressListener in options of GetObject. +type defaultDownloadProgressListener struct { +} + +// ProgressChanged no-ops +func (listener *defaultDownloadProgressListener) ProgressChanged(event *ProgressEvent) { +} + +// downloadWorker +func downloadWorker(id int, arg downloadWorkerArg, jobs <-chan downloadPart, results chan<- downloadPart, failed chan<- error, die <-chan bool) { + for part := range jobs { + if err := arg.hook(part); err != nil { + failed <- err + break + } + + // Resolve options + r := Range(part.Start, part.End) + p := Progress(&defaultDownloadProgressListener{}) + opts := make([]Option, len(arg.options)+2) + // Append orderly, can not be reversed! + opts = append(opts, arg.options...) + opts = append(opts, r, p) + + rd, err := arg.bucket.GetObject(arg.key, opts...) + if err != nil { + failed <- err + break + } + defer rd.Close() + + var crcCalc hash.Hash64 + if arg.enableCRC { + crcCalc = crc64.New(crcTable()) + contentLen := part.End - part.Start + 1 + rd = ioutil.NopCloser(TeeReader(rd, crcCalc, contentLen, nil, nil)) + } + defer rd.Close() + + select { + case <-die: + return + default: + } + + fd, err := os.OpenFile(arg.filePath, os.O_WRONLY, FilePermMode) + if err != nil { + failed <- err + break + } + + _, err = fd.Seek(part.Start-part.Offset, os.SEEK_SET) + if err != nil { + fd.Close() + failed <- err + break + } + + _, err = io.Copy(fd, rd) + if err != nil { + fd.Close() + failed <- err + break + } + + if arg.enableCRC { + part.CRC64 = crcCalc.Sum64() + } + + fd.Close() + results <- part + } +} + +// downloadScheduler +func downloadScheduler(jobs chan downloadPart, parts []downloadPart) { + for _, part := range parts { + jobs <- part + } + close(jobs) +} + +// downloadPart defines download part +type downloadPart struct { + Index int // Part number, starting from 0 + Start int64 // Start index + End int64 // End index + Offset int64 // Offset + CRC64 uint64 // CRC check value of part +} + +// getDownloadParts gets download parts +func getDownloadParts(objectSize, partSize int64, uRange *unpackedRange) []downloadPart { + parts := []downloadPart{} + part := downloadPart{} + i := 0 + start, end := adjustRange(uRange, objectSize) + for offset := start; offset < end; offset += partSize { + part.Index = i + part.Start = offset + part.End = GetPartEnd(offset, end, partSize) + part.Offset = start + part.CRC64 = 0 + parts = append(parts, part) + i++ + } + return parts +} + +// getObjectBytes gets object bytes length +func getObjectBytes(parts []downloadPart) int64 { + var ob int64 + for _, part := range parts { + ob += (part.End - part.Start + 1) + } + return ob +} + +// combineCRCInParts caculates the total CRC of continuous parts +func combineCRCInParts(dps []downloadPart) uint64 { + if dps == nil || len(dps) == 0 { + return 0 + } + + crc := dps[0].CRC64 + for i := 1; i < len(dps); i++ { + crc = CRC64Combine(crc, dps[i].CRC64, (uint64)(dps[i].End-dps[i].Start+1)) + } + + return crc +} + +// downloadFile downloads file concurrently without checkpoint. +func (bucket Bucket) downloadFile(objectKey, filePath string, partSize int64, options []Option, routines int, uRange *unpackedRange) error { + tempFilePath := filePath + TempFileSuffix + listener := getProgressListener(options) + + payerOptions := []Option{} + payer := getPayer(options) + if payer != "" { + payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) + } + + // If the file does not exist, create one. If exists, the download will overwrite it. + fd, err := os.OpenFile(tempFilePath, os.O_WRONLY|os.O_CREATE, FilePermMode) + if err != nil { + return err + } + fd.Close() + + meta, err := bucket.GetObjectDetailedMeta(objectKey, payerOptions...) + if err != nil { + return err + } + + objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0) + if err != nil { + return err + } + + enableCRC := false + expectedCRC := (uint64)(0) + if bucket.getConfig().IsEnableCRC && meta.Get(HTTPHeaderOssCRC64) != "" { + if uRange == nil || (!uRange.hasStart && !uRange.hasEnd) { + enableCRC = true + expectedCRC, _ = strconv.ParseUint(meta.Get(HTTPHeaderOssCRC64), 10, 0) + } + } + + // Get the parts of the file + parts := getDownloadParts(objectSize, partSize, uRange) + jobs := make(chan downloadPart, len(parts)) + results := make(chan downloadPart, len(parts)) + failed := make(chan error) + die := make(chan bool) + + var completedBytes int64 + totalBytes := getObjectBytes(parts) + event := newProgressEvent(TransferStartedEvent, 0, totalBytes) + publishProgress(listener, event) + + // Start the download workers + arg := downloadWorkerArg{&bucket, objectKey, tempFilePath, options, downloadPartHooker, enableCRC} + for w := 1; w <= routines; w++ { + go downloadWorker(w, arg, jobs, results, failed, die) + } + + // Download parts concurrently + go downloadScheduler(jobs, parts) + + // Waiting for parts download finished + completed := 0 + for completed < len(parts) { + select { + case part := <-results: + completed++ + completedBytes += (part.End - part.Start + 1) + parts[part.Index].CRC64 = part.CRC64 + event = newProgressEvent(TransferDataEvent, completedBytes, totalBytes) + publishProgress(listener, event) + case err := <-failed: + close(die) + event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes) + publishProgress(listener, event) + return err + } + + if completed >= len(parts) { + break + } + } + + event = newProgressEvent(TransferCompletedEvent, completedBytes, totalBytes) + publishProgress(listener, event) + + if enableCRC { + actualCRC := combineCRCInParts(parts) + err = checkDownloadCRC(actualCRC, expectedCRC) + if err != nil { + return err + } + } + + return os.Rename(tempFilePath, filePath) +} + +// ----- Concurrent download with chcekpoint ----- + +const downloadCpMagic = "92611BED-89E2-46B6-89E5-72F273D4B0A3" + +type downloadCheckpoint struct { + Magic string // Magic + MD5 string // Checkpoint content MD5 + FilePath string // Local file + Object string // Key + ObjStat objectStat // Object status + Parts []downloadPart // All download parts + PartStat []bool // Parts' download status + Start int64 // Start point of the file + End int64 // End point of the file + enableCRC bool // Whether has CRC check + CRC uint64 // CRC check value +} + +type objectStat struct { + Size int64 // Object size + LastModified string // Last modified time + Etag string // Etag +} + +// isValid flags of checkpoint data is valid. It returns true when the data is valid and the checkpoint is valid and the object is not updated. +func (cp downloadCheckpoint) isValid(meta http.Header, uRange *unpackedRange) (bool, error) { + // Compare the CP's Magic and the MD5 + cpb := cp + cpb.MD5 = "" + js, _ := json.Marshal(cpb) + sum := md5.Sum(js) + b64 := base64.StdEncoding.EncodeToString(sum[:]) + + if cp.Magic != downloadCpMagic || b64 != cp.MD5 { + return false, nil + } + + objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0) + if err != nil { + return false, err + } + + // Compare the object size, last modified time and etag + if cp.ObjStat.Size != objectSize || + cp.ObjStat.LastModified != meta.Get(HTTPHeaderLastModified) || + cp.ObjStat.Etag != meta.Get(HTTPHeaderEtag) { + return false, nil + } + + // Check the download range + if uRange != nil { + start, end := adjustRange(uRange, objectSize) + if start != cp.Start || end != cp.End { + return false, nil + } + } + + return true, nil +} + +// load checkpoint from local file +func (cp *downloadCheckpoint) load(filePath string) error { + contents, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + + err = json.Unmarshal(contents, cp) + return err +} + +// dump funciton dumps to file +func (cp *downloadCheckpoint) dump(filePath string) error { + bcp := *cp + + // Calculate MD5 + bcp.MD5 = "" + js, err := json.Marshal(bcp) + if err != nil { + return err + } + sum := md5.Sum(js) + b64 := base64.StdEncoding.EncodeToString(sum[:]) + bcp.MD5 = b64 + + // Serialize + js, err = json.Marshal(bcp) + if err != nil { + return err + } + + // Dump + return ioutil.WriteFile(filePath, js, FilePermMode) +} + +// todoParts gets unfinished parts +func (cp downloadCheckpoint) todoParts() []downloadPart { + dps := []downloadPart{} + for i, ps := range cp.PartStat { + if !ps { + dps = append(dps, cp.Parts[i]) + } + } + return dps +} + +// getCompletedBytes gets completed size +func (cp downloadCheckpoint) getCompletedBytes() int64 { + var completedBytes int64 + for i, part := range cp.Parts { + if cp.PartStat[i] { + completedBytes += (part.End - part.Start + 1) + } + } + return completedBytes +} + +// prepare initiates download tasks +func (cp *downloadCheckpoint) prepare(meta http.Header, bucket *Bucket, objectKey, filePath string, partSize int64, uRange *unpackedRange) error { + // CP + cp.Magic = downloadCpMagic + cp.FilePath = filePath + cp.Object = objectKey + + objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0) + if err != nil { + return err + } + + cp.ObjStat.Size = objectSize + cp.ObjStat.LastModified = meta.Get(HTTPHeaderLastModified) + cp.ObjStat.Etag = meta.Get(HTTPHeaderEtag) + + if bucket.getConfig().IsEnableCRC && meta.Get(HTTPHeaderOssCRC64) != "" { + if uRange == nil || (!uRange.hasStart && !uRange.hasEnd) { + cp.enableCRC = true + cp.CRC, _ = strconv.ParseUint(meta.Get(HTTPHeaderOssCRC64), 10, 0) + } + } + + // Parts + cp.Parts = getDownloadParts(objectSize, partSize, uRange) + cp.PartStat = make([]bool, len(cp.Parts)) + for i := range cp.PartStat { + cp.PartStat[i] = false + } + + return nil +} + +func (cp *downloadCheckpoint) complete(cpFilePath, downFilepath string) error { + os.Remove(cpFilePath) + return os.Rename(downFilepath, cp.FilePath) +} + +// downloadFileWithCp downloads files with checkpoint. +func (bucket Bucket) downloadFileWithCp(objectKey, filePath string, partSize int64, options []Option, cpFilePath string, routines int, uRange *unpackedRange) error { + tempFilePath := filePath + TempFileSuffix + listener := getProgressListener(options) + + payerOptions := []Option{} + payer := getPayer(options) + if payer != "" { + payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) + } + + // Load checkpoint data. + dcp := downloadCheckpoint{} + err := dcp.load(cpFilePath) + if err != nil { + os.Remove(cpFilePath) + } + + // Get the object detailed meta. + meta, err := bucket.GetObjectDetailedMeta(objectKey, payerOptions...) + if err != nil { + return err + } + + // Load error or data invalid. Re-initialize the download. + valid, err := dcp.isValid(meta, uRange) + if err != nil || !valid { + if err = dcp.prepare(meta, &bucket, objectKey, filePath, partSize, uRange); err != nil { + return err + } + os.Remove(cpFilePath) + } + + // Create the file if not exists. Otherwise the parts download will overwrite it. + fd, err := os.OpenFile(tempFilePath, os.O_WRONLY|os.O_CREATE, FilePermMode) + if err != nil { + return err + } + fd.Close() + + // Unfinished parts + parts := dcp.todoParts() + jobs := make(chan downloadPart, len(parts)) + results := make(chan downloadPart, len(parts)) + failed := make(chan error) + die := make(chan bool) + + completedBytes := dcp.getCompletedBytes() + event := newProgressEvent(TransferStartedEvent, completedBytes, dcp.ObjStat.Size) + publishProgress(listener, event) + + // Start the download workers routine + arg := downloadWorkerArg{&bucket, objectKey, tempFilePath, options, downloadPartHooker, dcp.enableCRC} + for w := 1; w <= routines; w++ { + go downloadWorker(w, arg, jobs, results, failed, die) + } + + // Concurrently downloads parts + go downloadScheduler(jobs, parts) + + // Wait for the parts download finished + completed := 0 + for completed < len(parts) { + select { + case part := <-results: + completed++ + dcp.PartStat[part.Index] = true + dcp.Parts[part.Index].CRC64 = part.CRC64 + dcp.dump(cpFilePath) + completedBytes += (part.End - part.Start + 1) + event = newProgressEvent(TransferDataEvent, completedBytes, dcp.ObjStat.Size) + publishProgress(listener, event) + case err := <-failed: + close(die) + event = newProgressEvent(TransferFailedEvent, completedBytes, dcp.ObjStat.Size) + publishProgress(listener, event) + return err + } + + if completed >= len(parts) { + break + } + } + + event = newProgressEvent(TransferCompletedEvent, completedBytes, dcp.ObjStat.Size) + publishProgress(listener, event) + + if dcp.enableCRC { + actualCRC := combineCRCInParts(dcp.Parts) + err = checkDownloadCRC(actualCRC, dcp.CRC) + if err != nil { + return err + } + } + + return dcp.complete(cpFilePath, tempFilePath) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download_test.go new file mode 100644 index 000000000..62a079bb5 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/download_test.go @@ -0,0 +1,658 @@ +package oss + +import ( + "bytes" + "fmt" + "os" + "time" + + . "gopkg.in/check.v1" +) + +type OssDownloadSuite struct { + client *Client + bucket *Bucket +} + +var _ = Suite(&OssDownloadSuite{}) + +// SetUpSuite runs once when the suite starts running +func (s *OssDownloadSuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + s.client = client + + s.client.CreateBucket(bucketName) + time.Sleep(5 * time.Second) + + bucket, err := s.client.Bucket(bucketName) + c.Assert(err, IsNil) + s.bucket = bucket + + testLogger.Println("test download started") +} + +// TearDownSuite runs before each test or benchmark starts running +func (s *OssDownloadSuite) TearDownSuite(c *C) { + // Delete part + lmur, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + + for _, upload := range lmur.Uploads { + var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName, + Key: upload.Key, UploadID: upload.UploadID} + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } + + // Delete objects + lor, err := s.bucket.ListObjects() + c.Assert(err, IsNil) + + for _, object := range lor.Objects { + err = s.bucket.DeleteObject(object.Key) + c.Assert(err, IsNil) + } + + testLogger.Println("test download completed") +} + +// SetUpTest runs after each test or benchmark runs +func (s *OssDownloadSuite) SetUpTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) +} + +// TearDownTest runs once after all tests or benchmarks have finished running +func (s *OssDownloadSuite) TearDownTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".temp") + c.Assert(err, IsNil) +} + +// TestDownloadRoutineWithoutRecovery multipart downloads without checkpoint +func (s *OssDownloadSuite) TestDownloadRoutineWithoutRecovery(c *C) { + objectName := objectNamePrefix + "tdrwr" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "down-new-file.jpg" + + // Upload a file + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + // Download the file by default + err = s.bucket.DownloadFile(objectName, newFile, 100*1024) + c.Assert(err, IsNil) + + // Check + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Use 2 coroutines to download the file and total parts count is 5 + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(2)) + c.Assert(err, IsNil) + + // Check + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Use 5 coroutines to download the file and the total parts count is 5. + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(5)) + c.Assert(err, IsNil) + + // Check + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Use 10 coroutines to download the file and the total parts count is 5. + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(10)) + c.Assert(err, IsNil) + + // Check + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// DownErrorHooker requests hook by downloadPart +func DownErrorHooker(part downloadPart) error { + if part.Index == 4 { + time.Sleep(time.Second) + return fmt.Errorf("ErrorHooker") + } + return nil +} + +// TestDownloadRoutineWithRecovery multi-routine resumable download +func (s *OssDownloadSuite) TestDownloadRoutineWithRecovery(c *C) { + objectName := objectNamePrefix + "tdrtr" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "down-new-file-2.jpg" + + // Upload a file + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + // Download a file with default checkpoint + downloadPartHooker = DownErrorHooker + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + downloadPartHooker = defaultDownloadPartHook + + // Check + dcp := downloadCheckpoint{} + err = dcp.load(newFile + ".cp") + c.Assert(err, IsNil) + c.Assert(dcp.Magic, Equals, downloadCpMagic) + c.Assert(len(dcp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ==")) + c.Assert(dcp.FilePath, Equals, newFile) + c.Assert(dcp.ObjStat.Size, Equals, int64(482048)) + c.Assert(len(dcp.ObjStat.LastModified) > 0, Equals, true) + c.Assert(dcp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"") + c.Assert(dcp.Object, Equals, objectName) + c.Assert(len(dcp.Parts), Equals, 5) + c.Assert(len(dcp.todoParts()), Equals, 1) + + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp")) + c.Assert(err, IsNil) + //download success, checkpoint file has been deleted + err = dcp.load(newFile + ".cp") + c.Assert(err, NotNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Resumable download with empty checkpoint file path + downloadPartHooker = DownErrorHooker + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, "")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + downloadPartHooker = defaultDownloadPartHook + + dcp = downloadCheckpoint{} + err = dcp.load(newFile + ".cp") + c.Assert(err, NotNil) + + // Resumable download with checkpoint dir + os.Remove(newFile) + downloadPartHooker = DownErrorHooker + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, CheckpointDir(true, "./")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + downloadPartHooker = defaultDownloadPartHook + + // Check + dcp = downloadCheckpoint{} + cpConf := cpConfig{IsEnable: true, DirPath: "./"} + cpFilePath := getDownloadCpFilePath(&cpConf, s.bucket.BucketName, objectName, newFile) + err = dcp.load(cpFilePath) + c.Assert(err, IsNil) + c.Assert(dcp.Magic, Equals, downloadCpMagic) + c.Assert(len(dcp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ==")) + c.Assert(dcp.FilePath, Equals, newFile) + c.Assert(dcp.ObjStat.Size, Equals, int64(482048)) + c.Assert(len(dcp.ObjStat.LastModified) > 0, Equals, true) + c.Assert(dcp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"") + c.Assert(dcp.Object, Equals, objectName) + c.Assert(len(dcp.Parts), Equals, 5) + c.Assert(len(dcp.todoParts()), Equals, 1) + + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, CheckpointDir(true, "./")) + c.Assert(err, IsNil) + //download success, checkpoint file has been deleted + err = dcp.load(cpFilePath) + c.Assert(err, NotNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Resumable download with checkpoint at a time. No error is expected in the download procedure. + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp")) + c.Assert(err, IsNil) + + err = dcp.load(newFile + ".cp") + c.Assert(err, NotNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Resumable download with checkpoint at a time. No error is expected in the download procedure. + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(10), Checkpoint(true, newFile+".cp")) + c.Assert(err, IsNil) + + err = dcp.load(newFile + ".cp") + c.Assert(err, NotNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestDownloadOption options +func (s *OssDownloadSuite) TestDownloadOption(c *C) { + objectName := objectNamePrefix + "tdmo" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "down-new-file-3.jpg" + + // Upload the file + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + + // IfMatch + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), IfMatch(meta.Get("Etag"))) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // IfNoneMatch + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), IfNoneMatch(meta.Get("Etag"))) + c.Assert(err, NotNil) + + // IfMatch + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), IfMatch(meta.Get("Etag"))) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // IfNoneMatch + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), IfNoneMatch(meta.Get("Etag"))) + c.Assert(err, NotNil) +} + +// TestDownloadObjectChange tests the file is updated during the upload +func (s *OssDownloadSuite) TestDownloadObjectChange(c *C) { + objectName := objectNamePrefix + "tdloc" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "down-new-file-4.jpg" + + // Upload a file + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + // Download with default checkpoint + downloadPartHooker = DownErrorHooker + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + downloadPartHooker = defaultDownloadPartHook + + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Checkpoint(true, newFile+".cp")) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) +} + +// TestDownloadNegative tests downloading negative +func (s *OssDownloadSuite) TestDownloadNegative(c *C) { + objectName := objectNamePrefix + "tdn" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "down-new-file-3.jpg" + + // Upload a file + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + // Worker routine error + downloadPartHooker = DownErrorHooker + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(2)) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + downloadPartHooker = defaultDownloadPartHook + + // Local file does not exist + err = s.bucket.DownloadFile(objectName, "/tmp/", 100*1024, Routines(2)) + c.Assert(err, NotNil) + + // Invalid part size + err = s.bucket.DownloadFile(objectName, newFile, 0, Routines(2)) + c.Assert(err, NotNil) + + err = s.bucket.DownloadFile(objectName, newFile, 1024*1024*1024*100, Routines(2)) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Local file does not exist + err = s.bucket.DownloadFile(objectName, "/tmp/", 100*1024) + c.Assert(err, NotNil) + + err = s.bucket.DownloadFile(objectName, "/tmp/", 100*1024, Routines(2)) + c.Assert(err, NotNil) + + // Invalid part size + err = s.bucket.DownloadFile(objectName, newFile, -1) + c.Assert(err, NotNil) + + err = s.bucket.DownloadFile(objectName, newFile, 0, Routines(2)) + c.Assert(err, NotNil) + + err = s.bucket.DownloadFile(objectName, newFile, 1024*1024*1024*100) + c.Assert(err, NotNil) + + err = s.bucket.DownloadFile(objectName, newFile, 1024*1024*1024*100, Routines(2)) + c.Assert(err, NotNil) +} + +// TestDownloadWithRange tests concurrent downloading with range specified and checkpoint enabled +func (s *OssDownloadSuite) TestDownloadWithRange(c *C) { + objectName := objectNamePrefix + "tdwr" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "down-new-file-tdwr.jpg" + newFileGet := "down-new-file-tdwr-2.jpg" + + // Upload a file + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + fileSize, err := getFileSize(fileName) + c.Assert(err, IsNil) + + // Download with range, from 1024 to 4096 + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), Range(1024, 4095)) + c.Assert(err, IsNil) + + // Check + eq, err := compareFilesWithRange(fileName, 1024, newFile, 0, 3072) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + err = s.bucket.GetObjectToFile(objectName, newFileGet, Range(1024, 4095)) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download with range, from 1024 to 4096 + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 1024, Routines(3), NormalizedRange("1024-4095")) + c.Assert(err, IsNil) + + // Check + eq, err = compareFilesWithRange(fileName, 1024, newFile, 0, 3072) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("1024-4095")) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download with range, from 2048 to the end + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 1024*1024, Routines(3), NormalizedRange("2048-")) + c.Assert(err, IsNil) + + // Check + eq, err = compareFilesWithRange(fileName, 2048, newFile, 0, fileSize-2048) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("2048-")) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download with range, the last 4096 + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 1024, Routines(3), NormalizedRange("-4096")) + c.Assert(err, IsNil) + + // Check + eq, err = compareFilesWithRange(fileName, fileSize-4096, newFile, 0, 4096) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("-4096")) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestDownloadWithCheckoutAndRange tests concurrent downloading with range specified and checkpoint enabled +func (s *OssDownloadSuite) TestDownloadWithCheckoutAndRange(c *C) { + objectName := objectNamePrefix + "tdwcr" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "down-new-file-tdwcr.jpg" + newFileGet := "down-new-file-tdwcr-2.jpg" + + // Upload a file + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, fileName+".cp")) + c.Assert(err, IsNil) + + fileSize, err := getFileSize(fileName) + c.Assert(err, IsNil) + + // Download with range, from 1024 to 4096 + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(3), Checkpoint(true, newFile+".cp"), Range(1024, 4095)) + c.Assert(err, IsNil) + + // Check + eq, err := compareFilesWithRange(fileName, 1024, newFile, 0, 3072) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + err = s.bucket.GetObjectToFile(objectName, newFileGet, Range(1024, 4095)) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download with range, from 1024 to 4096 + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 1024, Routines(3), Checkpoint(true, newFile+".cp"), NormalizedRange("1024-4095")) + c.Assert(err, IsNil) + + // Check + eq, err = compareFilesWithRange(fileName, 1024, newFile, 0, 3072) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("1024-4095")) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download with range, from 2048 to the end + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 1024*1024, Routines(3), Checkpoint(true, newFile+".cp"), NormalizedRange("2048-")) + c.Assert(err, IsNil) + + // Check + eq, err = compareFilesWithRange(fileName, 2048, newFile, 0, fileSize-2048) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("2048-")) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download with range, the last 4096 bytes + os.Remove(newFile) + err = s.bucket.DownloadFile(objectName, newFile, 1024, Routines(3), Checkpoint(true, newFile+".cp"), NormalizedRange("-4096")) + c.Assert(err, IsNil) + + // Check + eq, err = compareFilesWithRange(fileName, fileSize-4096, newFile, 0, 4096) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(newFileGet) + err = s.bucket.GetObjectToFile(objectName, newFileGet, NormalizedRange("-4096")) + c.Assert(err, IsNil) + + // Compare get and download + eq, err = compareFiles(newFile, newFileGet) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestCombineCRCInDownloadParts tests combineCRCInParts +func (s *OssDownloadSuite) TestCombineCRCInDownloadParts(c *C) { + crc := combineCRCInParts(nil) + c.Assert(crc == 0, Equals, true) + + crc = combineCRCInParts(make([]downloadPart, 0)) + c.Assert(crc == 0, Equals, true) + + parts := make([]downloadPart, 1) + parts[0].CRC64 = 10278880121275185425 + crc = combineCRCInParts(parts) + c.Assert(crc == 10278880121275185425, Equals, true) + + parts = make([]downloadPart, 2) + parts[0].CRC64 = 6748440630437108969 + parts[0].Start = 0 + parts[0].End = 4 + parts[1].CRC64 = 10278880121275185425 + parts[1].Start = 5 + parts[1].End = 8 + crc = combineCRCInParts(parts) + c.Assert(crc == 11051210869376104954, Equals, true) +} + +func getFileSize(fileName string) (int64, error) { + file, err := os.Open(fileName) + if err != nil { + return 0, err + } + defer file.Close() + + stat, err := file.Stat() + if err != nil { + return 0, err + } + + return stat.Size(), nil +} + +// compareFilesWithRange compares the content between fileL and fileR with specified range +func compareFilesWithRange(fileL string, offsetL int64, fileR string, offsetR int64, size int64) (bool, error) { + finL, err := os.Open(fileL) + if err != nil { + return false, err + } + defer finL.Close() + finL.Seek(offsetL, os.SEEK_SET) + + finR, err := os.Open(fileR) + if err != nil { + return false, err + } + defer finR.Close() + finR.Seek(offsetR, os.SEEK_SET) + + statL, err := finL.Stat() + if err != nil { + return false, err + } + + statR, err := finR.Stat() + if err != nil { + return false, err + } + + if (offsetL+size > statL.Size()) || (offsetR+size > statR.Size()) { + return false, nil + } + + part := statL.Size() - offsetL + if part > 16*1024 { + part = 16 * 1024 + } + + bufL := make([]byte, part) + bufR := make([]byte, part) + for readN := int64(0); readN < size; { + n, _ := finL.Read(bufL) + if 0 == n { + break + } + + n, _ = finR.Read(bufR) + if 0 == n { + break + } + + tailer := part + if tailer > size-readN { + tailer = size - readN + } + readN += tailer + + if !bytes.Equal(bufL[0:tailer], bufR[0:tailer]) { + return false, nil + } + } + + return true, nil +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error.go new file mode 100644 index 000000000..6d7b4e0fa --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error.go @@ -0,0 +1,94 @@ +package oss + +import ( + "encoding/xml" + "fmt" + "net/http" + "strings" +) + +// ServiceError contains fields of the error response from Oss Service REST API. +type ServiceError struct { + XMLName xml.Name `xml:"Error"` + Code string `xml:"Code"` // The error code returned from OSS to the caller + Message string `xml:"Message"` // The detail error message from OSS + RequestID string `xml:"RequestId"` // The UUID used to uniquely identify the request + HostID string `xml:"HostId"` // The OSS server cluster's Id + Endpoint string `xml:"Endpoint"` + RawMessage string // The raw messages from OSS + StatusCode int // HTTP status code +} + +// Error implements interface error +func (e ServiceError) Error() string { + if e.Endpoint == "" { + return fmt.Sprintf("oss: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=\"%s\", RequestId=%s", + e.StatusCode, e.Code, e.Message, e.RequestID) + } + return fmt.Sprintf("oss: service returned error: StatusCode=%d, ErrorCode=%s, ErrorMessage=\"%s\", RequestId=%s, Endpoint=%s", + e.StatusCode, e.Code, e.Message, e.RequestID, e.Endpoint) +} + +// UnexpectedStatusCodeError is returned when a storage service responds with neither an error +// nor with an HTTP status code indicating success. +type UnexpectedStatusCodeError struct { + allowed []int // The expected HTTP stats code returned from OSS + got int // The actual HTTP status code from OSS +} + +// Error implements interface error +func (e UnexpectedStatusCodeError) Error() string { + s := func(i int) string { return fmt.Sprintf("%d %s", i, http.StatusText(i)) } + + got := s(e.got) + expected := []string{} + for _, v := range e.allowed { + expected = append(expected, s(v)) + } + return fmt.Sprintf("oss: status code from service response is %s; was expecting %s", + got, strings.Join(expected, " or ")) +} + +// Got is the actual status code returned by oss. +func (e UnexpectedStatusCodeError) Got() int { + return e.got +} + +// checkRespCode returns UnexpectedStatusError if the given response code is not +// one of the allowed status codes; otherwise nil. +func checkRespCode(respCode int, allowed []int) error { + for _, v := range allowed { + if respCode == v { + return nil + } + } + return UnexpectedStatusCodeError{allowed, respCode} +} + +// CRCCheckError is returned when crc check is inconsistent between client and server +type CRCCheckError struct { + clientCRC uint64 // Calculated CRC64 in client + serverCRC uint64 // Calculated CRC64 in server + operation string // Upload operations such as PutObject/AppendObject/UploadPart, etc + requestID string // The request id of this operation +} + +// Error implements interface error +func (e CRCCheckError) Error() string { + return fmt.Sprintf("oss: the crc of %s is inconsistent, client %d but server %d; request id is %s", + e.operation, e.clientCRC, e.serverCRC, e.requestID) +} + +func checkDownloadCRC(clientCRC, serverCRC uint64) error { + if clientCRC == serverCRC { + return nil + } + return CRCCheckError{clientCRC, serverCRC, "DownloadFile", ""} +} + +func checkCRC(resp *Response, operation string) error { + if resp.Headers.Get(HTTPHeaderOssCRC64) == "" || resp.ClientCRC == resp.ServerCRC { + return nil + } + return CRCCheckError{resp.ClientCRC, resp.ServerCRC, operation, resp.Headers.Get(HTTPHeaderOssRequestID)} +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error_test.go new file mode 100644 index 000000000..097ad02d6 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/error_test.go @@ -0,0 +1,111 @@ +package oss + +import ( + "math" + "net/http" + "strings" + + . "gopkg.in/check.v1" +) + +type OssErrorSuite struct{} + +var _ = Suite(&OssErrorSuite{}) + +func (s *OssErrorSuite) TestCheckCRCHasCRCInResp(c *C) { + headers := http.Header{ + "Expires": {"-1"}, + "Content-Length": {"0"}, + "Content-Encoding": {"gzip"}, + "X-Oss-Hash-Crc64ecma": {"0"}, + } + + resp := &Response{ + StatusCode: 200, + Headers: headers, + Body: nil, + ClientCRC: math.MaxUint64, + ServerCRC: math.MaxUint64, + } + + err := checkCRC(resp, "test") + c.Assert(err, IsNil) +} + +func (s *OssErrorSuite) TestCheckCRCNotHasCRCInResp(c *C) { + headers := http.Header{ + "Expires": {"-1"}, + "Content-Length": {"0"}, + "Content-Encoding": {"gzip"}, + } + + resp := &Response{ + StatusCode: 200, + Headers: headers, + Body: nil, + ClientCRC: math.MaxUint64, + ServerCRC: math.MaxUint32, + } + + err := checkCRC(resp, "test") + c.Assert(err, IsNil) +} + +func (s *OssErrorSuite) TestCheckCRCCNegative(c *C) { + headers := http.Header{ + "Expires": {"-1"}, + "Content-Length": {"0"}, + "Content-Encoding": {"gzip"}, + "X-Oss-Hash-Crc64ecma": {"0"}, + } + + resp := &Response{ + StatusCode: 200, + Headers: headers, + Body: nil, + ClientCRC: 0, + ServerCRC: math.MaxUint64, + } + + err := checkCRC(resp, "test") + c.Assert(err, NotNil) + testLogger.Println("error:", err) +} + +func (s *OssErrorSuite) TestCheckDownloadCRC(c *C) { + err := checkDownloadCRC(0xFBF9D9603A6FA020, 0xFBF9D9603A6FA020) + c.Assert(err, IsNil) + + err = checkDownloadCRC(0, 0) + c.Assert(err, IsNil) + + err = checkDownloadCRC(0xDB6EFFF26AA94946, 0) + c.Assert(err, NotNil) + testLogger.Println("error:", err) +} + +func (s *OssErrorSuite) TestServiceErrorEndPoint(c *C) { + xmlBody := ` + + AccessDenied + The bucket you visit is not belong to you. + 5C1B5E9BD79A6B9B6466166E + oss-c-sdk-test-verify-b.oss-cn-shenzhen.aliyuncs.com + ` + serverError, _ := serviceErrFromXML([]byte(xmlBody), 403, "5C1B5E9BD79A6B9B6466166E") + errMsg := serverError.Error() + c.Assert(strings.Contains(errMsg, "Endpoint="), Equals, false) + + xmlBodyWithEndPoint := ` + + AccessDenied + The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint. + 5C1B595ED51820B569C6A12F + hello-hangzws.oss-cn-qingdao.aliyuncs.com + hello-hangzws + oss-cn-shenzhen.aliyuncs.com + ` + serverError, _ = serviceErrFromXML([]byte(xmlBodyWithEndPoint), 406, "5C1B595ED51820B569C6A12F") + errMsg = serverError.Error() + c.Assert(strings.Contains(errMsg, "Endpoint=oss-cn-shenzhen.aliyuncs.com"), Equals, true) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_6.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_6.go new file mode 100644 index 000000000..943dc8fd0 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_6.go @@ -0,0 +1,28 @@ +// +build !go1.7 + +// "golang.org/x/time/rate" is depended on golang context package go1.7 onward +// this file is only for build,not supports limit upload speed +package oss + +import ( + "fmt" + "io" +) + +const ( + perTokenBandwidthSize int = 1024 +) + +type OssLimiter struct { +} + +type LimitSpeedReader struct { + io.ReadCloser + reader io.Reader + ossLimiter *OssLimiter +} + +func GetOssLimiter(uploadSpeed int) (ossLimiter *OssLimiter, err error) { + err = fmt.Errorf("rate.Limiter is not supported below version go1.7") + return nil, err +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_7.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_7.go new file mode 100644 index 000000000..012f98964 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/limit_reader_1_7.go @@ -0,0 +1,91 @@ +// +build go1.7 + +package oss + +import ( + "fmt" + "io" + "math" + "time" + + "golang.org/x/time/rate" +) + +const ( + perTokenBandwidthSize int = 1024 +) + +// OssLimiter: wrapper rate.Limiter +type OssLimiter struct { + limiter *rate.Limiter +} + +// GetOssLimiter:create OssLimiter +// uploadSpeed:KB/s +func GetOssLimiter(uploadSpeed int) (ossLimiter *OssLimiter, err error) { + limiter := rate.NewLimiter(rate.Limit(uploadSpeed), uploadSpeed) + + // first consume the initial full token,the limiter will behave more accurately + limiter.AllowN(time.Now(), uploadSpeed) + + return &OssLimiter{ + limiter: limiter, + }, nil +} + +// LimitSpeedReader: for limit bandwidth upload +type LimitSpeedReader struct { + io.ReadCloser + reader io.Reader + ossLimiter *OssLimiter +} + +// Read +func (r *LimitSpeedReader) Read(p []byte) (n int, err error) { + n = 0 + err = nil + start := 0 + burst := r.ossLimiter.limiter.Burst() + var end int + var tmpN int + var tc int + for start < len(p) { + if start+burst*perTokenBandwidthSize < len(p) { + end = start + burst*perTokenBandwidthSize + } else { + end = len(p) + } + + tmpN, err = r.reader.Read(p[start:end]) + if tmpN > 0 { + n += tmpN + start = n + } + + if err != nil { + return + } + + tc = int(math.Ceil(float64(tmpN) / float64(perTokenBandwidthSize))) + now := time.Now() + re := r.ossLimiter.limiter.ReserveN(now, tc) + if !re.OK() { + err = fmt.Errorf("LimitSpeedReader.Read() failure,ReserveN error,start:%d,end:%d,burst:%d,perTokenBandwidthSize:%d", + start, end, burst, perTokenBandwidthSize) + return + } else { + timeDelay := re.Delay() + time.Sleep(timeDelay) + } + } + return +} + +// Close ... +func (r *LimitSpeedReader) Close() error { + rc, ok := r.reader.(io.ReadCloser) + if ok { + return rc.Close() + } + return nil +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel.go new file mode 100644 index 000000000..003930614 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel.go @@ -0,0 +1,257 @@ +package oss + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "net/http" + "strconv" + "time" +) + +// +// CreateLiveChannel create a live-channel +// +// channelName the name of the channel +// config configuration of the channel +// +// CreateLiveChannelResult the result of create live-channel +// error nil if success, otherwise error +// +func (bucket Bucket) CreateLiveChannel(channelName string, config LiveChannelConfiguration) (CreateLiveChannelResult, error) { + var out CreateLiveChannelResult + + bs, err := xml.Marshal(config) + if err != nil { + return out, err + } + + buffer := new(bytes.Buffer) + buffer.Write(bs) + + params := map[string]interface{}{} + params["live"] = nil + resp, err := bucket.do("PUT", channelName, params, nil, buffer, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// +// PutLiveChannelStatus Set the status of the live-channel: enabled/disabled +// +// channelName the name of the channel +// status enabled/disabled +// +// error nil if success, otherwise error +// +func (bucket Bucket) PutLiveChannelStatus(channelName, status string) error { + params := map[string]interface{}{} + params["live"] = nil + params["status"] = status + + resp, err := bucket.do("PUT", channelName, params, nil, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// PostVodPlaylist create an playlist based on the specified playlist name, startTime and endTime +// +// channelName the name of the channel +// playlistName the name of the playlist, must end with ".m3u8" +// startTime the start time of the playlist +// endTime the endtime of the playlist +// +// error nil if success, otherwise error +// +func (bucket Bucket) PostVodPlaylist(channelName, playlistName string, startTime, endTime time.Time) error { + params := map[string]interface{}{} + params["vod"] = nil + params["startTime"] = strconv.FormatInt(startTime.Unix(), 10) + params["endTime"] = strconv.FormatInt(endTime.Unix(), 10) + + key := fmt.Sprintf("%s/%s", channelName, playlistName) + resp, err := bucket.do("POST", key, params, nil, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + + return checkRespCode(resp.StatusCode, []int{http.StatusOK}) +} + +// GetVodPlaylist get the playlist based on the specified channelName, startTime and endTime +// +// channelName the name of the channel +// startTime the start time of the playlist +// endTime the endtime of the playlist +// +// io.ReadCloser reader instance for reading data from response. It must be called close() after the usage and only valid when error is nil. +// error nil if success, otherwise error +// +func (bucket Bucket) GetVodPlaylist(channelName string, startTime, endTime time.Time) (io.ReadCloser, error) { + params := map[string]interface{}{} + params["vod"] = nil + params["startTime"] = strconv.FormatInt(startTime.Unix(), 10) + params["endTime"] = strconv.FormatInt(endTime.Unix(), 10) + + resp, err := bucket.do("GET", channelName, params, nil, nil, nil) + if err != nil { + return nil, err + } + + return resp.Body, nil +} + +// +// GetLiveChannelStat Get the state of the live-channel +// +// channelName the name of the channel +// +// LiveChannelStat the state of the live-channel +// error nil if success, otherwise error +// +func (bucket Bucket) GetLiveChannelStat(channelName string) (LiveChannelStat, error) { + var out LiveChannelStat + params := map[string]interface{}{} + params["live"] = nil + params["comp"] = "stat" + + resp, err := bucket.do("GET", channelName, params, nil, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// +// GetLiveChannelInfo Get the configuration info of the live-channel +// +// channelName the name of the channel +// +// LiveChannelConfiguration the configuration info of the live-channel +// error nil if success, otherwise error +// +func (bucket Bucket) GetLiveChannelInfo(channelName string) (LiveChannelConfiguration, error) { + var out LiveChannelConfiguration + params := map[string]interface{}{} + params["live"] = nil + + resp, err := bucket.do("GET", channelName, params, nil, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// +// GetLiveChannelHistory Get push records of live-channel +// +// channelName the name of the channel +// +// LiveChannelHistory push records +// error nil if success, otherwise error +// +func (bucket Bucket) GetLiveChannelHistory(channelName string) (LiveChannelHistory, error) { + var out LiveChannelHistory + params := map[string]interface{}{} + params["live"] = nil + params["comp"] = "history" + + resp, err := bucket.do("GET", channelName, params, nil, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// +// ListLiveChannel list the live-channels +// +// options Prefix: filter by the name start with the value of "Prefix" +// MaxKeys: the maximum count returned +// Marker: cursor from which starting list +// +// ListLiveChannelResult live-channel list +// error nil if success, otherwise error +// +func (bucket Bucket) ListLiveChannel(options ...Option) (ListLiveChannelResult, error) { + var out ListLiveChannelResult + + params, err := getRawParams(options) + if err != nil { + return out, err + } + + params["live"] = nil + + resp, err := bucket.do("GET", "", params, nil, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// +// DeleteLiveChannel Delete the live-channel. When a client trying to stream the live-channel, the operation will fail. it will only delete the live-channel itself and the object generated by the live-channel will not be deleted. +// +// channelName the name of the channel +// +// error nil if success, otherwise error +// +func (bucket Bucket) DeleteLiveChannel(channelName string) error { + params := map[string]interface{}{} + params["live"] = nil + + if channelName == "" { + return fmt.Errorf("invalid argument: channel name is empty") + } + + resp, err := bucket.do("DELETE", channelName, params, nil, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + +// +// SignRtmpURL Generate a RTMP push-stream signature URL for the trusted user to push the RTMP stream to the live-channel. +// +// channelName the name of the channel +// playlistName the name of the playlist, must end with ".m3u8" +// expires expiration (in seconds) +// +// string singed rtmp push stream url +// error nil if success, otherwise error +// +func (bucket Bucket) SignRtmpURL(channelName, playlistName string, expires int64) (string, error) { + if expires <= 0 { + return "", fmt.Errorf("invalid argument: %d, expires must greater than 0", expires) + } + expiration := time.Now().Unix() + expires + + return bucket.Client.Conn.signRtmpURL(bucket.BucketName, channelName, playlistName, expiration), nil +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel_test.go new file mode 100644 index 000000000..a6e6a173e --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/livechannel_test.go @@ -0,0 +1,428 @@ +package oss + +import ( + "fmt" + "strings" + "time" + + . "gopkg.in/check.v1" +) + +type OssBucketLiveChannelSuite struct { + client *Client + bucket *Bucket +} + +var _ = Suite(&OssBucketLiveChannelSuite{}) + +// SetUpSuite Run once when the suite starts running +func (s *OssBucketLiveChannelSuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + s.client = client + + err = s.client.CreateBucket(bucketName) + c.Assert(err, IsNil) + time.Sleep(5 * time.Second) + + bucket, err := s.client.Bucket(bucketName) + c.Assert(err, IsNil) + s.bucket = bucket + + testLogger.Println("test livechannel started...") +} + +// TearDownSuite Run once after all tests or benchmarks +func (s *OssBucketLiveChannelSuite) TearDownSuite(c *C) { + marker := "" + for { + result, err := s.bucket.ListLiveChannel(Marker(marker)) + c.Assert(err, IsNil) + + for _, channel := range result.LiveChannel { + err := s.bucket.DeleteLiveChannel(channel.Name) + c.Assert(err, IsNil) + } + + if result.IsTruncated { + marker = result.NextMarker + } else { + break + } + } + + testLogger.Println("test livechannel done...") +} + +// SetUpTest Run before each test or benchmark starts +func (s *OssBucketLiveChannelSuite) SetUpTest(c *C) { + +} + +// TearDownTest Run after each test or benchmark runs. +func (s *OssBucketLiveChannelSuite) TearDownTest(c *C) { + +} + +// TestCreateLiveChannel +func (s *OssBucketLiveChannelSuite) TestCreateLiveChannel(c *C) { + channelName := "test-create-channel" + playlistName := "test-create-channel.m3u8" + + target := LiveChannelTarget{ + PlaylistName: playlistName, + Type: "HLS", + } + + config := LiveChannelConfiguration{ + Description: "livechannel for test", + Status: "enabled", + Target: target, + } + + result, err := s.bucket.CreateLiveChannel(channelName, config) + c.Assert(err, IsNil) + + playURL := getPlayURL(bucketName, channelName, playlistName) + publishURL := getPublishURL(bucketName, channelName) + c.Assert(result.PlayUrls[0], Equals, playURL) + c.Assert(result.PublishUrls[0], Equals, publishURL) + + err = s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) + + invalidType := randStr(4) + invalidTarget := LiveChannelTarget{ + PlaylistName: playlistName, + Type: invalidType, + } + + invalidConfig := LiveChannelConfiguration{ + Description: "livechannel for test", + Status: "enabled", + Target: invalidTarget, + } + + _, err = s.bucket.CreateLiveChannel(channelName, invalidConfig) + c.Assert(err, NotNil) +} + +// TestDeleteLiveChannel +func (s *OssBucketLiveChannelSuite) TestDeleteLiveChannel(c *C) { + channelName := "test-delete-channel" + + target := LiveChannelTarget{ + Type: "HLS", + } + + config := LiveChannelConfiguration{ + Target: target, + } + + _, err := s.bucket.CreateLiveChannel(channelName, config) + c.Assert(err, IsNil) + + err = s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) + + emptyChannelName := "" + err = s.bucket.DeleteLiveChannel(emptyChannelName) + c.Assert(err, NotNil) + + longChannelName := randStr(65) + err = s.bucket.DeleteLiveChannel(longChannelName) + c.Assert(err, NotNil) + + config, err = s.bucket.GetLiveChannelInfo(channelName) + c.Assert(err, NotNil) +} + +// TestGetLiveChannelInfo +func (s *OssBucketLiveChannelSuite) TestGetLiveChannelInfo(c *C) { + channelName := "test-get-channel-status" + + _, err := s.bucket.GetLiveChannelInfo(channelName) + c.Assert(err, NotNil) + + createCfg := LiveChannelConfiguration{ + Target: LiveChannelTarget{ + Type: "HLS", + FragDuration: 10, + FragCount: 4, + PlaylistName: "test-get-channel-status.m3u8", + }, + } + + _, err = s.bucket.CreateLiveChannel(channelName, createCfg) + c.Assert(err, IsNil) + + getCfg, err := s.bucket.GetLiveChannelInfo(channelName) + c.Assert(err, IsNil) + c.Assert("enabled", Equals, getCfg.Status) //The default value is enabled + c.Assert("HLS", Equals, getCfg.Target.Type) + c.Assert(10, Equals, getCfg.Target.FragDuration) + c.Assert(4, Equals, getCfg.Target.FragCount) + c.Assert("test-get-channel-status.m3u8", Equals, getCfg.Target.PlaylistName) + + err = s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) +} + +// TestPutLiveChannelStatus +func (s *OssBucketLiveChannelSuite) TestPutLiveChannelStatus(c *C) { + channelName := "test-put-channel-status" + + config := LiveChannelConfiguration{ + Status: "disabled", + Target: LiveChannelTarget{ + Type: "HLS", + }, + } + + _, err := s.bucket.CreateLiveChannel(channelName, config) + c.Assert(err, IsNil) + getCfg, err := s.bucket.GetLiveChannelInfo(channelName) + c.Assert(err, IsNil) + c.Assert("disabled", Equals, getCfg.Status) + + err = s.bucket.PutLiveChannelStatus(channelName, "enabled") + c.Assert(err, IsNil) + getCfg, err = s.bucket.GetLiveChannelInfo(channelName) + c.Assert(err, IsNil) + c.Assert("enabled", Equals, getCfg.Status) + + err = s.bucket.PutLiveChannelStatus(channelName, "disabled") + c.Assert(err, IsNil) + getCfg, err = s.bucket.GetLiveChannelInfo(channelName) + c.Assert(err, IsNil) + c.Assert("disabled", Equals, getCfg.Status) + + invalidStatus := randLowStr(9) + err = s.bucket.PutLiveChannelStatus(channelName, invalidStatus) + c.Assert(err, NotNil) + + err = s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) +} + +// TestGetLiveChannelHistory +func (s *OssBucketLiveChannelSuite) TestGetLiveChannelHistory(c *C) { + channelName := "test-get-channel-history" + + _, err := s.bucket.GetLiveChannelHistory(channelName) + c.Assert(err, NotNil) + + config := LiveChannelConfiguration{ + Target: LiveChannelTarget{ + Type: "HLS", + }, + } + + _, err = s.bucket.CreateLiveChannel(channelName, config) + c.Assert(err, IsNil) + + history, err := s.bucket.GetLiveChannelHistory(channelName) + c.Assert(err, IsNil) + c.Assert(len(history.Record), Equals, 0) + + err = s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) +} + +// TestGetLiveChannelStat +func (s *OssBucketLiveChannelSuite) TestGetLiveChannelStat(c *C) { + channelName := "test-get-channel-stat" + + _, err := s.bucket.GetLiveChannelStat(channelName) + c.Assert(err, NotNil) + + config := LiveChannelConfiguration{ + Target: LiveChannelTarget{ + Type: "HLS", + }, + } + + _, err = s.bucket.CreateLiveChannel(channelName, config) + c.Assert(err, IsNil) + + stat, err := s.bucket.GetLiveChannelStat(channelName) + c.Assert(err, IsNil) + c.Assert(stat.Status, Equals, "Idle") + + err = s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) +} + +// TestPostVodPlaylist +func (s *OssBucketLiveChannelSuite) TestPostVodPlaylist(c *C) { + channelName := "test-post-vod-playlist" + playlistName := "test-post-vod-playlist.m3u8" + + config := LiveChannelConfiguration{ + Target: LiveChannelTarget{ + Type: "HLS", + }, + } + + _, err := s.bucket.CreateLiveChannel(channelName, config) + c.Assert(err, IsNil) + + endTime := time.Now().Add(-1 * time.Minute) + startTime := endTime.Add(-60 * time.Minute) + + err = s.bucket.PostVodPlaylist(channelName, playlistName, startTime, endTime) + c.Assert(err, NotNil) + + err = s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) +} + +// TestPostVodPlaylist +func (s *OssBucketLiveChannelSuite) TestGetVodPlaylist(c *C) { + channelName := "test-get-vod-playlist" + + config := LiveChannelConfiguration{ + Target: LiveChannelTarget{ + Type: "HLS", + }, + } + + _, err := s.bucket.CreateLiveChannel(channelName, config) + c.Assert(err, IsNil) + + endTime := time.Now().Add(-1 * time.Minute) + startTime := endTime.Add(-60 * time.Minute) + + _, err = s.bucket.GetVodPlaylist(channelName, startTime, endTime) + c.Assert(err, NotNil) + + err = s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) +} + +// TestListLiveChannel +func (s *OssBucketLiveChannelSuite) TestListLiveChannel(c *C) { + result, err := s.bucket.ListLiveChannel() + c.Assert(err, IsNil) + ok := compareListResult(result, "", "", "", 100, false, 0) + c.Assert(ok, Equals, true) + + prefix := "test-list-channel" + for i := 0; i < 200; i++ { + channelName := fmt.Sprintf("%s-%03d", prefix, i) + + config := LiveChannelConfiguration{ + Target: LiveChannelTarget{ + Type: "HLS", + }, + } + + _, err := s.bucket.CreateLiveChannel(channelName, config) + c.Assert(err, IsNil) + } + + result, err = s.bucket.ListLiveChannel() + c.Assert(err, IsNil) + nextMarker := fmt.Sprintf("%s-099", prefix) + ok = compareListResult(result, "", "", nextMarker, 100, true, 100) + c.Assert(ok, Equals, true) + + randPrefix := randStr(5) + result, err = s.bucket.ListLiveChannel(Prefix(randPrefix)) + c.Assert(err, IsNil) + ok = compareListResult(result, randPrefix, "", "", 100, false, 0) + c.Assert(ok, Equals, true) + + marker := fmt.Sprintf("%s-100", prefix) + result, err = s.bucket.ListLiveChannel(Prefix(prefix), Marker(marker)) + c.Assert(err, IsNil) + ok = compareListResult(result, prefix, marker, "", 100, false, 99) + c.Assert(ok, Equals, true) + + maxKeys := 1000 + result, err = s.bucket.ListLiveChannel(MaxKeys(maxKeys)) + c.Assert(err, IsNil) + ok = compareListResult(result, "", "", "", maxKeys, false, 200) + + invalidMaxKeys := -1 + result, err = s.bucket.ListLiveChannel(MaxKeys(invalidMaxKeys)) + c.Assert(err, NotNil) + + for i := 0; i < 200; i++ { + channelName := fmt.Sprintf("%s-%03d", prefix, i) + err := s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) + } +} + +// TestSignRtmpURL +func (s *OssBucketLiveChannelSuite) TestSignRtmpURL(c *C) { + channelName := "test-sign-rtmp-url" + playlistName := "test-sign-rtmp-url.m3u8" + + config := LiveChannelConfiguration{ + Target: LiveChannelTarget{ + Type: "HLS", + PlaylistName: playlistName, + }, + } + + _, err := s.bucket.CreateLiveChannel(channelName, config) + c.Assert(err, IsNil) + + expires := int64(3600) + signedRtmpURL, err := s.bucket.SignRtmpURL(channelName, playlistName, expires) + c.Assert(err, IsNil) + playURL := getPublishURL(s.bucket.BucketName, channelName) + hasPrefix := strings.HasPrefix(signedRtmpURL, playURL) + c.Assert(hasPrefix, Equals, true) + + invalidExpires := int64(-1) + signedRtmpURL, err = s.bucket.SignRtmpURL(channelName, playlistName, invalidExpires) + c.Assert(err, NotNil) + + err = s.bucket.DeleteLiveChannel(channelName) + c.Assert(err, IsNil) +} + +// private +// getPlayURL Get the play url of the live channel +func getPlayURL(bucketName, channelName, playlistName string) string { + host := "" + useHTTPS := false + if strings.Contains(endpoint, "https://") { + host = endpoint[8:] + useHTTPS = true + } else if strings.Contains(endpoint, "http://") { + host = endpoint[7:] + } else { + host = endpoint + } + + if useHTTPS { + return fmt.Sprintf("https://%s.%s/%s/%s", bucketName, host, channelName, playlistName) + } + return fmt.Sprintf("http://%s.%s/%s/%s", bucketName, host, channelName, playlistName) +} + +// getPublistURL Get the push url of the live stream channel +func getPublishURL(bucketName, channelName string) string { + host := "" + if strings.Contains(endpoint, "https://") { + host = endpoint[8:] + } else if strings.Contains(endpoint, "http://") { + host = endpoint[7:] + } else { + host = endpoint + } + + return fmt.Sprintf("rtmp://%s.%s/live/%s", bucketName, host, channelName) +} + +func compareListResult(result ListLiveChannelResult, prefix, marker, nextMarker string, maxKey int, isTruncated bool, count int) bool { + if result.Prefix != prefix || result.Marker != marker || result.NextMarker != nextMarker || result.MaxKeys != maxKey || result.IsTruncated != isTruncated || len(result.LiveChannel) != count { + return false + } + + return true +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/mime.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/mime.go new file mode 100644 index 000000000..11485973d --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/mime.go @@ -0,0 +1,245 @@ +package oss + +import ( + "mime" + "path" + "strings" +) + +var extToMimeType = map[string]string{ + ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + ".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + ".potx": "application/vnd.openxmlformats-officedocument.presentationml.template", + ".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", + ".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide", + ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + ".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + ".xlam": "application/vnd.ms-excel.addin.macroEnabled.12", + ".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12", + ".apk": "application/vnd.android.package-archive", + ".hqx": "application/mac-binhex40", + ".cpt": "application/mac-compactpro", + ".doc": "application/msword", + ".ogg": "application/ogg", + ".pdf": "application/pdf", + ".rtf": "text/rtf", + ".mif": "application/vnd.mif", + ".xls": "application/vnd.ms-excel", + ".ppt": "application/vnd.ms-powerpoint", + ".odc": "application/vnd.oasis.opendocument.chart", + ".odb": "application/vnd.oasis.opendocument.database", + ".odf": "application/vnd.oasis.opendocument.formula", + ".odg": "application/vnd.oasis.opendocument.graphics", + ".otg": "application/vnd.oasis.opendocument.graphics-template", + ".odi": "application/vnd.oasis.opendocument.image", + ".odp": "application/vnd.oasis.opendocument.presentation", + ".otp": "application/vnd.oasis.opendocument.presentation-template", + ".ods": "application/vnd.oasis.opendocument.spreadsheet", + ".ots": "application/vnd.oasis.opendocument.spreadsheet-template", + ".odt": "application/vnd.oasis.opendocument.text", + ".odm": "application/vnd.oasis.opendocument.text-master", + ".ott": "application/vnd.oasis.opendocument.text-template", + ".oth": "application/vnd.oasis.opendocument.text-web", + ".sxw": "application/vnd.sun.xml.writer", + ".stw": "application/vnd.sun.xml.writer.template", + ".sxc": "application/vnd.sun.xml.calc", + ".stc": "application/vnd.sun.xml.calc.template", + ".sxd": "application/vnd.sun.xml.draw", + ".std": "application/vnd.sun.xml.draw.template", + ".sxi": "application/vnd.sun.xml.impress", + ".sti": "application/vnd.sun.xml.impress.template", + ".sxg": "application/vnd.sun.xml.writer.global", + ".sxm": "application/vnd.sun.xml.math", + ".sis": "application/vnd.symbian.install", + ".wbxml": "application/vnd.wap.wbxml", + ".wmlc": "application/vnd.wap.wmlc", + ".wmlsc": "application/vnd.wap.wmlscriptc", + ".bcpio": "application/x-bcpio", + ".torrent": "application/x-bittorrent", + ".bz2": "application/x-bzip2", + ".vcd": "application/x-cdlink", + ".pgn": "application/x-chess-pgn", + ".cpio": "application/x-cpio", + ".csh": "application/x-csh", + ".dvi": "application/x-dvi", + ".spl": "application/x-futuresplash", + ".gtar": "application/x-gtar", + ".hdf": "application/x-hdf", + ".jar": "application/x-java-archive", + ".jnlp": "application/x-java-jnlp-file", + ".js": "application/x-javascript", + ".ksp": "application/x-kspread", + ".chrt": "application/x-kchart", + ".kil": "application/x-killustrator", + ".latex": "application/x-latex", + ".rpm": "application/x-rpm", + ".sh": "application/x-sh", + ".shar": "application/x-shar", + ".swf": "application/x-shockwave-flash", + ".sit": "application/x-stuffit", + ".sv4cpio": "application/x-sv4cpio", + ".sv4crc": "application/x-sv4crc", + ".tar": "application/x-tar", + ".tcl": "application/x-tcl", + ".tex": "application/x-tex", + ".man": "application/x-troff-man", + ".me": "application/x-troff-me", + ".ms": "application/x-troff-ms", + ".ustar": "application/x-ustar", + ".src": "application/x-wais-source", + ".zip": "application/zip", + ".m3u": "audio/x-mpegurl", + ".ra": "audio/x-pn-realaudio", + ".wav": "audio/x-wav", + ".wma": "audio/x-ms-wma", + ".wax": "audio/x-ms-wax", + ".pdb": "chemical/x-pdb", + ".xyz": "chemical/x-xyz", + ".bmp": "image/bmp", + ".gif": "image/gif", + ".ief": "image/ief", + ".png": "image/png", + ".wbmp": "image/vnd.wap.wbmp", + ".ras": "image/x-cmu-raster", + ".pnm": "image/x-portable-anymap", + ".pbm": "image/x-portable-bitmap", + ".pgm": "image/x-portable-graymap", + ".ppm": "image/x-portable-pixmap", + ".rgb": "image/x-rgb", + ".xbm": "image/x-xbitmap", + ".xpm": "image/x-xpixmap", + ".xwd": "image/x-xwindowdump", + ".css": "text/css", + ".rtx": "text/richtext", + ".tsv": "text/tab-separated-values", + ".jad": "text/vnd.sun.j2me.app-descriptor", + ".wml": "text/vnd.wap.wml", + ".wmls": "text/vnd.wap.wmlscript", + ".etx": "text/x-setext", + ".mxu": "video/vnd.mpegurl", + ".flv": "video/x-flv", + ".wm": "video/x-ms-wm", + ".wmv": "video/x-ms-wmv", + ".wmx": "video/x-ms-wmx", + ".wvx": "video/x-ms-wvx", + ".avi": "video/x-msvideo", + ".movie": "video/x-sgi-movie", + ".ice": "x-conference/x-cooltalk", + ".3gp": "video/3gpp", + ".ai": "application/postscript", + ".aif": "audio/x-aiff", + ".aifc": "audio/x-aiff", + ".aiff": "audio/x-aiff", + ".asc": "text/plain", + ".atom": "application/atom+xml", + ".au": "audio/basic", + ".bin": "application/octet-stream", + ".cdf": "application/x-netcdf", + ".cgm": "image/cgm", + ".class": "application/octet-stream", + ".dcr": "application/x-director", + ".dif": "video/x-dv", + ".dir": "application/x-director", + ".djv": "image/vnd.djvu", + ".djvu": "image/vnd.djvu", + ".dll": "application/octet-stream", + ".dmg": "application/octet-stream", + ".dms": "application/octet-stream", + ".dtd": "application/xml-dtd", + ".dv": "video/x-dv", + ".dxr": "application/x-director", + ".eps": "application/postscript", + ".exe": "application/octet-stream", + ".ez": "application/andrew-inset", + ".gram": "application/srgs", + ".grxml": "application/srgs+xml", + ".gz": "application/x-gzip", + ".htm": "text/html", + ".html": "text/html", + ".ico": "image/x-icon", + ".ics": "text/calendar", + ".ifb": "text/calendar", + ".iges": "model/iges", + ".igs": "model/iges", + ".jp2": "image/jp2", + ".jpe": "image/jpeg", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".kar": "audio/midi", + ".lha": "application/octet-stream", + ".lzh": "application/octet-stream", + ".m4a": "audio/mp4a-latm", + ".m4p": "audio/mp4a-latm", + ".m4u": "video/vnd.mpegurl", + ".m4v": "video/x-m4v", + ".mac": "image/x-macpaint", + ".mathml": "application/mathml+xml", + ".mesh": "model/mesh", + ".mid": "audio/midi", + ".midi": "audio/midi", + ".mov": "video/quicktime", + ".mp2": "audio/mpeg", + ".mp3": "audio/mpeg", + ".mp4": "video/mp4", + ".mpe": "video/mpeg", + ".mpeg": "video/mpeg", + ".mpg": "video/mpeg", + ".mpga": "audio/mpeg", + ".msh": "model/mesh", + ".nc": "application/x-netcdf", + ".oda": "application/oda", + ".ogv": "video/ogv", + ".pct": "image/pict", + ".pic": "image/pict", + ".pict": "image/pict", + ".pnt": "image/x-macpaint", + ".pntg": "image/x-macpaint", + ".ps": "application/postscript", + ".qt": "video/quicktime", + ".qti": "image/x-quicktime", + ".qtif": "image/x-quicktime", + ".ram": "audio/x-pn-realaudio", + ".rdf": "application/rdf+xml", + ".rm": "application/vnd.rn-realmedia", + ".roff": "application/x-troff", + ".sgm": "text/sgml", + ".sgml": "text/sgml", + ".silo": "model/mesh", + ".skd": "application/x-koan", + ".skm": "application/x-koan", + ".skp": "application/x-koan", + ".skt": "application/x-koan", + ".smi": "application/smil", + ".smil": "application/smil", + ".snd": "audio/basic", + ".so": "application/octet-stream", + ".svg": "image/svg+xml", + ".t": "application/x-troff", + ".texi": "application/x-texinfo", + ".texinfo": "application/x-texinfo", + ".tif": "image/tiff", + ".tiff": "image/tiff", + ".tr": "application/x-troff", + ".txt": "text/plain", + ".vrml": "model/vrml", + ".vxml": "application/voicexml+xml", + ".webm": "video/webm", + ".wrl": "model/vrml", + ".xht": "application/xhtml+xml", + ".xhtml": "application/xhtml+xml", + ".xml": "application/xml", + ".xsl": "application/xml", + ".xslt": "application/xslt+xml", + ".xul": "application/vnd.mozilla.xul+xml", +} + +// TypeByExtension returns the MIME type associated with the file extension ext. +// gets the file's MIME type for HTTP header Content-Type +func TypeByExtension(filePath string) string { + typ := mime.TypeByExtension(path.Ext(filePath)) + if typ == "" { + typ = extToMimeType[strings.ToLower(path.Ext(filePath))] + } + return typ +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/model.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/model.go new file mode 100644 index 000000000..51f1c31e3 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/model.go @@ -0,0 +1,68 @@ +package oss + +import ( + "hash" + "io" + "net/http" +) + +// Response defines HTTP response from OSS +type Response struct { + StatusCode int + Headers http.Header + Body io.ReadCloser + ClientCRC uint64 + ServerCRC uint64 +} + +func (r *Response) Read(p []byte) (n int, err error) { + return r.Body.Read(p) +} + +func (r *Response) Close() error { + return r.Body.Close() +} + +// PutObjectRequest is the request of DoPutObject +type PutObjectRequest struct { + ObjectKey string + Reader io.Reader +} + +// GetObjectRequest is the request of DoGetObject +type GetObjectRequest struct { + ObjectKey string +} + +// GetObjectResult is the result of DoGetObject +type GetObjectResult struct { + Response *Response + ClientCRC hash.Hash64 + ServerCRC uint64 +} + +// AppendObjectRequest is the requtest of DoAppendObject +type AppendObjectRequest struct { + ObjectKey string + Reader io.Reader + Position int64 +} + +// AppendObjectResult is the result of DoAppendObject +type AppendObjectResult struct { + NextPosition int64 + CRC uint64 +} + +// UploadPartRequest is the request of DoUploadPart +type UploadPartRequest struct { + InitResult *InitiateMultipartUploadResult + Reader io.Reader + PartSize int64 + PartNumber int +} + +// UploadPartResult is the result of DoUploadPart +type UploadPartResult struct { + Part UploadPart +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy.go new file mode 100644 index 000000000..e2597c24e --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy.go @@ -0,0 +1,468 @@ +package oss + +import ( + "crypto/md5" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" + "strconv" +) + +// CopyFile is multipart copy object +// +// srcBucketName source bucket name +// srcObjectKey source object name +// destObjectKey target object name in the form of bucketname.objectkey +// partSize the part size in byte. +// options object's contraints. Check out function InitiateMultipartUpload. +// +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) CopyFile(srcBucketName, srcObjectKey, destObjectKey string, partSize int64, options ...Option) error { + destBucketName := bucket.BucketName + if partSize < MinPartSize || partSize > MaxPartSize { + return errors.New("oss: part size invalid range (1024KB, 5GB]") + } + + cpConf := getCpConfig(options) + routines := getRoutines(options) + + if cpConf != nil && cpConf.IsEnable { + cpFilePath := getCopyCpFilePath(cpConf, srcBucketName, srcObjectKey, destBucketName, destObjectKey) + if cpFilePath != "" { + return bucket.copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, destObjectKey, partSize, options, cpFilePath, routines) + } + } + + return bucket.copyFile(srcBucketName, srcObjectKey, destBucketName, destObjectKey, + partSize, options, routines) +} + +func getCopyCpFilePath(cpConf *cpConfig, srcBucket, srcObject, destBucket, destObject string) string { + if cpConf.FilePath == "" && cpConf.DirPath != "" { + dest := fmt.Sprintf("oss://%v/%v", destBucket, destObject) + src := fmt.Sprintf("oss://%v/%v", srcBucket, srcObject) + cpFileName := getCpFileName(src, dest) + cpConf.FilePath = cpConf.DirPath + string(os.PathSeparator) + cpFileName + } + return cpConf.FilePath +} + +// ----- Concurrently copy without checkpoint --------- + +// copyWorkerArg defines the copy worker arguments +type copyWorkerArg struct { + bucket *Bucket + imur InitiateMultipartUploadResult + srcBucketName string + srcObjectKey string + options []Option + hook copyPartHook +} + +// copyPartHook is the hook for testing purpose +type copyPartHook func(part copyPart) error + +var copyPartHooker copyPartHook = defaultCopyPartHook + +func defaultCopyPartHook(part copyPart) error { + return nil +} + +// copyWorker copies worker +func copyWorker(id int, arg copyWorkerArg, jobs <-chan copyPart, results chan<- UploadPart, failed chan<- error, die <-chan bool) { + for chunk := range jobs { + if err := arg.hook(chunk); err != nil { + failed <- err + break + } + chunkSize := chunk.End - chunk.Start + 1 + part, err := arg.bucket.UploadPartCopy(arg.imur, arg.srcBucketName, arg.srcObjectKey, + chunk.Start, chunkSize, chunk.Number, arg.options...) + if err != nil { + failed <- err + break + } + select { + case <-die: + return + default: + } + results <- part + } +} + +// copyScheduler +func copyScheduler(jobs chan copyPart, parts []copyPart) { + for _, part := range parts { + jobs <- part + } + close(jobs) +} + +// copyPart structure +type copyPart struct { + Number int // Part number (from 1 to 10,000) + Start int64 // The start index in the source file. + End int64 // The end index in the source file +} + +// getCopyParts calculates copy parts +func getCopyParts(objectSize, partSize int64) []copyPart { + parts := []copyPart{} + part := copyPart{} + i := 0 + for offset := int64(0); offset < objectSize; offset += partSize { + part.Number = i + 1 + part.Start = offset + part.End = GetPartEnd(offset, objectSize, partSize) + parts = append(parts, part) + i++ + } + return parts +} + +// getSrcObjectBytes gets the source file size +func getSrcObjectBytes(parts []copyPart) int64 { + var ob int64 + for _, part := range parts { + ob += (part.End - part.Start + 1) + } + return ob +} + +// copyFile is a concurrently copy without checkpoint +func (bucket Bucket) copyFile(srcBucketName, srcObjectKey, destBucketName, destObjectKey string, + partSize int64, options []Option, routines int) error { + descBucket, err := bucket.Client.Bucket(destBucketName) + srcBucket, err := bucket.Client.Bucket(srcBucketName) + listener := getProgressListener(options) + + payerOptions := []Option{} + payer := getPayer(options) + if payer != "" { + payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) + } + + meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, payerOptions...) + if err != nil { + return err + } + + objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0) + if err != nil { + return err + } + + // Get copy parts + parts := getCopyParts(objectSize, partSize) + // Initialize the multipart upload + imur, err := descBucket.InitiateMultipartUpload(destObjectKey, options...) + if err != nil { + return err + } + + jobs := make(chan copyPart, len(parts)) + results := make(chan UploadPart, len(parts)) + failed := make(chan error) + die := make(chan bool) + + var completedBytes int64 + totalBytes := getSrcObjectBytes(parts) + event := newProgressEvent(TransferStartedEvent, 0, totalBytes) + publishProgress(listener, event) + + // Start to copy workers + arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, payerOptions, copyPartHooker} + for w := 1; w <= routines; w++ { + go copyWorker(w, arg, jobs, results, failed, die) + } + + // Start the scheduler + go copyScheduler(jobs, parts) + + // Wait for the parts finished. + completed := 0 + ups := make([]UploadPart, len(parts)) + for completed < len(parts) { + select { + case part := <-results: + completed++ + ups[part.PartNumber-1] = part + completedBytes += (parts[part.PartNumber-1].End - parts[part.PartNumber-1].Start + 1) + event = newProgressEvent(TransferDataEvent, completedBytes, totalBytes) + publishProgress(listener, event) + case err := <-failed: + close(die) + descBucket.AbortMultipartUpload(imur, payerOptions...) + event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes) + publishProgress(listener, event) + return err + } + + if completed >= len(parts) { + break + } + } + + event = newProgressEvent(TransferCompletedEvent, completedBytes, totalBytes) + publishProgress(listener, event) + + // Complete the multipart upload + _, err = descBucket.CompleteMultipartUpload(imur, ups, payerOptions...) + if err != nil { + bucket.AbortMultipartUpload(imur, payerOptions...) + return err + } + return nil +} + +// ----- Concurrently copy with checkpoint ----- + +const copyCpMagic = "84F1F18C-FF1D-403B-A1D8-9DEB5F65910A" + +type copyCheckpoint struct { + Magic string // Magic + MD5 string // CP content MD5 + SrcBucketName string // Source bucket + SrcObjectKey string // Source object + DestBucketName string // Target bucket + DestObjectKey string // Target object + CopyID string // Copy ID + ObjStat objectStat // Object stat + Parts []copyPart // Copy parts + CopyParts []UploadPart // The uploaded parts + PartStat []bool // The part status +} + +// isValid checks if the data is valid which means CP is valid and object is not updated. +func (cp copyCheckpoint) isValid(meta http.Header) (bool, error) { + // Compare CP's magic number and the MD5. + cpb := cp + cpb.MD5 = "" + js, _ := json.Marshal(cpb) + sum := md5.Sum(js) + b64 := base64.StdEncoding.EncodeToString(sum[:]) + + if cp.Magic != downloadCpMagic || b64 != cp.MD5 { + return false, nil + } + + objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0) + if err != nil { + return false, err + } + + // Compare the object size and last modified time and etag. + if cp.ObjStat.Size != objectSize || + cp.ObjStat.LastModified != meta.Get(HTTPHeaderLastModified) || + cp.ObjStat.Etag != meta.Get(HTTPHeaderEtag) { + return false, nil + } + + return true, nil +} + +// load loads from the checkpoint file +func (cp *copyCheckpoint) load(filePath string) error { + contents, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + + err = json.Unmarshal(contents, cp) + return err +} + +// update updates the parts status +func (cp *copyCheckpoint) update(part UploadPart) { + cp.CopyParts[part.PartNumber-1] = part + cp.PartStat[part.PartNumber-1] = true +} + +// dump dumps the CP to the file +func (cp *copyCheckpoint) dump(filePath string) error { + bcp := *cp + + // Calculate MD5 + bcp.MD5 = "" + js, err := json.Marshal(bcp) + if err != nil { + return err + } + sum := md5.Sum(js) + b64 := base64.StdEncoding.EncodeToString(sum[:]) + bcp.MD5 = b64 + + // Serialization + js, err = json.Marshal(bcp) + if err != nil { + return err + } + + // Dump + return ioutil.WriteFile(filePath, js, FilePermMode) +} + +// todoParts returns unfinished parts +func (cp copyCheckpoint) todoParts() []copyPart { + dps := []copyPart{} + for i, ps := range cp.PartStat { + if !ps { + dps = append(dps, cp.Parts[i]) + } + } + return dps +} + +// getCompletedBytes returns finished bytes count +func (cp copyCheckpoint) getCompletedBytes() int64 { + var completedBytes int64 + for i, part := range cp.Parts { + if cp.PartStat[i] { + completedBytes += (part.End - part.Start + 1) + } + } + return completedBytes +} + +// prepare initializes the multipart upload +func (cp *copyCheckpoint) prepare(meta http.Header, srcBucket *Bucket, srcObjectKey string, destBucket *Bucket, destObjectKey string, + partSize int64, options []Option) error { + // CP + cp.Magic = copyCpMagic + cp.SrcBucketName = srcBucket.BucketName + cp.SrcObjectKey = srcObjectKey + cp.DestBucketName = destBucket.BucketName + cp.DestObjectKey = destObjectKey + + objectSize, err := strconv.ParseInt(meta.Get(HTTPHeaderContentLength), 10, 0) + if err != nil { + return err + } + + cp.ObjStat.Size = objectSize + cp.ObjStat.LastModified = meta.Get(HTTPHeaderLastModified) + cp.ObjStat.Etag = meta.Get(HTTPHeaderEtag) + + // Parts + cp.Parts = getCopyParts(objectSize, partSize) + cp.PartStat = make([]bool, len(cp.Parts)) + for i := range cp.PartStat { + cp.PartStat[i] = false + } + cp.CopyParts = make([]UploadPart, len(cp.Parts)) + + // Init copy + imur, err := destBucket.InitiateMultipartUpload(destObjectKey, options...) + if err != nil { + return err + } + cp.CopyID = imur.UploadID + + return nil +} + +func (cp *copyCheckpoint) complete(bucket *Bucket, parts []UploadPart, cpFilePath string, options []Option) error { + imur := InitiateMultipartUploadResult{Bucket: cp.DestBucketName, + Key: cp.DestObjectKey, UploadID: cp.CopyID} + _, err := bucket.CompleteMultipartUpload(imur, parts, options...) + if err != nil { + return err + } + os.Remove(cpFilePath) + return err +} + +// copyFileWithCp is concurrently copy with checkpoint +func (bucket Bucket) copyFileWithCp(srcBucketName, srcObjectKey, destBucketName, destObjectKey string, + partSize int64, options []Option, cpFilePath string, routines int) error { + descBucket, err := bucket.Client.Bucket(destBucketName) + srcBucket, err := bucket.Client.Bucket(srcBucketName) + listener := getProgressListener(options) + + payerOptions := []Option{} + payer := getPayer(options) + if payer != "" { + payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) + } + + // Load CP data + ccp := copyCheckpoint{} + err = ccp.load(cpFilePath) + if err != nil { + os.Remove(cpFilePath) + } + + // Make sure the object is not updated. + meta, err := srcBucket.GetObjectDetailedMeta(srcObjectKey, payerOptions...) + if err != nil { + return err + } + + // Load error or the CP data is invalid---reinitialize + valid, err := ccp.isValid(meta) + if err != nil || !valid { + if err = ccp.prepare(meta, srcBucket, srcObjectKey, descBucket, destObjectKey, partSize, options); err != nil { + return err + } + os.Remove(cpFilePath) + } + + // Unfinished parts + parts := ccp.todoParts() + imur := InitiateMultipartUploadResult{ + Bucket: destBucketName, + Key: destObjectKey, + UploadID: ccp.CopyID} + + jobs := make(chan copyPart, len(parts)) + results := make(chan UploadPart, len(parts)) + failed := make(chan error) + die := make(chan bool) + + completedBytes := ccp.getCompletedBytes() + event := newProgressEvent(TransferStartedEvent, completedBytes, ccp.ObjStat.Size) + publishProgress(listener, event) + + // Start the worker coroutines + arg := copyWorkerArg{descBucket, imur, srcBucketName, srcObjectKey, payerOptions, copyPartHooker} + for w := 1; w <= routines; w++ { + go copyWorker(w, arg, jobs, results, failed, die) + } + + // Start the scheduler + go copyScheduler(jobs, parts) + + // Wait for the parts completed. + completed := 0 + for completed < len(parts) { + select { + case part := <-results: + completed++ + ccp.update(part) + ccp.dump(cpFilePath) + completedBytes += (parts[part.PartNumber-1].End - parts[part.PartNumber-1].Start + 1) + event = newProgressEvent(TransferDataEvent, completedBytes, ccp.ObjStat.Size) + publishProgress(listener, event) + case err := <-failed: + close(die) + event = newProgressEvent(TransferFailedEvent, completedBytes, ccp.ObjStat.Size) + publishProgress(listener, event) + return err + } + + if completed >= len(parts) { + break + } + } + + event = newProgressEvent(TransferCompletedEvent, completedBytes, ccp.ObjStat.Size) + publishProgress(listener, event) + + return ccp.complete(descBucket, ccp.CopyParts, cpFilePath, payerOptions) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy_test.go new file mode 100644 index 000000000..d0e83cdc9 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multicopy_test.go @@ -0,0 +1,481 @@ +package oss + +import ( + "fmt" + "os" + "time" + + . "gopkg.in/check.v1" +) + +type OssCopySuite struct { + client *Client + bucket *Bucket +} + +var _ = Suite(&OssCopySuite{}) + +// SetUpSuite runs once when the suite starts running +func (s *OssCopySuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + s.client = client + + s.client.CreateBucket(bucketName) + time.Sleep(5 * time.Second) + + bucket, err := s.client.Bucket(bucketName) + c.Assert(err, IsNil) + s.bucket = bucket + + testLogger.Println("test copy started") +} + +// TearDownSuite runs before each test or benchmark starts running +func (s *OssCopySuite) TearDownSuite(c *C) { + // Delete Part + lmur, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + + for _, upload := range lmur.Uploads { + var imur = InitiateMultipartUploadResult{Bucket: bucketName, + Key: upload.Key, UploadID: upload.UploadID} + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } + + // Delete objects + lor, err := s.bucket.ListObjects() + c.Assert(err, IsNil) + + for _, object := range lor.Objects { + err = s.bucket.DeleteObject(object.Key) + c.Assert(err, IsNil) + } + + testLogger.Println("test copy completed") +} + +// SetUpTest runs after each test or benchmark runs +func (s *OssCopySuite) SetUpTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) +} + +// TearDownTest runs once after all tests or benchmarks have finished running +func (s *OssCopySuite) TearDownTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) +} + +// TestCopyRoutineWithoutRecovery is multi-routine copy without resumable recovery +func (s *OssCopySuite) TestCopyRoutineWithoutRecovery(c *C) { + srcObjectName := objectNamePrefix + "tcrwr" + destObjectName := srcObjectName + "-copy" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "copy-new-file.jpg" + + // Upload source file + err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Does not specify parameter 'routines', by default it's single routine + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Specify one routine. + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(1)) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Specify three routines, which is less than parts count 5 + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Specify 5 routines which is the same as parts count + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(5)) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Specify routine count 10, which is more than parts count + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(10)) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Invalid routine count, will use single routine + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(-1)) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Option + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(3), Meta("myprop", "mypropval")) + + meta, err := s.bucket.GetObjectDetailedMeta(destObjectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + err = s.bucket.DeleteObject(srcObjectName) + c.Assert(err, IsNil) +} + +// CopyErrorHooker is a copypart request hook +func CopyErrorHooker(part copyPart) error { + if part.Number == 5 { + time.Sleep(time.Second) + return fmt.Errorf("ErrorHooker") + } + return nil +} + +// TestCopyRoutineWithoutRecoveryNegative is a multiple routines copy without checkpoint +func (s *OssCopySuite) TestCopyRoutineWithoutRecoveryNegative(c *C) { + srcObjectName := objectNamePrefix + "tcrwrn" + destObjectName := srcObjectName + "-copy" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + + // Upload source file + err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + copyPartHooker = CopyErrorHooker + // Worker routine errors + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(2)) + + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + copyPartHooker = defaultCopyPartHook + + // Source bucket does not exist + err = s.bucket.CopyFile("NotExist", srcObjectName, destObjectName, 100*1024, Routines(2)) + c.Assert(err, NotNil) + + // Target object does not exist + err = s.bucket.CopyFile(bucketName, "NotExist", destObjectName, 100*1024, Routines(2)) + + // The part size is invalid + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024, Routines(2)) + c.Assert(err, NotNil) + + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*1024*1024*100, Routines(2)) + c.Assert(err, NotNil) + + // Delete the source file + err = s.bucket.DeleteObject(srcObjectName) + c.Assert(err, IsNil) +} + +// TestCopyRoutineWithRecovery is a multiple routines copy with resumable recovery +func (s *OssCopySuite) TestCopyRoutineWithRecovery(c *C) { + srcObjectName := objectNamePrefix + "tcrtr" + destObjectName := srcObjectName + "-copy" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "copy-new-file.jpg" + + // Upload source file + err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Routines default value, CP's default path is destObjectName+.cp + // Copy object with checkpoint enabled, single runtine. + // Copy 4 parts---the CopyErrorHooker makes sure the copy of part 5 will fail. + copyPartHooker = CopyErrorHooker + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Checkpoint(true, destObjectName+".cp")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + copyPartHooker = defaultCopyPartHook + + // Check CP + ccp := copyCheckpoint{} + err = ccp.load(destObjectName + ".cp") + c.Assert(err, IsNil) + c.Assert(ccp.Magic, Equals, copyCpMagic) + c.Assert(len(ccp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ==")) + c.Assert(ccp.SrcBucketName, Equals, bucketName) + c.Assert(ccp.SrcObjectKey, Equals, srcObjectName) + c.Assert(ccp.DestBucketName, Equals, bucketName) + c.Assert(ccp.DestObjectKey, Equals, destObjectName) + c.Assert(len(ccp.CopyID), Equals, len("3F79722737D1469980DACEDCA325BB52")) + c.Assert(ccp.ObjStat.Size, Equals, int64(482048)) + c.Assert(len(ccp.ObjStat.LastModified), Equals, len("2015-12-17 18:43:03 +0800 CST")) + c.Assert(ccp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"") + c.Assert(len(ccp.Parts), Equals, 5) + c.Assert(len(ccp.todoParts()), Equals, 1) + c.Assert(ccp.PartStat[4], Equals, false) + + // Second copy, finish the last part + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Checkpoint(true, destObjectName+".cp")) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + err = ccp.load(fileName + ".cp") + c.Assert(err, NotNil) + + //multicopy with empty checkpoint path + copyPartHooker = CopyErrorHooker + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Checkpoint(true, "")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + copyPartHooker = defaultCopyPartHook + ccp = copyCheckpoint{} + err = ccp.load(destObjectName + ".cp") + c.Assert(err, NotNil) + + //multi copy with checkpoint dir + copyPartHooker = CopyErrorHooker + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(2), CheckpointDir(true, "./")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + copyPartHooker = defaultCopyPartHook + + // Check CP + ccp = copyCheckpoint{} + cpConf := cpConfig{IsEnable: true, DirPath: "./"} + cpFilePath := getCopyCpFilePath(&cpConf, bucketName, srcObjectName, s.bucket.BucketName, destObjectName) + err = ccp.load(cpFilePath) + c.Assert(err, IsNil) + c.Assert(ccp.Magic, Equals, copyCpMagic) + c.Assert(len(ccp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ==")) + c.Assert(ccp.SrcBucketName, Equals, bucketName) + c.Assert(ccp.SrcObjectKey, Equals, srcObjectName) + c.Assert(ccp.DestBucketName, Equals, bucketName) + c.Assert(ccp.DestObjectKey, Equals, destObjectName) + c.Assert(len(ccp.CopyID), Equals, len("3F79722737D1469980DACEDCA325BB52")) + c.Assert(ccp.ObjStat.Size, Equals, int64(482048)) + c.Assert(len(ccp.ObjStat.LastModified), Equals, len("2015-12-17 18:43:03 +0800 CST")) + c.Assert(ccp.ObjStat.Etag, Equals, "\"2351E662233817A7AE974D8C5B0876DD-5\"") + c.Assert(len(ccp.Parts), Equals, 5) + c.Assert(len(ccp.todoParts()), Equals, 1) + c.Assert(ccp.PartStat[4], Equals, false) + + // Second copy, finish the last part. + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(2), CheckpointDir(true, "./")) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + err = ccp.load(srcObjectName + ".cp") + c.Assert(err, NotNil) + + // First copy without error. + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(3), Checkpoint(true, destObjectName+".cp")) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Copy with multiple coroutines, no errors. + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(10), Checkpoint(true, destObjectName+".cp")) + c.Assert(err, IsNil) + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Option + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(5), Checkpoint(true, destObjectName+".cp"), Meta("myprop", "mypropval")) + c.Assert(err, IsNil) + + meta, err := s.bucket.GetObjectDetailedMeta(destObjectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + + err = s.bucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Delete the source file + err = s.bucket.DeleteObject(srcObjectName) + c.Assert(err, IsNil) +} + +// TestCopyRoutineWithRecoveryNegative is a multiple routineed copy without checkpoint +func (s *OssCopySuite) TestCopyRoutineWithRecoveryNegative(c *C) { + srcObjectName := objectNamePrefix + "tcrwrn" + destObjectName := srcObjectName + "-copy" + + // Source bucket does not exist + err := s.bucket.CopyFile("NotExist", srcObjectName, destObjectName, 100*1024, Checkpoint(true, destObjectName+".cp")) + c.Assert(err, NotNil) + c.Assert(err, NotNil) + + // Source object does not exist + err = s.bucket.CopyFile(bucketName, "NotExist", destObjectName, 100*1024, Routines(2), Checkpoint(true, destObjectName+".cp")) + c.Assert(err, NotNil) + + // Specify part size is invalid. + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024, Checkpoint(true, destObjectName+".cp")) + c.Assert(err, NotNil) + + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*1024*1024*100, Routines(2), Checkpoint(true, destObjectName+".cp")) + c.Assert(err, NotNil) +} + +// TestCopyFileCrossBucket is a cross bucket's direct copy. +func (s *OssCopySuite) TestCopyFileCrossBucket(c *C) { + destBucketName := bucketName + "-cfcb-desc" + srcObjectName := objectNamePrefix + "tcrtr" + destObjectName := srcObjectName + "-copy" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "copy-new-file.jpg" + + destBucket, err := s.client.Bucket(destBucketName) + c.Assert(err, IsNil) + + // Create a target bucket + err = s.client.CreateBucket(destBucketName) + + // Upload source file + err = s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Copy files + err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(5), Checkpoint(true, destObjectName+".cp")) + c.Assert(err, IsNil) + + err = destBucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = destBucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Copy file with options + err = destBucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(10), Checkpoint(true, "copy.cp"), Meta("myprop", "mypropval")) + c.Assert(err, IsNil) + + err = destBucket.GetObjectToFile(destObjectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = destBucket.DeleteObject(destObjectName) + c.Assert(err, IsNil) + os.Remove(newFile) + + // Delete target bucket + err = s.client.DeleteBucket(destBucketName) + c.Assert(err, IsNil) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart.go new file mode 100644 index 000000000..b5a3a05b5 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart.go @@ -0,0 +1,290 @@ +package oss + +import ( + "bytes" + "encoding/xml" + "io" + "net/http" + "net/url" + "os" + "sort" + "strconv" +) + +// InitiateMultipartUpload initializes multipart upload +// +// objectKey object name +// options the object constricts for upload. The valid options are CacheControl, ContentDisposition, ContentEncoding, Expires, +// ServerSideEncryption, Meta, check out the following link: +// https://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/InitiateMultipartUpload.html +// +// InitiateMultipartUploadResult the return value of the InitiateMultipartUpload, which is used for calls later on such as UploadPartFromFile,UploadPartCopy. +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) InitiateMultipartUpload(objectKey string, options ...Option) (InitiateMultipartUploadResult, error) { + var imur InitiateMultipartUploadResult + opts := addContentType(options, objectKey) + params := map[string]interface{}{} + params["uploads"] = nil + resp, err := bucket.do("POST", objectKey, params, opts, nil, nil) + if err != nil { + return imur, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &imur) + return imur, err +} + +// UploadPart uploads parts +// +// After initializing a Multipart Upload, the upload Id and object key could be used for uploading the parts. +// Each part has its part number (ranges from 1 to 10,000). And for each upload Id, the part number identifies the position of the part in the whole file. +// And thus with the same part number and upload Id, another part upload will overwrite the data. +// Except the last one, minimal part size is 100KB. There's no limit on the last part size. +// +// imur the returned value of InitiateMultipartUpload. +// reader io.Reader the reader for the part's data. +// size the part size. +// partNumber the part number (ranges from 1 to 10,000). Invalid part number will lead to InvalidArgument error. +// +// UploadPart the return value of the upload part. It consists of PartNumber and ETag. It's valid when error is nil. +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) UploadPart(imur InitiateMultipartUploadResult, reader io.Reader, + partSize int64, partNumber int, options ...Option) (UploadPart, error) { + request := &UploadPartRequest{ + InitResult: &imur, + Reader: reader, + PartSize: partSize, + PartNumber: partNumber, + } + + result, err := bucket.DoUploadPart(request, options) + + return result.Part, err +} + +// UploadPartFromFile uploads part from the file. +// +// imur the return value of a successful InitiateMultipartUpload. +// filePath the local file path to upload. +// startPosition the start position in the local file. +// partSize the part size. +// partNumber the part number (from 1 to 10,000) +// +// UploadPart the return value consists of PartNumber and ETag. +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) UploadPartFromFile(imur InitiateMultipartUploadResult, filePath string, + startPosition, partSize int64, partNumber int, options ...Option) (UploadPart, error) { + var part = UploadPart{} + fd, err := os.Open(filePath) + if err != nil { + return part, err + } + defer fd.Close() + fd.Seek(startPosition, os.SEEK_SET) + + request := &UploadPartRequest{ + InitResult: &imur, + Reader: fd, + PartSize: partSize, + PartNumber: partNumber, + } + + result, err := bucket.DoUploadPart(request, options) + + return result.Part, err +} + +// DoUploadPart does the actual part upload. +// +// request part upload request +// +// UploadPartResult the result of uploading part. +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) DoUploadPart(request *UploadPartRequest, options []Option) (*UploadPartResult, error) { + listener := getProgressListener(options) + options = append(options, ContentLength(request.PartSize)) + params := map[string]interface{}{} + params["partNumber"] = strconv.Itoa(request.PartNumber) + params["uploadId"] = request.InitResult.UploadID + resp, err := bucket.do("PUT", request.InitResult.Key, params, options, + &io.LimitedReader{R: request.Reader, N: request.PartSize}, listener) + if err != nil { + return &UploadPartResult{}, err + } + defer resp.Body.Close() + + part := UploadPart{ + ETag: resp.Headers.Get(HTTPHeaderEtag), + PartNumber: request.PartNumber, + } + + if bucket.getConfig().IsEnableCRC { + err = checkCRC(resp, "DoUploadPart") + if err != nil { + return &UploadPartResult{part}, err + } + } + + return &UploadPartResult{part}, nil +} + +// UploadPartCopy uploads part copy +// +// imur the return value of InitiateMultipartUpload +// copySrc source Object name +// startPosition the part's start index in the source file +// partSize the part size +// partNumber the part number, ranges from 1 to 10,000. If it exceeds the range OSS returns InvalidArgument error. +// options the constraints of source object for the copy. The copy happens only when these contraints are met. Otherwise it returns error. +// CopySourceIfNoneMatch, CopySourceIfModifiedSince CopySourceIfUnmodifiedSince, check out the following link for the detail +// https://help.aliyun.com/document_detail/oss/api-reference/multipart-upload/UploadPartCopy.html +// +// UploadPart the return value consists of PartNumber and ETag. +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) UploadPartCopy(imur InitiateMultipartUploadResult, srcBucketName, srcObjectKey string, + startPosition, partSize int64, partNumber int, options ...Option) (UploadPart, error) { + var out UploadPartCopyResult + var part UploadPart + + opts := []Option{CopySource(srcBucketName, url.QueryEscape(srcObjectKey)), + CopySourceRange(startPosition, partSize)} + opts = append(opts, options...) + params := map[string]interface{}{} + params["partNumber"] = strconv.Itoa(partNumber) + params["uploadId"] = imur.UploadID + resp, err := bucket.do("PUT", imur.Key, params, opts, nil, nil) + if err != nil { + return part, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + if err != nil { + return part, err + } + part.ETag = out.ETag + part.PartNumber = partNumber + + return part, nil +} + +// CompleteMultipartUpload completes the multipart upload. +// +// imur the return value of InitiateMultipartUpload. +// parts the array of return value of UploadPart/UploadPartFromFile/UploadPartCopy. +// +// CompleteMultipartUploadResponse the return value when the call succeeds. Only valid when the error is nil. +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) CompleteMultipartUpload(imur InitiateMultipartUploadResult, + parts []UploadPart, options ...Option) (CompleteMultipartUploadResult, error) { + var out CompleteMultipartUploadResult + + sort.Sort(uploadParts(parts)) + cxml := completeMultipartUploadXML{} + cxml.Part = parts + bs, err := xml.Marshal(cxml) + if err != nil { + return out, err + } + buffer := new(bytes.Buffer) + buffer.Write(bs) + + params := map[string]interface{}{} + params["uploadId"] = imur.UploadID + resp, err := bucket.do("POST", imur.Key, params, options, buffer, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + return out, err +} + +// AbortMultipartUpload aborts the multipart upload. +// +// imur the return value of InitiateMultipartUpload. +// +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) AbortMultipartUpload(imur InitiateMultipartUploadResult, options ...Option) error { + params := map[string]interface{}{} + params["uploadId"] = imur.UploadID + resp, err := bucket.do("DELETE", imur.Key, params, options, nil, nil) + if err != nil { + return err + } + defer resp.Body.Close() + return checkRespCode(resp.StatusCode, []int{http.StatusNoContent}) +} + +// ListUploadedParts lists the uploaded parts. +// +// imur the return value of InitiateMultipartUpload. +// +// ListUploadedPartsResponse the return value if it succeeds, only valid when error is nil. +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) ListUploadedParts(imur InitiateMultipartUploadResult, options ...Option) (ListUploadedPartsResult, error) { + var out ListUploadedPartsResult + options = append(options, EncodingType("url")) + + params := map[string]interface{}{} + params, err := getRawParams(options) + if err != nil { + return out, err + } + + params["uploadId"] = imur.UploadID + resp, err := bucket.do("GET", imur.Key, params, nil, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + if err != nil { + return out, err + } + err = decodeListUploadedPartsResult(&out) + return out, err +} + +// ListMultipartUploads lists all ongoing multipart upload tasks +// +// options listObject's filter. Prefix specifies the returned object's prefix; KeyMarker specifies the returned object's start point in lexicographic order; +// MaxKeys specifies the max entries to return; Delimiter is the character for grouping object keys. +// +// ListMultipartUploadResponse the return value if it succeeds, only valid when error is nil. +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) ListMultipartUploads(options ...Option) (ListMultipartUploadResult, error) { + var out ListMultipartUploadResult + + options = append(options, EncodingType("url")) + params, err := getRawParams(options) + if err != nil { + return out, err + } + params["uploads"] = nil + + resp, err := bucket.do("GET", "", params, options, nil, nil) + if err != nil { + return out, err + } + defer resp.Body.Close() + + err = xmlUnmarshal(resp.Body, &out) + if err != nil { + return out, err + } + err = decodeListMultipartUploadResult(&out) + return out, err +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart_test.go new file mode 100644 index 000000000..a732f9c95 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/multipart_test.go @@ -0,0 +1,949 @@ +// multipart test + +package oss + +import ( + "math/rand" + "net/http" + "os" + "strconv" + "time" + + . "gopkg.in/check.v1" +) + +type OssBucketMultipartSuite struct { + client *Client + bucket *Bucket +} + +var _ = Suite(&OssBucketMultipartSuite{}) + +// SetUpSuite runs once when the suite starts running +func (s *OssBucketMultipartSuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + s.client = client + + s.client.CreateBucket(bucketName) + time.Sleep(5 * time.Second) + + bucket, err := s.client.Bucket(bucketName) + c.Assert(err, IsNil) + s.bucket = bucket + + // Delete part + lmur, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + + for _, upload := range lmur.Uploads { + var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName, + Key: upload.Key, UploadID: upload.UploadID} + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } + + // Delete objects + lor, err := s.bucket.ListObjects() + c.Assert(err, IsNil) + + for _, object := range lor.Objects { + err = s.bucket.DeleteObject(object.Key) + c.Assert(err, IsNil) + } + + testLogger.Println("test multipart started") +} + +// TearDownSuite runs before each test or benchmark starts running +func (s *OssBucketMultipartSuite) TearDownSuite(c *C) { + // Delete part + lmur, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + + for _, upload := range lmur.Uploads { + var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName, + Key: upload.Key, UploadID: upload.UploadID} + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } + + // Delete objects + lor, err := s.bucket.ListObjects() + c.Assert(err, IsNil) + + for _, object := range lor.Objects { + err = s.bucket.DeleteObject(object.Key) + c.Assert(err, IsNil) + } + + testLogger.Println("test multipart completed") +} + +// SetUpTest runs after each test or benchmark runs +func (s *OssBucketMultipartSuite) SetUpTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) +} + +// TearDownTest runs once after all tests or benchmarks have finished running +func (s *OssBucketMultipartSuite) TearDownTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".temp") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".txt1") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".txt2") + c.Assert(err, IsNil) +} + +// TestMultipartUpload +func (s *OssBucketMultipartSuite) TestMultipartUpload(c *C) { + objectName := objectNamePrefix + "tmu" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartNum(fileName, 3) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + options := []Option{ + Expires(futureDate), Meta("my", "myprop"), + } + + fd, err := os.Open(fileName) + c.Assert(err, IsNil) + defer fd.Close() + + imur, err := s.bucket.InitiateMultipartUpload(objectName, options...) + c.Assert(err, IsNil) + var parts []UploadPart + for _, chunk := range chunks { + fd.Seek(chunk.Offset, os.SEEK_SET) + part, err := s.bucket.UploadPart(imur, fd, chunk.Size, chunk.Number) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + cmur, err := s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + testLogger.Println("cmur:", cmur) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop") + c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat)) + c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart") + + err = s.bucket.GetObjectToFile(objectName, "newpic1.jpg") + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestMultipartUploadFromFile +func (s *OssBucketMultipartSuite) TestMultipartUploadFromFile(c *C) { + objectName := objectNamePrefix + "tmuff" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartNum(fileName, 3) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + options := []Option{ + Expires(futureDate), Meta("my", "myprop"), + } + imur, err := s.bucket.InitiateMultipartUpload(objectName, options...) + c.Assert(err, IsNil) + var parts []UploadPart + for _, chunk := range chunks { + part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + cmur, err := s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + testLogger.Println("cmur:", cmur) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop") + c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat)) + c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart") + + err = s.bucket.GetObjectToFile(objectName, "newpic1.jpg") + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestUploadPartCopy +func (s *OssBucketMultipartSuite) TestUploadPartCopy(c *C) { + objectSrc := objectNamePrefix + "tupc" + "src" + objectDesc := objectNamePrefix + "tupc" + "desc" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartNum(fileName, 3) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + err = s.bucket.PutObjectFromFile(objectSrc, fileName) + c.Assert(err, IsNil) + + options := []Option{ + Expires(futureDate), Meta("my", "myprop"), + } + imur, err := s.bucket.InitiateMultipartUpload(objectDesc, options...) + c.Assert(err, IsNil) + var parts []UploadPart + for _, chunk := range chunks { + part, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + cmur, err := s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + testLogger.Println("cmur:", cmur) + + meta, err := s.bucket.GetObjectDetailedMeta(objectDesc) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + c.Assert(meta.Get("X-Oss-Meta-My"), Equals, "myprop") + c.Assert(meta.Get("Expires"), Equals, futureDate.Format(http.TimeFormat)) + c.Assert(meta.Get("X-Oss-Object-Type"), Equals, "Multipart") + + err = s.bucket.GetObjectToFile(objectDesc, "newpic2.jpg") + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectSrc) + c.Assert(err, IsNil) + err = s.bucket.DeleteObject(objectDesc) + c.Assert(err, IsNil) +} + +func (s *OssBucketMultipartSuite) TestListUploadedParts(c *C) { + objectName := objectNamePrefix + "tlup" + objectSrc := objectNamePrefix + "tlup" + "src" + objectDesc := objectNamePrefix + "tlup" + "desc" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartSize(fileName, 100*1024) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + err = s.bucket.PutObjectFromFile(objectSrc, fileName) + c.Assert(err, IsNil) + + // Upload + imurUpload, err := s.bucket.InitiateMultipartUpload(objectName) + var partsUpload []UploadPart + for _, chunk := range chunks { + part, err := s.bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + partsUpload = append(partsUpload, part) + } + + // Copy + imurCopy, err := s.bucket.InitiateMultipartUpload(objectDesc) + var partsCopy []UploadPart + for _, chunk := range chunks { + part, err := s.bucket.UploadPartCopy(imurCopy, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + partsCopy = append(partsCopy, part) + } + + // List + lupr, err := s.bucket.ListUploadedParts(imurUpload) + c.Assert(err, IsNil) + testLogger.Println("lupr:", lupr) + c.Assert(len(lupr.UploadedParts), Equals, len(chunks)) + + lupr, err = s.bucket.ListUploadedParts(imurCopy) + c.Assert(err, IsNil) + testLogger.Println("lupr:", lupr) + c.Assert(len(lupr.UploadedParts), Equals, len(chunks)) + + lmur, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + testLogger.Println("lmur:", lmur) + + // Complete + _, err = s.bucket.CompleteMultipartUpload(imurUpload, partsUpload) + c.Assert(err, IsNil) + _, err = s.bucket.CompleteMultipartUpload(imurCopy, partsCopy) + c.Assert(err, IsNil) + + // Download + err = s.bucket.GetObjectToFile(objectDesc, "newpic3.jpg") + c.Assert(err, IsNil) + err = s.bucket.GetObjectToFile(objectName, "newpic4.jpg") + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + err = s.bucket.DeleteObject(objectDesc) + c.Assert(err, IsNil) + err = s.bucket.DeleteObject(objectSrc) + c.Assert(err, IsNil) +} + +func (s *OssBucketMultipartSuite) TestAbortMultipartUpload(c *C) { + objectName := objectNamePrefix + "tamu" + objectSrc := objectNamePrefix + "tamu" + "src" + objectDesc := objectNamePrefix + "tamu" + "desc" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartSize(fileName, 100*1024) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + err = s.bucket.PutObjectFromFile(objectSrc, fileName) + c.Assert(err, IsNil) + + // Upload + imurUpload, err := s.bucket.InitiateMultipartUpload(objectName) + var partsUpload []UploadPart + for _, chunk := range chunks { + part, err := s.bucket.UploadPartFromFile(imurUpload, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + partsUpload = append(partsUpload, part) + } + + // Copy + imurCopy, err := s.bucket.InitiateMultipartUpload(objectDesc) + var partsCopy []UploadPart + for _, chunk := range chunks { + part, err := s.bucket.UploadPartCopy(imurCopy, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + partsCopy = append(partsCopy, part) + } + + // List + lupr, err := s.bucket.ListUploadedParts(imurUpload) + c.Assert(err, IsNil) + testLogger.Println("lupr:", lupr) + c.Assert(len(lupr.UploadedParts), Equals, len(chunks)) + + lupr, err = s.bucket.ListUploadedParts(imurCopy) + c.Assert(err, IsNil) + testLogger.Println("lupr:", lupr) + c.Assert(len(lupr.UploadedParts), Equals, len(chunks)) + + lmur, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + testLogger.Println("lmur:", lmur) + c.Assert(len(lmur.Uploads), Equals, 2) + + // Abort + err = s.bucket.AbortMultipartUpload(imurUpload) + c.Assert(err, IsNil) + err = s.bucket.AbortMultipartUpload(imurCopy) + c.Assert(err, IsNil) + + lmur, err = s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + testLogger.Println("lmur:", lmur) + c.Assert(len(lmur.Uploads), Equals, 0) + + // Download + err = s.bucket.GetObjectToFile(objectDesc, "newpic3.jpg") + c.Assert(err, NotNil) + err = s.bucket.GetObjectToFile(objectName, "newpic4.jpg") + c.Assert(err, NotNil) +} + +// TestUploadPartCopyWithConstraints +func (s *OssBucketMultipartSuite) TestUploadPartCopyWithConstraints(c *C) { + objectSrc := objectNamePrefix + "tucwc" + "src" + objectDesc := objectNamePrefix + "tucwc" + "desc" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartNum(fileName, 3) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + err = s.bucket.PutObjectFromFile(objectSrc, fileName) + c.Assert(err, IsNil) + + imur, err := s.bucket.InitiateMultipartUpload(objectDesc) + var parts []UploadPart + for _, chunk := range chunks { + _, err = s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number), + CopySourceIfModifiedSince(futureDate)) + c.Assert(err, NotNil) + } + + for _, chunk := range chunks { + _, err = s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number), + CopySourceIfUnmodifiedSince(futureDate)) + c.Assert(err, IsNil) + } + + meta, err := s.bucket.GetObjectDetailedMeta(objectSrc) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + + for _, chunk := range chunks { + _, err = s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number), + CopySourceIfNoneMatch(meta.Get("Etag"))) + c.Assert(err, NotNil) + } + + for _, chunk := range chunks { + part, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number), + CopySourceIfMatch(meta.Get("Etag"))) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + cmur, err := s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + testLogger.Println("cmur:", cmur) + + err = s.bucket.GetObjectToFile(objectDesc, "newpic5.jpg") + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectSrc) + c.Assert(err, IsNil) + err = s.bucket.DeleteObject(objectDesc) + c.Assert(err, IsNil) +} + +// TestMultipartUploadFromFileOutofOrder +func (s *OssBucketMultipartSuite) TestMultipartUploadFromFileOutofOrder(c *C) { + objectName := objectNamePrefix + "tmuffoo" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartSize(fileName, 1024*100) + shuffleArray(chunks) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + imur, err := s.bucket.InitiateMultipartUpload(objectName) + var parts []UploadPart + for _, chunk := range chunks { + _, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + } + // Double upload + for _, chunk := range chunks { + part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + cmur, err := s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + testLogger.Println("cmur:", cmur) + + err = s.bucket.GetObjectToFile(objectName, "newpic6.jpg") + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestUploadPartCopyOutofOrder +func (s *OssBucketMultipartSuite) TestUploadPartCopyOutofOrder(c *C) { + objectSrc := objectNamePrefix + "tupcoo" + "src" + objectDesc := objectNamePrefix + "tupcoo" + "desc" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartSize(fileName, 1024*100) + shuffleArray(chunks) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + err = s.bucket.PutObjectFromFile(objectSrc, fileName) + c.Assert(err, IsNil) + + imur, err := s.bucket.InitiateMultipartUpload(objectDesc) + var parts []UploadPart + for _, chunk := range chunks { + _, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + } + // Double copy + for _, chunk := range chunks { + part, err := s.bucket.UploadPartCopy(imur, bucketName, objectSrc, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + cmur, err := s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + testLogger.Println("cmur:", cmur) + + err = s.bucket.GetObjectToFile(objectDesc, "newpic7.jpg") + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectSrc) + c.Assert(err, IsNil) + err = s.bucket.DeleteObject(objectDesc) + c.Assert(err, IsNil) +} + +// TestMultipartUploadFromFileType +func (s *OssBucketMultipartSuite) TestMultipartUploadFromFileType(c *C) { + objectName := objectNamePrefix + "tmuffwm" + ".jpg" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartNum(fileName, 4) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + imur, err := s.bucket.InitiateMultipartUpload(objectName) + var parts []UploadPart + for _, chunk := range chunks { + part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + testLogger.Println("parts:", parts) + cmur, err := s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + testLogger.Println("cmur:", cmur) + + err = s.bucket.GetObjectToFile(objectName, "newpic8.jpg") + c.Assert(err, IsNil) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("Content-Type"), Equals, "image/jpeg") + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +func (s *OssBucketMultipartSuite) TestListMultipartUploads(c *C) { + objectName := objectNamePrefix + "tlmu" + + imurs := []InitiateMultipartUploadResult{} + for i := 0; i < 20; i++ { + imur, err := s.bucket.InitiateMultipartUpload(objectName + strconv.Itoa(i)) + c.Assert(err, IsNil) + imurs = append(imurs, imur) + } + + lmpu, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 20) + + lmpu, err = s.bucket.ListMultipartUploads(MaxUploads(3)) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 3) + + lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName)) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 20) + + lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName + "1")) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 11) + + lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName + "22")) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 0) + + lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectName + "10")) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 17) + + lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectName+"10"), MaxUploads(3)) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 3) + + lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectName), Delimiter("4")) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 18) + c.Assert(len(lmpu.CommonPrefixes), Equals, 2) + + // Upload-id-marker + lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectName+"12"), UploadIDMarker("EEE")) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 15) + //testLogger.Println("UploadIDMarker", lmpu.Uploads) + + for _, imur := range imurs { + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } +} + +func (s *OssBucketMultipartSuite) TestListMultipartUploadsEncodingKey(c *C) { + objectName := objectNamePrefix + "让你任性让你狂" + "tlmuek" + + imurs := []InitiateMultipartUploadResult{} + for i := 0; i < 3; i++ { + imur, err := s.bucket.InitiateMultipartUpload(objectName + strconv.Itoa(i)) + c.Assert(err, IsNil) + imurs = append(imurs, imur) + } + + lmpu, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 3) + + lmpu, err = s.bucket.ListMultipartUploads(Prefix(objectNamePrefix + "让你任性让你狂tlmuek1")) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 1) + + lmpu, err = s.bucket.ListMultipartUploads(KeyMarker(objectNamePrefix + "让你任性让你狂tlmuek1")) + c.Assert(err, IsNil) + c.Assert(len(lmpu.Uploads), Equals, 1) + + lmpu, err = s.bucket.ListMultipartUploads(EncodingType("url")) + c.Assert(err, IsNil) + for i, upload := range lmpu.Uploads { + c.Assert(upload.Key, Equals, objectNamePrefix+"让你任性让你狂tlmuek"+strconv.Itoa(i)) + } + + for _, imur := range imurs { + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } +} + +func (s *OssBucketMultipartSuite) TestMultipartNegative(c *C) { + objectName := objectNamePrefix + "tmn" + + // Key tool long + data := make([]byte, 100*1024) + imur, err := s.bucket.InitiateMultipartUpload(string(data)) + c.Assert(err, NotNil) + + // Invalid imur + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + fd, err := os.Open(fileName) + c.Assert(err, IsNil) + defer fd.Close() + + _, err = s.bucket.UploadPart(imur, fd, 1024, 1) + c.Assert(err, NotNil) + + _, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 1024, 1) + c.Assert(err, NotNil) + + _, err = s.bucket.UploadPartCopy(imur, bucketName, fileName, 0, 1024, 1) + c.Assert(err, NotNil) + + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, NotNil) + + _, err = s.bucket.ListUploadedParts(imur) + c.Assert(err, NotNil) + + // Invalid exist + imur, err = s.bucket.InitiateMultipartUpload(objectName) + c.Assert(err, IsNil) + + _, err = s.bucket.UploadPart(imur, fd, 1024, 1) + c.Assert(err, IsNil) + + _, err = s.bucket.UploadPart(imur, fd, 102400, 10001) + c.Assert(err, NotNil) + + // _, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 1024, 1) + // c.Assert(err, IsNil) + + _, err = s.bucket.UploadPartFromFile(imur, fileName, 0, 102400, 10001) + c.Assert(err, NotNil) + + _, err = s.bucket.UploadPartCopy(imur, bucketName, fileName, 0, 1024, 1) + c.Assert(err, NotNil) + + _, err = s.bucket.UploadPartCopy(imur, bucketName, fileName, 0, 1024, 1000) + c.Assert(err, NotNil) + + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + + // Invalid option + _, err = s.bucket.InitiateMultipartUpload(objectName, IfModifiedSince(futureDate)) + c.Assert(err, IsNil) +} + +func (s *OssBucketMultipartSuite) TestMultipartUploadFromFileBigFile(c *C) { + objectName := objectNamePrefix + "tmuffbf" + bigFile := "D:\\tmp\\bigfile.zip" + newFile := "D:\\tmp\\newbigfile.zip" + + exist, err := isFileExist(bigFile) + c.Assert(err, IsNil) + if !exist { + return + } + + chunks, err := SplitFileByPartNum(bigFile, 64) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + imur, err := s.bucket.InitiateMultipartUpload(objectName) + var parts []UploadPart + start := GetNowSec() + for _, chunk := range chunks { + part, err := s.bucket.UploadPartFromFile(imur, bigFile, chunk.Offset, chunk.Size, (int)(chunk.Number)) + c.Assert(err, IsNil) + parts = append(parts, part) + } + end := GetNowSec() + testLogger.Println("Uplaod big file:", bigFile, "use sec:", end-start) + + testLogger.Println("parts:", parts) + _, err = s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + + start = GetNowSec() + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + end = GetNowSec() + testLogger.Println("Download big file:", bigFile, "use sec:", end-start) + + start = GetNowSec() + eq, err := compareFiles(bigFile, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + end = GetNowSec() + testLogger.Println("Compare big file:", bigFile, "use sec:", end-start) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestUploadFile +func (s *OssBucketMultipartSuite) TestUploadFile(c *C) { + objectName := objectNamePrefix + "tuff" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "newfiletuff.jpg" + + // Upload with 100K part size + err := s.bucket.UploadFile(objectName, fileName, 100*1024) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Upload with part size equals to 1/4 of the file size + err = s.bucket.UploadFile(objectName, fileName, 482048/4) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Upload with part size equals to the file size + err = s.bucket.UploadFile(objectName, fileName, 482048) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Upload with part size is bigger than the file size + err = s.bucket.UploadFile(objectName, fileName, 482049) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Option + options := []Option{ + Expires(futureDate), + ObjectACL(ACLPublicRead), + Meta("myprop", "mypropval")} + err = s.bucket.UploadFile(objectName, fileName, 482049, options...) + c.Assert(err, IsNil) + + // Check + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + acl, err := s.bucket.GetObjectACL(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectAcl:", acl) + c.Assert(acl.ACL, Equals, "default") + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") +} + +func (s *OssBucketMultipartSuite) TestUploadFileNegative(c *C) { + objectName := objectNamePrefix + "tufn" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + // Smaller than the required minimal part size (100KB) + err := s.bucket.UploadFile(objectName, fileName, 100*1024-1) + c.Assert(err, NotNil) + + // Bigger than the max part size (5G) + err = s.bucket.UploadFile(objectName, fileName, 1024*1024*1024*5+1) + c.Assert(err, NotNil) + + // File does not exist + err = s.bucket.UploadFile(objectName, "/root/123abc9874", 1024*1024*1024) + c.Assert(err, NotNil) + + // Invalid key , key is empty. + err = s.bucket.UploadFile("", fileName, 100*1024) + c.Assert(err, NotNil) +} + +// TestDownloadFile +func (s *OssBucketMultipartSuite) TestDownloadFile(c *C) { + objectName := objectNamePrefix + "tdff" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "newfiletdff.jpg" + + err := s.bucket.UploadFile(objectName, fileName, 100*1024) + c.Assert(err, IsNil) + + // Download file with part size of 100K + err = s.bucket.DownloadFile(objectName, newFile, 100*1024) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download the file with part size equals to 1/4 of the file size + err = s.bucket.DownloadFile(objectName, newFile, 482048/4) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download the file with part size same as the file size + err = s.bucket.DownloadFile(objectName, newFile, 482048) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Download the file with part size bigger than the file size + err = s.bucket.DownloadFile(objectName, newFile, 482049) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // Option + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + testLogger.Println("GetObjectDetailedMeta:", meta) + + // If-Match + err = s.bucket.DownloadFile(objectName, newFile, 482048/4, IfMatch(meta.Get("Etag"))) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + // If-None-Match + err = s.bucket.DownloadFile(objectName, newFile, 482048, IfNoneMatch(meta.Get("Etag"))) + c.Assert(err, NotNil) + + os.Remove(newFile) + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +func (s *OssBucketMultipartSuite) TestDownloadFileNegative(c *C) { + objectName := objectNamePrefix + "tufn" + newFile := "newfiletudff.jpg" + + // Smaller than the required minimal part size (100KB) + err := s.bucket.DownloadFile(objectName, newFile, 100*1024-1) + c.Assert(err, NotNil) + + // Bigger than the required max part size (5G) + err = s.bucket.DownloadFile(objectName, newFile, 1024*1024*1024+1) + c.Assert(err, NotNil) + + // File does not exist + err = s.bucket.DownloadFile(objectName, "/OSS/TEMP/ZIBI/QUQU/BALA", 1024*1024*1024+1) + c.Assert(err, NotNil) + + // Key does not exist + err = s.bucket.DownloadFile(objectName, newFile, 100*1024) + c.Assert(err, NotNil) +} + +// Private +func shuffleArray(chunks []FileChunk) []FileChunk { + for i := range chunks { + j := rand.Intn(i + 1) + chunks[i], chunks[j] = chunks[j], chunks[i] + } + return chunks +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option.go new file mode 100644 index 000000000..5952f8ae3 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option.go @@ -0,0 +1,433 @@ +package oss + +import ( + "fmt" + "net/http" + "strconv" + "strings" + "time" +) + +type optionType string + +const ( + optionParam optionType = "HTTPParameter" // URL parameter + optionHTTP optionType = "HTTPHeader" // HTTP header + optionArg optionType = "FuncArgument" // Function argument +) + +const ( + deleteObjectsQuiet = "delete-objects-quiet" + routineNum = "x-routine-num" + checkpointConfig = "x-cp-config" + initCRC64 = "init-crc64" + progressListener = "x-progress-listener" + storageClass = "storage-class" +) + +type ( + optionValue struct { + Value interface{} + Type optionType + } + + // Option HTTP option + Option func(map[string]optionValue) error +) + +// ACL is an option to set X-Oss-Acl header +func ACL(acl ACLType) Option { + return setHeader(HTTPHeaderOssACL, string(acl)) +} + +// ContentType is an option to set Content-Type header +func ContentType(value string) Option { + return setHeader(HTTPHeaderContentType, value) +} + +// ContentLength is an option to set Content-Length header +func ContentLength(length int64) Option { + return setHeader(HTTPHeaderContentLength, strconv.FormatInt(length, 10)) +} + +// CacheControl is an option to set Cache-Control header +func CacheControl(value string) Option { + return setHeader(HTTPHeaderCacheControl, value) +} + +// ContentDisposition is an option to set Content-Disposition header +func ContentDisposition(value string) Option { + return setHeader(HTTPHeaderContentDisposition, value) +} + +// ContentEncoding is an option to set Content-Encoding header +func ContentEncoding(value string) Option { + return setHeader(HTTPHeaderContentEncoding, value) +} + +// ContentLanguage is an option to set Content-Language header +func ContentLanguage(value string) Option { + return setHeader(HTTPHeaderContentLanguage, value) +} + +// ContentMD5 is an option to set Content-MD5 header +func ContentMD5(value string) Option { + return setHeader(HTTPHeaderContentMD5, value) +} + +// Expires is an option to set Expires header +func Expires(t time.Time) Option { + return setHeader(HTTPHeaderExpires, t.Format(http.TimeFormat)) +} + +// Meta is an option to set Meta header +func Meta(key, value string) Option { + return setHeader(HTTPHeaderOssMetaPrefix+key, value) +} + +// Range is an option to set Range header, [start, end] +func Range(start, end int64) Option { + return setHeader(HTTPHeaderRange, fmt.Sprintf("bytes=%d-%d", start, end)) +} + +// NormalizedRange is an option to set Range header, such as 1024-2048 or 1024- or -2048 +func NormalizedRange(nr string) Option { + return setHeader(HTTPHeaderRange, fmt.Sprintf("bytes=%s", strings.TrimSpace(nr))) +} + +// AcceptEncoding is an option to set Accept-Encoding header +func AcceptEncoding(value string) Option { + return setHeader(HTTPHeaderAcceptEncoding, value) +} + +// IfModifiedSince is an option to set If-Modified-Since header +func IfModifiedSince(t time.Time) Option { + return setHeader(HTTPHeaderIfModifiedSince, t.Format(http.TimeFormat)) +} + +// IfUnmodifiedSince is an option to set If-Unmodified-Since header +func IfUnmodifiedSince(t time.Time) Option { + return setHeader(HTTPHeaderIfUnmodifiedSince, t.Format(http.TimeFormat)) +} + +// IfMatch is an option to set If-Match header +func IfMatch(value string) Option { + return setHeader(HTTPHeaderIfMatch, value) +} + +// IfNoneMatch is an option to set IfNoneMatch header +func IfNoneMatch(value string) Option { + return setHeader(HTTPHeaderIfNoneMatch, value) +} + +// CopySource is an option to set X-Oss-Copy-Source header +func CopySource(sourceBucket, sourceObject string) Option { + return setHeader(HTTPHeaderOssCopySource, "/"+sourceBucket+"/"+sourceObject) +} + +// CopySourceRange is an option to set X-Oss-Copy-Source header +func CopySourceRange(startPosition, partSize int64) Option { + val := "bytes=" + strconv.FormatInt(startPosition, 10) + "-" + + strconv.FormatInt((startPosition+partSize-1), 10) + return setHeader(HTTPHeaderOssCopySourceRange, val) +} + +// CopySourceIfMatch is an option to set X-Oss-Copy-Source-If-Match header +func CopySourceIfMatch(value string) Option { + return setHeader(HTTPHeaderOssCopySourceIfMatch, value) +} + +// CopySourceIfNoneMatch is an option to set X-Oss-Copy-Source-If-None-Match header +func CopySourceIfNoneMatch(value string) Option { + return setHeader(HTTPHeaderOssCopySourceIfNoneMatch, value) +} + +// CopySourceIfModifiedSince is an option to set X-Oss-CopySource-If-Modified-Since header +func CopySourceIfModifiedSince(t time.Time) Option { + return setHeader(HTTPHeaderOssCopySourceIfModifiedSince, t.Format(http.TimeFormat)) +} + +// CopySourceIfUnmodifiedSince is an option to set X-Oss-Copy-Source-If-Unmodified-Since header +func CopySourceIfUnmodifiedSince(t time.Time) Option { + return setHeader(HTTPHeaderOssCopySourceIfUnmodifiedSince, t.Format(http.TimeFormat)) +} + +// MetadataDirective is an option to set X-Oss-Metadata-Directive header +func MetadataDirective(directive MetadataDirectiveType) Option { + return setHeader(HTTPHeaderOssMetadataDirective, string(directive)) +} + +// ServerSideEncryption is an option to set X-Oss-Server-Side-Encryption header +func ServerSideEncryption(value string) Option { + return setHeader(HTTPHeaderOssServerSideEncryption, value) +} + +// ServerSideEncryptionKeyID is an option to set X-Oss-Server-Side-Encryption-Key-Id header +func ServerSideEncryptionKeyID(value string) Option { + return setHeader(HTTPHeaderOssServerSideEncryptionKeyID, value) +} + +// ObjectACL is an option to set X-Oss-Object-Acl header +func ObjectACL(acl ACLType) Option { + return setHeader(HTTPHeaderOssObjectACL, string(acl)) +} + +// symlinkTarget is an option to set X-Oss-Symlink-Target +func symlinkTarget(targetObjectKey string) Option { + return setHeader(HTTPHeaderOssSymlinkTarget, targetObjectKey) +} + +// Origin is an option to set Origin header +func Origin(value string) Option { + return setHeader(HTTPHeaderOrigin, value) +} + +// ObjectStorageClass is an option to set the storage class of object +func ObjectStorageClass(storageClass StorageClassType) Option { + return setHeader(HTTPHeaderOssStorageClass, string(storageClass)) +} + +// Callback is an option to set callback values +func Callback(callback string) Option { + return setHeader(HTTPHeaderOssCallback, callback) +} + +// CallbackVar is an option to set callback user defined values +func CallbackVar(callbackVar string) Option { + return setHeader(HTTPHeaderOssCallbackVar, callbackVar) +} + +// RequestPayer is an option to set payer who pay for the request +func RequestPayer(payerType PayerType) Option { + return setHeader(HTTPHeaderOSSRequester, string(payerType)) +} + +// Delimiter is an option to set delimiler parameter +func Delimiter(value string) Option { + return addParam("delimiter", value) +} + +// Marker is an option to set marker parameter +func Marker(value string) Option { + return addParam("marker", value) +} + +// MaxKeys is an option to set maxkeys parameter +func MaxKeys(value int) Option { + return addParam("max-keys", strconv.Itoa(value)) +} + +// Prefix is an option to set prefix parameter +func Prefix(value string) Option { + return addParam("prefix", value) +} + +// EncodingType is an option to set encoding-type parameter +func EncodingType(value string) Option { + return addParam("encoding-type", value) +} + +// MaxUploads is an option to set max-uploads parameter +func MaxUploads(value int) Option { + return addParam("max-uploads", strconv.Itoa(value)) +} + +// KeyMarker is an option to set key-marker parameter +func KeyMarker(value string) Option { + return addParam("key-marker", value) +} + +// UploadIDMarker is an option to set upload-id-marker parameter +func UploadIDMarker(value string) Option { + return addParam("upload-id-marker", value) +} + +// MaxParts is an option to set max-parts parameter +func MaxParts(value int) Option { + return addParam("max-parts", strconv.Itoa(value)) +} + +// PartNumberMarker is an option to set part-number-marker parameter +func PartNumberMarker(value int) Option { + return addParam("part-number-marker", strconv.Itoa(value)) +} + +// DeleteObjectsQuiet false:DeleteObjects in verbose mode; true:DeleteObjects in quite mode. Default is false. +func DeleteObjectsQuiet(isQuiet bool) Option { + return addArg(deleteObjectsQuiet, isQuiet) +} + +// StorageClass bucket storage class +func StorageClass(value StorageClassType) Option { + return addArg(storageClass, value) +} + +// Checkpoint configuration +type cpConfig struct { + IsEnable bool + FilePath string + DirPath string +} + +// Checkpoint sets the isEnable flag and checkpoint file path for DownloadFile/UploadFile. +func Checkpoint(isEnable bool, filePath string) Option { + return addArg(checkpointConfig, &cpConfig{IsEnable: isEnable, FilePath: filePath}) +} + +// CheckpointDir sets the isEnable flag and checkpoint dir path for DownloadFile/UploadFile. +func CheckpointDir(isEnable bool, dirPath string) Option { + return addArg(checkpointConfig, &cpConfig{IsEnable: isEnable, DirPath: dirPath}) +} + +// Routines DownloadFile/UploadFile routine count +func Routines(n int) Option { + return addArg(routineNum, n) +} + +// InitCRC Init AppendObject CRC +func InitCRC(initCRC uint64) Option { + return addArg(initCRC64, initCRC) +} + +// Progress set progress listener +func Progress(listener ProgressListener) Option { + return addArg(progressListener, listener) +} + +// ResponseContentType is an option to set response-content-type param +func ResponseContentType(value string) Option { + return addParam("response-content-type", value) +} + +// ResponseContentLanguage is an option to set response-content-language param +func ResponseContentLanguage(value string) Option { + return addParam("response-content-language", value) +} + +// ResponseExpires is an option to set response-expires param +func ResponseExpires(value string) Option { + return addParam("response-expires", value) +} + +// ResponseCacheControl is an option to set response-cache-control param +func ResponseCacheControl(value string) Option { + return addParam("response-cache-control", value) +} + +// ResponseContentDisposition is an option to set response-content-disposition param +func ResponseContentDisposition(value string) Option { + return addParam("response-content-disposition", value) +} + +// ResponseContentEncoding is an option to set response-content-encoding param +func ResponseContentEncoding(value string) Option { + return addParam("response-content-encoding", value) +} + +// Process is an option to set x-oss-process param +func Process(value string) Option { + return addParam("x-oss-process", value) +} + +func setHeader(key string, value interface{}) Option { + return func(params map[string]optionValue) error { + if value == nil { + return nil + } + params[key] = optionValue{value, optionHTTP} + return nil + } +} + +func addParam(key string, value interface{}) Option { + return func(params map[string]optionValue) error { + if value == nil { + return nil + } + params[key] = optionValue{value, optionParam} + return nil + } +} + +func addArg(key string, value interface{}) Option { + return func(params map[string]optionValue) error { + if value == nil { + return nil + } + params[key] = optionValue{value, optionArg} + return nil + } +} + +func handleOptions(headers map[string]string, options []Option) error { + params := map[string]optionValue{} + for _, option := range options { + if option != nil { + if err := option(params); err != nil { + return err + } + } + } + + for k, v := range params { + if v.Type == optionHTTP { + headers[k] = v.Value.(string) + } + } + return nil +} + +func getRawParams(options []Option) (map[string]interface{}, error) { + // Option + params := map[string]optionValue{} + for _, option := range options { + if option != nil { + if err := option(params); err != nil { + return nil, err + } + } + } + + paramsm := map[string]interface{}{} + // Serialize + for k, v := range params { + if v.Type == optionParam { + vs := params[k] + paramsm[k] = vs.Value.(string) + } + } + + return paramsm, nil +} + +func findOption(options []Option, param string, defaultVal interface{}) (interface{}, error) { + params := map[string]optionValue{} + for _, option := range options { + if option != nil { + if err := option(params); err != nil { + return nil, err + } + } + } + + if val, ok := params[param]; ok { + return val.Value, nil + } + return defaultVal, nil +} + +func isOptionSet(options []Option, option string) (bool, interface{}, error) { + params := map[string]optionValue{} + for _, option := range options { + if option != nil { + if err := option(params); err != nil { + return false, nil, err + } + } + } + + if val, ok := params[option]; ok { + return true, val.Value, nil + } + return false, nil, nil +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option_test.go new file mode 100644 index 000000000..24213b594 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/option_test.go @@ -0,0 +1,299 @@ +package oss + +import ( + "net/http" + + . "gopkg.in/check.v1" +) + +type OssOptionSuite struct{} + +var _ = Suite(&OssOptionSuite{}) + +type optionTestCase struct { + option Option + key string + value string +} + +var headerTestcases = []optionTestCase{ + { + option: Meta("User", "baymax"), + key: "X-Oss-Meta-User", + value: "baymax", + }, + { + option: ACL(ACLPrivate), + key: "X-Oss-Acl", + value: "private", + }, + { + option: ContentType("plain/text"), + key: "Content-Type", + value: "plain/text", + }, + { + option: CacheControl("no-cache"), + key: "Cache-Control", + value: "no-cache", + }, + { + option: ContentDisposition("Attachment; filename=example.txt"), + key: "Content-Disposition", + value: "Attachment; filename=example.txt", + }, + { + option: ContentEncoding("gzip"), + key: "Content-Encoding", + value: "gzip", + }, + { + option: Expires(pastDate), + key: "Expires", + value: pastDate.Format(http.TimeFormat), + }, + { + option: Range(0, 9), + key: "Range", + value: "bytes=0-9", + }, + { + option: Origin("localhost"), + key: "Origin", + value: "localhost", + }, + { + option: CopySourceRange(0, 9), + key: "X-Oss-Copy-Source-Range", + value: "bytes=0-8", + }, + { + option: IfModifiedSince(pastDate), + key: "If-Modified-Since", + value: pastDate.Format(http.TimeFormat), + }, + { + option: IfUnmodifiedSince(futureDate), + key: "If-Unmodified-Since", + value: futureDate.Format(http.TimeFormat), + }, + { + option: IfMatch("xyzzy"), + key: "If-Match", + value: "xyzzy", + }, + { + option: IfNoneMatch("xyzzy"), + key: "If-None-Match", + value: "xyzzy", + }, + { + option: CopySource("bucket_name", "object_name"), + key: "X-Oss-Copy-Source", + value: "/bucket_name/object_name", + }, + { + option: CopySourceIfModifiedSince(pastDate), + key: "X-Oss-Copy-Source-If-Modified-Since", + value: pastDate.Format(http.TimeFormat), + }, + { + option: CopySourceIfUnmodifiedSince(futureDate), + key: "X-Oss-Copy-Source-If-Unmodified-Since", + value: futureDate.Format(http.TimeFormat), + }, + { + option: CopySourceIfMatch("xyzzy"), + key: "X-Oss-Copy-Source-If-Match", + value: "xyzzy", + }, + { + option: CopySourceIfNoneMatch("xyzzy"), + key: "X-Oss-Copy-Source-If-None-Match", + value: "xyzzy", + }, + { + option: MetadataDirective(MetaCopy), + key: "X-Oss-Metadata-Directive", + value: "COPY", + }, + { + option: ServerSideEncryption("AES256"), + key: "X-Oss-Server-Side-Encryption", + value: "AES256", + }, + { + option: ObjectACL(ACLPrivate), + key: "X-Oss-Object-Acl", + value: "private", + }, + { + option: ObjectStorageClass(StorageStandard), + key: "X-Oss-Storage-Class", + value: "Standard", + }, + { + option: Callback("JTdCJTIyY2FsbGJhY2tVcmwlMjIlM0ElMjJleGFtcGxlLmNvbS9pbmRleC5odG1sJTIyJTdE"), + key: "X-Oss-Callback", + value: "JTdCJTIyY2FsbGJhY2tVcmwlMjIlM0ElMjJleGFtcGxlLmNvbS9pbmRleC5odG1sJTIyJTdE", + }, + { + option: CallbackVar("JTdCJTIyeCUzQXZhcjElMjIlM0ElMjJ2YWx1ZTElMjIlMkMlMjJ4JTNBdmFyMiUyMiUzQSUyMnZhbHVlMiUyMiU3RA=="), + key: "X-Oss-Callback-Var", + value: "JTdCJTIyeCUzQXZhcjElMjIlM0ElMjJ2YWx1ZTElMjIlMkMlMjJ4JTNBdmFyMiUyMiUzQSUyMnZhbHVlMiUyMiU3RA==", + }, + { + option: ContentLanguage("zh-CN"), + key: "Content-Language", + value: "zh-CN", + }, + { + option: ServerSideEncryptionKeyID("xossekid"), + key: "X-Oss-Server-Side-Encryption-Key-Id", + value: "xossekid", + }, +} + +func (s *OssOptionSuite) TestHeaderOptions(c *C) { + for _, testcase := range headerTestcases { + headers := make(map[string]optionValue) + err := testcase.option(headers) + c.Assert(err, IsNil) + + expected, actual := testcase.value, headers[testcase.key].Value + c.Assert(expected, Equals, actual) + } +} + +var paramTestCases = []optionTestCase{ + { + option: Delimiter("/"), + key: "delimiter", + value: "/", + }, + { + option: Marker("abc"), + key: "marker", + value: "abc", + }, + { + option: MaxKeys(150), + key: "max-keys", + value: "150", + }, + { + option: Prefix("fun"), + key: "prefix", + value: "fun", + }, + { + option: EncodingType("ascii"), + key: "encoding-type", + value: "ascii", + }, + { + option: MaxUploads(100), + key: "max-uploads", + value: "100", + }, + { + option: KeyMarker("abc"), + key: "key-marker", + value: "abc", + }, + { + option: UploadIDMarker("xyz"), + key: "upload-id-marker", + value: "xyz", + }, + { + option: MaxParts(1000), + key: "max-parts", + value: "1000", + }, + { + option: PartNumberMarker(1), + key: "part-number-marker", + value: "1", + }, + { + option: Process("image/format,png"), + key: "x-oss-process", + value: "image/format,png", + }, +} + +func (s *OssOptionSuite) TestParamOptions(c *C) { + for _, testcase := range paramTestCases { + params := make(map[string]optionValue) + err := testcase.option(params) + c.Assert(err, IsNil) + + expected, actual := testcase.value, params[testcase.key].Value + c.Assert(expected, Equals, actual) + } +} + +func (s *OssOptionSuite) TestHandleOptions(c *C) { + headers := make(map[string]string) + options := []Option{} + + for _, testcase := range headerTestcases { + options = append(options, testcase.option) + } + + err := handleOptions(headers, options) + c.Assert(err, IsNil) + + for _, testcase := range headerTestcases { + expected, actual := testcase.value, headers[testcase.key] + c.Assert(expected, Equals, actual) + } + + options = []Option{IfMatch(""), nil} + headers = map[string]string{} + err = handleOptions(headers, options) + c.Assert(err, IsNil) + c.Assert(len(headers), Equals, 1) +} + +func (s *OssOptionSuite) TestHandleParams(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + + options := []Option{} + + for _, testcase := range paramTestCases { + options = append(options, testcase.option) + } + + params, err := getRawParams(options) + c.Assert(err, IsNil) + + out := client.Conn.getURLParams(params) + c.Assert(len(out), Equals, 191) + + options = []Option{KeyMarker(""), nil} + + params, err = getRawParams(options) + c.Assert(err, IsNil) + + out = client.Conn.getURLParams(params) + c.Assert(out, Equals, "key-marker=") +} + +func (s *OssOptionSuite) TestFindOption(c *C) { + options := []Option{} + + for _, testcase := range headerTestcases { + options = append(options, testcase.option) + } + + str, err := findOption(options, "X-Oss-Acl", "") + c.Assert(err, IsNil) + c.Assert(str, Equals, "private") + + str, err = findOption(options, "MyProp", "") + c.Assert(err, IsNil) + c.Assert(str, Equals, "") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress.go new file mode 100644 index 000000000..b38d803fe --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress.go @@ -0,0 +1,112 @@ +package oss + +import "io" + +// ProgressEventType defines transfer progress event type +type ProgressEventType int + +const ( + // TransferStartedEvent transfer started, set TotalBytes + TransferStartedEvent ProgressEventType = 1 + iota + // TransferDataEvent transfer data, set ConsumedBytes anmd TotalBytes + TransferDataEvent + // TransferCompletedEvent transfer completed + TransferCompletedEvent + // TransferFailedEvent transfer encounters an error + TransferFailedEvent +) + +// ProgressEvent defines progress event +type ProgressEvent struct { + ConsumedBytes int64 + TotalBytes int64 + EventType ProgressEventType +} + +// ProgressListener listens progress change +type ProgressListener interface { + ProgressChanged(event *ProgressEvent) +} + +// -------------------- Private -------------------- + +func newProgressEvent(eventType ProgressEventType, consumed, total int64) *ProgressEvent { + return &ProgressEvent{ + ConsumedBytes: consumed, + TotalBytes: total, + EventType: eventType} +} + +// publishProgress +func publishProgress(listener ProgressListener, event *ProgressEvent) { + if listener != nil && event != nil { + listener.ProgressChanged(event) + } +} + +type readerTracker struct { + completedBytes int64 +} + +type teeReader struct { + reader io.Reader + writer io.Writer + listener ProgressListener + consumedBytes int64 + totalBytes int64 + tracker *readerTracker +} + +// TeeReader returns a Reader that writes to w what it reads from r. +// All reads from r performed through it are matched with +// corresponding writes to w. There is no internal buffering - +// the write must complete before the read completes. +// Any error encountered while writing is reported as a read error. +func TeeReader(reader io.Reader, writer io.Writer, totalBytes int64, listener ProgressListener, tracker *readerTracker) io.ReadCloser { + return &teeReader{ + reader: reader, + writer: writer, + listener: listener, + consumedBytes: 0, + totalBytes: totalBytes, + tracker: tracker, + } +} + +func (t *teeReader) Read(p []byte) (n int, err error) { + n, err = t.reader.Read(p) + + // Read encountered error + if err != nil && err != io.EOF { + event := newProgressEvent(TransferFailedEvent, t.consumedBytes, t.totalBytes) + publishProgress(t.listener, event) + } + + if n > 0 { + t.consumedBytes += int64(n) + // CRC + if t.writer != nil { + if n, err := t.writer.Write(p[:n]); err != nil { + return n, err + } + } + // Progress + if t.listener != nil { + event := newProgressEvent(TransferDataEvent, t.consumedBytes, t.totalBytes) + publishProgress(t.listener, event) + } + // Track + if t.tracker != nil { + t.tracker.completedBytes = t.consumedBytes + } + } + + return +} + +func (t *teeReader) Close() error { + if rc, ok := t.reader.(io.ReadCloser); ok { + return rc.Close() + } + return nil +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress_test.go new file mode 100644 index 000000000..54ed30ea8 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/progress_test.go @@ -0,0 +1,460 @@ +// bucket test + +package oss + +import ( + "bytes" + "io/ioutil" + "math/rand" + "os" + "strings" + "time" + + . "gopkg.in/check.v1" +) + +type OssProgressSuite struct { + client *Client + bucket *Bucket +} + +var _ = Suite(&OssProgressSuite{}) + +// SetUpSuite runs once when the suite starts running +func (s *OssProgressSuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + s.client = client + + s.client.CreateBucket(bucketName) + time.Sleep(5 * time.Second) + + bucket, err := s.client.Bucket(bucketName) + c.Assert(err, IsNil) + s.bucket = bucket + + testLogger.Println("test progress started") +} + +// TearDownSuite runs before each test or benchmark starts running +func (s *OssProgressSuite) TearDownSuite(c *C) { + // Abort multipart uploads + lmu, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + + for _, upload := range lmu.Uploads { + imur := InitiateMultipartUploadResult{Bucket: bucketName, Key: upload.Key, UploadID: upload.UploadID} + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } + + // Delete objects + lor, err := s.bucket.ListObjects() + c.Assert(err, IsNil) + + for _, object := range lor.Objects { + err = s.bucket.DeleteObject(object.Key) + c.Assert(err, IsNil) + } + + testLogger.Println("test progress completed") +} + +// SetUpTest runs after each test or benchmark runs +func (s *OssProgressSuite) SetUpTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".txt") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".html") + c.Assert(err, IsNil) +} + +// TearDownTest runs once after all tests or benchmarks have finished running +func (s *OssProgressSuite) TearDownTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".txt") + c.Assert(err, IsNil) + + err = removeTempFiles("../oss", ".html") + c.Assert(err, IsNil) +} + +// OssProgressListener is the progress listener +type OssProgressListener struct { +} + +// ProgressChanged handles progress event +func (listener *OssProgressListener) ProgressChanged(event *ProgressEvent) { + switch event.EventType { + case TransferStartedEvent: + testLogger.Printf("Transfer Started, ConsumedBytes: %d, TotalBytes %d.\n", + event.ConsumedBytes, event.TotalBytes) + case TransferDataEvent: + testLogger.Printf("Transfer Data, ConsumedBytes: %d, TotalBytes %d, %d%%.\n", + event.ConsumedBytes, event.TotalBytes, event.ConsumedBytes*100/event.TotalBytes) + case TransferCompletedEvent: + testLogger.Printf("Transfer Completed, ConsumedBytes: %d, TotalBytes %d.\n", + event.ConsumedBytes, event.TotalBytes) + case TransferFailedEvent: + testLogger.Printf("Transfer Failed, ConsumedBytes: %d, TotalBytes %d.\n", + event.ConsumedBytes, event.TotalBytes) + default: + } +} + +// TestPutObject +func (s *OssProgressSuite) TestPutObject(c *C) { + objectName := objectNamePrefix + "tpo.html" + localFile := "../sample/The Go Programming Language.html" + + // PutObject + fd, err := os.Open(localFile) + c.Assert(err, IsNil) + defer fd.Close() + + err = s.bucket.PutObject(objectName, fd, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + // PutObjectFromFile + err = s.bucket.PutObjectFromFile(objectName, localFile, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + // DoPutObject + fd, err = os.Open(localFile) + c.Assert(err, IsNil) + defer fd.Close() + + request := &PutObjectRequest{ + ObjectKey: objectName, + Reader: fd, + } + + options := []Option{Progress(&OssProgressListener{})} + _, err = s.bucket.DoPutObject(request, options) + c.Assert(err, IsNil) + + // PutObject size is 0 + err = s.bucket.PutObject(objectName, strings.NewReader(""), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + testLogger.Println("OssProgressSuite.TestPutObject") +} + +// TestSignURL +func (s *OssProgressSuite) TestSignURL(c *C) { + objectName := objectNamePrefix + randStr(5) + filePath := randLowStr(10) + content := randStr(20) + createFile(filePath, content, c) + + // Sign URL for put + str, err := s.bucket.SignURL(objectName, HTTPPut, 60, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Put object with URL + fd, err := os.Open(filePath) + c.Assert(err, IsNil) + defer fd.Close() + + err = s.bucket.PutObjectWithURL(str, fd, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + // Put object from file with URL + err = s.bucket.PutObjectFromFileWithURL(str, filePath, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + // DoPutObject + fd, err = os.Open(filePath) + c.Assert(err, IsNil) + defer fd.Close() + + options := []Option{Progress(&OssProgressListener{})} + _, err = s.bucket.DoPutObjectWithURL(str, fd, options) + c.Assert(err, IsNil) + + // Sign URL for get + str, err = s.bucket.SignURL(objectName, HTTPGet, 60, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + c.Assert(strings.Contains(str, HTTPParamExpires+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamAccessKeyID+"="), Equals, true) + c.Assert(strings.Contains(str, HTTPParamSignature+"="), Equals, true) + + // Get object with URL + body, err := s.bucket.GetObjectWithURL(str, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + str, err = readBody(body) + c.Assert(err, IsNil) + c.Assert(str, Equals, content) + + // Get object to file with URL + str, err = s.bucket.SignURL(objectName, HTTPGet, 10, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + newFile := randStr(10) + err = s.bucket.GetObjectToFileWithURL(str, newFile, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + eq, err := compareFiles(filePath, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + os.Remove(filePath) + os.Remove(newFile) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + testLogger.Println("OssProgressSuite.TestSignURL") +} + +func (s *OssProgressSuite) TestPutObjectNegative(c *C) { + objectName := objectNamePrefix + "tpon.html" + localFile := "../sample/The Go Programming Language.html" + + // Invalid endpoint + client, err := New("http://oss-cn-taikang.aliyuncs.com", accessID, accessKey) + c.Assert(err, IsNil) + + bucket, err := client.Bucket(bucketName) + c.Assert(err, IsNil) + + err = bucket.PutObjectFromFile(objectName, localFile, Progress(&OssProgressListener{})) + testLogger.Println(err) + c.Assert(err, NotNil) + + testLogger.Println("OssProgressSuite.TestPutObjectNegative") +} + +// TestAppendObject +func (s *OssProgressSuite) TestAppendObject(c *C) { + objectName := objectNamePrefix + "tao" + objectValue := "昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,却道海棠依旧。知否?知否?应是绿肥红瘦。" + var val = []byte(objectValue) + var nextPos int64 + var midPos = 1 + rand.Intn(len(val)-1) + + // AppendObject + nextPos, err := s.bucket.AppendObject(objectName, bytes.NewReader(val[0:midPos]), nextPos, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + // DoAppendObject + request := &AppendObjectRequest{ + ObjectKey: objectName, + Reader: bytes.NewReader(val[midPos:]), + Position: nextPos, + } + options := []Option{Progress(&OssProgressListener{})} + _, err = s.bucket.DoAppendObject(request, options) + c.Assert(err, IsNil) + + testLogger.Println("OssProgressSuite.TestAppendObject") +} + +// TestMultipartUpload +func (s *OssProgressSuite) TestMultipartUpload(c *C) { + objectName := objectNamePrefix + "tmu.jpg" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartNum(fileName, 3) + c.Assert(err, IsNil) + testLogger.Println("chunks:", chunks) + + fd, err := os.Open(fileName) + c.Assert(err, IsNil) + defer fd.Close() + + // Initiate + imur, err := s.bucket.InitiateMultipartUpload(objectName) + c.Assert(err, IsNil) + + // UploadPart + var parts []UploadPart + for _, chunk := range chunks { + fd.Seek(chunk.Offset, os.SEEK_SET) + part, err := s.bucket.UploadPart(imur, fd, chunk.Size, chunk.Number, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + // Complete + _, err = s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + testLogger.Println("OssProgressSuite.TestMultipartUpload") +} + +// TestMultipartUploadFromFile +func (s *OssProgressSuite) TestMultipartUploadFromFile(c *C) { + objectName := objectNamePrefix + "tmuff.jpg" + var fileName = "../sample/BingWallpaper-2015-11-07.jpg" + + chunks, err := SplitFileByPartNum(fileName, 3) + c.Assert(err, IsNil) + + // Initiate + imur, err := s.bucket.InitiateMultipartUpload(objectName) + c.Assert(err, IsNil) + + // UploadPart + var parts []UploadPart + for _, chunk := range chunks { + part, err := s.bucket.UploadPartFromFile(imur, fileName, chunk.Offset, chunk.Size, chunk.Number, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + parts = append(parts, part) + } + + // Complete + _, err = s.bucket.CompleteMultipartUpload(imur, parts) + c.Assert(err, IsNil) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + testLogger.Println("OssProgressSuite.TestMultipartUploadFromFile") +} + +// TestGetObject +func (s *OssProgressSuite) TestGetObject(c *C) { + objectName := objectNamePrefix + "tgo.jpg" + localFile := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "newpic-progress-1.jpg" + + // PutObject + err := s.bucket.PutObjectFromFile(objectName, localFile, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + // GetObject + body, err := s.bucket.GetObject(objectName, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + _, err = ioutil.ReadAll(body) + c.Assert(err, IsNil) + body.Close() + + // GetObjectToFile + err = s.bucket.GetObjectToFile(objectName, newFile, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + // DoGetObject + request := &GetObjectRequest{objectName} + options := []Option{Progress(&OssProgressListener{})} + result, err := s.bucket.DoGetObject(request, options) + c.Assert(err, IsNil) + _, err = ioutil.ReadAll(result.Response.Body) + c.Assert(err, IsNil) + result.Response.Body.Close() + + // GetObject with range + body, err = s.bucket.GetObject(objectName, Range(1024, 4*1024), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + _, err = ioutil.ReadAll(body) + c.Assert(err, IsNil) + body.Close() + + // PutObject size is 0 + err = s.bucket.PutObject(objectName, strings.NewReader(""), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + // GetObject size is 0 + body, err = s.bucket.GetObject(objectName, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + _, err = ioutil.ReadAll(body) + c.Assert(err, IsNil) + body.Close() + + testLogger.Println("OssProgressSuite.TestGetObject") +} + +// TestGetObjectNegative +func (s *OssProgressSuite) TestGetObjectNegative(c *C) { + objectName := objectNamePrefix + "tgon.jpg" + localFile := "../sample/BingWallpaper-2015-11-07.jpg" + + // PutObject + err := s.bucket.PutObjectFromFile(objectName, localFile) + c.Assert(err, IsNil) + + // GetObject + body, err := s.bucket.GetObject(objectName, Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + buf := make([]byte, 4*1024) + n, err := body.Read(buf) + c.Assert(err, IsNil) + + //time.Sleep(70 * time.Second) TODO + + // Read should fail + for err == nil { + n, err = body.Read(buf) + n += n + } + c.Assert(err, NotNil) + body.Close() + + testLogger.Println("OssProgressSuite.TestGetObjectNegative") +} + +// TestUploadFile +func (s *OssProgressSuite) TestUploadFile(c *C) { + objectName := objectNamePrefix + "tuf.jpg" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(5), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, objectName+".cp"), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + testLogger.Println("OssProgressSuite.TestUploadFile") +} + +// TestDownloadFile +func (s *OssProgressSuite) TestDownloadFile(c *C) { + objectName := objectNamePrefix + "tdf.jpg" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "down-new-file-progress-2.jpg" + + // Upload + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + err = s.bucket.DownloadFile(objectName, newFile, 100*1024, Routines(5), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + err = s.bucket.DownloadFile(objectName, newFile, 1024*1024, Routines(3), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + err = s.bucket.DownloadFile(objectName, newFile, 50*1024, Routines(3), Checkpoint(true, ""), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + testLogger.Println("OssProgressSuite.TestDownloadFile") +} + +// TestCopyFile +func (s *OssProgressSuite) TestCopyFile(c *C) { + srcObjectName := objectNamePrefix + "tcf.jpg" + destObjectName := srcObjectName + "-copy" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + + // Upload + err := s.bucket.UploadFile(srcObjectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 100*1024, Routines(5), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + err = s.bucket.CopyFile(bucketName, srcObjectName, destObjectName, 1024*100, Routines(3), Checkpoint(true, ""), Progress(&OssProgressListener{})) + c.Assert(err, IsNil) + + testLogger.Println("OssProgressSuite.TestCopyFile") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_6.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_6.go new file mode 100644 index 000000000..e6de4cdd2 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_6.go @@ -0,0 +1,26 @@ +// +build !go1.7 + +package oss + +import ( + "net" + "net/http" +) + +func newTransport(conn *Conn, config *Config) *http.Transport { + httpTimeOut := conn.config.HTTPTimeout + httpMaxConns := conn.config.HTTPMaxConns + // New Transport + transport := &http.Transport{ + Dial: func(netw, addr string) (net.Conn, error) { + conn, err := net.DialTimeout(netw, addr, httpTimeOut.ConnectTimeout) + if err != nil { + return nil, err + } + return newTimeoutConn(conn, httpTimeOut.ReadWriteTimeout, httpTimeOut.LongTimeout), nil + }, + MaxIdleConnsPerHost: httpMaxConns.MaxIdleConnsPerHost, + ResponseHeaderTimeout: httpTimeOut.HeaderTimeout, + } + return transport +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_7.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_7.go new file mode 100644 index 000000000..006ea47a0 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/transport_1_7.go @@ -0,0 +1,28 @@ +// +build go1.7 + +package oss + +import ( + "net" + "net/http" +) + +func newTransport(conn *Conn, config *Config) *http.Transport { + httpTimeOut := conn.config.HTTPTimeout + httpMaxConns := conn.config.HTTPMaxConns + // New Transport + transport := &http.Transport{ + Dial: func(netw, addr string) (net.Conn, error) { + conn, err := net.DialTimeout(netw, addr, httpTimeOut.ConnectTimeout) + if err != nil { + return nil, err + } + return newTimeoutConn(conn, httpTimeOut.ReadWriteTimeout, httpTimeOut.LongTimeout), nil + }, + MaxIdleConns: httpMaxConns.MaxIdleConns, + MaxIdleConnsPerHost: httpMaxConns.MaxIdleConnsPerHost, + IdleConnTimeout: httpTimeOut.IdleConnTimeout, + ResponseHeaderTimeout: httpTimeOut.HeaderTimeout, + } + return transport +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type.go new file mode 100644 index 000000000..d205d9ac2 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type.go @@ -0,0 +1,566 @@ +package oss + +import ( + "encoding/xml" + "net/url" + "time" +) + +// ListBucketsResult defines the result object from ListBuckets request +type ListBucketsResult struct { + XMLName xml.Name `xml:"ListAllMyBucketsResult"` + Prefix string `xml:"Prefix"` // The prefix in this query + Marker string `xml:"Marker"` // The marker filter + MaxKeys int `xml:"MaxKeys"` // The max entry count to return. This information is returned when IsTruncated is true. + IsTruncated bool `xml:"IsTruncated"` // Flag true means there's remaining buckets to return. + NextMarker string `xml:"NextMarker"` // The marker filter for the next list call + Owner Owner `xml:"Owner"` // The owner information + Buckets []BucketProperties `xml:"Buckets>Bucket"` // The bucket list +} + +// BucketProperties defines bucket properties +type BucketProperties struct { + XMLName xml.Name `xml:"Bucket"` + Name string `xml:"Name"` // Bucket name + Location string `xml:"Location"` // Bucket datacenter + CreationDate time.Time `xml:"CreationDate"` // Bucket create time + StorageClass string `xml:"StorageClass"` // Bucket storage class +} + +// GetBucketACLResult defines GetBucketACL request's result +type GetBucketACLResult struct { + XMLName xml.Name `xml:"AccessControlPolicy"` + ACL string `xml:"AccessControlList>Grant"` // Bucket ACL + Owner Owner `xml:"Owner"` // Bucket owner +} + +// LifecycleConfiguration is the Bucket Lifecycle configuration +type LifecycleConfiguration struct { + XMLName xml.Name `xml:"LifecycleConfiguration"` + Rules []LifecycleRule `xml:"Rule"` +} + +// LifecycleRule defines Lifecycle rules +type LifecycleRule struct { + XMLName xml.Name `xml:"Rule"` + ID string `xml:"ID"` // The rule ID + Prefix string `xml:"Prefix"` // The object key prefix + Status string `xml:"Status"` // The rule status (enabled or not) + Expiration LifecycleExpiration `xml:"Expiration"` // The expiration property +} + +// LifecycleExpiration defines the rule's expiration property +type LifecycleExpiration struct { + XMLName xml.Name `xml:"Expiration"` + Days int `xml:"Days,omitempty"` // Relative expiration time: The expiration time in days after the last modified time + Date time.Time `xml:"Date,omitempty"` // Absolute expiration time: The expiration time in date. +} + +type lifecycleXML struct { + XMLName xml.Name `xml:"LifecycleConfiguration"` + Rules []lifecycleRule `xml:"Rule"` +} + +type lifecycleRule struct { + XMLName xml.Name `xml:"Rule"` + ID string `xml:"ID"` + Prefix string `xml:"Prefix"` + Status string `xml:"Status"` + Expiration lifecycleExpiration `xml:"Expiration"` +} + +type lifecycleExpiration struct { + XMLName xml.Name `xml:"Expiration"` + Days int `xml:"Days,omitempty"` + Date string `xml:"Date,omitempty"` +} + +const expirationDateFormat = "2006-01-02T15:04:05.000Z" + +func convLifecycleRule(rules []LifecycleRule) []lifecycleRule { + rs := []lifecycleRule{} + for _, rule := range rules { + r := lifecycleRule{} + r.ID = rule.ID + r.Prefix = rule.Prefix + r.Status = rule.Status + if rule.Expiration.Date.IsZero() { + r.Expiration.Days = rule.Expiration.Days + } else { + r.Expiration.Date = rule.Expiration.Date.Format(expirationDateFormat) + } + rs = append(rs, r) + } + return rs +} + +// BuildLifecycleRuleByDays builds a lifecycle rule with specified expiration days +func BuildLifecycleRuleByDays(id, prefix string, status bool, days int) LifecycleRule { + var statusStr = "Enabled" + if !status { + statusStr = "Disabled" + } + return LifecycleRule{ID: id, Prefix: prefix, Status: statusStr, + Expiration: LifecycleExpiration{Days: days}} +} + +// BuildLifecycleRuleByDate builds a lifecycle rule with specified expiration time. +func BuildLifecycleRuleByDate(id, prefix string, status bool, year, month, day int) LifecycleRule { + var statusStr = "Enabled" + if !status { + statusStr = "Disabled" + } + date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC) + return LifecycleRule{ID: id, Prefix: prefix, Status: statusStr, + Expiration: LifecycleExpiration{Date: date}} +} + +// GetBucketLifecycleResult defines GetBucketLifecycle's result object +type GetBucketLifecycleResult LifecycleConfiguration + +// RefererXML defines Referer configuration +type RefererXML struct { + XMLName xml.Name `xml:"RefererConfiguration"` + AllowEmptyReferer bool `xml:"AllowEmptyReferer"` // Allow empty referrer + RefererList []string `xml:"RefererList>Referer"` // Referer whitelist +} + +// GetBucketRefererResult defines result object for GetBucketReferer request +type GetBucketRefererResult RefererXML + +// LoggingXML defines logging configuration +type LoggingXML struct { + XMLName xml.Name `xml:"BucketLoggingStatus"` + LoggingEnabled LoggingEnabled `xml:"LoggingEnabled"` // The logging configuration information +} + +type loggingXMLEmpty struct { + XMLName xml.Name `xml:"BucketLoggingStatus"` +} + +// LoggingEnabled defines the logging configuration information +type LoggingEnabled struct { + XMLName xml.Name `xml:"LoggingEnabled"` + TargetBucket string `xml:"TargetBucket"` // The bucket name for storing the log files + TargetPrefix string `xml:"TargetPrefix"` // The log file prefix +} + +// GetBucketLoggingResult defines the result from GetBucketLogging request +type GetBucketLoggingResult LoggingXML + +// WebsiteXML defines Website configuration +type WebsiteXML struct { + XMLName xml.Name `xml:"WebsiteConfiguration"` + IndexDocument IndexDocument `xml:"IndexDocument"` // The index page + ErrorDocument ErrorDocument `xml:"ErrorDocument"` // The error page +} + +// IndexDocument defines the index page info +type IndexDocument struct { + XMLName xml.Name `xml:"IndexDocument"` + Suffix string `xml:"Suffix"` // The file name for the index page +} + +// ErrorDocument defines the 404 error page info +type ErrorDocument struct { + XMLName xml.Name `xml:"ErrorDocument"` + Key string `xml:"Key"` // 404 error file name +} + +// GetBucketWebsiteResult defines the result from GetBucketWebsite request. +type GetBucketWebsiteResult WebsiteXML + +// CORSXML defines CORS configuration +type CORSXML struct { + XMLName xml.Name `xml:"CORSConfiguration"` + CORSRules []CORSRule `xml:"CORSRule"` // CORS rules +} + +// CORSRule defines CORS rules +type CORSRule struct { + XMLName xml.Name `xml:"CORSRule"` + AllowedOrigin []string `xml:"AllowedOrigin"` // Allowed origins. By default it's wildcard '*' + AllowedMethod []string `xml:"AllowedMethod"` // Allowed methods + AllowedHeader []string `xml:"AllowedHeader"` // Allowed headers + ExposeHeader []string `xml:"ExposeHeader"` // Allowed response headers + MaxAgeSeconds int `xml:"MaxAgeSeconds"` // Max cache ages in seconds +} + +// GetBucketCORSResult defines the result from GetBucketCORS request. +type GetBucketCORSResult CORSXML + +// GetBucketInfoResult defines the result from GetBucketInfo request. +type GetBucketInfoResult struct { + XMLName xml.Name `xml:"BucketInfo"` + BucketInfo BucketInfo `xml:"Bucket"` +} + +// BucketInfo defines Bucket information +type BucketInfo struct { + XMLName xml.Name `xml:"Bucket"` + Name string `xml:"Name"` // Bucket name + Location string `xml:"Location"` // Bucket datacenter + CreationDate time.Time `xml:"CreationDate"` // Bucket creation time + ExtranetEndpoint string `xml:"ExtranetEndpoint"` // Bucket external endpoint + IntranetEndpoint string `xml:"IntranetEndpoint"` // Bucket internal endpoint + ACL string `xml:"AccessControlList>Grant"` // Bucket ACL + Owner Owner `xml:"Owner"` // Bucket owner + StorageClass string `xml:"StorageClass"` // Bucket storage class +} + +// ListObjectsResult defines the result from ListObjects request +type ListObjectsResult struct { + XMLName xml.Name `xml:"ListBucketResult"` + Prefix string `xml:"Prefix"` // The object prefix + Marker string `xml:"Marker"` // The marker filter. + MaxKeys int `xml:"MaxKeys"` // Max keys to return + Delimiter string `xml:"Delimiter"` // The delimiter for grouping objects' name + IsTruncated bool `xml:"IsTruncated"` // Flag indicates if all results are returned (when it's false) + NextMarker string `xml:"NextMarker"` // The start point of the next query + Objects []ObjectProperties `xml:"Contents"` // Object list + CommonPrefixes []string `xml:"CommonPrefixes>Prefix"` // You can think of commonprefixes as "folders" whose names end with the delimiter +} + +// ObjectProperties defines Objecct properties +type ObjectProperties struct { + XMLName xml.Name `xml:"Contents"` + Key string `xml:"Key"` // Object key + Type string `xml:"Type"` // Object type + Size int64 `xml:"Size"` // Object size + ETag string `xml:"ETag"` // Object ETag + Owner Owner `xml:"Owner"` // Object owner information + LastModified time.Time `xml:"LastModified"` // Object last modified time + StorageClass string `xml:"StorageClass"` // Object storage class (Standard, IA, Archive) +} + +// Owner defines Bucket/Object's owner +type Owner struct { + XMLName xml.Name `xml:"Owner"` + ID string `xml:"ID"` // Owner ID + DisplayName string `xml:"DisplayName"` // Owner's display name +} + +// CopyObjectResult defines result object of CopyObject +type CopyObjectResult struct { + XMLName xml.Name `xml:"CopyObjectResult"` + LastModified time.Time `xml:"LastModified"` // New object's last modified time. + ETag string `xml:"ETag"` // New object's ETag +} + +// GetObjectACLResult defines result of GetObjectACL request +type GetObjectACLResult GetBucketACLResult + +type deleteXML struct { + XMLName xml.Name `xml:"Delete"` + Objects []DeleteObject `xml:"Object"` // Objects to delete + Quiet bool `xml:"Quiet"` // Flag of quiet mode. +} + +// DeleteObject defines the struct for deleting object +type DeleteObject struct { + XMLName xml.Name `xml:"Object"` + Key string `xml:"Key"` // Object name +} + +// DeleteObjectsResult defines result of DeleteObjects request +type DeleteObjectsResult struct { + XMLName xml.Name `xml:"DeleteResult"` + DeletedObjects []string `xml:"Deleted>Key"` // Deleted object list +} + +// InitiateMultipartUploadResult defines result of InitiateMultipartUpload request +type InitiateMultipartUploadResult struct { + XMLName xml.Name `xml:"InitiateMultipartUploadResult"` + Bucket string `xml:"Bucket"` // Bucket name + Key string `xml:"Key"` // Object name to upload + UploadID string `xml:"UploadId"` // Generated UploadId +} + +// UploadPart defines the upload/copy part +type UploadPart struct { + XMLName xml.Name `xml:"Part"` + PartNumber int `xml:"PartNumber"` // Part number + ETag string `xml:"ETag"` // ETag value of the part's data +} + +type uploadParts []UploadPart + +func (slice uploadParts) Len() int { + return len(slice) +} + +func (slice uploadParts) Less(i, j int) bool { + return slice[i].PartNumber < slice[j].PartNumber +} + +func (slice uploadParts) Swap(i, j int) { + slice[i], slice[j] = slice[j], slice[i] +} + +// UploadPartCopyResult defines result object of multipart copy request. +type UploadPartCopyResult struct { + XMLName xml.Name `xml:"CopyPartResult"` + LastModified time.Time `xml:"LastModified"` // Last modified time + ETag string `xml:"ETag"` // ETag +} + +type completeMultipartUploadXML struct { + XMLName xml.Name `xml:"CompleteMultipartUpload"` + Part []UploadPart `xml:"Part"` +} + +// CompleteMultipartUploadResult defines result object of CompleteMultipartUploadRequest +type CompleteMultipartUploadResult struct { + XMLName xml.Name `xml:"CompleteMultipartUploadResult"` + Location string `xml:"Location"` // Object URL + Bucket string `xml:"Bucket"` // Bucket name + ETag string `xml:"ETag"` // Object ETag + Key string `xml:"Key"` // Object name +} + +// ListUploadedPartsResult defines result object of ListUploadedParts +type ListUploadedPartsResult struct { + XMLName xml.Name `xml:"ListPartsResult"` + Bucket string `xml:"Bucket"` // Bucket name + Key string `xml:"Key"` // Object name + UploadID string `xml:"UploadId"` // Upload ID + NextPartNumberMarker string `xml:"NextPartNumberMarker"` // Next part number + MaxParts int `xml:"MaxParts"` // Max parts count + IsTruncated bool `xml:"IsTruncated"` // Flag indicates all entries returned.false: all entries returned. + UploadedParts []UploadedPart `xml:"Part"` // Uploaded parts +} + +// UploadedPart defines uploaded part +type UploadedPart struct { + XMLName xml.Name `xml:"Part"` + PartNumber int `xml:"PartNumber"` // Part number + LastModified time.Time `xml:"LastModified"` // Last modified time + ETag string `xml:"ETag"` // ETag cache + Size int `xml:"Size"` // Part size +} + +// ListMultipartUploadResult defines result object of ListMultipartUpload +type ListMultipartUploadResult struct { + XMLName xml.Name `xml:"ListMultipartUploadsResult"` + Bucket string `xml:"Bucket"` // Bucket name + Delimiter string `xml:"Delimiter"` // Delimiter for grouping object. + Prefix string `xml:"Prefix"` // Object prefix + KeyMarker string `xml:"KeyMarker"` // Object key marker + UploadIDMarker string `xml:"UploadIdMarker"` // UploadId marker + NextKeyMarker string `xml:"NextKeyMarker"` // Next key marker, if not all entries returned. + NextUploadIDMarker string `xml:"NextUploadIdMarker"` // Next uploadId marker, if not all entries returned. + MaxUploads int `xml:"MaxUploads"` // Max uploads to return + IsTruncated bool `xml:"IsTruncated"` // Flag indicates all entries are returned. + Uploads []UncompletedUpload `xml:"Upload"` // Ongoing uploads (not completed, not aborted) + CommonPrefixes []string `xml:"CommonPrefixes>Prefix"` // Common prefixes list. +} + +// UncompletedUpload structure wraps an uncompleted upload task +type UncompletedUpload struct { + XMLName xml.Name `xml:"Upload"` + Key string `xml:"Key"` // Object name + UploadID string `xml:"UploadId"` // The UploadId + Initiated time.Time `xml:"Initiated"` // Initialization time in the format such as 2012-02-23T04:18:23.000Z +} + +// ProcessObjectResult defines result object of ProcessObject +type ProcessObjectResult struct { + Bucket string `json:"bucket"` + FileSize int `json:"fileSize"` + Object string `json:"object"` + Status string `json:"status"` +} + +// decodeDeleteObjectsResult decodes deleting objects result in URL encoding +func decodeDeleteObjectsResult(result *DeleteObjectsResult) error { + var err error + for i := 0; i < len(result.DeletedObjects); i++ { + result.DeletedObjects[i], err = url.QueryUnescape(result.DeletedObjects[i]) + if err != nil { + return err + } + } + return nil +} + +// decodeListObjectsResult decodes list objects result in URL encoding +func decodeListObjectsResult(result *ListObjectsResult) error { + var err error + result.Prefix, err = url.QueryUnescape(result.Prefix) + if err != nil { + return err + } + result.Marker, err = url.QueryUnescape(result.Marker) + if err != nil { + return err + } + result.Delimiter, err = url.QueryUnescape(result.Delimiter) + if err != nil { + return err + } + result.NextMarker, err = url.QueryUnescape(result.NextMarker) + if err != nil { + return err + } + for i := 0; i < len(result.Objects); i++ { + result.Objects[i].Key, err = url.QueryUnescape(result.Objects[i].Key) + if err != nil { + return err + } + } + for i := 0; i < len(result.CommonPrefixes); i++ { + result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i]) + if err != nil { + return err + } + } + return nil +} + +// decodeListUploadedPartsResult decodes +func decodeListUploadedPartsResult(result *ListUploadedPartsResult) error { + var err error + result.Key, err = url.QueryUnescape(result.Key) + if err != nil { + return err + } + return nil +} + +// decodeListMultipartUploadResult decodes list multipart upload result in URL encoding +func decodeListMultipartUploadResult(result *ListMultipartUploadResult) error { + var err error + result.Prefix, err = url.QueryUnescape(result.Prefix) + if err != nil { + return err + } + result.Delimiter, err = url.QueryUnescape(result.Delimiter) + if err != nil { + return err + } + result.KeyMarker, err = url.QueryUnescape(result.KeyMarker) + if err != nil { + return err + } + result.NextKeyMarker, err = url.QueryUnescape(result.NextKeyMarker) + if err != nil { + return err + } + for i := 0; i < len(result.Uploads); i++ { + result.Uploads[i].Key, err = url.QueryUnescape(result.Uploads[i].Key) + if err != nil { + return err + } + } + for i := 0; i < len(result.CommonPrefixes); i++ { + result.CommonPrefixes[i], err = url.QueryUnescape(result.CommonPrefixes[i]) + if err != nil { + return err + } + } + return nil +} + +// createBucketConfiguration defines the configuration for creating a bucket. +type createBucketConfiguration struct { + XMLName xml.Name `xml:"CreateBucketConfiguration"` + StorageClass StorageClassType `xml:"StorageClass,omitempty"` +} + +// LiveChannelConfiguration defines the configuration for live-channel +type LiveChannelConfiguration struct { + XMLName xml.Name `xml:"LiveChannelConfiguration"` + Description string `xml:"Description,omitempty"` //Description of live-channel, up to 128 bytes + Status string `xml:"Status,omitempty"` //Specify the status of livechannel + Target LiveChannelTarget `xml:"Target"` //target configuration of live-channel + // use point instead of struct to avoid omit empty snapshot + Snapshot *LiveChannelSnapshot `xml:"Snapshot,omitempty"` //snapshot configuration of live-channel +} + +// LiveChannelTarget target configuration of live-channel +type LiveChannelTarget struct { + XMLName xml.Name `xml:"Target"` + Type string `xml:"Type"` //the type of object, only supports HLS + FragDuration int `xml:"FragDuration,omitempty"` //the length of each ts object (in seconds), in the range [1,100] + FragCount int `xml:"FragCount,omitempty"` //the number of ts objects in the m3u8 object, in the range of [1,100] + PlaylistName string `xml:"PlaylistName,omitempty"` //the name of m3u8 object, which must end with ".m3u8" and the length range is [6,128] +} + +// LiveChannelSnapshot snapshot configuration of live-channel +type LiveChannelSnapshot struct { + XMLName xml.Name `xml:"Snapshot"` + RoleName string `xml:"RoleName,omitempty"` //The role of snapshot operations, it sholud has write permission of DestBucket and the permission to send messages to the NotifyTopic. + DestBucket string `xml:"DestBucket,omitempty"` //Bucket the snapshots will be written to. should be the same owner as the source bucket. + NotifyTopic string `xml:"NotifyTopic,omitempty"` //Topics of MNS for notifying users of high frequency screenshot operation results + Interval int `xml:"Interval,omitempty"` //interval of snapshots, threre is no snapshot if no I-frame during the interval time +} + +// CreateLiveChannelResult the result of crete live-channel +type CreateLiveChannelResult struct { + XMLName xml.Name `xml:"CreateLiveChannelResult"` + PublishUrls []string `xml:"PublishUrls>Url"` //push urls list + PlayUrls []string `xml:"PlayUrls>Url"` //play urls list +} + +// LiveChannelStat the result of get live-channel state +type LiveChannelStat struct { + XMLName xml.Name `xml:"LiveChannelStat"` + Status string `xml:"Status"` //Current push status of live-channel: Disabled,Live,Idle + ConnectedTime time.Time `xml:"ConnectedTime"` //The time when the client starts pushing, format: ISO8601 + RemoteAddr string `xml:"RemoteAddr"` //The ip address of the client + Video LiveChannelVideo `xml:"Video"` //Video stream information + Audio LiveChannelAudio `xml:"Audio"` //Audio stream information +} + +// LiveChannelVideo video stream information +type LiveChannelVideo struct { + XMLName xml.Name `xml:"Video"` + Width int `xml:"Width"` //Width (unit: pixels) + Height int `xml:"Height"` //Height (unit: pixels) + FrameRate int `xml:"FrameRate"` //FramRate + Bandwidth int `xml:"Bandwidth"` //Bandwidth (unit: B/s) +} + +// LiveChannelAudio audio stream information +type LiveChannelAudio struct { + XMLName xml.Name `xml:"Audio"` + SampleRate int `xml:"SampleRate"` //SampleRate + Bandwidth int `xml:"Bandwidth"` //Bandwidth (unit: B/s) + Codec string `xml:"Codec"` //Encoding forma +} + +// LiveChannelHistory the result of GetLiveChannelHistory, at most return up to lastest 10 push records +type LiveChannelHistory struct { + XMLName xml.Name `xml:"LiveChannelHistory"` + Record []LiveRecord `xml:"LiveRecord"` //push records list +} + +// LiveRecord push recode +type LiveRecord struct { + XMLName xml.Name `xml:"LiveRecord"` + StartTime time.Time `xml:"StartTime"` //StartTime, format: ISO8601 + EndTime time.Time `xml:"EndTime"` //EndTime, format: ISO8601 + RemoteAddr string `xml:"RemoteAddr"` //The ip address of remote client +} + +// ListLiveChannelResult the result of ListLiveChannel +type ListLiveChannelResult struct { + XMLName xml.Name `xml:"ListLiveChannelResult"` + Prefix string `xml:"Prefix"` //Filter by the name start with the value of "Prefix" + Marker string `xml:"Marker"` //cursor from which starting list + MaxKeys int `xml:"MaxKeys"` //The maximum count returned. the default value is 100. it cannot be greater than 1000. + IsTruncated bool `xml:"IsTruncated"` //Indicates whether all results have been returned, "true" indicates partial results returned while "false" indicates all results have been returned + NextMarker string `xml:"NextMarker"` //NextMarker indicate the Marker value of the next request + LiveChannel []LiveChannelInfo `xml:"LiveChannel"` //The infomation of live-channel +} + +// LiveChannelInfo the infomation of live-channel +type LiveChannelInfo struct { + XMLName xml.Name `xml:"LiveChannel"` + Name string `xml:"Name"` //The name of live-channel + Description string `xml:"Description"` //Description of live-channel + Status string `xml:"Status"` //Status: disabled or enabled + LastModified time.Time `xml:"LastModified"` //Last modification time, format: ISO8601 + PublishUrls []string `xml:"PublishUrls>Url"` //push urls list + PlayUrls []string `xml:"PlayUrls>Url"` //play urls list +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type_test.go new file mode 100644 index 000000000..63f23e5f6 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type_test.go @@ -0,0 +1,127 @@ +package oss + +import ( + "net/url" + "sort" + + . "gopkg.in/check.v1" +) + +type OssTypeSuite struct{} + +var _ = Suite(&OssTypeSuite{}) + +var ( + goStr = "go go + go <> go" + chnStr = "试问闲情几许" + goURLStr = url.QueryEscape(goStr) + chnURLStr = url.QueryEscape(chnStr) +) + +func (s *OssTypeSuite) TestConvLifecycleRule(c *C) { + r1 := BuildLifecycleRuleByDate("id1", "one", true, 2015, 11, 11) + r2 := BuildLifecycleRuleByDays("id2", "two", false, 3) + + rs := convLifecycleRule([]LifecycleRule{r1}) + c.Assert(rs[0].ID, Equals, "id1") + c.Assert(rs[0].Prefix, Equals, "one") + c.Assert(rs[0].Status, Equals, "Enabled") + c.Assert(rs[0].Expiration.Date, Equals, "2015-11-11T00:00:00.000Z") + c.Assert(rs[0].Expiration.Days, Equals, 0) + + rs = convLifecycleRule([]LifecycleRule{r2}) + c.Assert(rs[0].ID, Equals, "id2") + c.Assert(rs[0].Prefix, Equals, "two") + c.Assert(rs[0].Status, Equals, "Disabled") + c.Assert(rs[0].Expiration.Date, Equals, "") + c.Assert(rs[0].Expiration.Days, Equals, 3) +} + +func (s *OssTypeSuite) TestDecodeDeleteObjectsResult(c *C) { + var res DeleteObjectsResult + err := decodeDeleteObjectsResult(&res) + c.Assert(err, IsNil) + + res.DeletedObjects = []string{""} + err = decodeDeleteObjectsResult(&res) + c.Assert(err, IsNil) + c.Assert(res.DeletedObjects[0], Equals, "") + + res.DeletedObjects = []string{goURLStr, chnURLStr} + err = decodeDeleteObjectsResult(&res) + c.Assert(err, IsNil) + c.Assert(res.DeletedObjects[0], Equals, goStr) + c.Assert(res.DeletedObjects[1], Equals, chnStr) +} + +func (s *OssTypeSuite) TestDecodeListObjectsResult(c *C) { + var res ListObjectsResult + err := decodeListObjectsResult(&res) + c.Assert(err, IsNil) + + res = ListObjectsResult{} + err = decodeListObjectsResult(&res) + c.Assert(err, IsNil) + + res = ListObjectsResult{Prefix: goURLStr, Marker: goURLStr, + Delimiter: goURLStr, NextMarker: goURLStr, + Objects: []ObjectProperties{{Key: chnURLStr}}, + CommonPrefixes: []string{chnURLStr}} + + err = decodeListObjectsResult(&res) + c.Assert(err, IsNil) + + c.Assert(res.Prefix, Equals, goStr) + c.Assert(res.Marker, Equals, goStr) + c.Assert(res.Delimiter, Equals, goStr) + c.Assert(res.NextMarker, Equals, goStr) + c.Assert(res.Objects[0].Key, Equals, chnStr) + c.Assert(res.CommonPrefixes[0], Equals, chnStr) +} + +func (s *OssTypeSuite) TestDecodeListMultipartUploadResult(c *C) { + res := ListMultipartUploadResult{} + err := decodeListMultipartUploadResult(&res) + c.Assert(err, IsNil) + + res = ListMultipartUploadResult{Prefix: goURLStr, KeyMarker: goURLStr, + Delimiter: goURLStr, NextKeyMarker: goURLStr, + Uploads: []UncompletedUpload{{Key: chnURLStr}}} + + err = decodeListMultipartUploadResult(&res) + c.Assert(err, IsNil) + + c.Assert(res.Prefix, Equals, goStr) + c.Assert(res.KeyMarker, Equals, goStr) + c.Assert(res.Delimiter, Equals, goStr) + c.Assert(res.NextKeyMarker, Equals, goStr) + c.Assert(res.Uploads[0].Key, Equals, chnStr) +} + +func (s *OssTypeSuite) TestSortUploadPart(c *C) { + parts := []UploadPart{} + + sort.Sort(uploadParts(parts)) + c.Assert(len(parts), Equals, 0) + + parts = []UploadPart{ + {PartNumber: 5, ETag: "E5"}, + {PartNumber: 1, ETag: "E1"}, + {PartNumber: 4, ETag: "E4"}, + {PartNumber: 2, ETag: "E2"}, + {PartNumber: 3, ETag: "E3"}, + } + + sort.Sort(uploadParts(parts)) + + c.Assert(parts[0].PartNumber, Equals, 1) + c.Assert(parts[0].ETag, Equals, "E1") + c.Assert(parts[1].PartNumber, Equals, 2) + c.Assert(parts[1].ETag, Equals, "E2") + c.Assert(parts[2].PartNumber, Equals, 3) + c.Assert(parts[2].ETag, Equals, "E3") + c.Assert(parts[3].PartNumber, Equals, 4) + c.Assert(parts[3].ETag, Equals, "E4") + c.Assert(parts[4].PartNumber, Equals, 5) + c.Assert(parts[4].ETag, Equals, "E5") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload.go new file mode 100644 index 000000000..80371447d --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload.go @@ -0,0 +1,526 @@ +package oss + +import ( + "crypto/md5" + "encoding/base64" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "time" +) + +// UploadFile is multipart file upload. +// +// objectKey the object name. +// filePath the local file path to upload. +// partSize the part size in byte. +// options the options for uploading object. +// +// error it's nil if the operation succeeds, otherwise it's an error object. +// +func (bucket Bucket) UploadFile(objectKey, filePath string, partSize int64, options ...Option) error { + if partSize < MinPartSize || partSize > MaxPartSize { + return errors.New("oss: part size invalid range (100KB, 5GB]") + } + + cpConf := getCpConfig(options) + routines := getRoutines(options) + + if cpConf != nil && cpConf.IsEnable { + cpFilePath := getUploadCpFilePath(cpConf, filePath, bucket.BucketName, objectKey) + if cpFilePath != "" { + return bucket.uploadFileWithCp(objectKey, filePath, partSize, options, cpFilePath, routines) + } + } + + return bucket.uploadFile(objectKey, filePath, partSize, options, routines) +} + +func getUploadCpFilePath(cpConf *cpConfig, srcFile, destBucket, destObject string) string { + if cpConf.FilePath == "" && cpConf.DirPath != "" { + dest := fmt.Sprintf("oss://%v/%v", destBucket, destObject) + absPath, _ := filepath.Abs(srcFile) + cpFileName := getCpFileName(absPath, dest) + cpConf.FilePath = cpConf.DirPath + string(os.PathSeparator) + cpFileName + } + return cpConf.FilePath +} + +// ----- concurrent upload without checkpoint ----- + +// getCpConfig gets checkpoint configuration +func getCpConfig(options []Option) *cpConfig { + cpcOpt, err := findOption(options, checkpointConfig, nil) + if err != nil || cpcOpt == nil { + return nil + } + + return cpcOpt.(*cpConfig) +} + +// getCpFileName return the name of the checkpoint file +func getCpFileName(src, dest string) string { + md5Ctx := md5.New() + md5Ctx.Write([]byte(src)) + srcCheckSum := hex.EncodeToString(md5Ctx.Sum(nil)) + + md5Ctx.Reset() + md5Ctx.Write([]byte(dest)) + destCheckSum := hex.EncodeToString(md5Ctx.Sum(nil)) + + return fmt.Sprintf("%v-%v.cp", srcCheckSum, destCheckSum) +} + +// getRoutines gets the routine count. by default it's 1. +func getRoutines(options []Option) int { + rtnOpt, err := findOption(options, routineNum, nil) + if err != nil || rtnOpt == nil { + return 1 + } + + rs := rtnOpt.(int) + if rs < 1 { + rs = 1 + } else if rs > 100 { + rs = 100 + } + + return rs +} + +// getPayer return the payer of the request +func getPayer(options []Option) string { + payerOpt, err := findOption(options, HTTPHeaderOSSRequester, nil) + if err != nil || payerOpt == nil { + return "" + } + + return payerOpt.(string) +} + +// getProgressListener gets the progress callback +func getProgressListener(options []Option) ProgressListener { + isSet, listener, _ := isOptionSet(options, progressListener) + if !isSet { + return nil + } + return listener.(ProgressListener) +} + +// uploadPartHook is for testing usage +type uploadPartHook func(id int, chunk FileChunk) error + +var uploadPartHooker uploadPartHook = defaultUploadPart + +func defaultUploadPart(id int, chunk FileChunk) error { + return nil +} + +// workerArg defines worker argument structure +type workerArg struct { + bucket *Bucket + filePath string + imur InitiateMultipartUploadResult + options []Option + hook uploadPartHook +} + +// worker is the worker coroutine function +func worker(id int, arg workerArg, jobs <-chan FileChunk, results chan<- UploadPart, failed chan<- error, die <-chan bool) { + for chunk := range jobs { + if err := arg.hook(id, chunk); err != nil { + failed <- err + break + } + part, err := arg.bucket.UploadPartFromFile(arg.imur, arg.filePath, chunk.Offset, chunk.Size, chunk.Number, arg.options...) + if err != nil { + failed <- err + break + } + select { + case <-die: + return + default: + } + results <- part + } +} + +// scheduler function +func scheduler(jobs chan FileChunk, chunks []FileChunk) { + for _, chunk := range chunks { + jobs <- chunk + } + close(jobs) +} + +func getTotalBytes(chunks []FileChunk) int64 { + var tb int64 + for _, chunk := range chunks { + tb += chunk.Size + } + return tb +} + +// uploadFile is a concurrent upload, without checkpoint +func (bucket Bucket) uploadFile(objectKey, filePath string, partSize int64, options []Option, routines int) error { + listener := getProgressListener(options) + + chunks, err := SplitFileByPartSize(filePath, partSize) + if err != nil { + return err + } + + payerOptions := []Option{} + payer := getPayer(options) + if payer != "" { + payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) + } + + // Initialize the multipart upload + imur, err := bucket.InitiateMultipartUpload(objectKey, options...) + if err != nil { + return err + } + + jobs := make(chan FileChunk, len(chunks)) + results := make(chan UploadPart, len(chunks)) + failed := make(chan error) + die := make(chan bool) + + var completedBytes int64 + totalBytes := getTotalBytes(chunks) + event := newProgressEvent(TransferStartedEvent, 0, totalBytes) + publishProgress(listener, event) + + // Start the worker coroutine + arg := workerArg{&bucket, filePath, imur, payerOptions, uploadPartHooker} + for w := 1; w <= routines; w++ { + go worker(w, arg, jobs, results, failed, die) + } + + // Schedule the jobs + go scheduler(jobs, chunks) + + // Waiting for the upload finished + completed := 0 + parts := make([]UploadPart, len(chunks)) + for completed < len(chunks) { + select { + case part := <-results: + completed++ + parts[part.PartNumber-1] = part + completedBytes += chunks[part.PartNumber-1].Size + event = newProgressEvent(TransferDataEvent, completedBytes, totalBytes) + publishProgress(listener, event) + case err := <-failed: + close(die) + event = newProgressEvent(TransferFailedEvent, completedBytes, totalBytes) + publishProgress(listener, event) + bucket.AbortMultipartUpload(imur, payerOptions...) + return err + } + + if completed >= len(chunks) { + break + } + } + + event = newProgressEvent(TransferStartedEvent, completedBytes, totalBytes) + publishProgress(listener, event) + + // Complete the multpart upload + _, err = bucket.CompleteMultipartUpload(imur, parts, payerOptions...) + if err != nil { + bucket.AbortMultipartUpload(imur, payerOptions...) + return err + } + return nil +} + +// ----- concurrent upload with checkpoint ----- +const uploadCpMagic = "FE8BB4EA-B593-4FAC-AD7A-2459A36E2E62" + +type uploadCheckpoint struct { + Magic string // Magic + MD5 string // Checkpoint file content's MD5 + FilePath string // Local file path + FileStat cpStat // File state + ObjectKey string // Key + UploadID string // Upload ID + Parts []cpPart // All parts of the local file +} + +type cpStat struct { + Size int64 // File size + LastModified time.Time // File's last modified time + MD5 string // Local file's MD5 +} + +type cpPart struct { + Chunk FileChunk // File chunk + Part UploadPart // Uploaded part + IsCompleted bool // Upload complete flag +} + +// isValid checks if the uploaded data is valid---it's valid when the file is not updated and the checkpoint data is valid. +func (cp uploadCheckpoint) isValid(filePath string) (bool, error) { + // Compare the CP's magic number and MD5. + cpb := cp + cpb.MD5 = "" + js, _ := json.Marshal(cpb) + sum := md5.Sum(js) + b64 := base64.StdEncoding.EncodeToString(sum[:]) + + if cp.Magic != uploadCpMagic || b64 != cp.MD5 { + return false, nil + } + + // Make sure if the local file is updated. + fd, err := os.Open(filePath) + if err != nil { + return false, err + } + defer fd.Close() + + st, err := fd.Stat() + if err != nil { + return false, err + } + + md, err := calcFileMD5(filePath) + if err != nil { + return false, err + } + + // Compare the file size, file's last modified time and file's MD5 + if cp.FileStat.Size != st.Size() || + cp.FileStat.LastModified != st.ModTime() || + cp.FileStat.MD5 != md { + return false, nil + } + + return true, nil +} + +// load loads from the file +func (cp *uploadCheckpoint) load(filePath string) error { + contents, err := ioutil.ReadFile(filePath) + if err != nil { + return err + } + + err = json.Unmarshal(contents, cp) + return err +} + +// dump dumps to the local file +func (cp *uploadCheckpoint) dump(filePath string) error { + bcp := *cp + + // Calculate MD5 + bcp.MD5 = "" + js, err := json.Marshal(bcp) + if err != nil { + return err + } + sum := md5.Sum(js) + b64 := base64.StdEncoding.EncodeToString(sum[:]) + bcp.MD5 = b64 + + // Serialization + js, err = json.Marshal(bcp) + if err != nil { + return err + } + + // Dump + return ioutil.WriteFile(filePath, js, FilePermMode) +} + +// updatePart updates the part status +func (cp *uploadCheckpoint) updatePart(part UploadPart) { + cp.Parts[part.PartNumber-1].Part = part + cp.Parts[part.PartNumber-1].IsCompleted = true +} + +// todoParts returns unfinished parts +func (cp *uploadCheckpoint) todoParts() []FileChunk { + fcs := []FileChunk{} + for _, part := range cp.Parts { + if !part.IsCompleted { + fcs = append(fcs, part.Chunk) + } + } + return fcs +} + +// allParts returns all parts +func (cp *uploadCheckpoint) allParts() []UploadPart { + ps := []UploadPart{} + for _, part := range cp.Parts { + ps = append(ps, part.Part) + } + return ps +} + +// getCompletedBytes returns completed bytes count +func (cp *uploadCheckpoint) getCompletedBytes() int64 { + var completedBytes int64 + for _, part := range cp.Parts { + if part.IsCompleted { + completedBytes += part.Chunk.Size + } + } + return completedBytes +} + +// calcFileMD5 calculates the MD5 for the specified local file +func calcFileMD5(filePath string) (string, error) { + return "", nil +} + +// prepare initializes the multipart upload +func prepare(cp *uploadCheckpoint, objectKey, filePath string, partSize int64, bucket *Bucket, options []Option) error { + // CP + cp.Magic = uploadCpMagic + cp.FilePath = filePath + cp.ObjectKey = objectKey + + // Local file + fd, err := os.Open(filePath) + if err != nil { + return err + } + defer fd.Close() + + st, err := fd.Stat() + if err != nil { + return err + } + cp.FileStat.Size = st.Size() + cp.FileStat.LastModified = st.ModTime() + md, err := calcFileMD5(filePath) + if err != nil { + return err + } + cp.FileStat.MD5 = md + + // Chunks + parts, err := SplitFileByPartSize(filePath, partSize) + if err != nil { + return err + } + + cp.Parts = make([]cpPart, len(parts)) + for i, part := range parts { + cp.Parts[i].Chunk = part + cp.Parts[i].IsCompleted = false + } + + // Init load + imur, err := bucket.InitiateMultipartUpload(objectKey, options...) + if err != nil { + return err + } + cp.UploadID = imur.UploadID + + return nil +} + +// complete completes the multipart upload and deletes the local CP files +func complete(cp *uploadCheckpoint, bucket *Bucket, parts []UploadPart, cpFilePath string, options []Option) error { + imur := InitiateMultipartUploadResult{Bucket: bucket.BucketName, + Key: cp.ObjectKey, UploadID: cp.UploadID} + _, err := bucket.CompleteMultipartUpload(imur, parts, options...) + if err != nil { + return err + } + os.Remove(cpFilePath) + return err +} + +// uploadFileWithCp handles concurrent upload with checkpoint +func (bucket Bucket) uploadFileWithCp(objectKey, filePath string, partSize int64, options []Option, cpFilePath string, routines int) error { + listener := getProgressListener(options) + + payerOptions := []Option{} + payer := getPayer(options) + if payer != "" { + payerOptions = append(payerOptions, RequestPayer(PayerType(payer))) + } + + // Load CP data + ucp := uploadCheckpoint{} + err := ucp.load(cpFilePath) + if err != nil { + os.Remove(cpFilePath) + } + + // Load error or the CP data is invalid. + valid, err := ucp.isValid(filePath) + if err != nil || !valid { + if err = prepare(&ucp, objectKey, filePath, partSize, &bucket, options); err != nil { + return err + } + os.Remove(cpFilePath) + } + + chunks := ucp.todoParts() + imur := InitiateMultipartUploadResult{ + Bucket: bucket.BucketName, + Key: objectKey, + UploadID: ucp.UploadID} + + jobs := make(chan FileChunk, len(chunks)) + results := make(chan UploadPart, len(chunks)) + failed := make(chan error) + die := make(chan bool) + + completedBytes := ucp.getCompletedBytes() + event := newProgressEvent(TransferStartedEvent, completedBytes, ucp.FileStat.Size) + publishProgress(listener, event) + + // Start the workers + arg := workerArg{&bucket, filePath, imur, payerOptions, uploadPartHooker} + for w := 1; w <= routines; w++ { + go worker(w, arg, jobs, results, failed, die) + } + + // Schedule jobs + go scheduler(jobs, chunks) + + // Waiting for the job finished + completed := 0 + for completed < len(chunks) { + select { + case part := <-results: + completed++ + ucp.updatePart(part) + ucp.dump(cpFilePath) + completedBytes += ucp.Parts[part.PartNumber-1].Chunk.Size + event = newProgressEvent(TransferDataEvent, completedBytes, ucp.FileStat.Size) + publishProgress(listener, event) + case err := <-failed: + close(die) + event = newProgressEvent(TransferFailedEvent, completedBytes, ucp.FileStat.Size) + publishProgress(listener, event) + return err + } + + if completed >= len(chunks) { + break + } + } + + event = newProgressEvent(TransferCompletedEvent, completedBytes, ucp.FileStat.Size) + publishProgress(listener, event) + + // Complete the multipart upload + err = complete(&ucp, &bucket, ucp.allParts(), cpFilePath, payerOptions) + return err +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload_test.go new file mode 100644 index 000000000..5157c72b4 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/upload_test.go @@ -0,0 +1,459 @@ +package oss + +import ( + "fmt" + "io" + "os" + "time" + + . "gopkg.in/check.v1" +) + +type OssUploadSuite struct { + client *Client + bucket *Bucket +} + +var _ = Suite(&OssUploadSuite{}) + +// SetUpSuite runs once when the suite starts running +func (s *OssUploadSuite) SetUpSuite(c *C) { + client, err := New(endpoint, accessID, accessKey) + c.Assert(err, IsNil) + s.client = client + + s.client.CreateBucket(bucketName) + time.Sleep(5 * time.Second) + + bucket, err := s.client.Bucket(bucketName) + c.Assert(err, IsNil) + s.bucket = bucket + + testLogger.Println("test upload started") +} + +// TearDownSuite runs before each test or benchmark starts running +func (s *OssUploadSuite) TearDownSuite(c *C) { + // Delete part + lmur, err := s.bucket.ListMultipartUploads() + c.Assert(err, IsNil) + + for _, upload := range lmur.Uploads { + var imur = InitiateMultipartUploadResult{Bucket: s.bucket.BucketName, + Key: upload.Key, UploadID: upload.UploadID} + err = s.bucket.AbortMultipartUpload(imur) + c.Assert(err, IsNil) + } + + // Delete objects + lor, err := s.bucket.ListObjects() + c.Assert(err, IsNil) + + for _, object := range lor.Objects { + err = s.bucket.DeleteObject(object.Key) + c.Assert(err, IsNil) + } + + testLogger.Println("test upload completed") +} + +// SetUpTest runs after each test or benchmark runs +func (s *OssUploadSuite) SetUpTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) +} + +// TearDownTest runs once after all tests or benchmarks have finished running +func (s *OssUploadSuite) TearDownTest(c *C) { + err := removeTempFiles("../oss", ".jpg") + c.Assert(err, IsNil) +} + +// TestUploadRoutineWithoutRecovery tests multiroutineed upload without checkpoint +func (s *OssUploadSuite) TestUploadRoutineWithoutRecovery(c *C) { + objectName := objectNamePrefix + "turwr" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "upload-new-file.jpg" + + // Routines is not specified, by default single routine + err := s.bucket.UploadFile(objectName, fileName, 100*1024) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Specify routine count as 1 + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(1)) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Specify routine count as 3, which is smaller than parts count 5 + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3)) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Specify routine count as 5, which is same as the part count 5 + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(5)) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Specify routine count as 10, which is bigger than the part count 5. + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(10)) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Invalid routine count, it will use 1 automatically. + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(0)) + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Invalid routine count, it will use 1 automatically + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(-1)) + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Option + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Meta("myprop", "mypropval")) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// ErrorHooker is a UploadPart hook---it will fail the 5th part's upload. +func ErrorHooker(id int, chunk FileChunk) error { + if chunk.Number == 5 { + time.Sleep(time.Second) + return fmt.Errorf("ErrorHooker") + } + return nil +} + +// TestUploadRoutineWithoutRecoveryNegative is multiroutineed upload without checkpoint +func (s *OssUploadSuite) TestUploadRoutineWithoutRecoveryNegative(c *C) { + objectName := objectNamePrefix + "turwrn" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + + uploadPartHooker = ErrorHooker + // Worker routine error + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(2)) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + uploadPartHooker = defaultUploadPart + + // Local file does not exist + err = s.bucket.UploadFile(objectName, "NotExist", 100*1024, Routines(2)) + c.Assert(err, NotNil) + + // The part size is invalid + err = s.bucket.UploadFile(objectName, fileName, 1024, Routines(2)) + c.Assert(err, NotNil) + + err = s.bucket.UploadFile(objectName, fileName, 1024*1024*1024*100, Routines(2)) + c.Assert(err, NotNil) +} + +// TestUploadRoutineWithRecovery is multi-routine upload with resumable recovery +func (s *OssUploadSuite) TestUploadRoutineWithRecovery(c *C) { + objectName := objectNamePrefix + "turtr" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + newFile := "upload-new-file-2.jpg" + + // Use default routines and default CP file path (fileName+.cp) + // First upload for 4 parts + uploadPartHooker = ErrorHooker + err := s.bucket.UploadFile(objectName, fileName, 100*1024, Checkpoint(true, fileName+".cp")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + uploadPartHooker = defaultUploadPart + + // Check CP + ucp := uploadCheckpoint{} + err = ucp.load(fileName + ".cp") + c.Assert(err, IsNil) + c.Assert(ucp.Magic, Equals, uploadCpMagic) + c.Assert(len(ucp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ==")) + c.Assert(ucp.FilePath, Equals, fileName) + c.Assert(ucp.FileStat.Size, Equals, int64(482048)) + c.Assert(len(ucp.FileStat.LastModified.String()) > 0, Equals, true) + c.Assert(ucp.FileStat.MD5, Equals, "") + c.Assert(ucp.ObjectKey, Equals, objectName) + c.Assert(len(ucp.UploadID), Equals, len("3F79722737D1469980DACEDCA325BB52")) + c.Assert(len(ucp.Parts), Equals, 5) + c.Assert(len(ucp.todoParts()), Equals, 1) + c.Assert(len(ucp.allParts()), Equals, 5) + + // Second upload, finish the remaining part + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Checkpoint(true, fileName+".cp")) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + err = ucp.load(fileName + ".cp") + c.Assert(err, NotNil) + + // Resumable upload with empty checkpoint path + uploadPartHooker = ErrorHooker + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), CheckpointDir(true, "")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + uploadPartHooker = defaultUploadPart + ucp = uploadCheckpoint{} + err = ucp.load(fileName + ".cp") + c.Assert(err, NotNil) + + // Resumable upload with checkpoint dir + uploadPartHooker = ErrorHooker + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), CheckpointDir(true, "./")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + uploadPartHooker = defaultUploadPart + + // Check CP + ucp = uploadCheckpoint{} + cpConf := cpConfig{IsEnable: true, DirPath: "./"} + cpFilePath := getUploadCpFilePath(&cpConf, fileName, s.bucket.BucketName, objectName) + err = ucp.load(cpFilePath) + c.Assert(err, IsNil) + c.Assert(ucp.Magic, Equals, uploadCpMagic) + c.Assert(len(ucp.MD5), Equals, len("LC34jZU5xK4hlxi3Qn3XGQ==")) + c.Assert(ucp.FilePath, Equals, fileName) + c.Assert(ucp.FileStat.Size, Equals, int64(482048)) + c.Assert(len(ucp.FileStat.LastModified.String()) > 0, Equals, true) + c.Assert(ucp.FileStat.MD5, Equals, "") + c.Assert(ucp.ObjectKey, Equals, objectName) + c.Assert(len(ucp.UploadID), Equals, len("3F79722737D1469980DACEDCA325BB52")) + c.Assert(len(ucp.Parts), Equals, 5) + c.Assert(len(ucp.todoParts()), Equals, 1) + c.Assert(len(ucp.allParts()), Equals, 5) + + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), CheckpointDir(true, "./")) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + err = ucp.load(cpFilePath) + c.Assert(err, NotNil) + + // Upload all 5 parts without error + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, objectName+".cp")) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Upload all 5 parts with 10 routines without error + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(10), Checkpoint(true, objectName+".cp")) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) + + // Option + err = s.bucket.UploadFile(objectName, fileName, 100*1024, Routines(3), Checkpoint(true, objectName+".cp"), Meta("myprop", "mypropval")) + + meta, err := s.bucket.GetObjectDetailedMeta(objectName) + c.Assert(err, IsNil) + c.Assert(meta.Get("X-Oss-Meta-Myprop"), Equals, "mypropval") + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err = compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +// TestUploadRoutineWithRecoveryNegative is multiroutineed upload without checkpoint +func (s *OssUploadSuite) TestUploadRoutineWithRecoveryNegative(c *C) { + objectName := objectNamePrefix + "turrn" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + + // The local file does not exist + err := s.bucket.UploadFile(objectName, "NotExist", 100*1024, Checkpoint(true, "NotExist.cp")) + c.Assert(err, NotNil) + + err = s.bucket.UploadFile(objectName, "NotExist", 100*1024, Routines(2), Checkpoint(true, "NotExist.cp")) + c.Assert(err, NotNil) + + // Specified part size is invalid + err = s.bucket.UploadFile(objectName, fileName, 1024, Checkpoint(true, fileName+".cp")) + c.Assert(err, NotNil) + + err = s.bucket.UploadFile(objectName, fileName, 1024, Routines(2), Checkpoint(true, fileName+".cp")) + c.Assert(err, NotNil) + + err = s.bucket.UploadFile(objectName, fileName, 1024*1024*1024*100, Checkpoint(true, fileName+".cp")) + c.Assert(err, NotNil) + + err = s.bucket.UploadFile(objectName, fileName, 1024*1024*1024*100, Routines(2), Checkpoint(true, fileName+".cp")) + c.Assert(err, NotNil) +} + +// TestUploadLocalFileChange tests the file is updated while being uploaded +func (s *OssUploadSuite) TestUploadLocalFileChange(c *C) { + objectName := objectNamePrefix + "tulfc" + fileName := "../sample/BingWallpaper-2015-11-07.jpg" + localFile := "BingWallpaper-2015-11-07.jpg" + newFile := "upload-new-file-3.jpg" + + os.Remove(localFile) + err := copyFile(fileName, localFile) + c.Assert(err, IsNil) + + // First upload for 4 parts + uploadPartHooker = ErrorHooker + err = s.bucket.UploadFile(objectName, localFile, 100*1024, Checkpoint(true, localFile+".cp")) + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "ErrorHooker") + uploadPartHooker = defaultUploadPart + + os.Remove(localFile) + err = copyFile(fileName, localFile) + c.Assert(err, IsNil) + + // Updating the file. The second upload will re-upload all 5 parts. + err = s.bucket.UploadFile(objectName, localFile, 100*1024, Checkpoint(true, localFile+".cp")) + c.Assert(err, IsNil) + + os.Remove(newFile) + err = s.bucket.GetObjectToFile(objectName, newFile) + c.Assert(err, IsNil) + + eq, err := compareFiles(fileName, newFile) + c.Assert(err, IsNil) + c.Assert(eq, Equals, true) + + err = s.bucket.DeleteObject(objectName) + c.Assert(err, IsNil) +} + +func copyFile(src, dst string) error { + srcFile, err := os.Open(src) + if err != nil { + return err + } + defer srcFile.Close() + + dstFile, err := os.Create(dst) + if err != nil { + return err + } + defer dstFile.Close() + + _, err = io.Copy(dstFile, srcFile) + return err +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils.go new file mode 100644 index 000000000..c0e7b2b1b --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils.go @@ -0,0 +1,265 @@ +package oss + +import ( + "bytes" + "errors" + "fmt" + "hash/crc64" + "net/http" + "os" + "os/exec" + "runtime" + "strconv" + "strings" + "time" +) + +// userAgent gets user agent +// It has the SDK version information, OS information and GO version +func userAgent() string { + sys := getSysInfo() + return fmt.Sprintf("aliyun-sdk-go/%s (%s/%s/%s;%s)", Version, sys.name, + sys.release, sys.machine, runtime.Version()) +} + +type sysInfo struct { + name string // OS name such as windows/Linux + release string // OS version 2.6.32-220.23.2.ali1089.el5.x86_64 etc + machine string // CPU type amd64/x86_64 +} + +// getSysInfo gets system info +// gets the OS information and CPU type +func getSysInfo() sysInfo { + name := runtime.GOOS + release := "-" + machine := runtime.GOARCH + if out, err := exec.Command("uname", "-s").CombinedOutput(); err == nil { + name = string(bytes.TrimSpace(out)) + } + if out, err := exec.Command("uname", "-r").CombinedOutput(); err == nil { + release = string(bytes.TrimSpace(out)) + } + if out, err := exec.Command("uname", "-m").CombinedOutput(); err == nil { + machine = string(bytes.TrimSpace(out)) + } + return sysInfo{name: name, release: release, machine: machine} +} + +// unpackedRange +type unpackedRange struct { + hasStart bool // Flag indicates if the start point is specified + hasEnd bool // Flag indicates if the end point is specified + start int64 // Start point + end int64 // End point +} + +// invalidRangeError returns invalid range error +func invalidRangeError(r string) error { + return fmt.Errorf("InvalidRange %s", r) +} + +// parseRange parse various styles of range such as bytes=M-N +func parseRange(normalizedRange string) (*unpackedRange, error) { + var err error + hasStart := false + hasEnd := false + var start int64 + var end int64 + + // Bytes==M-N or ranges=M-N + nrSlice := strings.Split(normalizedRange, "=") + if len(nrSlice) != 2 || nrSlice[0] != "bytes" { + return nil, invalidRangeError(normalizedRange) + } + + // Bytes=M-N,X-Y + rSlice := strings.Split(nrSlice[1], ",") + rStr := rSlice[0] + + if strings.HasSuffix(rStr, "-") { // M- + startStr := rStr[:len(rStr)-1] + start, err = strconv.ParseInt(startStr, 10, 64) + if err != nil { + return nil, invalidRangeError(normalizedRange) + } + hasStart = true + } else if strings.HasPrefix(rStr, "-") { // -N + len := rStr[1:] + end, err = strconv.ParseInt(len, 10, 64) + if err != nil { + return nil, invalidRangeError(normalizedRange) + } + if end == 0 { // -0 + return nil, invalidRangeError(normalizedRange) + } + hasEnd = true + } else { // M-N + valSlice := strings.Split(rStr, "-") + if len(valSlice) != 2 { + return nil, invalidRangeError(normalizedRange) + } + start, err = strconv.ParseInt(valSlice[0], 10, 64) + if err != nil { + return nil, invalidRangeError(normalizedRange) + } + hasStart = true + end, err = strconv.ParseInt(valSlice[1], 10, 64) + if err != nil { + return nil, invalidRangeError(normalizedRange) + } + hasEnd = true + } + + return &unpackedRange{hasStart, hasEnd, start, end}, nil +} + +// adjustRange returns adjusted range, adjust the range according to the length of the file +func adjustRange(ur *unpackedRange, size int64) (start, end int64) { + if ur == nil { + return 0, size + } + + if ur.hasStart && ur.hasEnd { + start = ur.start + end = ur.end + 1 + if ur.start < 0 || ur.start >= size || ur.end > size || ur.start > ur.end { + start = 0 + end = size + } + } else if ur.hasStart { + start = ur.start + end = size + if ur.start < 0 || ur.start >= size { + start = 0 + } + } else if ur.hasEnd { + start = size - ur.end + end = size + if ur.end < 0 || ur.end > size { + start = 0 + end = size + } + } + return +} + +// GetNowSec returns Unix time, the number of seconds elapsed since January 1, 1970 UTC. +// gets the current time in Unix time, in seconds. +func GetNowSec() int64 { + return time.Now().Unix() +} + +// GetNowNanoSec returns t as a Unix time, the number of nanoseconds elapsed +// since January 1, 1970 UTC. The result is undefined if the Unix time +// in nanoseconds cannot be represented by an int64. Note that this +// means the result of calling UnixNano on the zero Time is undefined. +// gets the current time in Unix time, in nanoseconds. +func GetNowNanoSec() int64 { + return time.Now().UnixNano() +} + +// GetNowGMT gets the current time in GMT format. +func GetNowGMT() string { + return time.Now().UTC().Format(http.TimeFormat) +} + +// FileChunk is the file chunk definition +type FileChunk struct { + Number int // Chunk number + Offset int64 // Chunk offset + Size int64 // Chunk size. +} + +// SplitFileByPartNum splits big file into parts by the num of parts. +// Split the file with specified parts count, returns the split result when error is nil. +func SplitFileByPartNum(fileName string, chunkNum int) ([]FileChunk, error) { + if chunkNum <= 0 || chunkNum > 10000 { + return nil, errors.New("chunkNum invalid") + } + + file, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer file.Close() + + stat, err := file.Stat() + if err != nil { + return nil, err + } + + if int64(chunkNum) > stat.Size() { + return nil, errors.New("oss: chunkNum invalid") + } + + var chunks []FileChunk + var chunk = FileChunk{} + var chunkN = (int64)(chunkNum) + for i := int64(0); i < chunkN; i++ { + chunk.Number = int(i + 1) + chunk.Offset = i * (stat.Size() / chunkN) + if i == chunkN-1 { + chunk.Size = stat.Size()/chunkN + stat.Size()%chunkN + } else { + chunk.Size = stat.Size() / chunkN + } + chunks = append(chunks, chunk) + } + + return chunks, nil +} + +// SplitFileByPartSize splits big file into parts by the size of parts. +// Splits the file by the part size. Returns the FileChunk when error is nil. +func SplitFileByPartSize(fileName string, chunkSize int64) ([]FileChunk, error) { + if chunkSize <= 0 { + return nil, errors.New("chunkSize invalid") + } + + file, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer file.Close() + + stat, err := file.Stat() + if err != nil { + return nil, err + } + var chunkN = stat.Size() / chunkSize + if chunkN >= 10000 { + return nil, errors.New("Too many parts, please increase part size") + } + + var chunks []FileChunk + var chunk = FileChunk{} + for i := int64(0); i < chunkN; i++ { + chunk.Number = int(i + 1) + chunk.Offset = i * chunkSize + chunk.Size = chunkSize + chunks = append(chunks, chunk) + } + + if stat.Size()%chunkSize > 0 { + chunk.Number = len(chunks) + 1 + chunk.Offset = int64(len(chunks)) * chunkSize + chunk.Size = stat.Size() % chunkSize + chunks = append(chunks, chunk) + } + + return chunks, nil +} + +// GetPartEnd calculates the end position +func GetPartEnd(begin int64, total int64, per int64) int64 { + if begin+per > total { + return total - 1 + } + return begin + per - 1 +} + +// crcTable returns the table constructed from the specified polynomial +var crcTable = func() *crc64.Table { + return crc64.MakeTable(crc64.ECMA) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils_test.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils_test.go new file mode 100644 index 000000000..354ea178f --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils_test.go @@ -0,0 +1,220 @@ +package oss + +import . "gopkg.in/check.v1" + +type OssUtilsSuite struct{} + +var _ = Suite(&OssUtilsSuite{}) + +func (s *OssUtilsSuite) TestUtilsTime(c *C) { + c.Assert(GetNowSec() > 1448597674, Equals, true) + c.Assert(GetNowNanoSec() > 1448597674000000000, Equals, true) + c.Assert(len(GetNowGMT()), Equals, len("Fri, 27 Nov 2015 04:14:34 GMT")) +} + +func (s *OssUtilsSuite) TestUtilsSplitFile(c *C) { + localFile := "../sample/BingWallpaper-2015-11-07.jpg" + + // Num + parts, err := SplitFileByPartNum(localFile, 4) + c.Assert(err, IsNil) + c.Assert(len(parts), Equals, 4) + testLogger.Println("parts 4:", parts) + for i, part := range parts { + c.Assert(part.Number, Equals, i+1) + c.Assert(part.Offset, Equals, int64(i*120512)) + c.Assert(part.Size, Equals, int64(120512)) + } + + parts, err = SplitFileByPartNum(localFile, 5) + c.Assert(err, IsNil) + c.Assert(len(parts), Equals, 5) + testLogger.Println("parts 5:", parts) + for i, part := range parts { + c.Assert(part.Number, Equals, i+1) + c.Assert(part.Offset, Equals, int64(i*96409)) + } + + _, err = SplitFileByPartNum(localFile, 10001) + c.Assert(err, NotNil) + + _, err = SplitFileByPartNum(localFile, 0) + c.Assert(err, NotNil) + + _, err = SplitFileByPartNum(localFile, -1) + c.Assert(err, NotNil) + + _, err = SplitFileByPartNum("notexist", 1024) + c.Assert(err, NotNil) + + // Size + parts, err = SplitFileByPartSize(localFile, 120512) + c.Assert(err, IsNil) + c.Assert(len(parts), Equals, 4) + testLogger.Println("parts 4:", parts) + for i, part := range parts { + c.Assert(part.Number, Equals, i+1) + c.Assert(part.Offset, Equals, int64(i*120512)) + c.Assert(part.Size, Equals, int64(120512)) + } + + parts, err = SplitFileByPartSize(localFile, 96409) + c.Assert(err, IsNil) + c.Assert(len(parts), Equals, 6) + testLogger.Println("parts 6:", parts) + for i, part := range parts { + c.Assert(part.Number, Equals, i+1) + c.Assert(part.Offset, Equals, int64(i*96409)) + } + + _, err = SplitFileByPartSize(localFile, 0) + c.Assert(err, NotNil) + + _, err = SplitFileByPartSize(localFile, -1) + c.Assert(err, NotNil) + + _, err = SplitFileByPartSize(localFile, 10) + c.Assert(err, NotNil) + + _, err = SplitFileByPartSize("noexist", 120512) + c.Assert(err, NotNil) +} + +func (s *OssUtilsSuite) TestUtilsFileExt(c *C) { + c.Assert(TypeByExtension("test.txt"), Equals, "text/plain; charset=utf-8") + c.Assert(TypeByExtension("test.jpg"), Equals, "image/jpeg") + c.Assert(TypeByExtension("test.pdf"), Equals, "application/pdf") + c.Assert(TypeByExtension("test"), Equals, "") + c.Assert(TypeByExtension("/root/dir/test.txt"), Equals, "text/plain; charset=utf-8") + c.Assert(TypeByExtension("root/dir/test.txt"), Equals, "text/plain; charset=utf-8") + c.Assert(TypeByExtension("root\\dir\\test.txt"), Equals, "text/plain; charset=utf-8") + c.Assert(TypeByExtension("D:\\work\\dir\\test.txt"), Equals, "text/plain; charset=utf-8") +} + +func (s *OssUtilsSuite) TestGetPartEnd(c *C) { + end := GetPartEnd(3, 10, 3) + c.Assert(end, Equals, int64(5)) + + end = GetPartEnd(9, 10, 3) + c.Assert(end, Equals, int64(9)) + + end = GetPartEnd(7, 10, 3) + c.Assert(end, Equals, int64(9)) +} + +func (s *OssUtilsSuite) TestParseRange(c *C) { + // InvalidRange bytes==M-N + _, err := parseRange("bytes==M-N") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "InvalidRange bytes==M-N") + + // InvalidRange ranges=M-N + _, err = parseRange("ranges=M-N") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "InvalidRange ranges=M-N") + + // InvalidRange ranges=M-N + _, err = parseRange("bytes=M-N") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "InvalidRange bytes=M-N") + + // InvalidRange ranges=M- + _, err = parseRange("bytes=M-") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "InvalidRange bytes=M-") + + // InvalidRange ranges=-N + _, err = parseRange("bytes=-N") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "InvalidRange bytes=-N") + + // InvalidRange ranges=-0 + _, err = parseRange("bytes=-0") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "InvalidRange bytes=-0") + + // InvalidRange bytes=1-2-3 + _, err = parseRange("bytes=1-2-3") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "InvalidRange bytes=1-2-3") + + // InvalidRange bytes=1-N + _, err = parseRange("bytes=1-N") + c.Assert(err, NotNil) + c.Assert(err.Error(), Equals, "InvalidRange bytes=1-N") + + // Ranges=M-N + ur, err := parseRange("bytes=1024-4096") + c.Assert(err, IsNil) + c.Assert(ur.start, Equals, (int64)(1024)) + c.Assert(ur.end, Equals, (int64)(4096)) + c.Assert(ur.hasStart, Equals, true) + c.Assert(ur.hasEnd, Equals, true) + + // Ranges=M-N,X-Y + ur, err = parseRange("bytes=1024-4096,2048-4096") + c.Assert(err, IsNil) + c.Assert(ur.start, Equals, (int64)(1024)) + c.Assert(ur.end, Equals, (int64)(4096)) + c.Assert(ur.hasStart, Equals, true) + c.Assert(ur.hasEnd, Equals, true) + + // Ranges=M- + ur, err = parseRange("bytes=1024-") + c.Assert(err, IsNil) + c.Assert(ur.start, Equals, (int64)(1024)) + c.Assert(ur.end, Equals, (int64)(0)) + c.Assert(ur.hasStart, Equals, true) + c.Assert(ur.hasEnd, Equals, false) + + // Ranges=-N + ur, err = parseRange("bytes=-4096") + c.Assert(err, IsNil) + c.Assert(ur.start, Equals, (int64)(0)) + c.Assert(ur.end, Equals, (int64)(4096)) + c.Assert(ur.hasStart, Equals, false) + c.Assert(ur.hasEnd, Equals, true) +} + +func (s *OssUtilsSuite) TestAdjustRange(c *C) { + // Nil + start, end := adjustRange(nil, 8192) + c.Assert(start, Equals, (int64)(0)) + c.Assert(end, Equals, (int64)(8192)) + + // 1024-4096 + ur := &unpackedRange{true, true, 1024, 4095} + start, end = adjustRange(ur, 8192) + c.Assert(start, Equals, (int64)(1024)) + c.Assert(end, Equals, (int64)(4096)) + + // 1024- + ur = &unpackedRange{true, false, 1024, 4096} + start, end = adjustRange(ur, 8192) + c.Assert(start, Equals, (int64)(1024)) + c.Assert(end, Equals, (int64)(8192)) + + // -4096 + ur = &unpackedRange{false, true, 1024, 4096} + start, end = adjustRange(ur, 8192) + c.Assert(start, Equals, (int64)(4096)) + c.Assert(end, Equals, (int64)(8192)) + + // Invalid range 4096-1024 + ur = &unpackedRange{true, true, 4096, 1024} + start, end = adjustRange(ur, 8192) + c.Assert(start, Equals, (int64)(0)) + c.Assert(end, Equals, (int64)(8192)) + + // Invalid range -1- + ur = &unpackedRange{true, false, -1, 0} + start, end = adjustRange(ur, 8192) + c.Assert(start, Equals, (int64)(0)) + c.Assert(end, Equals, (int64)(8192)) + + // Invalid range -9999 + ur = &unpackedRange{false, true, 0, 9999} + start, end = adjustRange(ur, 8192) + c.Assert(start, Equals, (int64)(0)) + c.Assert(end, Equals, (int64)(8192)) +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample.go new file mode 100644 index 000000000..8fd8cc814 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample.go @@ -0,0 +1,36 @@ +// main of samples + +package main + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/sample" +) + +func main() { + sample.CreateBucketSample() + sample.NewBucketSample() + sample.ListBucketsSample() + sample.BucketACLSample() + sample.BucketLifecycleSample() + sample.BucketRefererSample() + sample.BucketLoggingSample() + sample.BucketCORSSample() + + sample.ObjectACLSample() + sample.ObjectMetaSample() + sample.ListObjectsSample() + sample.DeleteObjectSample() + sample.AppendObjectSample() + sample.CopyObjectSample() + sample.PutObjectSample() + sample.GetObjectSample() + + sample.CnameSample() + sample.SignURLSample() + + sample.ArchiveSample() + + fmt.Println("All samples completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/BingWallpaper-2015-11-07.jpg b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/BingWallpaper-2015-11-07.jpg new file mode 100644 index 0000000000000000000000000000000000000000..90960f0f2a0fad64b8c832315cd27966b218cee4 GIT binary patch literal 482048 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 *j-^X+IKt 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%0A&-m23b#{}M?RBT>5V(U6 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%0D19N=*E)+3h>;Ot;@);3ktJ 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

xN>kA{J8aGl{Mjoa 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νq+ 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 Z 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 E16yGfHUIzs literal 0 HcmV?d00001 diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/The Go Programming Language.html b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/The Go Programming Language.html new file mode 100644 index 000000000..ee91f0737 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/The Go Programming Language.html @@ -0,0 +1,3663 @@ + + + + + + + + http - The Go Programming Language + + + + + + + + + + + +

+... +
+ +
+ + +
+
+
+
+ Run + Format + +
+
+ + +
+
+ + +

Package http

+ + + + + + + + + + + + + + +
+
+
import "net/http"
+
+
+
Overview
+
Index
+ +
Examples
+ + +
Subdirectories
+ +
+
+ +
+ +
+

Overview ▾

+

+Package http provides HTTP client and server implementations. +

+

+Get, Head, Post, and PostForm make HTTP (or HTTPS) requests: +

+
resp, err := http.Get("http://example.com/")
+...
+resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
+...
+resp, err := http.PostForm("http://example.com/form",
+	url.Values{"key": {"Value"}, "id": {"123"}})
+
+

+The client must close the response body when finished with it: +

+
resp, err := http.Get("http://example.com/")
+if err != nil {
+	// handle error
+}
+defer resp.Body.Close()
+body, err := ioutil.ReadAll(resp.Body)
+// ...
+
+

+For control over HTTP client headers, redirect policy, and other +settings, create a Client: +

+
client := &http.Client{
+	CheckRedirect: redirectPolicyFunc,
+}
+
+resp, err := client.Get("http://example.com")
+// ...
+
+req, err := http.NewRequest("GET", "http://example.com", nil)
+// ...
+req.Header.Add("If-None-Match", `W/"wyzzy"`)
+resp, err := client.Do(req)
+// ...
+
+

+For control over proxies, TLS configuration, keep-alives, +compression, and other settings, create a Transport: +

+
tr := &http.Transport{
+	TLSClientConfig:    &tls.Config{RootCAs: pool},
+	DisableCompression: true,
+}
+client := &http.Client{Transport: tr}
+resp, err := client.Get("https://example.com")
+
+

+Clients and Transports are safe for concurrent use by multiple +goroutines and for efficiency should only be created once and re-used. +

+

+ListenAndServe starts an HTTP server with a given address and handler. +The handler is usually nil, which means to use DefaultServeMux. +Handle and HandleFunc add handlers to DefaultServeMux: +

+
http.Handle("/foo", fooHandler)
+
+http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
+	fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
+})
+
+log.Fatal(http.ListenAndServe(":8080", nil))
+
+

+More control over the server's behavior is available by creating a +custom Server: +

+
s := &http.Server{
+	Addr:           ":8080",
+	Handler:        myHandler,
+	ReadTimeout:    10 * time.Second,
+	WriteTimeout:   10 * time.Second,
+	MaxHeaderBytes: 1 << 20,
+}
+log.Fatal(s.ListenAndServe())
+
+ +
+
+ + +
+ +
+

Index ▾

+ + +
+
+ +
Constants
+ + +
Variables
+ + + +
func CanonicalHeaderKey(s string) string
+ + +
func DetectContentType(data []byte) string
+ + +
func Error(w ResponseWriter, error string, code int)
+ + +
func Handle(pattern string, handler Handler)
+ + +
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
+ + +
func ListenAndServe(addr string, handler Handler) error
+ + +
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error
+ + +
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser
+ + +
func NotFound(w ResponseWriter, r *Request)
+ + +
func ParseHTTPVersion(vers string) (major, minor int, ok bool)
+ + +
func ParseTime(text string) (t time.Time, err error)
+ + +
func ProxyFromEnvironment(req *Request) (*url.URL, error)
+ + +
func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error)
+ + +
func Redirect(w ResponseWriter, r *Request, urlStr string, code int)
+ + +
func Serve(l net.Listener, handler Handler) error
+ + +
func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker)
+ + +
func ServeFile(w ResponseWriter, r *Request, name string)
+ + +
func SetCookie(w ResponseWriter, cookie *Cookie)
+ + +
func StatusText(code int) string
+ + + +
type Client
+ + + +
    func (c *Client) Do(req *Request) (resp *Response, err error)
+ + +
    func (c *Client) Get(url string) (resp *Response, err error)
+ + +
    func (c *Client) Head(url string) (resp *Response, err error)
+ + +
    func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error)
+ + +
    func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)
+ + + +
type CloseNotifier
+ + + + +
type ConnState
+ + + +
    func (c ConnState) String() string
+ + + +
type Cookie
+ + + +
    func (c *Cookie) String() string
+ + + +
type CookieJar
+ + + + +
type Dir
+ + + +
    func (d Dir) Open(name string) (File, error)
+ + + +
type File
+ + + + +
type FileSystem
+ + + + +
type Flusher
+ + + + +
type Handler
+ + +
    func FileServer(root FileSystem) Handler
+ + +
    func NotFoundHandler() Handler
+ + +
    func RedirectHandler(url string, code int) Handler
+ + +
    func StripPrefix(prefix string, h Handler) Handler
+ + +
    func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
+ + + + +
type HandlerFunc
+ + + +
    func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
+ + + +
type Header
+ + + +
    func (h Header) Add(key, value string)
+ + +
    func (h Header) Del(key string)
+ + +
    func (h Header) Get(key string) string
+ + +
    func (h Header) Set(key, value string)
+ + +
    func (h Header) Write(w io.Writer) error
+ + +
    func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error
+ + + +
type Hijacker
+ + + + +
type ProtocolError
+ + + +
    func (err *ProtocolError) Error() string
+ + + +
type Request
+ + +
    func NewRequest(method, urlStr string, body io.Reader) (*Request, error)
+ + +
    func ReadRequest(b *bufio.Reader) (req *Request, err error)
+ + + +
    func (r *Request) AddCookie(c *Cookie)
+ + +
    func (r *Request) BasicAuth() (username, password string, ok bool)
+ + +
    func (r *Request) Cookie(name string) (*Cookie, error)
+ + +
    func (r *Request) Cookies() []*Cookie
+ + +
    func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)
+ + +
    func (r *Request) FormValue(key string) string
+ + +
    func (r *Request) MultipartReader() (*multipart.Reader, error)
+ + +
    func (r *Request) ParseForm() error
+ + +
    func (r *Request) ParseMultipartForm(maxMemory int64) error
+ + +
    func (r *Request) PostFormValue(key string) string
+ + +
    func (r *Request) ProtoAtLeast(major, minor int) bool
+ + +
    func (r *Request) Referer() string
+ + +
    func (r *Request) SetBasicAuth(username, password string)
+ + +
    func (r *Request) UserAgent() string
+ + +
    func (r *Request) Write(w io.Writer) error
+ + +
    func (r *Request) WriteProxy(w io.Writer) error
+ + + +
type Response
+ + +
    func Get(url string) (resp *Response, err error)
+ + +
    func Head(url string) (resp *Response, err error)
+ + +
    func Post(url string, bodyType string, body io.Reader) (resp *Response, err error)
+ + +
    func PostForm(url string, data url.Values) (resp *Response, err error)
+ + +
    func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
+ + + +
    func (r *Response) Cookies() []*Cookie
+ + +
    func (r *Response) Location() (*url.URL, error)
+ + +
    func (r *Response) ProtoAtLeast(major, minor int) bool
+ + +
    func (r *Response) Write(w io.Writer) error
+ + + +
type ResponseWriter
+ + + + +
type RoundTripper
+ + +
    func NewFileTransport(fs FileSystem) RoundTripper
+ + + + +
type ServeMux
+ + +
    func NewServeMux() *ServeMux
+ + + +
    func (mux *ServeMux) Handle(pattern string, handler Handler)
+ + +
    func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
+ + +
    func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
+ + +
    func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
+ + + +
type Server
+ + + +
    func (srv *Server) ListenAndServe() error
+ + +
    func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error
+ + +
    func (srv *Server) Serve(l net.Listener) error
+ + +
    func (srv *Server) SetKeepAlivesEnabled(v bool)
+ + + +
type Transport
+ + + +
    func (t *Transport) CancelRequest(req *Request)
+ + +
    func (t *Transport) CloseIdleConnections()
+ + +
    func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper)
+ + +
    func (t *Transport) RoundTrip(req *Request) (resp *Response, err error)
+ + + +
+
+ + + + + + +

Package files

+

+ + + client.go + + cookie.go + + doc.go + + filetransport.go + + fs.go + + header.go + + jar.go + + lex.go + + request.go + + response.go + + server.go + + sniff.go + + status.go + + transfer.go + + transport.go + + +

+ +
+
+ + + + +

Constants

+ +
const (
+        StatusContinue           = 100
+        StatusSwitchingProtocols = 101
+
+        StatusOK                   = 200
+        StatusCreated              = 201
+        StatusAccepted             = 202
+        StatusNonAuthoritativeInfo = 203
+        StatusNoContent            = 204
+        StatusResetContent         = 205
+        StatusPartialContent       = 206
+
+        StatusMultipleChoices   = 300
+        StatusMovedPermanently  = 301
+        StatusFound             = 302
+        StatusSeeOther          = 303
+        StatusNotModified       = 304
+        StatusUseProxy          = 305
+        StatusTemporaryRedirect = 307
+
+        StatusBadRequest                   = 400
+        StatusUnauthorized                 = 401
+        StatusPaymentRequired              = 402
+        StatusForbidden                    = 403
+        StatusNotFound                     = 404
+        StatusMethodNotAllowed             = 405
+        StatusNotAcceptable                = 406
+        StatusProxyAuthRequired            = 407
+        StatusRequestTimeout               = 408
+        StatusConflict                     = 409
+        StatusGone                         = 410
+        StatusLengthRequired               = 411
+        StatusPreconditionFailed           = 412
+        StatusRequestEntityTooLarge        = 413
+        StatusRequestURITooLong            = 414
+        StatusUnsupportedMediaType         = 415
+        StatusRequestedRangeNotSatisfiable = 416
+        StatusExpectationFailed            = 417
+        StatusTeapot                       = 418
+
+        StatusInternalServerError     = 500
+        StatusNotImplemented          = 501
+        StatusBadGateway              = 502
+        StatusServiceUnavailable      = 503
+        StatusGatewayTimeout          = 504
+        StatusHTTPVersionNotSupported = 505
+)
+

+HTTP status codes, defined in RFC 2616. +

+ + +
const DefaultMaxHeaderBytes = 1 << 20 // 1 MB
+
+

+DefaultMaxHeaderBytes is the maximum permitted size of the headers +in an HTTP request. +This can be overridden by setting Server.MaxHeaderBytes. +

+ + +
const DefaultMaxIdleConnsPerHost = 2
+

+DefaultMaxIdleConnsPerHost is the default value of Transport's +MaxIdleConnsPerHost. +

+ + +
const TimeFormat = "Mon, 02 Jan 2006 15:04:05 GMT"
+

+TimeFormat is the time format to use with +time.Parse and time.Time.Format when parsing +or generating times in HTTP headers. +It is like time.RFC1123 but hard codes GMT as the time zone. +

+ + + + +

Variables

+ +
var (
+        ErrHeaderTooLong        = &ProtocolError{"header too long"}
+        ErrShortBody            = &ProtocolError{"entity body too short"}
+        ErrNotSupported         = &ProtocolError{"feature not supported"}
+        ErrUnexpectedTrailer    = &ProtocolError{"trailer header without chunked transfer encoding"}
+        ErrMissingContentLength = &ProtocolError{"missing ContentLength in HEAD response"}
+        ErrNotMultipart         = &ProtocolError{"request Content-Type isn't multipart/form-data"}
+        ErrMissingBoundary      = &ProtocolError{"no multipart boundary param in Content-Type"}
+)
+ + +
var (
+        ErrWriteAfterFlush = errors.New("Conn.Write called after Flush")
+        ErrBodyNotAllowed  = errors.New("http: request method or response status code does not allow body")
+        ErrHijacked        = errors.New("Conn has been hijacked")
+        ErrContentLength   = errors.New("Conn.Write wrote more than the declared Content-Length")
+)
+

+Errors introduced by the HTTP server. +

+ + +
var DefaultClient = &Client{}
+

+DefaultClient is the default Client and is used by Get, Head, and Post. +

+ + +
var DefaultServeMux = NewServeMux()
+

+DefaultServeMux is the default ServeMux used by Serve. +

+ + +
var ErrBodyReadAfterClose = errors.New("http: invalid Read on closed Body")
+

+ErrBodyReadAfterClose is returned when reading a Request or Response +Body after the body has been closed. This typically happens when the body is +read after an HTTP Handler calls WriteHeader or Write on its +ResponseWriter. +

+ + +
var ErrHandlerTimeout = errors.New("http: Handler timeout")
+

+ErrHandlerTimeout is returned on ResponseWriter Write calls +in handlers which have timed out. +

+ + +
var ErrLineTooLong = internal.ErrLineTooLong
+

+ErrLineTooLong is returned when reading request or response bodies +with malformed chunked encoding. +

+ + +
var ErrMissingFile = errors.New("http: no such file")
+

+ErrMissingFile is returned by FormFile when the provided file field name +is either not present in the request or not a file field. +

+ + +
var ErrNoCookie = errors.New("http: named cookie not present")
+

+ErrNoCookie is returned by Request's Cookie method when a cookie is not found. +

+ + +
var ErrNoLocation = errors.New("http: no Location header in response")
+

+ErrNoLocation is returned by Response's Location method +when no Location header is present. +

+ + + + + + +

func CanonicalHeaderKey

+
func CanonicalHeaderKey(s string) string
+

+CanonicalHeaderKey returns the canonical format of the +header key s. The canonicalization converts the first +letter and any letter following a hyphen to upper case; +the rest are converted to lowercase. For example, the +canonical key for "accept-encoding" is "Accept-Encoding". +If s contains a space or invalid header field bytes, it is +returned without modifications. +

+ + + + + + + +

func DetectContentType

+
func DetectContentType(data []byte) string
+

+DetectContentType implements the algorithm described +at http://mimesniff.spec.whatwg.org/ to determine the +Content-Type of the given data. It considers at most the +first 512 bytes of data. DetectContentType always returns +a valid MIME type: if it cannot determine a more specific one, it +returns "application/octet-stream". +

+ + + + + + + +

func Error

+
func Error(w ResponseWriter, error string, code int)
+

+Error replies to the request with the specified error message and HTTP code. +The error message should be plain text. +

+ + + + + + + +

func Handle

+
func Handle(pattern string, handler Handler)
+

+Handle registers the handler for the given pattern +in the DefaultServeMux. +The documentation for ServeMux explains how patterns are matched. +

+ + + + + + + +

func HandleFunc

+
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
+

+HandleFunc registers the handler function for the given pattern +in the DefaultServeMux. +The documentation for ServeMux explains how patterns are matched. +

+ + + + + + + +

func ListenAndServe

+
func ListenAndServe(addr string, handler Handler) error
+

+ListenAndServe listens on the TCP network address addr +and then calls Serve with handler to handle requests +on incoming connections. Handler is typically nil, +in which case the DefaultServeMux is used. +

+

+A trivial example server is: +

+
package main
+
+import (
+	"io"
+	"net/http"
+	"log"
+)
+
+// hello world, the web server
+func HelloServer(w http.ResponseWriter, req *http.Request) {
+	io.WriteString(w, "hello, world!\n")
+}
+
+func main() {
+	http.HandleFunc("/hello", HelloServer)
+	err := http.ListenAndServe(":12345", nil)
+	if err != nil {
+		log.Fatal("ListenAndServe: ", err)
+	}
+}
+
+ + + + + + + +

func ListenAndServeTLS

+
func ListenAndServeTLS(addr string, certFile string, keyFile string, handler Handler) error
+

+ListenAndServeTLS acts identically to ListenAndServe, except that it +expects HTTPS connections. Additionally, files containing a certificate and +matching private key for the server must be provided. If the certificate +is signed by a certificate authority, the certFile should be the concatenation +of the server's certificate, any intermediates, and the CA's certificate. +

+

+A trivial example server is: +

+
import (
+	"log"
+	"net/http"
+)
+
+func handler(w http.ResponseWriter, req *http.Request) {
+	w.Header().Set("Content-Type", "text/plain")
+	w.Write([]byte("This is an example server.\n"))
+}
+
+func main() {
+	http.HandleFunc("/", handler)
+	log.Printf("About to listen on 10443. Go to https://127.0.0.1:10443/")
+	err := http.ListenAndServeTLS(":10443", "cert.pem", "key.pem", nil)
+	if err != nil {
+		log.Fatal(err)
+	}
+}
+
+

+One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem. +

+ + + + + + + +

func MaxBytesReader

+
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser
+

+MaxBytesReader is similar to io.LimitReader but is intended for +limiting the size of incoming request bodies. In contrast to +io.LimitReader, MaxBytesReader's result is a ReadCloser, returns a +non-EOF error for a Read beyond the limit, and closes the +underlying reader when its Close method is called. +

+

+MaxBytesReader prevents clients from accidentally or maliciously +sending a large request and wasting server resources. +

+ + + + + + + +

func NotFound

+
func NotFound(w ResponseWriter, r *Request)
+

+NotFound replies to the request with an HTTP 404 not found error. +

+ + + + + + + +

func ParseHTTPVersion

+
func ParseHTTPVersion(vers string) (major, minor int, ok bool)
+

+ParseHTTPVersion parses a HTTP version string. +"HTTP/1.0" returns (1, 0, true). +

+ + + + + + + +

func ParseTime

+
func ParseTime(text string) (t time.Time, err error)
+

+ParseTime parses a time header (such as the Date: header), +trying each of the three formats allowed by HTTP/1.1: +TimeFormat, time.RFC850, and time.ANSIC. +

+ + + + + + + +

func ProxyFromEnvironment

+
func ProxyFromEnvironment(req *Request) (*url.URL, error)
+

+ProxyFromEnvironment returns the URL of the proxy to use for a +given request, as indicated by the environment variables +HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions +thereof). HTTPS_PROXY takes precedence over HTTP_PROXY for https +requests. +

+

+The environment values may be either a complete URL or a +"host[:port]", in which case the "http" scheme is assumed. +An error is returned if the value is a different form. +

+

+A nil URL and nil error are returned if no proxy is defined in the +environment, or a proxy should not be used for the given request, +as defined by NO_PROXY. +

+

+As a special case, if req.URL.Host is "localhost" (with or without +a port number), then a nil URL and nil error will be returned. +

+ + + + + + + +

func ProxyURL

+
func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error)
+

+ProxyURL returns a proxy function (for use in a Transport) +that always returns the same URL. +

+ + + + + + + +

func Redirect

+
func Redirect(w ResponseWriter, r *Request, urlStr string, code int)
+

+Redirect replies to the request with a redirect to url, +which may be a path relative to the request path. +

+ + + + + + + +

func Serve

+
func Serve(l net.Listener, handler Handler) error
+

+Serve accepts incoming HTTP connections on the listener l, +creating a new service goroutine for each. The service goroutines +read requests and then call handler to reply to them. +Handler is typically nil, in which case the DefaultServeMux is used. +

+ + + + + + + +

func ServeContent

+
func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, content io.ReadSeeker)
+

+ServeContent replies to the request using the content in the +provided ReadSeeker. The main benefit of ServeContent over io.Copy +is that it handles Range requests properly, sets the MIME type, and +handles If-Modified-Since requests. +

+

+If the response's Content-Type header is not set, ServeContent +first tries to deduce the type from name's file extension and, +if that fails, falls back to reading the first block of the content +and passing it to DetectContentType. +The name is otherwise unused; in particular it can be empty and is +never sent in the response. +

+

+If modtime is not the zero time or Unix epoch, ServeContent +includes it in a Last-Modified header in the response. If the +request includes an If-Modified-Since header, ServeContent uses +modtime to decide whether the content needs to be sent at all. +

+

+The content's Seek method must work: ServeContent uses +a seek to the end of the content to determine its size. +

+

+If the caller has set w's ETag header, ServeContent uses it to +handle requests using If-Range and If-None-Match. +

+

+Note that *os.File implements the io.ReadSeeker interface. +

+ + + + + + + +

func ServeFile

+
func ServeFile(w ResponseWriter, r *Request, name string)
+

+ServeFile replies to the request with the contents of the named +file or directory. +

+

+As a special case, ServeFile redirects any request where r.URL.Path +ends in "/index.html" to the same path, without the final +"index.html". To avoid such redirects either modify the path or +use ServeContent. +

+ + + + + + + +

func SetCookie

+
func SetCookie(w ResponseWriter, cookie *Cookie)
+

+SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. +The provided cookie must have a valid Name. Invalid cookies may be +silently dropped. +

+ + + + + + + +

func StatusText

+
func StatusText(code int) string
+

+StatusText returns a text for the HTTP status code. It returns the empty +string if the code is unknown. +

+ + + + + + + + +

type Client

+
type Client struct {
+        // Transport specifies the mechanism by which individual
+        // HTTP requests are made.
+        // If nil, DefaultTransport is used.
+        Transport RoundTripper
+
+        // CheckRedirect specifies the policy for handling redirects.
+        // If CheckRedirect is not nil, the client calls it before
+        // following an HTTP redirect. The arguments req and via are
+        // the upcoming request and the requests made already, oldest
+        // first. If CheckRedirect returns an error, the Client's Get
+        // method returns both the previous Response and
+        // CheckRedirect's error (wrapped in a url.Error) instead of
+        // issuing the Request req.
+        //
+        // If CheckRedirect is nil, the Client uses its default policy,
+        // which is to stop after 10 consecutive requests.
+        CheckRedirect func(req *Request, via []*Request) error
+
+        // Jar specifies the cookie jar.
+        // If Jar is nil, cookies are not sent in requests and ignored
+        // in responses.
+        Jar CookieJar
+
+        // Timeout specifies a time limit for requests made by this
+        // Client. The timeout includes connection time, any
+        // redirects, and reading the response body. The timer remains
+        // running after Get, Head, Post, or Do return and will
+        // interrupt reading of the Response.Body.
+        //
+        // A Timeout of zero means no timeout.
+        //
+        // The Client's Transport must support the CancelRequest
+        // method or Client will return errors when attempting to make
+        // a request with Get, Head, Post, or Do. Client's default
+        // Transport (DefaultTransport) supports CancelRequest.
+        Timeout time.Duration
+}
+

+A Client is an HTTP client. Its zero value (DefaultClient) is a +usable client that uses DefaultTransport. +

+

+The Client's Transport typically has internal state (cached TCP +connections), so Clients should be reused instead of created as +needed. Clients are safe for concurrent use by multiple goroutines. +

+

+A Client is higher-level than a RoundTripper (such as Transport) +and additionally handles HTTP details such as cookies and +redirects. +

+ + + + + + + + + + + + + + +

func (*Client) Do

+
func (c *Client) Do(req *Request) (resp *Response, err error)
+

+Do sends an HTTP request and returns an HTTP response, following +policy (e.g. redirects, cookies, auth) as configured on the client. +

+

+An error is returned if caused by client policy (such as +CheckRedirect), or if there was an HTTP protocol error. +A non-2xx response doesn't cause an error. +

+

+When err is nil, resp always contains a non-nil resp.Body. +

+

+Callers should close resp.Body when done reading from it. If +resp.Body is not closed, the Client's underlying RoundTripper +(typically Transport) may not be able to re-use a persistent TCP +connection to the server for a subsequent "keep-alive" request. +

+

+The request Body, if non-nil, will be closed by the underlying +Transport, even on errors. +

+

+Generally Get, Post, or PostForm will be used instead of Do. +

+ + + + + + +

func (*Client) Get

+
func (c *Client) Get(url string) (resp *Response, err error)
+

+Get issues a GET to the specified URL. If the response is one of the +following redirect codes, Get follows the redirect after calling the +Client's CheckRedirect function: +

+
301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+
+

+An error is returned if the Client's CheckRedirect function fails +or if there was an HTTP protocol error. A non-2xx response doesn't +cause an error. +

+

+When err is nil, resp always contains a non-nil resp.Body. +Caller should close resp.Body when done reading from it. +

+

+To make a request with custom headers, use NewRequest and Client.Do. +

+ + + + + + +

func (*Client) Head

+
func (c *Client) Head(url string) (resp *Response, err error)
+

+Head issues a HEAD to the specified URL. If the response is one of the +following redirect codes, Head follows the redirect after calling the +Client's CheckRedirect function: +

+
301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+
+ + + + + + +

func (*Client) Post

+
func (c *Client) Post(url string, bodyType string, body io.Reader) (resp *Response, err error)
+

+Post issues a POST to the specified URL. +

+

+Caller should close resp.Body when done reading from it. +

+

+If the provided body is an io.Closer, it is closed after the +request. +

+

+To set custom headers, use NewRequest and Client.Do. +

+ + + + + + +

func (*Client) PostForm

+
func (c *Client) PostForm(url string, data url.Values) (resp *Response, err error)
+

+PostForm issues a POST to the specified URL, +with data's keys and values URL-encoded as the request body. +

+

+The Content-Type header is set to application/x-www-form-urlencoded. +To set other headers, use NewRequest and DefaultClient.Do. +

+

+When err is nil, resp always contains a non-nil resp.Body. +Caller should close resp.Body when done reading from it. +

+ + + + + + + + +

type CloseNotifier

+
type CloseNotifier interface {
+        // CloseNotify returns a channel that receives a single value
+        // when the client connection has gone away.
+        CloseNotify() <-chan bool
+}
+

+The CloseNotifier interface is implemented by ResponseWriters which +allow detecting when the underlying connection has gone away. +

+

+This mechanism can be used to cancel long operations on the server +if the client has disconnected before the response is ready. +

+ + + + + + + + + + + + + + + + +

type ConnState

+
type ConnState int
+

+A ConnState represents the state of a client connection to a server. +It's used by the optional Server.ConnState hook. +

+ + + +
const (
+        // StateNew represents a new connection that is expected to
+        // send a request immediately. Connections begin at this
+        // state and then transition to either StateActive or
+        // StateClosed.
+        StateNew ConnState = iota
+
+        // StateActive represents a connection that has read 1 or more
+        // bytes of a request. The Server.ConnState hook for
+        // StateActive fires before the request has entered a handler
+        // and doesn't fire again until the request has been
+        // handled. After the request is handled, the state
+        // transitions to StateClosed, StateHijacked, or StateIdle.
+        StateActive
+
+        // StateIdle represents a connection that has finished
+        // handling a request and is in the keep-alive state, waiting
+        // for a new request. Connections transition from StateIdle
+        // to either StateActive or StateClosed.
+        StateIdle
+
+        // StateHijacked represents a hijacked connection.
+        // This is a terminal state. It does not transition to StateClosed.
+        StateHijacked
+
+        // StateClosed represents a closed connection.
+        // This is a terminal state. Hijacked connections do not
+        // transition to StateClosed.
+        StateClosed
+)
+ + + + + + + + + + + + + +

func (ConnState) String

+
func (c ConnState) String() string
+ + + + + + + + + +
type Cookie struct {
+        Name  string
+        Value string
+
+        Path       string    // optional
+        Domain     string    // optional
+        Expires    time.Time // optional
+        RawExpires string    // for reading cookies only
+
+        // MaxAge=0 means no 'Max-Age' attribute specified.
+        // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
+        // MaxAge>0 means Max-Age attribute present and given in seconds
+        MaxAge   int
+        Secure   bool
+        HttpOnly bool
+        Raw      string
+        Unparsed []string // Raw text of unparsed attribute-value pairs
+}
+

+A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an +HTTP response or the Cookie header of an HTTP request. +

+

+See http://tools.ietf.org/html/rfc6265 for details. +

+ + + + + + + + + + + + + + +

func (*Cookie) String

+
func (c *Cookie) String() string
+

+String returns the serialization of the cookie for use in a Cookie +header (if only Name and Value are set) or a Set-Cookie response +header (if other fields are set). +If c is nil or c.Name is invalid, the empty string is returned. +

+ + + + + + + + +

type CookieJar

+
type CookieJar interface {
+        // SetCookies handles the receipt of the cookies in a reply for the
+        // given URL.  It may or may not choose to save the cookies, depending
+        // on the jar's policy and implementation.
+        SetCookies(u *url.URL, cookies []*Cookie)
+
+        // Cookies returns the cookies to send in a request for the given URL.
+        // It is up to the implementation to honor the standard cookie use
+        // restrictions such as in RFC 6265.
+        Cookies(u *url.URL) []*Cookie
+}
+

+A CookieJar manages storage and use of cookies in HTTP requests. +

+

+Implementations of CookieJar must be safe for concurrent use by multiple +goroutines. +

+

+The net/http/cookiejar package provides a CookieJar implementation. +

+ + + + + + + + + + + + + + + + +

type Dir

+
type Dir string
+

+A Dir implements FileSystem using the native file system restricted to a +specific directory tree. +

+

+While the FileSystem.Open method takes '/'-separated paths, a Dir's string +value is a filename on the native file system, not a URL, so it is separated +by filepath.Separator, which isn't necessarily '/'. +

+

+An empty Dir is treated as ".". +

+ + + + + + + + + + + + + + +

func (Dir) Open

+
func (d Dir) Open(name string) (File, error)
+ + + + + + + + +

type File

+
type File interface {
+        io.Closer
+        io.Reader
+        Readdir(count int) ([]os.FileInfo, error)
+        Seek(offset int64, whence int) (int64, error)
+        Stat() (os.FileInfo, error)
+}
+

+A File is returned by a FileSystem's Open method and can be +served by the FileServer implementation. +

+

+The methods should behave the same as those on an *os.File. +

+ + + + + + + + + + + + + + + + +

type FileSystem

+
type FileSystem interface {
+        Open(name string) (File, error)
+}
+

+A FileSystem implements access to a collection of named files. +The elements in a file path are separated by slash ('/', U+002F) +characters, regardless of host operating system convention. +

+ + + + + + + + + + + + + + + + +

type Flusher

+
type Flusher interface {
+        // Flush sends any buffered data to the client.
+        Flush()
+}
+

+The Flusher interface is implemented by ResponseWriters that allow +an HTTP handler to flush buffered data to the client. +

+

+Note that even for ResponseWriters that support Flush, +if the client is connected through an HTTP proxy, +the buffered data may not reach the client until the response +completes. +

+ + + + + + + + + + + + + + + + +

type Handler

+
type Handler interface {
+        ServeHTTP(ResponseWriter, *Request)
+}
+

+Objects implementing the Handler interface can be +registered to serve a particular path or subtree +in the HTTP server. +

+

+ServeHTTP should write reply headers and data to the ResponseWriter +and then return. Returning signals that the request is finished +and that the HTTP server can move on to the next request on +the connection. +

+

+If ServeHTTP panics, the server (the caller of ServeHTTP) assumes +that the effect of the panic was isolated to the active request. +It recovers the panic, logs a stack trace to the server error log, +and hangs up the connection. +

+ + + + + + + + + + + + +

func FileServer

+
func FileServer(root FileSystem) Handler
+

+FileServer returns a handler that serves HTTP requests +with the contents of the file system rooted at root. +

+

+To use the operating system's file system implementation, +use http.Dir: +

+
http.Handle("/", http.FileServer(http.Dir("/tmp")))
+
+

+As a special case, the returned file server redirects any request +ending in "/index.html" to the same path, without the final +"index.html". +

+ +
+ +
+

Example

+ + + +
+
+
+
+ Run + Format + +
+
+ +
+
+
+ +
+

Example (StripPrefix)

+ + + +
+
+
+
+ Run + Format + +
+
+ +
+
+ + + + +

func NotFoundHandler

+
func NotFoundHandler() Handler
+

+NotFoundHandler returns a simple request handler +that replies to each request with a “404 page not found” reply. +

+ + + + + +

func RedirectHandler

+
func RedirectHandler(url string, code int) Handler
+

+RedirectHandler returns a request handler that redirects +each request it receives to the given url using the given +status code. +

+ + + + + +

func StripPrefix

+
func StripPrefix(prefix string, h Handler) Handler
+

+StripPrefix returns a handler that serves HTTP requests +by removing the given prefix from the request URL's Path +and invoking the handler h. StripPrefix handles a +request for a path that doesn't begin with prefix by +replying with an HTTP 404 not found error. +

+ +
+ +
+

Example

+ + + +
+
+
+
+ Run + Format + +
+
+ +
+
+ + + + +

func TimeoutHandler

+
func TimeoutHandler(h Handler, dt time.Duration, msg string) Handler
+

+TimeoutHandler returns a Handler that runs h with the given time limit. +

+

+The new Handler calls h.ServeHTTP to handle each request, but if a +call runs for longer than its time limit, the handler responds with +a 503 Service Unavailable error and the given message in its body. +(If msg is empty, a suitable default message will be sent.) +After such a timeout, writes by h to its ResponseWriter will return +ErrHandlerTimeout. +

+ + + + + + + + + +

type HandlerFunc

+
type HandlerFunc func(ResponseWriter, *Request)
+

+The HandlerFunc type is an adapter to allow the use of +ordinary functions as HTTP handlers. If f is a function +with the appropriate signature, HandlerFunc(f) is a +Handler object that calls f. +

+ + + + + + + + + + + + + + +

func (HandlerFunc) ServeHTTP

+
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
+

+ServeHTTP calls f(w, r). +

+ + + + + + + + + +
type Header map[string][]string
+

+A Header represents the key-value pairs in an HTTP header. +

+ + + + + + + + + + + + + + +

func (Header) Add

+
func (h Header) Add(key, value string)
+

+Add adds the key, value pair to the header. +It appends to any existing values associated with key. +

+ + + + + + +

func (Header) Del

+
func (h Header) Del(key string)
+

+Del deletes the values associated with key. +

+ + + + + + +

func (Header) Get

+
func (h Header) Get(key string) string
+

+Get gets the first value associated with the given key. +If there are no values associated with the key, Get returns "". +To access multiple values of a key, access the map directly +with CanonicalHeaderKey. +

+ + + + + + +

func (Header) Set

+
func (h Header) Set(key, value string)
+

+Set sets the header entries associated with key to +the single element value. It replaces any existing +values associated with key. +

+ + + + + + +

func (Header) Write

+
func (h Header) Write(w io.Writer) error
+

+Write writes a header in wire format. +

+ + + + + + +

func (Header) WriteSubset

+
func (h Header) WriteSubset(w io.Writer, exclude map[string]bool) error
+

+WriteSubset writes a header in wire format. +If exclude is not nil, keys where exclude[key] == true are not written. +

+ + + + + + + + +

type Hijacker

+
type Hijacker interface {
+        // Hijack lets the caller take over the connection.
+        // After a call to Hijack(), the HTTP server library
+        // will not do anything else with the connection.
+        //
+        // It becomes the caller's responsibility to manage
+        // and close the connection.
+        //
+        // The returned net.Conn may have read or write deadlines
+        // already set, depending on the configuration of the
+        // Server. It is the caller's responsibility to set
+        // or clear those deadlines as needed.
+        Hijack() (net.Conn, *bufio.ReadWriter, error)
+}
+

+The Hijacker interface is implemented by ResponseWriters that allow +an HTTP handler to take over the connection. +

+ + + + + + +
+ +
+

Example

+ + + +
+
+
+
+ Run + Format + +
+
+ +
+
+ + + + + + + + + + +

type ProtocolError

+
type ProtocolError struct {
+        ErrorString string
+}
+

+HTTP request parsing errors. +

+ + + + + + + + + + + + + + +

func (*ProtocolError) Error

+
func (err *ProtocolError) Error() string
+ + + + + + + + +

type Request

+
type Request struct {
+        // Method specifies the HTTP method (GET, POST, PUT, etc.).
+        // For client requests an empty string means GET.
+        Method string
+
+        // URL specifies either the URI being requested (for server
+        // requests) or the URL to access (for client requests).
+        //
+        // For server requests the URL is parsed from the URI
+        // supplied on the Request-Line as stored in RequestURI.  For
+        // most requests, fields other than Path and RawQuery will be
+        // empty. (See RFC 2616, Section 5.1.2)
+        //
+        // For client requests, the URL's Host specifies the server to
+        // connect to, while the Request's Host field optionally
+        // specifies the Host header value to send in the HTTP
+        // request.
+        URL *url.URL
+
+        // The protocol version for incoming requests.
+        // Client requests always use HTTP/1.1.
+        Proto      string // "HTTP/1.0"
+        ProtoMajor int    // 1
+        ProtoMinor int    // 0
+
+        // A header maps request lines to their values.
+        // If the header says
+        //
+        //	accept-encoding: gzip, deflate
+        //	Accept-Language: en-us
+        //	Connection: keep-alive
+        //
+        // then
+        //
+        //	Header = map[string][]string{
+        //		"Accept-Encoding": {"gzip, deflate"},
+        //		"Accept-Language": {"en-us"},
+        //		"Connection": {"keep-alive"},
+        //	}
+        //
+        // HTTP defines that header names are case-insensitive.
+        // The request parser implements this by canonicalizing the
+        // name, making the first character and any characters
+        // following a hyphen uppercase and the rest lowercase.
+        //
+        // For client requests certain headers are automatically
+        // added and may override values in Header.
+        //
+        // See the documentation for the Request.Write method.
+        Header Header
+
+        // Body is the request's body.
+        //
+        // For client requests a nil body means the request has no
+        // body, such as a GET request. The HTTP Client's Transport
+        // is responsible for calling the Close method.
+        //
+        // For server requests the Request Body is always non-nil
+        // but will return EOF immediately when no body is present.
+        // The Server will close the request body. The ServeHTTP
+        // Handler does not need to.
+        Body io.ReadCloser
+
+        // ContentLength records the length of the associated content.
+        // The value -1 indicates that the length is unknown.
+        // Values >= 0 indicate that the given number of bytes may
+        // be read from Body.
+        // For client requests, a value of 0 means unknown if Body is not nil.
+        ContentLength int64
+
+        // TransferEncoding lists the transfer encodings from outermost to
+        // innermost. An empty list denotes the "identity" encoding.
+        // TransferEncoding can usually be ignored; chunked encoding is
+        // automatically added and removed as necessary when sending and
+        // receiving requests.
+        TransferEncoding []string
+
+        // Close indicates whether to close the connection after
+        // replying to this request (for servers) or after sending
+        // the request (for clients).
+        Close bool
+
+        // For server requests Host specifies the host on which the
+        // URL is sought. Per RFC 2616, this is either the value of
+        // the "Host" header or the host name given in the URL itself.
+        // It may be of the form "host:port".
+        //
+        // For client requests Host optionally overrides the Host
+        // header to send. If empty, the Request.Write method uses
+        // the value of URL.Host.
+        Host string
+
+        // Form contains the parsed form data, including both the URL
+        // field's query parameters and the POST or PUT form data.
+        // This field is only available after ParseForm is called.
+        // The HTTP client ignores Form and uses Body instead.
+        Form url.Values
+
+        // PostForm contains the parsed form data from POST, PATCH,
+        // or PUT body parameters.
+        //
+        // This field is only available after ParseForm is called.
+        // The HTTP client ignores PostForm and uses Body instead.
+        PostForm url.Values
+
+        // MultipartForm is the parsed multipart form, including file uploads.
+        // This field is only available after ParseMultipartForm is called.
+        // The HTTP client ignores MultipartForm and uses Body instead.
+        MultipartForm *multipart.Form
+
+        // Trailer specifies additional headers that are sent after the request
+        // body.
+        //
+        // For server requests the Trailer map initially contains only the
+        // trailer keys, with nil values. (The client declares which trailers it
+        // will later send.)  While the handler is reading from Body, it must
+        // not reference Trailer. After reading from Body returns EOF, Trailer
+        // can be read again and will contain non-nil values, if they were sent
+        // by the client.
+        //
+        // For client requests Trailer must be initialized to a map containing
+        // the trailer keys to later send. The values may be nil or their final
+        // values. The ContentLength must be 0 or -1, to send a chunked request.
+        // After the HTTP request is sent the map values can be updated while
+        // the request body is read. Once the body returns EOF, the caller must
+        // not mutate Trailer.
+        //
+        // Few HTTP clients, servers, or proxies support HTTP trailers.
+        Trailer Header
+
+        // RemoteAddr allows HTTP servers and other software to record
+        // the network address that sent the request, usually for
+        // logging. This field is not filled in by ReadRequest and
+        // has no defined format. The HTTP server in this package
+        // sets RemoteAddr to an "IP:port" address before invoking a
+        // handler.
+        // This field is ignored by the HTTP client.
+        RemoteAddr string
+
+        // RequestURI is the unmodified Request-URI of the
+        // Request-Line (RFC 2616, Section 5.1) as sent by the client
+        // to a server. Usually the URL field should be used instead.
+        // It is an error to set this field in an HTTP client request.
+        RequestURI string
+
+        // TLS allows HTTP servers and other software to record
+        // information about the TLS connection on which the request
+        // was received. This field is not filled in by ReadRequest.
+        // The HTTP server in this package sets the field for
+        // TLS-enabled connections before invoking a handler;
+        // otherwise it leaves the field nil.
+        // This field is ignored by the HTTP client.
+        TLS *tls.ConnectionState
+
+        // Cancel is an optional channel whose closure indicates that the client
+        // request should be regarded as canceled. Not all implementations of
+        // RoundTripper may support Cancel.
+        //
+        // For server requests, this field is not applicable.
+        Cancel <-chan struct{}
+}
+

+A Request represents an HTTP request received by a server +or to be sent by a client. +

+

+The field semantics differ slightly between client and server +usage. In addition to the notes on the fields below, see the +documentation for Request.Write and RoundTripper. +

+ + + + + + + + + + + + +

func NewRequest

+
func NewRequest(method, urlStr string, body io.Reader) (*Request, error)
+

+NewRequest returns a new Request given a method, URL, and optional body. +

+

+If the provided body is also an io.Closer, the returned +Request.Body is set to body and will be closed by the Client +methods Do, Post, and PostForm, and Transport.RoundTrip. +

+

+NewRequest returns a Request suitable for use with Client.Do or +Transport.RoundTrip. +To create a request for use with testing a Server Handler use either +ReadRequest or manually update the Request fields. See the Request +type's documentation for the difference between inbound and outbound +request fields. +

+ + + + + +

func ReadRequest

+
func ReadRequest(b *bufio.Reader) (req *Request, err error)
+

+ReadRequest reads and parses an incoming request from b. +

+ + + + + + + +

func (*Request) AddCookie

+
func (r *Request) AddCookie(c *Cookie)
+

+AddCookie adds a cookie to the request. Per RFC 6265 section 5.4, +AddCookie does not attach more than one Cookie header field. That +means all cookies, if any, are written into the same line, +separated by semicolon. +

+ + + + + + +

func (*Request) BasicAuth

+
func (r *Request) BasicAuth() (username, password string, ok bool)
+

+BasicAuth returns the username and password provided in the request's +Authorization header, if the request uses HTTP Basic Authentication. +See RFC 2617, Section 2. +

+ + + + + + +

func (*Request) Cookie

+
func (r *Request) Cookie(name string) (*Cookie, error)
+

+Cookie returns the named cookie provided in the request or +ErrNoCookie if not found. +

+ + + + + + +

func (*Request) Cookies

+
func (r *Request) Cookies() []*Cookie
+

+Cookies parses and returns the HTTP cookies sent with the request. +

+ + + + + + +

func (*Request) FormFile

+
func (r *Request) FormFile(key string) (multipart.File, *multipart.FileHeader, error)
+

+FormFile returns the first file for the provided form key. +FormFile calls ParseMultipartForm and ParseForm if necessary. +

+ + + + + + +

func (*Request) FormValue

+
func (r *Request) FormValue(key string) string
+

+FormValue returns the first value for the named component of the query. +POST and PUT body parameters take precedence over URL query string values. +FormValue calls ParseMultipartForm and ParseForm if necessary and ignores +any errors returned by these functions. +If key is not present, FormValue returns the empty string. +To access multiple values of the same key, call ParseForm and +then inspect Request.Form directly. +

+ + + + + + +

func (*Request) MultipartReader

+
func (r *Request) MultipartReader() (*multipart.Reader, error)
+

+MultipartReader returns a MIME multipart reader if this is a +multipart/form-data POST request, else returns nil and an error. +Use this function instead of ParseMultipartForm to +process the request body as a stream. +

+ + + + + + +

func (*Request) ParseForm

+
func (r *Request) ParseForm() error
+

+ParseForm parses the raw query from the URL and updates r.Form. +

+

+For POST or PUT requests, it also parses the request body as a form and +put the results into both r.PostForm and r.Form. +POST and PUT body parameters take precedence over URL query string values +in r.Form. +

+

+If the request Body's size has not already been limited by MaxBytesReader, +the size is capped at 10MB. +

+

+ParseMultipartForm calls ParseForm automatically. +It is idempotent. +

+ + + + + + +

func (*Request) ParseMultipartForm

+
func (r *Request) ParseMultipartForm(maxMemory int64) error
+

+ParseMultipartForm parses a request body as multipart/form-data. +The whole request body is parsed and up to a total of maxMemory bytes of +its file parts are stored in memory, with the remainder stored on +disk in temporary files. +ParseMultipartForm calls ParseForm if necessary. +After one call to ParseMultipartForm, subsequent calls have no effect. +

+ + + + + + +

func (*Request) PostFormValue

+
func (r *Request) PostFormValue(key string) string
+

+PostFormValue returns the first value for the named component of the POST +or PUT request body. URL query parameters are ignored. +PostFormValue calls ParseMultipartForm and ParseForm if necessary and ignores +any errors returned by these functions. +If key is not present, PostFormValue returns the empty string. +

+ + + + + + +

func (*Request) ProtoAtLeast

+
func (r *Request) ProtoAtLeast(major, minor int) bool
+

+ProtoAtLeast reports whether the HTTP protocol used +in the request is at least major.minor. +

+ + + + + + +

func (*Request) Referer

+
func (r *Request) Referer() string
+

+Referer returns the referring URL, if sent in the request. +

+

+Referer is misspelled as in the request itself, a mistake from the +earliest days of HTTP. This value can also be fetched from the +Header map as Header["Referer"]; the benefit of making it available +as a method is that the compiler can diagnose programs that use the +alternate (correct English) spelling req.Referrer() but cannot +diagnose programs that use Header["Referrer"]. +

+ + + + + + +

func (*Request) SetBasicAuth

+
func (r *Request) SetBasicAuth(username, password string)
+

+SetBasicAuth sets the request's Authorization header to use HTTP +Basic Authentication with the provided username and password. +

+

+With HTTP Basic Authentication the provided username and password +are not encrypted. +

+ + + + + + +

func (*Request) UserAgent

+
func (r *Request) UserAgent() string
+

+UserAgent returns the client's User-Agent, if sent in the request. +

+ + + + + + +

func (*Request) Write

+
func (r *Request) Write(w io.Writer) error
+

+Write writes an HTTP/1.1 request, which is the header and body, in wire format. +This method consults the following fields of the request: +

+
Host
+URL
+Method (defaults to "GET")
+Header
+ContentLength
+TransferEncoding
+Body
+
+

+If Body is present, Content-Length is <= 0 and TransferEncoding +hasn't been set to "identity", Write adds "Transfer-Encoding: +chunked" to the header. Body is closed after it is sent. +

+ + + + + + +

func (*Request) WriteProxy

+
func (r *Request) WriteProxy(w io.Writer) error
+

+WriteProxy is like Write but writes the request in the form +expected by an HTTP proxy. In particular, WriteProxy writes the +initial Request-URI line of the request with an absolute URI, per +section 5.1.2 of RFC 2616, including the scheme and host. +In either case, WriteProxy also writes a Host header, using +either r.Host or r.URL.Host. +

+ + + + + + + + +

type Response

+
type Response struct {
+        Status     string // e.g. "200 OK"
+        StatusCode int    // e.g. 200
+        Proto      string // e.g. "HTTP/1.0"
+        ProtoMajor int    // e.g. 1
+        ProtoMinor int    // e.g. 0
+
+        // Header maps header keys to values.  If the response had multiple
+        // headers with the same key, they may be concatenated, with comma
+        // delimiters.  (Section 4.2 of RFC 2616 requires that multiple headers
+        // be semantically equivalent to a comma-delimited sequence.) Values
+        // duplicated by other fields in this struct (e.g., ContentLength) are
+        // omitted from Header.
+        //
+        // Keys in the map are canonicalized (see CanonicalHeaderKey).
+        Header Header
+
+        // Body represents the response body.
+        //
+        // The http Client and Transport guarantee that Body is always
+        // non-nil, even on responses without a body or responses with
+        // a zero-length body. It is the caller's responsibility to
+        // close Body. The default HTTP client's Transport does not
+        // attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections
+        // ("keep-alive") unless the Body is read to completion and is
+        // closed.
+        //
+        // The Body is automatically dechunked if the server replied
+        // with a "chunked" Transfer-Encoding.
+        Body io.ReadCloser
+
+        // ContentLength records the length of the associated content.  The
+        // value -1 indicates that the length is unknown.  Unless Request.Method
+        // is "HEAD", values >= 0 indicate that the given number of bytes may
+        // be read from Body.
+        ContentLength int64
+
+        // Contains transfer encodings from outer-most to inner-most. Value is
+        // nil, means that "identity" encoding is used.
+        TransferEncoding []string
+
+        // Close records whether the header directed that the connection be
+        // closed after reading Body.  The value is advice for clients: neither
+        // ReadResponse nor Response.Write ever closes a connection.
+        Close bool
+
+        // Trailer maps trailer keys to values, in the same
+        // format as the header.
+        Trailer Header
+
+        // The Request that was sent to obtain this Response.
+        // Request's Body is nil (having already been consumed).
+        // This is only populated for Client requests.
+        Request *Request
+
+        // TLS contains information about the TLS connection on which the
+        // response was received. It is nil for unencrypted responses.
+        // The pointer is shared between responses and should not be
+        // modified.
+        TLS *tls.ConnectionState
+}
+

+Response represents the response from an HTTP request. +

+ + + + + + + + + + + + +

func Get

+
func Get(url string) (resp *Response, err error)
+

+Get issues a GET to the specified URL. If the response is one of +the following redirect codes, Get follows the redirect, up to a +maximum of 10 redirects: +

+
301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+
+

+An error is returned if there were too many redirects or if there +was an HTTP protocol error. A non-2xx response doesn't cause an +error. +

+

+When err is nil, resp always contains a non-nil resp.Body. +Caller should close resp.Body when done reading from it. +

+

+Get is a wrapper around DefaultClient.Get. +

+

+To make a request with custom headers, use NewRequest and +DefaultClient.Do. +

+ +
+ +
+

Example

+ + + +
+
+
+
+ Run + Format + +
+
+ +
+
+ + + + + +
func Head(url string) (resp *Response, err error)
+

+Head issues a HEAD to the specified URL. If the response is one of +the following redirect codes, Head follows the redirect, up to a +maximum of 10 redirects: +

+
301 (Moved Permanently)
+302 (Found)
+303 (See Other)
+307 (Temporary Redirect)
+
+

+Head is a wrapper around DefaultClient.Head +

+ + + + + +

func Post

+
func Post(url string, bodyType string, body io.Reader) (resp *Response, err error)
+

+Post issues a POST to the specified URL. +

+

+Caller should close resp.Body when done reading from it. +

+

+If the provided body is an io.Closer, it is closed after the +request. +

+

+Post is a wrapper around DefaultClient.Post. +

+

+To set custom headers, use NewRequest and DefaultClient.Do. +

+ + + + + +

func PostForm

+
func PostForm(url string, data url.Values) (resp *Response, err error)
+

+PostForm issues a POST to the specified URL, with data's keys and +values URL-encoded as the request body. +

+

+The Content-Type header is set to application/x-www-form-urlencoded. +To set other headers, use NewRequest and DefaultClient.Do. +

+

+When err is nil, resp always contains a non-nil resp.Body. +Caller should close resp.Body when done reading from it. +

+

+PostForm is a wrapper around DefaultClient.PostForm. +

+ + + + + +

func ReadResponse

+
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
+

+ReadResponse reads and returns an HTTP response from r. +The req parameter optionally specifies the Request that corresponds +to this Response. If nil, a GET request is assumed. +Clients must call resp.Body.Close when finished reading resp.Body. +After that call, clients can inspect resp.Trailer to find key/value +pairs included in the response trailer. +

+ + + + + + + +

func (*Response) Cookies

+
func (r *Response) Cookies() []*Cookie
+

+Cookies parses and returns the cookies set in the Set-Cookie headers. +

+ + + + + + +

func (*Response) Location

+
func (r *Response) Location() (*url.URL, error)
+

+Location returns the URL of the response's "Location" header, +if present. Relative redirects are resolved relative to +the Response's Request. ErrNoLocation is returned if no +Location header is present. +

+ + + + + + +

func (*Response) ProtoAtLeast

+
func (r *Response) ProtoAtLeast(major, minor int) bool
+

+ProtoAtLeast reports whether the HTTP protocol used +in the response is at least major.minor. +

+ + + + + + +

func (*Response) Write

+
func (r *Response) Write(w io.Writer) error
+

+Write writes r to w in the HTTP/1.n server response format, +including the status line, headers, body, and optional trailer. +

+

+This method consults the following fields of the response r: +

+
StatusCode
+ProtoMajor
+ProtoMinor
+Request.Method
+TransferEncoding
+Trailer
+Body
+ContentLength
+Header, values for non-canonical keys will have unpredictable behavior
+
+

+The Response Body is closed after it is sent. +

+ + + + + + + + +

type ResponseWriter

+
type ResponseWriter interface {
+        // Header returns the header map that will be sent by
+        // WriteHeader. Changing the header after a call to
+        // WriteHeader (or Write) has no effect unless the modified
+        // headers were declared as trailers by setting the
+        // "Trailer" header before the call to WriteHeader (see example).
+        // To suppress implicit response headers, set their value to nil.
+        Header() Header
+
+        // Write writes the data to the connection as part of an HTTP reply.
+        // If WriteHeader has not yet been called, Write calls WriteHeader(http.StatusOK)
+        // before writing the data.  If the Header does not contain a
+        // Content-Type line, Write adds a Content-Type set to the result of passing
+        // the initial 512 bytes of written data to DetectContentType.
+        Write([]byte) (int, error)
+
+        // WriteHeader sends an HTTP response header with status code.
+        // If WriteHeader is not called explicitly, the first call to Write
+        // will trigger an implicit WriteHeader(http.StatusOK).
+        // Thus explicit calls to WriteHeader are mainly used to
+        // send error codes.
+        WriteHeader(int)
+}
+

+A ResponseWriter interface is used by an HTTP handler to +construct an HTTP response. +

+ + + + + + +
+ +
+

Example (Trailers)

+

HTTP Trailers are a set of key/value pairs like headers that come +after the HTTP response, instead of before. +

+ + +
+
+
+
+ Run + Format + +
+
+ +
+
+ + + + + + + + + + +

type RoundTripper

+
type RoundTripper interface {
+        // RoundTrip executes a single HTTP transaction, returning
+        // the Response for the request req.  RoundTrip should not
+        // attempt to interpret the response.  In particular,
+        // RoundTrip must return err == nil if it obtained a response,
+        // regardless of the response's HTTP status code.  A non-nil
+        // err should be reserved for failure to obtain a response.
+        // Similarly, RoundTrip should not attempt to handle
+        // higher-level protocol details such as redirects,
+        // authentication, or cookies.
+        //
+        // RoundTrip should not modify the request, except for
+        // consuming and closing the Body, including on errors. The
+        // request's URL and Header fields are guaranteed to be
+        // initialized.
+        RoundTrip(*Request) (*Response, error)
+}
+

+RoundTripper is an interface representing the ability to execute a +single HTTP transaction, obtaining the Response for a given Request. +

+

+A RoundTripper must be safe for concurrent use by multiple +goroutines. +

+ + + + + +
var DefaultTransport RoundTripper = &Transport{
+        Proxy: ProxyFromEnvironment,
+        Dial: (&net.Dialer{
+                Timeout:   30 * time.Second,
+                KeepAlive: 30 * time.Second,
+        }).Dial,
+        TLSHandshakeTimeout: 10 * time.Second,
+}
+

+DefaultTransport is the default implementation of Transport and is +used by DefaultClient. It establishes network connections as needed +and caches them for reuse by subsequent calls. It uses HTTP proxies +as directed by the $HTTP_PROXY and $NO_PROXY (or $http_proxy and +$no_proxy) environment variables. +

+ + + + + + + + + +

func NewFileTransport

+
func NewFileTransport(fs FileSystem) RoundTripper
+

+NewFileTransport returns a new RoundTripper, serving the provided +FileSystem. The returned RoundTripper ignores the URL host in its +incoming requests, as well as most other properties of the +request. +

+

+The typical use case for NewFileTransport is to register the "file" +protocol with a Transport, as in: +

+
t := &http.Transport{}
+t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
+c := &http.Client{Transport: t}
+res, err := c.Get("file:///etc/passwd")
+...
+
+ + + + + + + + + +

type ServeMux

+
type ServeMux struct {
+        // contains filtered or unexported fields
+}
+

+ServeMux is an HTTP request multiplexer. +It matches the URL of each incoming request against a list of registered +patterns and calls the handler for the pattern that +most closely matches the URL. +

+

+Patterns name fixed, rooted paths, like "/favicon.ico", +or rooted subtrees, like "/images/" (note the trailing slash). +Longer patterns take precedence over shorter ones, so that +if there are handlers registered for both "/images/" +and "/images/thumbnails/", the latter handler will be +called for paths beginning "/images/thumbnails/" and the +former will receive requests for any other paths in the +"/images/" subtree. +

+

+Note that since a pattern ending in a slash names a rooted subtree, +the pattern "/" matches all paths not matched by other registered +patterns, not just the URL with Path == "/". +

+

+Patterns may optionally begin with a host name, restricting matches to +URLs on that host only. Host-specific patterns take precedence over +general patterns, so that a handler might register for the two patterns +"/codesearch" and "codesearch.google.com/" without also taking over +requests for "http://www.google.com/". +

+

+ServeMux also takes care of sanitizing the URL request path, +redirecting any request containing . or .. elements to an +equivalent .- and ..-free URL. +

+ + + + + + + + + + + + +

func NewServeMux

+
func NewServeMux() *ServeMux
+

+NewServeMux allocates and returns a new ServeMux. +

+ + + + + + + +

func (*ServeMux) Handle

+
func (mux *ServeMux) Handle(pattern string, handler Handler)
+

+Handle registers the handler for the given pattern. +If a handler already exists for pattern, Handle panics. +

+ + +
+ +
+

Example

+ + + +

Code:

+
    mux := http.NewServeMux()
+    mux.Handle("/api/", apiHandler{})
+    mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
+            // The "/" pattern matches everything, so we need to check
+            // that we're at the root here.
+            if req.URL.Path != "/" {
+                    http.NotFound(w, req)
+                    return
+            }
+            fmt.Fprintf(w, "Welcome to the home page!")
+    })
+
+ + +
+
+ + + + +

func (*ServeMux) HandleFunc

+
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
+

+HandleFunc registers the handler function for the given pattern. +

+ + + + + + +

func (*ServeMux) Handler

+
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
+

+Handler returns the handler to use for the given request, +consulting r.Method, r.Host, and r.URL.Path. It always returns +a non-nil handler. If the path is not in its canonical form, the +handler will be an internally-generated handler that redirects +to the canonical path. +

+

+Handler also returns the registered pattern that matches the +request or, in the case of internally-generated redirects, +the pattern that will match after following the redirect. +

+

+If there is no registered handler that applies to the request, +Handler returns a “page not found” handler and an empty pattern. +

+ + + + + + +

func (*ServeMux) ServeHTTP

+
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)
+

+ServeHTTP dispatches the request to the handler whose +pattern most closely matches the request URL. +

+ + + + + + + + +

type Server

+
type Server struct {
+        Addr           string        // TCP address to listen on, ":http" if empty
+        Handler        Handler       // handler to invoke, http.DefaultServeMux if nil
+        ReadTimeout    time.Duration // maximum duration before timing out read of the request
+        WriteTimeout   time.Duration // maximum duration before timing out write of the response
+        MaxHeaderBytes int           // maximum size of request headers, DefaultMaxHeaderBytes if 0
+        TLSConfig      *tls.Config   // optional TLS config, used by ListenAndServeTLS
+
+        // TLSNextProto optionally specifies a function to take over
+        // ownership of the provided TLS connection when an NPN
+        // protocol upgrade has occurred.  The map key is the protocol
+        // name negotiated. The Handler argument should be used to
+        // handle HTTP requests and will initialize the Request's TLS
+        // and RemoteAddr if not already set.  The connection is
+        // automatically closed when the function returns.
+        TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
+
+        // ConnState specifies an optional callback function that is
+        // called when a client connection changes state. See the
+        // ConnState type and associated constants for details.
+        ConnState func(net.Conn, ConnState)
+
+        // ErrorLog specifies an optional logger for errors accepting
+        // connections and unexpected behavior from handlers.
+        // If nil, logging goes to os.Stderr via the log package's
+        // standard logger.
+        ErrorLog *log.Logger
+        // contains filtered or unexported fields
+}
+

+A Server defines parameters for running an HTTP server. +The zero value for Server is a valid configuration. +

+ + + + + + + + + + + + + + +

func (*Server) ListenAndServe

+
func (srv *Server) ListenAndServe() error
+

+ListenAndServe listens on the TCP network address srv.Addr and then +calls Serve to handle requests on incoming connections. If +srv.Addr is blank, ":http" is used. +

+ + + + + + +

func (*Server) ListenAndServeTLS

+
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error
+

+ListenAndServeTLS listens on the TCP network address srv.Addr and +then calls Serve to handle requests on incoming TLS connections. +

+

+Filenames containing a certificate and matching private key for the +server must be provided if the Server's TLSConfig.Certificates is +not populated. If the certificate is signed by a certificate +authority, the certFile should be the concatenation of the server's +certificate, any intermediates, and the CA's certificate. +

+

+If srv.Addr is blank, ":https" is used. +

+ + + + + + +

func (*Server) Serve

+
func (srv *Server) Serve(l net.Listener) error
+

+Serve accepts incoming connections on the Listener l, creating a +new service goroutine for each. The service goroutines read requests and +then call srv.Handler to reply to them. +

+ + + + + + +

func (*Server) SetKeepAlivesEnabled

+
func (srv *Server) SetKeepAlivesEnabled(v bool)
+

+SetKeepAlivesEnabled controls whether HTTP keep-alives are enabled. +By default, keep-alives are always enabled. Only very +resource-constrained environments or servers in the process of +shutting down should disable them. +

+ + + + + + + + +

type Transport

+
type Transport struct {
+
+        // Proxy specifies a function to return a proxy for a given
+        // Request. If the function returns a non-nil error, the
+        // request is aborted with the provided error.
+        // If Proxy is nil or returns a nil *URL, no proxy is used.
+        Proxy func(*Request) (*url.URL, error)
+
+        // Dial specifies the dial function for creating unencrypted
+        // TCP connections.
+        // If Dial is nil, net.Dial is used.
+        Dial func(network, addr string) (net.Conn, error)
+
+        // DialTLS specifies an optional dial function for creating
+        // TLS connections for non-proxied HTTPS requests.
+        //
+        // If DialTLS is nil, Dial and TLSClientConfig are used.
+        //
+        // If DialTLS is set, the Dial hook is not used for HTTPS
+        // requests and the TLSClientConfig and TLSHandshakeTimeout
+        // are ignored. The returned net.Conn is assumed to already be
+        // past the TLS handshake.
+        DialTLS func(network, addr string) (net.Conn, error)
+
+        // TLSClientConfig specifies the TLS configuration to use with
+        // tls.Client. If nil, the default configuration is used.
+        TLSClientConfig *tls.Config
+
+        // TLSHandshakeTimeout specifies the maximum amount of time waiting to
+        // wait for a TLS handshake. Zero means no timeout.
+        TLSHandshakeTimeout time.Duration
+
+        // DisableKeepAlives, if true, prevents re-use of TCP connections
+        // between different HTTP requests.
+        DisableKeepAlives bool
+
+        // DisableCompression, if true, prevents the Transport from
+        // requesting compression with an "Accept-Encoding: gzip"
+        // request header when the Request contains no existing
+        // Accept-Encoding value. If the Transport requests gzip on
+        // its own and gets a gzipped response, it's transparently
+        // decoded in the Response.Body. However, if the user
+        // explicitly requested gzip it is not automatically
+        // uncompressed.
+        DisableCompression bool
+
+        // MaxIdleConnsPerHost, if non-zero, controls the maximum idle
+        // (keep-alive) to keep per-host.  If zero,
+        // DefaultMaxIdleConnsPerHost is used.
+        MaxIdleConnsPerHost int
+
+        // ResponseHeaderTimeout, if non-zero, specifies the amount of
+        // time to wait for a server's response headers after fully
+        // writing the request (including its body, if any). This
+        // time does not include the time to read the response body.
+        ResponseHeaderTimeout time.Duration
+        // contains filtered or unexported fields
+}
+

+Transport is an implementation of RoundTripper that supports HTTP, +HTTPS, and HTTP proxies (for either HTTP or HTTPS with CONNECT). +Transport can also cache connections for future re-use. +

+ + + + + + + + + + + + + + +

func (*Transport) CancelRequest

+
func (t *Transport) CancelRequest(req *Request)
+

+CancelRequest cancels an in-flight request by closing its connection. +CancelRequest should only be called after RoundTrip has returned. +

+ + + + + + +

func (*Transport) CloseIdleConnections

+
func (t *Transport) CloseIdleConnections()
+

+CloseIdleConnections closes any connections which were previously +connected from previous requests but are now sitting idle in +a "keep-alive" state. It does not interrupt any connections currently +in use. +

+ + + + + + +

func (*Transport) RegisterProtocol

+
func (t *Transport) RegisterProtocol(scheme string, rt RoundTripper)
+

+RegisterProtocol registers a new protocol with scheme. +The Transport will pass requests using the given scheme to rt. +It is rt's responsibility to simulate HTTP request semantics. +

+

+RegisterProtocol can be used by other packages to provide +implementations of protocol schemes like "ftp" or "file". +

+ + + + + + +

func (*Transport) RoundTrip

+
func (t *Transport) RoundTrip(req *Request) (resp *Response, err error)
+

+RoundTrip implements the RoundTripper interface. +

+

+For higher-level HTTP client support (such as handling of cookies +and redirects), see Get, Post, and the Client type. +

+ + + + + + + + + + + + + + + + +

Subdirectories

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameSynopsis
..
+ cgi + + Package cgi implements CGI (Common Gateway Interface) as specified in RFC 3875. +
+ cookiejar + + Package cookiejar implements an in-memory RFC 6265-compliant http.CookieJar. +
+ fcgi + + Package fcgi implements the FastCGI protocol. +
+ httptest + + Package httptest provides utilities for HTTP testing. +
+ httputil + + Package httputil provides HTTP utility functions, complementing the more common ones in the net/http package. +
+ pprof + + Package pprof serves via its HTTP server runtime profiling data in the format expected by the pprof visualization tool. +
+
+ + + + + + + + +
+
+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/append_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/append_object.go new file mode 100644 index 000000000..4e7f8a821 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/append_object.go @@ -0,0 +1,156 @@ +// Package sample examples +package sample + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strconv" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// AppendObjectSample shows the append file's usage +func AppendObjectSample() { + // Create bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + err = bucket.DeleteObject(objectKey) + + var str = "弃我去者,昨日之日不可留。 乱我心者,今日之日多烦忧!" + var nextPos int64 + + // Case 1: Append a string to the object + // The first append position is 0 and the return value is for the next append's position. + nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos) + if err != nil { + HandleError(err) + } + + // Second append + nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos) + if err != nil { + HandleError(err) + } + + // Download + body, err := bucket.GetObject(objectKey) + if err != nil { + HandleError(err) + } + data, err := ioutil.ReadAll(body) + body.Close() + if err != nil { + HandleError(err) + } + fmt.Println(objectKey, ":", string(data)) + + err = bucket.DeleteObject(objectKey) + if err != nil { + HandleError(err) + } + + // Case 2: Append byte array to the object + nextPos = 0 + // The first append position is 0, and the return value is for the next append's position. + nextPos, err = bucket.AppendObject(objectKey, bytes.NewReader([]byte(str)), nextPos) + if err != nil { + HandleError(err) + } + + // Second append + nextPos, err = bucket.AppendObject(objectKey, bytes.NewReader([]byte(str)), nextPos) + if err != nil { + HandleError(err) + } + + // Download + body, err = bucket.GetObject(objectKey) + if err != nil { + HandleError(err) + } + data, err = ioutil.ReadAll(body) + body.Close() + if err != nil { + HandleError(err) + } + fmt.Println(objectKey, ":", string(data)) + + err = bucket.DeleteObject(objectKey) + if err != nil { + HandleError(err) + } + + // Case 3: Append a local file to the object + fd, err := os.Open(localFile) + if err != nil { + HandleError(err) + } + defer fd.Close() + + nextPos = 0 + nextPos, err = bucket.AppendObject(objectKey, fd, nextPos) + if err != nil { + HandleError(err) + } + + // Case 4: Get the next append position by GetObjectDetailedMeta + props, err := bucket.GetObjectDetailedMeta(objectKey) + nextPos, err = strconv.ParseInt(props.Get(oss.HTTPHeaderOssNextAppendPosition), 10, 0) + if err != nil { + HandleError(err) + } + + nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos) + if err != nil { + HandleError(err) + } + + err = bucket.DeleteObject(objectKey) + if err != nil { + HandleError(err) + } + + // Case 5: Specify the object properties for the first append, including the "x-oss-meta"'s custom metadata. + options := []oss.Option{ + oss.Expires(futureDate), + oss.ObjectACL(oss.ACLPublicRead), + oss.Meta("myprop", "mypropval")} + nextPos = 0 + fd.Seek(0, os.SEEK_SET) + nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos, options...) + if err != nil { + HandleError(err) + } + // Second append + fd.Seek(0, os.SEEK_SET) + nextPos, err = bucket.AppendObject(objectKey, strings.NewReader(str), nextPos) + if err != nil { + HandleError(err) + } + + props, err = bucket.GetObjectDetailedMeta(objectKey) + if err != nil { + HandleError(err) + } + fmt.Println("myprop:", props.Get("x-oss-meta-myprop")) + + goar, err := bucket.GetObjectACL(objectKey) + if err != nil { + HandleError(err) + } + fmt.Println("Object ACL:", goar.ACL) + + // Delete the object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("AppendObjectSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/archive.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/archive.go new file mode 100644 index 000000000..1002ef5f0 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/archive.go @@ -0,0 +1,74 @@ +package sample + +import ( + "fmt" + "strings" + "time" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// ArchiveSample archives sample +func ArchiveSample() { + // Create archive bucket + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + HandleError(err) + } + + err = client.CreateBucket(bucketName, oss.StorageClass(oss.StorageArchive)) + if err != nil { + HandleError(err) + } + + archiveBucket, err := client.Bucket(bucketName) + if err != nil { + HandleError(err) + } + + // Put archive object + var val = "花间一壶酒,独酌无相亲。 举杯邀明月,对影成三人。" + err = archiveBucket.PutObject(objectKey, strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + // Check whether the object is archive class + meta, err := archiveBucket.GetObjectDetailedMeta(objectKey) + if err != nil { + HandleError(err) + } + + if meta.Get("X-Oss-Storage-Class") == string(oss.StorageArchive) { + // Restore object + err = archiveBucket.RestoreObject(objectKey) + if err != nil { + HandleError(err) + } + + // Wait for restore completed + meta, err = archiveBucket.GetObjectDetailedMeta(objectKey) + for meta.Get("X-Oss-Restore") == "ongoing-request=\"true\"" { + fmt.Println("x-oss-restore:" + meta.Get("X-Oss-Restore")) + time.Sleep(1000 * time.Second) + meta, err = archiveBucket.GetObjectDetailedMeta(objectKey) + } + } + + // Get restored object + err = archiveBucket.GetObjectToFile(objectKey, localFile) + if err != nil { + HandleError(err) + } + + // Restore repeatedly + err = archiveBucket.RestoreObject(objectKey) + + // Delete object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("ArchiveSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_acl.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_acl.go new file mode 100644 index 000000000..92c78c759 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_acl.go @@ -0,0 +1,43 @@ +package sample + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// BucketACLSample shows how to get and set the bucket ACL +func BucketACLSample() { + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + HandleError(err) + } + + // Create a bucket with default parameters + err = client.CreateBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Set bucket ACL. The valid ACLs are ACLPrivate、ACLPublicRead、ACLPublicReadWrite + err = client.SetBucketACL(bucketName, oss.ACLPublicRead) + if err != nil { + HandleError(err) + } + + // Get bucket ACL + gbar, err := client.GetBucketACL(bucketName) + if err != nil { + HandleError(err) + } + fmt.Println("Bucket ACL:", gbar.ACL) + + // Delete the bucket + err = client.DeleteBucket(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("BucketACLSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_cors.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_cors.go new file mode 100644 index 000000000..7af95e6dd --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_cors.go @@ -0,0 +1,71 @@ +package sample + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// BucketCORSSample shows how to get or set the bucket CORS. +func BucketCORSSample() { + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + HandleError(err) + } + + // Create the bucket with default parameters + err = client.CreateBucket(bucketName) + if err != nil { + HandleError(err) + } + + rule1 := oss.CORSRule{ + AllowedOrigin: []string{"*"}, + AllowedMethod: []string{"PUT", "GET", "POST"}, + AllowedHeader: []string{}, + ExposeHeader: []string{}, + MaxAgeSeconds: 100, + } + + rule2 := oss.CORSRule{ + AllowedOrigin: []string{"http://www.a.com", "http://www.b.com"}, + AllowedMethod: []string{"GET"}, + AllowedHeader: []string{"Authorization"}, + ExposeHeader: []string{"x-oss-test", "x-oss-test1"}, + MaxAgeSeconds: 100, + } + + // Case 1: Set the bucket CORS rules + err = client.SetBucketCORS(bucketName, []oss.CORSRule{rule1}) + if err != nil { + HandleError(err) + } + + // Case 2: Set the bucket CORS rules. if CORS rules exist, they will be overwritten. + err = client.SetBucketCORS(bucketName, []oss.CORSRule{rule1, rule2}) + if err != nil { + HandleError(err) + } + + // Get the bucket's CORS + gbl, err := client.GetBucketCORS(bucketName) + if err != nil { + HandleError(err) + } + fmt.Println("Bucket CORS:", gbl.CORSRules) + + // Delete bucket's CORS + err = client.DeleteBucketCORS(bucketName) + if err != nil { + HandleError(err) + } + + // Delete bucket + err = client.DeleteBucket(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("BucketCORSSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_lifecycle.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_lifecycle.go new file mode 100644 index 000000000..02cf9efbc --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_lifecycle.go @@ -0,0 +1,68 @@ +package sample + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// BucketLifecycleSample shows how to set, get and delete bucket's lifecycle. +func BucketLifecycleSample() { + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + HandleError(err) + } + + // Create the bucket with default parameters + err = client.CreateBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Case 1: Set the lifecycle. The rule ID is id1 and the applied objects' prefix is one and expired time is 11/11/2015 + var rule1 = oss.BuildLifecycleRuleByDate("id1", "one", true, 2015, 11, 11) + var rules = []oss.LifecycleRule{rule1} + err = client.SetBucketLifecycle(bucketName, rules) + if err != nil { + HandleError(err) + } + + // Case 2: Set the lifecycle, The rule ID is id2 and the applied objects' prefix is two and the expired time is three days after the object created. + var rule2 = oss.BuildLifecycleRuleByDays("id2", "two", true, 3) + rules = []oss.LifecycleRule{rule2} + err = client.SetBucketLifecycle(bucketName, rules) + if err != nil { + HandleError(err) + } + + // Case 3: Create two rules in the bucket for different objects. The rule with the same ID will be overwritten. + var rule3 = oss.BuildLifecycleRuleByDays("id1", "two", true, 365) + var rule4 = oss.BuildLifecycleRuleByDate("id2", "one", true, 2016, 11, 11) + rules = []oss.LifecycleRule{rule3, rule4} + err = client.SetBucketLifecycle(bucketName, rules) + if err != nil { + HandleError(err) + } + + // Get the bucket's lifecycle + gbl, err := client.GetBucketLifecycle(bucketName) + if err != nil { + HandleError(err) + } + fmt.Println("Bucket Lifecycle:", gbl.Rules) + + // Delete bucket's Lifecycle + err = client.DeleteBucketLifecycle(bucketName) + if err != nil { + HandleError(err) + } + + // Delete bucket + err = client.DeleteBucket(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("BucketLifecycleSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_logging.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_logging.go new file mode 100644 index 000000000..3a7ad1bd9 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_logging.go @@ -0,0 +1,90 @@ +package sample + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// BucketLoggingSample shows how to set, get and delete the bucket logging configuration +func BucketLoggingSample() { + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + HandleError(err) + } + + // Create the bucket with default parameters + err = client.CreateBucket(bucketName) + if err != nil { + HandleError(err) + } + // Create target bucket to store the logging files. + var targetBucketName = "target-bucket" + err = client.CreateBucket(targetBucketName) + if err != nil { + HandleError(err) + } + + // Case 1: Set the logging for the object prefixed with "prefix-1" and save their access logs to the target bucket + err = client.SetBucketLogging(bucketName, targetBucketName, "prefix-1", true) + if err != nil { + HandleError(err) + } + + // Case 2: Set the logging for the object prefixed with "prefix-2" and save their logs to the same bucket + // Note: the rule will overwrite other rules if they have same bucket and prefix + err = client.SetBucketLogging(bucketName, bucketName, "prefix-2", true) + if err != nil { + HandleError(err) + } + + // Delete the bucket's logging configuration + err = client.DeleteBucketLogging(bucketName) + if err != nil { + HandleError(err) + } + + // Case 3: Set the logging without enabling it + err = client.SetBucketLogging(bucketName, targetBucketName, "prefix-3", false) + if err != nil { + HandleError(err) + } + + // Get the bucket's logging configuration + gbl, err := client.GetBucketLogging(bucketName) + if err != nil { + HandleError(err) + } + fmt.Println("Bucket Logging:", gbl.LoggingEnabled) + + err = client.SetBucketLogging(bucketName, bucketName, "prefix2", true) + if err != nil { + HandleError(err) + } + + // Get the bucket's logging configuration + gbl, err = client.GetBucketLogging(bucketName) + if err != nil { + HandleError(err) + } + fmt.Println("Bucket Logging:", gbl.LoggingEnabled) + + // Delete the bucket's logging configuration + err = client.DeleteBucketLogging(bucketName) + if err != nil { + HandleError(err) + } + + // Delete bucket + err = client.DeleteBucket(bucketName) + if err != nil { + HandleError(err) + } + err = client.DeleteBucket(targetBucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("BucketLoggingSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_referer.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_referer.go new file mode 100644 index 000000000..7d1e4db15 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/bucket_referer.go @@ -0,0 +1,57 @@ +package sample + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// BucketRefererSample shows how to set, get and delete the bucket referer. +func BucketRefererSample() { + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + HandleError(err) + } + + // Create the bucket with default parameters + err = client.CreateBucket(bucketName) + if err != nil { + HandleError(err) + } + + var referers = []string{ + "http://www.aliyun.com", + "http://www.???.aliyuncs.com", + "http://www.*.com", + } + + // Case 1: Set referers. The referers are with wildcards ? and * which could represent one and zero to multiple characters + err = client.SetBucketReferer(bucketName, referers, false) + if err != nil { + HandleError(err) + } + + // Case 2: Clear referers + referers = []string{} + err = client.SetBucketReferer(bucketName, referers, true) + if err != nil { + HandleError(err) + } + + // Get bucket referer configuration + gbr, err := client.GetBucketReferer(bucketName) + if err != nil { + HandleError(err) + } + fmt.Println("Bucket Referers:", gbr.RefererList, + "AllowEmptyReferer:", gbr.AllowEmptyReferer) + + // Delete bucket + err = client.DeleteBucket(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("BucketRefererSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/cname_sample.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/cname_sample.go new file mode 100644 index 000000000..e25823617 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/cname_sample.go @@ -0,0 +1,96 @@ +package sample + +import ( + "fmt" + "io/ioutil" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// CnameSample shows the cname usage +func CnameSample() { + // New client + client, err := oss.New(endpoint4Cname, accessID4Cname, accessKey4Cname, + oss.UseCname(true)) + if err != nil { + HandleError(err) + } + + // Create bucket + err = client.CreateBucket(bucketName4Cname) + if err != nil { + HandleError(err) + } + + // Set bucket ACL + err = client.SetBucketACL(bucketName4Cname, oss.ACLPrivate) + if err != nil { + HandleError(err) + } + + // Look up bucket ACL + gbar, err := client.GetBucketACL(bucketName4Cname) + if err != nil { + HandleError(err) + } + fmt.Println("Bucket ACL:", gbar.ACL) + + // List buckets, the list operation could not be done by cname's endpoint + _, err = client.ListBuckets() + if err == nil { + HandleError(err) + } + + bucket, err := client.Bucket(bucketName4Cname) + if err != nil { + HandleError(err) + } + + objectValue := "长忆观潮, 满郭人争江上望。来疑沧海尽成空, 万面鼓声中。弄潮儿向涛头立, 手把红旗旗不湿。别来几向梦中看, 梦觉尚心寒。" + + // Put object + err = bucket.PutObject(objectKey, strings.NewReader(objectValue)) + if err != nil { + HandleError(err) + } + + // Get object + body, err := bucket.GetObject(objectKey) + if err != nil { + HandleError(err) + } + data, err := ioutil.ReadAll(body) + body.Close() + if err != nil { + HandleError(err) + } + fmt.Println(objectKey, ":", string(data)) + + // Put object from file + err = bucket.PutObjectFromFile(objectKey, localFile) + if err != nil { + HandleError(err) + } + + // Get object to file + err = bucket.GetObjectToFile(objectKey, newPicName) + if err != nil { + HandleError(err) + } + + // List objects + lor, err := bucket.ListObjects() + if err != nil { + HandleError(err) + } + fmt.Println("objects:", lor.Objects) + + // Delete object + err = bucket.DeleteObject(objectKey) + if err != nil { + HandleError(err) + } + + fmt.Println("CnameSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/comm.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/comm.go new file mode 100644 index 000000000..b641d480e --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/comm.go @@ -0,0 +1,167 @@ +package sample + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +var ( + pastDate = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC) + futureDate = time.Date(2049, time.January, 10, 23, 0, 0, 0, time.UTC) +) + +// HandleError is the error handling method in the sample code +func HandleError(err error) { + fmt.Println("occurred error:", err) + os.Exit(-1) +} + +// GetTestBucket creates the test bucket +func GetTestBucket(bucketName string) (*oss.Bucket, error) { + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + return nil, err + } + + // Create bucket + err = client.CreateBucket(bucketName) + if err != nil { + return nil, err + } + + // Get bucket + bucket, err := client.Bucket(bucketName) + if err != nil { + return nil, err + } + + return bucket, nil +} + +// DeleteTestBucketAndLiveChannel 删除sample的channelname和bucket,该函数为了简化sample,让sample代码更明了 +func DeleteTestBucketAndLiveChannel(bucketName string) error { + // New Client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + return err + } + + // Get Bucket + bucket, err := client.Bucket(bucketName) + if err != nil { + return err + } + + marker := "" + for { + result, err := bucket.ListLiveChannel(oss.Marker(marker)) + if err != nil { + HandleError(err) + } + + for _, channel := range result.LiveChannel { + err := bucket.DeleteLiveChannel(channel.Name) + if err != nil { + HandleError(err) + } + } + + if result.IsTruncated { + marker = result.NextMarker + } else { + break + } + } + + // Delete Bucket + err = client.DeleteBucket(bucketName) + if err != nil { + return err + } + + return nil +} + +// DeleteTestBucketAndObject deletes the test bucket and its objects +func DeleteTestBucketAndObject(bucketName string) error { + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + return err + } + + // Get bucket + bucket, err := client.Bucket(bucketName) + if err != nil { + return err + } + + // Delete part + lmur, err := bucket.ListMultipartUploads() + if err != nil { + return err + } + + for _, upload := range lmur.Uploads { + var imur = oss.InitiateMultipartUploadResult{Bucket: bucket.BucketName, + Key: upload.Key, UploadID: upload.UploadID} + err = bucket.AbortMultipartUpload(imur) + if err != nil { + return err + } + } + + // Delete objects + lor, err := bucket.ListObjects() + if err != nil { + return err + } + + for _, object := range lor.Objects { + err = bucket.DeleteObject(object.Key) + if err != nil { + return err + } + } + + // Delete bucket + err = client.DeleteBucket(bucketName) + if err != nil { + return err + } + + return nil +} + +// Object defines pair of key and value +type Object struct { + Key string + Value string +} + +// CreateObjects creates some objects +func CreateObjects(bucket *oss.Bucket, objects []Object) error { + for _, object := range objects { + err := bucket.PutObject(object.Key, strings.NewReader(object.Value)) + if err != nil { + return err + } + } + return nil +} + +// DeleteObjects deletes some objects. +func DeleteObjects(bucket *oss.Bucket, objects []Object) error { + for _, object := range objects { + err := bucket.DeleteObject(object.Key) + if err != nil { + return err + } + } + return nil +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/config.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/config.go new file mode 100644 index 000000000..74825bd10 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/config.go @@ -0,0 +1,25 @@ +package sample + +const ( + // Sample code's env configuration. You need to specify them with the actual configuration if you want to run sample code + endpoint string = "" + accessID string = "" + accessKey string = "" + bucketName string = "" + kmsID string = "" + + // The cname endpoint + // These information are required to run sample/cname_sample + endpoint4Cname string = "" + accessID4Cname string = "" + accessKey4Cname string = "" + bucketName4Cname string = "" + + // The object name in the sample code + objectKey string = "my-object" + + // The local files to run sample code. + localFile string = "src/sample/BingWallpaper-2015-11-07.jpg" + htmlLocalFile string = "src/sample/The Go Programming Language.html" + newPicName string = "src/sample/NewBingWallpaper-2015-11-07.jpg" +) diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/copy_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/copy_object.go new file mode 100644 index 000000000..6c15be570 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/copy_object.go @@ -0,0 +1,114 @@ +package sample + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// CopyObjectSample shows the copy files usage +func CopyObjectSample() { + // Create a bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Create an object + err = bucket.PutObjectFromFile(objectKey, localFile) + if err != nil { + HandleError(err) + } + + // Case 1: Copy an existing object + var descObjectKey = "descobject" + _, err = bucket.CopyObject(objectKey, descObjectKey) + if err != nil { + HandleError(err) + } + + // Case 2: Copy an existing object to another existing object + _, err = bucket.CopyObject(objectKey, descObjectKey) + if err != nil { + HandleError(err) + } + + err = bucket.DeleteObject(descObjectKey) + if err != nil { + HandleError(err) + } + + // Case 3: Copy file with constraints. When the constraints are met, the copy executes. otherwise the copy does not execute. + // constraints are not met, copy does not execute + _, err = bucket.CopyObject(objectKey, descObjectKey, oss.CopySourceIfModifiedSince(futureDate)) + if err == nil { + HandleError(err) + } + fmt.Println("CopyObjectError:", err) + // Constraints are met, the copy executes + _, err = bucket.CopyObject(objectKey, descObjectKey, oss.CopySourceIfUnmodifiedSince(futureDate)) + if err != nil { + HandleError(err) + } + + // Case 4: Specify the properties when copying. The MetadataDirective needs to be MetaReplace + options := []oss.Option{ + oss.Expires(futureDate), + oss.Meta("myprop", "mypropval"), + oss.MetadataDirective(oss.MetaReplace)} + _, err = bucket.CopyObject(objectKey, descObjectKey, options...) + if err != nil { + HandleError(err) + } + + meta, err := bucket.GetObjectDetailedMeta(descObjectKey) + if err != nil { + HandleError(err) + } + fmt.Println("meta:", meta) + + // Case 5: When the source file is the same as the target file, the copy could be used to update metadata + options = []oss.Option{ + oss.Expires(futureDate), + oss.Meta("myprop", "mypropval"), + oss.MetadataDirective(oss.MetaReplace)} + + _, err = bucket.CopyObject(objectKey, objectKey, options...) + if err != nil { + HandleError(err) + } + fmt.Println("meta:", meta) + + // Case 6: Big file's multipart copy. It supports concurrent copy with resumable upload + // copy file with multipart. The part size is 100K. By default one routine is used without resumable upload + err = bucket.CopyFile(bucketName, objectKey, descObjectKey, 100*1024) + if err != nil { + HandleError(err) + } + + // Part size is 100K and three coroutines for the concurrent copy + err = bucket.CopyFile(bucketName, objectKey, descObjectKey, 100*1024, oss.Routines(3)) + if err != nil { + HandleError(err) + } + + // Part size is 100K and three coroutines for the concurrent copy with resumable upload + err = bucket.CopyFile(bucketName, objectKey, descObjectKey, 100*1024, oss.Routines(3), oss.Checkpoint(true, "")) + if err != nil { + HandleError(err) + } + + // Specify the checkpoint file path. If the checkpoint file path is not specified, the current folder is used. + err = bucket.CopyFile(bucketName, objectKey, descObjectKey, 100*1024, oss.Checkpoint(true, localFile+".cp")) + if err != nil { + HandleError(err) + } + + // Delete object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("CopyObjectSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/create_bucket.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/create_bucket.go new file mode 100644 index 000000000..976e5c5f8 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/create_bucket.go @@ -0,0 +1,50 @@ +package sample + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// CreateBucketSample shows how to create bucket +func CreateBucketSample() { + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + HandleError(err) + } + + DeleteTestBucketAndObject(bucketName) + + // Case 1: Create a bucket with default parameters + err = client.CreateBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Delete bucket + err = client.DeleteBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Case 2: Create the bucket with ACL + err = client.CreateBucket(bucketName, oss.ACL(oss.ACLPublicRead)) + if err != nil { + HandleError(err) + } + + // Case 3: Repeat the same bucket. OSS will not return error, but just no op. The ACL is not updated. + err = client.CreateBucket(bucketName, oss.ACL(oss.ACLPublicReadWrite)) + if err != nil { + HandleError(err) + } + + // Delete bucket + err = client.DeleteBucket(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("CreateBucketSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/delete_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/delete_object.go new file mode 100644 index 000000000..d4d898e09 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/delete_object.go @@ -0,0 +1,108 @@ +package sample + +import ( + "fmt" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// DeleteObjectSample shows how to delete single file or multiple files +func DeleteObjectSample() { + // Create a bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + var val = "抽刀断水水更流,举杯销愁愁更愁。 人生在世不称意,明朝散发弄扁舟。" + + // Case 1: Delete an object + err = bucket.PutObject(objectKey, strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + err = bucket.DeleteObject(objectKey) + if err != nil { + HandleError(err) + } + + // Case 2: Delete multiple Objects + err = bucket.PutObject(objectKey+"1", strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + err = bucket.PutObject(objectKey+"2", strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + delRes, err := bucket.DeleteObjects([]string{objectKey + "1", objectKey + "2"}) + if err != nil { + HandleError(err) + } + fmt.Println("Del Res:", delRes) + + lsRes, err := bucket.ListObjects() + if err != nil { + HandleError(err) + } + fmt.Println("Objects:", getObjectsFormResponse(lsRes)) + + // Case 3: Delete multiple objects and it will return deleted objects in detail mode which is by default. + err = bucket.PutObject(objectKey+"1", strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + err = bucket.PutObject(objectKey+"2", strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + delRes, err = bucket.DeleteObjects([]string{objectKey + "1", objectKey + "2"}, + oss.DeleteObjectsQuiet(false)) + if err != nil { + HandleError(err) + } + fmt.Println("Detail Del Res:", delRes) + + lsRes, err = bucket.ListObjects() + if err != nil { + HandleError(err) + } + fmt.Println("Objects:", getObjectsFormResponse(lsRes)) + + // Case 4: Delete multiple objects and returns undeleted objects in quiet mode + err = bucket.PutObject(objectKey+"1", strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + err = bucket.PutObject(objectKey+"2", strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + delRes, err = bucket.DeleteObjects([]string{objectKey + "1", objectKey + "2"}, oss.DeleteObjectsQuiet(true)) + if err != nil { + HandleError(err) + } + fmt.Println("Sample Del Res:", delRes) + + lsRes, err = bucket.ListObjects() + if err != nil { + HandleError(err) + } + fmt.Println("Objects:", getObjectsFormResponse(lsRes)) + + // Delete object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("DeleteObjectSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/get_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/get_object.go new file mode 100644 index 000000000..c668a24e6 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/get_object.go @@ -0,0 +1,143 @@ +package sample + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// GetObjectSample shows the streaming download, range download and resumable download. +func GetObjectSample() { + // Create bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Upload the object + err = bucket.PutObjectFromFile(objectKey, localFile) + if err != nil { + HandleError(err) + } + + // Case 1: Download the object into ReadCloser(). The body needs to be closed + body, err := bucket.GetObject(objectKey) + if err != nil { + HandleError(err) + } + data, err := ioutil.ReadAll(body) + body.Close() + if err != nil { + HandleError(err) + } + data = data // use data + + // Case 2: Download the object to byte array. This is for small object. + buf := new(bytes.Buffer) + body, err = bucket.GetObject(objectKey) + if err != nil { + HandleError(err) + } + io.Copy(buf, body) + body.Close() + + // Case 3: Download the object to local file. The file handle needs to be specified + fd, err := os.OpenFile("mynewfile-1.jpg", os.O_WRONLY|os.O_CREATE, 0660) + if err != nil { + HandleError(err) + } + defer fd.Close() + + body, err = bucket.GetObject(objectKey) + if err != nil { + HandleError(err) + } + io.Copy(fd, body) + body.Close() + + // Case 4: Download the object to local file with file name specified + err = bucket.GetObjectToFile(objectKey, "mynewfile-2.jpg") + if err != nil { + HandleError(err) + } + + // Case 5: Get the object with contraints. When contraints are met, download the file. Otherwise return precondition error + // last modified time constraint is met, download the file + body, err = bucket.GetObject(objectKey, oss.IfModifiedSince(pastDate)) + if err != nil { + HandleError(err) + } + body.Close() + // Last modified time contraint is not met, do not download the file + _, err = bucket.GetObject(objectKey, oss.IfUnmodifiedSince(pastDate)) + if err == nil { + HandleError(err) + } + + meta, err := bucket.GetObjectDetailedMeta(objectKey) + if err != nil { + HandleError(err) + } + etag := meta.Get(oss.HTTPHeaderEtag) + // Check the content, etag contraint is met, download the file + body, err = bucket.GetObject(objectKey, oss.IfMatch(etag)) + if err != nil { + HandleError(err) + } + body.Close() + + // Check the content, etag contraint is not met, do not download the file + body, err = bucket.GetObject(objectKey, oss.IfNoneMatch(etag)) + if err == nil { + HandleError(err) + } + + // Case 6: Big file's multipart download, concurrent and resumable download is supported. + // multipart download with part size 100KB. By default single coroutine is used and no checkpoint + err = bucket.DownloadFile(objectKey, "mynewfile-3.jpg", 100*1024) + if err != nil { + HandleError(err) + } + + // Part size is 100K and 3 coroutines are used + err = bucket.DownloadFile(objectKey, "mynewfile-3.jpg", 100*1024, oss.Routines(3)) + if err != nil { + HandleError(err) + } + + // Part size is 100K and 3 coroutines with checkpoint + err = bucket.DownloadFile(objectKey, "mynewfile-3.jpg", 100*1024, oss.Routines(3), oss.Checkpoint(true, "")) + if err != nil { + HandleError(err) + } + + // Specify the checkpoint file path to record which parts have been downloaded. + // This file path can be specified by the 2nd parameter of Checkpoint, it will be the download directory if the file path is empty. + err = bucket.DownloadFile(objectKey, "mynewfile-3.jpg", 100*1024, oss.Checkpoint(true, "mynewfile.cp")) + if err != nil { + HandleError(err) + } + + // Case 7: Use GZIP encoding for downloading the file, GetObject/GetObjectToFile are the same. + err = bucket.PutObjectFromFile(objectKey, htmlLocalFile) + if err != nil { + HandleError(err) + } + + err = bucket.GetObjectToFile(objectKey, "myhtml.gzip", oss.AcceptEncoding("gzip")) + if err != nil { + HandleError(err) + } + + // Delete the object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("GetObjectSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_buckets.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_buckets.go new file mode 100644 index 000000000..2d50826fe --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_buckets.go @@ -0,0 +1,129 @@ +package sample + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// ListBucketsSample shows the list bucket, including default and specified parameters. +func ListBucketsSample() { + var myBuckets = []string{ + "my-bucket-1", + "my-bucket-11", + "my-bucket-2", + "my-bucket-21", + "my-bucket-22", + "my-bucket-3", + "my-bucket-31", + "my-bucket-32"} + + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + HandleError(err) + } + + // Remove other bucket + lbr, err := client.ListBuckets() + if err != nil { + HandleError(err) + } + + for _, bucket := range lbr.Buckets { + err = client.DeleteBucket(bucket.Name) + if err != nil { + //HandleError(err) + } + } + + // Create bucket + for _, bucketName := range myBuckets { + err = client.CreateBucket(bucketName) + if err != nil { + HandleError(err) + } + } + + // Case 1: Use default parameter + lbr, err = client.ListBuckets() + if err != nil { + HandleError(err) + } + fmt.Println("my buckets:", lbr.Buckets) + + // Case 2: Specify the max keys : 3 + lbr, err = client.ListBuckets(oss.MaxKeys(3)) + if err != nil { + HandleError(err) + } + fmt.Println("my buckets max num:", lbr.Buckets) + + // Case 3: Specify the prefix of buckets. + lbr, err = client.ListBuckets(oss.Prefix("my-bucket-2")) + if err != nil { + HandleError(err) + } + fmt.Println("my buckets prefix :", lbr.Buckets) + + // Case 4: Specify the marker to return from a certain one + lbr, err = client.ListBuckets(oss.Marker("my-bucket-22")) + if err != nil { + HandleError(err) + } + fmt.Println("my buckets marker :", lbr.Buckets) + + // Case 5: Specify max key and list all buckets with paging, return 3 items each time. + marker := oss.Marker("") + for { + lbr, err = client.ListBuckets(oss.MaxKeys(3), marker) + if err != nil { + HandleError(err) + } + marker = oss.Marker(lbr.NextMarker) + fmt.Println("my buckets page :", lbr.Buckets) + if !lbr.IsTruncated { + break + } + } + + // Case 6: List bucket with marker and max key; return 3 items each time. + marker = oss.Marker("my-bucket-22") + for { + lbr, err = client.ListBuckets(oss.MaxKeys(3), marker) + if err != nil { + HandleError(err) + } + marker = oss.Marker(lbr.NextMarker) + fmt.Println("my buckets marker&page :", lbr.Buckets) + if !lbr.IsTruncated { + break + } + } + + // Case 7: List bucket with prefix and max key, return 3 items each time. + pre := oss.Prefix("my-bucket-2") + marker = oss.Marker("") + for { + lbr, err = client.ListBuckets(oss.MaxKeys(3), pre, marker) + if err != nil { + HandleError(err) + } + pre = oss.Prefix(lbr.Prefix) + marker = oss.Marker(lbr.NextMarker) + fmt.Println("my buckets prefix&page :", lbr.Buckets) + if !lbr.IsTruncated { + break + } + } + + // Delete bucket + for _, bucketName := range myBuckets { + err = client.DeleteBucket(bucketName) + if err != nil { + HandleError(err) + } + } + + fmt.Println("ListsBucketSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_objects.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_objects.go new file mode 100644 index 000000000..d0b08360a --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/list_objects.go @@ -0,0 +1,148 @@ +package sample + +import ( + "fmt" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// ListObjectsSample shows the file list, including default and specified parameters. +func ListObjectsSample() { + var myObjects = []Object{ + {"my-object-1", ""}, + {"my-object-11", ""}, + {"my-object-2", ""}, + {"my-object-21", ""}, + {"my-object-22", ""}, + {"my-object-3", ""}, + {"my-object-31", ""}, + {"my-object-32", ""}} + + // Create bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Create objects + err = CreateObjects(bucket, myObjects) + if err != nil { + HandleError(err) + } + + // Case 1: Use default parameters + lor, err := bucket.ListObjects() + if err != nil { + HandleError(err) + } + fmt.Println("my objects:", getObjectsFormResponse(lor)) + + // Case 2: Specify max keys + lor, err = bucket.ListObjects(oss.MaxKeys(3)) + if err != nil { + HandleError(err) + } + fmt.Println("my objects max num:", getObjectsFormResponse(lor)) + + // Case 3: Specify prefix of objects + lor, err = bucket.ListObjects(oss.Prefix("my-object-2")) + if err != nil { + HandleError(err) + } + fmt.Println("my objects prefix :", getObjectsFormResponse(lor)) + + // Case 4: Specify the marker + lor, err = bucket.ListObjects(oss.Marker("my-object-22")) + if err != nil { + HandleError(err) + } + fmt.Println("my objects marker :", getObjectsFormResponse(lor)) + + // Case 5: List object with paging. each page has 3 objects + marker := oss.Marker("") + for { + lor, err = bucket.ListObjects(oss.MaxKeys(3), marker) + if err != nil { + HandleError(err) + } + marker = oss.Marker(lor.NextMarker) + fmt.Println("my objects page :", getObjectsFormResponse(lor)) + if !lor.IsTruncated { + break + } + } + + // Case 6: List object with paging , marker and max keys; return 3 items each time. + marker = oss.Marker("my-object-22") + for { + lor, err = bucket.ListObjects(oss.MaxKeys(3), marker) + if err != nil { + HandleError(err) + } + marker = oss.Marker(lor.NextMarker) + fmt.Println("my objects marker&page :", getObjectsFormResponse(lor)) + if !lor.IsTruncated { + break + } + } + + // Case 7: List object with paging , with prefix and max keys; return 2 items each time. + pre := oss.Prefix("my-object-2") + marker = oss.Marker("") + for { + lor, err = bucket.ListObjects(oss.MaxKeys(2), marker, pre) + if err != nil { + HandleError(err) + } + pre = oss.Prefix(lor.Prefix) + marker = oss.Marker(lor.NextMarker) + fmt.Println("my objects prefix&page :", getObjectsFormResponse(lor)) + if !lor.IsTruncated { + break + } + } + + err = DeleteObjects(bucket, myObjects) + if err != nil { + HandleError(err) + } + + // Case 8: Combine the prefix and delimiter for grouping. ListObjectsResponse.Objects is the objects returned. + // ListObjectsResponse.CommonPrefixes is the common prefixes returned. + myObjects = []Object{ + {"fun/test.txt", ""}, + {"fun/test.jpg", ""}, + {"fun/movie/001.avi", ""}, + {"fun/movie/007.avi", ""}, + {"fun/music/001.mp3", ""}, + {"fun/music/001.mp3", ""}} + + // Create object + err = CreateObjects(bucket, myObjects) + if err != nil { + HandleError(err) + } + + lor, err = bucket.ListObjects(oss.Prefix("fun/"), oss.Delimiter("/")) + if err != nil { + HandleError(err) + } + fmt.Println("my objects prefix :", getObjectsFormResponse(lor), + "common prefixes:", lor.CommonPrefixes) + + // Delete object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("ListObjectsSample completed") +} + +func getObjectsFormResponse(lor oss.ListObjectsResult) string { + var output string + for _, object := range lor.Objects { + output += object.Key + " " + } + return output +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/livechannel.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/livechannel.go new file mode 100644 index 000000000..d258be3f5 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/livechannel.go @@ -0,0 +1,445 @@ +package sample + +import ( + "fmt" + "io/ioutil" + "time" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// CreateLiveChannelSample Samples for create a live-channel +func CreateLiveChannelSample() { + channelName := "create-livechannel" + //create bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Case 1 - Create live-channel with Completely configure + config := oss.LiveChannelConfiguration{ + Description: "sample-for-livechannel", //description information, up to 128 bytes + Status: "enabled", //enabled or disabled + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + FragDuration: 10, //the length of each ts object (in seconds), in the range [1,100], default: 5 + FragCount: 4, //the number of ts objects in the m3u8 object, in the range of [1,100], default: 3 + PlaylistName: "test-get-channel-status.m3u8", //the name of m3u8 object, which must end with ".m3u8" and the length range is [6,128],default: playlist.m3u8 + }, + } + + result, err := bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + playURL := result.PlayUrls[0] + publishURL := result.PublishUrls[0] + fmt.Printf("create livechannel:%s with config respones: playURL:%s, publishURL: %s\n", channelName, playURL, publishURL) + + // Case 2 - Create live-channel only specified type of target which is required + simpleCfg := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", + }, + } + result, err = bucket.CreateLiveChannel(channelName, simpleCfg) + if err != nil { + HandleError(err) + } + playURL = result.PlayUrls[0] + publishURL = result.PublishUrls[0] + fmt.Printf("create livechannel:%s with simple config respones: playURL:%s, publishURL: %s\n", channelName, playURL, publishURL) + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("PutObjectSample completed") +} + +// PutLiveChannelStatusSample Set the status of the live-channel sample: enabled/disabled +func PutLiveChannelStatusSample() { + channelName := "put-livechannel-status" + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + config := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + }, + } + + _, err = bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + // Case 1 - Set the status of live-channel to disabled + err = bucket.PutLiveChannelStatus(channelName, "disabled") + if err != nil { + HandleError(err) + } + + // Case 2 - Set the status of live-channel to enabled + err = bucket.PutLiveChannelStatus(channelName, "enabled") + if err != nil { + HandleError(err) + } + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("PutLiveChannelStatusSample completed") +} + +// PostVodPlayListSample Sample for generate playlist +func PostVodPlayListSample() { + channelName := "post-vod-playlist" + playlistName := "playlist.m3u8" + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + config := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + PlaylistName: "playlist.m3u8", + }, + } + + _, err = bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + //This stage you can push live stream, and after that you could generator playlist + + endTime := time.Now().Add(-1 * time.Minute) + startTime := endTime.Add(-60 * time.Minute) + err = bucket.PostVodPlaylist(channelName, playlistName, startTime, endTime) + if err != nil { + HandleError(err) + } + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("PostVodPlayListSampleSample completed") +} + +// GetVodPlayListSample Sample for generate playlist and return the content of the playlist +func GetVodPlayListSample() { + channelName := "get-vod-playlist" + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + config := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + PlaylistName: "playlist.m3u8", + }, + } + + _, err = bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + //This stage you can push live stream, and after that you could generator playlist + + endTime := time.Now().Add(-1 * time.Minute) + startTime := endTime.Add(-60 * time.Minute) + body, err := bucket.GetVodPlaylist(channelName, startTime, endTime) + if err != nil { + HandleError(err) + } + defer body.Close() + + data, err := ioutil.ReadAll(body) + if err != nil { + HandleError(err) + } + fmt.Printf("content of playlist is:%v\n", string(data)) + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("PostVodPlayListSampleSample completed") +} + +// GetLiveChannelStatSample Sample for get the state of live-channel +func GetLiveChannelStatSample() { + channelName := "get-livechannel-stat" + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + config := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + }, + } + + _, err = bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + stat, err := bucket.GetLiveChannelStat(channelName) + if err != nil { + HandleError(err) + } + + status := stat.Status + connectedTime := stat.ConnectedTime + remoteAddr := stat.RemoteAddr + + audioBW := stat.Audio.Bandwidth + audioCodec := stat.Audio.Codec + audioSampleRate := stat.Audio.SampleRate + + videoBW := stat.Video.Bandwidth + videoFrameRate := stat.Video.FrameRate + videoHeight := stat.Video.Height + videoWidth := stat.Video.Width + + fmt.Printf("get channel stat:(%v, %v,%v, %v), audio(%v, %v, %v), video(%v, %v, %v, %v)\n", channelName, status, connectedTime, remoteAddr, audioBW, audioCodec, audioSampleRate, videoBW, videoFrameRate, videoHeight, videoWidth) + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("GetLiveChannelStatSample completed") +} + +// GetLiveChannelInfoSample Sample for get the configuration infomation of live-channel +func GetLiveChannelInfoSample() { + channelName := "get-livechannel-info" + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + config := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + }, + } + + _, err = bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + info, err := bucket.GetLiveChannelInfo(channelName) + if err != nil { + HandleError(err) + } + + desc := info.Description + status := info.Status + fragCount := info.Target.FragCount + fragDuation := info.Target.FragDuration + playlistName := info.Target.PlaylistName + targetType := info.Target.Type + + fmt.Printf("get channel stat:(%v,%v, %v), target(%v, %v, %v, %v)\n", channelName, desc, status, fragCount, fragDuation, playlistName, targetType) + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("GetLiveChannelInfoSample completed") +} + +// GetLiveChannelHistorySample Sample for get push records of live-channel +func GetLiveChannelHistorySample() { + channelName := "get-livechannel-info" + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + config := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + }, + } + + _, err = bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + //at most return up to lastest 10 push records + history, err := bucket.GetLiveChannelHistory(channelName) + for _, record := range history.Record { + remoteAddr := record.RemoteAddr + startTime := record.StartTime + endTime := record.EndTime + fmt.Printf("get channel:%s history:(%v, %v, %v)\n", channelName, remoteAddr, startTime, endTime) + } + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("GetLiveChannelHistorySample completed") +} + +// ListLiveChannelSample Samples for list live-channels with specified bucket name +func ListLiveChannelSample() { + channelName := "list-livechannel" + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + config := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + }, + } + + _, err = bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + // Case 1: list all the live-channels + marker := "" + for { + // Set the marker value, the first time is "", the value of NextMarker that returned should as the marker in the next time + // At most return up to lastest 100 live-channels if "max-keys" is not specified + result, err := bucket.ListLiveChannel(oss.Marker(marker)) + if err != nil { + HandleError(err) + } + + for _, channel := range result.LiveChannel { + fmt.Printf("list livechannel: (%v, %v, %v, %v, %v, %v)\n", channel.Name, channel.Status, channel.Description, channel.LastModified, channel.PlayUrls[0], channel.PublishUrls[0]) + } + + if result.IsTruncated { + marker = result.NextMarker + } else { + break + } + } + + // Case 2: Use the parameter "max-keys" to specify the maximum number of records returned, the value of max-keys cannot exceed 1000 + // if "max-keys" the default value is 100 + result, err := bucket.ListLiveChannel(oss.MaxKeys(10)) + if err != nil { + HandleError(err) + } + for _, channel := range result.LiveChannel { + fmt.Printf("list livechannel: (%v, %v, %v, %v, %v, %v)\n", channel.Name, channel.Status, channel.Description, channel.LastModified, channel.PlayUrls[0], channel.PublishUrls[0]) + } + + // Case 3: Only list the live-channels with the value of parameter "prefix" as prefix + // max-keys, prefix, maker parameters can be combined + result, err = bucket.ListLiveChannel(oss.MaxKeys(10), oss.Prefix("list-")) + if err != nil { + HandleError(err) + } + for _, channel := range result.LiveChannel { + fmt.Printf("list livechannel: (%v, %v, %v, %v, %v, %v)\n", channel.Name, channel.Status, channel.Description, channel.LastModified, channel.PlayUrls[0], channel.PublishUrls[0]) + } + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("ListLiveChannelSample completed") +} + +// DeleteLiveChannelSample Sample for delete live-channel +func DeleteLiveChannelSample() { + channelName := "delete-livechannel" + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + config := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + }, + } + + _, err = bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + err = bucket.DeleteLiveChannel(channelName) + if err != nil { + HandleError(err) + } + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("DeleteLiveChannelSample completed") +} + +// SignRtmpURLSample Sample for generate a RTMP push-stream signature URL for the trusted user to push the RTMP stream to the live channel. +func SignRtmpURLSample() { + channelName := "sign-rtmp-url" + playlistName := "playlist.m3u8" + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + config := oss.LiveChannelConfiguration{ + Target: oss.LiveChannelTarget{ + Type: "HLS", //the type of object, only supports HLS, required + PlaylistName: "playlist.m3u8", + }, + } + + result, err := bucket.CreateLiveChannel(channelName, config) + if err != nil { + HandleError(err) + } + + playURL := result.PlayUrls[0] + publishURL := result.PublishUrls[0] + fmt.Printf("livechannel:%s, playURL:%s, publishURL: %s\n", channelName, playURL, publishURL) + + signedRtmpURL, err := bucket.SignRtmpURL(channelName, playlistName, 3600) + if err != nil { + HandleError(err) + } + fmt.Printf("livechannel:%s, sinedRtmpURL: %s\n", channelName, signedRtmpURL) + + err = DeleteTestBucketAndLiveChannel(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("SignRtmpURLSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/new_bucket.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/new_bucket.go new file mode 100644 index 000000000..2a375c1c5 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/new_bucket.go @@ -0,0 +1,50 @@ +package sample + +import ( + "fmt" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// NewBucketSample shows how to initialize client and bucket +func NewBucketSample() { + // New client + client, err := oss.New(endpoint, accessID, accessKey) + if err != nil { + HandleError(err) + } + + // Create bucket + err = client.CreateBucket(bucketName) + if err != nil { + HandleError(err) + } + + // New bucket + bucket, err := client.Bucket(bucketName) + if err != nil { + HandleError(err) + } + + // Put object, uploads an object + var objectName = "myobject" + err = bucket.PutObject(objectName, strings.NewReader("MyObjectValue")) + if err != nil { + HandleError(err) + } + + // Delete object, deletes an object + err = bucket.DeleteObject(objectName) + if err != nil { + HandleError(err) + } + + // Delete bucket + err = client.DeleteBucket(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("NewBucketSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_acl.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_acl.go new file mode 100755 index 000000000..167de79e9 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_acl.go @@ -0,0 +1,44 @@ +package sample + +import ( + "fmt" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// ObjectACLSample shows how to set and get object ACL +func ObjectACLSample() { + // Create bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Create object + err = bucket.PutObject(objectKey, strings.NewReader("YoursObjectValue")) + if err != nil { + HandleError(err) + } + + // Case 1: Set bucket ACL, valid ACLs are ACLPrivate、ACLPublicRead、ACLPublicReadWrite + err = bucket.SetObjectACL(objectKey, oss.ACLPrivate) + if err != nil { + HandleError(err) + } + + // Get object ACL, returns one of the three values: private、public-read、public-read-write + goar, err := bucket.GetObjectACL(objectKey) + if err != nil { + HandleError(err) + } + fmt.Println("Object ACL:", goar.ACL) + + // Delete object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("ObjectACLSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_meta.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_meta.go new file mode 100755 index 000000000..6150d6bdb --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/object_meta.go @@ -0,0 +1,73 @@ +package sample + +import ( + "fmt" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// ObjectMetaSample shows how to get and set the object metadata +func ObjectMetaSample() { + // Create bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Delete object + err = bucket.PutObject(objectKey, strings.NewReader("YoursObjectValue")) + if err != nil { + HandleError(err) + } + + // Case 0: Set bucket meta. one or more properties could be set + // Note: Meta is case insensitive + options := []oss.Option{ + oss.Expires(futureDate), + oss.Meta("myprop", "mypropval")} + err = bucket.SetObjectMeta(objectKey, options...) + if err != nil { + HandleError(err) + } + + // Case 1: Get the object metadata. Only return basic meta information includes ETag, size and last modified. + props, err := bucket.GetObjectMeta(objectKey) + if err != nil { + HandleError(err) + } + fmt.Println("Object Meta:", props) + + // Case 2: Get all the detailed object meta including custom meta + props, err = bucket.GetObjectDetailedMeta(objectKey) + if err != nil { + HandleError(err) + } + fmt.Println("Expires:", props.Get("Expires")) + + // Case 3: Get the object's all metadata with contraints. When constraints are met, return the metadata. + props, err = bucket.GetObjectDetailedMeta(objectKey, oss.IfUnmodifiedSince(futureDate)) + if err != nil { + HandleError(err) + } + fmt.Println("MyProp:", props.Get("X-Oss-Meta-Myprop")) + + _, err = bucket.GetObjectDetailedMeta(objectKey, oss.IfModifiedSince(futureDate)) + if err == nil { + HandleError(err) + } + + goar, err := bucket.GetObjectACL(objectKey) + if err != nil { + HandleError(err) + } + fmt.Println("Object ACL:", goar.ACL) + + // Delete object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("ObjectMetaSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/put_object.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/put_object.go new file mode 100755 index 000000000..e12afddae --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/put_object.go @@ -0,0 +1,132 @@ +package sample + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// PutObjectSample illustrates two methods for uploading a file: simple upload and multipart upload. +func PutObjectSample() { + // Create bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + var val = "花间一壶酒,独酌无相亲。 举杯邀明月,对影成三人。" + + // Case 1: Upload an object from a string + err = bucket.PutObject(objectKey, strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + // Case 2: Upload an object whose value is a byte[] + err = bucket.PutObject(objectKey, bytes.NewReader([]byte(val))) + if err != nil { + HandleError(err) + } + + // Case 3: Upload the local file with file handle, user should open the file at first. + fd, err := os.Open(localFile) + if err != nil { + HandleError(err) + } + defer fd.Close() + + err = bucket.PutObject(objectKey, fd) + if err != nil { + HandleError(err) + } + + // Case 4: Upload an object with local file name, user need not open the file. + err = bucket.PutObjectFromFile(objectKey, localFile) + if err != nil { + HandleError(err) + } + + // Case 5: Upload an object with specified properties, PutObject/PutObjectFromFile/UploadFile also support this feature. + options := []oss.Option{ + oss.Expires(futureDate), + oss.ObjectACL(oss.ACLPublicRead), + oss.Meta("myprop", "mypropval"), + } + err = bucket.PutObject(objectKey, strings.NewReader(val), options...) + if err != nil { + HandleError(err) + } + + props, err := bucket.GetObjectDetailedMeta(objectKey) + if err != nil { + HandleError(err) + } + fmt.Println("Object Meta:", props) + + // Case 6: Upload an object with sever side encrpytion kms and kms id specified + err = bucket.PutObject(objectKey, strings.NewReader(val), oss.ServerSideEncryption("KMS"), oss.ServerSideEncryptionKeyID(kmsID)) + if err != nil { + HandleError(err) + } + + // Case 7: Upload an object with callback + callbackMap := map[string]string{} + callbackMap["callbackUrl"] = "http://oss-demo.aliyuncs.com:23450" + callbackMap["callbackHost"] = "oss-cn-hangzhou.aliyuncs.com" + callbackMap["callbackBody"] = "filename=${object}&size=${size}&mimeType=${mimeType}" + callbackMap["callbackBodyType"] = "application/x-www-form-urlencoded" + + callbackBuffer := bytes.NewBuffer([]byte{}) + callbackEncoder := json.NewEncoder(callbackBuffer) + //do not encode '&' to "\u0026" + callbackEncoder.SetEscapeHTML(false) + err = callbackEncoder.Encode(callbackMap) + if err != nil { + HandleError(err) + } + + callbackVal := base64.StdEncoding.EncodeToString(callbackBuffer.Bytes()) + err = bucket.PutObject(objectKey, strings.NewReader(val), oss.Callback(callbackVal)) + if err != nil { + HandleError(err) + } + + // Case 8: Big file's multipart upload. It supports concurrent upload with resumable upload. + // multipart upload with 100K as part size. By default 1 coroutine is used and no checkpoint is used. + err = bucket.UploadFile(objectKey, localFile, 100*1024) + if err != nil { + HandleError(err) + } + + // Part size is 100K and 3 coroutines are used + err = bucket.UploadFile(objectKey, localFile, 100*1024, oss.Routines(3)) + if err != nil { + HandleError(err) + } + + // Part size is 100K and 3 coroutines with checkpoint + err = bucket.UploadFile(objectKey, localFile, 100*1024, oss.Routines(3), oss.Checkpoint(true, "")) + if err != nil { + HandleError(err) + } + + // Specify the local file path for checkpoint files. + // the 2nd parameter of Checkpoint can specify the file path, when the file path is empty, it will upload the directory. + err = bucket.UploadFile(objectKey, localFile, 100*1024, oss.Checkpoint(true, localFile+".cp")) + if err != nil { + HandleError(err) + } + + // Delete object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("PutObjectSample completed") +} diff --git a/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sign_url.go b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sign_url.go new file mode 100755 index 000000000..894136e47 --- /dev/null +++ b/vendor/github.com/aliyun/aliyun-oss-go-sdk/sample/sign_url.go @@ -0,0 +1,74 @@ +package sample + +import ( + "fmt" + "io/ioutil" + "strings" + + "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +// SignURLSample signs URL sample +func SignURLSample() { + // Create bucket + bucket, err := GetTestBucket(bucketName) + if err != nil { + HandleError(err) + } + + // Put object + signedURL, err := bucket.SignURL(objectKey, oss.HTTPPut, 60) + if err != nil { + HandleError(err) + } + + var val = "花间一壶酒,独酌无相亲。 举杯邀明月,对影成三人。" + err = bucket.PutObjectWithURL(signedURL, strings.NewReader(val)) + if err != nil { + HandleError(err) + } + + // Put object with option + options := []oss.Option{ + oss.Meta("myprop", "mypropval"), + oss.ContentType("image/tiff"), + } + + signedURL, err = bucket.SignURL(objectKey, oss.HTTPPut, 60, options...) + if err != nil { + HandleError(err) + } + + err = bucket.PutObjectFromFileWithURL(signedURL, localFile, options...) + if err != nil { + HandleError(err) + } + + // Get object + signedURL, err = bucket.SignURL(objectKey, oss.HTTPGet, 60) + if err != nil { + HandleError(err) + } + + body, err := bucket.GetObjectWithURL(signedURL) + if err != nil { + HandleError(err) + } + // Read content + data, err := ioutil.ReadAll(body) + body.Close() + data = data // use data + + err = bucket.GetObjectToFileWithURL(signedURL, "mynewfile-1.jpg") + if err != nil { + HandleError(err) + } + + // Delete the object and bucket + err = DeleteTestBucketAndObject(bucketName) + if err != nil { + HandleError(err) + } + + fmt.Println("SignURLSample completed") +} From 02870e0966516ff40041510abc594a8ca91ffaa6 Mon Sep 17 00:00:00 2001 From: buhe Date: Tue, 12 Mar 2019 12:02:46 +0800 Subject: [PATCH 2/2] fmt source code --- pkg/apis/mysql/v1alpha1/types.go | 4 ++-- pkg/backup/storage/oss/provider.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/apis/mysql/v1alpha1/types.go b/pkg/apis/mysql/v1alpha1/types.go index 4d10cac7f..f03db8417 100644 --- a/pkg/apis/mysql/v1alpha1/types.go +++ b/pkg/apis/mysql/v1alpha1/types.go @@ -177,8 +177,8 @@ type S3StorageProvider struct { // StorageProvider defines the configuration for storing a Backup in a storage // service. type StorageProvider struct { - S3 *S3StorageProvider `json:"s3"` - ProviderType string `json:"providerType"` + S3 *S3StorageProvider `json:"s3"` + ProviderType string `json:"providerType"` } // BackupSpec defines the specification for a MySQL backup. This includes what should be backed up, diff --git a/pkg/backup/storage/oss/provider.go b/pkg/backup/storage/oss/provider.go index a08c1071f..b07ee87f8 100644 --- a/pkg/backup/storage/oss/provider.go +++ b/pkg/backup/storage/oss/provider.go @@ -61,7 +61,7 @@ func (p *Provider) Store(key string, body io.ReadCloser) error { if err != nil { return errors.Wrapf(err, "error storing backup (provider=\"S3\", endpoint=%q, bucket=%q, key=%q)", p.Endpoint, p.Bucket, key) } - err = bucket.PutObject(key, body); + err = bucket.PutObject(key, body) return errors.Wrapf(err, "error storing backup (provider=\"S3\", endpoint=%q, bucket=%q, key=%q)", p.Endpoint, p.Bucket, key) }