diff --git a/internal/backend/remote-state/oss/backend.go b/internal/backend/remote-state/oss/backend.go index 5a2b2880c..c4131060e 100644 --- a/internal/backend/remote-state/oss/backend.go +++ b/internal/backend/remote-state/oss/backend.go @@ -24,32 +24,84 @@ import ( "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore" "github.com/hashicorp/go-cleanhttp" + "github.com/jmespath/go-jmespath" + "github.com/mitchellh/go-homedir" + "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/legacy/helper/schema" "github.com/hashicorp/terraform/version" - "github.com/jmespath/go-jmespath" - "github.com/mitchellh/go-homedir" ) +// deprecated in favor to flatten parameters +func deprecatedAssumeRoleSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + ConflictsWith: []string{"assume_role_role_arn", "assume_role_session_name", "assume_role_policy", "assume_role_session_expiration"}, + MaxItems: 1, + Deprecated: "use flatten assume_role_* instead", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "role_arn": { + Type: schema.TypeString, + Required: true, + Description: "The ARN of a RAM role to assume prior to making API calls.", + DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_ARN", ""), + }, + "session_name": { + Type: schema.TypeString, + Optional: true, + Description: "The session name to use when assuming the role.", + DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_SESSION_NAME", ""), + }, + "policy": { + Type: schema.TypeString, + Optional: true, + 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.", + }, + "session_expiration": { + Type: schema.TypeInt, + Optional: true, + Description: "The time after which the established session for assuming role expires.", + ValidateFunc: func(v interface{}, k string) ([]string, []error) { + min := 900 + max := 3600 + value, ok := v.(int) + if !ok { + return nil, []error{fmt.Errorf("expected type of %s to be int", k)} + } + + if value < min || value > max { + return nil, []error{fmt.Errorf("expected %s to be in the range (%d - %d), got %d", k, min, max, v)} + } + + return nil, nil + }, + }, + }, + }, + } +} + // New creates a new backend for OSS remote state. func New() backend.Backend { s := &schema.Backend{ Schema: map[string]*schema.Schema{ - "access_key": &schema.Schema{ + "access_key": { Type: schema.TypeString, Optional: true, Description: "Alibaba Cloud Access Key ID", DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ACCESS_KEY", os.Getenv("ALICLOUD_ACCESS_KEY_ID")), }, - "secret_key": &schema.Schema{ + "secret_key": { Type: schema.TypeString, Optional: true, Description: "Alibaba Cloud Access Secret Key", DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SECRET_KEY", os.Getenv("ALICLOUD_ACCESS_KEY_SECRET")), }, - "security_token": &schema.Schema{ + "security_token": { Type: schema.TypeString, Optional: true, Description: "Alibaba Cloud Security Token", @@ -63,7 +115,7 @@ func New() backend.Backend { 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.", }, - "region": &schema.Schema{ + "region": { Type: schema.TypeString, Optional: true, Description: "The region of the OSS bucket.", @@ -82,13 +134,13 @@ func New() backend.Backend { DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_OSS_ENDPOINT", os.Getenv("OSS_ENDPOINT")), }, - "bucket": &schema.Schema{ + "bucket": { Type: schema.TypeString, Required: true, Description: "The name of the OSS bucket", }, - "prefix": &schema.Schema{ + "prefix": { Type: schema.TypeString, Optional: true, Description: "The directory where state files will be saved inside the bucket", @@ -102,7 +154,7 @@ func New() backend.Backend { }, }, - "key": &schema.Schema{ + "key": { Type: schema.TypeString, Optional: true, Description: "The path of the state file inside the bucket", @@ -122,14 +174,14 @@ func New() backend.Backend { Default: "", }, - "encrypt": &schema.Schema{ + "encrypt": { Type: schema.TypeBool, Optional: true, Description: "Whether to enable server side encryption of the state file", Default: false, }, - "acl": &schema.Schema{ + "acl": { Type: schema.TypeString, Optional: true, Description: "Object ACL to be applied to the state file", @@ -158,27 +210,32 @@ func New() backend.Backend { 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.", DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_PROFILE", ""), }, + "assume_role": deprecatedAssumeRoleSchema(), "assume_role_role_arn": { - Type: schema.TypeString, - Required: true, - Description: "The ARN of a RAM role to assume prior to making API calls.", - DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_ARN", ""), + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"assume_role"}, + Description: "The ARN of a RAM role to assume prior to making API calls.", + DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_ARN", ""), }, "assume_role_session_name": { - Type: schema.TypeString, - Optional: true, - Description: "The session name to use when assuming the role.", - DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_SESSION_NAME", ""), + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"assume_role"}, + Description: "The session name to use when assuming the role.", + DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_ASSUME_ROLE_SESSION_NAME", ""), }, "assume_role_policy": { - Type: schema.TypeString, - Optional: true, - 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.", + Type: schema.TypeString, + Optional: true, + ConflictsWith: []string{"assume_role"}, + 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.", }, "assume_role_session_expiration": { - Type: schema.TypeInt, - Optional: true, - Description: "The time after which the established session for assuming role expires.", + Type: schema.TypeInt, + Optional: true, + ConflictsWith: []string{"assume_role"}, + Description: "The time after which the established session for assuming role expires.", ValidateFunc: func(v interface{}, k string) ([]string, []error) { min := 900 max := 3600 @@ -214,7 +271,6 @@ type Backend struct { stateKey string serverSideEncryption bool acl string - endpoint string otsEndpoint string otsTable string } @@ -260,13 +316,29 @@ func (b *Backend) configure(ctx context.Context) error { sessionExpiration = (int)(expiredSeconds.(float64)) } - roleArn = d.Get("assume_role_role_arn").(string) - sessionName = d.Get("assume_role_session_name").(string) + if v, ok := d.GetOk("assume_role"); ok { + // deprecated assume_role block + for _, v := range v.(*schema.Set).List() { + assumeRole := v.(map[string]interface{}) + if assumeRole["role_arn"].(string) != "" { + roleArn = assumeRole["role_arn"].(string) + } + if assumeRole["session_name"].(string) != "" { + sessionName = assumeRole["session_name"].(string) + } + policy = assumeRole["policy"].(string) + sessionExpiration = assumeRole["session_expiration"].(int) + } + } else { + roleArn = d.Get("assume_role_role_arn").(string) + sessionName = d.Get("assume_role_session_name").(string) + policy = d.Get("assume_role_policy").(string) + sessionExpiration = d.Get("assume_role_session_expiration").(int) + } + if sessionName == "" { sessionName = "terraform" } - policy = d.Get("assume_role_policy").(string) - sessionExpiration = d.Get("assume_role_session_expiration").(int) if sessionExpiration == 0 { if v := os.Getenv("ALICLOUD_ASSUME_ROLE_SESSION_EXPIRATION"); v != "" { if expiredSeconds, err := strconv.Atoi(v); err == nil { @@ -346,13 +418,13 @@ func (b *Backend) getOSSEndpointByRegion(access_key, secret_key, security_token, locationClient, err := location.NewClientWithOptions(region, getSdkConfig(), credentials.NewStsTokenCredential(access_key, secret_key, security_token)) if err != nil { - return nil, fmt.Errorf("Unable to initialize the location client: %#v", err) + return nil, fmt.Errorf("unable to initialize the location client: %#v", err) } locationClient.AppendUserAgent(TerraformUA, TerraformVersion) endpointsResponse, err := locationClient.DescribeEndpoints(args) if err != nil { - return nil, fmt.Errorf("Describe oss endpoint using region: %#v got an error: %#v.", region, err) + return nil, fmt.Errorf("describe oss endpoint using region: %#v got an error: %#v", region, err) } return endpointsResponse, nil } @@ -442,7 +514,7 @@ func (a *Invoker) Run(f func() error) error { catcher.RetryCount-- if catcher.RetryCount <= 0 { - return fmt.Errorf("Retry timeout and got an error: %#v.", err) + return fmt.Errorf("retry timeout and got an error: %#v", err) } else { time.Sleep(time.Duration(catcher.RetryWaitSeconds) * time.Second) return a.Run(f) @@ -552,7 +624,7 @@ func getAuthCredentialByEcsRoleName(ecsRoleName string) (accessKey, secretKey, t response := responses.NewCommonResponse() err = responses.Unmarshal(response, httpResponse, "") if err != nil { - err = fmt.Errorf("Unmarshal Ecs sts token response err : %s", err.Error()) + err = fmt.Errorf("unmarshal Ecs sts token response err : %s", err.Error()) return } diff --git a/internal/backend/remote-state/oss/backend_state.go b/internal/backend/remote-state/oss/backend_state.go index d91ed6c5c..d08e1d133 100644 --- a/internal/backend/remote-state/oss/backend_state.go +++ b/internal/backend/remote-state/oss/backend_state.go @@ -3,19 +3,18 @@ package oss import ( "errors" "fmt" + "log" + "path" "sort" "strings" "github.com/aliyun/aliyun-oss-go-sdk/oss" + "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore" + "github.com/hashicorp/terraform/internal/backend" "github.com/hashicorp/terraform/internal/states" "github.com/hashicorp/terraform/internal/states/remote" "github.com/hashicorp/terraform/internal/states/statemgr" - - "log" - "path" - - "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore" ) const ( @@ -43,7 +42,7 @@ func (b *Backend) remoteClient(name string) (*RemoteClient, error) { TableName: b.otsTable, }) if err != nil { - return client, fmt.Errorf("Error describing table store %s: %#v", b.otsTable, err) + return client, fmt.Errorf("error describing table store %s: %#v", b.otsTable, err) } } @@ -53,7 +52,7 @@ func (b *Backend) remoteClient(name string) (*RemoteClient, error) { func (b *Backend) Workspaces() ([]string, error) { bucket, err := b.ossClient.Bucket(b.bucketName) if err != nil { - return []string{""}, fmt.Errorf("Error getting bucket: %#v", err) + return []string{""}, fmt.Errorf("error getting bucket: %#v", err) } var options []oss.Option @@ -85,7 +84,7 @@ func (b *Backend) Workspaces() ([]string, error) { } else { options = append(options, oss.Marker(lastObj)) } - resp, err = bucket.ListObjects(options...) + bucket.ListObjects(options...) } else { break } @@ -135,7 +134,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) { lockInfo.Operation = "init" lockId, err := client.Lock(lockInfo) if err != nil { - return nil, fmt.Errorf("Failed to lock OSS state: %s", err) + return nil, fmt.Errorf("failed to lock OSS state: %s", err) } // Local helper function so we can call it multiple places diff --git a/internal/backend/remote-state/oss/client.go b/internal/backend/remote-state/oss/client.go index ccf19576a..78d835ae1 100644 --- a/internal/backend/remote-state/oss/client.go +++ b/internal/backend/remote-state/oss/client.go @@ -3,22 +3,21 @@ package oss import ( "bytes" "crypto/md5" + "encoding/hex" "encoding/json" "fmt" "io" - - "encoding/hex" "log" - "sync" "time" "github.com/aliyun/aliyun-oss-go-sdk/oss" "github.com/aliyun/aliyun-tablestore-go-sdk/tablestore" "github.com/hashicorp/go-multierror" uuid "github.com/hashicorp/go-uuid" + "github.com/pkg/errors" + "github.com/hashicorp/terraform/internal/states/remote" "github.com/hashicorp/terraform/internal/states/statemgr" - "github.com/pkg/errors" ) const ( @@ -48,8 +47,6 @@ type RemoteClient struct { lockFile string serverSideEncryption bool acl string - info *statemgr.LockInfo - mu sync.Mutex otsTable string } @@ -99,7 +96,7 @@ func (c *RemoteClient) Get() (payload *remote.Payload, err error) { func (c *RemoteClient) Put(data []byte) error { bucket, err := c.ossClient.Bucket(c.bucketName) if err != nil { - return fmt.Errorf("Error getting bucket: %#v", err) + return fmt.Errorf("error getting bucket: %#v", err) } body := bytes.NewReader(data) @@ -116,7 +113,7 @@ func (c *RemoteClient) Put(data []byte) error { if body != nil { if err := bucket.PutObject(c.stateFile, body, options...); err != nil { - return fmt.Errorf("Failed to upload state %s: %#v", c.stateFile, err) + return fmt.Errorf("failed to upload state %s: %#v", c.stateFile, err) } } @@ -124,7 +121,7 @@ func (c *RemoteClient) Put(data []byte) error { if err := c.putMD5(sum[:]); err != nil { // if this errors out, we unfortunately have to error out altogether, // since the next Get will inevitably fail. - return fmt.Errorf("Failed to store state MD5: %s", err) + return fmt.Errorf("failed to store state MD5: %s", err) } return nil } @@ -132,13 +129,13 @@ func (c *RemoteClient) Put(data []byte) error { func (c *RemoteClient) Delete() error { bucket, err := c.ossClient.Bucket(c.bucketName) if err != nil { - return fmt.Errorf("Error getting bucket %s: %#v", c.bucketName, err) + return fmt.Errorf("error getting bucket %s: %#v", c.bucketName, err) } log.Printf("[DEBUG] Deleting remote state from OSS: %#v", c.stateFile) if err := bucket.DeleteObject(c.stateFile); err != nil { - return fmt.Errorf("Error deleting state %s: %#v", c.stateFile, err) + return fmt.Errorf("error deleting state %s: %#v", c.stateFile, err) } if err := c.deleteMD5(); err != nil { @@ -413,11 +410,11 @@ func (c *RemoteClient) lockPath() string { func (c *RemoteClient) getObj() (*remote.Payload, error) { bucket, err := c.ossClient.Bucket(c.bucketName) if err != nil { - return nil, fmt.Errorf("Error getting bucket %s: %#v", c.bucketName, err) + return nil, fmt.Errorf("error getting bucket %s: %#v", c.bucketName, err) } if exist, err := bucket.IsObjectExist(c.stateFile); err != nil { - return nil, fmt.Errorf("Estimating object %s is exist got an error: %#v", c.stateFile, err) + return nil, fmt.Errorf("estimating object %s is exist got an error: %#v", c.stateFile, err) } else if !exist { return nil, nil } @@ -425,12 +422,12 @@ func (c *RemoteClient) getObj() (*remote.Payload, error) { var options []oss.Option output, err := bucket.GetObject(c.stateFile, options...) if err != nil { - return nil, fmt.Errorf("Error getting object: %#v", err) + return nil, fmt.Errorf("error getting object: %#v", err) } buf := bytes.NewBuffer(nil) if _, err := io.Copy(buf, output); err != nil { - return nil, fmt.Errorf("Failed to read remote state: %s", err) + return nil, fmt.Errorf("failed to read remote state: %s", err) } sum := md5.Sum(buf.Bytes()) payload := &remote.Payload{ @@ -452,5 +449,4 @@ This may be caused by unusually long delays in OSS processing a previous state update. Please wait for a minute or two and try again. If this problem persists, and neither OSS nor TableStore are experiencing an outage, you may need to manually verify the remote state and update the Digest value stored in the -TableStore table to the following value: %x -` +TableStore table to the following value: %x`