command/refresh: default state path, optional args

This commit is contained in:
Mitchell Hashimoto 2014-07-11 21:56:43 -07:00
parent 1911ee215b
commit 9a6f1e594b
2 changed files with 180 additions and 49 deletions

View File

@ -7,7 +7,6 @@ import (
"os" "os"
"strings" "strings"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
) )
@ -20,68 +19,57 @@ type RefreshCommand struct {
} }
func (c *RefreshCommand) Run(args []string) int { func (c *RefreshCommand) Run(args []string) int {
var outPath string var statePath, stateOutPath string
statePath := "terraform.tfstate"
configPath := "."
cmdFlags := flag.NewFlagSet("refresh", flag.ContinueOnError) cmdFlags := flag.NewFlagSet("refresh", flag.ContinueOnError)
cmdFlags.StringVar(&outPath, "out", "", "output path") cmdFlags.StringVar(&statePath, "state", DefaultStateFilename, "path")
cmdFlags.StringVar(&stateOutPath, "state-out", "", "path")
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil { if err := cmdFlags.Parse(args); err != nil {
return 1 return 1
} }
var configPath string
args = cmdFlags.Args() args = cmdFlags.Args()
if len(args) != 2 { if len(args) > 1 {
// TODO(mitchellh): this is temporary until we can assume current c.Ui.Error("The apply command expacts at most one argument.")
// dir for Terraform config.
c.Ui.Error("TEMPORARY: The refresh command requires two args.")
cmdFlags.Usage() cmdFlags.Usage()
return 1 return 1
} else if len(args) == 1 {
configPath = args[0]
} else {
var err error
configPath, err = os.Getwd()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
}
} }
statePath = args[0] // If we don't specify an output path, default to out normal state
configPath = args[1] // path.
if outPath == "" { if stateOutPath == "" {
outPath = statePath stateOutPath = statePath
} }
// Load up the state // Build the context based on the arguments given
f, err := os.Open(statePath)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading state: %s", err))
return 1
}
state, err := terraform.ReadState(f)
f.Close()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading state: %s", err))
return 1
}
b, err := config.Load(configPath)
if err != nil {
c.Ui.Error(fmt.Sprintf("Error loading blueprint: %s", err))
return 1
}
c.ContextOpts.Config = b
c.ContextOpts.State = state
c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui}) c.ContextOpts.Hooks = append(c.ContextOpts.Hooks, &UiHook{Ui: c.Ui})
ctx := terraform.NewContext(c.ContextOpts) ctx, err := ContextArg(configPath, statePath, c.ContextOpts)
if err != nil {
c.Ui.Error(err.Error())
return 1
}
if !validateContext(ctx, c.Ui) { if !validateContext(ctx, c.Ui) {
return 1 return 1
} }
state, err = ctx.Refresh() state, err := ctx.Refresh()
if err != nil { if err != nil {
c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err)) c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
return 1 return 1
} }
log.Printf("[INFO] Writing state output to: %s", outPath) log.Printf("[INFO] Writing state output to: %s", stateOutPath)
f, err = os.Create(outPath) f, err := os.Create(stateOutPath)
if err == nil { if err == nil {
defer f.Close() defer f.Close()
err = terraform.WriteState(state, f) err = terraform.WriteState(state, f)
@ -96,21 +84,27 @@ func (c *RefreshCommand) Run(args []string) int {
func (c *RefreshCommand) Help() string { func (c *RefreshCommand) Help() string {
helpText := ` helpText := `
Usage: terraform refresh [options] [terraform.tfstate] [terraform.tf] Usage: terraform refresh [options] [dir]
Refresh and update the state of your infrastructure. This is read-only Update the state file of your infrastructure with metadata that matches
operation that will not modify infrastructure. The read-only property the physical resources they are tracking.
is dependent on resource providers being implemented correctly.
This will not modify your infrastructure, but it can modify your
state file to update metadata. This metadata might cause new changes
to occur when you generate a plan or call apply next.
Options: Options:
-out=path Path to write updated state file. If this is not specified, -state=path Path to read and save state (unless state-out
the existing state file will be overridden. is specified). Defaults to "terraform.tfstate".
-state-out=path Path to write updated state file. By default, the
"-state" path will be used.
` `
return strings.TrimSpace(helpText) return strings.TrimSpace(helpText)
} }
func (c *RefreshCommand) Synopsis() string { func (c *RefreshCommand) Synopsis() string {
return "Refresh the state of your infrastructure" return "Refresh the local state of your infrastructure"
} }

View File

@ -3,6 +3,7 @@ package command
import ( import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath"
"reflect" "reflect"
"testing" "testing"
@ -32,7 +33,7 @@ func TestRefresh(t *testing.T) {
p.RefreshReturn = &terraform.ResourceState{ID: "yes"} p.RefreshReturn = &terraform.ResourceState{ID: "yes"}
args := []string{ args := []string{
statePath, "-state", statePath,
testFixturePath("refresh"), testFixturePath("refresh"),
} }
if code := c.Run(args); code != 0 { if code := c.Run(args); code != 0 {
@ -61,6 +62,142 @@ func TestRefresh(t *testing.T) {
} }
} }
func TestRefresh_cwd(t *testing.T) {
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("err: %s", err)
}
if err := os.Chdir(testFixturePath("refresh")); err != nil {
t.Fatalf("err: %s", err)
}
defer os.Chdir(cwd)
state := &terraform.State{
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
ID: "bar",
Type: "test_instance",
},
},
}
statePath := testStateFile(t, state)
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
ContextOpts: testCtxConfig(p),
Ui: ui,
}
p.RefreshFn = nil
p.RefreshReturn = &terraform.ResourceState{ID: "yes"}
args := []string{
"-state", statePath,
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !p.RefreshCalled {
t.Fatal("refresh should be called")
}
f, err := os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := newState.Resources["test_instance.foo"]
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestRefresh_defaultState(t *testing.T) {
originalState := &terraform.State{
Resources: map[string]*terraform.ResourceState{
"test_instance.foo": &terraform.ResourceState{
ID: "bar",
Type: "test_instance",
},
},
}
// Write the state file in a temporary directory with the
// default filename.
td, err := ioutil.TempDir("", "tf")
if err != nil {
t.Fatalf("err: %s", err)
}
statePath := filepath.Join(td, DefaultStateFilename)
f, err := os.Create(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
err = terraform.WriteState(originalState, f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
// Change to that directory
cwd, err := os.Getwd()
if err != nil {
t.Fatalf("err: %s", err)
}
if err := os.Chdir(filepath.Dir(statePath)); err != nil {
t.Fatalf("err: %s", err)
}
defer os.Chdir(cwd)
p := testProvider()
ui := new(cli.MockUi)
c := &RefreshCommand{
ContextOpts: testCtxConfig(p),
Ui: ui,
}
p.RefreshFn = nil
p.RefreshReturn = &terraform.ResourceState{ID: "yes"}
args := []string{
testFixturePath("refresh"),
}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}
if !p.RefreshCalled {
t.Fatal("refresh should be called")
}
f, err = os.Open(statePath)
if err != nil {
t.Fatalf("err: %s", err)
}
newState, err := terraform.ReadState(f)
f.Close()
if err != nil {
t.Fatalf("err: %s", err)
}
actual := newState.Resources["test_instance.foo"]
expected := p.RefreshReturn
if !reflect.DeepEqual(actual, expected) {
t.Fatalf("bad: %#v", actual)
}
}
func TestRefresh_outPath(t *testing.T) { func TestRefresh_outPath(t *testing.T) {
state := &terraform.State{ state := &terraform.State{
Resources: map[string]*terraform.ResourceState{ Resources: map[string]*terraform.ResourceState{
@ -92,8 +229,8 @@ func TestRefresh_outPath(t *testing.T) {
p.RefreshReturn = &terraform.ResourceState{ID: "yes"} p.RefreshReturn = &terraform.ResourceState{ID: "yes"}
args := []string{ args := []string{
"-out", outPath, "-state", statePath,
statePath, "-state-out", outPath,
testFixturePath("refresh"), testFixturePath("refresh"),
} }
if code := c.Run(args); code != 0 { if code := c.Run(args); code != 0 {