yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aws/aws_request.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package aws
    16  
    17  import (
    18  	"encoding/xml"
    19  	"io"
    20  	"io/ioutil"
    21  	"net/url"
    22  	"strings"
    23  
    24  	"github.com/aws/aws-sdk-go/aws"
    25  	"github.com/aws/aws-sdk-go/aws/awserr"
    26  	"github.com/aws/aws-sdk-go/aws/client"
    27  	"github.com/aws/aws-sdk-go/aws/client/metadata"
    28  	"github.com/aws/aws-sdk-go/aws/corehandlers"
    29  	"github.com/aws/aws-sdk-go/aws/request"
    30  	v4 "github.com/aws/aws-sdk-go/aws/signer/v4"
    31  	"github.com/aws/aws-sdk-go/private/protocol/query"
    32  	xj "github.com/basgys/goxml2json"
    33  
    34  	"yunion.io/x/jsonutils"
    35  	"yunion.io/x/log"
    36  	"yunion.io/x/pkg/errors"
    37  
    38  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    39  )
    40  
    41  var UnmarshalHandler = request.NamedHandler{Name: "yunion.query.Unmarshal", Fn: Unmarshal}
    42  
    43  func Unmarshal(r *request.Request) {
    44  	defer r.HTTPResponse.Body.Close()
    45  	if r.DataFilled() {
    46  		var decoder *xml.Decoder
    47  		if DEBUG {
    48  			body, err := ioutil.ReadAll(r.HTTPResponse.Body)
    49  			if err != nil {
    50  				r.Error = awserr.NewRequestFailure(
    51  					awserr.New("ioutil.ReadAll", "read response body", err),
    52  					r.HTTPResponse.StatusCode,
    53  					r.RequestID,
    54  				)
    55  				return
    56  			}
    57  			log.Debugf("response: \n%s", string(body))
    58  			decoder = xml.NewDecoder(strings.NewReader(string(body)))
    59  		} else {
    60  			decoder = xml.NewDecoder(r.HTTPResponse.Body)
    61  		}
    62  		if r.ClientInfo.ServiceID == EC2_SERVICE_ID {
    63  			err := decoder.Decode(r.Data)
    64  			if err != nil {
    65  				r.Error = awserr.NewRequestFailure(
    66  					awserr.New("SerializationError", "failed decoding EC2 Query response", err),
    67  					r.HTTPResponse.StatusCode,
    68  					r.RequestID,
    69  				)
    70  			}
    71  			return
    72  		}
    73  		for {
    74  			tok, err := decoder.Token()
    75  			if err != nil {
    76  				if err == io.EOF {
    77  					break
    78  				}
    79  				r.Error = awserr.NewRequestFailure(
    80  					awserr.New("decoder.Token()", "get token", err),
    81  					r.HTTPResponse.StatusCode,
    82  					r.RequestID,
    83  				)
    84  				return
    85  			}
    86  
    87  			if tok == nil {
    88  				break
    89  			}
    90  
    91  			switch typed := tok.(type) {
    92  			case xml.CharData:
    93  				continue
    94  			case xml.StartElement:
    95  				if typed.Name.Local == r.Operation.Name+"Result" {
    96  					err = decoder.DecodeElement(r.Data, &typed)
    97  					if err != nil {
    98  						r.Error = awserr.NewRequestFailure(
    99  							awserr.New("DecodeElement", "failed decoding Query response", err),
   100  							r.HTTPResponse.StatusCode,
   101  							r.RequestID,
   102  						)
   103  					}
   104  					return
   105  				}
   106  			case xml.EndElement:
   107  				break
   108  			}
   109  		}
   110  
   111  	}
   112  }
   113  
   114  var buildHandler = request.NamedHandler{Name: "yunion.query.Build", Fn: Build}
   115  
   116  func Build(r *request.Request) {
   117  	body := url.Values{
   118  		"Action":  {r.Operation.Name},
   119  		"Version": {r.ClientInfo.APIVersion},
   120  	}
   121  	if r.Params != nil {
   122  		if params, ok := r.Params.(map[string]string); ok {
   123  			for k, v := range params {
   124  				body.Add(k, v)
   125  			}
   126  		}
   127  	}
   128  
   129  	if DEBUG {
   130  		log.Debugf("params: %s", body.Encode())
   131  	}
   132  
   133  	if !r.IsPresigned() {
   134  		r.HTTPRequest.Method = "POST"
   135  		r.HTTPRequest.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=utf-8")
   136  		r.SetBufferBody([]byte(body.Encode()))
   137  	} else { // This is a pre-signed request
   138  		r.HTTPRequest.Method = "GET"
   139  		r.HTTPRequest.URL.RawQuery = body.Encode()
   140  	}
   141  }
   142  
   143  var UnmarshalErrorHandler = request.NamedHandler{Name: "awssdk.ec2query.UnmarshalError", Fn: UnmarshalError}
   144  
   145  type sAwsError struct {
   146  	Errors struct {
   147  		Type    string
   148  		Code    string
   149  		Message string
   150  	} `json:"Error"`
   151  	RequestID string
   152  }
   153  
   154  func (self sAwsError) Error() string {
   155  	return jsonutils.Marshal(self).String()
   156  }
   157  
   158  func UnmarshalError(r *request.Request) {
   159  	defer r.HTTPResponse.Body.Close()
   160  
   161  	result, err := xj.Convert(r.HTTPResponse.Body)
   162  	if err != nil {
   163  		r.Error = errors.Wrapf(err, "xj.Convert")
   164  		return
   165  	}
   166  
   167  	obj, err := jsonutils.Parse([]byte(result.String()))
   168  	if err != nil {
   169  		r.Error = errors.Wrapf(err, "jsonutils.Parse")
   170  		return
   171  	}
   172  
   173  	respErr := &sAwsError{}
   174  	if obj.Contains("ErrorResponse") {
   175  		err = obj.Unmarshal(respErr, "ErrorResponse")
   176  		if err != nil {
   177  			r.Error = errors.Wrapf(err, "obj.Unmarshal")
   178  			return
   179  		}
   180  	} else if obj.Contains("Response", "Errors") {
   181  		err = obj.Unmarshal(respErr, "Response", "Errors")
   182  		if err != nil {
   183  			r.Error = errors.Wrapf(err, "obj.Unmarshal")
   184  			return
   185  		}
   186  	}
   187  
   188  	if strings.Contains(respErr.Errors.Code, "NotFound") || respErr.Errors.Code == "NoSuchEntity" {
   189  		r.Error = errors.Wrapf(cloudprovider.ErrNotFound, jsonutils.Marshal(respErr).String())
   190  		return
   191  	}
   192  
   193  	r.Error = respErr
   194  	return
   195  }
   196  
   197  func (self *SAwsClient) request(regionId, serviceName, serviceId, apiVersion string, apiName string, params map[string]string, retval interface{}, assumeRole bool) error {
   198  	if len(regionId) == 0 {
   199  		regionId = self.getDefaultRegionId()
   200  	}
   201  	session, err := self.getAwsSession(regionId, assumeRole)
   202  	if err != nil {
   203  		return err
   204  	}
   205  	c := session.ClientConfig(serviceName)
   206  	metadata := metadata.ClientInfo{
   207  		ServiceName:   serviceName,
   208  		ServiceID:     serviceId,
   209  		SigningName:   c.SigningName,
   210  		SigningRegion: c.SigningRegion,
   211  		Endpoint:      c.Endpoint,
   212  		APIVersion:    apiVersion,
   213  	}
   214  
   215  	if self.debug {
   216  		logLevel := aws.LogLevelType(uint(aws.LogDebugWithRequestErrors) + uint(aws.LogDebugWithHTTPBody))
   217  		c.Config.LogLevel = &logLevel
   218  	}
   219  
   220  	client := client.New(*c.Config, metadata, c.Handlers)
   221  	client.Handlers.Sign.PushBackNamed(v4.SignRequestHandler)
   222  	client.Handlers.Build.PushBackNamed(buildHandler)
   223  	client.Handlers.Unmarshal.PushBackNamed(UnmarshalHandler)
   224  	client.Handlers.UnmarshalMeta.PushBackNamed(query.UnmarshalMetaHandler)
   225  	client.Handlers.UnmarshalError.PushBackNamed(UnmarshalErrorHandler)
   226  	client.Handlers.Validate.Remove(corehandlers.ValidateEndpointHandler)
   227  	return jsonRequest(client, apiName, params, retval, true)
   228  }
   229  
   230  func jsonRequest(cli *client.Client, apiName string, params map[string]string, retval interface{}, debug bool) error {
   231  	op := &request.Operation{
   232  		Name:       apiName,
   233  		HTTPMethod: "POST",
   234  		HTTPPath:   "/",
   235  		Paginator: &request.Paginator{
   236  			InputTokens:     []string{"NextToken"},
   237  			OutputTokens:    []string{"NextToken"},
   238  			LimitToken:      "MaxResults",
   239  			TruncationToken: "",
   240  		},
   241  	}
   242  
   243  	req := cli.NewRequest(op, params, retval)
   244  	err := req.Send()
   245  	if err != nil {
   246  		if e, ok := err.(awserr.RequestFailure); ok && e.StatusCode() == 404 {
   247  			return cloudprovider.ErrNotFound
   248  		}
   249  		return err
   250  	}
   251  	return nil
   252  }