mirror of
https://github.com/goharbor/harbor.git
synced 2024-09-28 05:18:01 +02:00
Tecent TCR Provider
1. Docker image registry. 2. Helm chart registry. Signed-off-by: 疯魔慕薇 <kfanjian@gmail.com> Signed-off-by: fanjiankong <fanjiankong@tencent.com>
This commit is contained in:
parent
ec2f251d63
commit
9f8a743da9
@ -66,6 +66,7 @@ require (
|
||||
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect
|
||||
github.com/spf13/viper v1.4.0 // indirect
|
||||
github.com/stretchr/testify v1.5.1
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.62
|
||||
github.com/theupdateframework/notary v0.6.1
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||
golang.org/x/net v0.0.0-20200625001655-4c5254603344
|
||||
|
@ -766,6 +766,8 @@ github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.62 h1:Vnr3IqaafEuQUciG6D6EaeLJm26Mg8sjAfbI4OoeauM=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go v1.0.62/go.mod h1:asUz5BPXxgoPGaRgZaVm1iGcUAuHyYUo1nXqKa83cvI=
|
||||
github.com/theupdateframework/notary v0.6.1 h1:7wshjstgS9x9F5LuB1L5mBI2xNMObWqjz+cjWoom6l0=
|
||||
github.com/theupdateframework/notary v0.6.1/go.mod h1:MOfgIfmox8s7/7fduvB2xyPPMJCrjRLRizA8OFwpnKY=
|
||||
github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
|
@ -54,6 +54,8 @@ import (
|
||||
_ "github.com/goharbor/harbor/src/replication/adapter/dtr"
|
||||
// register the Artifact Hub adapter
|
||||
_ "github.com/goharbor/harbor/src/replication/adapter/artifacthub"
|
||||
// register the TencentCloud TCR adapter
|
||||
_ "github.com/goharbor/harbor/src/replication/adapter/tencentcr"
|
||||
)
|
||||
|
||||
// Replication implements the job interface
|
||||
|
233
src/replication/adapter/tencentcr/adapter.go
Normal file
233
src/replication/adapter/tencentcr/adapter.go
Normal file
@ -0,0 +1,233 @@
|
||||
package tencentcr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/distribution/registry/client/auth/challenge"
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/goharbor/harbor/src/pkg/registry/auth/bearer"
|
||||
adp "github.com/goharbor/harbor/src/replication/adapter"
|
||||
"github.com/goharbor/harbor/src/replication/adapter/native"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
"github.com/goharbor/harbor/src/replication/util"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions"
|
||||
tcr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalidTcrEndpoint error = errors.New("[tencent-tcr.newAdapter] Invalid TCR instance endpoint")
|
||||
errPingTcrEndpointFailed error = errors.New("[tencent-tcr.newAdapter] Ping TCR instance endpoint failed")
|
||||
)
|
||||
|
||||
func init() {
|
||||
if err := adp.RegisterFactory(model.RegistryTypeTencentTcr, new(factory)); err != nil {
|
||||
log.Errorf("failed to register factory for %s: %v", model.RegistryTypeTencentTcr, err)
|
||||
return
|
||||
}
|
||||
log.Infof("the factory for adapter %s registered", model.RegistryTypeTencentTcr)
|
||||
}
|
||||
|
||||
type factory struct{}
|
||||
|
||||
/**
|
||||
* Implement Factory Interface
|
||||
**/
|
||||
var _ adp.Factory = &factory{}
|
||||
|
||||
// Create ...
|
||||
func (f *factory) Create(r *model.Registry) (adp.Adapter, error) {
|
||||
return newAdapter(r)
|
||||
}
|
||||
|
||||
// AdapterPattern ...
|
||||
func (f *factory) AdapterPattern() *model.AdapterPattern {
|
||||
return getAdapterInfo()
|
||||
}
|
||||
|
||||
func getAdapterInfo() *model.AdapterPattern {
|
||||
return &model.AdapterPattern{}
|
||||
}
|
||||
|
||||
type adapter struct {
|
||||
*native.Adapter
|
||||
registryID *string
|
||||
regionName *string
|
||||
tcrClient *tcr.Client
|
||||
pageSize *int64
|
||||
client *commonhttp.Client
|
||||
registry *model.Registry
|
||||
}
|
||||
|
||||
/**
|
||||
* Implement Adapter Interface
|
||||
**/
|
||||
var _ adp.Adapter = &adapter{}
|
||||
|
||||
func newAdapter(registry *model.Registry) (a *adapter, err error) {
|
||||
if !isSecretID(registry.Credential.AccessKey) {
|
||||
err = errors.New("[tencent-tcr.newAdapter] Please use SecretId/SecretKey, NOT docker login Username/Password")
|
||||
log.Debugf("[tencent-tcr.newAdapter] error=%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Query TCR instance info via endpoint.
|
||||
var registryURL *url.URL
|
||||
registryURL, _ = url.Parse(registry.URL)
|
||||
|
||||
if strings.Index(registryURL.Host, ".tencentcloudcr.com") < 0 {
|
||||
log.Errorf("[tencent-tcr.newAdapter] errInvalidTcrEndpoint=%v", err)
|
||||
return nil, errInvalidTcrEndpoint
|
||||
}
|
||||
|
||||
realm, service, err := ping(registry)
|
||||
log.Debugf("[tencent-tcr.newAdapter] realm=%s, service=%s error=%v", realm, service, err)
|
||||
if err != nil {
|
||||
log.Errorf("[tencent-tcr.newAdapter] ping failed. error=%v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Create TCR API client
|
||||
var tcrCredential = common.NewCredential(registry.Credential.AccessKey, registry.Credential.AccessSecret)
|
||||
var cfp = profile.NewClientProfile()
|
||||
var client *tcr.Client
|
||||
// temp client used to get TCR instance info
|
||||
client, err = tcr.NewClient(tcrCredential, regions.Guangzhou, cfp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var req = tcr.NewDescribeInstancesRequest()
|
||||
req.AllRegion = common.BoolPtr(true)
|
||||
req.Filters = []*tcr.Filter{
|
||||
{
|
||||
Name: common.StringPtr("RegistryName"),
|
||||
Values: []*string{common.StringPtr(strings.ReplaceAll(registryURL.Host, ".tencentcloudcr.com", ""))},
|
||||
},
|
||||
}
|
||||
var resp = tcr.NewDescribeInstancesResponse()
|
||||
resp, err = client.DescribeInstances(req)
|
||||
if err != nil {
|
||||
log.Errorf("DescribeInstances error=%s", err.Error())
|
||||
return
|
||||
}
|
||||
if *resp.Response.TotalCount == 0 {
|
||||
err = fmt.Errorf("[tencent-tcr.newAdapter] Can not get TCR instance info. RequestId=%s", *resp.Response.RequestId)
|
||||
return
|
||||
}
|
||||
var instanceInfo = resp.Response.Registries[0]
|
||||
log.Debugf("[tencent-tcr.InstanceInfo] registry.URL=%s, host=%s, PublicDomain=%s, RegionName=%s, RegistryId=%s",
|
||||
registry.URL, registryURL.Host, *instanceInfo.PublicDomain, *instanceInfo.RegionName, *instanceInfo.RegistryId)
|
||||
|
||||
// rebuild TCR SDK client
|
||||
client, err = tcr.NewClient(tcrCredential, *instanceInfo.RegionName, cfp)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var credential = NewAuth(instanceInfo.RegistryId, client)
|
||||
var transport = util.GetHTTPTransport(registry.Insecure)
|
||||
var authorizer = bearer.NewAuthorizer(realm, service, credential, transport)
|
||||
|
||||
return &adapter{
|
||||
registry: registry,
|
||||
registryID: instanceInfo.RegistryId,
|
||||
regionName: instanceInfo.RegionName,
|
||||
tcrClient: client,
|
||||
pageSize: common.Int64Ptr(20),
|
||||
client: commonhttp.NewClient(
|
||||
&http.Client{
|
||||
Transport: transport,
|
||||
},
|
||||
credential,
|
||||
),
|
||||
Adapter: native.NewAdapterWithAuthorizer(registry, authorizer),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ping(registry *model.Registry) (string, string, error) {
|
||||
client := &http.Client{
|
||||
Transport: util.GetHTTPTransport(registry.Insecure),
|
||||
}
|
||||
|
||||
resp, err := client.Get(registry.URL + "/v2/")
|
||||
log.Debugf("[tencent-tcr.ping] error=%v", err)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
challenges := challenge.ResponseChallenges(resp)
|
||||
for _, challenge := range challenges {
|
||||
if challenge.Scheme == "bearer" {
|
||||
return challenge.Parameters["realm"], challenge.Parameters["service"], nil
|
||||
}
|
||||
}
|
||||
return "", "", fmt.Errorf("[tencent-tcr.ping] bearer auth scheme isn't supported: %v", challenges)
|
||||
}
|
||||
|
||||
func (a *adapter) Info() (info *model.RegistryInfo, err error) {
|
||||
info = &model.RegistryInfo{
|
||||
Type: model.RegistryTypeTencentTcr,
|
||||
SupportedResourceTypes: []model.ResourceType{
|
||||
model.ResourceTypeImage,
|
||||
model.ResourceTypeChart,
|
||||
},
|
||||
SupportedResourceFilters: []*model.FilterStyle{
|
||||
{
|
||||
Type: model.FilterTypeName,
|
||||
Style: model.FilterStyleTypeText,
|
||||
},
|
||||
{
|
||||
Type: model.FilterTypeTag,
|
||||
Style: model.FilterStyleTypeText,
|
||||
},
|
||||
},
|
||||
SupportedTriggers: []model.TriggerType{
|
||||
model.TriggerTypeManual,
|
||||
model.TriggerTypeScheduled,
|
||||
},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) PrepareForPush(resources []*model.Resource) (err error) {
|
||||
log.Debugf("[tencent-tcr.PrepareForPush]")
|
||||
for _, resource := range resources {
|
||||
if resource == nil {
|
||||
return errors.New("the resource cannot be null")
|
||||
}
|
||||
if resource.Metadata == nil {
|
||||
return errors.New("[tencent-tcr.PrepareForPush] the metadata of resource cannot be null")
|
||||
}
|
||||
if resource.Metadata.Repository == nil {
|
||||
return errors.New("[tencent-tcr.PrepareForPush] the namespace of resource cannot be null")
|
||||
}
|
||||
if len(resource.Metadata.Repository.Name) == 0 {
|
||||
return errors.New("[tencent-tcr.PrepareForPush] the name of the namespace cannot be null")
|
||||
}
|
||||
var paths = strings.Split(resource.Metadata.Repository.Name, "/")
|
||||
var namespace = paths[0]
|
||||
var repository = path.Join(paths[1:]...)
|
||||
|
||||
log.Debugf("[tencent-tcr.PrepareForPush.createPrivateNamespace] namespace=%s", namespace)
|
||||
err = a.createPrivateNamespace(namespace)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("[tencent-tcr.PrepareForPush.createRepository] namespace=%s, repository=%s", namespace, repository)
|
||||
err = a.createRepository(namespace, repository)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
303
src/replication/adapter/tencentcr/adapter_test.go
Normal file
303
src/replication/adapter/tencentcr/adapter_test.go
Normal file
@ -0,0 +1,303 @@
|
||||
package tencentcr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils/test"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
adp "github.com/goharbor/harbor/src/replication/adapter"
|
||||
"github.com/goharbor/harbor/src/replication/adapter/native"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||
tcr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924"
|
||||
)
|
||||
|
||||
var (
|
||||
mockAccessKey = "AKIDxxxx"
|
||||
mockAccessSecret = "xxxxx"
|
||||
tcrClient *tcr.Client
|
||||
)
|
||||
|
||||
func setup() {
|
||||
if ak := os.Getenv("TENCENT_AK"); ak != "" {
|
||||
log.Info("USE AK from ENV")
|
||||
mockAccessKey = ak
|
||||
}
|
||||
if sk := os.Getenv("TENCENT_SK"); sk != "" {
|
||||
log.Info("USE SK from ENV")
|
||||
mockAccessSecret = sk
|
||||
}
|
||||
// var tcrCredential = common.NewCredential(mockAccessKey, mockAccessSecret)
|
||||
// var cfp = profile.NewClientProfile()
|
||||
|
||||
// tcrClient, _ = tcr.NewClient(tcrCredential, regions.Guangzhou, cfp)
|
||||
}
|
||||
|
||||
func teardown() {}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setup()
|
||||
code := m.Run()
|
||||
teardown()
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func TestAdapter_NewAdapter(t *testing.T) {
|
||||
factory, err := adp.GetFactory("BadName")
|
||||
assert.Nil(t, factory)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
factory, err = adp.GetFactory(model.RegistryTypeTencentTcr)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, factory)
|
||||
}
|
||||
|
||||
func TestAdapter_NewAdapter_NilAKSK(t *testing.T) {
|
||||
// Nil AK/SK
|
||||
adapter, err := newAdapter(&model.Registry{
|
||||
Type: model.RegistryTypeTencentTcr,
|
||||
Credential: &model.Credential{},
|
||||
})
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, adapter)
|
||||
}
|
||||
|
||||
func TestAdapter_NewAdapter_InvalidEndpoint(t *testing.T) {
|
||||
// Invaild endpoint
|
||||
adapter, err := newAdapter(&model.Registry{
|
||||
Type: model.RegistryTypeTencentTcr,
|
||||
Credential: &model.Credential{
|
||||
AccessKey: mockAccessKey,
|
||||
AccessSecret: mockAccessSecret,
|
||||
},
|
||||
URL: "$$$",
|
||||
})
|
||||
assert.NotNil(t, err)
|
||||
assert.EqualError(t, err, errInvalidTcrEndpoint.Error())
|
||||
assert.Nil(t, adapter)
|
||||
}
|
||||
|
||||
func TestAdapter_NewAdapter_Pingfailed(t *testing.T) {
|
||||
// Invaild endpoint
|
||||
adapter, err := newAdapter(&model.Registry{
|
||||
Type: model.RegistryTypeTencentTcr,
|
||||
Credential: &model.Credential{
|
||||
AccessKey: mockAccessKey,
|
||||
AccessSecret: mockAccessSecret,
|
||||
},
|
||||
URL: "https://.tencentcloudcr.com",
|
||||
})
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, adapter)
|
||||
}
|
||||
|
||||
func TestAdapter_NewAdapter_InvalidAKSK(t *testing.T) {
|
||||
// Error AK/SK
|
||||
adapter, err := newAdapter(&model.Registry{
|
||||
Type: model.RegistryTypeTencentTcr,
|
||||
Credential: &model.Credential{
|
||||
AccessKey: "mockAccessKey",
|
||||
AccessSecret: "mockAccessSecret",
|
||||
},
|
||||
})
|
||||
assert.NotNil(t, err)
|
||||
assert.Nil(t, adapter)
|
||||
}
|
||||
|
||||
func TestAdapter_NewAdapter_Ok(t *testing.T) {
|
||||
adapter, err := newAdapter(&model.Registry{
|
||||
Type: model.RegistryTypeTencentTcr,
|
||||
Credential: &model.Credential{
|
||||
AccessKey: mockAccessKey,
|
||||
AccessSecret: mockAccessSecret,
|
||||
},
|
||||
URL: "https://harbor-community.tencentcloudcr.com",
|
||||
})
|
||||
if sdkerr, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
log.Infof("sdk error, error=%v", sdkerr)
|
||||
return
|
||||
}
|
||||
assert.NotNil(t, adapter)
|
||||
assert.Nil(t, err)
|
||||
|
||||
}
|
||||
|
||||
func TestAdapter_NewAdapter_InsecureOk(t *testing.T) {
|
||||
adapter, err := newAdapter(&model.Registry{
|
||||
Type: model.RegistryTypeTencentTcr,
|
||||
Credential: &model.Credential{
|
||||
AccessKey: mockAccessKey,
|
||||
AccessSecret: mockAccessSecret,
|
||||
},
|
||||
Insecure: true,
|
||||
URL: "https://harbor-community.tencentcloudcr.com",
|
||||
})
|
||||
if sdkerr, ok := err.(*errors.TencentCloudSDKError); ok {
|
||||
log.Infof("sdk error, error=%v", sdkerr)
|
||||
return
|
||||
}
|
||||
assert.NotNil(t, adapter)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func getMockAdapter(t *testing.T, hasCred, health bool) (*adapter, *httptest.Server) {
|
||||
server := test.NewServer(
|
||||
&test.RequestHandlerMapping{
|
||||
Method: http.MethodGet,
|
||||
Pattern: "/v2/_catalog",
|
||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`
|
||||
{
|
||||
"repositories": [
|
||||
"test1"
|
||||
]
|
||||
}`))
|
||||
},
|
||||
},
|
||||
&test.RequestHandlerMapping{
|
||||
Method: http.MethodGet,
|
||||
Pattern: "/v2/{repo}/tags/list",
|
||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`
|
||||
{
|
||||
"name": "test1",
|
||||
"tags": [
|
||||
"latest"
|
||||
]
|
||||
}`))
|
||||
},
|
||||
},
|
||||
&test.RequestHandlerMapping{
|
||||
Method: http.MethodGet,
|
||||
Pattern: "/v2/",
|
||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println(r.Method, r.URL)
|
||||
if health {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
}
|
||||
},
|
||||
},
|
||||
&test.RequestHandlerMapping{
|
||||
Method: http.MethodGet,
|
||||
Pattern: "/",
|
||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println(r.Method, r.URL)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
},
|
||||
&test.RequestHandlerMapping{
|
||||
Method: http.MethodPost,
|
||||
Pattern: "/",
|
||||
Handler: func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println(r.Method, r.URL)
|
||||
if buf, e := ioutil.ReadAll(&io.LimitedReader{R: r.Body, N: 80}); e == nil {
|
||||
fmt.Println("\t", string(buf))
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
registry := &model.Registry{
|
||||
Type: model.RegistryTypeAwsEcr,
|
||||
URL: server.URL,
|
||||
}
|
||||
if hasCred {
|
||||
registry.Credential = &model.Credential{
|
||||
AccessKey: "AKIDxxxx",
|
||||
AccessSecret: "abcdefg",
|
||||
}
|
||||
}
|
||||
return &adapter{
|
||||
registry: registry,
|
||||
Adapter: native.NewAdapter(registry),
|
||||
}, server
|
||||
}
|
||||
|
||||
func TestAdapter_Info(t *testing.T) {
|
||||
tcrAdapter, _ := getMockAdapter(t, true, true)
|
||||
info, err := tcrAdapter.Info()
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, info)
|
||||
}
|
||||
|
||||
func TestAdapter_PrepareForPush(t *testing.T) {
|
||||
a, s := getMockAdapter(t, true, true)
|
||||
defer s.Close()
|
||||
resources := []*model.Resource{
|
||||
{
|
||||
Type: model.ResourceTypeImage,
|
||||
Metadata: &model.ResourceMetadata{
|
||||
Repository: &model.Repository{
|
||||
Name: "busybox",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := a.PrepareForPush(resources)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestAdapter_PrepareForPush_NilResource(t *testing.T) {
|
||||
a, s := getMockAdapter(t, true, true)
|
||||
defer s.Close()
|
||||
var resources = []*model.Resource{nil}
|
||||
|
||||
err := a.PrepareForPush(resources)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestAdapter_PrepareForPush_NilMeata(t *testing.T) {
|
||||
a, s := getMockAdapter(t, true, true)
|
||||
defer s.Close()
|
||||
resources := []*model.Resource{
|
||||
{
|
||||
Type: model.ResourceTypeImage,
|
||||
},
|
||||
}
|
||||
|
||||
err := a.PrepareForPush(resources)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestAdapter_PrepareForPush_NilRepository(t *testing.T) {
|
||||
a, s := getMockAdapter(t, true, true)
|
||||
defer s.Close()
|
||||
resources := []*model.Resource{
|
||||
{
|
||||
Type: model.ResourceTypeImage,
|
||||
Metadata: &model.ResourceMetadata{},
|
||||
},
|
||||
}
|
||||
|
||||
err := a.PrepareForPush(resources)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestAdapter_PrepareForPush_NilRepositoryName(t *testing.T) {
|
||||
a, s := getMockAdapter(t, true, true)
|
||||
defer s.Close()
|
||||
resources := []*model.Resource{
|
||||
{
|
||||
Type: model.ResourceTypeImage,
|
||||
Metadata: &model.ResourceMetadata{
|
||||
Repository: &model.Repository{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := a.PrepareForPush(resources)
|
||||
assert.NotNil(t, err)
|
||||
}
|
181
src/replication/adapter/tencentcr/artifact_registry.go
Normal file
181
src/replication/adapter/tencentcr/artifact_registry.go
Normal file
@ -0,0 +1,181 @@
|
||||
package tencentcr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/utils"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
adp "github.com/goharbor/harbor/src/replication/adapter"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
"github.com/goharbor/harbor/src/replication/util"
|
||||
tcr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924"
|
||||
)
|
||||
|
||||
const (
|
||||
tcrQPSLimit = 15
|
||||
)
|
||||
|
||||
/**
|
||||
* Implement ArtifactRegistry Interface
|
||||
**/
|
||||
var _ adp.ArtifactRegistry = &adapter{}
|
||||
|
||||
func filterToPatterns(filters []*model.Filter) (namespacePattern, repoPattern, tagsPattern string) {
|
||||
for _, filter := range filters {
|
||||
if filter.Type == model.FilterTypeName {
|
||||
repoPattern = filter.Value.(string)
|
||||
}
|
||||
if filter.Type == model.FilterTypeTag {
|
||||
tagsPattern = filter.Value.(string)
|
||||
}
|
||||
}
|
||||
namespacePattern = strings.Split(repoPattern, "/")[0]
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) FetchArtifacts(filters []*model.Filter) (resources []*model.Resource, err error) {
|
||||
// get filter pattern
|
||||
var namespacePattern, repoPattern, tagsPattern = filterToPatterns(filters)
|
||||
log.Debugf("[tencent-tcr.FetchArtifacts] namespacePattern=%s repoPattern=%s tagsPattern=%s", namespacePattern, repoPattern, tagsPattern)
|
||||
|
||||
// 1. list namespaces
|
||||
var namespaces []string
|
||||
namespaces, err = a.listCandidateNamespaces(namespacePattern)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("[tencent-tcr.FetchArtifacts] namespaces=%v", namespaces)
|
||||
|
||||
// 2. list repos
|
||||
var filteredRepos []tcr.TcrRepositoryInfo
|
||||
for _, ns := range namespaces {
|
||||
var repos []tcr.TcrRepositoryInfo
|
||||
repos, err = a.listReposByNamespace(ns)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
log.Debugf("[tencent-tcr.FetchArtifacts] namespace=%s, repositories=%d", ns, len(repos))
|
||||
|
||||
if _, ok := util.IsSpecificPathComponent(repoPattern); ok {
|
||||
log.Debugf("[tencent-tcr.FetchArtifacts] specific_repos=%s", repoPattern)
|
||||
// TODO: Check repo is exist.
|
||||
filteredRepos = append(filteredRepos, repos...)
|
||||
} else {
|
||||
// 3. filter repos
|
||||
for _, repo := range repos {
|
||||
var ok bool
|
||||
ok, err = util.Match(repoPattern, *repo.Name)
|
||||
log.Debugf("[tencent-tcr.FetchArtifacts] namespace=%s, repository=%s, repoPattern=%s, Match=%v", *repo.Namespace, *repo.Name, repoPattern, ok)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if ok {
|
||||
filteredRepos = append(filteredRepos, repo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debugf("[tencent-tcr.FetchArtifacts] filteredRepos=%d", len(filteredRepos))
|
||||
|
||||
// 4. list images
|
||||
var rawResources = make([]*model.Resource, len(filteredRepos))
|
||||
runner := utils.NewLimitedConcurrentRunner(tcrQPSLimit)
|
||||
|
||||
for i, r := range filteredRepos {
|
||||
// !copy
|
||||
index := i
|
||||
repo := r
|
||||
|
||||
runner.AddTask(func() error {
|
||||
var images []string
|
||||
_, images, err = a.getImages(*repo.Namespace, *repo.Name, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("[tencent-tcr.FetchArtifacts.listImages] repo=%s, error=%v", *repo.Name, err)
|
||||
}
|
||||
|
||||
var filteredImages []string
|
||||
if tagsPattern != "" {
|
||||
for _, image := range images {
|
||||
var ok bool
|
||||
ok, err = util.Match(tagsPattern, image)
|
||||
if err != nil {
|
||||
return fmt.Errorf("[tencent-tcr.FetchArtifacts.matchImage] image='%s', error=%v", image, err)
|
||||
}
|
||||
if ok {
|
||||
filteredImages = append(filteredImages, image)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
filteredImages = images
|
||||
}
|
||||
|
||||
log.Debugf("[tencent-tcr.FetchArtifacts] repo=%s, images=%v, filteredImages=%v", *repo.Name, images, filteredImages)
|
||||
|
||||
if len(filteredImages) > 0 {
|
||||
rawResources[index] = &model.Resource{
|
||||
Type: model.ResourceTypeImage,
|
||||
Registry: a.registry,
|
||||
Metadata: &model.ResourceMetadata{
|
||||
Repository: &model.Repository{
|
||||
Name: *repo.Name,
|
||||
},
|
||||
Vtags: filteredImages,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err = runner.Wait(); err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch artifacts: %v", err)
|
||||
}
|
||||
|
||||
for _, res := range rawResources {
|
||||
if res != nil {
|
||||
resources = append(resources, res)
|
||||
}
|
||||
}
|
||||
log.Debugf("[tencent-tcr.FetchArtifacts] resources.size=%d", len(resources))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) listCandidateNamespaces(namespacePattern string) (namespaces []string, err error) {
|
||||
// filter namespaces
|
||||
if len(namespacePattern) > 0 {
|
||||
if nms, ok := util.IsSpecificPathComponent(namespacePattern); ok {
|
||||
// Check is exist
|
||||
var exist bool
|
||||
for _, ns := range nms {
|
||||
exist, err = a.isNamespaceExist(ns)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !exist {
|
||||
continue
|
||||
}
|
||||
namespaces = append(namespaces, nms...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(namespaces) > 0 {
|
||||
log.Debugf("[tencent-tcr.listCandidateNamespaces] pattern=%s, namespaces=%v", namespacePattern, namespaces)
|
||||
return namespaces, nil
|
||||
}
|
||||
|
||||
// list all
|
||||
return a.listNamespaces()
|
||||
}
|
||||
|
||||
func (a *adapter) DeleteManifest(repository, reference string) (err error) {
|
||||
parts := strings.Split(repository, "/")
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("tcr only support repo in format <namespace>/<name>, but got: %s", repository)
|
||||
}
|
||||
log.Warningf("[tencent-tcr.DeleteManifest] namespace=%s, repository=%s, tag=%s", parts[0], parts[1], reference)
|
||||
|
||||
return a.deleteImage(parts[0], parts[1], reference)
|
||||
}
|
170
src/replication/adapter/tencentcr/artifact_registry_test.go
Normal file
170
src/replication/adapter/tencentcr/artifact_registry_test.go
Normal file
@ -0,0 +1,170 @@
|
||||
package tencentcr
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/replication/adapter/native"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
tcr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924"
|
||||
)
|
||||
|
||||
func Test_filterToPatterns(t *testing.T) {
|
||||
type args struct {
|
||||
filters []*model.Filter
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantNamespacePattern string
|
||||
wantRepoPattern string
|
||||
wantTagsPattern string
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotNamespacePattern, gotRepoPattern, gotTagsPattern := filterToPatterns(tt.args.filters)
|
||||
if gotNamespacePattern != tt.wantNamespacePattern {
|
||||
t.Errorf("filterToPatterns() gotNamespacePattern = %v, want %v", gotNamespacePattern, tt.wantNamespacePattern)
|
||||
}
|
||||
if gotRepoPattern != tt.wantRepoPattern {
|
||||
t.Errorf("filterToPatterns() gotRepoPattern = %v, want %v", gotRepoPattern, tt.wantRepoPattern)
|
||||
}
|
||||
if gotTagsPattern != tt.wantTagsPattern {
|
||||
t.Errorf("filterToPatterns() gotTagsPattern = %v, want %v", gotTagsPattern, tt.wantTagsPattern)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapter_FetchArtifacts(t *testing.T) {
|
||||
type fields struct {
|
||||
Adapter *native.Adapter
|
||||
registryID *string
|
||||
regionName *string
|
||||
tcrClient *tcr.Client
|
||||
pageSize *int64
|
||||
client *commonhttp.Client
|
||||
registry *model.Registry
|
||||
}
|
||||
type args struct {
|
||||
filters []*model.Filter
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantResources []*model.Resource
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &adapter{
|
||||
Adapter: tt.fields.Adapter,
|
||||
registryID: tt.fields.registryID,
|
||||
regionName: tt.fields.regionName,
|
||||
tcrClient: tt.fields.tcrClient,
|
||||
pageSize: tt.fields.pageSize,
|
||||
client: tt.fields.client,
|
||||
registry: tt.fields.registry,
|
||||
}
|
||||
gotResources, err := a.FetchArtifacts(tt.args.filters)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("adapter.FetchArtifacts() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotResources, tt.wantResources) {
|
||||
t.Errorf("adapter.FetchArtifacts() = %v, want %v", gotResources, tt.wantResources)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapter_listCandidateNamespaces(t *testing.T) {
|
||||
type fields struct {
|
||||
Adapter *native.Adapter
|
||||
registryID *string
|
||||
regionName *string
|
||||
tcrClient *tcr.Client
|
||||
pageSize *int64
|
||||
client *commonhttp.Client
|
||||
registry *model.Registry
|
||||
}
|
||||
type args struct {
|
||||
namespacePattern string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantNamespaces []string
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &adapter{
|
||||
Adapter: tt.fields.Adapter,
|
||||
registryID: tt.fields.registryID,
|
||||
regionName: tt.fields.regionName,
|
||||
tcrClient: tt.fields.tcrClient,
|
||||
pageSize: tt.fields.pageSize,
|
||||
client: tt.fields.client,
|
||||
registry: tt.fields.registry,
|
||||
}
|
||||
gotNamespaces, err := a.listCandidateNamespaces(tt.args.namespacePattern)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("adapter.listCandidateNamespaces() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotNamespaces, tt.wantNamespaces) {
|
||||
t.Errorf("adapter.listCandidateNamespaces() = %v, want %v", gotNamespaces, tt.wantNamespaces)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapter_DeleteManifest(t *testing.T) {
|
||||
type fields struct {
|
||||
Adapter *native.Adapter
|
||||
registryID *string
|
||||
regionName *string
|
||||
tcrClient *tcr.Client
|
||||
pageSize *int64
|
||||
client *commonhttp.Client
|
||||
registry *model.Registry
|
||||
}
|
||||
type args struct {
|
||||
repository string
|
||||
reference string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
// TODO: Add test cases.
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &adapter{
|
||||
Adapter: tt.fields.Adapter,
|
||||
registryID: tt.fields.registryID,
|
||||
regionName: tt.fields.regionName,
|
||||
tcrClient: tt.fields.tcrClient,
|
||||
pageSize: tt.fields.pageSize,
|
||||
client: tt.fields.client,
|
||||
registry: tt.fields.registry,
|
||||
}
|
||||
if err := a.DeleteManifest(tt.args.repository, tt.args.reference); (err != nil) != tt.wantErr {
|
||||
t.Errorf("adapter.DeleteManifest() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
85
src/replication/adapter/tencentcr/auth.go
Normal file
85
src/replication/adapter/tencentcr/auth.go
Normal file
@ -0,0 +1,85 @@
|
||||
package tencentcr
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/goharbor/harbor/src/common/http/modifier"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
tcr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924"
|
||||
)
|
||||
|
||||
// Credential ...
|
||||
type Credential modifier.Modifier
|
||||
|
||||
var _ Credential = &qcloudAuthCredential{}
|
||||
|
||||
func (q *qcloudAuthCredential) Modify(r *http.Request) (err error) {
|
||||
if !q.isCacheTokenValid() {
|
||||
err = q.getTempInstanceToken()
|
||||
log.Debugf("qcloudAuthCredential.Modify.isCacheTokenValid.updateToken=%s, err=%v", q.cacheTokenExpiredAt, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
r.SetBasicAuth(q.cacheTokener.username, q.cacheTokener.token)
|
||||
log.Debugf("[qcloudAuthCredential.Modify]Host: %v, header: %#v", r.Host, r.Header)
|
||||
return
|
||||
}
|
||||
|
||||
func (q *qcloudAuthCredential) isCacheTokenValid() (ok bool) {
|
||||
if &q.cacheTokenExpiredAt == nil {
|
||||
return
|
||||
}
|
||||
if q.cacheTokener == nil {
|
||||
return
|
||||
}
|
||||
if time.Now().After(q.cacheTokenExpiredAt) {
|
||||
return
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Implements interface Credential
|
||||
type qcloudAuthCredential struct {
|
||||
registryID *string
|
||||
client *tcr.Client
|
||||
cacheTokener *temporaryTokener
|
||||
cacheTokenExpiredAt time.Time
|
||||
}
|
||||
|
||||
type temporaryTokener struct {
|
||||
username string
|
||||
token string
|
||||
}
|
||||
|
||||
// NewAuth ...
|
||||
func NewAuth(registryID *string, client *tcr.Client) Credential {
|
||||
return &qcloudAuthCredential{
|
||||
registryID: registryID,
|
||||
client: client,
|
||||
cacheTokener: &temporaryTokener{},
|
||||
}
|
||||
}
|
||||
|
||||
func (q *qcloudAuthCredential) getTempInstanceToken() (err error) {
|
||||
var req = tcr.NewCreateInstanceTokenRequest()
|
||||
req.RegistryId = q.registryID
|
||||
var resp *tcr.CreateInstanceTokenResponse
|
||||
resp, err = q.client.CreateInstanceToken(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
q.cacheTokener = &temporaryTokener{*resp.Response.Username, *resp.Response.Token}
|
||||
q.cacheTokenExpiredAt = time.Unix(*resp.Response.ExpTime/1e3, *resp.Response.ExpTime%1e3)
|
||||
log.Debugf("[qcloudAuthCredential.getTempInstanceToken]Update temp token=%#v, cacheTokenExpiredAt=%s, unix=%v", q.cacheTokener,
|
||||
q.cacheTokenExpiredAt.UTC().String(), *resp.Response.ExpTime)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func isSecretID(key string) (ok bool) {
|
||||
return strings.Index(key, "AKID") == 0
|
||||
}
|
267
src/replication/adapter/tencentcr/chart_registry.go
Normal file
267
src/replication/adapter/tencentcr/chart_registry.go
Normal file
@ -0,0 +1,267 @@
|
||||
package tencentcr
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
commonhttp "github.com/goharbor/harbor/src/common/http"
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
adp "github.com/goharbor/harbor/src/replication/adapter"
|
||||
"github.com/goharbor/harbor/src/replication/filter"
|
||||
"github.com/goharbor/harbor/src/replication/model"
|
||||
)
|
||||
|
||||
const (
|
||||
chartListURL = "%s/api/chartrepo/%s/charts"
|
||||
chartVersionURL = "%s/api/chartrepo/%s/charts/%s"
|
||||
chartInfoURL = "%s/api/chartrepo/%s/charts/%s/%s"
|
||||
)
|
||||
|
||||
type tcrChart struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Digest string `json:"digest"`
|
||||
Name string `json:"name"`
|
||||
URLs []string `json:"urls"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type tcrChartVersionDetail struct {
|
||||
Metadata *tcrChartVersionMetadata `json:"metadata"`
|
||||
}
|
||||
type tcrChartVersionMetadata struct {
|
||||
URLs []string `json:"urls"`
|
||||
}
|
||||
|
||||
var _ adp.ChartRegistry = &adapter{}
|
||||
|
||||
func (a *adapter) FetchCharts(filters []*model.Filter) (resources []*model.Resource, err error) {
|
||||
log.Debugf("[tencent-tcr.FetchCharts]filters: %#v", filters)
|
||||
// 1. list namespaces
|
||||
var nsPattern, _, _ = filterToPatterns(filters)
|
||||
var nms []string
|
||||
nms, err = a.listCandidateNamespaces(nsPattern)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 2. list repositories
|
||||
for _, ns := range nms {
|
||||
var url = fmt.Sprintf(chartListURL, a.registry.URL, ns)
|
||||
var repositories = []*model.Repository{}
|
||||
err = a.client.Get(url, &repositories)
|
||||
log.Debugf("[tencent-tcr.FetchCharts] url=%s, namespace=%s, repositories=%v, error=%v", url, ns, repositories, err)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len(repositories) == 0 {
|
||||
continue
|
||||
}
|
||||
for _, repository := range repositories {
|
||||
repository.Name = fmt.Sprintf("%s/%s", ns, repository.Name)
|
||||
}
|
||||
repositories, err = filter.DoFilterRepositories(repositories, filters)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 3. list versions
|
||||
for _, repository := range repositories {
|
||||
var name = strings.SplitN(repository.Name, "/", 2)[1]
|
||||
var url = fmt.Sprintf(chartVersionURL, a.registry.URL, ns, name)
|
||||
var charts = []*tcrChart{}
|
||||
err = a.client.Get(url, &charts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(charts) == 0 {
|
||||
continue
|
||||
}
|
||||
var artifacts []*model.Artifact
|
||||
for _, chart := range charts {
|
||||
artifacts = append(artifacts, &model.Artifact{
|
||||
Tags: []string{chart.Version},
|
||||
})
|
||||
}
|
||||
artifacts, err = filter.DoFilterArtifacts(artifacts, filters)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(artifacts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, artifact := range artifacts {
|
||||
resources = append(resources, &model.Resource{
|
||||
Type: model.ResourceTypeChart,
|
||||
Registry: a.registry,
|
||||
Metadata: &model.ResourceMetadata{
|
||||
Repository: &model.Repository{
|
||||
Name: repository.Name,
|
||||
},
|
||||
Artifacts: []*model.Artifact{artifact},
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) ChartExist(name, version string) (exist bool, err error) {
|
||||
log.Debugf("[tencent-tcr.ChartExist] name=%s version=%s", name, version)
|
||||
_, err = a.getChartInfo(name, version)
|
||||
// if not found, return not exist
|
||||
if httpErr, ok := err.(*commonhttp.Error); ok && httpErr.Code == http.StatusNotFound {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
exist = true
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) getChartInfo(name, version string) (info *tcrChartVersionDetail, err error) {
|
||||
var namespace string
|
||||
var chart string
|
||||
namespace, chart, err = parseChartName(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var url = fmt.Sprintf(chartInfoURL, a.registry.URL, namespace, chart, version)
|
||||
info = &tcrChartVersionDetail{}
|
||||
err = a.client.Get(url, info)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) DownloadChart(name, version string) (rc io.ReadCloser, err error) {
|
||||
var info *tcrChartVersionDetail
|
||||
info, err = a.getChartInfo(name, version)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if info.Metadata == nil || len(info.Metadata.URLs) == 0 || len(info.Metadata.URLs[0]) == 0 {
|
||||
return nil, fmt.Errorf("[tencent-tcr.DownloadChart.NO_DOWNLOAD_URL] chart=%s:%s", name, version)
|
||||
}
|
||||
|
||||
var url = strings.ToLower(info.Metadata.URLs[0])
|
||||
// relative URL
|
||||
if !(strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")) {
|
||||
var namespace string
|
||||
namespace, _, err = parseChartName(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
url = fmt.Sprintf("%s/chartrepo/%s/%s", a.registry.URL, namespace, url)
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
var resp *http.Response
|
||||
var body []byte
|
||||
req, err = http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err = a.client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = fmt.Errorf("[tencent-tcr.DownloadChart.failed] chart=%s, status=%d, body=%s", req.URL.String(), resp.StatusCode, string(body))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
func (a *adapter) UploadChart(name, version string, reader io.Reader) (err error) {
|
||||
var namespace string
|
||||
var chart string
|
||||
namespace, chart, err = parseChartName(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// 1. write to form-data buffer
|
||||
var buf = &bytes.Buffer{}
|
||||
var writer = multipart.NewWriter(buf)
|
||||
var fw io.Writer
|
||||
fw, err = writer.CreateFormFile("chart", chart+".tgz")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
_, err = io.Copy(fw, reader)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
writer.Close()
|
||||
|
||||
// 2. upload
|
||||
var url = fmt.Sprintf(chartListURL, a.registry.URL, namespace)
|
||||
var req *http.Request
|
||||
var resp *http.Response
|
||||
req, err = http.NewRequest(http.MethodPost, url, buf)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
resp, err = a.client.Do(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 3. parse response
|
||||
var data []byte
|
||||
data, err = ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if resp.StatusCode < http.StatusOK || resp.StatusCode > 299 {
|
||||
err = &commonhttp.Error{
|
||||
Code: resp.StatusCode,
|
||||
Message: string(data),
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) DeleteChart(name, version string) (err error) {
|
||||
var namespace string
|
||||
var chart string
|
||||
namespace, chart, err = parseChartName(name)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var url = fmt.Sprintf(chartInfoURL, a.registry.URL, namespace, chart, version)
|
||||
|
||||
return a.client.Delete(url)
|
||||
}
|
||||
|
||||
func parseChartName(name string) (namespace, chart string, err error) {
|
||||
strs := strings.Split(name, "/")
|
||||
if len(strs) == 2 && len(strs[0]) > 0 && len(strs[1]) > 0 {
|
||||
return strs[0], strs[1], nil
|
||||
}
|
||||
return "", "", fmt.Errorf("[tencent-tcr.parseChartName.invalid_name] name=%s", name)
|
||||
}
|
240
src/replication/adapter/tencentcr/sdk.go
Normal file
240
src/replication/adapter/tencentcr/sdk.go
Normal file
@ -0,0 +1,240 @@
|
||||
package tencentcr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/goharbor/harbor/src/lib/log"
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
|
||||
tcr "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924"
|
||||
)
|
||||
|
||||
func (a *adapter) createPrivateNamespace(namespace string) (err error) {
|
||||
if a.tcrClient == nil {
|
||||
err = errors.New("[tencent-tcr.createPrivateNamespace] nil tcr client")
|
||||
return
|
||||
}
|
||||
|
||||
// 1. if exist skip
|
||||
log.Debugf("[tencent-tcr.PrepareForPush.createPrivateNamespace] namespace=%s", namespace)
|
||||
var exist bool
|
||||
exist, err = a.isNamespaceExist(namespace)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if exist {
|
||||
log.Warningf("[tencent-tcr.PrepareForPush.createPrivateNamespace.skip_exist] namespace=%s", namespace)
|
||||
return
|
||||
}
|
||||
|
||||
// !!! 2. WARNING: for safety, auto create namespace is private.
|
||||
var req = tcr.NewCreateNamespaceRequest()
|
||||
req.NamespaceName = &namespace
|
||||
req.RegistryId = a.registryID
|
||||
var isPublic = false
|
||||
req.IsPublic = &isPublic
|
||||
tcr.NewCreateNamespaceResponse()
|
||||
_, err = a.tcrClient.CreateNamespace(req)
|
||||
if err != nil {
|
||||
log.Debugf("[tencent-tcr.PrepareForPush.createPrivateNamespace] error=%v", err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) createRepository(namespace, repository string) (err error) {
|
||||
if a.tcrClient == nil {
|
||||
err = errors.New("[tencent-tcr.createRepository] nil tcr client")
|
||||
return
|
||||
}
|
||||
|
||||
// 1. if exist skip
|
||||
log.Debugf("[tencent-tcr.PrepareForPush.createRepository] namespace=%s, repository=%s", namespace, repository)
|
||||
var repoReq = tcr.NewDescribeRepositoriesRequest()
|
||||
repoReq.RegistryId = a.registryID
|
||||
repoReq.NamespaceName = &namespace
|
||||
repoReq.RepositoryName = &repository
|
||||
var repoResp = tcr.NewDescribeRepositoriesResponse()
|
||||
repoResp, err = a.tcrClient.DescribeRepositories(repoReq)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if int(*repoResp.Response.TotalCount) > 0 {
|
||||
log.Warningf("[tencent-tcr.PrepareForPush.createRepository.skip_exist] namespace=%s, repository=%s", namespace, repository)
|
||||
return
|
||||
}
|
||||
|
||||
// 2. create
|
||||
var req = tcr.NewCreateRepositoryRequest()
|
||||
req.NamespaceName = &namespace
|
||||
req.RepositoryName = &repository
|
||||
req.RegistryId = a.registryID
|
||||
var resp = tcr.NewCreateRepositoryResponse()
|
||||
resp, err = a.tcrClient.CreateRepository(req)
|
||||
if err != nil {
|
||||
log.Debugf("[tencent-tcr.PrepareForPush.createRepository] error=%v", err)
|
||||
return
|
||||
}
|
||||
log.Debugf("[tencent-tcr.PrepareForPush.createRepository] resp=%#v", *resp)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) listNamespaces() (namespaces []string, err error) {
|
||||
if a.tcrClient == nil {
|
||||
err = errors.New("[tencent-tcr.listNamespaces] nil tcr client")
|
||||
return
|
||||
}
|
||||
|
||||
// list namespaces
|
||||
var req = tcr.NewDescribeNamespacesRequest()
|
||||
req.RegistryId = a.registryID
|
||||
req.Limit = a.pageSize
|
||||
var resp = tcr.NewDescribeNamespacesResponse()
|
||||
|
||||
var page int64
|
||||
for {
|
||||
req.Offset = &page
|
||||
resp, err = a.tcrClient.DescribeNamespaces(req)
|
||||
if err != nil {
|
||||
log.Debugf("[tencent-tcr.DescribeNamespaces] registryID=%s, error=%v", *a.registryID, err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, ns := range resp.Response.NamespaceList {
|
||||
namespaces = append(namespaces, *ns.Name)
|
||||
}
|
||||
|
||||
if len(namespaces) >= int(*resp.Response.TotalCount) {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
log.Debugf("[tencent-tcr.FetchArtifacts.listNamespaces] registryID=%s, namespaces[%d]=%s", *a.registryID, len(namespaces), namespaces)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) isNamespaceExist(namespace string) (exist bool, err error) {
|
||||
if a.tcrClient == nil {
|
||||
err = errors.New("[tencent-tcr.isNamespaceExist] nil tcr client")
|
||||
return
|
||||
}
|
||||
|
||||
var req = tcr.NewDescribeNamespacesRequest()
|
||||
req.NamespaceName = &namespace
|
||||
req.RegistryId = a.registryID
|
||||
var resp = tcr.NewDescribeNamespacesResponse()
|
||||
resp, err = a.tcrClient.DescribeNamespaces(req)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Warningf("[tencent-tcr.PrepareForPush.isNamespaceExist] namespace=%s, total=%d", namespace, *resp.Response.TotalCount)
|
||||
if int(*resp.Response.TotalCount) != 1 {
|
||||
return
|
||||
}
|
||||
exist = true
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) listReposByNamespace(namespace string) (repos []tcr.TcrRepositoryInfo, err error) {
|
||||
if a.tcrClient == nil {
|
||||
err = errors.New("[tencent-tcr.listReposByNamespace] nil tcr client")
|
||||
return
|
||||
}
|
||||
|
||||
var req = tcr.NewDescribeRepositoriesRequest()
|
||||
req.RegistryId = a.registryID
|
||||
req.NamespaceName = common.StringPtr(namespace)
|
||||
req.Limit = a.pageSize
|
||||
var resp = tcr.NewDescribeRepositoriesResponse()
|
||||
|
||||
var page int64
|
||||
for {
|
||||
req.Offset = common.Int64Ptr(page)
|
||||
resp, err = a.tcrClient.DescribeRepositories(req)
|
||||
if err != nil {
|
||||
log.Debugf("[tencent-tcr.listReposByNamespace.DescribeRepositories] registryID=%s, namespace=%s, error=%v", *a.registryID, namespace, err)
|
||||
return
|
||||
}
|
||||
|
||||
size := len(resp.Response.RepositoryList)
|
||||
for i, repo := range resp.Response.RepositoryList {
|
||||
log.Debugf("[tencent-tcr.listReposByNamespace.DescribeRepositories] Retrives page=%d repo(%d/%d)=%s", page, i, size, *repo.Name)
|
||||
repos = append(repos, *repo)
|
||||
}
|
||||
|
||||
if len(repos) == int(*resp.Response.TotalCount) {
|
||||
log.Debugf("[tencent-tcr.listReposByNamespace.DescribeRepositories] Retrives all repos.")
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
log.Debugf("[tencent-tcr.listReposByNamespace] registryID=%s, namespace=%s, repos=%d",
|
||||
*a.registryID, namespace, len(repos))
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) getImages(namespace, repo, tag string) (images []*tcr.TcrImageInfo, imageNames []string, err error) {
|
||||
if a.tcrClient == nil {
|
||||
err = errors.New("[tencent-tcr.getImages] nil tcr client")
|
||||
return
|
||||
}
|
||||
|
||||
if namespace != "" {
|
||||
repo = strings.Replace(repo, namespace, "", 1)
|
||||
repo = strings.Replace(repo, "/", "", 1)
|
||||
}
|
||||
|
||||
var req = tcr.NewDescribeImagesRequest()
|
||||
req.RegistryId = a.registryID
|
||||
req.NamespaceName = &namespace
|
||||
req.RepositoryName = &repo
|
||||
req.Limit = a.pageSize
|
||||
if tag != "" {
|
||||
req.ImageVersion = &tag
|
||||
}
|
||||
var resp = tcr.NewDescribeImagesResponse()
|
||||
|
||||
var page int64
|
||||
for {
|
||||
log.Debugf("[tencent-tcr.getImages] registryID=%s, namespace=%s, repo=%s, tag=%s, page=%d",
|
||||
*a.registryID, namespace, repo, tag, page)
|
||||
req.Offset = &page
|
||||
resp, err = a.tcrClient.DescribeImages(req)
|
||||
if err != nil {
|
||||
log.Debugf("[tencent-tcr.getImages.DescribeImages] registryID=%s, namespace=%s, repo=%s, error=%v", *a.registryID, namespace, repo, err)
|
||||
return
|
||||
}
|
||||
|
||||
images = resp.Response.ImageInfoList
|
||||
for _, image := range resp.Response.ImageInfoList {
|
||||
imageNames = append(imageNames, *image.ImageVersion)
|
||||
}
|
||||
|
||||
if len(images) == int(*resp.Response.TotalCount) {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
log.Debugf("[tencent-tcr.getImages] registryID=%s, namespace=%s, repo=%s, tags[%d]=%v\n", *a.registryID, namespace, repo, len(imageNames), imageNames)
|
||||
return
|
||||
}
|
||||
|
||||
func (a *adapter) deleteImage(namespace, repository, reference string) (err error) {
|
||||
var req = tcr.NewDeleteImageRequest()
|
||||
req.RegistryId = a.registryID
|
||||
req.NamespaceName = common.StringPtr(namespace)
|
||||
req.RepositoryName = common.StringPtr(repository)
|
||||
req.ImageVersion = common.StringPtr(reference)
|
||||
|
||||
_, err = a.tcrClient.DeleteImage(req)
|
||||
if err != nil {
|
||||
log.Errorf("[tencent-tcr.deleteImage.DeleteImage] failed. namespace=%s, repository=%s, tag=%s, error=%s", namespace, repository, reference, err.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
133
src/replication/adapter/tencentcr/sdk_test.go
Normal file
133
src/replication/adapter/tencentcr/sdk_test.go
Normal file
@ -0,0 +1,133 @@
|
||||
package tencentcr
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_adapter_createPrivateNamespace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
wantErr bool
|
||||
}{
|
||||
{namespace: "ut_ns_123", wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &adapter{tcrClient: tcrClient}
|
||||
if err := a.createPrivateNamespace(tt.namespace); (err != nil) != tt.wantErr {
|
||||
t.Errorf("adapter.createPrivateNamespace() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapter_createRepository(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
repository string
|
||||
wantErr bool
|
||||
}{
|
||||
{namespace: "ut_ns_123", repository: "ut_repo_123", wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &adapter{tcrClient: tcrClient}
|
||||
if err := a.createRepository(tt.namespace, tt.repository); (err != nil) != tt.wantErr {
|
||||
t.Errorf("adapter.createRepository() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapter_listNamespaces(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
wantNamespaces []string
|
||||
wantErr bool
|
||||
}{
|
||||
{wantNamespaces: []string{}, wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &adapter{tcrClient: tcrClient}
|
||||
_, err := a.listNamespaces()
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("adapter.listNamespaces() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapter_isNamespaceExist(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
wantExist bool
|
||||
wantErr bool
|
||||
}{
|
||||
{namespace: "ut_ns_123", wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &adapter{tcrClient: tcrClient}
|
||||
gotExist, err := a.isNamespaceExist(tt.namespace)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("adapter.isNamespaceExist() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if gotExist != tt.wantExist {
|
||||
t.Errorf("adapter.isNamespaceExist() = %v, want %v", gotExist, tt.wantExist)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapter_listReposByNamespace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
wantErr bool
|
||||
}{
|
||||
{namespace: "ut_ns_123", wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &adapter{tcrClient: tcrClient}
|
||||
_, err := a.listReposByNamespace(tt.namespace)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("adapter.listReposByNamespace() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_adapter_getImages(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
repo string
|
||||
tag string
|
||||
wantImages []string
|
||||
wantErr bool
|
||||
}{
|
||||
{namespace: "ut_ns_123", repo: "ut_repo_123", wantErr: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := &adapter{tcrClient: tcrClient}
|
||||
_, gotImages, err := a.getImages(tt.namespace, tt.repo, tt.tag)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("adapter.getImages() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if !reflect.DeepEqual(gotImages, tt.wantImages) {
|
||||
t.Errorf("adapter.getImages() = %v, want %v", gotImages, tt.wantImages)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -32,6 +32,7 @@ const (
|
||||
RegistryTypeQuay RegistryType = "quay"
|
||||
RegistryTypeGitLab RegistryType = "gitlab"
|
||||
RegistryTypeDTR RegistryType = "dtr"
|
||||
RegistryTypeTencentTcr RegistryType = "tencent-tcr"
|
||||
|
||||
RegistryTypeHelmHub RegistryType = "helm-hub"
|
||||
RegistryTypeArtifactHub RegistryType = "artifact-hub"
|
||||
|
@ -59,6 +59,8 @@ import (
|
||||
_ "github.com/goharbor/harbor/src/replication/adapter/dtr"
|
||||
// register the Artifact Hub adapter
|
||||
_ "github.com/goharbor/harbor/src/replication/adapter/artifacthub"
|
||||
// register the TencentCloud TCR adapter
|
||||
_ "github.com/goharbor/harbor/src/replication/adapter/tencentcr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
201
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/LICENSE
generated
vendored
Normal file
201
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/LICENSE
generated
vendored
Normal 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.
|
278
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/client.go
generated
vendored
Normal file
278
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/client.go
generated
vendored
Normal file
@ -0,0 +1,278 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
|
||||
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.GetScheme() == "" {
|
||||
request.SetScheme(c.httpProfile.Scheme)
|
||||
}
|
||||
|
||||
if request.GetRootDomain() == "" {
|
||||
request.SetRootDomain(c.httpProfile.RootDomain)
|
||||
}
|
||||
|
||||
if request.GetDomain() == "" {
|
||||
domain := c.httpProfile.Endpoint
|
||||
if domain == "" {
|
||||
domain = request.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.Set("Content-Type", "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 {
|
||||
msg := fmt.Sprintf("Fail to get response because %s", err)
|
||||
return errors.NewTencentCloudSDKError("ClientError.NetworkError", msg, "")
|
||||
}
|
||||
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 := request.GetScheme() + "://" + 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 {
|
||||
msg := fmt.Sprintf("Fail to get response because %s", err)
|
||||
return errors.NewTencentCloudSDKError("ClientError.NetworkError", msg, "")
|
||||
}
|
||||
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.debug = clientProfile.Debug
|
||||
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 (c *Client) WithDebug(flag bool) *Client {
|
||||
c.debug = flag
|
||||
return c
|
||||
}
|
||||
|
||||
func NewClientWithSecretId(secretId, secretKey, region string) (client *Client, err error) {
|
||||
client = &Client{}
|
||||
client.Init(region).WithSecretId(secretId, secretKey)
|
||||
return
|
||||
}
|
58
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/credentials.go
generated
vendored
Normal file
58
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/credentials.go
generated
vendored
Normal 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,
|
||||
// }
|
||||
//}
|
35
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors/errors.go
generated
vendored
Normal file
35
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors/errors.go
generated
vendored
Normal 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
|
||||
}
|
275
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/request.go
generated
vendored
Normal file
275
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/request.go
generated
vendored
Normal file
@ -0,0 +1,275 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"io"
|
||||
//"log"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
POST = "POST"
|
||||
GET = "GET"
|
||||
|
||||
HTTP = "http"
|
||||
HTTPS = "https"
|
||||
|
||||
RootDomain = "tencentcloudapi.com"
|
||||
Path = "/"
|
||||
)
|
||||
|
||||
type Request interface {
|
||||
GetAction() string
|
||||
GetBodyReader() io.Reader
|
||||
GetScheme() string
|
||||
GetRootDomain() string
|
||||
GetServiceDomain(string) string
|
||||
GetDomain() string
|
||||
GetHttpMethod() string
|
||||
GetParams() map[string]string
|
||||
GetPath() string
|
||||
GetService() string
|
||||
GetUrl() string
|
||||
GetVersion() string
|
||||
SetScheme(string)
|
||||
SetRootDomain(string)
|
||||
SetDomain(string)
|
||||
SetHttpMethod(string)
|
||||
}
|
||||
|
||||
type BaseRequest struct {
|
||||
httpMethod string
|
||||
scheme string
|
||||
rootDomain 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) GetScheme() string {
|
||||
return r.scheme
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetRootDomain() string {
|
||||
return r.rootDomain
|
||||
}
|
||||
|
||||
func (r *BaseRequest) GetServiceDomain(service string) (domain string) {
|
||||
rootDomain := r.rootDomain
|
||||
if rootDomain == "" {
|
||||
rootDomain = RootDomain
|
||||
}
|
||||
domain = service + "." + rootDomain
|
||||
return
|
||||
}
|
||||
|
||||
func (r *BaseRequest) SetDomain(domain string) {
|
||||
r.domain = domain
|
||||
}
|
||||
|
||||
func (r *BaseRequest) SetScheme(scheme string) {
|
||||
scheme = strings.ToLower(scheme)
|
||||
switch scheme {
|
||||
case HTTP:
|
||||
r.scheme = HTTP
|
||||
default:
|
||||
r.scheme = HTTPS
|
||||
}
|
||||
}
|
||||
|
||||
func (r *BaseRequest) SetRootDomain(rootDomain string) {
|
||||
r.rootDomain = rootDomain
|
||||
}
|
||||
|
||||
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 r.GetScheme() + "://" + r.domain + r.path + "?" + GetUrlQueriesEncoded(r.params)
|
||||
} else if r.httpMethod == POST {
|
||||
return r.GetScheme() + "://" + 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
|
||||
}
|
||||
|
||||
// Deprecated, use request.GetServiceDomain instead
|
||||
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_1.0.62"
|
||||
}
|
||||
|
||||
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
|
||||
}
|
81
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/response.go
generated
vendored
Normal file
81
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/response.go
generated
vendored
Normal 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
|
||||
}
|
23
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile/client_profile.go
generated
vendored
Normal file
23
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile/client_profile.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
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
|
||||
Debug bool
|
||||
}
|
||||
|
||||
func NewClientProfile() *ClientProfile {
|
||||
return &ClientProfile{
|
||||
HttpProfile: NewHttpProfile(),
|
||||
SignMethod: "TC3-HMAC-SHA256",
|
||||
UnsignedPayload: false,
|
||||
Language: "zh-CN",
|
||||
Debug: false,
|
||||
}
|
||||
}
|
21
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile/http_profile.go
generated
vendored
Normal file
21
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile/http_profile.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package profile
|
||||
|
||||
type HttpProfile struct {
|
||||
ReqMethod string
|
||||
ReqTimeout int
|
||||
Scheme string
|
||||
RootDomain string
|
||||
Endpoint string
|
||||
// Deprecated, use Scheme instead
|
||||
Protocol string
|
||||
}
|
||||
|
||||
func NewHttpProfile() *HttpProfile {
|
||||
return &HttpProfile{
|
||||
ReqMethod: "POST",
|
||||
ReqTimeout: 60,
|
||||
Scheme: "HTTPS",
|
||||
RootDomain: "",
|
||||
Endpoint: "",
|
||||
}
|
||||
}
|
58
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions/regions.go
generated
vendored
Normal file
58
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions/regions.go
generated
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
// Copyright (c) 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.
|
||||
|
||||
package regions
|
||||
|
||||
const (
|
||||
// 曼谷
|
||||
Bangkok = "ap-bangkok"
|
||||
// 北京
|
||||
Beijing = "ap-beijing"
|
||||
// 成都
|
||||
Chengdu = "ap-chengdu"
|
||||
// 重庆
|
||||
Chongqing = "ap-chongqing"
|
||||
// 广州
|
||||
Guangzhou = "ap-guangzhou"
|
||||
// 广州Open
|
||||
GuangzhouOpen = "ap-guangzhou-open"
|
||||
// 中国香港
|
||||
HongKong = "ap-hongkong"
|
||||
// 孟买
|
||||
Mumbai = "ap-mumbai"
|
||||
// 首尔
|
||||
Seoul = "ap-seoul"
|
||||
// 上海
|
||||
Shanghai = "ap-shanghai"
|
||||
// 南京
|
||||
Nanjing = "ap-nanjing"
|
||||
// 上海金融
|
||||
ShanghaiFSI = "ap-shanghai-fsi"
|
||||
// 深圳金融
|
||||
ShenzhenFSI = "ap-shenzhen-fsi"
|
||||
// 新加坡
|
||||
Singapore = "ap-singapore"
|
||||
// 东京
|
||||
Tokyo = "ap-tokyo"
|
||||
// 法兰克福
|
||||
Frankfurt = "eu-frankfurt"
|
||||
// 莫斯科
|
||||
Moscow = "eu-moscow"
|
||||
// 阿什本
|
||||
Ashburn = "na-ashburn"
|
||||
// 硅谷
|
||||
SiliconValley = "na-siliconvalley"
|
||||
// 多伦多
|
||||
Toronto = "na-toronto"
|
||||
)
|
94
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/sign.go
generated
vendored
Normal file
94
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/sign.go
generated
vendored
Normal 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()
|
||||
}
|
95
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/types.go
generated
vendored
Normal file
95
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/types.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
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 BoolPtr(v bool) *bool {
|
||||
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 IntPtrs(vals []int) []*int {
|
||||
ptrs := make([]*int, len(vals))
|
||||
for i := 0; i < len(vals); i++ {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
return ptrs
|
||||
}
|
||||
|
||||
func Int64Ptrs(vals []int64) []*int64 {
|
||||
ptrs := make([]*int64, len(vals))
|
||||
for i := 0; i < len(vals); i++ {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
return ptrs
|
||||
}
|
||||
|
||||
func UintPtrs(vals []uint) []*uint {
|
||||
ptrs := make([]*uint, len(vals))
|
||||
for i := 0; i < len(vals); i++ {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
return ptrs
|
||||
}
|
||||
|
||||
func Uint64Ptrs(vals []uint64) []*uint64 {
|
||||
ptrs := make([]*uint64, len(vals))
|
||||
for i := 0; i < len(vals); i++ {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
return ptrs
|
||||
}
|
||||
|
||||
func Float64Ptrs(vals []float64) []*float64 {
|
||||
ptrs := make([]*float64, len(vals))
|
||||
for i := 0; i < len(vals); i++ {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
return ptrs
|
||||
}
|
||||
|
||||
func BoolPtrs(vals []bool) []*bool {
|
||||
ptrs := make([]*bool, len(vals))
|
||||
for i := 0; i < len(vals); i++ {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
return ptrs
|
||||
}
|
||||
|
||||
func StringPtrs(vals []string) []*string {
|
||||
ptrs := make([]*string, len(vals))
|
||||
for i := 0; i < len(vals); i++ {
|
||||
ptrs[i] = &vals[i]
|
||||
}
|
||||
return ptrs
|
||||
}
|
1519
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924/client.go
generated
vendored
Normal file
1519
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924/client.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
3222
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924/models.go
generated
vendored
Normal file
3222
src/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924/models.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
8
src/vendor/modules.txt
vendored
8
src/vendor/modules.txt
vendored
@ -477,6 +477,14 @@ github.com/stretchr/testify/assert
|
||||
github.com/stretchr/testify/mock
|
||||
github.com/stretchr/testify/require
|
||||
github.com/stretchr/testify/suite
|
||||
# github.com/tencentcloud/tencentcloud-sdk-go v1.0.62
|
||||
## explicit
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/regions
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tcr/v20190924
|
||||
# github.com/theupdateframework/notary v0.6.1
|
||||
## explicit
|
||||
github.com/theupdateframework/notary
|
||||
|
Loading…
Reference in New Issue
Block a user