Add Alibaba Cloud backend OSS with lock

This commit is contained in:
He Guimin 2019-03-07 18:31:36 +08:00
parent 88e76fa9ef
commit b887d44712
218 changed files with 34163 additions and 0 deletions

View File

@ -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() },

View File

@ -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
}

View File

@ -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.
`

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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
View File

@ -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
View File

@ -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=

201
vendor/github.com/aliyun/alibaba-cloud-sdk-go/LICENSE generated vendored Normal file
View File

@ -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.

View 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 {
}

View 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,
}
}

View 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,
}
}

View 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,
}
}

View 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,
}
}

View 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,
}
}

View 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)
}
}

View 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
}

View 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 (
"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
}

View 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)
}

View 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
}

View File

@ -0,0 +1,7 @@
package signers
type SessionCredential struct {
AccessKeyId string
AccessKeySecret string
StsToken string
}

View 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)
}

View 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
}

View 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
}

View 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
}

View 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)
}

View 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)
}

View File

@ -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
}

View File

@ -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)
//}

View File

@ -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
}

View File

@ -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)
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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)
}

View 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
}

View 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()
}

View File

@ -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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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))
}

View 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 = ""
}

View 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")
// }

View 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
}

View 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")
}

View 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)
}

View 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)
}

View File

@ -0,0 +1,6 @@
package resource
func GetTZData(name string) ([]byte, bool) {
data, ok := files["zoneinfo/"+name]
return data, ok
}

File diff suppressed because one or more lines are too long

View 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")
}
}

View 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{},
}
}

View File

@ -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...))
}
}
}

View File

@ -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)
}
}
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

View 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"`
}

97
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/auth.go generated vendored Normal file
View File

@ -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]
}

View File

@ -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
}

View File

@ -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)
}

77
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conf.go generated vendored Normal file
View File

@ -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
}

620
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/conn.go generated vendored Normal file
View File

@ -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)
}

145
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/const.go generated vendored Normal file
View File

@ -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
)

123
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/crc.go generated vendored Normal file
View File

@ -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
}

View File

@ -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)
}

View File

@ -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)}
}

245
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/mime.go generated vendored Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

468
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/type.go generated vendored Normal file
View File

@ -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"`
}

View File

@ -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
}

265
vendor/github.com/aliyun/aliyun-oss-go-sdk/oss/utils.go generated vendored Normal file
View File

@ -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)
}

3
vendor/github.com/json-iterator/go/.codecov.yml generated vendored Normal file
View File

@ -0,0 +1,3 @@
ignore:
- "output_tests/.*"

4
vendor/github.com/json-iterator/go/.gitignore generated vendored Normal file
View File

@ -0,0 +1,4 @@
/vendor
/bug_test.go
/coverage.txt
/.idea

14
vendor/github.com/json-iterator/go/.travis.yml generated vendored Normal file
View File

@ -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)

21
vendor/github.com/json-iterator/go/Gopkg.lock generated vendored Normal file
View File

@ -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