Skip to content

Commit

Permalink
Add tablestore config to store state lock
Browse files Browse the repository at this point in the history
  • Loading branch information
xiaozhu36 committed Apr 10, 2019
1 parent b887d44 commit 3f44dd9
Show file tree
Hide file tree
Showing 134 changed files with 18,490 additions and 10,328 deletions.
9 changes: 9 additions & 0 deletions addrs/resourcemode_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions backend/local/counthookaction_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions backend/operationtype_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 50 additions & 38 deletions backend/remote-state/oss/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import (

"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/resource"
"github.com/aliyun/alibaba-cloud-sdk-go/sdk/utils"
"github.com/aliyun/alibaba-cloud-sdk-go/services/location"
"github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
"github.com/hashicorp/go-cleanhttp"
"github.com/hashicorp/terraform/version"
"log"
Expand Down Expand Up @@ -44,7 +43,7 @@ func New() backend.Backend {
Type: schema.TypeString,
Optional: true,
Description: "Alibaba Cloud Security Token",
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SECURITY_TOKEN", os.Getenv("SECURITY_TOKEN")),
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_SECURITY_TOKEN", ""),
},

"region": &schema.Schema{
Expand All @@ -53,7 +52,12 @@ func New() backend.Backend {
Description: "The region of the OSS bucket.",
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_REGION", os.Getenv("ALICLOUD_DEFAULT_REGION")),
},

"tablestore_endpoint": {
Type: schema.TypeString,
Optional: true,
Description: "A custom endpoint for the TableStore API",
DefaultFunc: schema.EnvDefaultFunc("ALICLOUD_TABLESTORE_ENDPOINT", ""),
},
"endpoint": {
Type: schema.TypeString,
Optional: true,
Expand All @@ -67,30 +71,38 @@ func New() backend.Backend {
Description: "The name of the OSS bucket",
},

"path": &schema.Schema{
"prefix": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "The path relative to your object storage directory where the state file will be stored.",
Optional: true,
Description: "The directory where state files will be saved inside the bucket",
Default: "env:",
ValidateFunc: func(v interface{}, s string) ([]string, []error) {
prefix := v.(string)
if strings.HasPrefix(prefix, "/") || strings.HasPrefix(prefix, "./") {
return nil, []error{fmt.Errorf("workspace_key_prefix must not start with '/' or './'")}
}
return nil, nil
},
},

"name": &schema.Schema{
"key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Description: "The name of the state file inside the bucket",
Description: "The path of the state file inside the bucket",
ValidateFunc: func(v interface{}, s string) ([]string, []error) {
if strings.HasPrefix(v.(string), "/") || strings.HasSuffix(v.(string), "/") {
return nil, []error{fmt.Errorf("name can not start and end with '/'")}
return nil, []error{fmt.Errorf("key can not start and end with '/'")}
}
return nil, nil
},
Default: "terraform.tfstate",
},

"lock": &schema.Schema{
Type: schema.TypeBool,
"tablestore_table": {
Type: schema.TypeString,
Optional: true,
Description: "Whether to lock state access. Defaults to true",
Default: true,
Description: "TableStore table for state locking and consistency",
Default: "",
},

"encrypt": &schema.Schema{
Expand Down Expand Up @@ -130,14 +142,16 @@ type Backend struct {

// The fields below are set from configure
ossClient *oss.Client
otsClient *tablestore.TableStoreClient

bucketName string
statePath string
stateName string
statePrefix string
stateKey string
serverSideEncryption bool
acl string
endpoint string
lock bool
otsEndpoint string
otsTable string
}

func (b *Backend) configure(ctx context.Context) error {
Expand All @@ -149,27 +163,20 @@ func (b *Backend) configure(ctx context.Context) error {
d := schema.FromContextBackendConfig(ctx)

b.bucketName = d.Get("bucket").(string)
dir := strings.Trim(d.Get("path").(string), "/")
if strings.HasPrefix(dir, "./") {
dir = strings.TrimPrefix(dir, "./")

}

b.statePath = dir
b.stateName = d.Get("name").(string)
b.statePrefix = strings.TrimPrefix(strings.Trim(d.Get("prefix").(string), "/"), "./")
b.stateKey = d.Get("key").(string)
b.serverSideEncryption = d.Get("encrypt").(bool)
b.acl = d.Get("acl").(string)
b.lock = d.Get("lock").(bool)

access_key := d.Get("access_key").(string)
secret_key := d.Get("secret_key").(string)
security_token := d.Get("security_token").(string)
accessKey := d.Get("access_key").(string)
secretKey := d.Get("secret_key").(string)
securityToken := d.Get("security_token").(string)
region := d.Get("region").(string)
endpoint := d.Get("endpoint").(string)
schma := "https"

if endpoint == "" {
endpointItem, _ := b.getOSSEndpointByRegion(access_key, secret_key, security_token, region)
endpointItem, _ := b.getOSSEndpointByRegion(accessKey, secretKey, securityToken, region)
if endpointItem != nil && len(endpointItem.Endpoint) > 0 {
if len(endpointItem.Protocols.Protocols) > 0 {
// HTTP or HTTPS
Expand All @@ -191,13 +198,23 @@ func (b *Backend) configure(ctx context.Context) error {
}
log.Printf("[DEBUG] Instantiate OSS client using endpoint: %#v", endpoint)
var options []oss.ClientOption
if security_token != "" {
options = append(options, oss.SecurityToken(security_token))
if securityToken != "" {
options = append(options, oss.SecurityToken(securityToken))
}
options = append(options, oss.UserAgent(fmt.Sprintf("%s/%s", TerraformUA, TerraformVersion)))

client, err := oss.New(endpoint, access_key, secret_key, options...)
client, err := oss.New(endpoint, accessKey, secretKey, options...)
b.ossClient = client
otsEndpoint := d.Get("tablestore_endpoint").(string)
if otsEndpoint != "" {
if !strings.HasPrefix(otsEndpoint, "http") {
otsEndpoint = fmt.Sprintf("%s://%s", schma, otsEndpoint)
}
b.otsEndpoint = otsEndpoint
parts := strings.Split(strings.TrimPrefix(strings.TrimPrefix(otsEndpoint, "https://"), "http://"), ".")
b.otsClient = tablestore.NewClientWithConfig(otsEndpoint, parts[0], accessKey, secretKey, securityToken, tablestore.NewDefaultTableStoreConfig())
}
b.otsTable = d.Get("tablestore_table").(string)

return err
}
Expand All @@ -222,11 +239,6 @@ func (b *Backend) getOSSEndpointByRegion(access_key, secret_key, security_token,
}

func getSdkConfig() *sdk.Config {
// Fix bug "open /usr/local/go/lib/time/zoneinfo.zip: no such file or directory" which happened in windows.
if data, ok := resource.GetTZData("GMT"); ok {
utils.TZData = data
utils.LoadLocationFromTZData = time.LoadLocationFromTZData
}
return sdk.NewConfig().
WithMaxRetryTime(5).
WithTimeout(time.Duration(30) * time.Second).
Expand Down
79 changes: 42 additions & 37 deletions backend/remote-state/oss/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/hashicorp/terraform/state/remote"
"github.com/hashicorp/terraform/states"

"github.com/aliyun/aliyun-tablestore-go-sdk/tablestore"
"log"
"path"
)
Expand All @@ -33,7 +34,32 @@ func (b *Backend) remoteClient(name string) (*RemoteClient, error) {
lockFile: b.lockFile(name),
serverSideEncryption: b.serverSideEncryption,
acl: b.acl,
doLock: b.lock,
otsTable: b.otsTable,
otsClient: b.otsClient,
}
if b.otsEndpoint != "" && b.otsTable != "" {
table, err := b.otsClient.DescribeTable(&tablestore.DescribeTableRequest{
TableName: b.otsTable,
})
if err != nil {
return client, fmt.Errorf("Error describing table store %s: %#v", b.otsTable, err)
}
for _, t := range table.TableMeta.SchemaEntry {
pkMeta := TableStorePrimaryKeyMeta{
PKName: *t.Name,
}
if *t.Type == tablestore.PrimaryKeyType_INTEGER {
pkMeta.PKType = "Integer"
} else if *t.Type == tablestore.PrimaryKeyType_STRING {
pkMeta.PKType = "String"
} else if *t.Type == tablestore.PrimaryKeyType_BINARY {
pkMeta.PKType = "Binary"
} else {
return client, fmt.Errorf("Unsupported PrimaryKey type: %d.", *t.Type)
}
client.otsTabkePK = pkMeta
break
}
}

return client, nil
Expand All @@ -46,17 +72,25 @@ func (b *Backend) Workspaces() ([]string, error) {
}

var options []oss.Option
options = append(options, oss.Prefix(b.statePath))
options = append(options, oss.Prefix(b.statePrefix+"/"))
resp, err := bucket.ListObjects(options...)

if err != nil {
return nil, err
}

result := []string{backend.DefaultStateName}
prefix := b.statePrefix
for _, obj := range resp.Objects {
if b.keyEnv(obj.Key) != "" {
result = append(result, b.keyEnv(obj.Key))
// we have 3 parts, the state prefix, the workspace name, and the state file: <prefix>/<worksapce-name>/<key>
if path.Join(b.statePrefix, b.stateKey) == obj.Key {
// filter the default workspace
continue
}

parts := strings.Split(strings.TrimPrefix(obj.Key, prefix+"/"), "/")
if len(parts) > 0 && parts[0] != "" {
result = append(result, parts[0])
}
}

Expand All @@ -83,16 +117,13 @@ func (b *Backend) StateMgr(name string) (state.State, error) {
}
stateMgr := &remote.State{Client: client}

if !b.lock {
stateMgr.DisableLocks()
}
// Check to see if this state already exists.
existing, err := b.Workspaces()
if err != nil {
return nil, err
}

log.Printf("[DEBUG] Current state name: %s. All States:%#v", name, existing)
log.Printf("[DEBUG] Current workspace name: %s. All workspaces:%#v", name, existing)

exists := false
for _, s := range existing {
Expand Down Expand Up @@ -146,41 +177,15 @@ func (b *Backend) StateMgr(name string) (state.State, error) {
return stateMgr, nil
}

// extract the object name from the OSS key
func (b *Backend) keyEnv(key string) string {
// we have 3 parts, the state path, the state name, and the state file
parts := strings.Split(key, "/")
length := len(parts)
if length < 3 {
// use default state
return ""
}

// shouldn't happen since we listed by prefix
if strings.Join(parts[0:length-2], "/") != b.statePath {
return ""
}

// not our key, so don't include it in our listing
if parts[length-1] != b.stateName {
return ""
}

return parts[length-2]
}

func (b *Backend) stateFile(name string) string {
if name == backend.DefaultStateName {
return path.Join(b.statePath, b.stateName)
return path.Join(b.statePrefix, b.stateKey)
}
return path.Join(b.statePath, name, b.stateName)
return path.Join(b.statePrefix, name, b.stateKey)
}

func (b *Backend) lockFile(name string) string {
if name == backend.DefaultStateName {
return path.Join(b.statePath, b.stateName+lockFileSuffix)
}
return path.Join(b.statePath, name, b.stateName+lockFileSuffix)
return b.stateFile(name) + lockFileSuffix
}

const stateUnlockError = `
Expand Down
Loading

0 comments on commit 3f44dd9

Please sign in to comment.