command: start migrating to new state package
This commit is contained in:
parent
1eec77378b
commit
579f102f37
|
@ -10,6 +10,9 @@ import (
|
|||
// Set to true when we're testing
|
||||
var test bool = false
|
||||
|
||||
// DefaultDataDir is the default directory for storing local data.
|
||||
const DefaultDataDir = ".terraform"
|
||||
|
||||
// DefaultStateFilename is the default filename used for the state file.
|
||||
const DefaultStateFilename = "terraform.tfstate"
|
||||
|
||||
|
|
134
command/meta.go
134
command/meta.go
|
@ -10,7 +10,7 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/terraform/config/module"
|
||||
"github.com/hashicorp/terraform/remote"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"github.com/mitchellh/cli"
|
||||
"github.com/mitchellh/colorstring"
|
||||
|
@ -24,7 +24,7 @@ type Meta struct {
|
|||
|
||||
// State read when calling `Context`. This is available after calling
|
||||
// `Context`.
|
||||
state *terraform.State
|
||||
state state.State
|
||||
|
||||
// This can be set by the command itself to provide extra hooks.
|
||||
extraHooks []terraform.Hook
|
||||
|
@ -78,11 +78,6 @@ func (m *Meta) initStatePaths() {
|
|||
|
||||
// StateOutPath returns the true output path for the state file
|
||||
func (m *Meta) StateOutPath() string {
|
||||
m.initStatePaths()
|
||||
if m.useRemoteState {
|
||||
path, _ := remote.HiddenStatePath()
|
||||
return path
|
||||
}
|
||||
return m.stateOutPath
|
||||
}
|
||||
|
||||
|
@ -132,11 +127,12 @@ func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) {
|
|||
}
|
||||
|
||||
// Store the loaded state
|
||||
state, err := m.loadState()
|
||||
state, statePath, err := State(m.statePath)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
m.state = state
|
||||
m.stateOutPath = statePath
|
||||
|
||||
// Load the root module
|
||||
mod, err := module.NewTreeModule("", copts.Path)
|
||||
|
@ -154,7 +150,7 @@ func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) {
|
|||
}
|
||||
|
||||
opts.Module = mod
|
||||
opts.State = state
|
||||
opts.State = state.State()
|
||||
ctx := terraform.NewContext(opts)
|
||||
return ctx, false, nil
|
||||
}
|
||||
|
@ -175,6 +171,21 @@ func (m *Meta) InputMode() terraform.InputMode {
|
|||
return mode
|
||||
}
|
||||
|
||||
// State returns the state for this meta.
|
||||
func (m *Meta) State() (state.State, error) {
|
||||
if m.state != nil {
|
||||
return m.state, nil
|
||||
}
|
||||
|
||||
state, _, err := State(m.statePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
m.state = state
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// UIInput returns a UIInput object to be used for asking for input.
|
||||
func (m *Meta) UIInput() terraform.UIInput {
|
||||
return &UIInput{
|
||||
|
@ -182,115 +193,14 @@ func (m *Meta) UIInput() terraform.UIInput {
|
|||
}
|
||||
}
|
||||
|
||||
// laodState is used to load the Terraform state. We give precedence
|
||||
// to a remote state if enabled, and then check the normal state path.
|
||||
func (m *Meta) loadState() (*terraform.State, error) {
|
||||
// Check if we remote state is enabled
|
||||
localCache, _, err := remote.ReadLocalState()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading state: %s", err)
|
||||
}
|
||||
|
||||
// Set the state if enabled
|
||||
var state *terraform.State
|
||||
if localCache != nil {
|
||||
// Refresh the state
|
||||
log.Printf("[INFO] Refreshing local state...")
|
||||
changes, err := remote.RefreshState(localCache.Remote)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to refresh state: %v", err)
|
||||
}
|
||||
switch changes {
|
||||
case remote.StateChangeNoop:
|
||||
case remote.StateChangeInit:
|
||||
case remote.StateChangeLocalNewer:
|
||||
case remote.StateChangeUpdateLocal:
|
||||
// Reload the state since we've udpated
|
||||
localCache, _, err = remote.ReadLocalState()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading state: %s", err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("%s", changes)
|
||||
}
|
||||
|
||||
state = localCache
|
||||
m.useRemoteState = true
|
||||
}
|
||||
|
||||
// Load up the state
|
||||
if m.statePath != "" {
|
||||
f, err := os.Open(m.statePath)
|
||||
if err != nil && os.IsNotExist(err) {
|
||||
// If the state file doesn't exist, it is okay, since it
|
||||
// is probably a new infrastructure.
|
||||
err = nil
|
||||
} else if m.useRemoteState && err == nil {
|
||||
err = fmt.Errorf("Remote state enabled, but state file '%s' also present.", m.statePath)
|
||||
f.Close()
|
||||
} else if err == nil {
|
||||
state, err = terraform.ReadState(f)
|
||||
f.Close()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error loading state: %s", err)
|
||||
}
|
||||
}
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// PersistState is used to write out the state, handling backup of
|
||||
// the existing state file and respecting path configurations.
|
||||
func (m *Meta) PersistState(s *terraform.State) error {
|
||||
if m.useRemoteState {
|
||||
return m.persistRemoteState(s)
|
||||
}
|
||||
return m.persistLocalState(s)
|
||||
}
|
||||
|
||||
// persistRemoteState is used to handle persisting a state file
|
||||
// when remote state management is enabled
|
||||
func (m *Meta) persistRemoteState(s *terraform.State) error {
|
||||
log.Printf("[INFO] Persisting state to local cache")
|
||||
if err := remote.PersistState(s); err != nil {
|
||||
if err := m.state.WriteState(s); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Printf("[INFO] Uploading state to remote store")
|
||||
change, err := remote.PushState(s.Remote, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !change.SuccessfulPush() {
|
||||
return fmt.Errorf("Failed to upload state: %s", change)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// persistLocalState is used to handle persisting a state file
|
||||
// when remote state management is disabled.
|
||||
func (m *Meta) persistLocalState(s *terraform.State) error {
|
||||
m.initStatePaths()
|
||||
|
||||
// Create a backup of the state before updating
|
||||
if m.backupPath != "-" {
|
||||
log.Printf("[INFO] Writing backup state to: %s", m.backupPath)
|
||||
if err := remote.CopyFile(m.statePath, m.backupPath); err != nil {
|
||||
return fmt.Errorf("Failed to backup state: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Open the new state file
|
||||
fh, err := os.Create(m.stateOutPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open state file: %v", err)
|
||||
}
|
||||
defer fh.Close()
|
||||
|
||||
// Write out the state
|
||||
if err := terraform.WriteState(s, fh); err != nil {
|
||||
return fmt.Errorf("Failed to encode the state: %v", err)
|
||||
}
|
||||
return nil
|
||||
return m.state.PersistState()
|
||||
}
|
||||
|
||||
// Input returns true if we should ask for input for context.
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/remote"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -182,156 +181,3 @@ func TestMeta_initStatePaths(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeta_persistLocal(t *testing.T) {
|
||||
tmp, cwd := testCwd(t)
|
||||
defer testFixCwd(t, tmp, cwd)
|
||||
|
||||
m := new(Meta)
|
||||
s := terraform.NewState()
|
||||
if err := m.persistLocalState(s); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
exists, err := remote.ExistsFile(m.stateOutPath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatalf("state should exist")
|
||||
}
|
||||
|
||||
// Write again, shoudl backup
|
||||
if err := m.persistLocalState(s); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
exists, err = remote.ExistsFile(m.backupPath)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatalf("backup should exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeta_persistRemote(t *testing.T) {
|
||||
tmp, cwd := testCwd(t)
|
||||
defer testFixCwd(t, tmp, cwd)
|
||||
|
||||
err := remote.EnsureDirectory()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
s := terraform.NewState()
|
||||
conf, srv := testRemoteState(t, s, 200)
|
||||
s.Remote = conf
|
||||
defer srv.Close()
|
||||
|
||||
m := new(Meta)
|
||||
if err := m.persistRemoteState(s); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
local, _, err := remote.ReadLocalState()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if local == nil {
|
||||
t.Fatalf("state should exist")
|
||||
}
|
||||
|
||||
if err := m.persistRemoteState(s); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
backup := remote.LocalDirectory + "/" + remote.BackupHiddenStateFile
|
||||
exists, err := remote.ExistsFile(backup)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
t.Fatalf("backup should exist")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeta_loadState_remote(t *testing.T) {
|
||||
tmp, cwd := testCwd(t)
|
||||
defer testFixCwd(t, tmp, cwd)
|
||||
|
||||
err := remote.EnsureDirectory()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
s := terraform.NewState()
|
||||
s.Serial = 1000
|
||||
conf, srv := testRemoteState(t, s, 200)
|
||||
s.Remote = conf
|
||||
defer srv.Close()
|
||||
|
||||
s.Serial = 500
|
||||
if err := remote.PersistState(s); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
m := new(Meta)
|
||||
s1, err := m.loadState()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if s1.Serial < 1000 {
|
||||
t.Fatalf("Bad: %#v", s1)
|
||||
}
|
||||
|
||||
if !m.useRemoteState {
|
||||
t.Fatalf("should enable remote")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeta_loadState_statePath(t *testing.T) {
|
||||
tmp, cwd := testCwd(t)
|
||||
defer testFixCwd(t, tmp, cwd)
|
||||
|
||||
m := new(Meta)
|
||||
|
||||
s := terraform.NewState()
|
||||
s.Serial = 1000
|
||||
if err := m.persistLocalState(s); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
s1, err := m.loadState()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if s1.Serial < 1000 {
|
||||
t.Fatalf("Bad: %#v", s1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMeta_loadState_conflict(t *testing.T) {
|
||||
tmp, cwd := testCwd(t)
|
||||
defer testFixCwd(t, tmp, cwd)
|
||||
|
||||
err := remote.EnsureDirectory()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
m := new(Meta)
|
||||
|
||||
s := terraform.NewState()
|
||||
if err := remote.PersistState(s); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
if err := m.persistLocalState(s); err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
_, err = m.loadState()
|
||||
if err == nil {
|
||||
t.Fatalf("should error with conflict")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,12 +32,13 @@ func (c *OutputCommand) Run(args []string) int {
|
|||
}
|
||||
name := args[0]
|
||||
|
||||
state, err := c.Meta.loadState()
|
||||
stateStore, err := c.Meta.State()
|
||||
if err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error reading state: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
state := stateStore.State()
|
||||
if len(state.RootModule().Outputs) == 0 {
|
||||
c.Ui.Error(fmt.Sprintf(
|
||||
"The state file has no outputs defined. Define an output\n" +
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
statelib "github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
|
@ -36,7 +37,7 @@ func (c *ShowCommand) Run(args []string) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
var err, planErr, stateErr error
|
||||
var planErr, stateErr error
|
||||
var path string
|
||||
var plan *terraform.Plan
|
||||
var state *terraform.State
|
||||
|
@ -68,12 +69,13 @@ func (c *ShowCommand) Run(args []string) int {
|
|||
|
||||
} else {
|
||||
// We should use the default state if it exists.
|
||||
c.Meta.statePath = DefaultStateFilename
|
||||
state, err = c.Meta.loadState()
|
||||
if err != nil {
|
||||
stateStore := &statelib.LocalState{Path: DefaultStateFilename}
|
||||
if err := stateStore.RefreshState(); err != nil {
|
||||
c.Ui.Error(fmt.Sprintf("Error reading state: %s", err))
|
||||
return 1
|
||||
}
|
||||
|
||||
state = stateStore.State()
|
||||
if state == nil {
|
||||
c.Ui.Output("No state.")
|
||||
return 0
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/hashicorp/errwrap"
|
||||
"github.com/hashicorp/terraform/state"
|
||||
"github.com/hashicorp/terraform/state/remote"
|
||||
)
|
||||
|
||||
// State returns the proper state.State implementation to represent the
|
||||
// current environment.
|
||||
//
|
||||
// localPath is the path to where state would be if stored locally.
|
||||
// dataDir is the path to the local data directory where the remote state
|
||||
// cache would be stored.
|
||||
func State(localPath string) (state.State, string, error) {
|
||||
var result state.State
|
||||
var resultPath string
|
||||
|
||||
// Get the remote state cache path
|
||||
remoteCachePath := filepath.Join(DefaultDataDir, DefaultStateFilename)
|
||||
if _, err := os.Stat(remoteCachePath); err == nil {
|
||||
// We have a remote state, initialize that.
|
||||
result, err = remoteState(remoteCachePath)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
resultPath = remoteCachePath
|
||||
}
|
||||
|
||||
// Do we have a local state?
|
||||
if localPath != "" {
|
||||
local := &state.LocalState{Path: localPath}
|
||||
err := local.RefreshState()
|
||||
if err != nil {
|
||||
isNotExist := false
|
||||
errwrap.Walk(err, func(e error) {
|
||||
if !isNotExist && os.IsNotExist(e) {
|
||||
isNotExist = true
|
||||
}
|
||||
})
|
||||
if isNotExist {
|
||||
err = nil
|
||||
}
|
||||
} else {
|
||||
if result != nil {
|
||||
// We already have a remote state... that is an error.
|
||||
return nil, "", fmt.Errorf(
|
||||
"Remote state found, but state file '%s' also present.",
|
||||
localPath)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, "", errwrap.Wrapf(
|
||||
"Error reading local state: {{err}}", err)
|
||||
}
|
||||
|
||||
result = local
|
||||
resultPath = localPath
|
||||
}
|
||||
|
||||
// Return whatever state we have
|
||||
return result, resultPath, nil
|
||||
}
|
||||
|
||||
func remoteState(path string) (state.State, error) {
|
||||
// First create the local state for the path
|
||||
local := &state.LocalState{Path: path}
|
||||
if err := local.RefreshState(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
localState := local.State()
|
||||
|
||||
// If there is no remote settings, it is an error
|
||||
if localState.Remote == nil {
|
||||
return nil, fmt.Errorf("Remote state cache has no remote info")
|
||||
}
|
||||
|
||||
// Initialize the remote client based on the local state
|
||||
client, err := remote.NewClient(localState.Remote.Type, localState.Remote.Config)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(fmt.Sprintf(
|
||||
"Error initializing remote driver '%s': {{err}}",
|
||||
localState.Remote.Type), err)
|
||||
}
|
||||
|
||||
// Create the remote client
|
||||
durable := &remote.State{Client: client}
|
||||
|
||||
// Create the cached client
|
||||
cache := &state.CacheState{
|
||||
Cache: local,
|
||||
Durable: durable,
|
||||
}
|
||||
|
||||
// Refresh the cache
|
||||
if err := cache.RefreshState(); err != nil {
|
||||
return nil, errwrap.Wrapf(
|
||||
"Error reloading remote state: {{err}}", err)
|
||||
}
|
||||
switch cache.RefreshResult() {
|
||||
case state.CacheRefreshNoop:
|
||||
case state.CacheRefreshInit:
|
||||
case state.CacheRefreshLocalNewer:
|
||||
case state.CacheRefreshUpdateLocal:
|
||||
// Write our local state out to the durable storage to start.
|
||||
if err := cache.WriteState(localState); err != nil {
|
||||
return nil, errwrap.Wrapf("Error preparing remote state: {{err}}", err)
|
||||
}
|
||||
if err := cache.PersistState(); err != nil {
|
||||
return nil, errwrap.Wrapf("Error preparing remote state: {{err}}", err)
|
||||
}
|
||||
default:
|
||||
return nil, errwrap.Wrapf("Error initilizing remote state: {{err}}", err)
|
||||
}
|
||||
|
||||
return cache, nil
|
||||
}
|
|
@ -14,8 +14,10 @@ var Commands map[string]cli.CommandFactory
|
|||
// Ui is the cli.Ui used for communicating to the outside world.
|
||||
var Ui cli.Ui
|
||||
|
||||
const ErrorPrefix = "e:"
|
||||
const OutputPrefix = "o:"
|
||||
const (
|
||||
ErrorPrefix = "e:"
|
||||
OutputPrefix = "o:"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Ui = &cli.PrefixedUi{
|
||||
|
|
|
@ -4,6 +4,14 @@ import (
|
|||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
// State is the collection of all state interfaces.
|
||||
type State interface {
|
||||
StateReader
|
||||
StateWriter
|
||||
StateRefresher
|
||||
StatePersister
|
||||
}
|
||||
|
||||
// StateReader is the interface for things that can return a state. Retrieving
|
||||
// the state here must not error. Loading the state fresh (an operation that
|
||||
// can likely error) should be implemented by RefreshState. If a state hasn't
|
||||
|
|
Loading…
Reference in New Issue