github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/backend/remote-state/oss/backend.go (about)

     1  package oss
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
     8  	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
     9  	"github.com/aliyun/alibaba-cloud-sdk-go/services/sts"
    10  	"github.com/aliyun/aliyun-oss-go-sdk/oss"
    11  	"github.com/hashicorp/terraform/backend"
    12  	"github.com/hashicorp/terraform/helper/schema"
    13  	"github.com/hashicorp/terraform/helper/validation"
    14  	"github.com/jmespath/go-jmespath"
    15  	"io/ioutil"
    16  	"os"
    17  	"runtime"
    18  	"strings"
    19  
    20  	"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
    21  	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
    22  	"github.com/aliyun/alibaba-cloud-sdk-go/services/location"
    23  	"github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
    24  	"github.com/hashicorp/go-cleanhttp"
    25  	"github.com/hashicorp/terraform/version"
    26  	"github.com/mitchellh/go-homedir"
    27  	"log"
    28  	"net/http"
    29  	"strconv"
    30  	"time"
    31  )
    32  
    33  // New creates a new backend for OSS remote state.
    34  func New() backend.Backend {
    35  	s := &schema.Backend{
    36  		Schema: map[string]*schema.Schema{
    37  			"access_key": &schema.Schema{
    38  				Type:        schema.TypeString,
    39  				Optional:    true,
    40  				Description: "Alibaba Cloud Access Key ID",
    41  				DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ACCESS_KEY", os.Getenv("ALICLOUD_ACCESS_KEY_ID")),
    42  			},
    43  
    44  			"secret_key": &schema.Schema{
    45  				Type:        schema.TypeString,
    46  				Optional:    true,
    47  				Description: "Alibaba Cloud Access Secret Key",
    48  				DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SECRET_KEY", os.Getenv("ALICLOUD_ACCESS_KEY_SECRET")),
    49  			},
    50  
    51  			"security_token": &schema.Schema{
    52  				Type:        schema.TypeString,
    53  				Optional:    true,
    54  				Description: "Alibaba Cloud Security Token",
    55  				DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SECURITY_TOKEN", ""),
    56  			},
    57  
    58  			"ecs_role_name": {
    59  				Type:        schema.TypeString,
    60  				Optional:    true,
    61  				DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ECS_ROLE_NAME", os.Getenv("ALICLOUD_ECS_ROLE_NAME")),
    62  				Description: "The RAM Role Name attached on a ECS instance for API operations. You can retrieve this from the 'Access Control' section of the Alibaba Cloud console.",
    63  			},
    64  
    65  			"region": &schema.Schema{
    66  				Type:        schema.TypeString,
    67  				Optional:    true,
    68  				Description: "The region of the OSS bucket.",
    69  				DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_REGION", os.Getenv("ALICLOUD_DEFAULT_REGION")),
    70  			},
    71  			"tablestore_endpoint": {
    72  				Type:        schema.TypeString,
    73  				Optional:    true,
    74  				Description: "A custom endpoint for the TableStore API",
    75  				DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_TABLESTORE_ENDPOINT", ""),
    76  			},
    77  			"endpoint": {
    78  				Type:        schema.TypeString,
    79  				Optional:    true,
    80  				Description: "A custom endpoint for the OSS API",
    81  				DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_OSS_ENDPOINT", os.Getenv("OSS_ENDPOINT")),
    82  			},
    83  
    84  			"bucket": &schema.Schema{
    85  				Type:        schema.TypeString,
    86  				Required:    true,
    87  				Description: "The name of the OSS bucket",
    88  			},
    89  
    90  			"prefix": &schema.Schema{
    91  				Type:        schema.TypeString,
    92  				Optional:    true,
    93  				Description: "The directory where state files will be saved inside the bucket",
    94  				Default:     "env:",
    95  				ValidateFunc: func(v interface{}, s string) ([]string, []error) {
    96  					prefix := v.(string)
    97  					if strings.HasPrefix(prefix, "/") || strings.HasPrefix(prefix, "./") {
    98  						return nil, []error{fmt.Errorf("workspace_key_prefix must not start with '/' or './'")}
    99  					}
   100  					return nil, nil
   101  				},
   102  			},
   103  
   104  			"key": &schema.Schema{
   105  				Type:        schema.TypeString,
   106  				Optional:    true,
   107  				Description: "The path of the state file inside the bucket",
   108  				ValidateFunc: func(v interface{}, s string) ([]string, []error) {
   109  					if strings.HasPrefix(v.(string), "/") || strings.HasSuffix(v.(string), "/") {
   110  						return nil, []error{fmt.Errorf("key can not start and end with '/'")}
   111  					}
   112  					return nil, nil
   113  				},
   114  				Default: "terraform.tfstate",
   115  			},
   116  
   117  			"tablestore_table": {
   118  				Type:        schema.TypeString,
   119  				Optional:    true,
   120  				Description: "TableStore table for state locking and consistency",
   121  				Default:     "",
   122  			},
   123  
   124  			"encrypt": &schema.Schema{
   125  				Type:        schema.TypeBool,
   126  				Optional:    true,
   127  				Description: "Whether to enable server side encryption of the state file",
   128  				Default:     false,
   129  			},
   130  
   131  			"acl": &schema.Schema{
   132  				Type:        schema.TypeString,
   133  				Optional:    true,
   134  				Description: "Object ACL to be applied to the state file",
   135  				Default:     "",
   136  				ValidateFunc: func(v interface{}, k string) ([]string, []error) {
   137  					if value := v.(string); value != "" {
   138  						acls := oss.ACLType(value)
   139  						if acls != oss.ACLPrivate && acls != oss.ACLPublicRead && acls != oss.ACLPublicReadWrite {
   140  							return nil, []error{fmt.Errorf(
   141  								"%q must be a valid ACL value , expected %s, %s or %s, got %q",
   142  								k, oss.ACLPrivate, oss.ACLPublicRead, oss.ACLPublicReadWrite, acls)}
   143  						}
   144  					}
   145  					return nil, nil
   146  				},
   147  			},
   148  
   149  			"assume_role": assumeRoleSchema(),
   150  			"shared_credentials_file": {
   151  				Type:        schema.TypeString,
   152  				Optional:    true,
   153  				DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SHARED_CREDENTIALS_FILE", ""),
   154  				Description: "This is the path to the shared credentials file. If this is not set and a profile is specified, `~/.aliyun/config.json` will be used.",
   155  			},
   156  			"profile": {
   157  				Type:        schema.TypeString,
   158  				Optional:    true,
   159  				Description: "This is the Alibaba Cloud profile name as set in the shared credentials file. It can also be sourced from the `ALICLOUD_PROFILE` environment variable.",
   160  				DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_PROFILE", ""),
   161  			},
   162  		},
   163  	}
   164  
   165  	result := &Backend{Backend: s}
   166  	result.Backend.ConfigureFunc = result.configure
   167  	return result
   168  }
   169  
   170  func assumeRoleSchema() *schema.Schema {
   171  	return &schema.Schema{
   172  		Type:     schema.TypeSet,
   173  		Optional: true,
   174  		MaxItems: 1,
   175  		Elem: &schema.Resource{
   176  			Schema: map[string]*schema.Schema{
   177  				"role_arn": {
   178  					Type:        schema.TypeString,
   179  					Required:    true,
   180  					Description: "The ARN of a RAM role to assume prior to making API calls.",
   181  					DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_ARN", ""),
   182  				},
   183  				"session_name": {
   184  					Type:        schema.TypeString,
   185  					Optional:    true,
   186  					Description: "The session name to use when assuming the role.",
   187  					DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_SESSION_NAME", ""),
   188  				},
   189  				"policy": {
   190  					Type:        schema.TypeString,
   191  					Optional:    true,
   192  					Description: "The permissions applied when assuming a role. You cannot use this policy to grant permissions which exceed those of the role that is being assumed.",
   193  				},
   194  				"session_expiration": {
   195  					Type:         schema.TypeInt,
   196  					Optional:     true,
   197  					Description:  "The time after which the established session for assuming role expires.",
   198  					ValidateFunc: validation.IntBetween(900, 3600),
   199  				},
   200  			},
   201  		},
   202  	}
   203  }
   204  
   205  type Backend struct {
   206  	*schema.Backend
   207  
   208  	// The fields below are set from configure
   209  	ossClient *oss.Client
   210  	otsClient *tablestore.TableStoreClient
   211  
   212  	bucketName           string
   213  	statePrefix          string
   214  	stateKey             string
   215  	serverSideEncryption bool
   216  	acl                  string
   217  	endpoint             string
   218  	otsEndpoint          string
   219  	otsTable             string
   220  }
   221  
   222  func (b *Backend) configure(ctx context.Context) error {
   223  	if b.ossClient != nil {
   224  		return nil
   225  	}
   226  
   227  	// Grab the resource data
   228  	d := schema.FromContextBackendConfig(ctx)
   229  
   230  	b.bucketName = d.Get("bucket").(string)
   231  	b.statePrefix = strings.TrimPrefix(strings.Trim(d.Get("prefix").(string), "/"), "./")
   232  	b.stateKey = d.Get("key").(string)
   233  	b.serverSideEncryption = d.Get("encrypt").(bool)
   234  	b.acl = d.Get("acl").(string)
   235  
   236  	var getBackendConfig = func(str string, key string) string {
   237  		if str == "" {
   238  			value, err := getConfigFromProfile(d, key)
   239  			if err == nil && value != nil {
   240  				str = value.(string)
   241  			}
   242  		}
   243  		return str
   244  	}
   245  
   246  	accessKey := getBackendConfig(d.Get("access_key").(string), "access_key_id")
   247  	secretKey := getBackendConfig(d.Get("secret_key").(string), "access_key_secret")
   248  	securityToken := getBackendConfig(d.Get("security_token").(string), "sts_token")
   249  	region := getBackendConfig(d.Get("region").(string), "region_id")
   250  
   251  	endpoint := d.Get("endpoint").(string)
   252  	schma := "https"
   253  
   254  	roleArn := getBackendConfig("", "ram_role_arn")
   255  	sessionName := getBackendConfig("", "ram_session_name")
   256  	var policy string
   257  	var sessionExpiration int
   258  	expiredSeconds, err := getConfigFromProfile(d, "expired_seconds")
   259  	if err == nil && expiredSeconds != nil {
   260  		sessionExpiration = (int)(expiredSeconds.(float64))
   261  	}
   262  
   263  	if v, ok := d.GetOk("assume_role"); ok {
   264  		for _, v := range v.(*schema.Set).List() {
   265  			assumeRole := v.(map[string]interface{})
   266  			if assumeRole["role_arn"].(string) != "" {
   267  				roleArn = assumeRole["role_arn"].(string)
   268  			}
   269  			if assumeRole["session_name"].(string) != "" {
   270  				sessionName = assumeRole["session_name"].(string)
   271  			}
   272  			if sessionName == "" {
   273  				sessionName = "terraform"
   274  			}
   275  			policy = assumeRole["policy"].(string)
   276  			sessionExpiration = assumeRole["session_expiration"].(int)
   277  			if sessionExpiration == 0 {
   278  				if v := os.Getenv("ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION"); v != "" {
   279  					if expiredSeconds, err := strconv.Atoi(v); err == nil {
   280  						sessionExpiration = expiredSeconds
   281  					}
   282  				}
   283  				if sessionExpiration == 0 {
   284  					sessionExpiration = 3600
   285  				}
   286  			}
   287  		}
   288  	}
   289  
   290  	if accessKey == "" {
   291  		ecsRoleName := getBackendConfig(d.Get("ecs_role_name").(string), "ram_role_name")
   292  		subAccessKeyId, subAccessKeySecret, subSecurityToken, err := getAuthCredentialByEcsRoleName(ecsRoleName)
   293  		if err != nil {
   294  			return err
   295  		}
   296  		accessKey, secretKey, securityToken = subAccessKeyId, subAccessKeySecret, subSecurityToken
   297  	}
   298  
   299  	if roleArn != "" {
   300  		subAccessKeyId, subAccessKeySecret, subSecurityToken, err := getAssumeRoleAK(accessKey, secretKey, securityToken, region, roleArn, sessionName, policy, sessionExpiration)
   301  		if err != nil {
   302  			return err
   303  		}
   304  		accessKey, secretKey, securityToken = subAccessKeyId, subAccessKeySecret, subSecurityToken
   305  	}
   306  
   307  	if endpoint == "" {
   308  		endpointItem, _ := b.getOSSEndpointByRegion(accessKey, secretKey, securityToken, region)
   309  		if endpointItem != nil && len(endpointItem.Endpoint) > 0 {
   310  			if len(endpointItem.Protocols.Protocols) > 0 {
   311  				// HTTP or HTTPS
   312  				schma = strings.ToLower(endpointItem.Protocols.Protocols[0])
   313  				for _, p := range endpointItem.Protocols.Protocols {
   314  					if strings.ToLower(p) == "https" {
   315  						schma = strings.ToLower(p)
   316  						break
   317  					}
   318  				}
   319  			}
   320  			endpoint = endpointItem.Endpoint
   321  		} else {
   322  			endpoint = fmt.Sprintf("oss-%s.aliyuncs.com", region)
   323  		}
   324  	}
   325  	if !strings.HasPrefix(endpoint, "http") {
   326  		endpoint = fmt.Sprintf("%s://%s", schma, endpoint)
   327  	}
   328  	log.Printf("[DEBUG] Instantiate OSS client using endpoint: %#v", endpoint)
   329  	var options []oss.ClientOption
   330  	if securityToken != "" {
   331  		options = append(options, oss.SecurityToken(securityToken))
   332  	}
   333  	options = append(options, oss.UserAgent(fmt.Sprintf("%s/%s", TerraformUA, TerraformVersion)))
   334  
   335  	client, err := oss.New(endpoint, accessKey, secretKey, options...)
   336  	b.ossClient = client
   337  	otsEndpoint := d.Get("tablestore_endpoint").(string)
   338  	if otsEndpoint != "" {
   339  		if !strings.HasPrefix(otsEndpoint, "http") {
   340  			otsEndpoint = fmt.Sprintf("%s://%s", schma, otsEndpoint)
   341  		}
   342  		b.otsEndpoint = otsEndpoint
   343  		parts := strings.Split(strings.TrimPrefix(strings.TrimPrefix(otsEndpoint, "https://"), "http://"), ".")
   344  		b.otsClient = tablestore.NewClientWithConfig(otsEndpoint, parts[0], accessKey, secretKey, securityToken, tablestore.NewDefaultTableStoreConfig())
   345  	}
   346  	b.otsTable = d.Get("tablestore_table").(string)
   347  
   348  	return err
   349  }
   350  
   351  func (b *Backend) getOSSEndpointByRegion(access_key, secret_key, security_token, region string) (*location.DescribeEndpointResponse, error) {
   352  	args := location.CreateDescribeEndpointRequest()
   353  	args.ServiceCode = "oss"
   354  	args.Id = region
   355  	args.Domain = "location-readonly.aliyuncs.com"
   356  
   357  	locationClient, err := location.NewClientWithOptions(region, getSdkConfig(), credentials.NewStsTokenCredential(access_key, secret_key, security_token))
   358  	if err != nil {
   359  		return nil, fmt.Errorf("Unable to initialize the location client: %#v", err)
   360  
   361  	}
   362  	locationClient.AppendUserAgent(TerraformUA, TerraformVersion)
   363  	endpointsResponse, err := locationClient.DescribeEndpoint(args)
   364  	if err != nil {
   365  		return nil, fmt.Errorf("Describe oss endpoint using region: %#v got an error: %#v.", region, err)
   366  	}
   367  	return endpointsResponse, nil
   368  }
   369  
   370  func getAssumeRoleAK(accessKey, secretKey, stsToken, region, roleArn, sessionName, policy string, sessionExpiration int) (string, string, string, error) {
   371  	request := sts.CreateAssumeRoleRequest()
   372  	request.RoleArn = roleArn
   373  	request.RoleSessionName = sessionName
   374  	request.DurationSeconds = requests.NewInteger(sessionExpiration)
   375  	request.Policy = policy
   376  	request.Scheme = "https"
   377  
   378  	var client *sts.Client
   379  	var err error
   380  	if stsToken == "" {
   381  		client, err = sts.NewClientWithAccessKey(region, accessKey, secretKey)
   382  	} else {
   383  		client, err = sts.NewClientWithStsToken(region, accessKey, secretKey, stsToken)
   384  	}
   385  	if err != nil {
   386  		return "", "", "", err
   387  	}
   388  	response, err := client.AssumeRole(request)
   389  	if err != nil {
   390  		return "", "", "", err
   391  	}
   392  	return response.Credentials.AccessKeyId, response.Credentials.AccessKeySecret, response.Credentials.SecurityToken, nil
   393  }
   394  
   395  func getSdkConfig() *sdk.Config {
   396  	return sdk.NewConfig().
   397  		WithMaxRetryTime(5).
   398  		WithTimeout(time.Duration(30) * time.Second).
   399  		WithGoRoutinePoolSize(10).
   400  		WithDebug(false).
   401  		WithHttpTransport(getTransport()).
   402  		WithScheme("HTTPS")
   403  }
   404  
   405  func getTransport() *http.Transport {
   406  	handshakeTimeout, err := strconv.Atoi(os.Getenv("TLSHandshakeTimeout"))
   407  	if err != nil {
   408  		handshakeTimeout = 120
   409  	}
   410  	transport := cleanhttp.DefaultTransport()
   411  	transport.TLSHandshakeTimeout = time.Duration(handshakeTimeout) * time.Second
   412  	transport.Proxy = http.ProxyFromEnvironment
   413  	return transport
   414  }
   415  
   416  type Invoker struct {
   417  	catchers []*Catcher
   418  }
   419  
   420  type Catcher struct {
   421  	Reason           string
   422  	RetryCount       int
   423  	RetryWaitSeconds int
   424  }
   425  
   426  const TerraformUA = "HashiCorp-Terraform"
   427  
   428  var TerraformVersion = strings.TrimSuffix(version.String(), "-dev")
   429  var ClientErrorCatcher = Catcher{"AliyunGoClientFailure", 10, 3}
   430  var ServiceBusyCatcher = Catcher{"ServiceUnavailable", 10, 3}
   431  
   432  func NewInvoker() Invoker {
   433  	i := Invoker{}
   434  	i.AddCatcher(ClientErrorCatcher)
   435  	i.AddCatcher(ServiceBusyCatcher)
   436  	return i
   437  }
   438  
   439  func (a *Invoker) AddCatcher(catcher Catcher) {
   440  	a.catchers = append(a.catchers, &catcher)
   441  }
   442  
   443  func (a *Invoker) Run(f func() error) error {
   444  	err := f()
   445  
   446  	if err == nil {
   447  		return nil
   448  	}
   449  
   450  	for _, catcher := range a.catchers {
   451  		if strings.Contains(err.Error(), catcher.Reason) {
   452  			catcher.RetryCount--
   453  
   454  			if catcher.RetryCount <= 0 {
   455  				return fmt.Errorf("Retry timeout and got an error: %#v.", err)
   456  			} else {
   457  				time.Sleep(time.Duration(catcher.RetryWaitSeconds) * time.Second)
   458  				return a.Run(f)
   459  			}
   460  		}
   461  	}
   462  	return err
   463  }
   464  
   465  var providerConfig map[string]interface{}
   466  
   467  func getConfigFromProfile(d *schema.ResourceData, ProfileKey string) (interface{}, error) {
   468  
   469  	if providerConfig == nil {
   470  		if v, ok := d.GetOk("profile"); !ok || v.(string) == "" {
   471  			return nil, nil
   472  		}
   473  		current := d.Get("profile").(string)
   474  		// Set CredsFilename, expanding home directory
   475  		profilePath, err := homedir.Expand(d.Get("shared_credentials_file").(string))
   476  		if err != nil {
   477  			return nil, err
   478  		}
   479  		if profilePath == "" {
   480  			profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("HOME"))
   481  			if runtime.GOOS == "windows" {
   482  				profilePath = fmt.Sprintf("%s/.aliyun/config.json", os.Getenv("USERPROFILE"))
   483  			}
   484  		}
   485  		providerConfig = make(map[string]interface{})
   486  		_, err = os.Stat(profilePath)
   487  		if !os.IsNotExist(err) {
   488  			data, err := ioutil.ReadFile(profilePath)
   489  			if err != nil {
   490  				return nil, err
   491  			}
   492  			config := map[string]interface{}{}
   493  			err = json.Unmarshal(data, &config)
   494  			if err != nil {
   495  				return nil, err
   496  			}
   497  			for _, v := range config["profiles"].([]interface{}) {
   498  				if current == v.(map[string]interface{})["name"] {
   499  					providerConfig = v.(map[string]interface{})
   500  				}
   501  			}
   502  		}
   503  	}
   504  
   505  	mode := ""
   506  	if v, ok := providerConfig["mode"]; ok {
   507  		mode = v.(string)
   508  	} else {
   509  		return v, nil
   510  	}
   511  	switch ProfileKey {
   512  	case "access_key_id", "access_key_secret":
   513  		if mode == "EcsRamRole" {
   514  			return "", nil
   515  		}
   516  	case "ram_role_name":
   517  		if mode != "EcsRamRole" {
   518  			return "", nil
   519  		}
   520  	case "sts_token":
   521  		if mode != "StsToken" {
   522  			return "", nil
   523  		}
   524  	case "ram_role_arn", "ram_session_name":
   525  		if mode != "RamRoleArn" {
   526  			return "", nil
   527  		}
   528  	case "expired_seconds":
   529  		if mode != "RamRoleArn" {
   530  			return float64(0), nil
   531  		}
   532  	}
   533  
   534  	return providerConfig[ProfileKey], nil
   535  }
   536  
   537  var securityCredURL = "http://100.100.100.200/latest/meta-data/ram/security-credentials/"
   538  
   539  // getAuthCredentialByEcsRoleName aims to access meta to get sts credential
   540  // Actually, the job should be done by sdk, but currently not all resources and products support alibaba-cloud-sdk-go,
   541  // and their go sdk does support ecs role name.
   542  // This method is a temporary solution and it should be removed after all go sdk support ecs role name
   543  // The related PR: https://github.com/terraform-providers/terraform-provider-alicloud/pull/731
   544  func getAuthCredentialByEcsRoleName(ecsRoleName string) (accessKey, secretKey, token string, err error) {
   545  
   546  	if ecsRoleName == "" {
   547  		return
   548  	}
   549  	requestUrl := securityCredURL + ecsRoleName
   550  	httpRequest, err := http.NewRequest(requests.GET, requestUrl, strings.NewReader(""))
   551  	if err != nil {
   552  		err = fmt.Errorf("build sts requests err: %s", err.Error())
   553  		return
   554  	}
   555  	httpClient := &http.Client{}
   556  	httpResponse, err := httpClient.Do(httpRequest)
   557  	if err != nil {
   558  		err = fmt.Errorf("get Ecs sts token err : %s", err.Error())
   559  		return
   560  	}
   561  
   562  	response := responses.NewCommonResponse()
   563  	err = responses.Unmarshal(response, httpResponse, "")
   564  	if err != nil {
   565  		err = fmt.Errorf("Unmarshal Ecs sts token response err : %s", err.Error())
   566  		return
   567  	}
   568  
   569  	if response.GetHttpStatus() != http.StatusOK {
   570  		err = fmt.Errorf("get Ecs sts token err, httpStatus: %d, message = %s", response.GetHttpStatus(), response.GetHttpContentString())
   571  		return
   572  	}
   573  	var data interface{}
   574  	err = json.Unmarshal(response.GetHttpContentBytes(), &data)
   575  	if err != nil {
   576  		err = fmt.Errorf("refresh Ecs sts token err, json.Unmarshal fail: %s", err.Error())
   577  		return
   578  	}
   579  	code, err := jmespath.Search("Code", data)
   580  	if err != nil {
   581  		err = fmt.Errorf("refresh Ecs sts token err, fail to get Code: %s", err.Error())
   582  		return
   583  	}
   584  	if code.(string) != "Success" {
   585  		err = fmt.Errorf("refresh Ecs sts token err, Code is not Success")
   586  		return
   587  	}
   588  	accessKeyId, err := jmespath.Search("AccessKeyId", data)
   589  	if err != nil {
   590  		err = fmt.Errorf("refresh Ecs sts token err, fail to get AccessKeyId: %s", err.Error())
   591  		return
   592  	}
   593  	accessKeySecret, err := jmespath.Search("AccessKeySecret", data)
   594  	if err != nil {
   595  		err = fmt.Errorf("refresh Ecs sts token err, fail to get AccessKeySecret: %s", err.Error())
   596  		return
   597  	}
   598  	securityToken, err := jmespath.Search("SecurityToken", data)
   599  	if err != nil {
   600  		err = fmt.Errorf("refresh Ecs sts token err, fail to get SecurityToken: %s", err.Error())
   601  		return
   602  	}
   603  
   604  	if accessKeyId == nil || accessKeySecret == nil || securityToken == nil {
   605  		err = fmt.Errorf("there is no any available accesskey, secret and security token for Ecs role %s", ecsRoleName)
   606  		return
   607  	}
   608  
   609  	return accessKeyId.(string), accessKeySecret.(string), securityToken.(string), nil
   610  }