Add Alibaba Cloud backend OSS with lock
This commit is contained in:
parent
88e76fa9ef
commit
b887d44712
|
@ -22,6 +22,7 @@ import (
|
|||
backendHTTP "github.com/hashicorp/terraform/backend/remote-state/http"
|
||||
backendInmem "github.com/hashicorp/terraform/backend/remote-state/inmem"
|
||||
backendManta "github.com/hashicorp/terraform/backend/remote-state/manta"
|
||||
backendOSS "github.com/hashicorp/terraform/backend/remote-state/oss"
|
||||
backendPg "github.com/hashicorp/terraform/backend/remote-state/pg"
|
||||
backendS3 "github.com/hashicorp/terraform/backend/remote-state/s3"
|
||||
backendSwift "github.com/hashicorp/terraform/backend/remote-state/swift"
|
||||
|
@ -62,6 +63,7 @@ func Init(services *disco.Disco) {
|
|||
"http": func() backend.Backend { return backendHTTP.New() },
|
||||
"inmem": func() backend.Backend { return backendInmem.New() },
|
||||
"manta": func() backend.Backend { return backendManta.New() },
|
||||
"oss": func() backend.Backend { return backendOSS.New() },
|
||||
"pg": func() backend.Backend { return backendPg.New() },
|
||||
"s3": func() backend.Backend { return backendS3.New() },
|
||||
"swift": func() backend.Backend { return backendSwift.New() },
|
||||
|
|
|
@ -0,0 +1,297 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/resource"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/services/location"
|
||||
"github.com/hashicorp/go-cleanhttp"
|
||||
"github.com/hashicorp/terraform/version"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
// New creates a new backend for OSS remote state.
|
||||
func New() backend.Backend {
|
||||
s := &schema.Backend{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"access_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Alibaba Cloud Access Key ID",
|
||||
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ACCESS_KEY", os.Getenv("ALICLOUD_ACCESS_KEY_ID")),
|
||||
},
|
||||
|
||||
"secret_key": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Alibaba Cloud Access Secret Key",
|
||||
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SECRET_KEY", os.Getenv("ALICLOUD_ACCESS_KEY_SECRET")),
|
||||
},
|
||||
|
||||
"security_token": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Alibaba Cloud Security Token",
|
||||
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SECURITY_TOKEN", os.Getenv("SECURITY_TOKEN")),
|
||||
},
|
||||
|
||||
"region": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The region of the OSS bucket.",
|
||||
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_REGION", os.Getenv("ALICLOUD_DEFAULT_REGION")),
|
||||
},
|
||||
|
||||
"endpoint": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "A custom endpoint for the OSS API",
|
||||
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_OSS_ENDPOINT", os.Getenv("OSS_ENDPOINT")),
|
||||
},
|
||||
|
||||
"bucket": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "The name of the OSS bucket",
|
||||
},
|
||||
|
||||
"path": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
Description: "The path relative to your object storage directory where the state file will be stored.",
|
||||
},
|
||||
|
||||
"name": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "The name of the state file inside the bucket",
|
||||
ValidateFunc: func(v interface{}, s string) ([]string, []error) {
|
||||
if strings.HasPrefix(v.(string), "/") || strings.HasSuffix(v.(string), "/") {
|
||||
return nil, []error{fmt.Errorf("name can not start and end with '/'")}
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
Default: "terraform.tfstate",
|
||||
},
|
||||
|
||||
"lock": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Description: "Whether to lock state access. Defaults to true",
|
||||
Default: true,
|
||||
},
|
||||
|
||||
"encrypt": &schema.Schema{
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Description: "Whether to enable server side encryption of the state file",
|
||||
Default: false,
|
||||
},
|
||||
|
||||
"acl": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "Object ACL to be applied to the state file",
|
||||
Default: "",
|
||||
ValidateFunc: func(v interface{}, k string) ([]string, []error) {
|
||||
if value := v.(string); value != "" {
|
||||
acls := oss.ACLType(value)
|
||||
if acls != oss.ACLPrivate && acls != oss.ACLPublicRead && acls != oss.ACLPublicReadWrite {
|
||||
return nil, []error{fmt.Errorf(
|
||||
"%q must be a valid ACL value , expected %s, %s or %s, got %q",
|
||||
k, oss.ACLPrivate, oss.ACLPublicRead, oss.ACLPublicReadWrite, acls)}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result := &Backend{Backend: s}
|
||||
result.Backend.ConfigureFunc = result.configure
|
||||
return result
|
||||
}
|
||||
|
||||
type Backend struct {
|
||||
*schema.Backend
|
||||
|
||||
// The fields below are set from configure
|
||||
ossClient *oss.Client
|
||||
|
||||
bucketName string
|
||||
statePath string
|
||||
stateName string
|
||||
serverSideEncryption bool
|
||||
acl string
|
||||
endpoint string
|
||||
lock bool
|
||||
}
|
||||
|
||||
func (b *Backend) configure(ctx context.Context) error {
|
||||
if b.ossClient != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Grab the resource data
|
||||
d := schema.FromContextBackendConfig(ctx)
|
||||
|
||||
b.bucketName = d.Get("bucket").(string)
|
||||
dir := strings.Trim(d.Get("path").(string), "/")
|
||||
if strings.HasPrefix(dir, "./") {
|
||||
dir = strings.TrimPrefix(dir, "./")
|
||||
|
||||
}
|
||||
|
||||
b.statePath = dir
|
||||
b.stateName = d.Get("name").(string)
|
||||
b.serverSideEncryption = d.Get("encrypt").(bool)
|
||||
b.acl = d.Get("acl").(string)
|
||||
b.lock = d.Get("lock").(bool)
|
||||
|
||||
access_key := d.Get("access_key").(string)
|
||||
secret_key := d.Get("secret_key").(string)
|
||||
security_token := d.Get("security_token").(string)
|
||||
region := d.Get("region").(string)
|
||||
endpoint := d.Get("endpoint").(string)
|
||||
schma := "https"
|
||||
|
||||
if endpoint == "" {
|
||||
endpointItem, _ := b.getOSSEndpointByRegion(access_key, secret_key, security_token, region)
|
||||
if endpointItem != nil && len(endpointItem.Endpoint) > 0 {
|
||||
if len(endpointItem.Protocols.Protocols) > 0 {
|
||||
// HTTP or HTTPS
|
||||
schma = strings.ToLower(endpointItem.Protocols.Protocols[0])
|
||||
for _, p := range endpointItem.Protocols.Protocols {
|
||||
if strings.ToLower(p) == "https" {
|
||||
schma = strings.ToLower(p)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
endpoint = endpointItem.Endpoint
|
||||
} else {
|
||||
endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region)
|
||||
}
|
||||
}
|
||||
if !strings.HasPrefix(endpoint, "http") {
|
||||
endpoint = fmt.Sprintf("%s://%s", schma, endpoint)
|
||||
}
|
||||
log.Printf("[DEBUG] Instantiate OSS client using endpoint: %#v", endpoint)
|
||||
var options []oss.ClientOption
|
||||
if security_token != "" {
|
||||
options = append(options, oss.SecurityToken(security_token))
|
||||
}
|
||||
options = append(options, oss.UserAgent(fmt.Sprintf("%s/%s", TerraformUA, TerraformVersion)))
|
||||
|
||||
client, err := oss.New(endpoint, access_key, secret_key, options...)
|
||||
b.ossClient = client
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (b *Backend) getOSSEndpointByRegion(access_key, secret_key, security_token, region string) (*location.DescribeEndpointResponse, error) {
|
||||
args := location.CreateDescribeEndpointRequest()
|
||||
args.ServiceCode = "oss"
|
||||
args.Id = region
|
||||
args.Domain = "location-readonly.aliyuncs.com"
|
||||
|
||||
locationClient, err := location.NewClientWithOptions(region, getSdkConfig(), credentials.NewStsTokenCredential(access_key, secret_key, security_token))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to initialize the location client: %#v", err)
|
||||
|
||||
}
|
||||
locationClient.AppendUserAgent(TerraformUA, TerraformVersion)
|
||||
endpointsResponse, err := locationClient.DescribeEndpoint(args)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Describe oss endpoint using region: %#v got an error: %#v.", region, err)
|
||||
}
|
||||
return endpointsResponse, nil
|
||||
}
|
||||
|
||||
func getSdkConfig() *sdk.Config {
|
||||
// Fix bug "open /usr/local/go/lib/time/zoneinfo.zip: no such file or directory" which happened in windows.
|
||||
if data, ok := resource.GetTZData("GMT"); ok {
|
||||
utils.TZData = data
|
||||
utils.LoadLocationFromTZData = time.LoadLocationFromTZData
|
||||
}
|
||||
return sdk.NewConfig().
|
||||
WithMaxRetryTime(5).
|
||||
WithTimeout(time.Duration(30) * time.Second).
|
||||
WithGoRoutinePoolSize(10).
|
||||
WithDebug(false).
|
||||
WithHttpTransport(getTransport()).
|
||||
WithScheme("HTTPS")
|
||||
}
|
||||
|
||||
func getTransport() *http.Transport {
|
||||
handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout"))
|
||||
if err != nil {
|
||||
handshakeTimeout = 120
|
||||
}
|
||||
transport := cleanhttp.DefaultTransport()
|
||||
transport.TLSHandshakeTimeout = time.Duration(handshakeTimeout) * time.Second
|
||||
transport.Proxy = http.ProxyFromEnvironment
|
||||
return transport
|
||||
}
|
||||
|
||||
type Invoker struct {
|
||||
catchers []*Catcher
|
||||
}
|
||||
|
||||
type Catcher struct {
|
||||
Reason string
|
||||
RetryCount int
|
||||
RetryWaitSeconds int
|
||||
}
|
||||
|
||||
const TerraformUA = "HashiCorp-Terraform"
|
||||
|
||||
var TerraformVersion = strings.TrimSuffix(version.String(), "-dev")
|
||||
var ClientErrorCatcher = Catcher{"AliyunGoClientFailure", 10, 3}
|
||||
var ServiceBusyCatcher = Catcher{"ServiceUnavailable", 10, 3}
|
||||
|
||||
func NewInvoker() Invoker {
|
||||
i := Invoker{}
|
||||
i.AddCatcher(ClientErrorCatcher)
|
||||
i.AddCatcher(ServiceBusyCatcher)
|
||||
return i
|
||||
}
|
||||
|
||||
func (a *Invoker) AddCatcher(catcher Catcher) {
|
||||
a.catchers = append(a.catchers, &catcher)
|
||||
}
|
||||
|
||||
func (a *Invoker) Run(f func() error) error {
|
||||
err := f()
|
||||
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, catcher := range a.catchers {
|
||||
if strings.Contains(err.Error(), catcher.Reason) {
|
||||
catcher.RetryCount--
|
||||
|
||||
if catcher.RetryCount <= 0 {
|
||||
return fmt.Errorf("Retry timeout and got an error: %#v.", err)
|
||||
} else {
|
||||
time.Sleep(time.Duration(catcher.RetryWaitSeconds) * time.Second)
|
||||
return a.Run(f)
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
|
||||
"log"
|
||||
"path"
|
||||
)
|
||||
|
||||
const (
|
||||
lockFileSuffix = ".tflock"
|
||||
)
|
||||
|
||||
// get a remote client configured for this state
|
||||
func (b *Backend) remoteClient(name string) (*RemoteClient, error) {
|
||||
if name == "" {
|
||||
return nil, errors.New("missing state name")
|
||||
}
|
||||
|
||||
client := &RemoteClient{
|
||||
ossClient: b.ossClient,
|
||||
bucketName: b.bucketName,
|
||||
stateFile: b.stateFile(name),
|
||||
lockFile: b.lockFile(name),
|
||||
serverSideEncryption: b.serverSideEncryption,
|
||||
acl: b.acl,
|
||||
doLock: b.lock,
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (b *Backend) Workspaces() ([]string, error) {
|
||||
bucket, err := b.ossClient.Bucket(b.bucketName)
|
||||
if err != nil {
|
||||
return []string{""}, fmt.Errorf("Error getting bucket: %#v", err)
|
||||
}
|
||||
|
||||
var options []oss.Option
|
||||
options = append(options, oss.Prefix(b.statePath))
|
||||
resp, err := bucket.ListObjects(options...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := []string{backend.DefaultStateName}
|
||||
for _, obj := range resp.Objects {
|
||||
if b.keyEnv(obj.Key) != "" {
|
||||
result = append(result, b.keyEnv(obj.Key))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(result[1:])
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (b *Backend) DeleteWorkspace(name string) error {
|
||||
if name == backend.DefaultStateName || name == "" {
|
||||
return fmt.Errorf("can't delete default state")
|
||||
}
|
||||
|
||||
client, err := b.remoteClient(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return client.Delete()
|
||||
}
|
||||
|
||||
func (b *Backend) StateMgr(name string) (state.State, error) {
|
||||
client, err := b.remoteClient(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stateMgr := &remote.State{Client: client}
|
||||
|
||||
if !b.lock {
|
||||
stateMgr.DisableLocks()
|
||||
}
|
||||
// Check to see if this state already exists.
|
||||
existing, err := b.Workspaces()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Current state name: %s. All States:%#v", name, existing)
|
||||
|
||||
exists := false
|
||||
for _, s := range existing {
|
||||
if s == name {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
// We need to create the object so it's listed by States.
|
||||
if !exists {
|
||||
// take a lock on this state while we write it
|
||||
lockInfo := state.NewLockInfo()
|
||||
lockInfo.Operation = "init"
|
||||
lockId, err := client.Lock(lockInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to lock OSS state: %s", err)
|
||||
}
|
||||
|
||||
// Local helper function so we can call it multiple places
|
||||
lockUnlock := func(e error) error {
|
||||
if err := stateMgr.Unlock(lockId); err != nil {
|
||||
return fmt.Errorf(strings.TrimSpace(stateUnlockError), lockId, err)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
// Grab the value
|
||||
if err := stateMgr.RefreshState(); err != nil {
|
||||
err = lockUnlock(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we have no state, we have to create an empty state
|
||||
if v := stateMgr.State(); v == nil {
|
||||
if err := stateMgr.WriteState(states.NewState()); err != nil {
|
||||
err = lockUnlock(err)
|
||||
return nil, err
|
||||
}
|
||||
if err := stateMgr.PersistState(); err != nil {
|
||||
err = lockUnlock(err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock, the state should now be initialized
|
||||
if err := lockUnlock(nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
return stateMgr, nil
|
||||
}
|
||||
|
||||
// extract the object name from the OSS key
|
||||
func (b *Backend) keyEnv(key string) string {
|
||||
// we have 3 parts, the state path, the state name, and the state file
|
||||
parts := strings.Split(key, "/")
|
||||
length := len(parts)
|
||||
if length < 3 {
|
||||
// use default state
|
||||
return ""
|
||||
}
|
||||
|
||||
// shouldn't happen since we listed by prefix
|
||||
if strings.Join(parts[0:length-2], "/") != b.statePath {
|
||||
return ""
|
||||
}
|
||||
|
||||
// not our key, so don't include it in our listing
|
||||
if parts[length-1] != b.stateName {
|
||||
return ""
|
||||
}
|
||||
|
||||
return parts[length-2]
|
||||
}
|
||||
|
||||
func (b *Backend) stateFile(name string) string {
|
||||
if name == backend.DefaultStateName {
|
||||
return path.Join(b.statePath, b.stateName)
|
||||
}
|
||||
return path.Join(b.statePath, name, b.stateName)
|
||||
}
|
||||
|
||||
func (b *Backend) lockFile(name string) string {
|
||||
if name == backend.DefaultStateName {
|
||||
return path.Join(b.statePath, b.stateName+lockFileSuffix)
|
||||
}
|
||||
return path.Join(b.statePath, name, b.stateName+lockFileSuffix)
|
||||
}
|
||||
|
||||
const stateUnlockError = `
|
||||
Error unlocking Alibaba Cloud OSS state file:
|
||||
|
||||
Lock ID: %s
|
||||
Error message: %#v
|
||||
|
||||
You may have to force-unlock this state in order to use it again.
|
||||
The Alibaba Cloud backend acquires a lock during initialization to ensure the initial state file is created.
|
||||
`
|
|
@ -0,0 +1,134 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/config/hcl2shim"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// verify that we are doing ACC tests or the OSS tests specifically
|
||||
func testACC(t *testing.T) {
|
||||
skip := os.Getenv("TF_ACC") == "" && os.Getenv("TF_OSS_TEST") == ""
|
||||
if skip {
|
||||
t.Log("oss backend tests require setting TF_ACC or TF_OSS_TEST")
|
||||
t.Skip()
|
||||
}
|
||||
if os.Getenv("ALICLOUD_REGION") == "" {
|
||||
os.Setenv("ALICLOUD_REGION", "cn-beijing")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackend_impl(t *testing.T) {
|
||||
var _ backend.Backend = new(Backend)
|
||||
}
|
||||
|
||||
func TestBackendConfig(t *testing.T) {
|
||||
testACC(t)
|
||||
config := map[string]interface{}{
|
||||
"region": "cn-beijing",
|
||||
"bucket": "terraform-backend-oss-test",
|
||||
"path": "mystate",
|
||||
"name": "first.tfstate",
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
|
||||
|
||||
if !strings.HasPrefix(b.ossClient.Config.Endpoint, "http://oss-cn-beijing") {
|
||||
t.Fatalf("Incorrect region was provided")
|
||||
}
|
||||
if b.bucketName != "terraform-backend-oss-test" {
|
||||
t.Fatalf("Incorrect bucketName was provided")
|
||||
}
|
||||
if b.statePath != "mystate" {
|
||||
t.Fatalf("Incorrect state file path was provided")
|
||||
}
|
||||
if b.stateName != "first.tfstate" {
|
||||
t.Fatalf("Incorrect keyName was provided")
|
||||
}
|
||||
|
||||
if b.ossClient.Config.AccessKeyID == "" {
|
||||
t.Fatalf("No Access Key Id was provided")
|
||||
}
|
||||
if b.ossClient.Config.AccessKeySecret == "" {
|
||||
t.Fatalf("No Secret Access Key was provided")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendConfig_invalidKey(t *testing.T) {
|
||||
testACC(t)
|
||||
cfg := hcl2shim.HCL2ValueFromConfigValue(map[string]interface{}{
|
||||
"region": "cn-beijing",
|
||||
"bucket": "terraform-backend-oss-test",
|
||||
"path": "/leading-slash",
|
||||
"name": "/test.tfstate",
|
||||
})
|
||||
|
||||
_, results := New().PrepareConfig(cfg)
|
||||
if !results.HasErrors() {
|
||||
t.Fatal("expected config validation error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackend(t *testing.T) {
|
||||
testACC(t)
|
||||
|
||||
bucketName := fmt.Sprintf("terraform-remote-oss-test-%x", time.Now().Unix())
|
||||
statePath := "multi/level/path/"
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"path": statePath,
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"path": statePath,
|
||||
})).(*Backend)
|
||||
|
||||
createOSSBucket(t, b1.ossClient, bucketName)
|
||||
defer deleteOSSBucket(t, b1.ossClient, bucketName)
|
||||
|
||||
backend.TestBackendStates(t, b1)
|
||||
backend.TestBackendStateLocks(t, b1, b2)
|
||||
backend.TestBackendStateForceUnlock(t, b1, b2)
|
||||
}
|
||||
|
||||
func createOSSBucket(t *testing.T, ossClient *oss.Client, bucketName string) {
|
||||
// Be clear about what we're doing in case the user needs to clean this up later.
|
||||
if err := ossClient.CreateBucket(bucketName); err != nil {
|
||||
t.Fatal("failed to create test OSS bucket:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func deleteOSSBucket(t *testing.T, ossClient *oss.Client, bucketName string) {
|
||||
warning := "WARNING: Failed to delete the test OSS bucket. It may have been left in your Alicloud account and may incur storage charges. (error was %s)"
|
||||
|
||||
// first we have to get rid of the env objects, or we can't delete the bucket
|
||||
bucket, err := ossClient.Bucket(bucketName)
|
||||
if err != nil {
|
||||
t.Fatal("Error getting bucket:", err)
|
||||
return
|
||||
}
|
||||
objects, err := bucket.ListObjects()
|
||||
if err != nil {
|
||||
t.Logf(warning, err)
|
||||
return
|
||||
}
|
||||
for _, obj := range objects.Objects {
|
||||
if err := bucket.DeleteObject(obj.Key); err != nil {
|
||||
// this will need cleanup no matter what, so just warn and exit
|
||||
t.Logf(warning, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := ossClient.DeleteBucket(bucketName); err != nil {
|
||||
t.Logf(warning, err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type RemoteClient struct {
|
||||
ossClient *oss.Client
|
||||
bucketName string
|
||||
stateFile string
|
||||
lockFile string
|
||||
serverSideEncryption bool
|
||||
acl string
|
||||
doLock bool
|
||||
info *state.LockInfo
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Get() (payload *remote.Payload, err error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
buf, err := c.getObj(c.stateFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If there was no data, then return nil
|
||||
if buf == nil || len(buf.Bytes()) == 0 {
|
||||
log.Printf("[DEBUG] State %s has no data.", c.stateFile)
|
||||
return nil, nil
|
||||
}
|
||||
md5 := md5.Sum(buf.Bytes())
|
||||
|
||||
payload = &remote.Payload{
|
||||
Data: buf.Bytes(),
|
||||
MD5: md5[:],
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Put(data []byte) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
return c.putObj(c.stateFile, data)
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Delete() error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
return c.deleteObj(c.stateFile)
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if !c.doLock {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
bucket, err := c.ossClient.Bucket(c.bucketName)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error getting bucket: %#v", err)
|
||||
}
|
||||
|
||||
infoJson, err := json.Marshal(info)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if info.ID == "" {
|
||||
lockID, err := uuid.GenerateUUID()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
info.ID = lockID
|
||||
}
|
||||
|
||||
info.Path = c.lockFile
|
||||
exist, err := bucket.IsObjectExist(info.Path)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error checking object %s: %#v", info.Path, err)
|
||||
}
|
||||
if !exist {
|
||||
if err := c.putObj(info.Path, infoJson); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else if _, err := c.validLock(info.ID); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return info.ID, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) Unlock(id string) error {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
if !c.doLock {
|
||||
return nil
|
||||
}
|
||||
|
||||
lockInfo, err := c.validLock(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.deleteObj(c.lockFile); err != nil {
|
||||
return &state.LockError{
|
||||
Info: lockInfo,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) putObj(key string, data []byte) error {
|
||||
bucket, err := c.ossClient.Bucket(c.bucketName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting bucket: %#v", err)
|
||||
}
|
||||
body := bytes.NewReader(data)
|
||||
|
||||
var options []oss.Option
|
||||
if c.acl != "" {
|
||||
options = append(options, oss.ACL(oss.ACLType(c.acl)))
|
||||
}
|
||||
options = append(options, oss.ContentType("application/json"))
|
||||
if c.serverSideEncryption {
|
||||
options = append(options, oss.ServerSideEncryption("AES256"))
|
||||
}
|
||||
options = append(options, oss.ContentLength(int64(len(data))))
|
||||
|
||||
if body != nil {
|
||||
if err := bucket.PutObject(key, body, options...); err != nil {
|
||||
return fmt.Errorf("failed to upload %s: %#v", key, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) getObj(key string) (*bytes.Buffer, error) {
|
||||
bucket, err := c.ossClient.Bucket(c.bucketName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting bucket: %#v", err)
|
||||
}
|
||||
|
||||
if exist, err := bucket.IsObjectExist(key); err != nil {
|
||||
return nil, fmt.Errorf("Estimating object %s is exist got an error: %#v", key, err)
|
||||
} else if !exist {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var options []oss.Option
|
||||
output, err := bucket.GetObject(key, options...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error getting object: %#v", err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
if _, err := io.Copy(buf, output); err != nil {
|
||||
return nil, fmt.Errorf("Failed to read remote state: %s", err)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) deleteObj(key string) error {
|
||||
bucket, err := c.ossClient.Bucket(c.bucketName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error getting bucket: %#v", err)
|
||||
}
|
||||
|
||||
if err := bucket.DeleteObject(key); err != nil {
|
||||
return fmt.Errorf("Error deleting object %s: %#v", key, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// lockInfo reads the lock file, parses its contents and returns the parsed
|
||||
// LockInfo struct.
|
||||
func (c *RemoteClient) lockInfo() (*state.LockInfo, error) {
|
||||
buf, err := c.getObj(c.lockFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf == nil || len(buf.Bytes()) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
info := &state.LockInfo{}
|
||||
if err := json.Unmarshal(buf.Bytes(), info); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (c *RemoteClient) validLock(id string) (*state.LockInfo, *state.LockError) {
|
||||
lockErr := &state.LockError{}
|
||||
lockInfo, err := c.lockInfo()
|
||||
if err != nil {
|
||||
lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
|
||||
return nil, lockErr
|
||||
}
|
||||
lockErr.Info = lockInfo
|
||||
|
||||
if lockInfo.ID != id {
|
||||
lockErr.Err = fmt.Errorf("lock id %q does not match existing lock", id)
|
||||
return nil, lockErr
|
||||
}
|
||||
return lockInfo, nil
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/backend"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
)
|
||||
|
||||
func TestRemoteClient_impl(t *testing.T) {
|
||||
var _ remote.Client = new(RemoteClient)
|
||||
var _ remote.ClientLocker = new(RemoteClient)
|
||||
}
|
||||
|
||||
func TestRemoteClient(t *testing.T) {
|
||||
testACC(t)
|
||||
bucketName := fmt.Sprintf("terraform-remote-oss-test-%x", time.Now().Unix())
|
||||
path := "testState"
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"path": path,
|
||||
"encrypt": true,
|
||||
})).(*Backend)
|
||||
|
||||
createOSSBucket(t, b.ossClient, bucketName)
|
||||
defer deleteOSSBucket(t, b.ossClient, bucketName)
|
||||
|
||||
state, err := b.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
remote.TestClient(t, state.(*remote.State).Client)
|
||||
}
|
||||
|
||||
func TestOSS_stateLock(t *testing.T) {
|
||||
testACC(t)
|
||||
bucketName := fmt.Sprintf("terraform-remote-oss-test-%x", time.Now().Unix())
|
||||
path := "testState"
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"path": path,
|
||||
"encrypt": true,
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"path": path,
|
||||
"encrypt": true,
|
||||
})).(*Backend)
|
||||
|
||||
createOSSBucket(t, b1.ossClient, bucketName)
|
||||
defer deleteOSSBucket(t, b1.ossClient, bucketName)
|
||||
|
||||
s1, err := b1.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
s2, err := b2.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
remote.TestRemoteLocks(t, s1.(*remote.State).Client, s2.(*remote.State).Client)
|
||||
}
|
||||
|
||||
// verify that we can unlock a state with an existing lock
|
||||
func TestOSS_destroyLock(t *testing.T) {
|
||||
testACC(t)
|
||||
bucketName := fmt.Sprintf("terraform-remote-oss-test-%x", time.Now().Unix())
|
||||
path := "testState"
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"bucket": bucketName,
|
||||
"path": path,
|
||||
"encrypt": true,
|
||||
})).(*Backend)
|
||||
|
||||
createOSSBucket(t, b.ossClient, bucketName)
|
||||
defer deleteOSSBucket(t, b.ossClient, bucketName)
|
||||
|
||||
s, err := b.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
c := s.(*remote.State).Client.(*RemoteClient)
|
||||
|
||||
info := state.NewLockInfo()
|
||||
id, err := c.Lock(info)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if err := c.Unlock(id); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
res, err := c.getObj(c.lockFile)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
if res != nil && res.String() != "" {
|
||||
t.Fatalf("lock key not cleaned up at: %s", string(c.stateFile))
|
||||
}
|
||||
}
|
5
go.mod
5
go.mod
|
@ -8,6 +8,8 @@ require (
|
|||
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af // indirect
|
||||
github.com/agext/levenshtein v1.2.2
|
||||
github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6 // indirect
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190104080739-ef2ef6084d8f
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70
|
||||
github.com/apparentlymart/go-cidr v1.0.0
|
||||
github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0
|
||||
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
|
||||
|
@ -69,6 +71,7 @@ require (
|
|||
github.com/hashicorp/vault v0.10.4
|
||||
github.com/jonboulle/clockwork v0.1.0 // indirect
|
||||
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926
|
||||
github.com/json-iterator/go v1.1.5 // indirect
|
||||
github.com/jtolds/gls v4.2.1+incompatible // indirect
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba // indirect
|
||||
|
@ -90,6 +93,8 @@ require (
|
|||
github.com/mitchellh/panicwrap v0.0.0-20190213213626-17011010aaa4
|
||||
github.com/mitchellh/prefixedio v0.0.0-20190213213902-5733675afd51
|
||||
github.com/mitchellh/reflectwalk v1.0.0
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/packer-community/winrmcp v0.0.0-20180102160824-81144009af58
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c // indirect
|
||||
github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17 // indirect
|
||||
|
|
10
go.sum
10
go.sum
|
@ -28,6 +28,10 @@ github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXva
|
|||
github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6 h1:LoeFxdq5zUCBQPhbQKE6zvoGwHMxCBlqwbH9+9kHoHA=
|
||||
github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190104080739-ef2ef6084d8f h1:pM8wn2zKfEVQkR9cj//GkywiJXMwtZ9feuNsEkHqBC8=
|
||||
github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190104080739-ef2ef6084d8f/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70 h1:FrF4uxA24DF3ARNXVbUin3wa5fDLaB1Cy8mKks/LRz4=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e h1:ptBAamGVd6CfRsUtyHD+goy2JGhv1QC32v3gqM8mYAM=
|
||||
github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk=
|
||||
|
@ -228,6 +232,8 @@ github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0
|
|||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926 h1:kie3qOosvRKqwij2HGzXWffwpXvcqfPPXRUw8I4F/mg=
|
||||
github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA=
|
||||
github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE=
|
||||
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||
|
@ -301,6 +307,10 @@ github.com/mitchellh/prefixedio v0.0.0-20190213213902-5733675afd51 h1:eD92Am0Qf3
|
|||
github.com/mitchellh/prefixedio v0.0.0-20190213213902-5733675afd51/go.mod h1:kB1naBgV9ORnkiTVeyJOI1DavaJkG4oNIq0Af6ZVKUo=
|
||||
github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
|
||||
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo=
|
||||
github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM=
|
||||
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
18
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credential.go
generated
vendored
Normal file
18
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credential.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 auth
|
||||
|
||||
type Credential interface {
|
||||
}
|
34
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/access_key_credential.go
generated
vendored
Normal file
34
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/access_key_credential.go
generated
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
package credentials
|
||||
|
||||
// Deprecated: Use AccessKeyCredential in this package instead.
|
||||
type BaseCredential struct {
|
||||
AccessKeyId string
|
||||
AccessKeySecret string
|
||||
}
|
||||
|
||||
type AccessKeyCredential struct {
|
||||
AccessKeyId string
|
||||
AccessKeySecret string
|
||||
}
|
||||
|
||||
// Deprecated: Use NewAccessKeyCredential in this package instead.
|
||||
func NewBaseCredential(accessKeyId, accessKeySecret string) *BaseCredential {
|
||||
return &BaseCredential{
|
||||
AccessKeyId: accessKeyId,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
}
|
||||
}
|
||||
|
||||
func (baseCred *BaseCredential) ToAccessKeyCredential() *AccessKeyCredential {
|
||||
return &AccessKeyCredential{
|
||||
AccessKeyId: baseCred.AccessKeyId,
|
||||
AccessKeySecret: baseCred.AccessKeySecret,
|
||||
}
|
||||
}
|
||||
|
||||
func NewAccessKeyCredential(accessKeyId, accessKeySecret string) *AccessKeyCredential {
|
||||
return &AccessKeyCredential{
|
||||
AccessKeyId: accessKeyId,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
}
|
||||
}
|
29
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/ecs_ram_role.go
generated
vendored
Normal file
29
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/ecs_ram_role.go
generated
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
package credentials
|
||||
|
||||
// Deprecated: Use EcsRamRoleCredential in this package instead.
|
||||
type StsRoleNameOnEcsCredential struct {
|
||||
RoleName string
|
||||
}
|
||||
|
||||
// Deprecated: Use NewEcsRamRoleCredential in this package instead.
|
||||
func NewStsRoleNameOnEcsCredential(roleName string) *StsRoleNameOnEcsCredential {
|
||||
return &StsRoleNameOnEcsCredential{
|
||||
RoleName: roleName,
|
||||
}
|
||||
}
|
||||
|
||||
func (oldCred *StsRoleNameOnEcsCredential) ToEcsRamRoleCredential() *EcsRamRoleCredential {
|
||||
return &EcsRamRoleCredential{
|
||||
RoleName: oldCred.RoleName,
|
||||
}
|
||||
}
|
||||
|
||||
type EcsRamRoleCredential struct {
|
||||
RoleName string
|
||||
}
|
||||
|
||||
func NewEcsRamRoleCredential(roleName string) *EcsRamRoleCredential {
|
||||
return &EcsRamRoleCredential{
|
||||
RoleName: roleName,
|
||||
}
|
||||
}
|
15
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/rsa_key_pair_credential.go
generated
vendored
Normal file
15
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/rsa_key_pair_credential.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
package credentials
|
||||
|
||||
type RsaKeyPairCredential struct {
|
||||
PrivateKey string
|
||||
PublicKeyId string
|
||||
SessionExpiration int
|
||||
}
|
||||
|
||||
func NewRsaKeyPairCredential(privateKey, publicKeyId string, sessionExpiration int) *RsaKeyPairCredential {
|
||||
return &RsaKeyPairCredential{
|
||||
PrivateKey: privateKey,
|
||||
PublicKeyId: publicKeyId,
|
||||
SessionExpiration: sessionExpiration,
|
||||
}
|
||||
}
|
15
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/sts_credential.go
generated
vendored
Normal file
15
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/sts_credential.go
generated
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
package credentials
|
||||
|
||||
type StsTokenCredential struct {
|
||||
AccessKeyId string
|
||||
AccessKeySecret string
|
||||
AccessKeyStsToken string
|
||||
}
|
||||
|
||||
func NewStsTokenCredential(accessKeyId, accessKeySecret, accessKeyStsToken string) *StsTokenCredential {
|
||||
return &StsTokenCredential{
|
||||
AccessKeyId: accessKeyId,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
AccessKeyStsToken: accessKeyStsToken,
|
||||
}
|
||||
}
|
49
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/sts_role_arn_credential.go
generated
vendored
Normal file
49
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials/sts_role_arn_credential.go
generated
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
package credentials
|
||||
|
||||
// Deprecated: Use RamRoleArnCredential in this package instead.
|
||||
type StsRoleArnCredential struct {
|
||||
AccessKeyId string
|
||||
AccessKeySecret string
|
||||
RoleArn string
|
||||
RoleSessionName string
|
||||
RoleSessionExpiration int
|
||||
}
|
||||
|
||||
type RamRoleArnCredential struct {
|
||||
AccessKeyId string
|
||||
AccessKeySecret string
|
||||
RoleArn string
|
||||
RoleSessionName string
|
||||
RoleSessionExpiration int
|
||||
}
|
||||
|
||||
// Deprecated: Use RamRoleArnCredential in this package instead.
|
||||
func NewStsRoleArnCredential(accessKeyId, accessKeySecret, roleArn, roleSessionName string, roleSessionExpiration int) *StsRoleArnCredential {
|
||||
return &StsRoleArnCredential{
|
||||
AccessKeyId: accessKeyId,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
RoleArn: roleArn,
|
||||
RoleSessionName: roleSessionName,
|
||||
RoleSessionExpiration: roleSessionExpiration,
|
||||
}
|
||||
}
|
||||
|
||||
func (oldCred *StsRoleArnCredential) ToRamRoleArnCredential() *RamRoleArnCredential {
|
||||
return &RamRoleArnCredential{
|
||||
AccessKeyId: oldCred.AccessKeyId,
|
||||
AccessKeySecret: oldCred.AccessKeySecret,
|
||||
RoleArn: oldCred.RoleArn,
|
||||
RoleSessionName: oldCred.RoleSessionName,
|
||||
RoleSessionExpiration: oldCred.RoleSessionExpiration,
|
||||
}
|
||||
}
|
||||
|
||||
func NewRamRoleArnCredential(accessKeyId, accessKeySecret, roleArn, roleSessionName string, roleSessionExpiration int) *RamRoleArnCredential {
|
||||
return &RamRoleArnCredential{
|
||||
AccessKeyId: accessKeyId,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
RoleArn: roleArn,
|
||||
RoleSessionName: roleSessionName,
|
||||
RoleSessionExpiration: roleSessionExpiration,
|
||||
}
|
||||
}
|
133
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/roa_signature_composer.go
generated
vendored
Normal file
133
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/roa_signature_composer.go
generated
vendored
Normal file
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* 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 auth
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
|
||||
)
|
||||
|
||||
var debug utils.Debug
|
||||
|
||||
var hookGetDate = func(fn func() string) string {
|
||||
return fn()
|
||||
}
|
||||
|
||||
func init() {
|
||||
debug = utils.Init("sdk")
|
||||
}
|
||||
|
||||
func signRoaRequest(request requests.AcsRequest, signer Signer, regionId string) (err error) {
|
||||
completeROASignParams(request, signer, regionId)
|
||||
stringToSign := buildRoaStringToSign(request)
|
||||
request.SetStringToSign(stringToSign)
|
||||
signature := signer.Sign(stringToSign, "")
|
||||
accessKeyId, err := signer.GetAccessKeyId()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
request.GetHeaders()["Authorization"] = "acs " + accessKeyId + ":" + signature
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func completeROASignParams(request requests.AcsRequest, signer Signer, regionId string) {
|
||||
headerParams := request.GetHeaders()
|
||||
|
||||
// complete query params
|
||||
queryParams := request.GetQueryParams()
|
||||
//if _, ok := queryParams["RegionId"]; !ok {
|
||||
// queryParams["RegionId"] = regionId
|
||||
//}
|
||||
if extraParam := signer.GetExtraParam(); extraParam != nil {
|
||||
for key, value := range extraParam {
|
||||
if key == "SecurityToken" {
|
||||
headerParams["x-acs-security-token"] = value
|
||||
continue
|
||||
}
|
||||
|
||||
queryParams[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
// complete header params
|
||||
headerParams["Date"] = hookGetDate(utils.GetTimeInFormatRFC2616)
|
||||
headerParams["x-acs-signature-method"] = signer.GetName()
|
||||
headerParams["x-acs-signature-version"] = signer.GetVersion()
|
||||
if request.GetFormParams() != nil && len(request.GetFormParams()) > 0 {
|
||||
formString := utils.GetUrlFormedMap(request.GetFormParams())
|
||||
request.SetContent([]byte(formString))
|
||||
headerParams["Content-Type"] = requests.Form
|
||||
}
|
||||
contentMD5 := utils.GetMD5Base64(request.GetContent())
|
||||
headerParams["Content-MD5"] = contentMD5
|
||||
if _, contains := headerParams["Content-Type"]; !contains {
|
||||
headerParams["Content-Type"] = requests.Raw
|
||||
}
|
||||
switch format := request.GetAcceptFormat(); format {
|
||||
case "JSON":
|
||||
headerParams["Accept"] = requests.Json
|
||||
case "XML":
|
||||
headerParams["Accept"] = requests.Xml
|
||||
default:
|
||||
headerParams["Accept"] = requests.Raw
|
||||
}
|
||||
}
|
||||
|
||||
func buildRoaStringToSign(request requests.AcsRequest) (stringToSign string) {
|
||||
|
||||
headers := request.GetHeaders()
|
||||
|
||||
stringToSignBuilder := bytes.Buffer{}
|
||||
stringToSignBuilder.WriteString(request.GetMethod())
|
||||
stringToSignBuilder.WriteString(requests.HeaderSeparator)
|
||||
|
||||
// append header keys for sign
|
||||
appendIfContain(headers, &stringToSignBuilder, "Accept", requests.HeaderSeparator)
|
||||
appendIfContain(headers, &stringToSignBuilder, "Content-MD5", requests.HeaderSeparator)
|
||||
appendIfContain(headers, &stringToSignBuilder, "Content-Type", requests.HeaderSeparator)
|
||||
appendIfContain(headers, &stringToSignBuilder, "Date", requests.HeaderSeparator)
|
||||
|
||||
// sort and append headers witch starts with 'x-acs-'
|
||||
var acsHeaders []string
|
||||
for key := range headers {
|
||||
if strings.HasPrefix(key, "x-acs-") {
|
||||
acsHeaders = append(acsHeaders, key)
|
||||
}
|
||||
}
|
||||
sort.Strings(acsHeaders)
|
||||
for _, key := range acsHeaders {
|
||||
stringToSignBuilder.WriteString(key + ":" + headers[key])
|
||||
stringToSignBuilder.WriteString(requests.HeaderSeparator)
|
||||
}
|
||||
|
||||
// append query params
|
||||
stringToSignBuilder.WriteString(request.BuildQueries())
|
||||
stringToSign = stringToSignBuilder.String()
|
||||
debug("stringToSign: %s", stringToSign)
|
||||
return
|
||||
}
|
||||
|
||||
func appendIfContain(sourceMap map[string]string, target *bytes.Buffer, key, separator string) {
|
||||
if value, contain := sourceMap[key]; contain && len(value) > 0 {
|
||||
target.WriteString(sourceMap[key])
|
||||
target.WriteString(separator)
|
||||
}
|
||||
}
|
94
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/rpc_signature_composer.go
generated
vendored
Normal file
94
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/rpc_signature_composer.go
generated
vendored
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 auth
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
|
||||
)
|
||||
|
||||
var hookGetUUIDV4 = func(fn func() string) string {
|
||||
return fn()
|
||||
}
|
||||
|
||||
func signRpcRequest(request requests.AcsRequest, signer Signer, regionId string) (err error) {
|
||||
err = completeRpcSignParams(request, signer, regionId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// remove while retry
|
||||
if _, containsSign := request.GetQueryParams()["Signature"]; containsSign {
|
||||
delete(request.GetQueryParams(), "Signature")
|
||||
}
|
||||
stringToSign := buildRpcStringToSign(request)
|
||||
request.SetStringToSign(stringToSign)
|
||||
signature := signer.Sign(stringToSign, "&")
|
||||
request.GetQueryParams()["Signature"] = signature
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func completeRpcSignParams(request requests.AcsRequest, signer Signer, regionId string) (err error) {
|
||||
queryParams := request.GetQueryParams()
|
||||
queryParams["Version"] = request.GetVersion()
|
||||
queryParams["Action"] = request.GetActionName()
|
||||
queryParams["Format"] = request.GetAcceptFormat()
|
||||
queryParams["Timestamp"] = hookGetDate(utils.GetTimeInFormatISO8601)
|
||||
queryParams["SignatureMethod"] = signer.GetName()
|
||||
queryParams["SignatureType"] = signer.GetType()
|
||||
queryParams["SignatureVersion"] = signer.GetVersion()
|
||||
queryParams["SignatureNonce"] = hookGetUUIDV4(utils.GetUUIDV4)
|
||||
queryParams["AccessKeyId"], err = signer.GetAccessKeyId()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, contains := queryParams["RegionId"]; !contains {
|
||||
queryParams["RegionId"] = regionId
|
||||
}
|
||||
if extraParam := signer.GetExtraParam(); extraParam != nil {
|
||||
for key, value := range extraParam {
|
||||
queryParams[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
request.GetHeaders()["Content-Type"] = requests.Form
|
||||
formString := utils.GetUrlFormedMap(request.GetFormParams())
|
||||
request.SetContent([]byte(formString))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func buildRpcStringToSign(request requests.AcsRequest) (stringToSign string) {
|
||||
signParams := make(map[string]string)
|
||||
for key, value := range request.GetQueryParams() {
|
||||
signParams[key] = value
|
||||
}
|
||||
for key, value := range request.GetFormParams() {
|
||||
signParams[key] = value
|
||||
}
|
||||
|
||||
stringToSign = utils.GetUrlFormedMap(signParams)
|
||||
stringToSign = strings.Replace(stringToSign, "+", "%20", -1)
|
||||
stringToSign = strings.Replace(stringToSign, "*", "%2A", -1)
|
||||
stringToSign = strings.Replace(stringToSign, "%7E", "~", -1)
|
||||
stringToSign = url.QueryEscape(stringToSign)
|
||||
stringToSign = request.GetMethod() + "&%2F&" + stringToSign
|
||||
return
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* 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 auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
)
|
||||
|
||||
type Signer interface {
|
||||
GetName() string
|
||||
GetType() string
|
||||
GetVersion() string
|
||||
GetAccessKeyId() (string, error)
|
||||
GetExtraParam() map[string]string
|
||||
Sign(stringToSign, secretSuffix string) string
|
||||
}
|
||||
|
||||
func NewSignerWithCredential(credential Credential, commonApi func(request *requests.CommonRequest, signer interface{}) (response *responses.CommonResponse, err error)) (signer Signer, err error) {
|
||||
switch instance := credential.(type) {
|
||||
case *credentials.AccessKeyCredential:
|
||||
{
|
||||
signer = signers.NewAccessKeySigner(instance)
|
||||
}
|
||||
case *credentials.StsTokenCredential:
|
||||
{
|
||||
signer = signers.NewStsTokenSigner(instance)
|
||||
}
|
||||
case *credentials.RamRoleArnCredential:
|
||||
{
|
||||
signer, err = signers.NewRamRoleArnSigner(instance, commonApi)
|
||||
}
|
||||
case *credentials.RsaKeyPairCredential:
|
||||
{
|
||||
signer, err = signers.NewSignerKeyPair(instance, commonApi)
|
||||
}
|
||||
case *credentials.EcsRamRoleCredential:
|
||||
{
|
||||
signer = signers.NewEcsRamRoleSigner(instance, commonApi)
|
||||
}
|
||||
case *credentials.BaseCredential: // deprecated user interface
|
||||
{
|
||||
signer = signers.NewAccessKeySigner(instance.ToAccessKeyCredential())
|
||||
}
|
||||
case *credentials.StsRoleArnCredential: // deprecated user interface
|
||||
{
|
||||
signer, err = signers.NewRamRoleArnSigner(instance.ToRamRoleArnCredential(), commonApi)
|
||||
}
|
||||
case *credentials.StsRoleNameOnEcsCredential: // deprecated user interface
|
||||
{
|
||||
signer = signers.NewEcsRamRoleSigner(instance.ToEcsRamRoleCredential(), commonApi)
|
||||
}
|
||||
default:
|
||||
message := fmt.Sprintf(errors.UnsupportedCredentialErrorMessage, reflect.TypeOf(credential))
|
||||
err = errors.NewClientError(errors.UnsupportedCredentialErrorCode, message, nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func Sign(request requests.AcsRequest, signer Signer, regionId string) (err error) {
|
||||
switch request.GetStyle() {
|
||||
case requests.ROA:
|
||||
{
|
||||
err = signRoaRequest(request, signer, regionId)
|
||||
}
|
||||
case requests.RPC:
|
||||
{
|
||||
err = signRpcRequest(request, signer, regionId)
|
||||
}
|
||||
default:
|
||||
message := fmt.Sprintf(errors.UnknownRequestTypeErrorMessage, reflect.TypeOf(request))
|
||||
err = errors.NewClientError(errors.UnknownRequestTypeErrorCode, message, nil)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
56
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/algorithms.go
generated
vendored
Normal file
56
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/algorithms.go
generated
vendored
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 signers
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha1"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
)
|
||||
|
||||
func ShaHmac1(source, secret string) string {
|
||||
key := []byte(secret)
|
||||
hmac := hmac.New(sha1.New, key)
|
||||
hmac.Write([]byte(source))
|
||||
signedBytes := hmac.Sum(nil)
|
||||
signedString := base64.StdEncoding.EncodeToString(signedBytes)
|
||||
return signedString
|
||||
}
|
||||
|
||||
func Sha256WithRsa(source, secret string) string {
|
||||
decodeString, err := base64.StdEncoding.DecodeString(secret)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
private, err := x509.ParsePKCS8PrivateKey(decodeString)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
h := crypto.Hash.New(crypto.SHA256)
|
||||
h.Write([]byte(source))
|
||||
hashed := h.Sum(nil)
|
||||
signature, err := rsa.SignPKCS1v15(rand.Reader, private.(*rsa.PrivateKey),
|
||||
crypto.SHA256, hashed)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(signature)
|
||||
}
|
54
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/credential_updater.go
generated
vendored
Normal file
54
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/credential_updater.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 signers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
)
|
||||
|
||||
const defaultInAdvanceScale = 0.8
|
||||
|
||||
type credentialUpdater struct {
|
||||
credentialExpiration int
|
||||
lastUpdateTimestamp int64
|
||||
inAdvanceScale float64
|
||||
buildRequestMethod func() (*requests.CommonRequest, error)
|
||||
responseCallBack func(response *responses.CommonResponse) error
|
||||
refreshApi func(request *requests.CommonRequest) (response *responses.CommonResponse, err error)
|
||||
}
|
||||
|
||||
func (updater *credentialUpdater) needUpdateCredential() (result bool) {
|
||||
if updater.inAdvanceScale == 0 {
|
||||
updater.inAdvanceScale = defaultInAdvanceScale
|
||||
}
|
||||
return time.Now().Unix()-updater.lastUpdateTimestamp >= int64(float64(updater.credentialExpiration)*updater.inAdvanceScale)
|
||||
}
|
||||
|
||||
func (updater *credentialUpdater) updateCredential() (err error) {
|
||||
request, err := updater.buildRequestMethod()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
response, err := updater.refreshApi(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
updater.lastUpdateTimestamp = time.Now().Unix()
|
||||
err = updater.responseCallBack(response)
|
||||
return
|
||||
}
|
7
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/session_credential.go
generated
vendored
Normal file
7
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/session_credential.go
generated
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
package signers
|
||||
|
||||
type SessionCredential struct {
|
||||
AccessKeyId string
|
||||
AccessKeySecret string
|
||||
StsToken string
|
||||
}
|
54
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_access_key.go
generated
vendored
Normal file
54
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_access_key.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 signers
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
)
|
||||
|
||||
type AccessKeySigner struct {
|
||||
credential *credentials.AccessKeyCredential
|
||||
}
|
||||
|
||||
func (signer *AccessKeySigner) GetExtraParam() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAccessKeySigner(credential *credentials.AccessKeyCredential) *AccessKeySigner {
|
||||
return &AccessKeySigner{
|
||||
credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
func (*AccessKeySigner) GetName() string {
|
||||
return "HMAC-SHA1"
|
||||
}
|
||||
|
||||
func (*AccessKeySigner) GetType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (*AccessKeySigner) GetVersion() string {
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
func (signer *AccessKeySigner) GetAccessKeyId() (accessKeyId string, err error) {
|
||||
return signer.credential.AccessKeyId, nil
|
||||
}
|
||||
|
||||
func (signer *AccessKeySigner) Sign(stringToSign, secretSuffix string) string {
|
||||
secret := signer.credential.AccessKeySecret + secretSuffix
|
||||
return ShaHmac1(stringToSign, secret)
|
||||
}
|
167
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_ecs_ram_role.go
generated
vendored
Normal file
167
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_ecs_ram_role.go
generated
vendored
Normal file
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* 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 signers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
"github.com/jmespath/go-jmespath"
|
||||
)
|
||||
|
||||
var securityCredURL = "http://100.100.100.200/latest/meta-data/ram/security-credentials/"
|
||||
|
||||
type EcsRamRoleSigner struct {
|
||||
*credentialUpdater
|
||||
sessionCredential *SessionCredential
|
||||
credential *credentials.EcsRamRoleCredential
|
||||
commonApi func(request *requests.CommonRequest, signer interface{}) (response *responses.CommonResponse, err error)
|
||||
}
|
||||
|
||||
func NewEcsRamRoleSigner(credential *credentials.EcsRamRoleCredential, commonApi func(*requests.CommonRequest, interface{}) (response *responses.CommonResponse, err error)) (signer *EcsRamRoleSigner) {
|
||||
signer = &EcsRamRoleSigner{
|
||||
credential: credential,
|
||||
commonApi: commonApi,
|
||||
}
|
||||
|
||||
signer.credentialUpdater = &credentialUpdater{
|
||||
credentialExpiration: defaultDurationSeconds / 60,
|
||||
buildRequestMethod: signer.buildCommonRequest,
|
||||
responseCallBack: signer.refreshCredential,
|
||||
refreshApi: signer.refreshApi,
|
||||
}
|
||||
|
||||
return signer
|
||||
}
|
||||
|
||||
func (*EcsRamRoleSigner) GetName() string {
|
||||
return "HMAC-SHA1"
|
||||
}
|
||||
|
||||
func (*EcsRamRoleSigner) GetType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (*EcsRamRoleSigner) GetVersion() string {
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
func (signer *EcsRamRoleSigner) GetAccessKeyId() (accessKeyId string, err error) {
|
||||
if signer.sessionCredential == nil || signer.needUpdateCredential() {
|
||||
err = signer.updateCredential()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
if signer.sessionCredential == nil || len(signer.sessionCredential.AccessKeyId) <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
return signer.sessionCredential.AccessKeyId, nil
|
||||
}
|
||||
|
||||
func (signer *EcsRamRoleSigner) GetExtraParam() map[string]string {
|
||||
if signer.sessionCredential == nil {
|
||||
return make(map[string]string)
|
||||
}
|
||||
if len(signer.sessionCredential.StsToken) <= 0 {
|
||||
return make(map[string]string)
|
||||
}
|
||||
return map[string]string{"SecurityToken": signer.sessionCredential.StsToken}
|
||||
}
|
||||
|
||||
func (signer *EcsRamRoleSigner) Sign(stringToSign, secretSuffix string) string {
|
||||
secret := signer.sessionCredential.AccessKeyId + secretSuffix
|
||||
return ShaHmac1(stringToSign, secret)
|
||||
}
|
||||
|
||||
func (signer *EcsRamRoleSigner) buildCommonRequest() (request *requests.CommonRequest, err error) {
|
||||
return
|
||||
}
|
||||
|
||||
func (signer *EcsRamRoleSigner) refreshApi(request *requests.CommonRequest) (response *responses.CommonResponse, err error) {
|
||||
requestUrl := securityCredURL + signer.credential.RoleName
|
||||
httpRequest, err := http.NewRequest(requests.GET, requestUrl, strings.NewReader(""))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("refresh Ecs sts token err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
httpClient := &http.Client{}
|
||||
httpResponse, err := httpClient.Do(httpRequest)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("refresh Ecs sts token err: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
response = responses.NewCommonResponse()
|
||||
err = responses.Unmarshal(response, httpResponse, "")
|
||||
return
|
||||
}
|
||||
|
||||
func (signer *EcsRamRoleSigner) refreshCredential(response *responses.CommonResponse) (err error) {
|
||||
if response.GetHttpStatus() != http.StatusOK {
|
||||
return fmt.Errorf("refresh Ecs sts token err, httpStatus: %d, message = %s", response.GetHttpStatus(), response.GetHttpContentString())
|
||||
}
|
||||
var data interface{}
|
||||
err = json.Unmarshal(response.GetHttpContentBytes(), &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh Ecs sts token err, json.Unmarshal fail: %s", err.Error())
|
||||
}
|
||||
code, err := jmespath.Search("Code", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh Ecs sts token err, fail to get Code: %s", err.Error())
|
||||
}
|
||||
if code.(string) != "Success" {
|
||||
return fmt.Errorf("refresh Ecs sts token err, Code is not Success")
|
||||
}
|
||||
accessKeyId, err := jmespath.Search("AccessKeyId", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh Ecs sts token err, fail to get AccessKeyId: %s", err.Error())
|
||||
}
|
||||
accessKeySecret, err := jmespath.Search("AccessKeySecret", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh Ecs sts token err, fail to get AccessKeySecret: %s", err.Error())
|
||||
}
|
||||
securityToken, err := jmespath.Search("SecurityToken", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh Ecs sts token err, fail to get SecurityToken: %s", err.Error())
|
||||
}
|
||||
expiration, err := jmespath.Search("Expiration", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh Ecs sts token err, fail to get Expiration: %s", err.Error())
|
||||
}
|
||||
if accessKeyId == nil || accessKeySecret == nil || securityToken == nil || expiration == nil {
|
||||
return
|
||||
}
|
||||
|
||||
expirationTime, err := time.Parse("2006-01-02T15:04:05Z", expiration.(string))
|
||||
signer.credentialExpiration = int(expirationTime.Unix() - time.Now().Unix())
|
||||
signer.sessionCredential = &SessionCredential{
|
||||
AccessKeyId: accessKeyId.(string),
|
||||
AccessKeySecret: accessKeySecret.(string),
|
||||
StsToken: securityToken.(string),
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (signer *EcsRamRoleSigner) GetSessionCredential() *SessionCredential {
|
||||
return signer.sessionCredential
|
||||
}
|
147
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_key_pair.go
generated
vendored
Normal file
147
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_key_pair.go
generated
vendored
Normal file
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
* 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 signers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
"github.com/jmespath/go-jmespath"
|
||||
)
|
||||
|
||||
type SignerKeyPair struct {
|
||||
*credentialUpdater
|
||||
sessionCredential *SessionCredential
|
||||
credential *credentials.RsaKeyPairCredential
|
||||
commonApi func(request *requests.CommonRequest, signer interface{}) (response *responses.CommonResponse, err error)
|
||||
}
|
||||
|
||||
func NewSignerKeyPair(credential *credentials.RsaKeyPairCredential, commonApi func(*requests.CommonRequest, interface{}) (response *responses.CommonResponse, err error)) (signer *SignerKeyPair, err error) {
|
||||
signer = &SignerKeyPair{
|
||||
credential: credential,
|
||||
commonApi: commonApi,
|
||||
}
|
||||
|
||||
signer.credentialUpdater = &credentialUpdater{
|
||||
credentialExpiration: credential.SessionExpiration,
|
||||
buildRequestMethod: signer.buildCommonRequest,
|
||||
responseCallBack: signer.refreshCredential,
|
||||
refreshApi: signer.refreshApi,
|
||||
}
|
||||
|
||||
if credential.SessionExpiration > 0 {
|
||||
if credential.SessionExpiration >= 900 && credential.SessionExpiration <= 3600 {
|
||||
signer.credentialExpiration = credential.SessionExpiration
|
||||
} else {
|
||||
err = errors.NewClientError(errors.InvalidParamErrorCode, "Key Pair session duration should be in the range of 15min - 1Hr", nil)
|
||||
}
|
||||
} else {
|
||||
signer.credentialExpiration = defaultDurationSeconds
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (*SignerKeyPair) GetName() string {
|
||||
return "HMAC-SHA1"
|
||||
}
|
||||
|
||||
func (*SignerKeyPair) GetType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (*SignerKeyPair) GetVersion() string {
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
func (signer *SignerKeyPair) ensureCredential() error {
|
||||
if signer.sessionCredential == nil || signer.needUpdateCredential() {
|
||||
return signer.updateCredential()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (signer *SignerKeyPair) GetAccessKeyId() (accessKeyId string, err error) {
|
||||
err = signer.ensureCredential()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if signer.sessionCredential == nil || len(signer.sessionCredential.AccessKeyId) <= 0 {
|
||||
accessKeyId = ""
|
||||
return
|
||||
}
|
||||
|
||||
accessKeyId = signer.sessionCredential.AccessKeyId
|
||||
return
|
||||
}
|
||||
|
||||
func (signer *SignerKeyPair) GetExtraParam() map[string]string {
|
||||
return make(map[string]string)
|
||||
}
|
||||
|
||||
func (signer *SignerKeyPair) Sign(stringToSign, secretSuffix string) string {
|
||||
secret := signer.sessionCredential.AccessKeyId + secretSuffix
|
||||
return ShaHmac1(stringToSign, secret)
|
||||
}
|
||||
|
||||
func (signer *SignerKeyPair) buildCommonRequest() (request *requests.CommonRequest, err error) {
|
||||
request = requests.NewCommonRequest()
|
||||
request.Product = "Sts"
|
||||
request.Version = "2015-04-01"
|
||||
request.ApiName = "GenerateSessionAccessKey"
|
||||
request.Scheme = requests.HTTPS
|
||||
request.QueryParams["PublicKeyId"] = signer.credential.PublicKeyId
|
||||
request.QueryParams["DurationSeconds"] = strconv.Itoa(signer.credentialExpiration)
|
||||
return
|
||||
}
|
||||
|
||||
func (signer *SignerKeyPair) refreshApi(request *requests.CommonRequest) (response *responses.CommonResponse, err error) {
|
||||
signerV2 := NewSignerV2(signer.credential)
|
||||
return signer.commonApi(request, signerV2)
|
||||
}
|
||||
|
||||
func (signer *SignerKeyPair) refreshCredential(response *responses.CommonResponse) (err error) {
|
||||
if response.GetHttpStatus() != http.StatusOK {
|
||||
message := "refresh session AccessKey failed"
|
||||
err = errors.NewServerError(response.GetHttpStatus(), response.GetHttpContentString(), message)
|
||||
return
|
||||
}
|
||||
var data interface{}
|
||||
err = json.Unmarshal(response.GetHttpContentBytes(), &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh KeyPair err, json.Unmarshal fail: %s", err.Error())
|
||||
}
|
||||
accessKeyId, err := jmespath.Search("SessionAccessKey.SessionAccessKeyId", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh KeyPair err, fail to get SessionAccessKeyId: %s", err.Error())
|
||||
}
|
||||
accessKeySecret, err := jmespath.Search("SessionAccessKey.SessionAccessKeySecret", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh KeyPair err, fail to get SessionAccessKeySecret: %s", err.Error())
|
||||
}
|
||||
if accessKeyId == nil || accessKeySecret == nil {
|
||||
return
|
||||
}
|
||||
signer.sessionCredential = &SessionCredential{
|
||||
AccessKeyId: accessKeyId.(string),
|
||||
AccessKeySecret: accessKeySecret.(string),
|
||||
}
|
||||
return
|
||||
}
|
172
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_ram_role_arn.go
generated
vendored
Normal file
172
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_ram_role_arn.go
generated
vendored
Normal file
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* 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 signers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
"github.com/jmespath/go-jmespath"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultDurationSeconds = 3600
|
||||
)
|
||||
|
||||
type RamRoleArnSigner struct {
|
||||
*credentialUpdater
|
||||
roleSessionName string
|
||||
sessionCredential *SessionCredential
|
||||
credential *credentials.RamRoleArnCredential
|
||||
commonApi func(request *requests.CommonRequest, signer interface{}) (response *responses.CommonResponse, err error)
|
||||
}
|
||||
|
||||
func NewRamRoleArnSigner(credential *credentials.RamRoleArnCredential, commonApi func(request *requests.CommonRequest, signer interface{}) (response *responses.CommonResponse, err error)) (signer *RamRoleArnSigner, err error) {
|
||||
signer = &RamRoleArnSigner{
|
||||
credential: credential,
|
||||
commonApi: commonApi,
|
||||
}
|
||||
|
||||
signer.credentialUpdater = &credentialUpdater{
|
||||
credentialExpiration: credential.RoleSessionExpiration,
|
||||
buildRequestMethod: signer.buildCommonRequest,
|
||||
responseCallBack: signer.refreshCredential,
|
||||
refreshApi: signer.refreshApi,
|
||||
}
|
||||
|
||||
if len(credential.RoleSessionName) > 0 {
|
||||
signer.roleSessionName = credential.RoleSessionName
|
||||
} else {
|
||||
signer.roleSessionName = "aliyun-go-sdk-" + strconv.FormatInt(time.Now().UnixNano()/1000, 10)
|
||||
}
|
||||
if credential.RoleSessionExpiration > 0 {
|
||||
if credential.RoleSessionExpiration >= 900 && credential.RoleSessionExpiration <= 3600 {
|
||||
signer.credentialExpiration = credential.RoleSessionExpiration
|
||||
} else {
|
||||
err = errors.NewClientError(errors.InvalidParamErrorCode, "Assume Role session duration should be in the range of 15min - 1Hr", nil)
|
||||
}
|
||||
} else {
|
||||
signer.credentialExpiration = defaultDurationSeconds
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (*RamRoleArnSigner) GetName() string {
|
||||
return "HMAC-SHA1"
|
||||
}
|
||||
|
||||
func (*RamRoleArnSigner) GetType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (*RamRoleArnSigner) GetVersion() string {
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
func (signer *RamRoleArnSigner) GetAccessKeyId() (accessKeyId string, err error) {
|
||||
if signer.sessionCredential == nil || signer.needUpdateCredential() {
|
||||
err = signer.updateCredential()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if signer.sessionCredential == nil || len(signer.sessionCredential.AccessKeyId) <= 0 {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return signer.sessionCredential.AccessKeyId, nil
|
||||
}
|
||||
|
||||
func (signer *RamRoleArnSigner) GetExtraParam() map[string]string {
|
||||
if signer.sessionCredential == nil || signer.needUpdateCredential() {
|
||||
signer.updateCredential()
|
||||
}
|
||||
if signer.sessionCredential == nil || len(signer.sessionCredential.StsToken) <= 0 {
|
||||
return make(map[string]string)
|
||||
}
|
||||
return map[string]string{"SecurityToken": signer.sessionCredential.StsToken}
|
||||
}
|
||||
|
||||
func (signer *RamRoleArnSigner) Sign(stringToSign, secretSuffix string) string {
|
||||
secret := signer.sessionCredential.AccessKeySecret + secretSuffix
|
||||
return ShaHmac1(stringToSign, secret)
|
||||
}
|
||||
|
||||
func (signer *RamRoleArnSigner) buildCommonRequest() (request *requests.CommonRequest, err error) {
|
||||
request = requests.NewCommonRequest()
|
||||
request.Product = "Sts"
|
||||
request.Version = "2015-04-01"
|
||||
request.ApiName = "AssumeRole"
|
||||
request.Scheme = requests.HTTPS
|
||||
request.QueryParams["RoleArn"] = signer.credential.RoleArn
|
||||
request.QueryParams["RoleSessionName"] = signer.credential.RoleSessionName
|
||||
request.QueryParams["DurationSeconds"] = strconv.Itoa(signer.credentialExpiration)
|
||||
return
|
||||
}
|
||||
|
||||
func (signer *RamRoleArnSigner) refreshApi(request *requests.CommonRequest) (response *responses.CommonResponse, err error) {
|
||||
credential := &credentials.AccessKeyCredential{
|
||||
AccessKeyId: signer.credential.AccessKeyId,
|
||||
AccessKeySecret: signer.credential.AccessKeySecret,
|
||||
}
|
||||
signerV1 := NewAccessKeySigner(credential)
|
||||
return signer.commonApi(request, signerV1)
|
||||
}
|
||||
|
||||
func (signer *RamRoleArnSigner) refreshCredential(response *responses.CommonResponse) (err error) {
|
||||
if response.GetHttpStatus() != http.StatusOK {
|
||||
message := "refresh session token failed"
|
||||
err = errors.NewServerError(response.GetHttpStatus(), response.GetHttpContentString(), message)
|
||||
return
|
||||
}
|
||||
var data interface{}
|
||||
err = json.Unmarshal(response.GetHttpContentBytes(), &data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh RoleArn sts token err, json.Unmarshal fail: %s", err.Error())
|
||||
}
|
||||
accessKeyId, err := jmespath.Search("Credentials.AccessKeyId", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh RoleArn sts token err, fail to get AccessKeyId: %s", err.Error())
|
||||
}
|
||||
accessKeySecret, err := jmespath.Search("Credentials.AccessKeySecret", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh RoleArn sts token err, fail to get AccessKeySecret: %s", err.Error())
|
||||
}
|
||||
securityToken, err := jmespath.Search("Credentials.SecurityToken", data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("refresh RoleArn sts token err, fail to get SecurityToken: %s", err.Error())
|
||||
}
|
||||
if accessKeyId == nil || accessKeySecret == nil || securityToken == nil {
|
||||
return
|
||||
}
|
||||
signer.sessionCredential = &SessionCredential{
|
||||
AccessKeyId: accessKeyId.(string),
|
||||
AccessKeySecret: accessKeySecret.(string),
|
||||
StsToken: securityToken.(string),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (signer *RamRoleArnSigner) GetSessionCredential() *SessionCredential {
|
||||
return signer.sessionCredential
|
||||
}
|
54
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_sts_token.go
generated
vendored
Normal file
54
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_sts_token.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 signers
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
)
|
||||
|
||||
type StsTokenSigner struct {
|
||||
credential *credentials.StsTokenCredential
|
||||
}
|
||||
|
||||
func NewStsTokenSigner(credential *credentials.StsTokenCredential) *StsTokenSigner {
|
||||
return &StsTokenSigner{
|
||||
credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
func (*StsTokenSigner) GetName() string {
|
||||
return "HMAC-SHA1"
|
||||
}
|
||||
|
||||
func (*StsTokenSigner) GetType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (*StsTokenSigner) GetVersion() string {
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
func (signer *StsTokenSigner) GetAccessKeyId() (accessKeyId string, err error) {
|
||||
return signer.credential.AccessKeyId, nil
|
||||
}
|
||||
|
||||
func (signer *StsTokenSigner) GetExtraParam() map[string]string {
|
||||
return map[string]string{"SecurityToken": signer.credential.AccessKeyStsToken}
|
||||
}
|
||||
|
||||
func (signer *StsTokenSigner) Sign(stringToSign, secretSuffix string) string {
|
||||
secret := signer.credential.AccessKeySecret + secretSuffix
|
||||
return ShaHmac1(stringToSign, secret)
|
||||
}
|
54
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_v2.go
generated
vendored
Normal file
54
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers/signer_v2.go
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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 signers
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
)
|
||||
|
||||
type SignerV2 struct {
|
||||
credential *credentials.RsaKeyPairCredential
|
||||
}
|
||||
|
||||
func (signer *SignerV2) GetExtraParam() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSignerV2(credential *credentials.RsaKeyPairCredential) *SignerV2 {
|
||||
return &SignerV2{
|
||||
credential: credential,
|
||||
}
|
||||
}
|
||||
|
||||
func (*SignerV2) GetName() string {
|
||||
return "SHA256withRSA"
|
||||
}
|
||||
|
||||
func (*SignerV2) GetType() string {
|
||||
return "PRIVATEKEY"
|
||||
}
|
||||
|
||||
func (*SignerV2) GetVersion() string {
|
||||
return "1.0"
|
||||
}
|
||||
|
||||
func (signer *SignerV2) GetAccessKeyId() (accessKeyId string, err error) {
|
||||
return signer.credential.PublicKeyId, err
|
||||
}
|
||||
|
||||
func (signer *SignerV2) Sign(stringToSign, secretSuffix string) string {
|
||||
secret := signer.credential.PrivateKey
|
||||
return Sha256WithRsa(stringToSign, secret)
|
||||
}
|
|
@ -0,0 +1,442 @@
|
|||
/*
|
||||
* 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 sdk
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
|
||||
)
|
||||
|
||||
var debug utils.Debug
|
||||
|
||||
func init() {
|
||||
debug = utils.Init("sdk")
|
||||
}
|
||||
|
||||
// Version this value will be replaced while build: -ldflags="-X sdk.version=x.x.x"
|
||||
var Version = "0.0.1"
|
||||
|
||||
var DefaultUserAgent = fmt.Sprintf("AlibabaCloud (%s; %s) Golang/%s Core/%s", runtime.GOOS, runtime.GOARCH, strings.Trim(runtime.Version(), "go"), Version)
|
||||
|
||||
var hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) {
|
||||
return fn
|
||||
}
|
||||
|
||||
// Client the type Client
|
||||
type Client struct {
|
||||
regionId string
|
||||
config *Config
|
||||
userAgent map[string]string
|
||||
signer auth.Signer
|
||||
httpClient *http.Client
|
||||
asyncTaskQueue chan func()
|
||||
|
||||
debug bool
|
||||
isRunning bool
|
||||
// void "panic(write to close channel)" cause of addAsync() after Shutdown()
|
||||
asyncChanLock *sync.RWMutex
|
||||
}
|
||||
|
||||
func (client *Client) Init() (err error) {
|
||||
panic("not support yet")
|
||||
}
|
||||
|
||||
func (client *Client) InitWithOptions(regionId string, config *Config, credential auth.Credential) (err error) {
|
||||
client.isRunning = true
|
||||
client.asyncChanLock = new(sync.RWMutex)
|
||||
client.regionId = regionId
|
||||
client.config = config
|
||||
client.httpClient = &http.Client{}
|
||||
|
||||
if config.HttpTransport != nil {
|
||||
client.httpClient.Transport = config.HttpTransport
|
||||
}
|
||||
|
||||
if config.Timeout > 0 {
|
||||
client.httpClient.Timeout = config.Timeout
|
||||
}
|
||||
|
||||
if config.EnableAsync {
|
||||
client.EnableAsync(config.GoRoutinePoolSize, config.MaxTaskQueueSize)
|
||||
}
|
||||
|
||||
client.signer, err = auth.NewSignerWithCredential(credential, client.ProcessCommonRequestWithSigner)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// EnableAsync enable the async task queue
|
||||
func (client *Client) EnableAsync(routinePoolSize, maxTaskQueueSize int) {
|
||||
client.asyncTaskQueue = make(chan func(), maxTaskQueueSize)
|
||||
for i := 0; i < routinePoolSize; i++ {
|
||||
go func() {
|
||||
for client.isRunning {
|
||||
select {
|
||||
case task, notClosed := <-client.asyncTaskQueue:
|
||||
if notClosed {
|
||||
task()
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) InitWithAccessKey(regionId, accessKeyId, accessKeySecret string) (err error) {
|
||||
config := client.InitClientConfig()
|
||||
credential := &credentials.BaseCredential{
|
||||
AccessKeyId: accessKeyId,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
}
|
||||
return client.InitWithOptions(regionId, config, credential)
|
||||
}
|
||||
|
||||
func (client *Client) InitWithStsToken(regionId, accessKeyId, accessKeySecret, securityToken string) (err error) {
|
||||
config := client.InitClientConfig()
|
||||
credential := &credentials.StsTokenCredential{
|
||||
AccessKeyId: accessKeyId,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
AccessKeyStsToken: securityToken,
|
||||
}
|
||||
return client.InitWithOptions(regionId, config, credential)
|
||||
}
|
||||
|
||||
func (client *Client) InitWithRamRoleArn(regionId, accessKeyId, accessKeySecret, roleArn, roleSessionName string) (err error) {
|
||||
config := client.InitClientConfig()
|
||||
credential := &credentials.RamRoleArnCredential{
|
||||
AccessKeyId: accessKeyId,
|
||||
AccessKeySecret: accessKeySecret,
|
||||
RoleArn: roleArn,
|
||||
RoleSessionName: roleSessionName,
|
||||
}
|
||||
return client.InitWithOptions(regionId, config, credential)
|
||||
}
|
||||
|
||||
func (client *Client) InitWithRsaKeyPair(regionId, publicKeyId, privateKey string, sessionExpiration int) (err error) {
|
||||
config := client.InitClientConfig()
|
||||
credential := &credentials.RsaKeyPairCredential{
|
||||
PrivateKey: privateKey,
|
||||
PublicKeyId: publicKeyId,
|
||||
SessionExpiration: sessionExpiration,
|
||||
}
|
||||
return client.InitWithOptions(regionId, config, credential)
|
||||
}
|
||||
|
||||
func (client *Client) InitWithEcsRamRole(regionId, roleName string) (err error) {
|
||||
config := client.InitClientConfig()
|
||||
credential := &credentials.EcsRamRoleCredential{
|
||||
RoleName: roleName,
|
||||
}
|
||||
return client.InitWithOptions(regionId, config, credential)
|
||||
}
|
||||
|
||||
func (client *Client) InitClientConfig() (config *Config) {
|
||||
if client.config != nil {
|
||||
return client.config
|
||||
} else {
|
||||
return NewConfig()
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) DoAction(request requests.AcsRequest, response responses.AcsResponse) (err error) {
|
||||
return client.DoActionWithSigner(request, response, nil)
|
||||
}
|
||||
|
||||
func (client *Client) buildRequestWithSigner(request requests.AcsRequest, signer auth.Signer) (httpRequest *http.Request, err error) {
|
||||
// add clientVersion
|
||||
request.GetHeaders()["x-sdk-core-version"] = Version
|
||||
|
||||
regionId := client.regionId
|
||||
if len(request.GetRegionId()) > 0 {
|
||||
regionId = request.GetRegionId()
|
||||
}
|
||||
|
||||
// resolve endpoint
|
||||
resolveParam := &endpoints.ResolveParam{
|
||||
Domain: request.GetDomain(),
|
||||
Product: request.GetProduct(),
|
||||
RegionId: regionId,
|
||||
LocationProduct: request.GetLocationServiceCode(),
|
||||
LocationEndpointType: request.GetLocationEndpointType(),
|
||||
CommonApi: client.ProcessCommonRequest,
|
||||
}
|
||||
endpoint, err := endpoints.Resolve(resolveParam)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request.SetDomain(endpoint)
|
||||
if request.GetScheme() == "" {
|
||||
request.SetScheme(client.config.Scheme)
|
||||
}
|
||||
// init request params
|
||||
err = requests.InitParams(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// signature
|
||||
var finalSigner auth.Signer
|
||||
if signer != nil {
|
||||
finalSigner = signer
|
||||
} else {
|
||||
finalSigner = client.signer
|
||||
}
|
||||
httpRequest, err = buildHttpRequest(request, finalSigner, regionId)
|
||||
if err == nil {
|
||||
userAgent := DefaultUserAgent + getSendUserAgent(client.config.UserAgent, client.userAgent, request.GetUserAgent())
|
||||
httpRequest.Header.Set("User-Agent", userAgent)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func getSendUserAgent(configUserAgent string, clientUserAgent, requestUserAgent map[string]string) string {
|
||||
realUserAgent := ""
|
||||
for key1, value1 := range clientUserAgent {
|
||||
for key2, _ := range requestUserAgent {
|
||||
if key1 == key2 {
|
||||
key1 = ""
|
||||
}
|
||||
}
|
||||
if key1 != "" {
|
||||
realUserAgent += fmt.Sprintf(" %s/%s", key1, value1)
|
||||
|
||||
}
|
||||
}
|
||||
for key, value := range requestUserAgent {
|
||||
realUserAgent += fmt.Sprintf(" %s/%s", key, value)
|
||||
}
|
||||
if configUserAgent != "" {
|
||||
return realUserAgent + fmt.Sprintf(" Extra/%s", configUserAgent)
|
||||
}
|
||||
return realUserAgent
|
||||
}
|
||||
|
||||
func (client *Client) AppendUserAgent(key, value string) {
|
||||
newkey := true
|
||||
|
||||
if client.userAgent == nil {
|
||||
client.userAgent = make(map[string]string)
|
||||
}
|
||||
if strings.ToLower(key) != "core" && strings.ToLower(key) != "go" {
|
||||
for tag, _ := range client.userAgent {
|
||||
if tag == key {
|
||||
client.userAgent[tag] = value
|
||||
newkey = false
|
||||
}
|
||||
}
|
||||
if newkey {
|
||||
client.userAgent[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (client *Client) BuildRequestWithSigner(request requests.AcsRequest, signer auth.Signer) (err error) {
|
||||
_, err = client.buildRequestWithSigner(request, signer)
|
||||
return
|
||||
}
|
||||
|
||||
func (client *Client) DoActionWithSigner(request requests.AcsRequest, response responses.AcsResponse, signer auth.Signer) (err error) {
|
||||
httpRequest, err := client.buildRequestWithSigner(request, signer)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var httpResponse *http.Response
|
||||
for retryTimes := 0; retryTimes <= client.config.MaxRetryTime; retryTimes++ {
|
||||
debug("> %s %s %s", httpRequest.Method, httpRequest.URL.RequestURI(), httpRequest.Proto)
|
||||
debug("> Host: %s", httpRequest.Host)
|
||||
for key, value := range httpRequest.Header {
|
||||
debug("> %s: %v", key, strings.Join(value, ""))
|
||||
}
|
||||
debug(">")
|
||||
httpResponse, err = hookDo(client.httpClient.Do)(httpRequest)
|
||||
if err == nil {
|
||||
debug("< %s %s", httpResponse.Proto, httpResponse.Status)
|
||||
for key, value := range httpResponse.Header {
|
||||
debug("< %s: %v", key, strings.Join(value, ""))
|
||||
}
|
||||
}
|
||||
debug("<")
|
||||
// receive error
|
||||
if err != nil {
|
||||
if !client.config.AutoRetry {
|
||||
return
|
||||
} else if retryTimes >= client.config.MaxRetryTime {
|
||||
// timeout but reached the max retry times, return
|
||||
timeoutErrorMsg := fmt.Sprintf(errors.TimeoutErrorMessage, strconv.Itoa(retryTimes+1), strconv.Itoa(retryTimes+1))
|
||||
err = errors.NewClientError(errors.TimeoutErrorCode, timeoutErrorMsg, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
// if status code >= 500 or timeout, will trigger retry
|
||||
if client.config.AutoRetry && (err != nil || isServerError(httpResponse)) {
|
||||
// rewrite signatureNonce and signature
|
||||
httpRequest, err = client.buildRequestWithSigner(request, signer)
|
||||
// buildHttpRequest(request, finalSigner, regionId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
err = responses.Unmarshal(response, httpResponse, request.GetAcceptFormat())
|
||||
// wrap server errors
|
||||
if serverErr, ok := err.(*errors.ServerError); ok {
|
||||
var wrapInfo = map[string]string{}
|
||||
wrapInfo["StringToSign"] = request.GetStringToSign()
|
||||
err = errors.WrapServerError(serverErr, wrapInfo)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func buildHttpRequest(request requests.AcsRequest, singer auth.Signer, regionId string) (httpRequest *http.Request, err error) {
|
||||
err = auth.Sign(request, singer, regionId)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
requestMethod := request.GetMethod()
|
||||
requestUrl := request.BuildUrl()
|
||||
body := request.GetBodyReader()
|
||||
httpRequest, err = http.NewRequest(requestMethod, requestUrl, body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for key, value := range request.GetHeaders() {
|
||||
httpRequest.Header[key] = []string{value}
|
||||
}
|
||||
// host is a special case
|
||||
if host, containsHost := request.GetHeaders()["Host"]; containsHost {
|
||||
httpRequest.Host = host
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isServerError(httpResponse *http.Response) bool {
|
||||
return httpResponse.StatusCode >= http.StatusInternalServerError
|
||||
}
|
||||
|
||||
/**
|
||||
only block when any one of the following occurs:
|
||||
1. the asyncTaskQueue is full, increase the queue size to avoid this
|
||||
2. Shutdown() in progressing, the client is being closed
|
||||
**/
|
||||
func (client *Client) AddAsyncTask(task func()) (err error) {
|
||||
if client.asyncTaskQueue != nil {
|
||||
client.asyncChanLock.RLock()
|
||||
defer client.asyncChanLock.RUnlock()
|
||||
if client.isRunning {
|
||||
client.asyncTaskQueue <- task
|
||||
}
|
||||
} else {
|
||||
err = errors.NewClientError(errors.AsyncFunctionNotEnabledCode, errors.AsyncFunctionNotEnabledMessage, nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (client *Client) GetConfig() *Config {
|
||||
return client.config
|
||||
}
|
||||
|
||||
func NewClient() (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.Init()
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientWithOptions(regionId string, config *Config, credential auth.Credential) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithOptions(regionId, config, credential)
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientWithAccessKey(regionId, accessKeyId, accessKeySecret string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithAccessKey(regionId, accessKeyId, accessKeySecret)
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientWithStsToken(regionId, stsAccessKeyId, stsAccessKeySecret, stsToken string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithStsToken(regionId, stsAccessKeyId, stsAccessKeySecret, stsToken)
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientWithRamRoleArn(regionId string, accessKeyId, accessKeySecret, roleArn, roleSessionName string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithRamRoleArn(regionId, accessKeyId, accessKeySecret, roleArn, roleSessionName)
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientWithEcsRamRole(regionId string, roleName string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithEcsRamRole(regionId, roleName)
|
||||
return
|
||||
}
|
||||
|
||||
func NewClientWithRsaKeyPair(regionId string, publicKeyId, privateKey string, sessionExpiration int) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithRsaKeyPair(regionId, publicKeyId, privateKey, sessionExpiration)
|
||||
return
|
||||
}
|
||||
|
||||
// Deprecated: Use NewClientWithRamRoleArn in this package instead.
|
||||
func NewClientWithStsRoleArn(regionId string, accessKeyId, accessKeySecret, roleArn, roleSessionName string) (client *Client, err error) {
|
||||
return NewClientWithRamRoleArn(regionId, accessKeyId, accessKeySecret, roleArn, roleSessionName)
|
||||
}
|
||||
|
||||
// Deprecated: Use NewClientWithEcsRamRole in this package instead.
|
||||
func NewClientWithStsRoleNameOnEcs(regionId string, roleName string) (client *Client, err error) {
|
||||
return NewClientWithEcsRamRole(regionId, roleName)
|
||||
}
|
||||
|
||||
func (client *Client) ProcessCommonRequest(request *requests.CommonRequest) (response *responses.CommonResponse, err error) {
|
||||
request.TransToAcsRequest()
|
||||
response = responses.NewCommonResponse()
|
||||
err = client.DoAction(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
func (client *Client) ProcessCommonRequestWithSigner(request *requests.CommonRequest, signerInterface interface{}) (response *responses.CommonResponse, err error) {
|
||||
if signer, isSigner := signerInterface.(auth.Signer); isSigner {
|
||||
request.TransToAcsRequest()
|
||||
response = responses.NewCommonResponse()
|
||||
err = client.DoActionWithSigner(request, response, signer)
|
||||
return
|
||||
}
|
||||
panic("should not be here")
|
||||
}
|
||||
|
||||
func (client *Client) Shutdown() {
|
||||
// lock the addAsync()
|
||||
client.asyncChanLock.Lock()
|
||||
defer client.asyncChanLock.Unlock()
|
||||
if client.asyncTaskQueue != nil {
|
||||
close(client.asyncTaskQueue)
|
||||
}
|
||||
client.isRunning = false
|
||||
}
|
|
@ -0,0 +1,409 @@
|
|||
/*
|
||||
* 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 sdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type signertest struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (s *signertest) GetName() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *signertest) GetType() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *signertest) GetVersion() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *signertest) GetAccessKeyId() (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (s *signertest) GetExtraParam() map[string]string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *signertest) Sign(stringToSign, secretSuffix string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func Test_Client(t *testing.T) {
|
||||
defer func() {
|
||||
err := recover()
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "not support yet", err)
|
||||
}()
|
||||
NewClient()
|
||||
}
|
||||
|
||||
func Test_NewClientWithOptions(t *testing.T) {
|
||||
c := NewConfig()
|
||||
c.HttpTransport = &http.Transport{
|
||||
IdleConnTimeout: time.Duration(10 * time.Second),
|
||||
}
|
||||
c.EnableAsync = true
|
||||
c.GoRoutinePoolSize = 1
|
||||
c.MaxTaskQueueSize = 1
|
||||
credential := credentials.NewAccessKeyCredential("acesskeyid", "accesskeysecret")
|
||||
client, err := NewClientWithOptions("regionid", c, credential)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
}
|
||||
|
||||
func Test_NewClientWithAccessKey(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
}
|
||||
|
||||
func Test_NewClientWithStsToken(t *testing.T) {
|
||||
client, err := NewClientWithStsToken("regionid", "acesskeyid", "accesskeysecret", "token")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
}
|
||||
|
||||
func Test_NewClientWithRamRoleArn(t *testing.T) {
|
||||
client, err := NewClientWithRamRoleArn("regionid", "acesskeyid", "accesskeysecret", "roleArn", "roleSessionName")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
config := client.InitClientConfig()
|
||||
assert.NotNil(t, config)
|
||||
}
|
||||
|
||||
func Test_NewClientWithEcsRamRole(t *testing.T) {
|
||||
client, err := NewClientWithEcsRamRole("regionid", "roleName")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
}
|
||||
|
||||
func Test_NewClientWithRsaKeyPair(t *testing.T) {
|
||||
client, err := NewClientWithRsaKeyPair("regionid", "publicKey", "privateKey", 3600)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
}
|
||||
|
||||
func mockResponse(statusCode int, content string) (res *http.Response, err error) {
|
||||
status := strconv.Itoa(statusCode)
|
||||
res = &http.Response{
|
||||
Proto: "HTTP/1.1",
|
||||
ProtoMajor: 1,
|
||||
Header: make(http.Header),
|
||||
StatusCode: statusCode,
|
||||
Status: status + " " + http.StatusText(statusCode),
|
||||
}
|
||||
res.Body = ioutil.NopCloser(bytes.NewReader([]byte(content)))
|
||||
return
|
||||
}
|
||||
|
||||
func Test_DoAction(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
request := requests.NewCommonRequest()
|
||||
request.Domain = "ecs.aliyuncs.com"
|
||||
request.Version = "2014-05-26"
|
||||
request.ApiName = "DescribeInstanceStatus"
|
||||
|
||||
request.QueryParams["PageNumber"] = "1"
|
||||
request.QueryParams["PageSize"] = "30"
|
||||
request.TransToAcsRequest()
|
||||
response := responses.NewCommonResponse()
|
||||
origTestHookDo := hookDo
|
||||
defer func() { hookDo = origTestHookDo }()
|
||||
hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
return mockResponse(200, "")
|
||||
}
|
||||
}
|
||||
err = client.DoAction(request, response)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, response.GetHttpStatus())
|
||||
assert.Equal(t, "", response.GetHttpContentString())
|
||||
client.Shutdown()
|
||||
assert.Equal(t, false, client.isRunning)
|
||||
}
|
||||
|
||||
func Test_DoAction_Timeout(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
request := requests.NewCommonRequest()
|
||||
request.Domain = "ecs.aliyuncs.com"
|
||||
request.Version = "2014-05-26"
|
||||
request.ApiName = "DescribeInstanceStatus"
|
||||
|
||||
request.QueryParams["PageNumber"] = "1"
|
||||
request.QueryParams["PageSize"] = "30"
|
||||
request.TransToAcsRequest()
|
||||
response := responses.NewCommonResponse()
|
||||
origTestHookDo := hookDo
|
||||
defer func() { hookDo = origTestHookDo }()
|
||||
hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
return mockResponse(200, "")
|
||||
}
|
||||
}
|
||||
err = client.DoAction(request, response)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, response.GetHttpStatus())
|
||||
assert.Equal(t, "", response.GetHttpContentString())
|
||||
client.Shutdown()
|
||||
assert.Equal(t, false, client.isRunning)
|
||||
}
|
||||
|
||||
func Test_ProcessCommonRequest(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
|
||||
request := requests.NewCommonRequest()
|
||||
request.Domain = "ecs.aliyuncs.com"
|
||||
request.Version = "2014-05-26"
|
||||
request.ApiName = "DescribeInstanceStatus"
|
||||
|
||||
request.QueryParams["PageNumber"] = "1"
|
||||
request.QueryParams["PageSize"] = "30"
|
||||
|
||||
origTestHookDo := hookDo
|
||||
defer func() { hookDo = origTestHookDo }()
|
||||
hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
return mockResponse(200, "")
|
||||
}
|
||||
}
|
||||
response, err := client.ProcessCommonRequest(request)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 200, response.GetHttpStatus())
|
||||
assert.Equal(t, "", response.GetHttpContentString())
|
||||
}
|
||||
|
||||
func Test_DoAction_With500(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
request := requests.NewCommonRequest()
|
||||
request.Domain = "ecs.aliyuncs.com"
|
||||
request.Version = "2014-05-26"
|
||||
request.ApiName = "DescribeInstanceStatus"
|
||||
|
||||
request.QueryParams["PageNumber"] = "1"
|
||||
request.QueryParams["PageSize"] = "30"
|
||||
request.TransToAcsRequest()
|
||||
response := responses.NewCommonResponse()
|
||||
origTestHookDo := hookDo
|
||||
defer func() { hookDo = origTestHookDo }()
|
||||
hookDo = func(fn func(req *http.Request) (*http.Response, error)) func(req *http.Request) (*http.Response, error) {
|
||||
return func(req *http.Request) (*http.Response, error) {
|
||||
return mockResponse(500, "Server Internel Error")
|
||||
}
|
||||
}
|
||||
err = client.DoAction(request, response)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "SDK.ServerError\nErrorCode: \nRecommend: \nRequestId: \nMessage: Server Internel Error", err.Error())
|
||||
assert.Equal(t, 500, response.GetHttpStatus())
|
||||
assert.Equal(t, "Server Internel Error", response.GetHttpContentString())
|
||||
}
|
||||
|
||||
func TestClient_BuildRequestWithSigner(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
request := requests.NewCommonRequest()
|
||||
request.Domain = "ecs.aliyuncs.com"
|
||||
request.Version = "2014-05-26"
|
||||
request.ApiName = "DescribeInstanceStatus"
|
||||
|
||||
request.QueryParams["PageNumber"] = "1"
|
||||
request.QueryParams["PageSize"] = "30"
|
||||
request.RegionId = "regionid"
|
||||
request.TransToAcsRequest()
|
||||
client.config.UserAgent = "user_agent"
|
||||
err = client.BuildRequestWithSigner(request, nil)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestClient_BuildRequestWithSigner1(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
request := requests.NewCommonRequest()
|
||||
request.Domain = "ecs.aliyuncs.com"
|
||||
request.Version = "2014-05-26"
|
||||
request.ApiName = "DescribeInstanceStatus"
|
||||
|
||||
request.QueryParams["PageNumber"] = "1"
|
||||
request.QueryParams["PageSize"] = "30"
|
||||
request.RegionId = "regionid"
|
||||
request.TransToAcsRequest()
|
||||
signer := &signertest{
|
||||
name: "signer",
|
||||
}
|
||||
err = client.BuildRequestWithSigner(request, signer)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestClient_ProcessCommonRequestWithSigner(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
request := requests.NewCommonRequest()
|
||||
request.Domain = "ecs.aliyuncs.com"
|
||||
request.Version = "2014-05-26"
|
||||
request.ApiName = "DescribeInstanceStatus"
|
||||
|
||||
request.QueryParams["PageNumber"] = "1"
|
||||
request.QueryParams["PageSize"] = "30"
|
||||
request.RegionId = "regionid"
|
||||
signer := &signertest{
|
||||
name: "signer",
|
||||
}
|
||||
_, err = client.ProcessCommonRequestWithSigner(request, signer)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestClient_AppendUserAgent(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
request := requests.NewCommonRequest()
|
||||
request.Domain = "ecs.aliyuncs.com"
|
||||
request.Version = "2014-05-26"
|
||||
request.ApiName = "DescribeInstanceStatus"
|
||||
|
||||
request.RegionId = "regionid"
|
||||
signer := &signertest{
|
||||
name: "signer",
|
||||
}
|
||||
request.TransToAcsRequest()
|
||||
httpRequest, err := client.buildRequestWithSigner(request, signer)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, DefaultUserAgent, httpRequest.Header.Get("User-Agent"))
|
||||
|
||||
client.AppendUserAgent("test", "1.01")
|
||||
httpRequest, err = client.buildRequestWithSigner(request, signer)
|
||||
assert.Equal(t, DefaultUserAgent+" test/1.01", httpRequest.Header.Get("User-Agent"))
|
||||
|
||||
request.AppendUserAgent("test", "2.01")
|
||||
httpRequest, err = client.buildRequestWithSigner(request, signer)
|
||||
assert.Equal(t, DefaultUserAgent+" test/2.01", httpRequest.Header.Get("User-Agent"))
|
||||
|
||||
request.AppendUserAgent("test", "2.02")
|
||||
httpRequest, err = client.buildRequestWithSigner(request, signer)
|
||||
assert.Equal(t, DefaultUserAgent+" test/2.02", httpRequest.Header.Get("User-Agent"))
|
||||
|
||||
client.AppendUserAgent("test", "2.01")
|
||||
httpRequest, err = client.buildRequestWithSigner(request, signer)
|
||||
assert.Equal(t, DefaultUserAgent+" test/2.02", httpRequest.Header.Get("User-Agent"))
|
||||
|
||||
client.AppendUserAgent("core", "1.01")
|
||||
httpRequest, err = client.buildRequestWithSigner(request, signer)
|
||||
assert.Equal(t, DefaultUserAgent+" test/2.02", httpRequest.Header.Get("User-Agent"))
|
||||
|
||||
request.AppendUserAgent("core", "1.01")
|
||||
httpRequest, err = client.buildRequestWithSigner(request, signer)
|
||||
assert.Equal(t, DefaultUserAgent+" test/2.02", httpRequest.Header.Get("User-Agent"))
|
||||
|
||||
request1 := requests.NewCommonRequest()
|
||||
request1.Domain = "ecs.aliyuncs.com"
|
||||
request1.Version = "2014-05-26"
|
||||
request1.ApiName = "DescribeRegions"
|
||||
request1.RegionId = "regionid"
|
||||
request1.AppendUserAgent("sys", "1.01")
|
||||
request1.TransToAcsRequest()
|
||||
httpRequest, err = client.buildRequestWithSigner(request1, signer)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, DefaultUserAgent+" test/2.01 sys/1.01", httpRequest.Header.Get("User-Agent"))
|
||||
}
|
||||
|
||||
func TestClient_ProcessCommonRequestWithSigner_Error(t *testing.T) {
|
||||
client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
request := requests.NewCommonRequest()
|
||||
request.Domain = "ecs.aliyuncs.com"
|
||||
request.Version = "2014-05-26"
|
||||
request.ApiName = "DescribeInstanceStatus"
|
||||
|
||||
request.QueryParams["PageNumber"] = "1"
|
||||
request.QueryParams["PageSize"] = "30"
|
||||
request.RegionId = "regionid"
|
||||
defer func() {
|
||||
err := recover()
|
||||
assert.NotNil(t, err)
|
||||
}()
|
||||
_, err = client.ProcessCommonRequestWithSigner(request, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestClient_NewClientWithStsRoleNameOnEcs(t *testing.T) {
|
||||
client, err := NewClientWithStsRoleNameOnEcs("regionid", "rolename")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
config := client.GetConfig()
|
||||
assert.NotNil(t, config)
|
||||
err = client.AddAsyncTask(nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestClient_NewClientWithStsRoleArn(t *testing.T) {
|
||||
client, err := NewClientWithStsRoleArn("regionid", "acesskeyid", "accesskeysecret", "rolearn", "rolesessionname")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, client)
|
||||
assert.Equal(t, true, client.isRunning)
|
||||
task := func() {}
|
||||
client.asyncTaskQueue = make(chan func(), 1)
|
||||
err = client.AddAsyncTask(task)
|
||||
assert.Nil(t, err)
|
||||
client.Shutdown()
|
||||
assert.Equal(t, false, client.isRunning)
|
||||
}
|
||||
|
||||
//func Test_EnableAsync(t *testing.T) {
|
||||
// client, err := NewClientWithAccessKey("regionid", "acesskeyid", "accesskeysecret")
|
||||
// assert.Nil(t, err)
|
||||
// assert.NotNil(t, client)
|
||||
// assert.Equal(t, true, client.isRunning)
|
||||
// client.EnableAsync(2, 8)
|
||||
// client.Shutdown()
|
||||
// assert.Equal(t, false, client.isRunning)
|
||||
//}
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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 sdk
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
AutoRetry bool `default:"true"`
|
||||
MaxRetryTime int `default:"3"`
|
||||
UserAgent string `default:""`
|
||||
Debug bool `default:"false"`
|
||||
Timeout time.Duration `default:"10000000000"`
|
||||
HttpTransport *http.Transport `default:""`
|
||||
EnableAsync bool `default:"false"`
|
||||
MaxTaskQueueSize int `default:"1000"`
|
||||
GoRoutinePoolSize int `default:"5"`
|
||||
Scheme string `default:"HTTP"`
|
||||
}
|
||||
|
||||
func NewConfig() (config *Config) {
|
||||
config = &Config{}
|
||||
utils.InitStructWithDefaultTag(config)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Config) WithAutoRetry(isAutoRetry bool) *Config {
|
||||
c.AutoRetry = isAutoRetry
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithMaxRetryTime(maxRetryTime int) *Config {
|
||||
c.MaxRetryTime = maxRetryTime
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithUserAgent(userAgent string) *Config {
|
||||
c.UserAgent = userAgent
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithDebug(isDebug bool) *Config {
|
||||
c.Debug = isDebug
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithTimeout(timeout time.Duration) *Config {
|
||||
c.Timeout = timeout
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithHttpTransport(httpTransport *http.Transport) *Config {
|
||||
c.HttpTransport = httpTransport
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithEnableAsync(isEnableAsync bool) *Config {
|
||||
c.EnableAsync = isEnableAsync
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithMaxTaskQueueSize(maxTaskQueueSize int) *Config {
|
||||
c.MaxTaskQueueSize = maxTaskQueueSize
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithGoRoutinePoolSize(goRoutinePoolSize int) *Config {
|
||||
c.GoRoutinePoolSize = goRoutinePoolSize
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Config) WithScheme(scheme string) *Config {
|
||||
c.Scheme = scheme
|
||||
return c
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package sdk
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_Config(t *testing.T) {
|
||||
config := NewConfig()
|
||||
assert.NotNil(t, config, "NewConfig failed")
|
||||
assert.Equal(t, true, config.AutoRetry, "Default AutoRetry should be true")
|
||||
assert.Equal(t, 3, config.MaxRetryTime, "Default MaxRetryTime should be 3")
|
||||
assert.Equal(t, "", config.UserAgent, "Default UserAgent should be empty")
|
||||
assert.Equal(t, false, config.Debug, "Default AutoRetry should be false")
|
||||
assert.Equal(t, time.Duration(10000000000), config.Timeout, "Default Timeout should be 10000000000")
|
||||
assert.Equal(t, (*http.Transport)(nil), config.HttpTransport, "Default HttpTransport should be nil")
|
||||
assert.Equal(t, false, config.EnableAsync, "Default EnableAsync should be false")
|
||||
assert.Equal(t, 1000, config.MaxTaskQueueSize, "Default MaxTaskQueueSize should be 1000")
|
||||
assert.Equal(t, 5, config.GoRoutinePoolSize, "Default GoRoutinePoolSize should be 5")
|
||||
assert.Equal(t, "HTTP", config.Scheme, "Default Scheme should be HTTP")
|
||||
|
||||
transport := &http.Transport{
|
||||
MaxIdleConns: 10,
|
||||
IdleConnTimeout: 30 * time.Second,
|
||||
DisableCompression: true,
|
||||
}
|
||||
config.
|
||||
WithAutoRetry(false).
|
||||
WithMaxRetryTime(0).
|
||||
WithUserAgent("new user agent").
|
||||
WithDebug(true).
|
||||
WithTimeout(time.Duration(500000)).
|
||||
WithHttpTransport(transport).
|
||||
WithEnableAsync(true).
|
||||
WithMaxTaskQueueSize(1).
|
||||
WithGoRoutinePoolSize(10).
|
||||
WithScheme("HTTPS")
|
||||
|
||||
assert.Equal(t, 0, config.MaxRetryTime)
|
||||
assert.Equal(t, false, config.AutoRetry)
|
||||
assert.Equal(t, "new user agent", config.UserAgent)
|
||||
assert.Equal(t, true, config.Debug)
|
||||
assert.Equal(t, time.Duration(500000), config.Timeout)
|
||||
assert.Equal(t, transport, config.HttpTransport)
|
||||
assert.Equal(t, true, config.EnableAsync)
|
||||
assert.Equal(t, 1, config.MaxTaskQueueSize)
|
||||
assert.Equal(t, 10, config.GoRoutinePoolSize)
|
||||
assert.Equal(t, "HTTPS", config.Scheme)
|
||||
}
|
505
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/endpoints_config.go
generated
vendored
Normal file
505
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/endpoints_config.go
generated
vendored
Normal file
|
@ -0,0 +1,505 @@
|
|||
package endpoints
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const endpointsJson = "{" +
|
||||
" \"products\":[" +
|
||||
" {" +
|
||||
" \"code\": \"aegis\"," +
|
||||
" \"document_id\": \"28449\"," +
|
||||
" \"location_service_code\": \"vipaegis\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"aegis.cn-hangzhou.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"alidns\"," +
|
||||
" \"document_id\": \"29739\"," +
|
||||
" \"location_service_code\": \"alidns\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"alidns.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"arms\"," +
|
||||
" \"document_id\": \"42924\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": [ {" +
|
||||
" \"region\": \"ap-southeast-1\"," +
|
||||
" \"endpoint\": \"arms.ap-southeast-1.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-beijing\"," +
|
||||
" \"endpoint\": \"arms.cn-beijing.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-hangzhou\"," +
|
||||
" \"endpoint\": \"arms.cn-hangzhou.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-hongkong\"," +
|
||||
" \"endpoint\": \"arms.cn-hongkong.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-qingdao\"," +
|
||||
" \"endpoint\": \"arms.cn-qingdao.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-shanghai\"," +
|
||||
" \"endpoint\": \"arms.cn-shanghai.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-shenzhen\"," +
|
||||
" \"endpoint\": \"arms.cn-shenzhen.aliyuncs.com\"" +
|
||||
" }]," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"arms.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"batchcompute\"," +
|
||||
" \"document_id\": \"44717\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": [ {" +
|
||||
" \"region\": \"ap-southeast-1\"," +
|
||||
" \"endpoint\": \"batchcompute.ap-southeast-1.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-beijing\"," +
|
||||
" \"endpoint\": \"batchcompute.cn-beijing.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-hangzhou\"," +
|
||||
" \"endpoint\": \"batchcompute.cn-hangzhou.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-huhehaote\"," +
|
||||
" \"endpoint\": \"batchcompute.cn-huhehaote.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-qingdao\"," +
|
||||
" \"endpoint\": \"batchcompute.cn-qingdao.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-shanghai\"," +
|
||||
" \"endpoint\": \"batchcompute.cn-shanghai.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-shenzhen\"," +
|
||||
" \"endpoint\": \"batchcompute.cn-shenzhen.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-zhangjiakou\"," +
|
||||
" \"endpoint\": \"batchcompute.cn-zhangjiakou.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"us-west-1\"," +
|
||||
" \"endpoint\": \"batchcompute.us-west-1.aliyuncs.com\"" +
|
||||
" }]," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"batchcompute.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"ccc\"," +
|
||||
" \"document_id\": \"63027\"," +
|
||||
" \"location_service_code\": \"ccc\"," +
|
||||
" \"regional_endpoints\": [ {" +
|
||||
" \"region\": \"cn-hangzhou\"," +
|
||||
" \"endpoint\": \"ccc.cn-hangzhou.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-shanghai\"," +
|
||||
" \"endpoint\": \"ccc.cn-shanghai.aliyuncs.com\"" +
|
||||
" }]," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"ccc.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"cdn\"," +
|
||||
" \"document_id\": \"27148\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"cdn.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"cds\"," +
|
||||
" \"document_id\": \"62887\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"cds.cn-beijing.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"chatbot\"," +
|
||||
" \"document_id\": \"60760\"," +
|
||||
" \"location_service_code\": \"beebot\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"chatbot.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"cloudapi\"," +
|
||||
" \"document_id\": \"43590\"," +
|
||||
" \"location_service_code\": \"apigateway\"," +
|
||||
" \"regional_endpoints\": [ {" +
|
||||
" \"region\": \"ap-northeast-1\"," +
|
||||
" \"endpoint\": \"apigateway.ap-northeast-1.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"us-west-1\"," +
|
||||
" \"endpoint\": \"apigateway.us-west-1.aliyuncs.com\"" +
|
||||
" }]," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"apigateway.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"cloudauth\"," +
|
||||
" \"document_id\": \"60687\"," +
|
||||
" \"location_service_code\": \"cloudauth\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"cloudauth.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"cloudphoto\"," +
|
||||
" \"document_id\": \"59902\"," +
|
||||
" \"location_service_code\": \"cloudphoto\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"cloudphoto.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"cloudwf\"," +
|
||||
" \"document_id\": \"58111\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"cloudwf.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"cms\"," +
|
||||
" \"document_id\": \"28615\"," +
|
||||
" \"location_service_code\": \"cms\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"cr\"," +
|
||||
" \"document_id\": \"60716\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"cr.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"cs\"," +
|
||||
" \"document_id\": \"26043\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"cs.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"csb\"," +
|
||||
" \"document_id\": \"64837\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": [ {" +
|
||||
" \"region\": \"cn-beijing\"," +
|
||||
" \"endpoint\": \"csb.cn-beijing.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-hangzhou\"," +
|
||||
" \"endpoint\": \"csb.cn-hangzhou.aliyuncs.com\"" +
|
||||
" }]," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"csb.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"dds\"," +
|
||||
" \"document_id\": \"61715\"," +
|
||||
" \"location_service_code\": \"dds\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"mongodb.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"mongodb.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"dm\"," +
|
||||
" \"document_id\": \"29434\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": [ {" +
|
||||
" \"region\": \"ap-southeast-1\"," +
|
||||
" \"endpoint\": \"dm.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"ap-southeast-2\"," +
|
||||
" \"endpoint\": \"dm.ap-southeast-2.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-beijing\"," +
|
||||
" \"endpoint\": \"dm.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-hangzhou\"," +
|
||||
" \"endpoint\": \"dm.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-hongkong\"," +
|
||||
" \"endpoint\": \"dm.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-qingdao\"," +
|
||||
" \"endpoint\": \"dm.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-shanghai\"," +
|
||||
" \"endpoint\": \"dm.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"cn-shenzhen\"," +
|
||||
" \"endpoint\": \"dm.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"us-east-1\"," +
|
||||
" \"endpoint\": \"dm.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"us-west-1\"," +
|
||||
" \"endpoint\": \"dm.aliyuncs.com\"" +
|
||||
" }]," +
|
||||
" \"global_endpoint\": \"dm.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"dm.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"domain\"," +
|
||||
" \"document_id\": \"42875\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"domain.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"domain.aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"domain-intl\"," +
|
||||
" \"document_id\": \"\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"domain-intl.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"domain-intl.aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"drds\"," +
|
||||
" \"document_id\": \"51111\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"drds.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"drds.aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"ecs\"," +
|
||||
" \"document_id\": \"25484\"," +
|
||||
" \"location_service_code\": \"ecs\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"emr\"," +
|
||||
" \"document_id\": \"28140\"," +
|
||||
" \"location_service_code\": \"emr\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"emr.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"ess\"," +
|
||||
" \"document_id\": \"25925\"," +
|
||||
" \"location_service_code\": \"ess\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"ess.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"green\"," +
|
||||
" \"document_id\": \"28427\"," +
|
||||
" \"location_service_code\": \"green\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"green.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"hpc\"," +
|
||||
" \"document_id\": \"35201\"," +
|
||||
" \"location_service_code\": \"hpc\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"hpc.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"httpdns\"," +
|
||||
" \"document_id\": \"52679\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"httpdns-api.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"iot\"," +
|
||||
" \"document_id\": \"30557\"," +
|
||||
" \"location_service_code\": \"iot\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"iot.[RegionId].aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"itaas\"," +
|
||||
" \"document_id\": \"55759\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"itaas.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"jaq\"," +
|
||||
" \"document_id\": \"35037\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"jaq.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"live\"," +
|
||||
" \"document_id\": \"48207\"," +
|
||||
" \"location_service_code\": \"live\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"live.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"mts\"," +
|
||||
" \"document_id\": \"29212\"," +
|
||||
" \"location_service_code\": \"mts\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"nas\"," +
|
||||
" \"document_id\": \"62598\"," +
|
||||
" \"location_service_code\": \"nas\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"ons\"," +
|
||||
" \"document_id\": \"44416\"," +
|
||||
" \"location_service_code\": \"ons\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"polardb\"," +
|
||||
" \"document_id\": \"58764\"," +
|
||||
" \"location_service_code\": \"polardb\"," +
|
||||
" \"regional_endpoints\": [ {" +
|
||||
" \"region\": \"ap-south-1\"," +
|
||||
" \"endpoint\": \"polardb.ap-south-1.aliyuncs.com\"" +
|
||||
" }, {" +
|
||||
" \"region\": \"ap-southeast-5\"," +
|
||||
" \"endpoint\": \"polardb.ap-southeast-5.aliyuncs.com\"" +
|
||||
" }]," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"polardb.aliyuncs.com\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"push\"," +
|
||||
" \"document_id\": \"30074\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"cloudpush.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"qualitycheck\"," +
|
||||
" \"document_id\": \"50807\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": [ {" +
|
||||
" \"region\": \"cn-hangzhou\"," +
|
||||
" \"endpoint\": \"qualitycheck.cn-hangzhou.aliyuncs.com\"" +
|
||||
" }]," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"r-kvstore\"," +
|
||||
" \"document_id\": \"60831\"," +
|
||||
" \"location_service_code\": \"redisa\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"ram\"," +
|
||||
" \"document_id\": \"28672\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"ram.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"rds\"," +
|
||||
" \"document_id\": \"26223\"," +
|
||||
" \"location_service_code\": \"rds\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"ros\"," +
|
||||
" \"document_id\": \"28899\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"ros.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"sas-api\"," +
|
||||
" \"document_id\": \"28498\"," +
|
||||
" \"location_service_code\": \"sas\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"slb\"," +
|
||||
" \"document_id\": \"27565\"," +
|
||||
" \"location_service_code\": \"slb\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"sts\"," +
|
||||
" \"document_id\": \"28756\"," +
|
||||
" \"location_service_code\": \"\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"sts.aliyuncs.com\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"vod\"," +
|
||||
" \"document_id\": \"60574\"," +
|
||||
" \"location_service_code\": \"vod\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"vpc\"," +
|
||||
" \"document_id\": \"34962\"," +
|
||||
" \"location_service_code\": \"vpc\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }," +
|
||||
" {" +
|
||||
" \"code\": \"waf\"," +
|
||||
" \"document_id\": \"62847\"," +
|
||||
" \"location_service_code\": \"waf\"," +
|
||||
" \"regional_endpoints\": []," +
|
||||
" \"global_endpoint\": \"\"," +
|
||||
" \"regional_endpoint_pattern\": \"\"" +
|
||||
" }]" +
|
||||
"}"
|
||||
|
||||
var initOnce sync.Once
|
||||
var data interface{}
|
||||
|
||||
func getEndpointConfigData() interface{} {
|
||||
initOnce.Do(func() {
|
||||
err := json.Unmarshal([]byte(endpointsJson), &data)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("init endpoint config data failed. %s", err))
|
||||
}
|
||||
})
|
||||
return data
|
||||
}
|
43
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/local_global_resolver.go
generated
vendored
Normal file
43
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/local_global_resolver.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 endpoints
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jmespath/go-jmespath"
|
||||
)
|
||||
|
||||
type LocalGlobalResolver struct {
|
||||
}
|
||||
|
||||
func (resolver *LocalGlobalResolver) GetName() (name string) {
|
||||
name = "local global resolver"
|
||||
return
|
||||
}
|
||||
|
||||
func (resolver *LocalGlobalResolver) TryResolve(param *ResolveParam) (endpoint string, support bool, err error) {
|
||||
// get the global endpoints configs
|
||||
endpointExpression := fmt.Sprintf("products[?code=='%s'].global_endpoint", strings.ToLower(param.Product))
|
||||
endpointData, err := jmespath.Search(endpointExpression, getEndpointConfigData())
|
||||
if err == nil && endpointData != nil && len(endpointData.([]interface{})) > 0 {
|
||||
endpoint = endpointData.([]interface{})[0].(string)
|
||||
support = len(endpoint) > 0
|
||||
return
|
||||
}
|
||||
support = false
|
||||
return
|
||||
}
|
48
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/local_regional_resolver.go
generated
vendored
Normal file
48
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/local_regional_resolver.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 endpoints
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jmespath/go-jmespath"
|
||||
)
|
||||
|
||||
type LocalRegionalResolver struct {
|
||||
}
|
||||
|
||||
func (resolver *LocalRegionalResolver) GetName() (name string) {
|
||||
name = "local regional resolver"
|
||||
return
|
||||
}
|
||||
|
||||
func (resolver *LocalRegionalResolver) TryResolve(param *ResolveParam) (endpoint string, support bool, err error) {
|
||||
// get the regional endpoints configs
|
||||
regionalExpression := fmt.Sprintf("products[?code=='%s'].regional_endpoints", strings.ToLower(param.Product))
|
||||
regionalData, err := jmespath.Search(regionalExpression, getEndpointConfigData())
|
||||
if err == nil && regionalData != nil && len(regionalData.([]interface{})) > 0 {
|
||||
endpointExpression := fmt.Sprintf("[0][?region=='%s'].endpoint", strings.ToLower(param.RegionId))
|
||||
var endpointData interface{}
|
||||
endpointData, err = jmespath.Search(endpointExpression, regionalData)
|
||||
if err == nil && endpointData != nil && len(endpointData.([]interface{})) > 0 {
|
||||
endpoint = endpointData.([]interface{})[0].(string)
|
||||
support = len(endpoint) > 0
|
||||
return
|
||||
}
|
||||
}
|
||||
support = false
|
||||
return
|
||||
}
|
176
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/location_resolver.go
generated
vendored
Normal file
176
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/location_resolver.go
generated
vendored
Normal file
|
@ -0,0 +1,176 @@
|
|||
/*
|
||||
* 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 endpoints
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
)
|
||||
|
||||
const (
|
||||
// EndpointCacheExpireTime ...
|
||||
EndpointCacheExpireTime = 3600 //Seconds
|
||||
)
|
||||
|
||||
// Cache caches endpoint for specific product and region
|
||||
type Cache struct {
|
||||
sync.RWMutex
|
||||
cache map[string]interface{}
|
||||
}
|
||||
|
||||
// Get ...
|
||||
func (c *Cache) Get(k string) (v interface{}) {
|
||||
c.RLock()
|
||||
v = c.cache[k]
|
||||
c.RUnlock()
|
||||
return
|
||||
}
|
||||
|
||||
// Set ...
|
||||
func (c *Cache) Set(k string, v interface{}) {
|
||||
c.Lock()
|
||||
c.cache[k] = v
|
||||
c.Unlock()
|
||||
}
|
||||
|
||||
var lastClearTimePerProduct = &Cache{cache: make(map[string]interface{})}
|
||||
var endpointCache = &Cache{cache: make(map[string]interface{})}
|
||||
|
||||
// LocationResolver ...
|
||||
type LocationResolver struct {
|
||||
}
|
||||
|
||||
func (resolver *LocationResolver) GetName() (name string) {
|
||||
name = "location resolver"
|
||||
return
|
||||
}
|
||||
|
||||
// TryResolve resolves endpoint giving product and region
|
||||
func (resolver *LocationResolver) TryResolve(param *ResolveParam) (endpoint string, support bool, err error) {
|
||||
if len(param.LocationProduct) <= 0 {
|
||||
support = false
|
||||
return
|
||||
}
|
||||
|
||||
//get from cache
|
||||
cacheKey := param.Product + "#" + param.RegionId
|
||||
var ok bool
|
||||
endpoint, ok = endpointCache.Get(cacheKey).(string)
|
||||
|
||||
if ok && len(endpoint) > 0 && !CheckCacheIsExpire(cacheKey) {
|
||||
support = true
|
||||
return
|
||||
}
|
||||
|
||||
//get from remote
|
||||
getEndpointRequest := requests.NewCommonRequest()
|
||||
|
||||
getEndpointRequest.Product = "Location"
|
||||
getEndpointRequest.Version = "2015-06-12"
|
||||
getEndpointRequest.ApiName = "DescribeEndpoints"
|
||||
getEndpointRequest.Domain = "location-readonly.aliyuncs.com"
|
||||
getEndpointRequest.Method = "GET"
|
||||
getEndpointRequest.Scheme = requests.HTTPS
|
||||
|
||||
getEndpointRequest.QueryParams["Id"] = param.RegionId
|
||||
getEndpointRequest.QueryParams["ServiceCode"] = param.LocationProduct
|
||||
if len(param.LocationEndpointType) > 0 {
|
||||
getEndpointRequest.QueryParams["Type"] = param.LocationEndpointType
|
||||
} else {
|
||||
getEndpointRequest.QueryParams["Type"] = "openAPI"
|
||||
}
|
||||
|
||||
response, err := param.CommonApi(getEndpointRequest)
|
||||
if err != nil {
|
||||
support = false
|
||||
return
|
||||
}
|
||||
|
||||
if !response.IsSuccess() {
|
||||
support = false
|
||||
return
|
||||
}
|
||||
|
||||
var getEndpointResponse GetEndpointResponse
|
||||
err = json.Unmarshal([]byte(response.GetHttpContentString()), &getEndpointResponse)
|
||||
if err != nil {
|
||||
support = false
|
||||
return
|
||||
}
|
||||
|
||||
if !getEndpointResponse.Success || getEndpointResponse.Endpoints == nil {
|
||||
support = false
|
||||
return
|
||||
}
|
||||
if len(getEndpointResponse.Endpoints.Endpoint) <= 0 {
|
||||
support = false
|
||||
return
|
||||
}
|
||||
if len(getEndpointResponse.Endpoints.Endpoint[0].Endpoint) > 0 {
|
||||
endpoint = getEndpointResponse.Endpoints.Endpoint[0].Endpoint
|
||||
endpointCache.Set(cacheKey, endpoint)
|
||||
lastClearTimePerProduct.Set(cacheKey, time.Now().Unix())
|
||||
support = true
|
||||
return
|
||||
}
|
||||
|
||||
support = false
|
||||
return
|
||||
}
|
||||
|
||||
// CheckCacheIsExpire ...
|
||||
func CheckCacheIsExpire(cacheKey string) bool {
|
||||
lastClearTime, ok := lastClearTimePerProduct.Get(cacheKey).(int64)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if lastClearTime <= 0 {
|
||||
lastClearTime = time.Now().Unix()
|
||||
lastClearTimePerProduct.Set(cacheKey, lastClearTime)
|
||||
}
|
||||
|
||||
now := time.Now().Unix()
|
||||
elapsedTime := now - lastClearTime
|
||||
if elapsedTime > EndpointCacheExpireTime {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GetEndpointResponse ...
|
||||
type GetEndpointResponse struct {
|
||||
Endpoints *EndpointsObj
|
||||
RequestId string
|
||||
Success bool
|
||||
}
|
||||
|
||||
// EndpointsObj ...
|
||||
type EndpointsObj struct {
|
||||
Endpoint []EndpointObj
|
||||
}
|
||||
|
||||
// EndpointObj ...
|
||||
type EndpointObj struct {
|
||||
// Protocols map[string]string
|
||||
Type string
|
||||
Namespace string
|
||||
Id string
|
||||
SerivceCode string
|
||||
Endpoint string
|
||||
}
|
48
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/mapping_resolver.go
generated
vendored
Normal file
48
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/mapping_resolver.go
generated
vendored
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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 endpoints
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const keyFormatter = "%s::%s"
|
||||
|
||||
var endpointMapping = make(map[string]string)
|
||||
|
||||
// AddEndpointMapping Use product id and region id as key to store the endpoint into inner map
|
||||
func AddEndpointMapping(regionId, productId, endpoint string) (err error) {
|
||||
key := fmt.Sprintf(keyFormatter, strings.ToLower(regionId), strings.ToLower(productId))
|
||||
endpointMapping[key] = endpoint
|
||||
return nil
|
||||
}
|
||||
|
||||
// MappingResolver the mapping resolver type
|
||||
type MappingResolver struct {
|
||||
}
|
||||
|
||||
// GetName get the resolver name: "mapping resolver"
|
||||
func (resolver *MappingResolver) GetName() (name string) {
|
||||
name = "mapping resolver"
|
||||
return
|
||||
}
|
||||
|
||||
// TryResolve use Product and RegionId as key to find endpoint from inner map
|
||||
func (resolver *MappingResolver) TryResolve(param *ResolveParam) (endpoint string, support bool, err error) {
|
||||
key := fmt.Sprintf(keyFormatter, strings.ToLower(param.RegionId), strings.ToLower(param.Product))
|
||||
endpoint, contains := endpointMapping[key]
|
||||
return endpoint, contains, nil
|
||||
}
|
98
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/resolver.go
generated
vendored
Normal file
98
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/resolver.go
generated
vendored
Normal file
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* 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 endpoints
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
|
||||
)
|
||||
|
||||
var debug utils.Debug
|
||||
|
||||
func init() {
|
||||
debug = utils.Init("sdk")
|
||||
}
|
||||
|
||||
const (
|
||||
ResolveEndpointUserGuideLink = ""
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
var resolvers []Resolver
|
||||
|
||||
type Resolver interface {
|
||||
TryResolve(param *ResolveParam) (endpoint string, support bool, err error)
|
||||
GetName() (name string)
|
||||
}
|
||||
|
||||
// Resolve resolve endpoint with params
|
||||
// It will resolve with each supported resolver until anyone resolved
|
||||
func Resolve(param *ResolveParam) (endpoint string, err error) {
|
||||
supportedResolvers := getAllResolvers()
|
||||
var lastErr error
|
||||
for _, resolver := range supportedResolvers {
|
||||
endpoint, supported, resolveErr := resolver.TryResolve(param)
|
||||
if resolveErr != nil {
|
||||
lastErr = resolveErr
|
||||
}
|
||||
|
||||
if supported {
|
||||
debug("resolve endpoint with %s\n", param)
|
||||
debug("\t%s by resolver(%s)\n", endpoint, resolver.GetName())
|
||||
return endpoint, nil
|
||||
}
|
||||
}
|
||||
|
||||
// not support
|
||||
errorMsg := fmt.Sprintf(errors.CanNotResolveEndpointErrorMessage, param, ResolveEndpointUserGuideLink)
|
||||
err = errors.NewClientError(errors.CanNotResolveEndpointErrorCode, errorMsg, lastErr)
|
||||
return
|
||||
}
|
||||
|
||||
func getAllResolvers() []Resolver {
|
||||
once.Do(func() {
|
||||
resolvers = []Resolver{
|
||||
&SimpleHostResolver{},
|
||||
&MappingResolver{},
|
||||
&LocationResolver{},
|
||||
&LocalRegionalResolver{},
|
||||
&LocalGlobalResolver{},
|
||||
}
|
||||
})
|
||||
return resolvers
|
||||
}
|
||||
|
||||
type ResolveParam struct {
|
||||
Domain string
|
||||
Product string
|
||||
RegionId string
|
||||
LocationProduct string
|
||||
LocationEndpointType string
|
||||
CommonApi func(request *requests.CommonRequest) (response *responses.CommonResponse, err error) `json:"-"`
|
||||
}
|
||||
|
||||
func (param *ResolveParam) String() string {
|
||||
jsonBytes, err := json.Marshal(param)
|
||||
if err != nil {
|
||||
return fmt.Sprint("ResolveParam.String() process error:", err)
|
||||
}
|
||||
return string(jsonBytes)
|
||||
}
|
33
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/simple_host_resolver.go
generated
vendored
Normal file
33
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/endpoints/simple_host_resolver.go
generated
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 endpoints
|
||||
|
||||
// SimpleHostResolver the simple host resolver type
|
||||
type SimpleHostResolver struct {
|
||||
}
|
||||
|
||||
// GetName get the resolver name: "simple host resolver"
|
||||
func (resolver *SimpleHostResolver) GetName() (name string) {
|
||||
name = "simple host resolver"
|
||||
return
|
||||
}
|
||||
|
||||
// TryResolve if the Domain exist in param, use it as endpoint
|
||||
func (resolver *SimpleHostResolver) TryResolve(param *ResolveParam) (endpoint string, support bool, err error) {
|
||||
if support = len(param.Domain) > 0; support {
|
||||
endpoint = param.Domain
|
||||
}
|
||||
return
|
||||
}
|
92
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors/client_error.go
generated
vendored
Normal file
92
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors/client_error.go
generated
vendored
Normal file
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* 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 errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
DefaultClientErrorStatus = 400
|
||||
DefaultClientErrorCode = "SDK.ClientError"
|
||||
|
||||
UnsupportedCredentialErrorCode = "SDK.UnsupportedCredential"
|
||||
UnsupportedCredentialErrorMessage = "Specified credential (type = %s) is not supported, please check"
|
||||
|
||||
CanNotResolveEndpointErrorCode = "SDK.CanNotResolveEndpoint"
|
||||
CanNotResolveEndpointErrorMessage = "Can not resolve endpoint(param = %s), please check your accessKey with secret, and read the user guide\n %s"
|
||||
|
||||
UnsupportedParamPositionErrorCode = "SDK.UnsupportedParamPosition"
|
||||
UnsupportedParamPositionErrorMessage = "Specified param position (%s) is not supported, please upgrade sdk and retry"
|
||||
|
||||
AsyncFunctionNotEnabledCode = "SDK.AsyncFunctionNotEnabled"
|
||||
AsyncFunctionNotEnabledMessage = "Async function is not enabled in client, please invoke 'client.EnableAsync' function"
|
||||
|
||||
UnknownRequestTypeErrorCode = "SDK.UnknownRequestType"
|
||||
UnknownRequestTypeErrorMessage = "Unknown Request Type: %s"
|
||||
|
||||
MissingParamErrorCode = "SDK.MissingParam"
|
||||
InvalidParamErrorCode = "SDK.InvalidParam"
|
||||
|
||||
JsonUnmarshalErrorCode = "SDK.JsonUnmarshalError"
|
||||
JsonUnmarshalErrorMessage = "Failed to unmarshal response, but you can get the data via response.GetHttpStatusCode() and response.GetHttpContentString()"
|
||||
|
||||
TimeoutErrorCode = "SDK.TimeoutError"
|
||||
TimeoutErrorMessage = "The request timed out %s times(%s for retry), perhaps we should have the threshold raised a little?"
|
||||
)
|
||||
|
||||
type ClientError struct {
|
||||
errorCode string
|
||||
message string
|
||||
originError error
|
||||
}
|
||||
|
||||
func NewClientError(errorCode, message string, originErr error) Error {
|
||||
return &ClientError{
|
||||
errorCode: errorCode,
|
||||
message: message,
|
||||
originError: originErr,
|
||||
}
|
||||
}
|
||||
|
||||
func (err *ClientError) Error() string {
|
||||
clientErrMsg := fmt.Sprintf("[%s] %s", err.ErrorCode(), err.message)
|
||||
if err.originError != nil {
|
||||
return clientErrMsg + "\ncaused by:\n" + err.originError.Error()
|
||||
}
|
||||
return clientErrMsg
|
||||
}
|
||||
|
||||
func (err *ClientError) OriginError() error {
|
||||
return err.originError
|
||||
}
|
||||
|
||||
func (*ClientError) HttpStatus() int {
|
||||
return DefaultClientErrorStatus
|
||||
}
|
||||
|
||||
func (err *ClientError) ErrorCode() string {
|
||||
if err.errorCode == "" {
|
||||
return DefaultClientErrorCode
|
||||
} else {
|
||||
return err.errorCode
|
||||
}
|
||||
}
|
||||
|
||||
func (err *ClientError) Message() string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
func (err *ClientError) String() string {
|
||||
return err.Error()
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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 errors
|
||||
|
||||
type Error interface {
|
||||
error
|
||||
HttpStatus() int
|
||||
ErrorCode() string
|
||||
Message() string
|
||||
OriginError() error
|
||||
}
|
123
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors/server_error.go
generated
vendored
Normal file
123
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors/server_error.go
generated
vendored
Normal file
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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 errors
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/jmespath/go-jmespath"
|
||||
)
|
||||
|
||||
var wrapperList = []ServerErrorWrapper{
|
||||
&SignatureDostNotMatchWrapper{},
|
||||
}
|
||||
|
||||
type ServerError struct {
|
||||
httpStatus int
|
||||
requestId string
|
||||
hostId string
|
||||
errorCode string
|
||||
recommend string
|
||||
message string
|
||||
comment string
|
||||
}
|
||||
|
||||
type ServerErrorWrapper interface {
|
||||
tryWrap(error *ServerError, wrapInfo map[string]string) bool
|
||||
}
|
||||
|
||||
func (err *ServerError) Error() string {
|
||||
return fmt.Sprintf("SDK.ServerError\nErrorCode: %s\nRecommend: %s\nRequestId: %s\nMessage: %s",
|
||||
err.errorCode, err.comment+err.recommend, err.requestId, err.message)
|
||||
}
|
||||
|
||||
func NewServerError(httpStatus int, responseContent, comment string) Error {
|
||||
result := &ServerError{
|
||||
httpStatus: httpStatus,
|
||||
message: responseContent,
|
||||
comment: comment,
|
||||
}
|
||||
|
||||
var data interface{}
|
||||
err := json.Unmarshal([]byte(responseContent), &data)
|
||||
if err == nil {
|
||||
requestId, _ := jmespath.Search("RequestId", data)
|
||||
hostId, _ := jmespath.Search("HostId", data)
|
||||
errorCode, _ := jmespath.Search("Code", data)
|
||||
recommend, _ := jmespath.Search("Recommend", data)
|
||||
message, _ := jmespath.Search("Message", data)
|
||||
|
||||
if requestId != nil {
|
||||
result.requestId = requestId.(string)
|
||||
}
|
||||
if hostId != nil {
|
||||
result.hostId = hostId.(string)
|
||||
}
|
||||
if errorCode != nil {
|
||||
result.errorCode = errorCode.(string)
|
||||
}
|
||||
if recommend != nil {
|
||||
result.recommend = recommend.(string)
|
||||
}
|
||||
if message != nil {
|
||||
result.message = message.(string)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func WrapServerError(originError *ServerError, wrapInfo map[string]string) *ServerError {
|
||||
for _, wrapper := range wrapperList {
|
||||
ok := wrapper.tryWrap(originError, wrapInfo)
|
||||
if ok {
|
||||
return originError
|
||||
}
|
||||
}
|
||||
return originError
|
||||
}
|
||||
|
||||
func (err *ServerError) HttpStatus() int {
|
||||
return err.httpStatus
|
||||
}
|
||||
|
||||
func (err *ServerError) ErrorCode() string {
|
||||
return err.errorCode
|
||||
}
|
||||
|
||||
func (err *ServerError) Message() string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
func (err *ServerError) OriginError() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (err *ServerError) HostId() string {
|
||||
return err.hostId
|
||||
}
|
||||
|
||||
func (err *ServerError) RequestId() string {
|
||||
return err.requestId
|
||||
}
|
||||
|
||||
func (err *ServerError) Recommend() string {
|
||||
return err.recommend
|
||||
}
|
||||
|
||||
func (err *ServerError) Comment() string {
|
||||
return err.comment
|
||||
}
|
43
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors/signature_does_not_match_wrapper.go
generated
vendored
Normal file
43
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors/signature_does_not_match_wrapper.go
generated
vendored
Normal file
|
@ -0,0 +1,43 @@
|
|||
package errors
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
|
||||
)
|
||||
|
||||
const SignatureDostNotMatchErrorCode = "SignatureDoesNotMatch"
|
||||
const MessagePrefix = "Specified signature is not matched with our calculation. server string to sign is:"
|
||||
|
||||
var debug utils.Debug
|
||||
|
||||
func init() {
|
||||
debug = utils.Init("sdk")
|
||||
}
|
||||
|
||||
type SignatureDostNotMatchWrapper struct {
|
||||
}
|
||||
|
||||
func (*SignatureDostNotMatchWrapper) tryWrap(error *ServerError, wrapInfo map[string]string) (ok bool) {
|
||||
clientStringToSign := wrapInfo["StringToSign"]
|
||||
if error.errorCode == SignatureDostNotMatchErrorCode && clientStringToSign != "" {
|
||||
message := error.message
|
||||
if strings.HasPrefix(message, MessagePrefix) {
|
||||
serverStringToSign := message[len(MessagePrefix):]
|
||||
|
||||
if clientStringToSign == serverStringToSign {
|
||||
// user secret is error
|
||||
error.recommend = "Please check you AccessKeySecret"
|
||||
} else {
|
||||
debug("Client StringToSign: %s", clientStringToSign)
|
||||
debug("Server StringToSign: %s", serverStringToSign)
|
||||
error.recommend = "This may be a bug with the SDK and we hope you can submit this question in the " +
|
||||
"github issue(https://github.com/aliyun/alibaba-cloud-sdk-go/issues), thanks very much"
|
||||
}
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
ok = false
|
||||
return
|
||||
}
|
334
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/acs_request.go
generated
vendored
Normal file
334
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/acs_request.go
generated
vendored
Normal file
|
@ -0,0 +1,334 @@
|
|||
/*
|
||||
* 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 requests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
RPC = "RPC"
|
||||
ROA = "ROA"
|
||||
|
||||
HTTP = "HTTP"
|
||||
HTTPS = "HTTPS"
|
||||
|
||||
DefaultHttpPort = "80"
|
||||
|
||||
GET = "GET"
|
||||
PUT = "PUT"
|
||||
POST = "POST"
|
||||
DELETE = "DELETE"
|
||||
HEAD = "HEAD"
|
||||
OPTIONS = "OPTIONS"
|
||||
|
||||
Json = "application/json"
|
||||
Xml = "application/xml"
|
||||
Raw = "application/octet-stream"
|
||||
Form = "application/x-www-form-urlencoded"
|
||||
|
||||
Header = "Header"
|
||||
Query = "Query"
|
||||
Body = "Body"
|
||||
Path = "Path"
|
||||
|
||||
HeaderSeparator = "\n"
|
||||
)
|
||||
|
||||
// interface
|
||||
type AcsRequest interface {
|
||||
GetScheme() string
|
||||
GetMethod() string
|
||||
GetDomain() string
|
||||
GetPort() string
|
||||
GetRegionId() string
|
||||
GetHeaders() map[string]string
|
||||
GetQueryParams() map[string]string
|
||||
GetFormParams() map[string]string
|
||||
GetContent() []byte
|
||||
GetBodyReader() io.Reader
|
||||
GetStyle() string
|
||||
GetProduct() string
|
||||
GetVersion() string
|
||||
GetActionName() string
|
||||
GetAcceptFormat() string
|
||||
GetLocationServiceCode() string
|
||||
GetLocationEndpointType() string
|
||||
|
||||
GetUserAgent() map[string]string
|
||||
|
||||
SetStringToSign(stringToSign string)
|
||||
GetStringToSign() string
|
||||
|
||||
SetDomain(domain string)
|
||||
SetContent(content []byte)
|
||||
SetScheme(scheme string)
|
||||
BuildUrl() string
|
||||
BuildQueries() string
|
||||
|
||||
addHeaderParam(key, value string)
|
||||
addQueryParam(key, value string)
|
||||
addFormParam(key, value string)
|
||||
addPathParam(key, value string)
|
||||
}
|
||||
|
||||
// base class
|
||||
type baseRequest struct {
|
||||
Scheme string
|
||||
Method string
|
||||
Domain string
|
||||
Port string
|
||||
RegionId string
|
||||
|
||||
userAgent map[string]string
|
||||
product string
|
||||
version string
|
||||
|
||||
actionName string
|
||||
|
||||
AcceptFormat string
|
||||
|
||||
QueryParams map[string]string
|
||||
Headers map[string]string
|
||||
FormParams map[string]string
|
||||
Content []byte
|
||||
|
||||
locationServiceCode string
|
||||
locationEndpointType string
|
||||
|
||||
queries string
|
||||
|
||||
stringToSign string
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetQueryParams() map[string]string {
|
||||
return request.QueryParams
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetFormParams() map[string]string {
|
||||
return request.FormParams
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetContent() []byte {
|
||||
return request.Content
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetVersion() string {
|
||||
return request.version
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetActionName() string {
|
||||
return request.actionName
|
||||
}
|
||||
|
||||
func (request *baseRequest) SetContent(content []byte) {
|
||||
request.Content = content
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetUserAgent() map[string]string {
|
||||
return request.userAgent
|
||||
}
|
||||
|
||||
func (request *baseRequest) AppendUserAgent(key, value string) {
|
||||
newkey := true
|
||||
if request.userAgent == nil {
|
||||
request.userAgent = make(map[string]string)
|
||||
}
|
||||
if strings.ToLower(key) != "core" && strings.ToLower(key) != "go" {
|
||||
for tag, _ := range request.userAgent {
|
||||
if tag == key {
|
||||
request.userAgent[tag] = value
|
||||
newkey = false
|
||||
}
|
||||
}
|
||||
if newkey {
|
||||
request.userAgent[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (request *baseRequest) addHeaderParam(key, value string) {
|
||||
request.Headers[key] = value
|
||||
}
|
||||
|
||||
func (request *baseRequest) addQueryParam(key, value string) {
|
||||
request.QueryParams[key] = value
|
||||
}
|
||||
|
||||
func (request *baseRequest) addFormParam(key, value string) {
|
||||
request.FormParams[key] = value
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetAcceptFormat() string {
|
||||
return request.AcceptFormat
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetLocationServiceCode() string {
|
||||
return request.locationServiceCode
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetLocationEndpointType() string {
|
||||
return request.locationEndpointType
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetProduct() string {
|
||||
return request.product
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetScheme() string {
|
||||
return request.Scheme
|
||||
}
|
||||
|
||||
func (request *baseRequest) SetScheme(scheme string) {
|
||||
request.Scheme = scheme
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetMethod() string {
|
||||
return request.Method
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetDomain() string {
|
||||
return request.Domain
|
||||
}
|
||||
|
||||
func (request *baseRequest) SetDomain(host string) {
|
||||
request.Domain = host
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetPort() string {
|
||||
return request.Port
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetRegionId() string {
|
||||
return request.RegionId
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetHeaders() map[string]string {
|
||||
return request.Headers
|
||||
}
|
||||
|
||||
func (request *baseRequest) SetContentType(contentType string) {
|
||||
request.addHeaderParam("Content-Type", contentType)
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetContentType() (contentType string, contains bool) {
|
||||
contentType, contains = request.Headers["Content-Type"]
|
||||
return
|
||||
}
|
||||
|
||||
func (request *baseRequest) SetStringToSign(stringToSign string) {
|
||||
request.stringToSign = stringToSign
|
||||
}
|
||||
|
||||
func (request *baseRequest) GetStringToSign() string {
|
||||
return request.stringToSign
|
||||
}
|
||||
|
||||
func defaultBaseRequest() (request *baseRequest) {
|
||||
request = &baseRequest{
|
||||
Scheme: "",
|
||||
AcceptFormat: "JSON",
|
||||
Method: GET,
|
||||
QueryParams: make(map[string]string),
|
||||
Headers: map[string]string{
|
||||
"x-sdk-client": "golang/1.0.0",
|
||||
"x-sdk-invoke-type": "normal",
|
||||
"Accept-Encoding": "identity",
|
||||
},
|
||||
FormParams: make(map[string]string),
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func InitParams(request AcsRequest) (err error) {
|
||||
requestValue := reflect.ValueOf(request).Elem()
|
||||
err = flatRepeatedList(requestValue, request, "", "")
|
||||
return
|
||||
}
|
||||
|
||||
func flatRepeatedList(dataValue reflect.Value, request AcsRequest, position, prefix string) (err error) {
|
||||
dataType := dataValue.Type()
|
||||
for i := 0; i < dataType.NumField(); i++ {
|
||||
field := dataType.Field(i)
|
||||
name, containsNameTag := field.Tag.Lookup("name")
|
||||
fieldPosition := position
|
||||
if fieldPosition == "" {
|
||||
fieldPosition, _ = field.Tag.Lookup("position")
|
||||
}
|
||||
typeTag, containsTypeTag := field.Tag.Lookup("type")
|
||||
if containsNameTag {
|
||||
if !containsTypeTag {
|
||||
// simple param
|
||||
key := prefix + name
|
||||
value := dataValue.Field(i).String()
|
||||
err = addParam(request, fieldPosition, key, value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else if typeTag == "Repeated" {
|
||||
// repeated param
|
||||
repeatedFieldValue := dataValue.Field(i)
|
||||
if repeatedFieldValue.Kind() != reflect.Slice {
|
||||
// possible value: {"[]string", "*[]struct"}, we must call Elem() in the last condition
|
||||
repeatedFieldValue = repeatedFieldValue.Elem()
|
||||
}
|
||||
if repeatedFieldValue.IsValid() && !repeatedFieldValue.IsNil() {
|
||||
for m := 0; m < repeatedFieldValue.Len(); m++ {
|
||||
elementValue := repeatedFieldValue.Index(m)
|
||||
key := prefix + name + "." + strconv.Itoa(m+1)
|
||||
if elementValue.Type().String() == "string" {
|
||||
value := elementValue.String()
|
||||
err = addParam(request, fieldPosition, key, value)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err = flatRepeatedList(elementValue, request, fieldPosition, key+".")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func addParam(request AcsRequest, position, name, value string) (err error) {
|
||||
if len(value) > 0 {
|
||||
switch position {
|
||||
case Header:
|
||||
request.addHeaderParam(name, value)
|
||||
case Query:
|
||||
request.addQueryParam(name, value)
|
||||
case Path:
|
||||
request.addPathParam(name, value)
|
||||
case Body:
|
||||
request.addFormParam(name, value)
|
||||
default:
|
||||
errMsg := fmt.Sprintf(errors.UnsupportedParamPositionErrorMessage, position)
|
||||
err = errors.NewClientError(errors.UnsupportedParamPositionErrorCode, errMsg, nil)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
148
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/acs_request_test.go
generated
vendored
Normal file
148
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/acs_request_test.go
generated
vendored
Normal file
|
@ -0,0 +1,148 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_AcsRequest(t *testing.T) {
|
||||
r := defaultBaseRequest()
|
||||
assert.NotNil(t, r)
|
||||
|
||||
// query params
|
||||
query := r.GetQueryParams()
|
||||
assert.Equal(t, 0, len(query))
|
||||
r.addQueryParam("key", "value")
|
||||
assert.Equal(t, 1, len(query))
|
||||
assert.Equal(t, "value", query["key"])
|
||||
|
||||
// form params
|
||||
form := r.GetFormParams()
|
||||
assert.Equal(t, 0, len(form))
|
||||
r.addFormParam("key", "value")
|
||||
assert.Equal(t, 1, len(form))
|
||||
assert.Equal(t, "value", form["key"])
|
||||
|
||||
// getter/setter for stringtosign
|
||||
assert.Equal(t, "", r.GetStringToSign())
|
||||
r.SetStringToSign("s2s")
|
||||
assert.Equal(t, "s2s", r.GetStringToSign())
|
||||
|
||||
// content type
|
||||
_, contains := r.GetContentType()
|
||||
assert.False(t, contains)
|
||||
r.SetContentType("application/json")
|
||||
ct, contains := r.GetContentType()
|
||||
assert.Equal(t, "application/json", ct)
|
||||
assert.True(t, contains)
|
||||
|
||||
// default 3 headers & content-type
|
||||
headers := r.GetHeaders()
|
||||
assert.Equal(t, 4, len(headers))
|
||||
r.addHeaderParam("x-key", "x-key-value")
|
||||
assert.Equal(t, 5, len(headers))
|
||||
assert.Equal(t, "x-key-value", headers["x-key"])
|
||||
|
||||
// GetVersion
|
||||
assert.Equal(t, "", r.GetVersion())
|
||||
// GetActionName
|
||||
assert.Equal(t, "", r.GetActionName())
|
||||
|
||||
// GetMethod
|
||||
assert.Equal(t, "GET", r.GetMethod())
|
||||
r.Method = "POST"
|
||||
assert.Equal(t, "POST", r.GetMethod())
|
||||
|
||||
// Domain
|
||||
assert.Equal(t, "", r.GetDomain())
|
||||
r.SetDomain("ecs.aliyuncs.com")
|
||||
assert.Equal(t, "ecs.aliyuncs.com", r.GetDomain())
|
||||
|
||||
// Region
|
||||
assert.Equal(t, "", r.GetRegionId())
|
||||
r.RegionId = "cn-hangzhou"
|
||||
assert.Equal(t, "cn-hangzhou", r.GetRegionId())
|
||||
|
||||
// AcceptFormat
|
||||
assert.Equal(t, "JSON", r.GetAcceptFormat())
|
||||
r.AcceptFormat = "XML"
|
||||
assert.Equal(t, "XML", r.GetAcceptFormat())
|
||||
|
||||
// GetLocationServiceCode
|
||||
assert.Equal(t, "", r.GetLocationServiceCode())
|
||||
|
||||
// GetLocationEndpointType
|
||||
assert.Equal(t, "", r.GetLocationEndpointType())
|
||||
|
||||
// GetProduct
|
||||
assert.Equal(t, "", r.GetProduct())
|
||||
|
||||
// GetScheme
|
||||
assert.Equal(t, "", r.GetScheme())
|
||||
r.SetScheme("HTTPS")
|
||||
assert.Equal(t, "HTTPS", r.GetScheme())
|
||||
|
||||
// GetPort
|
||||
assert.Equal(t, "", r.GetPort())
|
||||
|
||||
// GetUserAgent
|
||||
r.AppendUserAgent("cli", "1.01")
|
||||
assert.Equal(t, "1.01", r.GetUserAgent()["cli"])
|
||||
// Content
|
||||
assert.Equal(t, []byte(nil), r.GetContent())
|
||||
r.SetContent([]byte("The Content"))
|
||||
assert.True(t, bytes.Equal([]byte("The Content"), r.GetContent()))
|
||||
}
|
||||
|
||||
type AcsRequestTest struct {
|
||||
*baseRequest
|
||||
Ontology AcsRequest
|
||||
Query string `position:"Query" name:"Query"`
|
||||
Header string `position:"Header" name:"Header"`
|
||||
Path string `position:"Path" name:"Path"`
|
||||
Body string `position:"Body" name:"Body"`
|
||||
TypeAcs *[]string `position:"type" name:"type" type:"Repeated"`
|
||||
}
|
||||
|
||||
func (r AcsRequestTest) BuildQueries() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r AcsRequestTest) BuildUrl() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r AcsRequestTest) GetBodyReader() io.Reader {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r AcsRequestTest) GetStyle() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r AcsRequestTest) addPathParam(key, value string) {
|
||||
return
|
||||
}
|
||||
|
||||
func Test_AcsRequest_InitParams(t *testing.T) {
|
||||
r := &AcsRequestTest{
|
||||
baseRequest: defaultBaseRequest(),
|
||||
Query: "query value",
|
||||
Header: "header value",
|
||||
Path: "path value",
|
||||
Body: "body value",
|
||||
}
|
||||
tmp := []string{r.Query, r.Header}
|
||||
r.TypeAcs = &tmp
|
||||
r.addQueryParam("qkey", "qvalue")
|
||||
InitParams(r)
|
||||
|
||||
queries := r.GetQueryParams()
|
||||
assert.Equal(t, "query value", queries["Query"])
|
||||
headers := r.GetHeaders()
|
||||
assert.Equal(t, "header value", headers["Header"])
|
||||
// TODO: check the body & path
|
||||
}
|
106
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/common_request.go
generated
vendored
Normal file
106
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/common_request.go
generated
vendored
Normal file
|
@ -0,0 +1,106 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CommonRequest struct {
|
||||
*baseRequest
|
||||
|
||||
Version string
|
||||
ApiName string
|
||||
Product string
|
||||
|
||||
// roa params
|
||||
PathPattern string
|
||||
PathParams map[string]string
|
||||
|
||||
Ontology AcsRequest
|
||||
}
|
||||
|
||||
func NewCommonRequest() (request *CommonRequest) {
|
||||
request = &CommonRequest{
|
||||
baseRequest: defaultBaseRequest(),
|
||||
}
|
||||
request.Headers["x-sdk-invoke-type"] = "common"
|
||||
request.PathParams = make(map[string]string)
|
||||
return
|
||||
}
|
||||
|
||||
func (request *CommonRequest) String() string {
|
||||
request.TransToAcsRequest()
|
||||
|
||||
resultBuilder := bytes.Buffer{}
|
||||
|
||||
mapOutput := func(m map[string]string) {
|
||||
if len(m) > 0 {
|
||||
sortedKeys := make([]string, 0)
|
||||
for k := range m {
|
||||
sortedKeys = append(sortedKeys, k)
|
||||
}
|
||||
|
||||
// sort 'string' key in increasing order
|
||||
sort.Strings(sortedKeys)
|
||||
|
||||
for _, key := range sortedKeys {
|
||||
resultBuilder.WriteString(key + ": " + m[key] + "\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Request Line
|
||||
resultBuilder.WriteString(fmt.Sprintf("%s %s %s/1.1\n", request.Method, request.BuildQueries(), strings.ToUpper(request.Scheme)))
|
||||
|
||||
// Headers
|
||||
resultBuilder.WriteString("Host" + ": " + request.Domain + "\n")
|
||||
mapOutput(request.Headers)
|
||||
|
||||
resultBuilder.WriteString("\n")
|
||||
// Body
|
||||
if len(request.Content) > 0 {
|
||||
resultBuilder.WriteString(string(request.Content) + "\n")
|
||||
} else {
|
||||
mapOutput(request.FormParams)
|
||||
}
|
||||
|
||||
return resultBuilder.String()
|
||||
}
|
||||
|
||||
func (request *CommonRequest) TransToAcsRequest() {
|
||||
if len(request.PathPattern) > 0 {
|
||||
roaRequest := &RoaRequest{}
|
||||
roaRequest.initWithCommonRequest(request)
|
||||
request.Ontology = roaRequest
|
||||
} else {
|
||||
rpcRequest := &RpcRequest{}
|
||||
rpcRequest.baseRequest = request.baseRequest
|
||||
rpcRequest.product = request.Product
|
||||
rpcRequest.version = request.Version
|
||||
rpcRequest.actionName = request.ApiName
|
||||
request.Ontology = rpcRequest
|
||||
}
|
||||
}
|
||||
|
||||
func (request *CommonRequest) BuildUrl() string {
|
||||
return request.Ontology.BuildUrl()
|
||||
}
|
||||
|
||||
func (request *CommonRequest) BuildQueries() string {
|
||||
return request.Ontology.BuildQueries()
|
||||
}
|
||||
|
||||
func (request *CommonRequest) GetBodyReader() io.Reader {
|
||||
return request.Ontology.GetBodyReader()
|
||||
}
|
||||
|
||||
func (request *CommonRequest) GetStyle() string {
|
||||
return request.Ontology.GetStyle()
|
||||
}
|
||||
|
||||
func (request *CommonRequest) addPathParam(key, value string) {
|
||||
request.PathParams[key] = value
|
||||
}
|
82
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/common_request_test.go
generated
vendored
Normal file
82
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/common_request_test.go
generated
vendored
Normal file
|
@ -0,0 +1,82 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_NewCommonRequest(t *testing.T) {
|
||||
r := NewCommonRequest()
|
||||
assert.NotNil(t, r)
|
||||
|
||||
assert.Equal(t, "common", r.GetHeaders()["x-sdk-invoke-type"])
|
||||
assert.Equal(t, 0, len(r.PathParams))
|
||||
|
||||
r.addPathParam("name", "value")
|
||||
assert.Equal(t, "value", r.PathParams["name"])
|
||||
}
|
||||
|
||||
func Test_CommonRequest_TransToAcsRequest(t *testing.T) {
|
||||
r := NewCommonRequest()
|
||||
assert.NotNil(t, r)
|
||||
r.TransToAcsRequest()
|
||||
|
||||
assert.Equal(t, "RPC", r.GetStyle())
|
||||
|
||||
r2 := NewCommonRequest()
|
||||
assert.NotNil(t, r2)
|
||||
r2.PathPattern = "/users/[user]"
|
||||
r2.TransToAcsRequest()
|
||||
|
||||
assert.Equal(t, "ROA", r2.GetStyle())
|
||||
}
|
||||
|
||||
func Test_CommonRequest_String(t *testing.T) {
|
||||
r := NewCommonRequest()
|
||||
assert.NotNil(t, r)
|
||||
r.SetDomain("domain")
|
||||
|
||||
expected := `GET /? /1.1
|
||||
Host: domain
|
||||
Accept-Encoding: identity
|
||||
x-sdk-client: golang/1.0.0
|
||||
x-sdk-invoke-type: common
|
||||
|
||||
`
|
||||
assert.Equal(t, expected, r.String())
|
||||
|
||||
r.SetContent([]byte("content"))
|
||||
|
||||
expected = `GET /? /1.1
|
||||
Host: domain
|
||||
Accept-Encoding: identity
|
||||
x-sdk-client: golang/1.0.0
|
||||
x-sdk-invoke-type: common
|
||||
|
||||
content
|
||||
`
|
||||
assert.Equal(t, expected, r.String())
|
||||
}
|
||||
|
||||
func Test_CommonRequest_BuildUrl(t *testing.T) {
|
||||
r := NewCommonRequest()
|
||||
assert.NotNil(t, r)
|
||||
r.SetDomain("host")
|
||||
r.SetScheme("http")
|
||||
|
||||
r.TransToAcsRequest()
|
||||
|
||||
assert.Equal(t, "http://host/?", r.BuildUrl())
|
||||
r.Port = "8080"
|
||||
assert.Equal(t, "http://host:8080/?", r.BuildUrl())
|
||||
}
|
||||
|
||||
func Test_CommonRequest_GetBodyReader(t *testing.T) {
|
||||
r := NewCommonRequest()
|
||||
r.TransToAcsRequest()
|
||||
reader := r.GetBodyReader()
|
||||
b, _ := ioutil.ReadAll(reader)
|
||||
assert.Equal(t, "", string(b))
|
||||
}
|
152
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/roa_request.go
generated
vendored
Normal file
152
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/roa_request.go
generated
vendored
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* 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 requests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
|
||||
)
|
||||
|
||||
type RoaRequest struct {
|
||||
*baseRequest
|
||||
pathPattern string
|
||||
PathParams map[string]string
|
||||
}
|
||||
|
||||
func (*RoaRequest) GetStyle() string {
|
||||
return ROA
|
||||
}
|
||||
|
||||
func (request *RoaRequest) GetBodyReader() io.Reader {
|
||||
if request.FormParams != nil && len(request.FormParams) > 0 {
|
||||
formString := utils.GetUrlFormedMap(request.FormParams)
|
||||
return strings.NewReader(formString)
|
||||
} else if len(request.Content) > 0 {
|
||||
return bytes.NewReader(request.Content)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// for sign method, need not url encoded
|
||||
func (request *RoaRequest) BuildQueries() string {
|
||||
return request.buildQueries()
|
||||
}
|
||||
|
||||
func (request *RoaRequest) buildPath() string {
|
||||
path := request.pathPattern
|
||||
for key, value := range request.PathParams {
|
||||
path = strings.Replace(path, "["+key+"]", value, 1)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func (request *RoaRequest) buildQueries() string {
|
||||
// replace path params with value
|
||||
path := request.buildPath()
|
||||
queryParams := request.QueryParams
|
||||
// sort QueryParams by key
|
||||
var queryKeys []string
|
||||
for key := range queryParams {
|
||||
queryKeys = append(queryKeys, key)
|
||||
}
|
||||
sort.Strings(queryKeys)
|
||||
|
||||
// append urlBuilder
|
||||
urlBuilder := bytes.Buffer{}
|
||||
urlBuilder.WriteString(path)
|
||||
if len(queryKeys) > 0 {
|
||||
urlBuilder.WriteString("?")
|
||||
}
|
||||
for i := 0; i < len(queryKeys); i++ {
|
||||
queryKey := queryKeys[i]
|
||||
urlBuilder.WriteString(queryKey)
|
||||
if value := queryParams[queryKey]; len(value) > 0 {
|
||||
urlBuilder.WriteString("=")
|
||||
urlBuilder.WriteString(value)
|
||||
}
|
||||
if i < len(queryKeys)-1 {
|
||||
urlBuilder.WriteString("&")
|
||||
}
|
||||
}
|
||||
result := urlBuilder.String()
|
||||
result = popStandardUrlencode(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func (request *RoaRequest) buildQueryString() string {
|
||||
queryParams := request.QueryParams
|
||||
// sort QueryParams by key
|
||||
q := url.Values{}
|
||||
for key, value := range queryParams {
|
||||
q.Add(key, value)
|
||||
}
|
||||
return q.Encode()
|
||||
}
|
||||
|
||||
func popStandardUrlencode(stringToSign string) (result string) {
|
||||
result = strings.Replace(stringToSign, "+", "%20", -1)
|
||||
result = strings.Replace(result, "*", "%2A", -1)
|
||||
result = strings.Replace(result, "%7E", "~", -1)
|
||||
return
|
||||
}
|
||||
|
||||
func (request *RoaRequest) BuildUrl() string {
|
||||
// for network trans, need url encoded
|
||||
scheme := strings.ToLower(request.Scheme)
|
||||
domain := request.Domain
|
||||
port := request.Port
|
||||
path := request.buildPath()
|
||||
url := fmt.Sprintf("%s://%s:%s%s", scheme, domain, port, path)
|
||||
querystring := request.buildQueryString()
|
||||
if len(querystring) > 0 {
|
||||
url = fmt.Sprintf("%s?%s", url, querystring)
|
||||
}
|
||||
return url
|
||||
}
|
||||
|
||||
func (request *RoaRequest) addPathParam(key, value string) {
|
||||
request.PathParams[key] = value
|
||||
}
|
||||
|
||||
func (request *RoaRequest) InitWithApiInfo(product, version, action, uriPattern, serviceCode, endpointType string) {
|
||||
request.baseRequest = defaultBaseRequest()
|
||||
request.PathParams = make(map[string]string)
|
||||
request.Headers["x-acs-version"] = version
|
||||
request.pathPattern = uriPattern
|
||||
request.locationServiceCode = serviceCode
|
||||
request.locationEndpointType = endpointType
|
||||
//request.product = product
|
||||
//request.version = version
|
||||
//request.actionName = action
|
||||
}
|
||||
|
||||
func (request *RoaRequest) initWithCommonRequest(commonRequest *CommonRequest) {
|
||||
request.baseRequest = commonRequest.baseRequest
|
||||
request.PathParams = commonRequest.PathParams
|
||||
//request.product = commonRequest.Product
|
||||
//request.version = commonRequest.Version
|
||||
request.Headers["x-acs-version"] = commonRequest.Version
|
||||
//request.actionName = commonRequest.ApiName
|
||||
request.pathPattern = commonRequest.PathPattern
|
||||
request.locationServiceCode = ""
|
||||
request.locationEndpointType = ""
|
||||
}
|
116
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/roa_request_test.go
generated
vendored
Normal file
116
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/roa_request_test.go
generated
vendored
Normal file
|
@ -0,0 +1,116 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_RoaRequest(t *testing.T) {
|
||||
r := &RoaRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "/", "serviceCode", "endpointType")
|
||||
assert.NotNil(t, r)
|
||||
|
||||
assert.Equal(t, "GET", r.GetMethod())
|
||||
assert.Equal(t, "ROA", r.GetStyle())
|
||||
// assert.Equal(t, "version", r.GetVersion())
|
||||
// assert.Equal(t, "action", r.GetActionName())
|
||||
assert.Equal(t, "serviceCode", r.GetLocationServiceCode())
|
||||
assert.Equal(t, "endpointType", r.GetLocationEndpointType())
|
||||
}
|
||||
|
||||
func Test_RoaRequest_initWithCommonRequest(t *testing.T) {
|
||||
r := &RoaRequest{}
|
||||
common := NewCommonRequest()
|
||||
r.initWithCommonRequest(common)
|
||||
assert.NotNil(t, r)
|
||||
|
||||
assert.Equal(t, "GET", r.GetMethod())
|
||||
assert.Equal(t, "ROA", r.GetStyle())
|
||||
assert.Equal(t, "common", r.Headers["x-sdk-invoke-type"])
|
||||
// assert.Equal(t, "version", r.GetVersion())
|
||||
// assert.Equal(t, "action", r.GetActionName())
|
||||
}
|
||||
|
||||
func Test_RoaRequest_BuildQueries(t *testing.T) {
|
||||
// url
|
||||
r := &RoaRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "/", "serviceCode", "endpointType")
|
||||
assert.Equal(t, "/", r.BuildQueries())
|
||||
r.addQueryParam("key", "value")
|
||||
assert.Equal(t, "/?key=value", r.BuildQueries())
|
||||
r.addQueryParam("key2", "value2")
|
||||
assert.Equal(t, "/?key=value&key2=value2", r.BuildQueries())
|
||||
// assert.Equal(t, "/?key=https%3A%2F%2Fdomain%2F%3Fq%3Dv", r.BuildQueries())
|
||||
}
|
||||
|
||||
func Test_RoaRequest_BuildUrl(t *testing.T) {
|
||||
r := &RoaRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "/", "serviceCode", "endpointType")
|
||||
r.Domain = "domain.com"
|
||||
r.Scheme = "http"
|
||||
r.Port = "80"
|
||||
assert.Equal(t, "http://domain.com:80/", r.BuildUrl())
|
||||
r.addQueryParam("key", "value")
|
||||
assert.Equal(t, "http://domain.com:80/?key=value", r.BuildUrl())
|
||||
r.addQueryParam("key", "https://domain/?q=v")
|
||||
assert.Equal(t, "http://domain.com:80/?key=https%3A%2F%2Fdomain%2F%3Fq%3Dv", r.BuildUrl())
|
||||
r.addQueryParam("url", "https://domain/?q1=v1&q2=v2")
|
||||
assert.Equal(t, "http://domain.com:80/?key=https%3A%2F%2Fdomain%2F%3Fq%3Dv&url=https%3A%2F%2Fdomain%2F%3Fq1%3Dv1%26q2%3Dv2", r.BuildUrl())
|
||||
}
|
||||
|
||||
func Test_RoaRequest_BuildUrl2(t *testing.T) {
|
||||
r := &RoaRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "/", "serviceCode", "endpointType")
|
||||
r.Domain = "domain.com"
|
||||
r.Scheme = "http"
|
||||
r.Port = "80"
|
||||
assert.Equal(t, "http://domain.com:80/", r.BuildUrl())
|
||||
r.addPathParam("key", "value")
|
||||
assert.Equal(t, "http://domain.com:80/", r.BuildUrl())
|
||||
|
||||
r = &RoaRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "/users/[user]", "serviceCode", "endpointType")
|
||||
r.Domain = "domain.com"
|
||||
r.Scheme = "http"
|
||||
r.Port = "80"
|
||||
r.addPathParam("user", "name")
|
||||
assert.Equal(t, "http://domain.com:80/users/name", r.BuildUrl())
|
||||
r.addQueryParam("key", "value")
|
||||
assert.Equal(t, "http://domain.com:80/users/name?key=value", r.BuildUrl())
|
||||
}
|
||||
|
||||
func Test_RoaRequest_GetBodyReader_Nil(t *testing.T) {
|
||||
r := &RoaRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "/", "serviceCode", "endpointType")
|
||||
|
||||
reader := r.GetBodyReader()
|
||||
assert.Nil(t, reader)
|
||||
}
|
||||
|
||||
func Test_RoaRequest_GetBodyReader_Form(t *testing.T) {
|
||||
r := &RoaRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "/", "serviceCode", "endpointType")
|
||||
|
||||
r.addFormParam("key", "value")
|
||||
reader := r.GetBodyReader()
|
||||
b, _ := ioutil.ReadAll(reader)
|
||||
assert.Equal(t, "key=value", string(b))
|
||||
}
|
||||
|
||||
func Test_RoaRequest_GetBodyReader_Content(t *testing.T) {
|
||||
r := &RoaRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "/", "serviceCode", "endpointType")
|
||||
|
||||
r.SetContent([]byte("Hello world"))
|
||||
reader := r.GetBodyReader()
|
||||
b, _ := ioutil.ReadAll(reader)
|
||||
assert.Equal(t, "Hello world", string(b))
|
||||
}
|
||||
|
||||
// func Test_RoaRequest_addPathParam(t *testing.T) {
|
||||
// r := &RoaRequest{}
|
||||
// r.InitWithApiInfo("product", "version", "action", "/", "serviceCode", "endpointType")
|
||||
// r.addPathParam("key", "value")
|
||||
// }
|
79
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/rpc_request.go
generated
vendored
Normal file
79
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/rpc_request.go
generated
vendored
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 requests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
|
||||
)
|
||||
|
||||
type RpcRequest struct {
|
||||
*baseRequest
|
||||
}
|
||||
|
||||
func (request *RpcRequest) init() {
|
||||
request.baseRequest = defaultBaseRequest()
|
||||
request.Method = POST
|
||||
}
|
||||
|
||||
func (*RpcRequest) GetStyle() string {
|
||||
return RPC
|
||||
}
|
||||
|
||||
func (request *RpcRequest) GetBodyReader() io.Reader {
|
||||
if request.FormParams != nil && len(request.FormParams) > 0 {
|
||||
formString := utils.GetUrlFormedMap(request.FormParams)
|
||||
return strings.NewReader(formString)
|
||||
} else {
|
||||
return strings.NewReader("")
|
||||
}
|
||||
}
|
||||
|
||||
func (request *RpcRequest) BuildQueries() string {
|
||||
request.queries = "/?" + utils.GetUrlFormedMap(request.QueryParams)
|
||||
return request.queries
|
||||
}
|
||||
|
||||
func (request *RpcRequest) BuildUrl() string {
|
||||
url := fmt.Sprintf("%s://%s", strings.ToLower(request.Scheme), request.Domain)
|
||||
if len(request.Port) > 0 {
|
||||
url = fmt.Sprintf("%s:%s", url, request.Port)
|
||||
}
|
||||
return url + request.BuildQueries()
|
||||
}
|
||||
|
||||
func (request *RpcRequest) GetVersion() string {
|
||||
return request.version
|
||||
}
|
||||
|
||||
func (request *RpcRequest) GetActionName() string {
|
||||
return request.actionName
|
||||
}
|
||||
|
||||
func (request *RpcRequest) addPathParam(key, value string) {
|
||||
panic("not support")
|
||||
}
|
||||
|
||||
func (request *RpcRequest) InitWithApiInfo(product, version, action, serviceCode, endpointType string) {
|
||||
request.init()
|
||||
request.product = product
|
||||
request.version = version
|
||||
request.actionName = action
|
||||
request.locationServiceCode = serviceCode
|
||||
request.locationEndpointType = endpointType
|
||||
}
|
70
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/rpc_request_test.go
generated
vendored
Normal file
70
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/rpc_request_test.go
generated
vendored
Normal file
|
@ -0,0 +1,70 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_RpcRequest(t *testing.T) {
|
||||
r := &RpcRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "serviceCode", "endpointType")
|
||||
assert.NotNil(t, r)
|
||||
|
||||
assert.Equal(t, "POST", r.GetMethod())
|
||||
assert.Equal(t, "RPC", r.GetStyle())
|
||||
assert.Equal(t, "product", r.GetProduct())
|
||||
assert.Equal(t, "version", r.GetVersion())
|
||||
assert.Equal(t, "action", r.GetActionName())
|
||||
assert.Equal(t, "serviceCode", r.GetLocationServiceCode())
|
||||
assert.Equal(t, "endpointType", r.GetLocationEndpointType())
|
||||
}
|
||||
|
||||
func Test_RpcRequest_BuildQueries(t *testing.T) {
|
||||
// url
|
||||
r := &RpcRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "serviceCode", "endpointType")
|
||||
assert.Equal(t, "/?", r.BuildQueries())
|
||||
r.addQueryParam("key", "value")
|
||||
assert.Equal(t, "/?key=value", r.BuildQueries())
|
||||
r.addQueryParam("key", "https://domain/?q=v")
|
||||
assert.Equal(t, "/?key=https%3A%2F%2Fdomain%2F%3Fq%3Dv", r.BuildQueries())
|
||||
}
|
||||
|
||||
func Test_RpcRequest_BuildUrl(t *testing.T) {
|
||||
r := &RpcRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "serviceCode", "endpointType")
|
||||
r.Domain = "domain.com"
|
||||
r.Scheme = "http"
|
||||
r.Port = "80"
|
||||
assert.Equal(t, "http://domain.com:80/?", r.BuildUrl())
|
||||
r.addQueryParam("key", "value")
|
||||
assert.Equal(t, "http://domain.com:80/?key=value", r.BuildUrl())
|
||||
r.addQueryParam("key", "https://domain/?q=v")
|
||||
assert.Equal(t, "http://domain.com:80/?key=https%3A%2F%2Fdomain%2F%3Fq%3Dv", r.BuildUrl())
|
||||
}
|
||||
|
||||
func Test_RpcRequest_GetBodyReader(t *testing.T) {
|
||||
r := &RpcRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "serviceCode", "endpointType")
|
||||
|
||||
reader := r.GetBodyReader()
|
||||
b, _ := ioutil.ReadAll(reader)
|
||||
assert.Equal(t, "", string(b))
|
||||
r.addFormParam("key", "value")
|
||||
reader = r.GetBodyReader()
|
||||
b, _ = ioutil.ReadAll(reader)
|
||||
assert.Equal(t, "key=value", string(b))
|
||||
}
|
||||
|
||||
func Test_RpcRequest_addPathParam(t *testing.T) {
|
||||
defer func() { //进行异常捕捉
|
||||
err := recover()
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "not support", err)
|
||||
}()
|
||||
r := &RpcRequest{}
|
||||
r.InitWithApiInfo("product", "version", "action", "serviceCode", "endpointType")
|
||||
r.addPathParam("key", "value")
|
||||
}
|
53
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/types.go
generated
vendored
Normal file
53
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/types.go
generated
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
package requests
|
||||
|
||||
import "strconv"
|
||||
|
||||
type Integer string
|
||||
|
||||
func NewInteger(integer int) Integer {
|
||||
return Integer(strconv.Itoa(integer))
|
||||
}
|
||||
|
||||
func (integer Integer) HasValue() bool {
|
||||
return integer != ""
|
||||
}
|
||||
|
||||
func (integer Integer) GetValue() (int, error) {
|
||||
return strconv.Atoi(string(integer))
|
||||
}
|
||||
|
||||
func NewInteger64(integer int64) Integer {
|
||||
return Integer(strconv.FormatInt(integer, 10))
|
||||
}
|
||||
|
||||
func (integer Integer) GetValue64() (int64, error) {
|
||||
return strconv.ParseInt(string(integer), 10, 0)
|
||||
}
|
||||
|
||||
type Boolean string
|
||||
|
||||
func NewBoolean(bool bool) Boolean {
|
||||
return Boolean(strconv.FormatBool(bool))
|
||||
}
|
||||
|
||||
func (boolean Boolean) HasValue() bool {
|
||||
return boolean != ""
|
||||
}
|
||||
|
||||
func (boolean Boolean) GetValue() (bool, error) {
|
||||
return strconv.ParseBool(string(boolean))
|
||||
}
|
||||
|
||||
type Float string
|
||||
|
||||
func NewFloat(f float64) Float {
|
||||
return Float(strconv.FormatFloat(f, 'f', 6, 64))
|
||||
}
|
||||
|
||||
func (float Float) HasValue() bool {
|
||||
return float != ""
|
||||
}
|
||||
|
||||
func (float Float) GetValue() (float64, error) {
|
||||
return strconv.ParseFloat(string(float), 64)
|
||||
}
|
51
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/types_test.go
generated
vendored
Normal file
51
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests/types_test.go
generated
vendored
Normal file
|
@ -0,0 +1,51 @@
|
|||
package requests
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewInteger(t *testing.T) {
|
||||
integer := NewInteger(123123)
|
||||
assert.True(t, integer.HasValue())
|
||||
value, err := integer.GetValue()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 123123, value)
|
||||
var expected Integer
|
||||
expected = "123123"
|
||||
assert.Equal(t, expected, integer)
|
||||
}
|
||||
|
||||
func TestNewInteger64(t *testing.T) {
|
||||
long := NewInteger64(123123123123123123)
|
||||
assert.True(t, long.HasValue())
|
||||
value, err := long.GetValue64()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, int64(123123123123123123), value)
|
||||
var expected Integer
|
||||
expected = "123123123123123123"
|
||||
assert.Equal(t, expected, long)
|
||||
}
|
||||
|
||||
func TestNewBoolean(t *testing.T) {
|
||||
boolean := NewBoolean(false)
|
||||
assert.True(t, boolean.HasValue())
|
||||
value, err := boolean.GetValue()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, false, value)
|
||||
var expected Boolean
|
||||
expected = "false"
|
||||
assert.Equal(t, expected, boolean)
|
||||
}
|
||||
|
||||
func TestNewFloat(t *testing.T) {
|
||||
float := NewFloat(123123.123123)
|
||||
assert.True(t, float.HasValue())
|
||||
value, err := float.GetValue()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 123123.123123, value)
|
||||
var expected Float
|
||||
expected = "123123.123123"
|
||||
assert.Equal(t, expected, float)
|
||||
}
|
6
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/resource/tzdata.go
generated
vendored
Normal file
6
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/resource/tzdata.go
generated
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
package resource
|
||||
|
||||
func GetTZData(name string) ([]byte, bool) {
|
||||
data, ok := files["zoneinfo/"+name]
|
||||
return data, ok
|
||||
}
|
1190
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/resource/zoneinfo.go
generated
vendored
Normal file
1190
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/resource/zoneinfo.go
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
327
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses/json_parser.go
generated
vendored
Normal file
327
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses/json_parser.go
generated
vendored
Normal file
|
@ -0,0 +1,327 @@
|
|||
package responses
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
const maxUint = ^uint(0)
|
||||
const maxInt = int(maxUint >> 1)
|
||||
const minInt = -maxInt - 1
|
||||
|
||||
var jsonParser jsoniter.API
|
||||
var initJson = &sync.Once{}
|
||||
|
||||
func initJsonParserOnce() {
|
||||
initJson.Do(func() {
|
||||
registerBetterFuzzyDecoder()
|
||||
jsonParser = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||
})
|
||||
}
|
||||
|
||||
func registerBetterFuzzyDecoder() {
|
||||
jsoniter.RegisterTypeDecoder("string", &nullableFuzzyStringDecoder{})
|
||||
jsoniter.RegisterTypeDecoder("bool", &fuzzyBoolDecoder{})
|
||||
jsoniter.RegisterTypeDecoder("float32", &nullableFuzzyFloat32Decoder{})
|
||||
jsoniter.RegisterTypeDecoder("float64", &nullableFuzzyFloat64Decoder{})
|
||||
jsoniter.RegisterTypeDecoder("int", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(maxInt) || val < float64(minInt) {
|
||||
iter.ReportError("fuzzy decode int", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*int)(ptr)) = int(val)
|
||||
} else {
|
||||
*((*int)(ptr)) = iter.ReadInt()
|
||||
}
|
||||
}})
|
||||
jsoniter.RegisterTypeDecoder("uint", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(maxUint) || val < 0 {
|
||||
iter.ReportError("fuzzy decode uint", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*uint)(ptr)) = uint(val)
|
||||
} else {
|
||||
*((*uint)(ptr)) = iter.ReadUint()
|
||||
}
|
||||
}})
|
||||
jsoniter.RegisterTypeDecoder("int8", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(math.MaxInt8) || val < float64(math.MinInt8) {
|
||||
iter.ReportError("fuzzy decode int8", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*int8)(ptr)) = int8(val)
|
||||
} else {
|
||||
*((*int8)(ptr)) = iter.ReadInt8()
|
||||
}
|
||||
}})
|
||||
jsoniter.RegisterTypeDecoder("uint8", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(math.MaxUint8) || val < 0 {
|
||||
iter.ReportError("fuzzy decode uint8", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*uint8)(ptr)) = uint8(val)
|
||||
} else {
|
||||
*((*uint8)(ptr)) = iter.ReadUint8()
|
||||
}
|
||||
}})
|
||||
jsoniter.RegisterTypeDecoder("int16", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(math.MaxInt16) || val < float64(math.MinInt16) {
|
||||
iter.ReportError("fuzzy decode int16", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*int16)(ptr)) = int16(val)
|
||||
} else {
|
||||
*((*int16)(ptr)) = iter.ReadInt16()
|
||||
}
|
||||
}})
|
||||
jsoniter.RegisterTypeDecoder("uint16", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(math.MaxUint16) || val < 0 {
|
||||
iter.ReportError("fuzzy decode uint16", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*uint16)(ptr)) = uint16(val)
|
||||
} else {
|
||||
*((*uint16)(ptr)) = iter.ReadUint16()
|
||||
}
|
||||
}})
|
||||
jsoniter.RegisterTypeDecoder("int32", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(math.MaxInt32) || val < float64(math.MinInt32) {
|
||||
iter.ReportError("fuzzy decode int32", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*int32)(ptr)) = int32(val)
|
||||
} else {
|
||||
*((*int32)(ptr)) = iter.ReadInt32()
|
||||
}
|
||||
}})
|
||||
jsoniter.RegisterTypeDecoder("uint32", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(math.MaxUint32) || val < 0 {
|
||||
iter.ReportError("fuzzy decode uint32", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*uint32)(ptr)) = uint32(val)
|
||||
} else {
|
||||
*((*uint32)(ptr)) = iter.ReadUint32()
|
||||
}
|
||||
}})
|
||||
jsoniter.RegisterTypeDecoder("int64", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(math.MaxInt64) || val < float64(math.MinInt64) {
|
||||
iter.ReportError("fuzzy decode int64", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*int64)(ptr)) = int64(val)
|
||||
} else {
|
||||
*((*int64)(ptr)) = iter.ReadInt64()
|
||||
}
|
||||
}})
|
||||
jsoniter.RegisterTypeDecoder("uint64", &nullableFuzzyIntegerDecoder{func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
if isFloat {
|
||||
val := iter.ReadFloat64()
|
||||
if val > float64(math.MaxUint64) || val < 0 {
|
||||
iter.ReportError("fuzzy decode uint64", "exceed range")
|
||||
return
|
||||
}
|
||||
*((*uint64)(ptr)) = uint64(val)
|
||||
} else {
|
||||
*((*uint64)(ptr)) = iter.ReadUint64()
|
||||
}
|
||||
}})
|
||||
}
|
||||
|
||||
type nullableFuzzyStringDecoder struct {
|
||||
}
|
||||
|
||||
func (decoder *nullableFuzzyStringDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
valueType := iter.WhatIsNext()
|
||||
switch valueType {
|
||||
case jsoniter.NumberValue:
|
||||
var number json.Number
|
||||
iter.ReadVal(&number)
|
||||
*((*string)(ptr)) = string(number)
|
||||
case jsoniter.StringValue:
|
||||
*((*string)(ptr)) = iter.ReadString()
|
||||
case jsoniter.BoolValue:
|
||||
*((*string)(ptr)) = strconv.FormatBool(iter.ReadBool())
|
||||
case jsoniter.NilValue:
|
||||
iter.ReadNil()
|
||||
*((*string)(ptr)) = ""
|
||||
default:
|
||||
iter.ReportError("fuzzyStringDecoder", "not number or string or bool")
|
||||
}
|
||||
}
|
||||
|
||||
type fuzzyBoolDecoder struct {
|
||||
}
|
||||
|
||||
func (decoder *fuzzyBoolDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
valueType := iter.WhatIsNext()
|
||||
switch valueType {
|
||||
case jsoniter.BoolValue:
|
||||
*((*bool)(ptr)) = iter.ReadBool()
|
||||
case jsoniter.NumberValue:
|
||||
var number json.Number
|
||||
iter.ReadVal(&number)
|
||||
num, err := number.Int64()
|
||||
if err != nil {
|
||||
iter.ReportError("fuzzyBoolDecoder", "get value from json.number failed")
|
||||
}
|
||||
if num == 0 {
|
||||
*((*bool)(ptr)) = false
|
||||
} else {
|
||||
*((*bool)(ptr)) = true
|
||||
}
|
||||
case jsoniter.StringValue:
|
||||
strValue := strings.ToLower(iter.ReadString())
|
||||
if strValue == "true" {
|
||||
*((*bool)(ptr)) = true
|
||||
} else if strValue == "false" || strValue == "" {
|
||||
*((*bool)(ptr)) = false
|
||||
} else {
|
||||
iter.ReportError("fuzzyBoolDecoder", "unsupported bool value: "+strValue)
|
||||
}
|
||||
case jsoniter.NilValue:
|
||||
iter.ReadNil()
|
||||
*((*bool)(ptr)) = false
|
||||
default:
|
||||
iter.ReportError("fuzzyBoolDecoder", "not number or string or nil")
|
||||
}
|
||||
}
|
||||
|
||||
type nullableFuzzyIntegerDecoder struct {
|
||||
fun func(isFloat bool, ptr unsafe.Pointer, iter *jsoniter.Iterator)
|
||||
}
|
||||
|
||||
func (decoder *nullableFuzzyIntegerDecoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
valueType := iter.WhatIsNext()
|
||||
var str string
|
||||
switch valueType {
|
||||
case jsoniter.NumberValue:
|
||||
var number json.Number
|
||||
iter.ReadVal(&number)
|
||||
str = string(number)
|
||||
case jsoniter.StringValue:
|
||||
str = iter.ReadString()
|
||||
// support empty string
|
||||
if str == "" {
|
||||
str = "0"
|
||||
}
|
||||
case jsoniter.BoolValue:
|
||||
if iter.ReadBool() {
|
||||
str = "1"
|
||||
} else {
|
||||
str = "0"
|
||||
}
|
||||
case jsoniter.NilValue:
|
||||
iter.ReadNil()
|
||||
str = "0"
|
||||
default:
|
||||
iter.ReportError("fuzzyIntegerDecoder", "not number or string")
|
||||
}
|
||||
newIter := iter.Pool().BorrowIterator([]byte(str))
|
||||
defer iter.Pool().ReturnIterator(newIter)
|
||||
isFloat := strings.IndexByte(str, '.') != -1
|
||||
decoder.fun(isFloat, ptr, newIter)
|
||||
if newIter.Error != nil && newIter.Error != io.EOF {
|
||||
iter.Error = newIter.Error
|
||||
}
|
||||
}
|
||||
|
||||
type nullableFuzzyFloat32Decoder struct {
|
||||
}
|
||||
|
||||
func (decoder *nullableFuzzyFloat32Decoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
valueType := iter.WhatIsNext()
|
||||
var str string
|
||||
switch valueType {
|
||||
case jsoniter.NumberValue:
|
||||
*((*float32)(ptr)) = iter.ReadFloat32()
|
||||
case jsoniter.StringValue:
|
||||
str = iter.ReadString()
|
||||
// support empty string
|
||||
if str == "" {
|
||||
*((*float32)(ptr)) = 0
|
||||
return
|
||||
}
|
||||
newIter := iter.Pool().BorrowIterator([]byte(str))
|
||||
defer iter.Pool().ReturnIterator(newIter)
|
||||
*((*float32)(ptr)) = newIter.ReadFloat32()
|
||||
if newIter.Error != nil && newIter.Error != io.EOF {
|
||||
iter.Error = newIter.Error
|
||||
}
|
||||
case jsoniter.BoolValue:
|
||||
// support bool to float32
|
||||
if iter.ReadBool() {
|
||||
*((*float32)(ptr)) = 1
|
||||
} else {
|
||||
*((*float32)(ptr)) = 0
|
||||
}
|
||||
case jsoniter.NilValue:
|
||||
iter.ReadNil()
|
||||
*((*float32)(ptr)) = 0
|
||||
default:
|
||||
iter.ReportError("nullableFuzzyFloat32Decoder", "not number or string")
|
||||
}
|
||||
}
|
||||
|
||||
type nullableFuzzyFloat64Decoder struct {
|
||||
}
|
||||
|
||||
func (decoder *nullableFuzzyFloat64Decoder) Decode(ptr unsafe.Pointer, iter *jsoniter.Iterator) {
|
||||
valueType := iter.WhatIsNext()
|
||||
var str string
|
||||
switch valueType {
|
||||
case jsoniter.NumberValue:
|
||||
*((*float64)(ptr)) = iter.ReadFloat64()
|
||||
case jsoniter.StringValue:
|
||||
str = iter.ReadString()
|
||||
// support empty string
|
||||
if str == "" {
|
||||
*((*float64)(ptr)) = 0
|
||||
return
|
||||
}
|
||||
newIter := iter.Pool().BorrowIterator([]byte(str))
|
||||
defer iter.Pool().ReturnIterator(newIter)
|
||||
*((*float64)(ptr)) = newIter.ReadFloat64()
|
||||
if newIter.Error != nil && newIter.Error != io.EOF {
|
||||
iter.Error = newIter.Error
|
||||
}
|
||||
case jsoniter.BoolValue:
|
||||
// support bool to float64
|
||||
if iter.ReadBool() {
|
||||
*((*float64)(ptr)) = 1
|
||||
} else {
|
||||
*((*float64)(ptr)) = 0
|
||||
}
|
||||
case jsoniter.NilValue:
|
||||
// support empty string
|
||||
iter.ReadNil()
|
||||
*((*float64)(ptr)) = 0
|
||||
default:
|
||||
iter.ReportError("nullableFuzzyFloat64Decoder", "not number or string")
|
||||
}
|
||||
}
|
145
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses/response.go
generated
vendored
Normal file
145
vendor/github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses/response.go
generated
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* 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 responses
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
|
||||
)
|
||||
|
||||
type AcsResponse interface {
|
||||
IsSuccess() bool
|
||||
GetHttpStatus() int
|
||||
GetHttpHeaders() map[string][]string
|
||||
GetHttpContentString() string
|
||||
GetHttpContentBytes() []byte
|
||||
GetOriginHttpResponse() *http.Response
|
||||
parseFromHttpResponse(httpResponse *http.Response) error
|
||||
}
|
||||
|
||||
// Unmarshal object from http response body to target Response
|
||||
func Unmarshal(response AcsResponse, httpResponse *http.Response, format string) (err error) {
|
||||
err = response.parseFromHttpResponse(httpResponse)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !response.IsSuccess() {
|
||||
err = errors.NewServerError(response.GetHttpStatus(), response.GetHttpContentString(), "")
|
||||
return
|
||||
}
|
||||
|
||||
if _, isCommonResponse := response.(*CommonResponse); isCommonResponse {
|
||||
// common response need not unmarshal
|
||||
return
|
||||
}
|
||||
|
||||
if len(response.GetHttpContentBytes()) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if strings.ToUpper(format) == "JSON" {
|
||||
initJsonParserOnce()
|
||||
err = jsonParser.Unmarshal(response.GetHttpContentBytes(), response)
|
||||
if err != nil {
|
||||
err = errors.NewClientError(errors.JsonUnmarshalErrorCode, errors.JsonUnmarshalErrorMessage, err)
|
||||
}
|
||||
} else if strings.ToUpper(format) == "XML" {
|
||||
err = xml.Unmarshal(response.GetHttpContentBytes(), response)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type BaseResponse struct {
|
||||
httpStatus int
|
||||
httpHeaders map[string][]string
|
||||
httpContentString string
|
||||
httpContentBytes []byte
|
||||
originHttpResponse *http.Response
|
||||
}
|
||||
|
||||
func (baseResponse *BaseResponse) GetHttpStatus() int {
|
||||
return baseResponse.httpStatus
|
||||
}
|
||||
|
||||
func (baseResponse *BaseResponse) GetHttpHeaders() map[string][]string {
|
||||
return baseResponse.httpHeaders
|
||||
}
|
||||
|
||||
func (baseResponse *BaseResponse) GetHttpContentString() string {
|
||||
return baseResponse.httpContentString
|
||||
}
|
||||
|
||||
func (baseResponse *BaseResponse) GetHttpContentBytes() []byte {
|
||||
return baseResponse.httpContentBytes
|
||||
}
|
||||
|
||||
func (baseResponse *BaseResponse) GetOriginHttpResponse() *http.Response {
|
||||
return baseResponse.originHttpResponse
|
||||
}
|
||||
|
||||
func (baseResponse *BaseResponse) IsSuccess() bool {
|
||||
if baseResponse.GetHttpStatus() >= 200 && baseResponse.GetHttpStatus() < 300 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (baseResponse *BaseResponse) parseFromHttpResponse(httpResponse *http.Response) (err error) {
|
||||
defer httpResponse.Body.Close()
|
||||
body, err := ioutil.ReadAll(httpResponse.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
baseResponse.httpStatus = httpResponse.StatusCode
|
||||
baseResponse.httpHeaders = httpResponse.Header
|
||||
baseResponse.httpContentBytes = body
|
||||
baseResponse.httpContentString = string(body)
|
||||
baseResponse.originHttpResponse = httpResponse
|
||||
return
|
||||
}
|
||||
|
||||
func (baseResponse *BaseResponse) String() string {
|
||||
resultBuilder := bytes.Buffer{}
|
||||
// statusCode
|
||||
// resultBuilder.WriteString("\n")
|
||||
resultBuilder.WriteString(fmt.Sprintf("%s %s\n", baseResponse.originHttpResponse.Proto, baseResponse.originHttpResponse.Status))
|
||||
// httpHeaders
|
||||
//resultBuilder.WriteString("Headers:\n")
|
||||
for key, value := range baseResponse.httpHeaders {
|
||||
resultBuilder.WriteString(key + ": " + strings.Join(value, ";") + "\n")
|
||||
}
|
||||
resultBuilder.WriteString("\n")
|
||||
// content
|
||||
//resultBuilder.WriteString("Content:\n")
|
||||
resultBuilder.WriteString(baseResponse.httpContentString + "\n")
|
||||
return resultBuilder.String()
|
||||
}
|
||||
|
||||
type CommonResponse struct {
|
||||
*BaseResponse
|
||||
}
|
||||
|
||||
func NewCommonResponse() (response *CommonResponse) {
|
||||
return &CommonResponse{
|
||||
BaseResponse: &BaseResponse{},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Debug func(format string, v ...interface{})
|
||||
|
||||
var hookGetEnv = func() string {
|
||||
return os.Getenv("DEBUG")
|
||||
}
|
||||
|
||||
var hookPrint = func(input string) {
|
||||
fmt.Println(input)
|
||||
}
|
||||
|
||||
func Init(flag string) Debug {
|
||||
enable := false
|
||||
|
||||
env := hookGetEnv()
|
||||
parts := strings.Split(env, ",")
|
||||
for _, part := range parts {
|
||||
if part == flag {
|
||||
enable = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return func(format string, v ...interface{}) {
|
||||
if enable {
|
||||
hookPrint(fmt.Sprintf(format, v...))
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 utils
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/satori/go.uuid"
|
||||
)
|
||||
|
||||
// if you use go 1.10 or higher, you can hack this util by these to avoid "TimeZone.zip not found" on Windows
|
||||
var LoadLocationFromTZData func(name string, data []byte) (*time.Location, error) = nil
|
||||
var TZData []byte = nil
|
||||
|
||||
func GetUUIDV4() (uuidHex string) {
|
||||
uuidV4 := uuid.NewV4()
|
||||
uuidHex = hex.EncodeToString(uuidV4.Bytes())
|
||||
return
|
||||
}
|
||||
|
||||
func GetMD5Base64(bytes []byte) (base64Value string) {
|
||||
md5Ctx := md5.New()
|
||||
md5Ctx.Write(bytes)
|
||||
md5Value := md5Ctx.Sum(nil)
|
||||
base64Value = base64.StdEncoding.EncodeToString(md5Value)
|
||||
return
|
||||
}
|
||||
|
||||
func GetGMTLocation() (*time.Location, error) {
|
||||
if LoadLocationFromTZData != nil && TZData != nil {
|
||||
return LoadLocationFromTZData("GMT", TZData)
|
||||
} else {
|
||||
return time.LoadLocation("GMT")
|
||||
}
|
||||
}
|
||||
|
||||
func GetTimeInFormatISO8601() (timeStr string) {
|
||||
gmt, err := GetGMTLocation()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return time.Now().In(gmt).Format("2006-01-02T15:04:05Z")
|
||||
}
|
||||
|
||||
func GetTimeInFormatRFC2616() (timeStr string) {
|
||||
gmt, err := GetGMTLocation()
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return time.Now().In(gmt).Format("Mon, 02 Jan 2006 15:04:05 GMT")
|
||||
}
|
||||
|
||||
func GetUrlFormedMap(source map[string]string) (urlEncoded string) {
|
||||
urlEncoder := url.Values{}
|
||||
for key, value := range source {
|
||||
urlEncoder.Add(key, value)
|
||||
}
|
||||
urlEncoded = urlEncoder.Encode()
|
||||
return
|
||||
}
|
||||
|
||||
func InitStructWithDefaultTag(bean interface{}) {
|
||||
configType := reflect.TypeOf(bean)
|
||||
for i := 0; i < configType.Elem().NumField(); i++ {
|
||||
field := configType.Elem().Field(i)
|
||||
defaultValue := field.Tag.Get("default")
|
||||
if defaultValue == "" {
|
||||
continue
|
||||
}
|
||||
setter := reflect.ValueOf(bean).Elem().Field(i)
|
||||
switch field.Type.String() {
|
||||
case "int":
|
||||
intValue, _ := strconv.ParseInt(defaultValue, 10, 64)
|
||||
setter.SetInt(intValue)
|
||||
case "time.Duration":
|
||||
intValue, _ := strconv.ParseInt(defaultValue, 10, 64)
|
||||
setter.SetInt(intValue)
|
||||
case "string":
|
||||
setter.SetString(defaultValue)
|
||||
case "bool":
|
||||
boolValue, _ := strconv.ParseBool(defaultValue)
|
||||
setter.SetBool(boolValue)
|
||||
}
|
||||
}
|
||||
}
|
81
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/client.go
generated
vendored
Normal file
81
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/client.go
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth"
|
||||
)
|
||||
|
||||
// Client is the sdk client struct, each func corresponds to an OpenAPI
|
||||
type Client struct {
|
||||
sdk.Client
|
||||
}
|
||||
|
||||
// NewClient creates a sdk client with environment variables
|
||||
func NewClient() (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.Init()
|
||||
return
|
||||
}
|
||||
|
||||
// NewClientWithOptions creates a sdk client with regionId/sdkConfig/credential
|
||||
// this is the common api to create a sdk client
|
||||
func NewClientWithOptions(regionId string, config *sdk.Config, credential auth.Credential) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithOptions(regionId, config, credential)
|
||||
return
|
||||
}
|
||||
|
||||
// NewClientWithAccessKey is a shortcut to create sdk client with accesskey
|
||||
// usage: https://help.aliyun.com/document_detail/66217.html
|
||||
func NewClientWithAccessKey(regionId, accessKeyId, accessKeySecret string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithAccessKey(regionId, accessKeyId, accessKeySecret)
|
||||
return
|
||||
}
|
||||
|
||||
// NewClientWithStsToken is a shortcut to create sdk client with sts token
|
||||
// usage: https://help.aliyun.com/document_detail/66222.html
|
||||
func NewClientWithStsToken(regionId, stsAccessKeyId, stsAccessKeySecret, stsToken string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithStsToken(regionId, stsAccessKeyId, stsAccessKeySecret, stsToken)
|
||||
return
|
||||
}
|
||||
|
||||
// NewClientWithRamRoleArn is a shortcut to create sdk client with ram roleArn
|
||||
// usage: https://help.aliyun.com/document_detail/66222.html
|
||||
func NewClientWithRamRoleArn(regionId string, accessKeyId, accessKeySecret, roleArn, roleSessionName string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithRamRoleArn(regionId, accessKeyId, accessKeySecret, roleArn, roleSessionName)
|
||||
return
|
||||
}
|
||||
|
||||
// NewClientWithEcsRamRole is a shortcut to create sdk client with ecs ram role
|
||||
// usage: https://help.aliyun.com/document_detail/66223.html
|
||||
func NewClientWithEcsRamRole(regionId string, roleName string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithEcsRamRole(regionId, roleName)
|
||||
return
|
||||
}
|
||||
|
||||
// NewClientWithRsaKeyPair is a shortcut to create sdk client with rsa key pair
|
||||
// attention: rsa key pair auth is only Japan regions available
|
||||
func NewClientWithRsaKeyPair(regionId string, publicKeyId, privateKey string, sessionExpiration int) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
err = client.InitWithRsaKeyPair(regionId, publicKeyId, privateKey, sessionExpiration)
|
||||
return
|
||||
}
|
111
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/describe_endpoint.go
generated
vendored
Normal file
111
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/describe_endpoint.go
generated
vendored
Normal file
|
@ -0,0 +1,111 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
)
|
||||
|
||||
// DescribeEndpoint invokes the location.DescribeEndpoint API synchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeendpoint.html
|
||||
func (client *Client) DescribeEndpoint(request *DescribeEndpointRequest) (response *DescribeEndpointResponse, err error) {
|
||||
response = CreateDescribeEndpointResponse()
|
||||
err = client.DoAction(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
// DescribeEndpointWithChan invokes the location.DescribeEndpoint API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeendpoint.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) DescribeEndpointWithChan(request *DescribeEndpointRequest) (<-chan *DescribeEndpointResponse, <-chan error) {
|
||||
responseChan := make(chan *DescribeEndpointResponse, 1)
|
||||
errChan := make(chan error, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
defer close(responseChan)
|
||||
defer close(errChan)
|
||||
response, err := client.DescribeEndpoint(request)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
responseChan <- response
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
close(responseChan)
|
||||
close(errChan)
|
||||
}
|
||||
return responseChan, errChan
|
||||
}
|
||||
|
||||
// DescribeEndpointWithCallback invokes the location.DescribeEndpoint API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeendpoint.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) DescribeEndpointWithCallback(request *DescribeEndpointRequest, callback func(response *DescribeEndpointResponse, err error)) <-chan int {
|
||||
result := make(chan int, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
var response *DescribeEndpointResponse
|
||||
var err error
|
||||
defer close(result)
|
||||
response, err = client.DescribeEndpoint(request)
|
||||
callback(response, err)
|
||||
result <- 1
|
||||
})
|
||||
if err != nil {
|
||||
defer close(result)
|
||||
callback(nil, err)
|
||||
result <- 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DescribeEndpointRequest is the request struct for api DescribeEndpoint
|
||||
type DescribeEndpointRequest struct {
|
||||
*requests.RpcRequest
|
||||
Password string `position:"Query" name:"Password"`
|
||||
ServiceCode string `position:"Query" name:"ServiceCode"`
|
||||
Id string `position:"Query" name:"Id"`
|
||||
}
|
||||
|
||||
// DescribeEndpointResponse is the response struct for api DescribeEndpoint
|
||||
type DescribeEndpointResponse struct {
|
||||
*responses.BaseResponse
|
||||
RequestId string `json:"RequestId" xml:"RequestId"`
|
||||
Endpoint string `json:"Endpoint" xml:"Endpoint"`
|
||||
Id string `json:"Id" xml:"Id"`
|
||||
Namespace string `json:"Namespace" xml:"Namespace"`
|
||||
SerivceCode string `json:"SerivceCode" xml:"SerivceCode"`
|
||||
Type string `json:"Type" xml:"Type"`
|
||||
Protocols ProtocolsInDescribeEndpoint `json:"Protocols" xml:"Protocols"`
|
||||
}
|
||||
|
||||
// CreateDescribeEndpointRequest creates a request to invoke DescribeEndpoint API
|
||||
func CreateDescribeEndpointRequest() (request *DescribeEndpointRequest) {
|
||||
request = &DescribeEndpointRequest{
|
||||
RpcRequest: &requests.RpcRequest{},
|
||||
}
|
||||
request.InitWithApiInfo("Location", "2015-06-12", "DescribeEndpoint", "location", "openAPI")
|
||||
return
|
||||
}
|
||||
|
||||
// CreateDescribeEndpointResponse creates a response to parse from DescribeEndpoint response
|
||||
func CreateDescribeEndpointResponse() (response *DescribeEndpointResponse) {
|
||||
response = &DescribeEndpointResponse{
|
||||
BaseResponse: &responses.BaseResponse{},
|
||||
}
|
||||
return
|
||||
}
|
107
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/describe_endpoints.go
generated
vendored
Normal file
107
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/describe_endpoints.go
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
)
|
||||
|
||||
// DescribeEndpoints invokes the location.DescribeEndpoints API synchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeendpoints.html
|
||||
func (client *Client) DescribeEndpoints(request *DescribeEndpointsRequest) (response *DescribeEndpointsResponse, err error) {
|
||||
response = CreateDescribeEndpointsResponse()
|
||||
err = client.DoAction(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
// DescribeEndpointsWithChan invokes the location.DescribeEndpoints API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeendpoints.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) DescribeEndpointsWithChan(request *DescribeEndpointsRequest) (<-chan *DescribeEndpointsResponse, <-chan error) {
|
||||
responseChan := make(chan *DescribeEndpointsResponse, 1)
|
||||
errChan := make(chan error, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
defer close(responseChan)
|
||||
defer close(errChan)
|
||||
response, err := client.DescribeEndpoints(request)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
responseChan <- response
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
close(responseChan)
|
||||
close(errChan)
|
||||
}
|
||||
return responseChan, errChan
|
||||
}
|
||||
|
||||
// DescribeEndpointsWithCallback invokes the location.DescribeEndpoints API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeendpoints.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) DescribeEndpointsWithCallback(request *DescribeEndpointsRequest, callback func(response *DescribeEndpointsResponse, err error)) <-chan int {
|
||||
result := make(chan int, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
var response *DescribeEndpointsResponse
|
||||
var err error
|
||||
defer close(result)
|
||||
response, err = client.DescribeEndpoints(request)
|
||||
callback(response, err)
|
||||
result <- 1
|
||||
})
|
||||
if err != nil {
|
||||
defer close(result)
|
||||
callback(nil, err)
|
||||
result <- 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DescribeEndpointsRequest is the request struct for api DescribeEndpoints
|
||||
type DescribeEndpointsRequest struct {
|
||||
*requests.RpcRequest
|
||||
ServiceCode string `position:"Query" name:"ServiceCode"`
|
||||
Id string `position:"Query" name:"Id"`
|
||||
Type string `position:"Query" name:"Type"`
|
||||
}
|
||||
|
||||
// DescribeEndpointsResponse is the response struct for api DescribeEndpoints
|
||||
type DescribeEndpointsResponse struct {
|
||||
*responses.BaseResponse
|
||||
RequestId string `json:"RequestId" xml:"RequestId"`
|
||||
Success bool `json:"Success" xml:"Success"`
|
||||
Endpoints Endpoints `json:"Endpoints" xml:"Endpoints"`
|
||||
}
|
||||
|
||||
// CreateDescribeEndpointsRequest creates a request to invoke DescribeEndpoints API
|
||||
func CreateDescribeEndpointsRequest() (request *DescribeEndpointsRequest) {
|
||||
request = &DescribeEndpointsRequest{
|
||||
RpcRequest: &requests.RpcRequest{},
|
||||
}
|
||||
request.InitWithApiInfo("Location", "2015-06-12", "DescribeEndpoints", "location", "openAPI")
|
||||
return
|
||||
}
|
||||
|
||||
// CreateDescribeEndpointsResponse creates a response to parse from DescribeEndpoints response
|
||||
func CreateDescribeEndpointsResponse() (response *DescribeEndpointsResponse) {
|
||||
response = &DescribeEndpointsResponse{
|
||||
BaseResponse: &responses.BaseResponse{},
|
||||
}
|
||||
return
|
||||
}
|
105
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/describe_regions.go
generated
vendored
Normal file
105
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/describe_regions.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
)
|
||||
|
||||
// DescribeRegions invokes the location.DescribeRegions API synchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeregions.html
|
||||
func (client *Client) DescribeRegions(request *DescribeRegionsRequest) (response *DescribeRegionsResponse, err error) {
|
||||
response = CreateDescribeRegionsResponse()
|
||||
err = client.DoAction(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
// DescribeRegionsWithChan invokes the location.DescribeRegions API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeregions.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) DescribeRegionsWithChan(request *DescribeRegionsRequest) (<-chan *DescribeRegionsResponse, <-chan error) {
|
||||
responseChan := make(chan *DescribeRegionsResponse, 1)
|
||||
errChan := make(chan error, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
defer close(responseChan)
|
||||
defer close(errChan)
|
||||
response, err := client.DescribeRegions(request)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
responseChan <- response
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
close(responseChan)
|
||||
close(errChan)
|
||||
}
|
||||
return responseChan, errChan
|
||||
}
|
||||
|
||||
// DescribeRegionsWithCallback invokes the location.DescribeRegions API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeregions.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) DescribeRegionsWithCallback(request *DescribeRegionsRequest, callback func(response *DescribeRegionsResponse, err error)) <-chan int {
|
||||
result := make(chan int, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
var response *DescribeRegionsResponse
|
||||
var err error
|
||||
defer close(result)
|
||||
response, err = client.DescribeRegions(request)
|
||||
callback(response, err)
|
||||
result <- 1
|
||||
})
|
||||
if err != nil {
|
||||
defer close(result)
|
||||
callback(nil, err)
|
||||
result <- 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DescribeRegionsRequest is the request struct for api DescribeRegions
|
||||
type DescribeRegionsRequest struct {
|
||||
*requests.RpcRequest
|
||||
Password string `position:"Query" name:"Password"`
|
||||
}
|
||||
|
||||
// DescribeRegionsResponse is the response struct for api DescribeRegions
|
||||
type DescribeRegionsResponse struct {
|
||||
*responses.BaseResponse
|
||||
RequestId string `json:"RequestId" xml:"RequestId"`
|
||||
TotalCount int `json:"TotalCount" xml:"TotalCount"`
|
||||
RegionIds RegionIds `json:"RegionIds" xml:"RegionIds"`
|
||||
}
|
||||
|
||||
// CreateDescribeRegionsRequest creates a request to invoke DescribeRegions API
|
||||
func CreateDescribeRegionsRequest() (request *DescribeRegionsRequest) {
|
||||
request = &DescribeRegionsRequest{
|
||||
RpcRequest: &requests.RpcRequest{},
|
||||
}
|
||||
request.InitWithApiInfo("Location", "2015-06-12", "DescribeRegions", "location", "openAPI")
|
||||
return
|
||||
}
|
||||
|
||||
// CreateDescribeRegionsResponse creates a response to parse from DescribeRegions response
|
||||
func CreateDescribeRegionsResponse() (response *DescribeRegionsResponse) {
|
||||
response = &DescribeRegionsResponse{
|
||||
BaseResponse: &responses.BaseResponse{},
|
||||
}
|
||||
return
|
||||
}
|
105
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/describe_services.go
generated
vendored
Normal file
105
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/describe_services.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
)
|
||||
|
||||
// DescribeServices invokes the location.DescribeServices API synchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeservices.html
|
||||
func (client *Client) DescribeServices(request *DescribeServicesRequest) (response *DescribeServicesResponse, err error) {
|
||||
response = CreateDescribeServicesResponse()
|
||||
err = client.DoAction(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
// DescribeServicesWithChan invokes the location.DescribeServices API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeservices.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) DescribeServicesWithChan(request *DescribeServicesRequest) (<-chan *DescribeServicesResponse, <-chan error) {
|
||||
responseChan := make(chan *DescribeServicesResponse, 1)
|
||||
errChan := make(chan error, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
defer close(responseChan)
|
||||
defer close(errChan)
|
||||
response, err := client.DescribeServices(request)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
responseChan <- response
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
close(responseChan)
|
||||
close(errChan)
|
||||
}
|
||||
return responseChan, errChan
|
||||
}
|
||||
|
||||
// DescribeServicesWithCallback invokes the location.DescribeServices API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/describeservices.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) DescribeServicesWithCallback(request *DescribeServicesRequest, callback func(response *DescribeServicesResponse, err error)) <-chan int {
|
||||
result := make(chan int, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
var response *DescribeServicesResponse
|
||||
var err error
|
||||
defer close(result)
|
||||
response, err = client.DescribeServices(request)
|
||||
callback(response, err)
|
||||
result <- 1
|
||||
})
|
||||
if err != nil {
|
||||
defer close(result)
|
||||
callback(nil, err)
|
||||
result <- 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// DescribeServicesRequest is the request struct for api DescribeServices
|
||||
type DescribeServicesRequest struct {
|
||||
*requests.RpcRequest
|
||||
Password string `position:"Query" name:"Password"`
|
||||
}
|
||||
|
||||
// DescribeServicesResponse is the response struct for api DescribeServices
|
||||
type DescribeServicesResponse struct {
|
||||
*responses.BaseResponse
|
||||
RequestId string `json:"RequestId" xml:"RequestId"`
|
||||
TotalCount int `json:"TotalCount" xml:"TotalCount"`
|
||||
Services Services `json:"Services" xml:"Services"`
|
||||
}
|
||||
|
||||
// CreateDescribeServicesRequest creates a request to invoke DescribeServices API
|
||||
func CreateDescribeServicesRequest() (request *DescribeServicesRequest) {
|
||||
request = &DescribeServicesRequest{
|
||||
RpcRequest: &requests.RpcRequest{},
|
||||
}
|
||||
request.InitWithApiInfo("Location", "2015-06-12", "DescribeServices", "location", "openAPI")
|
||||
return
|
||||
}
|
||||
|
||||
// CreateDescribeServicesResponse creates a response to parse from DescribeServices response
|
||||
func CreateDescribeServicesResponse() (response *DescribeServicesResponse) {
|
||||
response = &DescribeServicesResponse{
|
||||
BaseResponse: &responses.BaseResponse{},
|
||||
}
|
||||
return
|
||||
}
|
107
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/list_endpoints.go
generated
vendored
Normal file
107
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/list_endpoints.go
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
)
|
||||
|
||||
// ListEndpoints invokes the location.ListEndpoints API synchronously
|
||||
// api document: https://help.aliyun.com/api/location/listendpoints.html
|
||||
func (client *Client) ListEndpoints(request *ListEndpointsRequest) (response *ListEndpointsResponse, err error) {
|
||||
response = CreateListEndpointsResponse()
|
||||
err = client.DoAction(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
// ListEndpointsWithChan invokes the location.ListEndpoints API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/listendpoints.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) ListEndpointsWithChan(request *ListEndpointsRequest) (<-chan *ListEndpointsResponse, <-chan error) {
|
||||
responseChan := make(chan *ListEndpointsResponse, 1)
|
||||
errChan := make(chan error, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
defer close(responseChan)
|
||||
defer close(errChan)
|
||||
response, err := client.ListEndpoints(request)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
responseChan <- response
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
close(responseChan)
|
||||
close(errChan)
|
||||
}
|
||||
return responseChan, errChan
|
||||
}
|
||||
|
||||
// ListEndpointsWithCallback invokes the location.ListEndpoints API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/listendpoints.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) ListEndpointsWithCallback(request *ListEndpointsRequest, callback func(response *ListEndpointsResponse, err error)) <-chan int {
|
||||
result := make(chan int, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
var response *ListEndpointsResponse
|
||||
var err error
|
||||
defer close(result)
|
||||
response, err = client.ListEndpoints(request)
|
||||
callback(response, err)
|
||||
result <- 1
|
||||
})
|
||||
if err != nil {
|
||||
defer close(result)
|
||||
callback(nil, err)
|
||||
result <- 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ListEndpointsRequest is the request struct for api ListEndpoints
|
||||
type ListEndpointsRequest struct {
|
||||
*requests.RpcRequest
|
||||
Namespace string `position:"Query" name:"Namespace"`
|
||||
Id string `position:"Query" name:"Id"`
|
||||
SerivceCode string `position:"Query" name:"SerivceCode"`
|
||||
}
|
||||
|
||||
// ListEndpointsResponse is the response struct for api ListEndpoints
|
||||
type ListEndpointsResponse struct {
|
||||
*responses.BaseResponse
|
||||
RequestId string `json:"RequestId" xml:"RequestId"`
|
||||
Success bool `json:"Success" xml:"Success"`
|
||||
EndpointList EndpointListInListEndpoints `json:"EndpointList" xml:"EndpointList"`
|
||||
}
|
||||
|
||||
// CreateListEndpointsRequest creates a request to invoke ListEndpoints API
|
||||
func CreateListEndpointsRequest() (request *ListEndpointsRequest) {
|
||||
request = &ListEndpointsRequest{
|
||||
RpcRequest: &requests.RpcRequest{},
|
||||
}
|
||||
request.InitWithApiInfo("Location", "2015-06-12", "ListEndpoints", "location", "openAPI")
|
||||
return
|
||||
}
|
||||
|
||||
// CreateListEndpointsResponse creates a response to parse from ListEndpoints response
|
||||
func CreateListEndpointsResponse() (response *ListEndpointsResponse) {
|
||||
response = &ListEndpointsResponse{
|
||||
BaseResponse: &responses.BaseResponse{},
|
||||
}
|
||||
return
|
||||
}
|
105
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/list_endpoints_by_ip.go
generated
vendored
Normal file
105
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/list_endpoints_by_ip.go
generated
vendored
Normal file
|
@ -0,0 +1,105 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
import (
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
|
||||
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
|
||||
)
|
||||
|
||||
// ListEndpointsByIp invokes the location.ListEndpointsByIp API synchronously
|
||||
// api document: https://help.aliyun.com/api/location/listendpointsbyip.html
|
||||
func (client *Client) ListEndpointsByIp(request *ListEndpointsByIpRequest) (response *ListEndpointsByIpResponse, err error) {
|
||||
response = CreateListEndpointsByIpResponse()
|
||||
err = client.DoAction(request, response)
|
||||
return
|
||||
}
|
||||
|
||||
// ListEndpointsByIpWithChan invokes the location.ListEndpointsByIp API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/listendpointsbyip.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) ListEndpointsByIpWithChan(request *ListEndpointsByIpRequest) (<-chan *ListEndpointsByIpResponse, <-chan error) {
|
||||
responseChan := make(chan *ListEndpointsByIpResponse, 1)
|
||||
errChan := make(chan error, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
defer close(responseChan)
|
||||
defer close(errChan)
|
||||
response, err := client.ListEndpointsByIp(request)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
} else {
|
||||
responseChan <- response
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
close(responseChan)
|
||||
close(errChan)
|
||||
}
|
||||
return responseChan, errChan
|
||||
}
|
||||
|
||||
// ListEndpointsByIpWithCallback invokes the location.ListEndpointsByIp API asynchronously
|
||||
// api document: https://help.aliyun.com/api/location/listendpointsbyip.html
|
||||
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
|
||||
func (client *Client) ListEndpointsByIpWithCallback(request *ListEndpointsByIpRequest, callback func(response *ListEndpointsByIpResponse, err error)) <-chan int {
|
||||
result := make(chan int, 1)
|
||||
err := client.AddAsyncTask(func() {
|
||||
var response *ListEndpointsByIpResponse
|
||||
var err error
|
||||
defer close(result)
|
||||
response, err = client.ListEndpointsByIp(request)
|
||||
callback(response, err)
|
||||
result <- 1
|
||||
})
|
||||
if err != nil {
|
||||
defer close(result)
|
||||
callback(nil, err)
|
||||
result <- 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ListEndpointsByIpRequest is the request struct for api ListEndpointsByIp
|
||||
type ListEndpointsByIpRequest struct {
|
||||
*requests.RpcRequest
|
||||
Ip string `position:"Query" name:"Ip"`
|
||||
}
|
||||
|
||||
// ListEndpointsByIpResponse is the response struct for api ListEndpointsByIp
|
||||
type ListEndpointsByIpResponse struct {
|
||||
*responses.BaseResponse
|
||||
RequestId string `json:"RequestId" xml:"RequestId"`
|
||||
Success bool `json:"Success" xml:"Success"`
|
||||
EndpointList EndpointListInListEndpointsByIp `json:"EndpointList" xml:"EndpointList"`
|
||||
}
|
||||
|
||||
// CreateListEndpointsByIpRequest creates a request to invoke ListEndpointsByIp API
|
||||
func CreateListEndpointsByIpRequest() (request *ListEndpointsByIpRequest) {
|
||||
request = &ListEndpointsByIpRequest{
|
||||
RpcRequest: &requests.RpcRequest{},
|
||||
}
|
||||
request.InitWithApiInfo("Location", "2015-06-12", "ListEndpointsByIp", "location", "openAPI")
|
||||
return
|
||||
}
|
||||
|
||||
// CreateListEndpointsByIpResponse creates a response to parse from ListEndpointsByIp response
|
||||
func CreateListEndpointsByIpResponse() (response *ListEndpointsByIpResponse) {
|
||||
response = &ListEndpointsByIpResponse{
|
||||
BaseResponse: &responses.BaseResponse{},
|
||||
}
|
||||
return
|
||||
}
|
26
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_endpoint.go
generated
vendored
Normal file
26
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_endpoint.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// Endpoint is a nested struct in location response
|
||||
type Endpoint struct {
|
||||
Endpoint string `json:"Endpoint" xml:"Endpoint"`
|
||||
Id string `json:"Id" xml:"Id"`
|
||||
Namespace string `json:"Namespace" xml:"Namespace"`
|
||||
SerivceCode string `json:"SerivceCode" xml:"SerivceCode"`
|
||||
Type string `json:"Type" xml:"Type"`
|
||||
Protocols ProtocolsInDescribeEndpoints `json:"Protocols" xml:"Protocols"`
|
||||
}
|
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_endpoint_list_in_list_endpoints.go
generated
vendored
Normal file
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_endpoint_list_in_list_endpoints.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// EndpointListInListEndpoints is a nested struct in location response
|
||||
type EndpointListInListEndpoints struct {
|
||||
ItemEndpoint []ItemEndpoint `json:"ItemEndpoint" xml:"ItemEndpoint"`
|
||||
}
|
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_endpoint_list_in_list_endpoints_by_ip.go
generated
vendored
Normal file
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_endpoint_list_in_list_endpoints_by_ip.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// EndpointListInListEndpointsByIp is a nested struct in location response
|
||||
type EndpointListInListEndpointsByIp struct {
|
||||
ItemEndpoint []ItemEndpoint `json:"ItemEndpoint" xml:"ItemEndpoint"`
|
||||
}
|
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_endpoints.go
generated
vendored
Normal file
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_endpoints.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// Endpoints is a nested struct in location response
|
||||
type Endpoints struct {
|
||||
Endpoint []Endpoint `json:"Endpoint" xml:"Endpoint"`
|
||||
}
|
26
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_item_endpoint.go
generated
vendored
Normal file
26
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_item_endpoint.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// ItemEndpoint is a nested struct in location response
|
||||
type ItemEndpoint struct {
|
||||
Endpoint string `json:"Endpoint" xml:"Endpoint"`
|
||||
Product string `json:"Product" xml:"Product"`
|
||||
Namespace string `json:"Namespace" xml:"Namespace"`
|
||||
Id string `json:"Id" xml:"Id"`
|
||||
Type string `json:"Type" xml:"Type"`
|
||||
Protocols ProtocolsInListEndpointsByIp `json:"Protocols" xml:"Protocols"`
|
||||
}
|
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_protocols_in_describe_endpoint.go
generated
vendored
Normal file
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_protocols_in_describe_endpoint.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// ProtocolsInDescribeEndpoint is a nested struct in location response
|
||||
type ProtocolsInDescribeEndpoint struct {
|
||||
Protocols []string `json:"Protocols" xml:"Protocols"`
|
||||
}
|
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_protocols_in_describe_endpoints.go
generated
vendored
Normal file
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_protocols_in_describe_endpoints.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// ProtocolsInDescribeEndpoints is a nested struct in location response
|
||||
type ProtocolsInDescribeEndpoints struct {
|
||||
Protocols []string `json:"Protocols" xml:"Protocols"`
|
||||
}
|
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_protocols_in_list_endpoints.go
generated
vendored
Normal file
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_protocols_in_list_endpoints.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// ProtocolsInListEndpoints is a nested struct in location response
|
||||
type ProtocolsInListEndpoints struct {
|
||||
Protocols []string `json:"Protocols" xml:"Protocols"`
|
||||
}
|
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_protocols_in_list_endpoints_by_ip.go
generated
vendored
Normal file
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_protocols_in_list_endpoints_by_ip.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// ProtocolsInListEndpointsByIp is a nested struct in location response
|
||||
type ProtocolsInListEndpointsByIp struct {
|
||||
Protocols []string `json:"Protocols" xml:"Protocols"`
|
||||
}
|
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_region_ids.go
generated
vendored
Normal file
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_region_ids.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// RegionIds is a nested struct in location response
|
||||
type RegionIds struct {
|
||||
RegionIds []string `json:"RegionIds" xml:"RegionIds"`
|
||||
}
|
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_services.go
generated
vendored
Normal file
21
vendor/github.com/aliyun/alibaba-cloud-sdk-go/services/location/struct_services.go
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
package location
|
||||
|
||||
//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.
|
||||
//
|
||||
// Code generated by Alibaba Cloud SDK Code Generator.
|
||||
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
|
||||
|
||||
// Services is a nested struct in location response
|
||||
type Services struct {
|
||||
Services []string `json:"Services" xml:"Services"`
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"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
|
||||
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]
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,775 @@
|
|||
// Package oss implements functions for access oss service.
|
||||
// It has two main struct Client and Bucket.
|
||||
package oss
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"io"
|
||||
"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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package oss
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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.
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
return &config
|
||||
}
|
|
@ -0,0 +1,620 @@
|
|||
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)
|
||||
|
||||
resp, err := conn.client.Do(req)
|
||||
if err != nil {
|
||||
// Transfer failed
|
||||
event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength)
|
||||
publishProgress(listener, event)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
resp, err := conn.client.Do(req)
|
||||
if err != nil {
|
||||
// Transfer failed
|
||||
event = newProgressEvent(TransferFailedEvent, tracker.completedBytes, req.ContentLength)
|
||||
publishProgress(listener, event)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
req.Body = rc
|
||||
|
||||
return file, crc
|
||||
}
|
||||
|
||||
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 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
|
@ -0,0 +1,145 @@
|
|||
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"
|
||||
)
|
||||
|
||||
// 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.2" // Go SDK version
|
||||
)
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,468 @@
|
|||
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"`
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
ignore:
|
||||
- "output_tests/.*"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
/vendor
|
||||
/bug_test.go
|
||||
/coverage.txt
|
||||
/.idea
|
|
@ -0,0 +1,14 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- 1.8.x
|
||||
- 1.x
|
||||
|
||||
before_install:
|
||||
- go get -t -v ./...
|
||||
|
||||
script:
|
||||
- ./test.sh
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
|
@ -0,0 +1,21 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/modern-go/concurrent"
|
||||
packages = ["."]
|
||||
revision = "e0a39a4cb4216ea8db28e22a69f4ec25610d513a"
|
||||
version = "1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/modern-go/reflect2"
|
||||
packages = ["."]
|
||||
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
|
||||
version = "1.0.1"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "ea54a775e5a354cb015502d2e7aa4b74230fc77e894f34a838b268c25ec8eeb8"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue