diff --git a/command/meta_backend.go b/command/meta_backend.go index 85277bae3..7a53c8685 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -533,11 +533,16 @@ func (m *Meta) backendFromPlan(opts *BackendOpts) (backend.Backend, error) { } // Lock the state if we can - err = clistate.Lock(realMgr, "backend from plan", m.Ui, m.Colorize()) + lockInfo := &state.LockInfo{ + Operation: "plan", + Info: "backend from plan", + } + + lockID, err := clistate.Lock(realMgr, lockInfo, m.Ui, m.Colorize()) if err != nil { return nil, fmt.Errorf("Error locking state: %s", err) } - defer clistate.Unlock(realMgr, m.Ui, m.Colorize()) + defer clistate.Unlock(realMgr, lockID, m.Ui, m.Colorize()) if err := realMgr.RefreshState(); err != nil { return nil, fmt.Errorf("Error reading state: %s", err) @@ -986,11 +991,15 @@ func (m *Meta) backend_C_r_s( } // Lock the state if we can - err = clistate.Lock(sMgr, "backend from config", m.Ui, m.Colorize()) + lockInfo := &state.LockInfo{ + Info: "backend from config", + } + + lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize()) if err != nil { return nil, fmt.Errorf("Error locking state: %s", err) } - defer clistate.Unlock(sMgr, m.Ui, m.Colorize()) + defer clistate.Unlock(sMgr, lockID, m.Ui, m.Colorize()) // Store the metadata in our saved state location s := sMgr.State() @@ -1091,11 +1100,14 @@ func (m *Meta) backend_C_r_S_changed( } // Lock the state if we can - err = clistate.Lock(sMgr, "backend from config", m.Ui, m.Colorize()) + lockInfo := &state.LockInfo{ + Info: "backend from config", + } + lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize()) if err != nil { return nil, fmt.Errorf("Error locking state: %s", err) } - defer clistate.Unlock(sMgr, m.Ui, m.Colorize()) + defer clistate.Unlock(sMgr, lockID, m.Ui, m.Colorize()) // Update the backend state s = sMgr.State() @@ -1249,11 +1261,15 @@ func (m *Meta) backend_C_R_S_unchanged( } // Lock the state if we can - err = clistate.Lock(sMgr, "backend from config", m.Ui, m.Colorize()) + lockInfo := &state.LockInfo{ + Info: "backend from config", + } + + lockID, err := clistate.Lock(sMgr, lockInfo, m.Ui, m.Colorize()) if err != nil { return nil, fmt.Errorf("Error locking state: %s", err) } - defer clistate.Unlock(sMgr, m.Ui, m.Colorize()) + defer clistate.Unlock(sMgr, lockID, m.Ui, m.Colorize()) // Unset the remote state s = sMgr.State() diff --git a/command/meta_backend_migrate.go b/command/meta_backend_migrate.go index 4df4463f4..a51e4a57a 100644 --- a/command/meta_backend_migrate.go +++ b/command/meta_backend_migrate.go @@ -24,17 +24,25 @@ import ( // // This will attempt to lock both states for the migration. func (m *Meta) backendMigrateState(opts *backendMigrateOpts) error { - err := clistate.Lock(opts.One, "migration source state", m.Ui, m.Colorize()) + lockInfoOne := &state.LockInfo{ + Info: "migration source state", + } + + lockIDOne, err := clistate.Lock(opts.One, lockInfoOne, m.Ui, m.Colorize()) if err != nil { return fmt.Errorf("Error locking source state: %s", err) } - defer clistate.Unlock(opts.One, m.Ui, m.Colorize()) + defer clistate.Unlock(opts.One, lockIDOne, m.Ui, m.Colorize()) - err = clistate.Lock(opts.Two, "migration destination state", m.Ui, m.Colorize()) + lockInfoTwo := &state.LockInfo{ + Info: "migration source state", + } + + lockIDTwo, err := clistate.Lock(opts.Two, lockInfoTwo, m.Ui, m.Colorize()) if err != nil { return fmt.Errorf("Error locking destination state: %s", err) } - defer clistate.Unlock(opts.Two, m.Ui, m.Colorize()) + defer clistate.Unlock(opts.Two, lockIDTwo, m.Ui, m.Colorize()) one := opts.One.State() two := opts.Two.State() diff --git a/command/taint.go b/command/taint.go index c2321c813..95f4a5249 100644 --- a/command/taint.go +++ b/command/taint.go @@ -6,6 +6,7 @@ import ( "strings" clistate "github.com/hashicorp/terraform/command/state" + "github.com/hashicorp/terraform/state" "github.com/hashicorp/terraform/terraform" ) @@ -73,13 +74,16 @@ func (c *TaintCommand) Run(args []string) int { } if c.Meta.stateLock { - err := clistate.Lock(st, "taint", c.Ui, c.Colorize()) + lockInfo := &state.LockInfo{ + Operation: "taint", + } + lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize()) if err != nil { c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) return 1 } - defer clistate.Unlock(st, c.Ui, c.Colorize()) + defer clistate.Unlock(st, lockID, c.Ui, c.Colorize()) } // Get the actual state structure diff --git a/command/testdata/statelocker.go b/command/testdata/statelocker.go index 8f25f9d33..3128d59f7 100644 --- a/command/testdata/statelocker.go +++ b/command/testdata/statelocker.go @@ -23,7 +23,7 @@ func main() { Path: os.Args[1], } - err := s.Lock("command test") + lockID, err := s.Lock(&state.LockInfo{Operation: "test", Info: "state locker"}) if err != nil { io.WriteString(os.Stderr, err.Error()) return @@ -33,7 +33,7 @@ func main() { io.WriteString(os.Stdout, "LOCKED") defer func() { - if err := s.Unlock(); err != nil { + if err := s.Unlock(lockID); err != nil { io.WriteString(os.Stderr, err.Error()) } }() diff --git a/command/unlock.go b/command/unlock.go index fb1d22c4e..952d9bacf 100644 --- a/command/unlock.go +++ b/command/unlock.go @@ -92,7 +92,8 @@ func (c *UnlockCommand) Run(args []string) int { } } - if err := s.Unlock(); err != nil { + // FIXME: unlock should require the lock ID + if err := s.Unlock(""); err != nil { c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err)) return 1 } diff --git a/command/untaint.go b/command/untaint.go index 453a79ac8..3bd160d28 100644 --- a/command/untaint.go +++ b/command/untaint.go @@ -6,6 +6,7 @@ import ( "strings" clistate "github.com/hashicorp/terraform/command/state" + "github.com/hashicorp/terraform/state" ) // UntaintCommand is a cli.Command implementation that manually untaints @@ -61,13 +62,16 @@ func (c *UntaintCommand) Run(args []string) int { } if c.Meta.stateLock { - err := clistate.Lock(st, "untaint", c.Ui, c.Colorize()) + lockInfo := &state.LockInfo{ + Operation: "untaint", + } + lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize()) if err != nil { c.Ui.Error(fmt.Sprintf("Error locking state: %s", err)) return 1 } - defer clistate.Unlock(st, c.Ui, c.Colorize()) + defer clistate.Unlock(st, lockID, c.Ui, c.Colorize()) } // Get the actual state structure diff --git a/state/local.go b/state/local.go index 01708af6f..c92cc8e48 100644 --- a/state/local.go +++ b/state/local.go @@ -134,25 +134,25 @@ func (s *LocalState) RefreshState() error { } // Lock implements a local filesystem state.Locker. -func (s *LocalState) Lock(reason string) error { +func (s *LocalState) Lock(info *LockInfo) (string, error) { if s.stateFileOut == nil { if err := s.createStateFiles(); err != nil { - return err + return "", err } } if err := s.lock(); err != nil { if info, err := s.lockInfo(); err == nil { - return info.Err() + return "", info.Err() } - return fmt.Errorf("state file %q locked: %s", s.Path, err) + return "", fmt.Errorf("state file %q locked: %s", s.Path, err) } - return s.writeLockInfo(reason) + return "", s.writeLockInfo(info) } -func (s *LocalState) Unlock() error { +func (s *LocalState) Unlock(id string) error { // we can't be locked if we don't have a file if s.stateFileOut == nil { return nil @@ -232,18 +232,14 @@ func (s *LocalState) lockInfo() (*LockInfo, error) { } // write a new lock info file -func (s *LocalState) writeLockInfo(info string) error { +func (s *LocalState) writeLockInfo(info *LockInfo) error { path := s.lockInfoPath() + info.Path = s.Path + info.Created = time.Now().UTC() - lockInfo := &LockInfo{ - Path: s.Path, - Created: time.Now().UTC(), - Info: info, - } - - infoData, err := json.Marshal(lockInfo) + infoData, err := json.Marshal(info) if err != nil { - panic(fmt.Sprintf("could not marshal lock info: %#v", lockInfo)) + panic(fmt.Sprintf("could not marshal lock info: %#v", info)) } err = ioutil.WriteFile(path, infoData, 0600)