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"
"strings"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/cli"
)
@ -20,68 +19,57 @@ type RefreshCommand struct {
}
func (c *RefreshCommand) Run(args []string) int {
var outPath string
statePath := "terraform.tfstate"
configPath := "."
var statePath, stateOutPath string
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()) }
if err := cmdFlags.Parse(args); err != nil {
return 1
}
var configPath string
args = cmdFlags.Args()
if len(args) != 2 {
// TODO(mitchellh): this is temporary until we can assume current
// dir for Terraform config.
c.Ui.Error("TEMPORARY: The refresh command requires two args.")
if len(args) > 1 {
c.Ui.Error("The apply command expacts at most one argument.")
cmdFlags.Usage()
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]
configPath = args[1]
if outPath == "" {
outPath = statePath
// If we don't specify an output path, default to out normal state
// path.
if stateOutPath == "" {
stateOutPath = statePath
}
// Load up the state
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
// Build the context based on the arguments given
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) {
return 1
}
state, err = ctx.Refresh()
state, err := ctx.Refresh()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error refreshing state: %s", err))
return 1
}
log.Printf("[INFO] Writing state output to: %s", outPath)
f, err = os.Create(outPath)
log.Printf("[INFO] Writing state output to: %s", stateOutPath)
f, err := os.Create(stateOutPath)
if err == nil {
defer f.Close()
err = terraform.WriteState(state, f)
@ -96,21 +84,27 @@ func (c *RefreshCommand) Run(args []string) int {
func (c *RefreshCommand) Help() string {
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
operation that will not modify infrastructure. The read-only property
is dependent on resource providers being implemented correctly.
Update the state file of your infrastructure with metadata that matches
the physical resources they are tracking.
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:
-out=path Path to write updated state file. If this is not specified,
the existing state file will be overridden.
-state=path Path to read and save state (unless state-out
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)
}
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 (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"testing"
@ -32,7 +33,7 @@ func TestRefresh(t *testing.T) {
p.RefreshReturn = &terraform.ResourceState{ID: "yes"}
args := []string{
statePath,
"-state", statePath,
testFixturePath("refresh"),
}
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) {
state := &terraform.State{
Resources: map[string]*terraform.ResourceState{
@ -92,8 +229,8 @@ func TestRefresh_outPath(t *testing.T) {
p.RefreshReturn = &terraform.ResourceState{ID: "yes"}
args := []string{
"-out", outPath,
statePath,
"-state", statePath,
"-state-out", outPath,
testFixturePath("refresh"),
}
if code := c.Run(args); code != 0 {