github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/backend/remote-state/oss/backend.go (about)

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