435c0d9fc8
This PR switches the Nomad repository from using govendor to Go modules for managing dependencies. Aspects of the Nomad workflow remain pretty much the same. The usual Makefile targets should continue to work as they always did. The API submodule simply defers to the parent Nomad version on the repository, keeping the semantics of API versioning that currently exists.
551 lines
14 KiB
Go
551 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,
|
||
}
|
||
}
|