backend/remote-state/gcloud: Use the lock file's generation as lock ID.
This allows Unlock() to call Delete() without reading the lock file's content first.
This commit is contained in:
parent
edf2096e28
commit
97e1aa7ce9
|
@ -4,9 +4,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"cloud.google.com/go/storage"
|
"cloud.google.com/go/storage"
|
||||||
uuid "github.com/hashicorp/go-uuid"
|
multierror "github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/terraform/state"
|
"github.com/hashicorp/terraform/state"
|
||||||
"github.com/hashicorp/terraform/state/remote"
|
"github.com/hashicorp/terraform/state/remote"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -72,77 +73,81 @@ func (c *RemoteClient) Delete() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lock writes to a lock file, ensuring file creation. Returns the generation
|
||||||
|
// number, which must be passed to Unlock().
|
||||||
func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
|
func (c *RemoteClient) Lock(info *state.LockInfo) (string, error) {
|
||||||
if info.ID == "" {
|
|
||||||
lockID, err := uuid.GenerateUUID()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
info.ID = lockID
|
|
||||||
}
|
|
||||||
|
|
||||||
info.Path = c.lockFileURL()
|
|
||||||
|
|
||||||
infoJson, err := json.Marshal(info)
|
infoJson, err := json.Marshal(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
writer := c.lockFile().If(storage.Conditions{DoesNotExist: true}).NewWriter(c.storageContext)
|
lockFile := c.lockFile()
|
||||||
writer.Write(infoJson)
|
w := lockFile.If(storage.Conditions{DoesNotExist: true}).NewWriter(c.storageContext)
|
||||||
if err := writer.Close(); err != nil {
|
err = func() error {
|
||||||
return "", fmt.Errorf("Error while saving lock file (%v): %v", info.Path, err)
|
if _, err := w.Write(infoJson); err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
return w.Close()
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return "", c.lockError(fmt.Errorf("writing %q failed: %v", c.lockFileURL(), err))
|
||||||
|
}
|
||||||
|
|
||||||
|
info.ID = strconv.FormatInt(w.Attrs().Generation, 10)
|
||||||
|
info.Path = c.lockFileURL()
|
||||||
|
|
||||||
return info.ID, nil
|
return info.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) Unlock(id string) error {
|
func (c *RemoteClient) Unlock(id string) error {
|
||||||
lockErr := &state.LockError{}
|
gen, err := strconv.ParseInt(id, 10, 64)
|
||||||
|
|
||||||
lockFileReader, err := c.lockFile().NewReader(c.storageContext)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lockErr.Err = fmt.Errorf("Failed to retrieve lock info (%v): %v", c.lockFileURL(), err)
|
return err
|
||||||
return lockErr
|
|
||||||
}
|
|
||||||
defer lockFileReader.Close()
|
|
||||||
|
|
||||||
lockFileContents, err := ioutil.ReadAll(lockFileReader)
|
|
||||||
if err != nil {
|
|
||||||
lockErr.Err = fmt.Errorf("Failed to retrieve lock info (%v): %v", c.lockFileURL(), err)
|
|
||||||
return lockErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lockInfo := &state.LockInfo{}
|
if err := c.lockFile().If(storage.Conditions{GenerationMatch: gen}).Delete(c.storageContext); err != nil {
|
||||||
err = json.Unmarshal(lockFileContents, lockInfo)
|
return c.lockError(err)
|
||||||
if err != nil {
|
|
||||||
lockErr.Err = fmt.Errorf("Failed to unmarshal lock info (%v): %v", c.lockFileURL(), err)
|
|
||||||
return lockErr
|
|
||||||
}
|
|
||||||
|
|
||||||
lockErr.Info = lockInfo
|
|
||||||
|
|
||||||
if lockInfo.ID != id {
|
|
||||||
lockErr.Err = fmt.Errorf("Lock id %q does not match existing lock", id)
|
|
||||||
return lockErr
|
|
||||||
}
|
|
||||||
|
|
||||||
lockFileAttrs, err := lockFile.Attrs(c.storageContext)
|
|
||||||
if err != nil {
|
|
||||||
lockErr.Err = fmt.Errorf("Failed to fetch lock file attrs (%v): %v", c.lockFileURL(), err)
|
|
||||||
return lockErr
|
|
||||||
}
|
|
||||||
|
|
||||||
err = lockFile.If(storage.Conditions{GenerationMatch: lockFileAttrs.Generation}).Delete(c.storageContext)
|
|
||||||
if err != nil {
|
|
||||||
lockErr.Err = fmt.Errorf("Failed to delete lock file (%v): %v", c.lockFileURL(), err)
|
|
||||||
return lockErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *RemoteClient) lockError(err error) *state.LockError {
|
||||||
|
lockErr := &state.LockError{
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
|
||||||
|
info, infoErr := c.lockInfo()
|
||||||
|
if infoErr != nil {
|
||||||
|
lockErr.Err = multierror.Append(lockErr.Err, infoErr)
|
||||||
|
} else {
|
||||||
|
lockErr.Info = info
|
||||||
|
}
|
||||||
|
return lockErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// lockInfo reads the lock file, parses its contents and returns the parsed
|
||||||
|
// LockInfo struct.
|
||||||
|
func (c *RemoteClient) lockInfo() (*state.LockInfo, error) {
|
||||||
|
r, err := c.lockFile().NewReader(c.storageContext)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
rawData, err := ioutil.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info := &state.LockInfo{}
|
||||||
|
if err := json.Unmarshal(rawData, info); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) stateFile() *storage.ObjectHandle {
|
func (c *RemoteClient) stateFile() *storage.ObjectHandle {
|
||||||
return c.storageClient.Bucket(c.bucketName).Object(c.stateFilePath)
|
return c.storageClient.Bucket(c.bucketName).Object(c.stateFilePath)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue