backend/cos: Add TencentCloud backend cos with lock (#22540)

* add TencentCloud COS backend for remote state

* add vendor of dependence

* fixed error not handle and remove default value for prefix argument

* get appid from TF_COS_APPID environment variables
This commit is contained in:
Li Kexian 2020-02-14 00:37:11 +08:00 committed by GitHub
parent 7bd662d587
commit 76e5b446ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 6937 additions and 0 deletions

View File

@ -5,3 +5,4 @@
/backend/remote-state/azure @hashicorp/terraform-azure
/backend/remote-state/gcs @hashicorp/terraform-google
/backend/remote-state/s3 @hashicorp/terraform-aws
/backend/remote-state/s3 @likexian

View File

@ -16,6 +16,7 @@ import (
backendArtifactory "github.com/hashicorp/terraform/backend/remote-state/artifactory"
backendAzure "github.com/hashicorp/terraform/backend/remote-state/azure"
backendConsul "github.com/hashicorp/terraform/backend/remote-state/consul"
backendCos "github.com/hashicorp/terraform/backend/remote-state/cos"
backendEtcdv2 "github.com/hashicorp/terraform/backend/remote-state/etcdv2"
backendEtcdv3 "github.com/hashicorp/terraform/backend/remote-state/etcdv3"
backendGCS "github.com/hashicorp/terraform/backend/remote-state/gcs"
@ -57,6 +58,7 @@ func Init(services *disco.Disco) {
"atlas": func() backend.Backend { return backendAtlas.New() },
"azurerm": func() backend.Backend { return backendAzure.New() },
"consul": func() backend.Backend { return backendConsul.New() },
"cos": func() backend.Backend { return backendCos.New() },
"etcd": func() backend.Backend { return backendEtcdv2.New() },
"etcdv3": func() backend.Backend { return backendEtcdv3.New() },
"gcs": func() backend.Backend { return backendGCS.New() },

View File

@ -18,6 +18,7 @@ func TestInit_backend(t *testing.T) {
{"atlas", "*atlas.Backend"},
{"azurerm", "*azure.Backend"},
{"consul", "*consul.Backend"},
{"cos", "*cos.Backend"},
{"etcdv3", "*etcd.Backend"},
{"gcs", "*gcs.Backend"},
{"inmem", "*inmem.Backend"},

View File

@ -0,0 +1,169 @@
package cos
import (
"context"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/helper/schema"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813"
"github.com/tencentyun/cos-go-sdk-v5"
)
// Default value from environment variable
const (
PROVIDER_SECRET_ID = "TENCENTCLOUD_SECRET_ID"
PROVIDER_SECRET_KEY = "TENCENTCLOUD_SECRET_KEY"
PROVIDER_REGION = "TENCENTCLOUD_REGION"
)
// Backend implements "backend".Backend for tencentCloud cos
type Backend struct {
*schema.Backend
cosContext context.Context
cosClient *cos.Client
tagClient *tag.Client
region string
bucket string
prefix string
key string
encrypt bool
acl string
}
// New creates a new backend for TencentCloud cos remote state.
func New() backend.Backend {
s := &schema.Backend{
Schema: map[string]*schema.Schema{
"secret_id": {
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SECRET_ID, nil),
Description: "Secret id of Tencent Cloud",
},
"secret_key": {
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc(PROVIDER_SECRET_KEY, nil),
Description: "Secret key of Tencent Cloud",
Sensitive: true,
},
"region": {
Type: schema.TypeString,
Required: true,
DefaultFunc: schema.EnvDefaultFunc(PROVIDER_REGION, nil),
Description: "The region of the COS bucket",
InputDefault: "ap-guangzhou",
},
"bucket": {
Type: schema.TypeString,
Required: true,
Description: "The name of the COS bucket",
},
"prefix": {
Type: schema.TypeString,
Optional: true,
Description: "The directory for saving the state file in bucket",
ValidateFunc: func(v interface{}, s string) ([]string, []error) {
prefix := v.(string)
if strings.HasPrefix(prefix, "/") || strings.HasPrefix(prefix, "./") {
return nil, []error{fmt.Errorf("prefix must not start with '/' or './'")}
}
return nil, nil
},
},
"key": {
Type: schema.TypeString,
Optional: true,
Description: "The path for saving the state file in bucket",
Default: "terraform.tfstate",
ValidateFunc: func(v interface{}, s string) ([]string, []error) {
if strings.HasPrefix(v.(string), "/") || strings.HasSuffix(v.(string), "/") {
return nil, []error{fmt.Errorf("key can not start and end with '/'")}
}
return nil, nil
},
},
"encrypt": {
Type: schema.TypeBool,
Optional: true,
Description: "Whether to enable server side encryption of the state file",
Default: true,
},
"acl": {
Type: schema.TypeString,
Optional: true,
Description: "Object ACL to be applied to the state file",
Default: "private",
ValidateFunc: func(v interface{}, s string) ([]string, []error) {
value := v.(string)
if value != "private" && value != "public-read" {
return nil, []error{fmt.Errorf(
"acl value invalid, expected %s or %s, got %s",
"private", "public-read", value)}
}
return nil, nil
},
},
},
}
result := &Backend{Backend: s}
result.Backend.ConfigureFunc = result.configure
return result
}
// configure init cos client
func (b *Backend) configure(ctx context.Context) error {
if b.cosClient != nil {
return nil
}
b.cosContext = ctx
data := schema.FromContextBackendConfig(b.cosContext)
b.region = data.Get("region").(string)
b.bucket = data.Get("bucket").(string)
b.prefix = data.Get("prefix").(string)
b.key = data.Get("key").(string)
b.encrypt = data.Get("encrypt").(bool)
b.acl = data.Get("acl").(string)
u, err := url.Parse(fmt.Sprintf("https://%s.cos.%s.myqcloud.com", b.bucket, b.region))
if err != nil {
return err
}
b.cosClient = cos.NewClient(
&cos.BaseURL{BucketURL: u},
&http.Client{
Timeout: 60 * time.Second,
Transport: &cos.AuthorizationTransport{
SecretID: data.Get("secret_id").(string),
SecretKey: data.Get("secret_key").(string),
},
},
)
credential := common.NewCredential(
data.Get("secret_id").(string),
data.Get("secret_key").(string),
)
cpf := profile.NewClientProfile()
cpf.HttpProfile.ReqMethod = "POST"
cpf.HttpProfile.ReqTimeout = 300
cpf.Language = "en-US"
b.tagClient, err = tag.NewClient(credential, b.region, cpf)
return err
}

View File

@ -0,0 +1,178 @@
package cos
import (
"fmt"
"log"
"path"
"sort"
"strings"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/states"
"github.com/likexian/gokit/assert"
)
// Define file suffix
const (
stateFileSuffix = ".tfstate"
lockFileSuffix = ".tflock"
)
// Workspaces returns a list of names for the workspaces
func (b *Backend) Workspaces() ([]string, error) {
c, err := b.client("tencentcloud")
if err != nil {
return nil, err
}
obs, err := c.getBucket(b.prefix)
log.Printf("[DEBUG] list all workspaces, objects: %v, error: %v", obs, err)
if err != nil {
return nil, err
}
ws := []string{backend.DefaultStateName}
for _, vv := range obs {
// <name>.tfstate
if !strings.HasSuffix(vv.Key, stateFileSuffix) {
continue
}
// default worksapce
if path.Join(b.prefix, b.key) == vv.Key {
continue
}
// <prefix>/<worksapce>/<key>
prefix := strings.TrimRight(b.prefix, "/") + "/"
parts := strings.Split(strings.TrimPrefix(vv.Key, prefix), "/")
if len(parts) > 0 && parts[0] != "" {
ws = append(ws, parts[0])
}
}
sort.Strings(ws[1:])
log.Printf("[DEBUG] list all workspaces, workspaces: %v", ws)
return ws, nil
}
// DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted.
func (b *Backend) DeleteWorkspace(name string) error {
log.Printf("[DEBUG] delete workspace, workspace: %v", name)
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("default state is not allow to delete")
}
c, err := b.client(name)
if err != nil {
return err
}
return c.Delete()
}
// StateMgr manage the state, if the named state not exists, a new file will created
func (b *Backend) StateMgr(name string) (state.State, error) {
log.Printf("[DEBUG] state manager, current workspace: %v", name)
c, err := b.client(name)
if err != nil {
return nil, err
}
stateMgr := &remote.State{Client: c}
ws, err := b.Workspaces()
if err != nil {
return nil, err
}
if !assert.IsContains(ws, name) {
log.Printf("[DEBUG] workspace %v not exists", name)
// take a lock on this state while we write it
lockInfo := state.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := c.Lock(lockInfo)
if err != nil {
return nil, fmt.Errorf("Failed to lock cos 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(unlockErrMsg, err, lockId)
}
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
}
// client returns a remoteClient for the named state.
func (b *Backend) client(name string) (*remoteClient, error) {
if strings.TrimSpace(name) == "" {
return nil, fmt.Errorf("state name not allow to be empty")
}
return &remoteClient{
cosContext: b.cosContext,
cosClient: b.cosClient,
tagClient: b.tagClient,
bucket: b.bucket,
stateFile: b.stateFile(name),
lockFile: b.lockFile(name),
encrypt: b.encrypt,
acl: b.acl,
}, nil
}
// stateFile returns state file path by name
func (b *Backend) stateFile(name string) string {
if name == backend.DefaultStateName {
return path.Join(b.prefix, b.key)
}
return path.Join(b.prefix, name, b.key)
}
// lockFile returns lock file path by name
func (b *Backend) lockFile(name string) string {
return b.stateFile(name) + lockFileSuffix
}
// unlockErrMsg is error msg for unlock failed
const unlockErrMsg = `
Unlocking the state file on TencentCloud cos backend failed:
Error message: %v
Lock ID (gen): %s
You may have to force-unlock this state in order to use it again.
The TencentCloud backend acquires a lock during initialization
to ensure the initial state file is created.
`

View File

@ -0,0 +1,227 @@
package cos
import (
"crypto/md5"
"fmt"
"os"
"testing"
"time"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/state/remote"
"github.com/likexian/gokit/assert"
)
const (
defaultPrefix = ""
defaultKey = "terraform.tfstate"
)
// Testing Thanks to GCS
func TestStateFile(t *testing.T) {
t.Parallel()
cases := []struct {
prefix string
stateName string
key string
wantStateFile string
wantLockFile string
}{
{"", "default", "default.tfstate", "default.tfstate", "default.tfstate.tflock"},
{"", "default", "test.tfstate", "test.tfstate", "test.tfstate.tflock"},
{"", "dev", "test.tfstate", "dev/test.tfstate", "dev/test.tfstate.tflock"},
{"terraform/test", "default", "default.tfstate", "terraform/test/default.tfstate", "terraform/test/default.tfstate.tflock"},
{"terraform/test", "default", "test.tfstate", "terraform/test/test.tfstate", "terraform/test/test.tfstate.tflock"},
{"terraform/test", "dev", "test.tfstate", "terraform/test/dev/test.tfstate", "terraform/test/dev/test.tfstate.tflock"},
}
for _, c := range cases {
b := &Backend{
prefix: c.prefix,
key: c.key,
}
assert.Equal(t, b.stateFile(c.stateName), c.wantStateFile)
assert.Equal(t, b.lockFile(c.stateName), c.wantLockFile)
}
}
func TestRemoteClient(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be := setupBackend(t, bucket, defaultPrefix, defaultKey, false)
defer teardownBackend(t, be)
ss, err := be.StateMgr(backend.DefaultStateName)
assert.Nil(t, err)
rs, ok := ss.(*remote.State)
assert.True(t, ok)
remote.TestClient(t, rs.Client)
}
func TestRemoteClientWithPrefix(t *testing.T) {
t.Parallel()
prefix := "prefix/test"
bucket := bucketName(t)
be := setupBackend(t, bucket, prefix, defaultKey, false)
defer teardownBackend(t, be)
ss, err := be.StateMgr(backend.DefaultStateName)
assert.Nil(t, err)
rs, ok := ss.(*remote.State)
assert.True(t, ok)
remote.TestClient(t, rs.Client)
}
func TestRemoteClientWithEncryption(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be := setupBackend(t, bucket, defaultPrefix, defaultKey, true)
defer teardownBackend(t, be)
ss, err := be.StateMgr(backend.DefaultStateName)
assert.Nil(t, err)
rs, ok := ss.(*remote.State)
assert.True(t, ok)
remote.TestClient(t, rs.Client)
}
func TestRemoteLocks(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be := setupBackend(t, bucket, defaultPrefix, defaultKey, false)
defer teardownBackend(t, be)
remoteClient := func() (remote.Client, error) {
ss, err := be.StateMgr(backend.DefaultStateName)
if err != nil {
return nil, err
}
rs, ok := ss.(*remote.State)
if !ok {
return nil, fmt.Errorf("be.StateMgr(): got a %T, want a *remote.State", ss)
}
return rs.Client, nil
}
c0, err := remoteClient()
assert.Nil(t, err)
c1, err := remoteClient()
assert.Nil(t, err)
remote.TestRemoteLocks(t, c0, c1)
}
func TestBackend(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be0 := setupBackend(t, bucket, defaultPrefix, defaultKey, false)
defer teardownBackend(t, be0)
be1 := setupBackend(t, bucket, defaultPrefix, defaultKey, false)
defer teardownBackend(t, be1)
backend.TestBackendStates(t, be0)
backend.TestBackendStateLocks(t, be0, be1)
backend.TestBackendStateForceUnlock(t, be0, be1)
}
func TestBackendWithPrefix(t *testing.T) {
t.Parallel()
prefix := "prefix/test"
bucket := bucketName(t)
be0 := setupBackend(t, bucket, prefix, defaultKey, false)
defer teardownBackend(t, be0)
be1 := setupBackend(t, bucket, prefix+"/", defaultKey, false)
defer teardownBackend(t, be1)
backend.TestBackendStates(t, be0)
backend.TestBackendStateLocks(t, be0, be1)
}
func TestBackendWithEncryption(t *testing.T) {
t.Parallel()
bucket := bucketName(t)
be0 := setupBackend(t, bucket, defaultPrefix, defaultKey, true)
defer teardownBackend(t, be0)
be1 := setupBackend(t, bucket, defaultPrefix, defaultKey, true)
defer teardownBackend(t, be1)
backend.TestBackendStates(t, be0)
backend.TestBackendStateLocks(t, be0, be1)
}
func setupBackend(t *testing.T, bucket, prefix, key string, encrypt bool) backend.Backend {
t.Helper()
skip := os.Getenv("TF_COS_APPID") == ""
if skip {
t.Skip("This test require setting TF_COS_APPID environment variables")
}
if os.Getenv(PROVIDER_REGION) == "" {
os.Setenv(PROVIDER_REGION, "ap-guangzhou")
}
appId := os.Getenv("TF_COS_APPID")
region := os.Getenv(PROVIDER_REGION)
config := map[string]interface{}{
"region": region,
"bucket": bucket + appId,
"prefix": prefix,
"key": key,
}
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config))
be := b.(*Backend)
c, err := be.client("tencentcloud")
assert.Nil(t, err)
err = c.putBucket()
assert.Nil(t, err)
return b
}
func teardownBackend(t *testing.T, b backend.Backend) {
t.Helper()
c, err := b.(*Backend).client("tencentcloud")
assert.Nil(t, err)
err = c.deleteBucket(true)
assert.Nil(t, err)
}
func bucketName(t *testing.T) string {
unique := fmt.Sprintf("%s-%x", t.Name(), time.Now().UnixNano())
return fmt.Sprintf("terraform-test-%s-%s", fmt.Sprintf("%x", md5.Sum([]byte(unique)))[:10], "")
}

View File

@ -0,0 +1,403 @@
package cos
import (
"bytes"
"context"
"crypto/md5"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"strings"
"time"
multierror "github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/state"
"github.com/hashicorp/terraform/state/remote"
tag "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tag/v20180813"
"github.com/tencentyun/cos-go-sdk-v5"
)
const (
lockTagKey = "tencentcloud-terraform-lock"
)
// RemoteClient implements the client of remote state
type remoteClient struct {
cosContext context.Context
cosClient *cos.Client
tagClient *tag.Client
bucket string
stateFile string
lockFile string
encrypt bool
acl string
}
// Get returns remote state file
func (c *remoteClient) Get() (*remote.Payload, error) {
log.Printf("[DEBUG] get remote state file %s", c.stateFile)
exists, data, checksum, err := c.getObject(c.stateFile)
if err != nil {
return nil, err
}
if !exists {
return nil, nil
}
payload := &remote.Payload{
Data: data,
MD5: []byte(checksum),
}
return payload, nil
}
// Put put state file to remote
func (c *remoteClient) Put(data []byte) error {
log.Printf("[DEBUG] put remote state file %s", c.stateFile)
return c.putObject(c.stateFile, data)
}
// Delete delete remote state file
func (c *remoteClient) Delete() error {
log.Printf("[DEBUG] delete remote state file %s", c.stateFile)
return c.deleteObject(c.stateFile)
}
// Lock lock remote state file for writing
func (c *remoteClient) Lock(info *state.LockInfo) (string, error) {
log.Printf("[DEBUG] lock remote state file %s", c.lockFile)
err := c.cosLock(c.bucket, c.lockFile)
if err != nil {
return "", c.lockError(err)
}
defer c.cosUnlock(c.bucket, c.lockFile)
exists, _, _, err := c.getObject(c.lockFile)
if err != nil {
return "", c.lockError(err)
}
if exists {
return "", c.lockError(fmt.Errorf("lock file %s exists", c.lockFile))
}
info.Path = c.lockFile
data, err := json.Marshal(info)
if err != nil {
return "", c.lockError(err)
}
check := fmt.Sprintf("%x", md5.Sum(data))
err = c.putObject(c.lockFile, data)
if err != nil {
return "", c.lockError(err)
}
return check, nil
}
// Unlock unlock remote state file
func (c *remoteClient) Unlock(check string) error {
log.Printf("[DEBUG] unlock remote state file %s", c.lockFile)
info, err := c.lockInfo()
if err != nil {
return c.lockError(err)
}
if info.ID != check {
return c.lockError(fmt.Errorf("lock id mismatch, %v != %v", info.ID, check))
}
err = c.deleteObject(c.lockFile)
if err != nil {
return c.lockError(err)
}
return nil
}
// lockError returns state.LockError
func (c *remoteClient) lockError(err error) *state.LockError {
log.Printf("[DEBUG] failed to lock or unlock %s: %v", c.lockFile, err)
lockErr := &state.LockError{
Err: err,
}
info, infoErr := c.lockInfo()
if infoErr != nil {
lockErr.Err = multierror.Append(lockErr.Err, infoErr)
} else {
lockErr.Info = info
}
return lockErr
}
// lockInfo returns LockInfo from lock file
func (c *remoteClient) lockInfo() (*state.LockInfo, error) {
exists, data, checksum, err := c.getObject(c.lockFile)
if err != nil {
return nil, err
}
if !exists {
return nil, fmt.Errorf("lock file %s not exists", c.lockFile)
}
info := &state.LockInfo{}
if err := json.Unmarshal(data, info); err != nil {
return nil, err
}
info.ID = checksum
return info, nil
}
// getObject get remote object
func (c *remoteClient) getObject(cosFile string) (exists bool, data []byte, checksum string, err error) {
rsp, err := c.cosClient.Object.Get(c.cosContext, cosFile, nil)
if rsp == nil {
log.Printf("[DEBUG] getObject %s: error: %v", cosFile, err)
err = fmt.Errorf("failed to open file at %v: %v", cosFile, err)
return
}
defer rsp.Body.Close()
log.Printf("[DEBUG] getObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err)
if err != nil {
if rsp.StatusCode == 404 {
err = nil
} else {
err = fmt.Errorf("failed to open file at %v: %v", cosFile, err)
}
return
}
checksum = rsp.Header.Get("X-Cos-Meta-Md5")
log.Printf("[DEBUG] getObject %s: checksum: %s", cosFile, checksum)
if len(checksum) != 32 {
err = fmt.Errorf("failed to open file at %v: checksum %s invalid", cosFile, checksum)
return
}
exists = true
data, err = ioutil.ReadAll(rsp.Body)
log.Printf("[DEBUG] getObject %s: data length: %d", cosFile, len(data))
if err != nil {
err = fmt.Errorf("failed to open file at %v: %v", cosFile, err)
return
}
check := fmt.Sprintf("%x", md5.Sum(data))
log.Printf("[DEBUG] getObject %s: check: %s", cosFile, check)
if check != checksum {
err = fmt.Errorf("failed to open file at %v: checksum mismatch, %s != %s", cosFile, check, checksum)
return
}
return
}
// putObject put object to remote
func (c *remoteClient) putObject(cosFile string, data []byte) error {
opt := &cos.ObjectPutOptions{
ObjectPutHeaderOptions: &cos.ObjectPutHeaderOptions{
XCosMetaXXX: &http.Header{
"X-Cos-Meta-Md5": []string{fmt.Sprintf("%x", md5.Sum(data))},
},
},
ACLHeaderOptions: &cos.ACLHeaderOptions{
XCosACL: c.acl,
},
}
if c.encrypt {
opt.ObjectPutHeaderOptions.XCosServerSideEncryption = "AES256"
}
r := bytes.NewReader(data)
rsp, err := c.cosClient.Object.Put(c.cosContext, cosFile, r, opt)
if rsp == nil {
log.Printf("[DEBUG] putObject %s: error: %v", cosFile, err)
return fmt.Errorf("failed to save file to %v: %v", cosFile, err)
}
defer rsp.Body.Close()
log.Printf("[DEBUG] putObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err)
if err != nil {
return fmt.Errorf("failed to save file to %v: %v", cosFile, err)
}
return nil
}
// deleteObject delete remote object
func (c *remoteClient) deleteObject(cosFile string) error {
rsp, err := c.cosClient.Object.Delete(c.cosContext, cosFile)
if rsp == nil {
log.Printf("[DEBUG] deleteObject %s: error: %v", cosFile, err)
return fmt.Errorf("failed to delete file %v: %v", cosFile, err)
}
defer rsp.Body.Close()
log.Printf("[DEBUG] deleteObject %s: code: %d, error: %v", cosFile, rsp.StatusCode, err)
if rsp.StatusCode == 404 {
return nil
}
if err != nil {
return fmt.Errorf("failed to delete file %v: %v", cosFile, err)
}
return nil
}
// getBucket list bucket by prefix
func (c *remoteClient) getBucket(prefix string) (obs []cos.Object, err error) {
fs, rsp, err := c.cosClient.Bucket.Get(c.cosContext, &cos.BucketGetOptions{Prefix: prefix})
if rsp == nil {
log.Printf("[DEBUG] getBucket %s/%s: error: %v", c.bucket, prefix, err)
err = fmt.Errorf("bucket %s not exists", c.bucket)
return
}
defer rsp.Body.Close()
log.Printf("[DEBUG] getBucket %s/%s: code: %d, error: %v", c.bucket, prefix, rsp.StatusCode, err)
if rsp.StatusCode == 404 {
err = fmt.Errorf("bucket %s not exists", c.bucket)
return
}
if err != nil {
return
}
return fs.Contents, nil
}
// putBucket create cos bucket
func (c *remoteClient) putBucket() error {
rsp, err := c.cosClient.Bucket.Put(c.cosContext, nil)
if rsp == nil {
log.Printf("[DEBUG] putBucket %s: error: %v", c.bucket, err)
return fmt.Errorf("failed to create bucket %v: %v", c.bucket, err)
}
defer rsp.Body.Close()
log.Printf("[DEBUG] putBucket %s: code: %d, error: %v", c.bucket, rsp.StatusCode, err)
if rsp.StatusCode == 409 {
return nil
}
if err != nil {
return fmt.Errorf("failed to create bucket %v: %v", c.bucket, err)
}
return nil
}
// deleteBucket delete cos bucket
func (c *remoteClient) deleteBucket(recursive bool) error {
if recursive {
obs, err := c.getBucket("")
if err != nil {
if strings.Contains(err.Error(), "not exists") {
return nil
}
log.Printf("[DEBUG] deleteBucket %s: empty bucket error: %v", c.bucket, err)
return fmt.Errorf("failed to empty bucket %v: %v", c.bucket, err)
}
for _, v := range obs {
c.deleteObject(v.Key)
}
}
rsp, err := c.cosClient.Bucket.Delete(c.cosContext)
if rsp == nil {
log.Printf("[DEBUG] deleteBucket %s: error: %v", c.bucket, err)
return fmt.Errorf("failed to delete bucket %v: %v", c.bucket, err)
}
defer rsp.Body.Close()
log.Printf("[DEBUG] deleteBucket %s: code: %d, error: %v", c.bucket, rsp.StatusCode, err)
if rsp.StatusCode == 404 {
return nil
}
if err != nil {
return fmt.Errorf("failed to delete bucket %v: %v", c.bucket, err)
}
return nil
}
// cosLock lock cos for writing
func (c *remoteClient) cosLock(bucket, cosFile string) error {
log.Printf("[DEBUG] lock cos file %s:%s", bucket, cosFile)
cosPath := fmt.Sprintf("%s:%s", bucket, cosFile)
lockTagValue := fmt.Sprintf("%x", md5.Sum([]byte(cosPath)))
return c.CreateTag(lockTagKey, lockTagValue)
}
// cosUnlock unlock cos writing
func (c *remoteClient) cosUnlock(bucket, cosFile string) error {
log.Printf("[DEBUG] unlock cos file %s:%s", bucket, cosFile)
cosPath := fmt.Sprintf("%s:%s", bucket, cosFile)
lockTagValue := fmt.Sprintf("%x", md5.Sum([]byte(cosPath)))
var err error
for i := 0; i < 30; i++ {
err = c.DeleteTag(lockTagKey, lockTagValue)
if err == nil {
return nil
}
time.Sleep(1 * time.Second)
}
return err
}
// CreateTag create tag by key and value
func (c *remoteClient) CreateTag(key, value string) error {
request := tag.NewCreateTagRequest()
request.TagKey = &key
request.TagValue = &value
_, err := c.tagClient.CreateTag(request)
log.Printf("[DEBUG] create tag %s:%s: error: %v", key, value, err)
if err != nil {
return fmt.Errorf("failed to create tag: %s -> %s: %s", key, value, err)
}
return nil
}
// DeleteTag create tag by key and value
func (c *remoteClient) DeleteTag(key, value string) error {
request := tag.NewDeleteTagRequest()
request.TagKey = &key
request.TagValue = &value
_, err := c.tagClient.DeleteTag(request)
log.Printf("[DEBUG] delete tag %s:%s: error: %v", key, value, err)
if err != nil {
return fmt.Errorf("failed to delete tag: %s -> %s: %s", key, value, err)
}
return nil
}

3
go.mod
View File

@ -84,6 +84,7 @@ require (
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/keybase/go-crypto v0.0.0-20161004153544-93f5b35093ba // indirect
github.com/lib/pq v1.0.0
github.com/likexian/gokit v0.20.15
github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82
github.com/masterzen/winrm v0.0.0-20190223112901-5e5c9a7fe54b
github.com/mattn/go-colorable v0.1.1
@ -113,6 +114,8 @@ require (
github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a // indirect
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/spf13/afero v1.2.1
github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c
github.com/terraform-providers/terraform-provider-openstack v1.15.0
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 // indirect
github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5 // indirect

15
go.sum
View File

@ -41,6 +41,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022 h1:y8Gs8CzNfDF5AZvjr+5UyGQvQEBL7pwo+v+wX6q9JI8=
github.com/ChrisTrenkamp/goxpath v0.0.0-20170922090931-c385f95c6022/go.mod h1:nuWgzSkT5PnyOd+272uUmV0dnAnAn42Mk7PiQC5VzN4=
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
github.com/Unknwon/com v0.0.0-20151008135407-28b053d5a292 h1:tuQ7w+my8a8mkwN7x2TSd7OzTjkZ7rAeSyH4xncuAMI=
github.com/Unknwon/com v0.0.0-20151008135407-28b053d5a292/go.mod h1:KYCjqMOeHpNuTOiFQU6WEcTG7poCJrUs0YgyHNtn1no=
github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14=
@ -285,6 +286,14 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/likexian/gokit v0.0.0-20190309162924-0a377eecf7aa/go.mod h1:QdfYv6y6qPA9pbBA2qXtoT8BMKha6UyNbxWGWl/9Jfk=
github.com/likexian/gokit v0.0.0-20190418170008-ace88ad0983b/go.mod h1:KKqSnk/VVSW8kEyO2vVCXoanzEutKdlBAPohmGXkxCk=
github.com/likexian/gokit v0.0.0-20190501133040-e77ea8b19cdc/go.mod h1:3kvONayqCaj+UgrRZGpgfXzHdMYCAO0KAt4/8n0L57Y=
github.com/likexian/gokit v0.20.15 h1:DgtIqqTRFqtbiLJFzuRESwVrxWxfs8OlY6hnPYBa3BM=
github.com/likexian/gokit v0.20.15/go.mod h1:kn+nTv3tqh6yhor9BC4Lfiu58SmH8NmQ2PmEl+uM6nU=
github.com/likexian/simplejson-go v0.0.0-20190409170913-40473a74d76d/go.mod h1:Typ1BfnATYtZ/+/shXfFYLrovhFyuKvzwrdOnIDHlmg=
github.com/likexian/simplejson-go v0.0.0-20190419151922-c1f9f0b4f084/go.mod h1:U4O1vIJvIKwbMZKUJ62lppfdvkCdVd2nfMimHK81eec=
github.com/likexian/simplejson-go v0.0.0-20190502021454-d8787b4bfa0b/go.mod h1:3BWwtmKP9cXWwYCr5bkoVDEfLywacOv0s06OBEDpyt8=
github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82 h1:wnfcqULT+N2seWf6y4yHzmi7GD2kNx4Ute0qArktD48=
github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82/go.mod h1:y54tfGmO3NKssKveTEFFzH8C/akrSOy/iW9qEAUDV84=
github.com/masterzen/simplexml v0.0.0-20160608183007-4572e39b1ab9 h1:SmVbOZFWAlyQshuMfOkiAx1f5oUTsOGG5IXplAEYeeM=
@ -336,6 +345,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
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/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
@ -392,6 +403,10 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d h1:Z4EH+5EffvBEhh37F0C0DnpklTMh00JOkjW5zK3ofBI=
github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible h1:5Td2b0yfaOvw9M9nZ5Oav6Li9bxUNxt4DgxMfIPpsa0=
github.com/tencentcloud/tencentcloud-sdk-go v3.0.82+incompatible/go.mod h1:0PfYow01SHPMhKY31xa+EFz2RStxIqj6JFAJS+IkCi4=
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c h1:iRD1CqtWUjgEVEmjwTMbP1DMzz1HRytOsgx/rlw/vNs=
github.com/tencentyun/cos-go-sdk-v5 v0.0.0-20190808065407-f07404cefc8c/go.mod h1:wk2XFUg6egk4tSDNZtXeKfe2G6690UVyt163PuUxBZk=
github.com/terraform-providers/terraform-provider-openstack v1.15.0 h1:adpjqej+F8BAX9dHmuPF47sUIkgifeqBu6p7iCsyj0Y=
github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew=
github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6 h1:lYIiVDtZnyTWlNwiAxLj0bbpTcx1BWCFhXjfsvmPdNc=

205
vendor/github.com/likexian/gokit/LICENSE generated vendored Normal file
View File

@ -0,0 +1,205 @@
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 2012-2019 Li Kexian
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.
APPENDIX: Copyright 2012-2019 Li Kexian
https://www.likexian.com/

98
vendor/github.com/likexian/gokit/assert/README.md generated vendored Normal file
View File

@ -0,0 +1,98 @@
# GoKit - assert
Assert kits for Golang development.
## Installation
go get -u github.com/likexian/gokit
## Importing
import (
"github.com/likexian/gokit/assert"
)
## Documentation
Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/assert)
## Example
### assert panic
```go
func willItPanic() {
panic("failed")
}
assert.Panic(t, willItPanic)
```
### assert err is nil
```go
fp, err := os.Open("/data/dev/gokit/LICENSE")
assert.Nil(t, err)
```
### assert equal
```go
x := map[string]int{"a": 1, "b": 2}
y := map[string]int{"a": 1, "b": 2}
assert.Equal(t, x, y, "x shall equal to y")
```
### check string in array
```go
ok := assert.IsContains([]string{"a", "b", "c"}, "b")
if ok {
fmt.Println("value in array")
} else {
fmt.Println("value not in array")
}
```
### check string in interface array
```go
ok := assert.IsContains([]interface{}{0, "1", 2}, "1")
if ok {
fmt.Println("value in array")
} else {
fmt.Println("value not in array")
}
```
### check object in struct array
```go
ok := assert.IsContains([]A{A{0, 1}, A{1, 2}, A{1, 3}}, A{1, 2})
if ok {
fmt.Println("value in array")
} else {
fmt.Println("value not in array")
}
```
### a := c ? x : y
```go
a := 1
// b := a == 1 ? true : false
b := assert.If(a == 1, true, false)
```
## LICENSE
Copyright 2012-2019 Li Kexian
Licensed under the Apache License 2.0
## About
- [Li Kexian](https://www.likexian.com/)
## DONATE
- [Help me make perfect](https://www.likexian.com/donate/)

202
vendor/github.com/likexian/gokit/assert/assert.go generated vendored Normal file
View File

@ -0,0 +1,202 @@
/*
* Copyright 2012-2019 Li Kexian
*
* 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.
*
* A toolkit for Golang development
* https://www.likexian.com/
*/
package assert
import (
"fmt"
"reflect"
"runtime"
"testing"
)
// Version returns package version
func Version() string {
return "0.10.1"
}
// Author returns package author
func Author() string {
return "[Li Kexian](https://www.likexian.com/)"
}
// License returns package license
func License() string {
return "Licensed under the Apache License 2.0"
}
// Equal assert test value to be equal
func Equal(t *testing.T, got, exp interface{}, args ...interface{}) {
equal(t, got, exp, 1, args...)
}
// NotEqual assert test value to be not equal
func NotEqual(t *testing.T, got, exp interface{}, args ...interface{}) {
notEqual(t, got, exp, 1, args...)
}
// Nil assert test value to be nil
func Nil(t *testing.T, got interface{}, args ...interface{}) {
equal(t, got, nil, 1, args...)
}
// NotNil assert test value to be not nil
func NotNil(t *testing.T, got interface{}, args ...interface{}) {
notEqual(t, got, nil, 1, args...)
}
// True assert test value to be true
func True(t *testing.T, got interface{}, args ...interface{}) {
equal(t, got, true, 1, args...)
}
// False assert test value to be false
func False(t *testing.T, got interface{}, args ...interface{}) {
notEqual(t, got, true, 1, args...)
}
// Zero assert test value to be zero value
func Zero(t *testing.T, got interface{}, args ...interface{}) {
equal(t, IsZero(got), true, 1, args...)
}
// NotZero assert test value to be not zero value
func NotZero(t *testing.T, got interface{}, args ...interface{}) {
notEqual(t, IsZero(got), true, 1, args...)
}
// Len assert length of test vaue to be exp
func Len(t *testing.T, got interface{}, exp int, args ...interface{}) {
equal(t, Length(got), exp, 1, args...)
}
// NotLen assert length of test vaue to be not exp
func NotLen(t *testing.T, got interface{}, exp int, args ...interface{}) {
notEqual(t, Length(got), exp, 1, args...)
}
// Contains assert test value to be contains
func Contains(t *testing.T, got, exp interface{}, args ...interface{}) {
equal(t, IsContains(got, exp), true, 1, args...)
}
// NotContains assert test value to be contains
func NotContains(t *testing.T, got, exp interface{}, args ...interface{}) {
notEqual(t, IsContains(got, exp), true, 1, args...)
}
// Match assert test value match exp pattern
func Match(t *testing.T, got, exp interface{}, args ...interface{}) {
equal(t, IsMatch(got, exp), true, 1, args...)
}
// NotMatch assert test value not match exp pattern
func NotMatch(t *testing.T, got, exp interface{}, args ...interface{}) {
notEqual(t, IsMatch(got, exp), true, 1, args...)
}
// Lt assert test value less than exp
func Lt(t *testing.T, got, exp interface{}, args ...interface{}) {
equal(t, IsLt(got, exp), true, 1, args...)
}
// Le assert test value less than exp or equal
func Le(t *testing.T, got, exp interface{}, args ...interface{}) {
equal(t, IsLe(got, exp), true, 1, args...)
}
// Gt assert test value greater than exp
func Gt(t *testing.T, got, exp interface{}, args ...interface{}) {
equal(t, IsGt(got, exp), true, 1, args...)
}
// Ge assert test value greater than exp or equal
func Ge(t *testing.T, got, exp interface{}, args ...interface{}) {
equal(t, IsGe(got, exp), true, 1, args...)
}
// Panic assert testing to be panic
func Panic(t *testing.T, fn func(), args ...interface{}) {
defer func() {
ff := func() {
t.Error("! -", "assert expected to be panic")
if len(args) > 0 {
t.Error("! -", fmt.Sprint(args...))
}
}
ok := recover() != nil
assert(t, ok, ff, 2)
}()
fn()
}
// NotPanic assert testing to be panic
func NotPanic(t *testing.T, fn func(), args ...interface{}) {
defer func() {
ff := func() {
t.Error("! -", "assert expected to be not panic")
if len(args) > 0 {
t.Error("! -", fmt.Sprint(args...))
}
}
ok := recover() == nil
assert(t, ok, ff, 3)
}()
fn()
}
func equal(t *testing.T, got, exp interface{}, step int, args ...interface{}) {
fn := func() {
switch got.(type) {
case error:
t.Errorf("! unexpected error: \"%s\"", got)
default:
t.Errorf("! expected %#v, but got %#v", exp, got)
}
if len(args) > 0 {
t.Error("! -", fmt.Sprint(args...))
}
}
ok := reflect.DeepEqual(exp, got)
assert(t, ok, fn, step+1)
}
func notEqual(t *testing.T, got, exp interface{}, step int, args ...interface{}) {
fn := func() {
t.Errorf("! unexpected: %#v", got)
if len(args) > 0 {
t.Error("! -", fmt.Sprint(args...))
}
}
ok := !reflect.DeepEqual(exp, got)
assert(t, ok, fn, step+1)
}
func assert(t *testing.T, pass bool, fn func(), step int) {
if !pass {
_, file, line, ok := runtime.Caller(step + 1)
if ok {
t.Errorf("%s:%d", file, line)
}
fn()
t.FailNow()
}
}

334
vendor/github.com/likexian/gokit/assert/values.go generated vendored Normal file
View File

@ -0,0 +1,334 @@
/*
* Copyright 2012-2019 Li Kexian
*
* 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.
*
* A toolkit for Golang development
* https://www.likexian.com/
*/
package assert
import (
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
// ErrInvalid is value invalid for operation
var ErrInvalid = errors.New("value if invalid")
// ErrLess is expect to be greater error
var ErrLess = errors.New("left is less the right")
// ErrGreater is expect to be less error
var ErrGreater = errors.New("left is greater then right")
// CMP is compare operation
var CMP = struct {
LT string
LE string
GT string
GE string
}{
"<",
"<=",
">",
">=",
}
// IsZero returns value is zero value
func IsZero(v interface{}) bool {
vv := reflect.ValueOf(v)
switch vv.Kind() {
case reflect.Invalid:
return true
case reflect.Bool:
return !vv.Bool()
case reflect.Ptr, reflect.Interface:
return vv.IsNil()
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
return vv.Len() == 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return vv.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return vv.Uint() == 0
case reflect.Float32, reflect.Float64:
return vv.Float() == 0
default:
return false
}
}
// IsContains returns whether value is within array
func IsContains(array interface{}, value interface{}) bool {
vv := reflect.ValueOf(array)
if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface {
if vv.IsNil() {
return false
}
vv = vv.Elem()
}
switch vv.Kind() {
case reflect.Invalid:
return false
case reflect.Slice:
for i := 0; i < vv.Len(); i++ {
if reflect.DeepEqual(value, vv.Index(i).Interface()) {
return true
}
}
return false
case reflect.Map:
s := vv.MapKeys()
for i := 0; i < len(s); i++ {
if reflect.DeepEqual(value, s[i].Interface()) {
return true
}
}
return false
case reflect.String:
ss := reflect.ValueOf(value)
switch ss.Kind() {
case reflect.String:
return strings.Contains(vv.String(), ss.String())
}
return false
default:
return reflect.DeepEqual(array, value)
}
}
// IsMatch returns if value v contains any match of pattern r
// IsMatch(regexp.MustCompile("v\d+"), "v100")
// IsMatch("v\d+", "v100")
// IsMatch("\d+\.\d+", 100.1)
func IsMatch(r interface{}, v interface{}) bool {
var re *regexp.Regexp
if v, ok := r.(*regexp.Regexp); ok {
re = v
} else {
re = regexp.MustCompile(fmt.Sprint(r))
}
return re.MatchString(fmt.Sprint(v))
}
// Length returns length of value
func Length(v interface{}) int {
vv := reflect.ValueOf(v)
if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface {
if vv.IsNil() {
return 0
}
vv = vv.Elem()
}
switch vv.Kind() {
case reflect.Invalid:
return 0
case reflect.Ptr, reflect.Interface:
return 0
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
return vv.Len()
default:
return len(fmt.Sprintf("%#v", v))
}
}
// IsLt returns if x less than y, value invalid will returns false
func IsLt(x, y interface{}) bool {
return Compare(x, y, CMP.LT) == nil
}
// IsLe returns if x less than or equal to y, value invalid will returns false
func IsLe(x, y interface{}) bool {
return Compare(x, y, CMP.LE) == nil
}
// IsGt returns if x greater than y, value invalid will returns false
func IsGt(x, y interface{}) bool {
return Compare(x, y, CMP.GT) == nil
}
// IsGe returns if x greater than or equal to y, value invalid will returns false
func IsGe(x, y interface{}) bool {
return Compare(x, y, CMP.GE) == nil
}
// Compare compare x and y, by operation
// It returns nil for true, ErrInvalid for invalid operation, err for false
// Compare(1, 2, ">") // number compare -> true
// Compare("a", "a", ">=") // string compare -> true
// Compare([]string{"a", "b"}, []string{"a"}, "<") // slice len compare -> false
func Compare(x, y interface{}, op string) error {
if !IsContains([]string{CMP.LT, CMP.LE, CMP.GT, CMP.GE}, op) {
return ErrInvalid
}
vv := reflect.ValueOf(x)
if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface {
if vv.IsNil() {
return ErrInvalid
}
vv = vv.Elem()
}
var c float64
switch vv.Kind() {
case reflect.Invalid:
return ErrInvalid
case reflect.String:
yy := reflect.ValueOf(y)
switch yy.Kind() {
case reflect.String:
c = float64(strings.Compare(vv.String(), yy.String()))
default:
return ErrInvalid
}
case reflect.Slice, reflect.Map, reflect.Array:
yy := reflect.ValueOf(y)
switch yy.Kind() {
case reflect.Slice, reflect.Map, reflect.Array:
c = float64(vv.Len() - yy.Len())
default:
return ErrInvalid
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
yy, err := ToInt64(y)
if err != nil {
return ErrInvalid
}
c = float64(vv.Int() - yy)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
yy, err := ToUint64(y)
if err != nil {
return ErrInvalid
}
c = float64(vv.Uint()) - float64(yy)
case reflect.Float32, reflect.Float64:
yy, err := ToFloat64(y)
if err != nil {
return ErrInvalid
}
c = float64(vv.Float() - yy)
default:
return ErrInvalid
}
switch {
case c < 0:
switch op {
case CMP.LT, CMP.LE:
return nil
default:
return ErrLess
}
case c > 0:
switch op {
case CMP.GT, CMP.GE:
return nil
default:
return ErrGreater
}
default:
switch op {
case CMP.LT:
return ErrGreater
case CMP.GT:
return ErrLess
default:
return nil
}
}
}
// ToInt64 returns int value for int or uint or float
func ToInt64(v interface{}) (int64, error) {
vv := reflect.ValueOf(v)
switch vv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return int64(vv.Int()), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return int64(vv.Uint()), nil
case reflect.Float32, reflect.Float64:
return int64(vv.Float()), nil
case reflect.String:
r, err := strconv.ParseInt(vv.String(), 10, 64)
if err != nil {
return 0, ErrInvalid
}
return r, nil
default:
return 0, ErrInvalid
}
}
// ToUint64 returns uint value for int or uint or float
func ToUint64(v interface{}) (uint64, error) {
vv := reflect.ValueOf(v)
switch vv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return uint64(vv.Int()), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return uint64(vv.Uint()), nil
case reflect.Float32, reflect.Float64:
return uint64(vv.Float()), nil
case reflect.String:
r, err := strconv.ParseUint(vv.String(), 10, 64)
if err != nil {
return 0, ErrInvalid
}
return r, nil
default:
return 0, ErrInvalid
}
}
// ToFloat64 returns float64 value for int or uint or float
func ToFloat64(v interface{}) (float64, error) {
vv := reflect.ValueOf(v)
switch vv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return float64(vv.Int()), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return float64(vv.Uint()), nil
case reflect.Float32, reflect.Float64:
return float64(vv.Float()), nil
case reflect.String:
r, err := strconv.ParseFloat(vv.String(), 64)
if err != nil {
return 0, ErrInvalid
}
return r, nil
default:
return 0, ErrInvalid
}
}
// If returns x if c is true, else y
// z = If(c, x, y)
// equal to:
// z = c ? x : y
func If(c bool, x, y interface{}) interface{} {
if c {
return x
}
return y
}

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@ -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 (c) 2017-2018 Tencent Ltd.
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,261 @@
package common
import (
"encoding/hex"
"encoding/json"
"fmt"
"log"
"net/http"
"net/http/httputil"
"strconv"
"strings"
"time"
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
)
type Client struct {
region string
httpClient *http.Client
httpProfile *profile.HttpProfile
profile *profile.ClientProfile
credential *Credential
signMethod string
unsignedPayload bool
debug bool
}
func (c *Client) Send(request tchttp.Request, response tchttp.Response) (err error) {
if request.GetDomain() == "" {
domain := c.httpProfile.Endpoint
if domain == "" {
domain = tchttp.GetServiceDomain(request.GetService())
}
request.SetDomain(domain)
}
if request.GetHttpMethod() == "" {
request.SetHttpMethod(c.httpProfile.ReqMethod)
}
tchttp.CompleteCommonParams(request, c.GetRegion())
if c.signMethod == "HmacSHA1" || c.signMethod == "HmacSHA256" {
return c.sendWithSignatureV1(request, response)
} else {
return c.sendWithSignatureV3(request, response)
}
}
func (c *Client) sendWithSignatureV1(request tchttp.Request, response tchttp.Response) (err error) {
// TODO: not an elegant way, it should be done in common params, but finally it need to refactor
request.GetParams()["Language"] = c.profile.Language
err = tchttp.ConstructParams(request)
if err != nil {
return err
}
err = signRequest(request, c.credential, c.signMethod)
if err != nil {
return err
}
httpRequest, err := http.NewRequest(request.GetHttpMethod(), request.GetUrl(), request.GetBodyReader())
if err != nil {
return err
}
if request.GetHttpMethod() == "POST" {
httpRequest.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
}
if c.debug {
outbytes, err := httputil.DumpRequest(httpRequest, true)
if err != nil {
log.Printf("[ERROR] dump request failed because %s", err)
return err
}
log.Printf("[DEBUG] http request = %s", outbytes)
}
httpResponse, err := c.httpClient.Do(httpRequest)
if err != nil {
return err
}
err = tchttp.ParseFromHttpResponse(httpResponse, response)
return err
}
func (c *Client) sendWithSignatureV3(request tchttp.Request, response tchttp.Response) (err error) {
headers := map[string]string{
"Host": request.GetDomain(),
"X-TC-Action": request.GetAction(),
"X-TC-Version": request.GetVersion(),
"X-TC-Timestamp": request.GetParams()["Timestamp"],
"X-TC-RequestClient": request.GetParams()["RequestClient"],
"X-TC-Language": c.profile.Language,
}
if c.region != "" {
headers["X-TC-Region"] = c.region
}
if c.credential.Token != "" {
headers["X-TC-Token"] = c.credential.Token
}
if request.GetHttpMethod() == "GET" {
headers["Content-Type"] = "application/x-www-form-urlencoded"
} else {
headers["Content-Type"] = "application/json"
}
// start signature v3 process
// build canonical request string
httpRequestMethod := request.GetHttpMethod()
canonicalURI := "/"
canonicalQueryString := ""
if httpRequestMethod == "GET" {
err = tchttp.ConstructParams(request)
if err != nil {
return err
}
params := make(map[string]string)
for key, value := range request.GetParams() {
params[key] = value
}
delete(params, "Action")
delete(params, "Version")
delete(params, "Nonce")
delete(params, "Region")
delete(params, "RequestClient")
delete(params, "Timestamp")
canonicalQueryString = tchttp.GetUrlQueriesEncoded(params)
}
canonicalHeaders := fmt.Sprintf("content-type:%s\nhost:%s\n", headers["Content-Type"], headers["Host"])
signedHeaders := "content-type;host"
requestPayload := ""
if httpRequestMethod == "POST" {
b, err := json.Marshal(request)
if err != nil {
return err
}
requestPayload = string(b)
}
hashedRequestPayload := ""
if c.unsignedPayload {
hashedRequestPayload = sha256hex("UNSIGNED-PAYLOAD")
headers["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD"
} else {
hashedRequestPayload = sha256hex(requestPayload)
}
canonicalRequest := fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s",
httpRequestMethod,
canonicalURI,
canonicalQueryString,
canonicalHeaders,
signedHeaders,
hashedRequestPayload)
//log.Println("canonicalRequest:", canonicalRequest)
// build string to sign
algorithm := "TC3-HMAC-SHA256"
requestTimestamp := headers["X-TC-Timestamp"]
timestamp, _ := strconv.ParseInt(requestTimestamp, 10, 64)
t := time.Unix(timestamp, 0).UTC()
// must be the format 2006-01-02, ref to package time for more info
date := t.Format("2006-01-02")
credentialScope := fmt.Sprintf("%s/%s/tc3_request", date, request.GetService())
hashedCanonicalRequest := sha256hex(canonicalRequest)
string2sign := fmt.Sprintf("%s\n%s\n%s\n%s",
algorithm,
requestTimestamp,
credentialScope,
hashedCanonicalRequest)
//log.Println("string2sign", string2sign)
// sign string
secretDate := hmacsha256(date, "TC3"+c.credential.SecretKey)
secretService := hmacsha256(request.GetService(), secretDate)
secretKey := hmacsha256("tc3_request", secretService)
signature := hex.EncodeToString([]byte(hmacsha256(string2sign, secretKey)))
//log.Println("signature", signature)
// build authorization
authorization := fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s",
algorithm,
c.credential.SecretId,
credentialScope,
signedHeaders,
signature)
//log.Println("authorization", authorization)
headers["Authorization"] = authorization
url := "https://" + request.GetDomain() + request.GetPath()
if canonicalQueryString != "" {
url = url + "?" + canonicalQueryString
}
httpRequest, err := http.NewRequest(httpRequestMethod, url, strings.NewReader(requestPayload))
if err != nil {
return err
}
for k, v := range headers {
httpRequest.Header[k] = []string{v}
}
if c.debug {
outbytes, err := httputil.DumpRequest(httpRequest, true)
if err != nil {
log.Printf("[ERROR] dump request failed because %s", err)
return err
}
log.Printf("[DEBUG] http request = %s", outbytes)
}
httpResponse, err := c.httpClient.Do(httpRequest)
if err != nil {
return err
}
err = tchttp.ParseFromHttpResponse(httpResponse, response)
return err
}
func (c *Client) GetRegion() string {
return c.region
}
func (c *Client) Init(region string) *Client {
c.httpClient = &http.Client{}
c.region = region
c.signMethod = "TC3-HMAC-SHA256"
c.debug = false
log.SetFlags(log.LstdFlags | log.Lshortfile)
return c
}
func (c *Client) WithSecretId(secretId, secretKey string) *Client {
c.credential = NewCredential(secretId, secretKey)
return c
}
func (c *Client) WithCredential(cred *Credential) *Client {
c.credential = cred
return c
}
func (c *Client) WithProfile(clientProfile *profile.ClientProfile) *Client {
c.profile = clientProfile
c.signMethod = clientProfile.SignMethod
c.unsignedPayload = clientProfile.UnsignedPayload
c.httpProfile = clientProfile.HttpProfile
c.httpClient.Timeout = time.Duration(c.httpProfile.ReqTimeout) * time.Second
return c
}
func (c *Client) WithSignatureMethod(method string) *Client {
c.signMethod = method
return c
}
func (c *Client) WithHttpTransport(transport http.RoundTripper) *Client {
c.httpClient.Transport = transport
return c
}
func NewClientWithSecretId(secretId, secretKey, region string) (client *Client, err error) {
client = &Client{}
client.Init(region).WithSecretId(secretId, secretKey)
return
}

View File

@ -0,0 +1,58 @@
package common
type Credential struct {
SecretId string
SecretKey string
Token string
}
func NewCredential(secretId, secretKey string) *Credential {
return &Credential{
SecretId: secretId,
SecretKey: secretKey,
}
}
func NewTokenCredential(secretId, secretKey, token string) *Credential {
return &Credential{
SecretId: secretId,
SecretKey: secretKey,
Token: token,
}
}
func (c *Credential) GetCredentialParams() map[string]string {
p := map[string]string{
"SecretId": c.SecretId,
}
if c.Token != "" {
p["Token"] = c.Token
}
return p
}
// Nowhere use them and we haven't well designed these structures and
// underlying method, which leads to the situation that it is hard to
// refactor it to interfaces.
// Hence they are removed and merged into Credential.
//type TokenCredential struct {
// SecretId string
// SecretKey string
// Token string
//}
//func NewTokenCredential(secretId, secretKey, token string) *TokenCredential {
// return &TokenCredential{
// SecretId: secretId,
// SecretKey: secretKey,
// Token: token,
// }
//}
//func (c *TokenCredential) GetCredentialParams() map[string]string {
// return map[string]string{
// "SecretId": c.SecretId,
// "Token": c.Token,
// }
//}

View File

@ -0,0 +1,35 @@
package errors
import (
"fmt"
)
type TencentCloudSDKError struct {
Code string
Message string
RequestId string
}
func (e *TencentCloudSDKError) Error() string {
return fmt.Sprintf("[TencentCloudSDKError] Code=%s, Message=%s, RequestId=%s", e.Code, e.Message, e.RequestId)
}
func NewTencentCloudSDKError(code, message, requestId string) error {
return &TencentCloudSDKError{
Code: code,
Message: message,
RequestId: requestId,
}
}
func (e *TencentCloudSDKError) GetCode() string {
return e.Code
}
func (e *TencentCloudSDKError) GetMessage() string {
return e.Message
}
func (e *TencentCloudSDKError) GetRequestId() string {
return e.RequestId
}

View File

@ -0,0 +1,233 @@
package common
import (
"io"
//"log"
"math/rand"
"net/url"
"reflect"
"strconv"
"strings"
"time"
)
const (
POST = "POST"
GET = "GET"
RootDomain = "tencentcloudapi.com"
Path = "/"
)
type Request interface {
GetAction() string
GetBodyReader() io.Reader
GetDomain() string
GetHttpMethod() string
GetParams() map[string]string
GetPath() string
GetService() string
GetUrl() string
GetVersion() string
SetDomain(string)
SetHttpMethod(string)
}
type BaseRequest struct {
httpMethod string
domain string
path string
params map[string]string
formParams map[string]string
service string
version string
action string
}
func (r *BaseRequest) GetAction() string {
return r.action
}
func (r *BaseRequest) GetHttpMethod() string {
return r.httpMethod
}
func (r *BaseRequest) GetParams() map[string]string {
return r.params
}
func (r *BaseRequest) GetPath() string {
return r.path
}
func (r *BaseRequest) GetDomain() string {
return r.domain
}
func (r *BaseRequest) SetDomain(domain string) {
r.domain = domain
}
func (r *BaseRequest) SetHttpMethod(method string) {
switch strings.ToUpper(method) {
case POST:
{
r.httpMethod = POST
}
case GET:
{
r.httpMethod = GET
}
default:
{
r.httpMethod = GET
}
}
}
func (r *BaseRequest) GetService() string {
return r.service
}
func (r *BaseRequest) GetUrl() string {
if r.httpMethod == GET {
return "https://" + r.domain + r.path + "?" + GetUrlQueriesEncoded(r.params)
} else if r.httpMethod == POST {
return "https://" + r.domain + r.path
} else {
return ""
}
}
func (r *BaseRequest) GetVersion() string {
return r.version
}
func GetUrlQueriesEncoded(params map[string]string) string {
values := url.Values{}
for key, value := range params {
if value != "" {
values.Add(key, value)
}
}
return values.Encode()
}
func (r *BaseRequest) GetBodyReader() io.Reader {
if r.httpMethod == POST {
s := GetUrlQueriesEncoded(r.params)
return strings.NewReader(s)
} else {
return strings.NewReader("")
}
}
func (r *BaseRequest) Init() *BaseRequest {
r.domain = ""
r.path = Path
r.params = make(map[string]string)
r.formParams = make(map[string]string)
return r
}
func (r *BaseRequest) WithApiInfo(service, version, action string) *BaseRequest {
r.service = service
r.version = version
r.action = action
return r
}
func GetServiceDomain(service string) (domain string) {
domain = service + "." + RootDomain
return
}
func CompleteCommonParams(request Request, region string) {
params := request.GetParams()
params["Region"] = region
if request.GetVersion() != "" {
params["Version"] = request.GetVersion()
}
params["Action"] = request.GetAction()
params["Timestamp"] = strconv.FormatInt(time.Now().Unix(), 10)
params["Nonce"] = strconv.Itoa(rand.Int())
params["RequestClient"] = "SDK_GO_3.0.82"
}
func ConstructParams(req Request) (err error) {
value := reflect.ValueOf(req).Elem()
err = flatStructure(value, req, "")
//log.Printf("[DEBUG] params=%s", req.GetParams())
return
}
func flatStructure(value reflect.Value, request Request, prefix string) (err error) {
//log.Printf("[DEBUG] reflect value: %v", value.Type())
valueType := value.Type()
for i := 0; i < valueType.NumField(); i++ {
tag := valueType.Field(i).Tag
nameTag, hasNameTag := tag.Lookup("name")
if !hasNameTag {
continue
}
field := value.Field(i)
kind := field.Kind()
if kind == reflect.Ptr && field.IsNil() {
continue
}
if kind == reflect.Ptr {
field = field.Elem()
kind = field.Kind()
}
key := prefix + nameTag
if kind == reflect.String {
s := field.String()
if s != "" {
request.GetParams()[key] = s
}
} else if kind == reflect.Bool {
request.GetParams()[key] = strconv.FormatBool(field.Bool())
} else if kind == reflect.Int || kind == reflect.Int64 {
request.GetParams()[key] = strconv.FormatInt(field.Int(), 10)
} else if kind == reflect.Uint || kind == reflect.Uint64 {
request.GetParams()[key] = strconv.FormatUint(field.Uint(), 10)
} else if kind == reflect.Float64 {
request.GetParams()[key] = strconv.FormatFloat(field.Float(), 'f', -1, 64)
} else if kind == reflect.Slice {
list := value.Field(i)
for j := 0; j < list.Len(); j++ {
vj := list.Index(j)
key := prefix + nameTag + "." + strconv.Itoa(j)
kind = vj.Kind()
if kind == reflect.Ptr && vj.IsNil() {
continue
}
if kind == reflect.Ptr {
vj = vj.Elem()
kind = vj.Kind()
}
if kind == reflect.String {
request.GetParams()[key] = vj.String()
} else if kind == reflect.Bool {
request.GetParams()[key] = strconv.FormatBool(vj.Bool())
} else if kind == reflect.Int || kind == reflect.Int64 {
request.GetParams()[key] = strconv.FormatInt(vj.Int(), 10)
} else if kind == reflect.Uint || kind == reflect.Uint64 {
request.GetParams()[key] = strconv.FormatUint(vj.Uint(), 10)
} else if kind == reflect.Float64 {
request.GetParams()[key] = strconv.FormatFloat(vj.Float(), 'f', -1, 64)
} else {
if err = flatStructure(vj, request, key+"."); err != nil {
return
}
}
}
} else {
if err = flatStructure(reflect.ValueOf(field.Interface()), request, prefix+nameTag+"."); err != nil {
return
}
}
}
return
}

View File

@ -0,0 +1,81 @@
package common
import (
"encoding/json"
"fmt"
"io/ioutil"
//"log"
"net/http"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
)
type Response interface {
ParseErrorFromHTTPResponse(body []byte) error
}
type BaseResponse struct {
}
type ErrorResponse struct {
Response struct {
Error struct {
Code string `json:"Code"`
Message string `json:"Message"`
} `json:"Error" omitempty`
RequestId string `json:"RequestId"`
} `json:"Response"`
}
type DeprecatedAPIErrorResponse struct {
Code int `json:"code"`
Message string `json:"message"`
CodeDesc string `json:"codeDesc"`
}
func (r *BaseResponse) ParseErrorFromHTTPResponse(body []byte) (err error) {
resp := &ErrorResponse{}
err = json.Unmarshal(body, resp)
if err != nil {
msg := fmt.Sprintf("Fail to parse json content: %s, because: %s", body, err)
return errors.NewTencentCloudSDKError("ClientError.ParseJsonError", msg, "")
}
if resp.Response.Error.Code != "" {
return errors.NewTencentCloudSDKError(resp.Response.Error.Code, resp.Response.Error.Message, resp.Response.RequestId)
}
deprecated := &DeprecatedAPIErrorResponse{}
err = json.Unmarshal(body, deprecated)
if err != nil {
msg := fmt.Sprintf("Fail to parse json content: %s, because: %s", body, err)
return errors.NewTencentCloudSDKError("ClientError.ParseJsonError", msg, "")
}
if deprecated.Code != 0 {
return errors.NewTencentCloudSDKError(deprecated.CodeDesc, deprecated.Message, "")
}
return nil
}
func ParseFromHttpResponse(hr *http.Response, response Response) (err error) {
defer hr.Body.Close()
body, err := ioutil.ReadAll(hr.Body)
if err != nil {
msg := fmt.Sprintf("Fail to read response body because %s", err)
return errors.NewTencentCloudSDKError("ClientError.IOError", msg, "")
}
if hr.StatusCode != 200 {
msg := fmt.Sprintf("Request fail with http status code: %s, with body: %s", hr.Status, body)
return errors.NewTencentCloudSDKError("ClientError.HttpStatusCodeError", msg, "")
}
//log.Printf("[DEBUG] Response Body=%s", body)
err = response.ParseErrorFromHTTPResponse(body)
if err != nil {
return
}
err = json.Unmarshal(body, &response)
if err != nil {
msg := fmt.Sprintf("Fail to parse json content: %s, because: %s", body, err)
return errors.NewTencentCloudSDKError("ClientError.ParseJsonError", msg, "")
}
return
}

View File

@ -0,0 +1,21 @@
package profile
type ClientProfile struct {
HttpProfile *HttpProfile
// Valid choices: HmacSHA1, HmacSHA256, TC3-HMAC-SHA256.
// Default value is TC3-HMAC-SHA256.
SignMethod string
UnsignedPayload bool
// Valid choices: zh-CN, en-US.
// Default value is zh-CN.
Language string
}
func NewClientProfile() *ClientProfile {
return &ClientProfile{
HttpProfile: NewHttpProfile(),
SignMethod: "TC3-HMAC-SHA256",
UnsignedPayload: false,
Language: "zh-CN",
}
}

View File

@ -0,0 +1,17 @@
package profile
type HttpProfile struct {
ReqMethod string
ReqTimeout int
Endpoint string
Protocol string
}
func NewHttpProfile() *HttpProfile {
return &HttpProfile{
ReqMethod: "POST",
ReqTimeout: 60,
Endpoint: "",
Protocol: "HTTPS",
}
}

View File

@ -0,0 +1,94 @@
package common
import (
"bytes"
"crypto/hmac"
"crypto/sha1"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"sort"
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
)
const (
SHA256 = "HmacSHA256"
SHA1 = "HmacSHA1"
)
func Sign(s, secretKey, method string) string {
hashed := hmac.New(sha1.New, []byte(secretKey))
if method == SHA256 {
hashed = hmac.New(sha256.New, []byte(secretKey))
}
hashed.Write([]byte(s))
return base64.StdEncoding.EncodeToString(hashed.Sum(nil))
}
func sha256hex(s string) string {
b := sha256.Sum256([]byte(s))
return hex.EncodeToString(b[:])
}
func hmacsha256(s, key string) string {
hashed := hmac.New(sha256.New, []byte(key))
hashed.Write([]byte(s))
return string(hashed.Sum(nil))
}
func signRequest(request tchttp.Request, credential *Credential, method string) (err error) {
if method != SHA256 {
method = SHA1
}
checkAuthParams(request, credential, method)
s := getStringToSign(request)
signature := Sign(s, credential.SecretKey, method)
request.GetParams()["Signature"] = signature
return
}
func checkAuthParams(request tchttp.Request, credential *Credential, method string) {
params := request.GetParams()
credentialParams := credential.GetCredentialParams()
for key, value := range credentialParams {
params[key] = value
}
params["SignatureMethod"] = method
delete(params, "Signature")
}
func getStringToSign(request tchttp.Request) string {
method := request.GetHttpMethod()
domain := request.GetDomain()
path := request.GetPath()
var buf bytes.Buffer
buf.WriteString(method)
buf.WriteString(domain)
buf.WriteString(path)
buf.WriteString("?")
params := request.GetParams()
// sort params
keys := make([]string, 0, len(params))
for k, _ := range params {
keys = append(keys, k)
}
sort.Strings(keys)
for i := range keys {
k := keys[i]
// TODO: check if server side allows empty value in url.
if params[k] == "" {
continue
}
buf.WriteString(k)
buf.WriteString("=")
buf.WriteString(params[k])
buf.WriteString("&")
}
buf.Truncate(buf.Len() - 1)
return buf.String()
}

View File

@ -0,0 +1,47 @@
package common
func IntPtr(v int) *int {
return &v
}
func Int64Ptr(v int64) *int64 {
return &v
}
func UintPtr(v uint) *uint {
return &v
}
func Uint64Ptr(v uint64) *uint64 {
return &v
}
func Float64Ptr(v float64) *float64 {
return &v
}
func StringPtr(v string) *string {
return &v
}
func StringValues(ptrs []*string) []string {
values := make([]string, len(ptrs))
for i := 0; i < len(ptrs); i++ {
if ptrs[i] != nil {
values[i] = *ptrs[i]
}
}
return values
}
func StringPtrs(vals []string) []*string {
ptrs := make([]*string, len(vals))
for i := 0; i < len(vals); i++ {
ptrs[i] = &vals[i]
}
return ptrs
}
func BoolPtr(v bool) *bool {
return &v
}

View File

@ -0,0 +1,294 @@
// Copyright (c) 2017-2018 THL A29 Limited, a Tencent company. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v20180813
import (
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
)
const APIVersion = "2018-08-13"
type Client struct {
common.Client
}
// Deprecated
func NewClientWithSecretId(secretId, secretKey, region string) (client *Client, err error) {
cpf := profile.NewClientProfile()
client = &Client{}
client.Init(region).WithSecretId(secretId, secretKey).WithProfile(cpf)
return
}
func NewClient(credential *common.Credential, region string, clientProfile *profile.ClientProfile) (client *Client, err error) {
client = &Client{}
client.Init(region).
WithCredential(credential).
WithProfile(clientProfile)
return
}
func NewAddResourceTagRequest() (request *AddResourceTagRequest) {
request = &AddResourceTagRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "AddResourceTag")
return
}
func NewAddResourceTagResponse() (response *AddResourceTagResponse) {
response = &AddResourceTagResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 本接口用于给标签关联资源
func (c *Client) AddResourceTag(request *AddResourceTagRequest) (response *AddResourceTagResponse, err error) {
if request == nil {
request = NewAddResourceTagRequest()
}
response = NewAddResourceTagResponse()
err = c.Send(request, response)
return
}
func NewCreateTagRequest() (request *CreateTagRequest) {
request = &CreateTagRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "CreateTag")
return
}
func NewCreateTagResponse() (response *CreateTagResponse) {
response = &CreateTagResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 本接口用于创建一对标签键和标签值
func (c *Client) CreateTag(request *CreateTagRequest) (response *CreateTagResponse, err error) {
if request == nil {
request = NewCreateTagRequest()
}
response = NewCreateTagResponse()
err = c.Send(request, response)
return
}
func NewDeleteResourceTagRequest() (request *DeleteResourceTagRequest) {
request = &DeleteResourceTagRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "DeleteResourceTag")
return
}
func NewDeleteResourceTagResponse() (response *DeleteResourceTagResponse) {
response = &DeleteResourceTagResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 本接口用于解除标签和资源的关联关系
func (c *Client) DeleteResourceTag(request *DeleteResourceTagRequest) (response *DeleteResourceTagResponse, err error) {
if request == nil {
request = NewDeleteResourceTagRequest()
}
response = NewDeleteResourceTagResponse()
err = c.Send(request, response)
return
}
func NewDeleteTagRequest() (request *DeleteTagRequest) {
request = &DeleteTagRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "DeleteTag")
return
}
func NewDeleteTagResponse() (response *DeleteTagResponse) {
response = &DeleteTagResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 本接口用于删除一对标签键和标签值
func (c *Client) DeleteTag(request *DeleteTagRequest) (response *DeleteTagResponse, err error) {
if request == nil {
request = NewDeleteTagRequest()
}
response = NewDeleteTagResponse()
err = c.Send(request, response)
return
}
func NewDescribeResourceTagsByResourceIdsRequest() (request *DescribeResourceTagsByResourceIdsRequest) {
request = &DescribeResourceTagsByResourceIdsRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "DescribeResourceTagsByResourceIds")
return
}
func NewDescribeResourceTagsByResourceIdsResponse() (response *DescribeResourceTagsByResourceIdsResponse) {
response = &DescribeResourceTagsByResourceIdsResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 用于查询已有资源标签键值对
func (c *Client) DescribeResourceTagsByResourceIds(request *DescribeResourceTagsByResourceIdsRequest) (response *DescribeResourceTagsByResourceIdsResponse, err error) {
if request == nil {
request = NewDescribeResourceTagsByResourceIdsRequest()
}
response = NewDescribeResourceTagsByResourceIdsResponse()
err = c.Send(request, response)
return
}
func NewDescribeTagKeysRequest() (request *DescribeTagKeysRequest) {
request = &DescribeTagKeysRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "DescribeTagKeys")
return
}
func NewDescribeTagKeysResponse() (response *DescribeTagKeysResponse) {
response = &DescribeTagKeysResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 用于查询已建立的标签列表中的标签键。
func (c *Client) DescribeTagKeys(request *DescribeTagKeysRequest) (response *DescribeTagKeysResponse, err error) {
if request == nil {
request = NewDescribeTagKeysRequest()
}
response = NewDescribeTagKeysResponse()
err = c.Send(request, response)
return
}
func NewDescribeTagValuesRequest() (request *DescribeTagValuesRequest) {
request = &DescribeTagValuesRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "DescribeTagValues")
return
}
func NewDescribeTagValuesResponse() (response *DescribeTagValuesResponse) {
response = &DescribeTagValuesResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 用于查询已建立的标签列表中的标签值。
func (c *Client) DescribeTagValues(request *DescribeTagValuesRequest) (response *DescribeTagValuesResponse, err error) {
if request == nil {
request = NewDescribeTagValuesRequest()
}
response = NewDescribeTagValuesResponse()
err = c.Send(request, response)
return
}
func NewDescribeTagsRequest() (request *DescribeTagsRequest) {
request = &DescribeTagsRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "DescribeTags")
return
}
func NewDescribeTagsResponse() (response *DescribeTagsResponse) {
response = &DescribeTagsResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 用于查询已建立的标签列表。
func (c *Client) DescribeTags(request *DescribeTagsRequest) (response *DescribeTagsResponse, err error) {
if request == nil {
request = NewDescribeTagsRequest()
}
response = NewDescribeTagsResponse()
err = c.Send(request, response)
return
}
func NewModifyResourceTagsRequest() (request *ModifyResourceTagsRequest) {
request = &ModifyResourceTagsRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "ModifyResourceTags")
return
}
func NewModifyResourceTagsResponse() (response *ModifyResourceTagsResponse) {
response = &ModifyResourceTagsResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 本接口用于修改资源关联的所有标签
func (c *Client) ModifyResourceTags(request *ModifyResourceTagsRequest) (response *ModifyResourceTagsResponse, err error) {
if request == nil {
request = NewModifyResourceTagsRequest()
}
response = NewModifyResourceTagsResponse()
err = c.Send(request, response)
return
}
func NewUpdateResourceTagValueRequest() (request *UpdateResourceTagValueRequest) {
request = &UpdateResourceTagValueRequest{
BaseRequest: &tchttp.BaseRequest{},
}
request.Init().WithApiInfo("tag", APIVersion, "UpdateResourceTagValue")
return
}
func NewUpdateResourceTagValueResponse() (response *UpdateResourceTagValueResponse) {
response = &UpdateResourceTagValueResponse{
BaseResponse: &tchttp.BaseResponse{},
}
return
}
// 本接口用于修改资源已关联的标签值(标签键不变)
func (c *Client) UpdateResourceTagValue(request *UpdateResourceTagValueRequest) (response *UpdateResourceTagValueResponse, err error) {
if request == nil {
request = NewUpdateResourceTagValueRequest()
}
response = NewUpdateResourceTagValueResponse()
err = c.Send(request, response)
return
}

View File

@ -0,0 +1,523 @@
// Copyright (c) 2017-2018 THL A29 Limited, a Tencent company. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v20180813
import (
"encoding/json"
tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
)
type AddResourceTagRequest struct {
*tchttp.BaseRequest
// 标签键
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
// 标签值
TagValue *string `json:"TagValue,omitempty" name:"TagValue"`
// 资源六段式描述
Resource *string `json:"Resource,omitempty" name:"Resource"`
}
func (r *AddResourceTagRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *AddResourceTagRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type AddResourceTagResponse struct {
*tchttp.BaseResponse
Response *struct {
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *AddResourceTagResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *AddResourceTagResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type CreateTagRequest struct {
*tchttp.BaseRequest
// 标签键
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
// 标签值
TagValue *string `json:"TagValue,omitempty" name:"TagValue"`
}
func (r *CreateTagRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *CreateTagRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type CreateTagResponse struct {
*tchttp.BaseResponse
Response *struct {
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *CreateTagResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *CreateTagResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DeleteResourceTagRequest struct {
*tchttp.BaseRequest
// 标签键
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
// 资源六段式描述
Resource *string `json:"Resource,omitempty" name:"Resource"`
}
func (r *DeleteResourceTagRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DeleteResourceTagRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DeleteResourceTagResponse struct {
*tchttp.BaseResponse
Response *struct {
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *DeleteResourceTagResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DeleteResourceTagResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DeleteTagRequest struct {
*tchttp.BaseRequest
// 需要删除的标签键
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
// 需要删除的标签值
TagValue *string `json:"TagValue,omitempty" name:"TagValue"`
}
func (r *DeleteTagRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DeleteTagRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DeleteTagResponse struct {
*tchttp.BaseResponse
Response *struct {
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *DeleteTagResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DeleteTagResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DescribeResourceTagsByResourceIdsRequest struct {
*tchttp.BaseRequest
// 业务类型
ServiceType *string `json:"ServiceType,omitempty" name:"ServiceType"`
// 资源前缀
ResourcePrefix *string `json:"ResourcePrefix,omitempty" name:"ResourcePrefix"`
// 资源唯一标记
ResourceIds []*string `json:"ResourceIds,omitempty" name:"ResourceIds" list`
// 资源所在地域
ResourceRegion *string `json:"ResourceRegion,omitempty" name:"ResourceRegion"`
// 数据偏移量,默认为 0, 必须为Limit参数的整数倍
Offset *uint64 `json:"Offset,omitempty" name:"Offset"`
// 每页大小,默认为 15
Limit *uint64 `json:"Limit,omitempty" name:"Limit"`
}
func (r *DescribeResourceTagsByResourceIdsRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DescribeResourceTagsByResourceIdsRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DescribeResourceTagsByResourceIdsResponse struct {
*tchttp.BaseResponse
Response *struct {
// 结果总数
TotalCount *uint64 `json:"TotalCount,omitempty" name:"TotalCount"`
// 数据位移偏量
Offset *uint64 `json:"Offset,omitempty" name:"Offset"`
// 每页大小
Limit *uint64 `json:"Limit,omitempty" name:"Limit"`
// 标签列表
Tags []*TagResource `json:"Tags,omitempty" name:"Tags" list`
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *DescribeResourceTagsByResourceIdsResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DescribeResourceTagsByResourceIdsResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DescribeTagKeysRequest struct {
*tchttp.BaseRequest
// 创建者用户 Uin不传或为空只将 Uin 作为条件查询
CreateUin *uint64 `json:"CreateUin,omitempty" name:"CreateUin"`
// 数据偏移量,默认为 0, 必须为Limit参数的整数倍
Offset *uint64 `json:"Offset,omitempty" name:"Offset"`
// 每页大小,默认为 15
Limit *uint64 `json:"Limit,omitempty" name:"Limit"`
}
func (r *DescribeTagKeysRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DescribeTagKeysRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DescribeTagKeysResponse struct {
*tchttp.BaseResponse
Response *struct {
// 结果总数
TotalCount *uint64 `json:"TotalCount,omitempty" name:"TotalCount"`
// 数据位移偏量
Offset *uint64 `json:"Offset,omitempty" name:"Offset"`
// 每页大小
Limit *uint64 `json:"Limit,omitempty" name:"Limit"`
// 标签列表
Tags []*string `json:"Tags,omitempty" name:"Tags" list`
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *DescribeTagKeysResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DescribeTagKeysResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DescribeTagValuesRequest struct {
*tchttp.BaseRequest
// 标签键列表
TagKeys []*string `json:"TagKeys,omitempty" name:"TagKeys" list`
// 创建者用户 Uin不传或为空只将 Uin 作为条件查询
CreateUin *uint64 `json:"CreateUin,omitempty" name:"CreateUin"`
// 数据偏移量,默认为 0, 必须为Limit参数的整数倍
Offset *uint64 `json:"Offset,omitempty" name:"Offset"`
// 每页大小,默认为 15
Limit *uint64 `json:"Limit,omitempty" name:"Limit"`
}
func (r *DescribeTagValuesRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DescribeTagValuesRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DescribeTagValuesResponse struct {
*tchttp.BaseResponse
Response *struct {
// 结果总数
TotalCount *uint64 `json:"TotalCount,omitempty" name:"TotalCount"`
// 数据位移偏量
Offset *uint64 `json:"Offset,omitempty" name:"Offset"`
// 每页大小
Limit *uint64 `json:"Limit,omitempty" name:"Limit"`
// 标签列表
Tags []*Tag `json:"Tags,omitempty" name:"Tags" list`
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *DescribeTagValuesResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DescribeTagValuesResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DescribeTagsRequest struct {
*tchttp.BaseRequest
// 标签键,与标签值同时存在或同时不存在,不存在时表示查询该用户所有标签
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
// 标签值,与标签键同时存在或同时不存在,不存在时表示查询该用户所有标签
TagValue *string `json:"TagValue,omitempty" name:"TagValue"`
// 数据偏移量,默认为 0, 必须为Limit参数的整数倍
Offset *uint64 `json:"Offset,omitempty" name:"Offset"`
// 每页大小,默认为 15
Limit *uint64 `json:"Limit,omitempty" name:"Limit"`
// 创建者用户 Uin不传或为空只将 Uin 作为条件查询
CreateUin *uint64 `json:"CreateUin,omitempty" name:"CreateUin"`
}
func (r *DescribeTagsRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DescribeTagsRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type DescribeTagsResponse struct {
*tchttp.BaseResponse
Response *struct {
// 结果总数
TotalCount *uint64 `json:"TotalCount,omitempty" name:"TotalCount"`
// 数据位移偏量
Offset *uint64 `json:"Offset,omitempty" name:"Offset"`
// 每页大小
Limit *uint64 `json:"Limit,omitempty" name:"Limit"`
// 标签列表
Tags []*TagWithDelete `json:"Tags,omitempty" name:"Tags" list`
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *DescribeTagsResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *DescribeTagsResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type ModifyResourceTagsRequest struct {
*tchttp.BaseRequest
// 资源的六段式描述
Resource *string `json:"Resource,omitempty" name:"Resource"`
// 需要增加或修改的标签集合。如果Resource描述的资源未关联输入的标签键则增加关联若已关联则将该资源关联的键对应的标签值修改为输入值。本接口中ReplaceTags和DeleteTags二者必须存在其一且二者不能包含相同的标签键
ReplaceTags []*Tag `json:"ReplaceTags,omitempty" name:"ReplaceTags" list`
// 需要解关联的标签集合。本接口中ReplaceTags和DeleteTags二者必须存在其一且二者不能包含相同的标签键
DeleteTags []*TagKeyObject `json:"DeleteTags,omitempty" name:"DeleteTags" list`
}
func (r *ModifyResourceTagsRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *ModifyResourceTagsRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type ModifyResourceTagsResponse struct {
*tchttp.BaseResponse
Response *struct {
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *ModifyResourceTagsResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *ModifyResourceTagsResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type Tag struct {
// 标签键
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
// 标签值
TagValue *string `json:"TagValue,omitempty" name:"TagValue"`
}
type TagKeyObject struct {
// 标签键
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
}
type TagResource struct {
// 标签键
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
// 标签值
TagValue *string `json:"TagValue,omitempty" name:"TagValue"`
// 资源ID
ResourceId *string `json:"ResourceId,omitempty" name:"ResourceId"`
// 标签键MD5值
TagKeyMd5 *string `json:"TagKeyMd5,omitempty" name:"TagKeyMd5"`
// 标签值MD5值
TagValueMd5 *string `json:"TagValueMd5,omitempty" name:"TagValueMd5"`
}
type TagWithDelete struct {
// 标签键
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
// 标签值
TagValue *string `json:"TagValue,omitempty" name:"TagValue"`
// 是否可以删除
CanDelete *uint64 `json:"CanDelete,omitempty" name:"CanDelete"`
}
type UpdateResourceTagValueRequest struct {
*tchttp.BaseRequest
// 资源关联的标签键
TagKey *string `json:"TagKey,omitempty" name:"TagKey"`
// 修改后的标签值
TagValue *string `json:"TagValue,omitempty" name:"TagValue"`
// 资源的六段式描述
Resource *string `json:"Resource,omitempty" name:"Resource"`
}
func (r *UpdateResourceTagValueRequest) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *UpdateResourceTagValueRequest) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}
type UpdateResourceTagValueResponse struct {
*tchttp.BaseResponse
Response *struct {
// 唯一请求 ID每次请求都会返回。定位问题时需要提供该次请求的 RequestId。
RequestId *string `json:"RequestId,omitempty" name:"RequestId"`
} `json:"Response"`
}
func (r *UpdateResourceTagValueResponse) ToJsonString() string {
b, _ := json.Marshal(r)
return string(b)
}
func (r *UpdateResourceTagValueResponse) FromJsonString(s string) error {
return json.Unmarshal([]byte(s), &r)
}

View File

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

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

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

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

@ -0,0 +1,10 @@
module github.com/tencentyun/cos-go-sdk-v5
go 1.12
require (
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409
github.com/google/go-querystring v1.0.0
github.com/mozillazg/go-httpheader v0.2.1
github.com/stretchr/testify v1.3.0
)

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

@ -0,0 +1,13 @@
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409 h1:DTQ/38ao/CfXsrK0cSAL+h4R/u0VVvfWLZEOlLwEROI=
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ=
github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=

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

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

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

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

View File

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

View File

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

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

@ -0,0 +1,35 @@
package cos
import (
"context"
"encoding/xml"
"net/http"
)
// Service 相关 API
type ServiceService service
// ServiceGetResult is the result of Get Service
type ServiceGetResult struct {
XMLName xml.Name `xml:"ListAllMyBucketsResult"`
Owner *Owner `xml:"Owner"`
Buckets []Bucket `xml:"Buckets>Bucket,omitempty"`
}
// Get Service 接口实现获取该用户下所有Bucket列表。
//
// 该API接口需要使用Authorization签名认证
// 且只能获取签名中AccessID所属账户的Bucket列表。
//
// https://www.qcloud.com/document/product/436/8291
func (s *ServiceService) Get(ctx context.Context) (*ServiceGetResult, *Response, error) {
var res ServiceGetResult
sendOpt := sendOptions{
baseURL: s.client.BaseURL.ServiceURL,
uri: "/",
method: http.MethodGet,
result: &res,
}
resp, err := s.client.send(ctx, &sendOpt)
return &res, resp, err
}

View File

@ -0,0 +1,61 @@
---
layout: "backend-types"
page_title: "Backend Type: cos"
sidebar_current: "docs-backends-types-standard-cos"
description: |-
Terraform can store the state remotely, making it easier to version and work with in a team.
---
# COS
**Kind: Standard (with locking)**
Stores the state as an object in a configurable prefix in a given bucket on [Tencent Cloud Object Storage](https://intl.cloud.tencent.com/product/cos) (COS).
This backend also supports [state locking](/docs/state/locking.html).
~> **Warning!** It is highly recommended that you enable [Object Versioning](https://intl.cloud.tencent.com/document/product/436/19883)
on the COS bucket to allow for state recovery in the case of accidental deletions and human error.
## Example Configuration
```hcl
terraform {
backend "cos" {
region = "ap-guangzhou"
bucket = "bucket-for-terraform-state-1258798060"
prefix = "terraform/state"
}
}
```
This assumes we have a [COS Bucket](https://www.terraform.io/docs/providers/tencentcloud/r/cos_bucket.html) created named `bucket-for-terraform-state-1258798060`,
Terraform state will be written into the file `terraform/state/terraform.tfstate`.
## Using the COS remote state
To make use of the COS remote state we can use the [`terraform_remote_state` data source](/docs/providers/terraform/d/remote_state.html).
```hcl
data "terraform_remote_state" "foo" {
backend = "cos"
config = {
region = "ap-guangzhou"
bucket = "bucket-for-terraform-state-1258798060"
prefix = "terraform/state"
}
}
```
## Configuration variables
The following configuration options or environment variables are supported:
* `secret_id` - (Optional) Secret id of Tencent Cloud. It supports environment variables `TENCENTCLOUD_SECRET_ID`.
* `secret_key` - (Optional) Secret key of Tencent Cloud. It supports environment variables `TENCENTCLOUD_SECRET_KEY`.
* `region` - (Optional) The region of the COS bucket. It supports environment variables `TENCENTCLOUD_REGION`.
* `bucket` - (Required) The name of the COS bucket. You shall manually create it first.
* `prefix` - (Optional) The directory for saving the state file in bucket. Default to "env:".
* `key` - (Optional) The path for saving the state file in bucket. Defaults to `terraform.tfstate`.
* `encrypt` - (Optional) Whether to enable server side encryption of the state file. If it is true, COS will use 'AES256' encryption algorithm to encrypt state file.
* `acl` - (Optional) Object ACL to be applied to the state file, allows `private` and `public-read`. Defaults to `private`.

View File

@ -27,6 +27,7 @@ Multiple workspaces are currently supported by the following backends:
* [AzureRM](/docs/backends/types/azurerm.html)
* [Consul](/docs/backends/types/consul.html)
* [COS](/docs/backends/types/cos.html)
* [GCS](/docs/backends/types/gcs.html)
* [Local](/docs/backends/types/local.html)
* [Manta](/docs/backends/types/manta.html)

View File

@ -36,6 +36,9 @@
<li<%= sidebar_current("docs-backends-types-standard-consul") %>>
<a href="/docs/backends/types/consul.html">consul</a>
</li>
<li<%= sidebar_current("docs-backends-types-standard-cos") %>>
<a href="/docs/backends/types/cos.html">cos</a>
</li>
<li<%= sidebar_current("docs-backends-types-standard-etcdv2") %>>
<a href="/docs/backends/types/etcd.html">etcd</a>
</li>