Add lock/unlock commands

This commit is contained in:
James Bardin 2017-02-06 13:45:30 -05:00
parent b80ae5e13e
commit 015198ca11
4 changed files with 299 additions and 0 deletions

78
command/lock.go Normal file
View File

@ -0,0 +1,78 @@
package command
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/state"
)
// LockCommand is a cli.Command implementation that manually locks
// the state.
type LockCommand struct {
Meta
}
func (c *LockCommand) Run(args []string) int {
args = c.Meta.process(args, false)
cmdFlags := c.Meta.flagSet("lock")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
}
// assume everything is initialized. The user can manually init if this is
// required.
configPath, err := ModulePath(cmdFlags.Args())
if err != nil {
c.Ui.Error(err.Error())
return 1
}
// Load the backend
b, err := c.Backend(&BackendOpts{
ConfigPath: configPath,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
return 1
}
st, err := b.State()
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
}
s, ok := st.(state.Locker)
if !ok {
c.Ui.Error("Current state does not support locking")
return 1
}
if err := s.Lock("lock"); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to lock state: %s", err))
return 1
}
return 0
}
func (c *LockCommand) Help() string {
helpText := `
Usage: terraform lock [DIR]
Manually lock the state for the defined configuration.
This will not modify your infrastructure. This command obtains a lock on the
state for the current configuration. The behavior of this lock is dependent
on the backend being used. A lock on a local state file only lasts for the
duration of the calling process.
`
return strings.TrimSpace(helpText)
}
func (c *LockCommand) Synopsis() string {
return "Manually lock the terraform state"
}

97
command/lock_test.go Normal file
View File

@ -0,0 +1,97 @@
package command
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
func TestLock(t *testing.T) {
testData, _ := filepath.Abs("./testdata")
td := tempDir(t)
os.MkdirAll(td, 0755)
defer os.RemoveAll(td)
defer testChdir(t, td)()
// Write the legacy state
statePath := DefaultStateFilename
{
f, err := os.Create(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
err = terraform.WriteState(testState(), f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
}
p := testProvider()
ui := new(cli.MockUi)
c := &LockCommand{
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
if code := c.Run(nil); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
unlock, err := testLockState(testData, statePath)
if err == nil {
unlock()
t.Fatal("expected error locking state")
} else if !strings.Contains(err.Error(), "locked") {
t.Fatal("does not appear to be a lock error:", err)
}
}
func TestLock_lockedState(t *testing.T) {
testData, _ := filepath.Abs("./testdata")
td := tempDir(t)
os.MkdirAll(td, 0755)
defer os.RemoveAll(td)
defer testChdir(t, td)()
// Write the legacy state
statePath := DefaultStateFilename
{
f, err := os.Create(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
err = terraform.WriteState(testState(), f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
}
p := testProvider()
ui := new(cli.MockUi)
c := &LockCommand{
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
unlock, err := testLockState(testData, statePath)
if err != nil {
t.Fatal(err)
}
defer unlock()
if code := c.Run(nil); code == 0 {
t.Fatal("expected error when locking a locked state")
}
}

78
command/unlock.go Normal file
View File

@ -0,0 +1,78 @@
package command
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/state"
)
// UnlockCommand is a cli.Command implementation that manually unlocks
// the state.
type UnlockCommand struct {
Meta
}
func (c *UnlockCommand) Run(args []string) int {
args = c.Meta.process(args, false)
cmdFlags := c.Meta.flagSet("unlock")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
}
// assume everything is initialized. The user can manually init if this is
// required.
configPath, err := ModulePath(cmdFlags.Args())
if err != nil {
c.Ui.Error(err.Error())
return 1
}
// Load the backend
b, err := c.Backend(&BackendOpts{
ConfigPath: configPath,
})
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
return 1
}
st, err := b.State()
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
}
s, ok := st.(state.Locker)
if !ok {
c.Ui.Error("Current state does not support locking")
return 1
}
if err := s.Unlock(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err))
return 1
}
return 0
}
func (c *UnlockCommand) Help() string {
helpText := `
Usage: terraform unlock [DIR]
Manually unlock the state for the defined configuration.
This will not modify your infrastructure. This command removes the lock on the
state for the current configuration. The behavior of this lock is dependent
on the backend being used. Local state files cannot be unlocked by another
process.
`
return strings.TrimSpace(helpText)
}
func (c *UnlockCommand) Synopsis() string {
return "Manually unlock the terraform state"
}

46
command/unlock_test.go Normal file
View File

@ -0,0 +1,46 @@
package command
import (
"os"
"testing"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
// Since we can't unlock a local state file, just test that calling unlock
// doesn't fail.
// TODO: mock remote state for UI testing
func TestUnlock(t *testing.T) {
td := tempDir(t)
os.MkdirAll(td, 0755)
defer os.RemoveAll(td)
defer testChdir(t, td)()
// Write the legacy state
statePath := DefaultStateFilename
{
f, err := os.Create(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
err = terraform.WriteState(testState(), f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
}
p := testProvider()
ui := new(cli.MockUi)
c := &UnlockCommand{
Meta: Meta{
ContextOpts: testCtxConfig(p),
Ui: ui,
},
}
if code := c.Run(nil); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
}