forked from TrueCloudLab/distribution
90bed67126
Change-Id: I423ad03e63bd38aded3abfcba49079ff2fbb3b74 Signed-off-by: Li Yi <denverdino@gmail.com>
550 lines
14 KiB
Go
550 lines
14 KiB
Go
package common
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/denverdino/aliyungo/util"
|
|
)
|
|
|
|
// RemovalPolicy.N add index to array item
|
|
// RemovalPolicy=["a", "b"] => RemovalPolicy.1="a" RemovalPolicy.2="b"
|
|
type FlattenArray []string
|
|
|
|
// string contains underline which will be replaced with dot
|
|
// SystemDisk_Category => SystemDisk.Category
|
|
type UnderlineString string
|
|
|
|
// A Client represents a client of ECS services
|
|
type Client struct {
|
|
AccessKeyId string //Access Key Id
|
|
AccessKeySecret string //Access Key Secret
|
|
securityToken string
|
|
debug bool
|
|
httpClient *http.Client
|
|
endpoint string
|
|
version string
|
|
serviceCode string
|
|
regionID Region
|
|
businessInfo string
|
|
userAgent string
|
|
}
|
|
|
|
// Initialize properties of a client instance
|
|
func (client *Client) Init(endpoint, version, accessKeyId, accessKeySecret string) {
|
|
client.AccessKeyId = accessKeyId
|
|
ak := accessKeySecret
|
|
if !strings.HasSuffix(ak, "&") {
|
|
ak += "&"
|
|
}
|
|
client.AccessKeySecret = ak
|
|
client.debug = false
|
|
handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout"))
|
|
if err != nil {
|
|
handshakeTimeout = 0
|
|
}
|
|
if handshakeTimeout == 0 {
|
|
client.httpClient = &http.Client{}
|
|
} else {
|
|
t := &http.Transport{
|
|
TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second}
|
|
client.httpClient = &http.Client{Transport: t}
|
|
}
|
|
client.endpoint = endpoint
|
|
client.version = version
|
|
}
|
|
|
|
// Initialize properties of a client instance including regionID
|
|
func (client *Client) NewInit(endpoint, version, accessKeyId, accessKeySecret, serviceCode string, regionID Region) {
|
|
client.Init(endpoint, version, accessKeyId, accessKeySecret)
|
|
client.serviceCode = serviceCode
|
|
client.regionID = regionID
|
|
}
|
|
|
|
// Intialize client object when all properties are ready
|
|
func (client *Client) InitClient() *Client {
|
|
client.debug = false
|
|
handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout"))
|
|
if err != nil {
|
|
handshakeTimeout = 0
|
|
}
|
|
if handshakeTimeout == 0 {
|
|
client.httpClient = &http.Client{}
|
|
} else {
|
|
t := &http.Transport{
|
|
TLSHandshakeTimeout: time.Duration(handshakeTimeout) * time.Second}
|
|
client.httpClient = &http.Client{Transport: t}
|
|
}
|
|
return client
|
|
}
|
|
|
|
func (client *Client) NewInitForAssumeRole(endpoint, version, accessKeyId, accessKeySecret, serviceCode string, regionID Region, securityToken string) {
|
|
client.NewInit(endpoint, version, accessKeyId, accessKeySecret, serviceCode, regionID)
|
|
client.securityToken = securityToken
|
|
}
|
|
|
|
//getLocationEndpoint
|
|
func (client *Client) getEndpointByLocation() string {
|
|
locationClient := NewLocationClient(client.AccessKeyId, client.AccessKeySecret, client.securityToken)
|
|
locationClient.SetDebug(true)
|
|
return locationClient.DescribeOpenAPIEndpoint(client.regionID, client.serviceCode)
|
|
}
|
|
|
|
//NewClient using location service
|
|
func (client *Client) setEndpointByLocation(region Region, serviceCode, accessKeyId, accessKeySecret, securityToken string) {
|
|
locationClient := NewLocationClient(accessKeyId, accessKeySecret, securityToken)
|
|
locationClient.SetDebug(true)
|
|
ep := locationClient.DescribeOpenAPIEndpoint(region, serviceCode)
|
|
if ep == "" {
|
|
ep = loadEndpointFromFile(region, serviceCode)
|
|
}
|
|
|
|
if ep != "" {
|
|
client.endpoint = ep
|
|
}
|
|
}
|
|
|
|
// Ensure all necessary properties are valid
|
|
func (client *Client) ensureProperties() error {
|
|
var msg string
|
|
|
|
if client.endpoint == "" {
|
|
msg = fmt.Sprintf("endpoint cannot be empty!")
|
|
} else if client.version == "" {
|
|
msg = fmt.Sprintf("version cannot be empty!")
|
|
} else if client.AccessKeyId == "" {
|
|
msg = fmt.Sprintf("AccessKeyId cannot be empty!")
|
|
} else if client.AccessKeySecret == "" {
|
|
msg = fmt.Sprintf("AccessKeySecret cannot be empty!")
|
|
}
|
|
|
|
if msg != "" {
|
|
return errors.New(msg)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ----------------------------------------------------
|
|
// WithXXX methods
|
|
// ----------------------------------------------------
|
|
|
|
// WithEndpoint sets custom endpoint
|
|
func (client *Client) WithEndpoint(endpoint string) *Client {
|
|
client.SetEndpoint(endpoint)
|
|
return client
|
|
}
|
|
|
|
// WithVersion sets custom version
|
|
func (client *Client) WithVersion(version string) *Client {
|
|
client.SetVersion(version)
|
|
return client
|
|
}
|
|
|
|
// WithRegionID sets Region ID
|
|
func (client *Client) WithRegionID(regionID Region) *Client {
|
|
client.SetRegionID(regionID)
|
|
return client
|
|
}
|
|
|
|
//WithServiceCode sets serviceCode
|
|
func (client *Client) WithServiceCode(serviceCode string) *Client {
|
|
client.SetServiceCode(serviceCode)
|
|
return client
|
|
}
|
|
|
|
// WithAccessKeyId sets new AccessKeyId
|
|
func (client *Client) WithAccessKeyId(id string) *Client {
|
|
client.SetAccessKeyId(id)
|
|
return client
|
|
}
|
|
|
|
// WithAccessKeySecret sets new AccessKeySecret
|
|
func (client *Client) WithAccessKeySecret(secret string) *Client {
|
|
client.SetAccessKeySecret(secret)
|
|
return client
|
|
}
|
|
|
|
// WithSecurityToken sets securityToken
|
|
func (client *Client) WithSecurityToken(securityToken string) *Client {
|
|
client.SetSecurityToken(securityToken)
|
|
return client
|
|
}
|
|
|
|
// WithDebug sets debug mode to log the request/response message
|
|
func (client *Client) WithDebug(debug bool) *Client {
|
|
client.SetDebug(debug)
|
|
return client
|
|
}
|
|
|
|
// WithBusinessInfo sets business info to log the request/response message
|
|
func (client *Client) WithBusinessInfo(businessInfo string) *Client {
|
|
client.SetBusinessInfo(businessInfo)
|
|
return client
|
|
}
|
|
|
|
// WithUserAgent sets user agent to the request/response message
|
|
func (client *Client) WithUserAgent(userAgent string) *Client {
|
|
client.SetUserAgent(userAgent)
|
|
return client
|
|
}
|
|
|
|
// ----------------------------------------------------
|
|
// SetXXX methods
|
|
// ----------------------------------------------------
|
|
|
|
// SetEndpoint sets custom endpoint
|
|
func (client *Client) SetEndpoint(endpoint string) {
|
|
client.endpoint = endpoint
|
|
}
|
|
|
|
// SetEndpoint sets custom version
|
|
func (client *Client) SetVersion(version string) {
|
|
client.version = version
|
|
}
|
|
|
|
// SetEndpoint sets Region ID
|
|
func (client *Client) SetRegionID(regionID Region) {
|
|
client.regionID = regionID
|
|
}
|
|
|
|
//SetServiceCode sets serviceCode
|
|
func (client *Client) SetServiceCode(serviceCode string) {
|
|
client.serviceCode = serviceCode
|
|
}
|
|
|
|
// SetAccessKeyId sets new AccessKeyId
|
|
func (client *Client) SetAccessKeyId(id string) {
|
|
client.AccessKeyId = id
|
|
}
|
|
|
|
// SetAccessKeySecret sets new AccessKeySecret
|
|
func (client *Client) SetAccessKeySecret(secret string) {
|
|
client.AccessKeySecret = secret + "&"
|
|
}
|
|
|
|
// SetDebug sets debug mode to log the request/response message
|
|
func (client *Client) SetDebug(debug bool) {
|
|
client.debug = debug
|
|
}
|
|
|
|
// SetBusinessInfo sets business info to log the request/response message
|
|
func (client *Client) SetBusinessInfo(businessInfo string) {
|
|
if strings.HasPrefix(businessInfo, "/") {
|
|
client.businessInfo = businessInfo
|
|
} else if businessInfo != "" {
|
|
client.businessInfo = "/" + businessInfo
|
|
}
|
|
}
|
|
|
|
// SetUserAgent sets user agent to the request/response message
|
|
func (client *Client) SetUserAgent(userAgent string) {
|
|
client.userAgent = userAgent
|
|
}
|
|
|
|
//set SecurityToken
|
|
func (client *Client) SetSecurityToken(securityToken string) {
|
|
client.securityToken = securityToken
|
|
}
|
|
|
|
func (client *Client) initEndpoint() error {
|
|
// if set any value to "CUSTOMIZED_ENDPOINT" could skip location service.
|
|
// example: export CUSTOMIZED_ENDPOINT=true
|
|
if os.Getenv("CUSTOMIZED_ENDPOINT") != "" {
|
|
return nil
|
|
}
|
|
|
|
if client.serviceCode != "" && client.regionID != "" {
|
|
endpoint := client.getEndpointByLocation()
|
|
if endpoint == "" {
|
|
return GetCustomError("InvalidEndpoint", "endpoint is empty,pls check")
|
|
}
|
|
client.endpoint = endpoint
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Invoke sends the raw HTTP request for ECS services
|
|
func (client *Client) Invoke(action string, args interface{}, response interface{}) error {
|
|
if err := client.ensureProperties(); err != nil {
|
|
return err
|
|
}
|
|
|
|
//init endpoint
|
|
if err := client.initEndpoint(); err != nil {
|
|
return err
|
|
}
|
|
|
|
request := Request{}
|
|
request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID)
|
|
|
|
query := util.ConvertToQueryValues(request)
|
|
util.SetQueryValues(args, &query)
|
|
|
|
// Sign request
|
|
signature := util.CreateSignatureForRequest(ECSRequestMethod, &query, client.AccessKeySecret)
|
|
|
|
// Generate the request URL
|
|
requestURL := client.endpoint + "?" + query.Encode() + "&Signature=" + url.QueryEscape(signature)
|
|
|
|
httpReq, err := http.NewRequest(ECSRequestMethod, requestURL, nil)
|
|
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
|
|
// TODO move to util and add build val flag
|
|
httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo)
|
|
|
|
httpReq.Header.Set("User-Agent", httpReq.UserAgent()+" "+client.userAgent)
|
|
|
|
t0 := time.Now()
|
|
httpResp, err := client.httpClient.Do(httpReq)
|
|
t1 := time.Now()
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
statusCode := httpResp.StatusCode
|
|
|
|
if client.debug {
|
|
log.Printf("Invoke %s %s %d (%v)", ECSRequestMethod, requestURL, statusCode, t1.Sub(t0))
|
|
}
|
|
|
|
defer httpResp.Body.Close()
|
|
body, err := ioutil.ReadAll(httpResp.Body)
|
|
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
|
|
if client.debug {
|
|
var prettyJSON bytes.Buffer
|
|
err = json.Indent(&prettyJSON, body, "", " ")
|
|
log.Println(string(prettyJSON.Bytes()))
|
|
}
|
|
|
|
if statusCode >= 400 && statusCode <= 599 {
|
|
errorResponse := ErrorResponse{}
|
|
err = json.Unmarshal(body, &errorResponse)
|
|
ecsError := &Error{
|
|
ErrorResponse: errorResponse,
|
|
StatusCode: statusCode,
|
|
}
|
|
return ecsError
|
|
}
|
|
|
|
err = json.Unmarshal(body, response)
|
|
//log.Printf("%++v", response)
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Invoke sends the raw HTTP request for ECS services
|
|
func (client *Client) InvokeByFlattenMethod(action string, args interface{}, response interface{}) error {
|
|
if err := client.ensureProperties(); err != nil {
|
|
return err
|
|
}
|
|
|
|
//init endpoint
|
|
if err := client.initEndpoint(); err != nil {
|
|
return err
|
|
}
|
|
|
|
request := Request{}
|
|
request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID)
|
|
|
|
query := util.ConvertToQueryValues(request)
|
|
|
|
util.SetQueryValueByFlattenMethod(args, &query)
|
|
|
|
// Sign request
|
|
signature := util.CreateSignatureForRequest(ECSRequestMethod, &query, client.AccessKeySecret)
|
|
|
|
// Generate the request URL
|
|
requestURL := client.endpoint + "?" + query.Encode() + "&Signature=" + url.QueryEscape(signature)
|
|
|
|
httpReq, err := http.NewRequest(ECSRequestMethod, requestURL, nil)
|
|
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
|
|
// TODO move to util and add build val flag
|
|
httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo)
|
|
|
|
httpReq.Header.Set("User-Agent", httpReq.UserAgent()+" "+client.userAgent)
|
|
|
|
t0 := time.Now()
|
|
httpResp, err := client.httpClient.Do(httpReq)
|
|
t1 := time.Now()
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
statusCode := httpResp.StatusCode
|
|
|
|
if client.debug {
|
|
log.Printf("Invoke %s %s %d (%v)", ECSRequestMethod, requestURL, statusCode, t1.Sub(t0))
|
|
}
|
|
|
|
defer httpResp.Body.Close()
|
|
body, err := ioutil.ReadAll(httpResp.Body)
|
|
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
|
|
if client.debug {
|
|
var prettyJSON bytes.Buffer
|
|
err = json.Indent(&prettyJSON, body, "", " ")
|
|
log.Println(string(prettyJSON.Bytes()))
|
|
}
|
|
|
|
if statusCode >= 400 && statusCode <= 599 {
|
|
errorResponse := ErrorResponse{}
|
|
err = json.Unmarshal(body, &errorResponse)
|
|
ecsError := &Error{
|
|
ErrorResponse: errorResponse,
|
|
StatusCode: statusCode,
|
|
}
|
|
return ecsError
|
|
}
|
|
|
|
err = json.Unmarshal(body, response)
|
|
//log.Printf("%++v", response)
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Invoke sends the raw HTTP request for ECS services
|
|
//改进了一下上面那个方法,可以使用各种Http方法
|
|
//2017.1.30 增加了一个path参数,用来拓展访问的地址
|
|
func (client *Client) InvokeByAnyMethod(method, action, path string, args interface{}, response interface{}) error {
|
|
if err := client.ensureProperties(); err != nil {
|
|
return err
|
|
}
|
|
|
|
//init endpoint
|
|
if err := client.initEndpoint(); err != nil {
|
|
return err
|
|
}
|
|
|
|
request := Request{}
|
|
request.init(client.version, action, client.AccessKeyId, client.securityToken, client.regionID)
|
|
data := util.ConvertToQueryValues(request)
|
|
util.SetQueryValues(args, &data)
|
|
|
|
// Sign request
|
|
signature := util.CreateSignatureForRequest(method, &data, client.AccessKeySecret)
|
|
|
|
data.Add("Signature", signature)
|
|
// Generate the request URL
|
|
var (
|
|
httpReq *http.Request
|
|
err error
|
|
)
|
|
if method == http.MethodGet {
|
|
requestURL := client.endpoint + path + "?" + data.Encode()
|
|
//fmt.Println(requestURL)
|
|
httpReq, err = http.NewRequest(method, requestURL, nil)
|
|
} else {
|
|
//fmt.Println(client.endpoint + path)
|
|
httpReq, err = http.NewRequest(method, client.endpoint+path, strings.NewReader(data.Encode()))
|
|
httpReq.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
}
|
|
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
|
|
// TODO move to util and add build val flag
|
|
httpReq.Header.Set("X-SDK-Client", `AliyunGO/`+Version+client.businessInfo)
|
|
httpReq.Header.Set("User-Agent", httpReq.Header.Get("User-Agent")+" "+client.userAgent)
|
|
|
|
t0 := time.Now()
|
|
httpResp, err := client.httpClient.Do(httpReq)
|
|
t1 := time.Now()
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
statusCode := httpResp.StatusCode
|
|
|
|
if client.debug {
|
|
log.Printf("Invoke %s %s %d (%v) %v", ECSRequestMethod, client.endpoint, statusCode, t1.Sub(t0), data.Encode())
|
|
}
|
|
|
|
defer httpResp.Body.Close()
|
|
body, err := ioutil.ReadAll(httpResp.Body)
|
|
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
|
|
if client.debug {
|
|
var prettyJSON bytes.Buffer
|
|
err = json.Indent(&prettyJSON, body, "", " ")
|
|
log.Println(string(prettyJSON.Bytes()))
|
|
}
|
|
|
|
if statusCode >= 400 && statusCode <= 599 {
|
|
errorResponse := ErrorResponse{}
|
|
err = json.Unmarshal(body, &errorResponse)
|
|
ecsError := &Error{
|
|
ErrorResponse: errorResponse,
|
|
StatusCode: statusCode,
|
|
}
|
|
return ecsError
|
|
}
|
|
|
|
err = json.Unmarshal(body, response)
|
|
//log.Printf("%++v", response)
|
|
if err != nil {
|
|
return GetClientError(err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GenerateClientToken generates the Client Token with random string
|
|
func (client *Client) GenerateClientToken() string {
|
|
return util.CreateRandomString()
|
|
}
|
|
|
|
func GetClientErrorFromString(str string) error {
|
|
return &Error{
|
|
ErrorResponse: ErrorResponse{
|
|
Code: "AliyunGoClientFailure",
|
|
Message: str,
|
|
},
|
|
StatusCode: -1,
|
|
}
|
|
}
|
|
|
|
func GetClientError(err error) error {
|
|
return GetClientErrorFromString(err.Error())
|
|
}
|
|
|
|
func GetCustomError(code, message string) error {
|
|
return &Error{
|
|
ErrorResponse: ErrorResponse{
|
|
Code: code,
|
|
Message: message,
|
|
},
|
|
StatusCode: 400,
|
|
}
|
|
}
|