From 1819b6fb343ec35468c3f2394b13840264b690f2 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 27 Jun 2014 11:09:01 -0700 Subject: [PATCH] command/refresh --- command/refresh.go | 115 ++++++++++++++++++++++++++ command/refresh_test.go | 68 +++++++++++++++ command/test-fixtures/refresh/main.tf | 3 + commands.go | 7 ++ 4 files changed, 193 insertions(+) create mode 100644 command/refresh.go create mode 100644 command/refresh_test.go create mode 100644 command/test-fixtures/refresh/main.tf diff --git a/command/refresh.go b/command/refresh.go new file mode 100644 index 000000000..b3c9f4bb1 --- /dev/null +++ b/command/refresh.go @@ -0,0 +1,115 @@ +package command + +import ( + "flag" + "fmt" + "log" + "os" + "strings" + + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/cli" +) + +// RefreshCommand is a cli.Command implementation that refreshes the state +// file. +type RefreshCommand struct { + TFConfig *terraform.Config + Ui cli.Ui +} + +func (c *RefreshCommand) Run(args []string) int { + var outPath string + statePath := "terraform.tfstate" + configPath := "." + + cmdFlags := flag.NewFlagSet("refresh", flag.ContinueOnError) + cmdFlags.StringVar(&outPath, "out", "", "output path") + cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } + if err := cmdFlags.Parse(args); err != nil { + return 1 + } + + 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.") + cmdFlags.Usage() + return 1 + } + + statePath = args[0] + configPath = args[1] + if outPath == "" { + outPath = 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.TFConfig.Hooks = append(c.TFConfig.Hooks, &UiHook{Ui: c.Ui}) + tf, err := terraform.New(c.TFConfig) + if err != nil { + c.Ui.Error(fmt.Sprintf("Error initializing Terraform: %s", err)) + return 1 + } + + state, err = tf.Refresh(b, state) + 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) + if err == nil { + defer f.Close() + err = terraform.WriteState(state, f) + } + if err != nil { + c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err)) + return 1 + } + + return 0 +} + +func (c *RefreshCommand) Help() string { + helpText := ` +Usage: terraform refresh [options] [terraform.tfstate] [terraform.tf] + + 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. + +Options: + + -out=path Path to write updated state file. If this is not specified, + the existing state file will be overridden. + +` + return strings.TrimSpace(helpText) +} + +func (c *RefreshCommand) Synopsis() string { + return "Refresh the state of your infrastructure" +} diff --git a/command/refresh_test.go b/command/refresh_test.go new file mode 100644 index 000000000..33cff13c8 --- /dev/null +++ b/command/refresh_test.go @@ -0,0 +1,68 @@ +package command + +import ( + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/hashicorp/terraform/terraform" + "github.com/mitchellh/cli" +) + +func TestRefresh(t *testing.T) { + // Write out some prior state + tf, err := ioutil.TempFile("", "tf") + if err != nil { + t.Fatalf("err: %s", err) + } + statePath := tf.Name() + defer os.Remove(tf.Name()) + + state := &terraform.State{} + + err = terraform.WriteState(state, tf) + tf.Close() + if err != nil { + t.Fatalf("err: %s", err) + } + + p := testProvider() + ui := new(cli.MockUi) + c := &RefreshCommand{ + TFConfig: testTFConfig(p), + Ui: ui, + } + + p.RefreshFn = nil + p.RefreshReturn = &terraform.ResourceState{ID: "yes"} + + args := []string{ + statePath, + 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) + } +} diff --git a/command/test-fixtures/refresh/main.tf b/command/test-fixtures/refresh/main.tf new file mode 100644 index 000000000..5794f94d9 --- /dev/null +++ b/command/test-fixtures/refresh/main.tf @@ -0,0 +1,3 @@ +resource "test_instance" "foo" { + ami = "bar" +} diff --git a/commands.go b/commands.go index f290d4658..41d1b1d8c 100644 --- a/commands.go +++ b/commands.go @@ -40,6 +40,13 @@ func init() { }, nil }, + "refresh": func() (cli.Command, error) { + return &command.RefreshCommand{ + TFConfig: &TFConfig, + Ui: Ui, + }, nil + }, + "version": func() (cli.Command, error) { return &command.VersionCommand{ Revision: GitCommit,