github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/backend/remote-state/oss/backend.go (about)

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